{"version":3,"file":"index.cjs","sources":["../src/parser/DefineResolver.ts","../src/utils/time-v2.ts","../src/parser/ValidationV2.ts","../src/parser/InheritanceV2.ts","../src/parser/ScenarioParserV2.ts","../src/loader/dev/DevPluginRegistry.ts","../src/runtime/PluginContextV3.ts","../src/layout/DefaultConstraints.ts","../src/layout/LayoutEngine.ts","../src/layout/utils/anchors.ts","../src/utils/logger.ts","../src/core/TimelineControllerV2.ts","../src/core/Stage.ts","../src/core/TrackManager.ts","../src/runtime/DomSeparation.ts","../src/core/RendererV2.ts","../src/runtime/StyleApply.ts","../src/assets/AssetManager.ts","../src/controller/MotionTextController.ts","../src/loader/dev/DevPluginConfig.ts","../src/index.ts"],"sourcesContent":["import type {\n  DefineSection,\n  Scenario as ScenarioV2,\n} from '../types/scenario-v2';\n\n/**\n * DefineResolver\n *\n * Define 섹션의 참조를 해석하여 실제 값으로 치환하는 클래스\n * - \"define.keyname\" 형태의 문자열을 감지하여 실제 값으로 변환\n * - 중첩 객체와 배열 순회 지원\n * - 순환 참조 검출 및 에러 처리\n * - parse-time 해석으로 런타임 성능 최적화\n */\nexport class DefineResolver {\n  private readonly defines: DefineSection;\n  private readonly visited: Set<string> = new Set();\n  private readonly resolutionPath: string[] = [];\n\n  constructor(defines: DefineSection = {}) {\n    this.defines = defines;\n  }\n\n  /**\n   * 시나리오 전체를 해석하여 Define 참조를 실제 값으로 치환\n   * @param scenario v2.0 시나리오 객체\n   * @returns Define 참조가 해석된 시나리오 객체\n   */\n  resolveScenario(scenario: ScenarioV2): ScenarioV2 {\n    // 새로운 해석 세션 시작\n    this.visited.clear();\n    this.resolutionPath.length = 0;\n\n    // 시나리오의 define 섹션을 현재 defines와 병합\n    const combinedDefines = { ...this.defines, ...scenario.define };\n\n    // 임시로 새 DefineResolver를 생성하여 병합된 defines 사용\n    const tempResolver = new DefineResolver(combinedDefines);\n\n    // 시나리오 전체를 해석\n    const resolved = tempResolver.resolveObject(scenario) as ScenarioV2;\n\n    return resolved;\n  }\n\n  /**\n   * 객체를 순회하며 Define 참조를 해석\n   * @param obj 해석할 객체\n   * @param keyPath 현재 키 경로 (에러 메시지용)\n   * @returns Define 참조가 해석된 객체\n   */\n  private resolveObject(obj: any, keyPath: string = ''): any {\n    if (obj === null || obj === undefined) {\n      return obj;\n    }\n\n    // 문자열인 경우 Define 참조 확인\n    if (typeof obj === 'string') {\n      return this.resolveDefineReference(obj, keyPath);\n    }\n\n    // 배열인 경우 각 요소 해석\n    if (Array.isArray(obj)) {\n      return obj.map((item, index) =>\n        this.resolveObject(item, `${keyPath}[${index}]`)\n      );\n    }\n\n    // 객체인 경우 각 속성 해석\n    if (typeof obj === 'object') {\n      const resolved: any = {};\n      for (const [key, value] of Object.entries(obj)) {\n        const newKeyPath = keyPath ? `${keyPath}.${key}` : key;\n        resolved[key] = this.resolveObject(value, newKeyPath);\n      }\n      return resolved;\n    }\n\n    // 원시 타입은 그대로 반환\n    return obj;\n  }\n\n  /**\n   * Define 참조 문자열을 실제 값으로 해석 (중첩 경로 지원)\n   * @param value 해석할 문자열 값\n   * @param keyPath 현재 키 경로 (에러 메시지용)\n   * @returns 해석된 값 또는 원본 문자열\n   */\n  private resolveDefineReference(value: string, keyPath: string): any {\n    // \"define.keyname\" 패턴이 아니면 그대로 반환\n    if (!value.startsWith('define.')) {\n      return value;\n    }\n\n    const fullPath = value.substring(7); // \"define.\" 제거\n    const pathParts = fullPath.split('.');\n\n    // 빈 키는 에러\n    if (pathParts.length === 0 || !pathParts[0]) {\n      throw new Error(`Invalid define reference: \"${value}\" at ${keyPath}`);\n    }\n\n    const rootKey = pathParts[0];\n\n    // defines에서 키 찾기\n    if (!(rootKey in this.defines)) {\n      throw new Error(\n        `Undefined define key: \"${rootKey}\" referenced at ${keyPath}`\n      );\n    }\n\n    // 정확한 경로 기반 순환 참조 검출\n    const fullRefPath = `define.${fullPath}`;\n    if (this.resolutionPath.includes(fullRefPath)) {\n      const cyclePath = [...this.resolutionPath, fullRefPath].join(' -> ');\n      throw new Error(`Circular reference detected in define: ${cyclePath}`);\n    }\n\n    // 해석 경로에 추가 (순환 참조 추적용)\n    this.visited.add(rootKey);\n    this.resolutionPath.push(fullRefPath);\n\n    try {\n      // Root 값 해석\n      let resolved = this.resolveObject(\n        this.defines[rootKey],\n        `define.${rootKey}`\n      );\n\n      // 중첩 경로가 있는 경우 깊이 탐색\n      for (let i = 1; i < pathParts.length; i++) {\n        const part = pathParts[i];\n        if (resolved === null || resolved === undefined) {\n          throw new Error(\n            `Cannot access \"${part}\" on ${typeof resolved} at ${keyPath}`\n          );\n        }\n        if (typeof resolved !== 'object') {\n          throw new Error(\n            `Cannot access property \"${part}\" on non-object at ${keyPath}`\n          );\n        }\n        if (!(part in resolved)) {\n          throw new Error(\n            `Property \"${part}\" not found in define at ${keyPath}`\n          );\n        }\n        resolved = resolved[part];\n      }\n\n      return resolved;\n    } finally {\n      // 해석 완료 후 경로에서 제거\n      this.visited.delete(rootKey);\n      this.resolutionPath.pop();\n    }\n  }\n\n  /**\n   * Define 섹션에서 사용 가능한 키 목록 반환\n   * @returns 정의된 키 배열\n   */\n  getAvailableKeys(): string[] {\n    return Object.keys(this.defines);\n  }\n\n  /**\n   * 특정 키의 타입 확인\n   * @param key 확인할 키\n   * @returns 값의 타입\n   */\n  getKeyType(key: string): string | null {\n    if (!(key in this.defines)) {\n      return null;\n    }\n    const value = this.defines[key];\n    if (Array.isArray(value)) {\n      return 'array';\n    }\n    return typeof value;\n  }\n}\n","// Time utilities for v2.0 - Array-based time handling\n// Reference: context/scenario-json-spec-v-2-0.md\n//\n// v1.3 → v2.0 주요 변경사항:\n// - 모든 시간은 [start, end] 배열 형태\n// - displayTime: [start, end] 노드 표시 시간\n// - time_offset: [start, end] 플러그인 오프셋\n// - domLifetime: [start, end] DOM 생존 시간\n\nimport type { TimeRange } from '../types/scenario-v2';\n\n// ============================================================================\n// Core Time Functions\n// ============================================================================\n\n/**\n * 주어진 시간이 시간 구간 내에 있는지 확인\n * @param t - 현재 시간\n * @param timeRange - [start, end] 시간 구간\n * @returns 시간 구간 내에 있으면 true\n */\nexport function isWithinTimeRange(t: number, timeRange: TimeRange): boolean {\n  if (!Array.isArray(timeRange) || timeRange.length !== 2) {\n    return false;\n  }\n\n  const [start, end] = timeRange;\n  if (!Number.isFinite(t) || !Number.isFinite(start) || !Number.isFinite(end)) {\n    return false;\n  }\n\n  return t >= start && t <= end;\n}\n\n/**\n * 시간 구간 내에서의 진행도(0~1) 계산\n * @param currentTime - 현재 시간\n * @param timeRange - [start, end] 시간 구간\n * @returns 0~1 사이의 진행도. 구간 밖이면 clamp됨\n */\nexport function progressInTimeRange(\n  currentTime: number,\n  timeRange: TimeRange\n): number {\n  if (!Array.isArray(timeRange) || timeRange.length !== 2) {\n    return 0;\n  }\n\n  const [start, end] = timeRange;\n  if (\n    !Number.isFinite(currentTime) ||\n    !Number.isFinite(start) ||\n    !Number.isFinite(end)\n  ) {\n    return 0;\n  }\n\n  const duration = end - start;\n  if (duration <= 0) {\n    return 0;\n  }\n\n  const progress = (currentTime - start) / duration;\n  return Math.max(0, Math.min(1, progress));\n}\n\n/**\n * 플러그인 실행 창 계산\n * @param displayTime - 노드의 [start, end] 표시 시간\n * @param timeOffset - 플러그인의 [offsetStart, offsetEnd] 상대 오프셋 (0~1)\n * @returns 플러그인의 절대 실행 시간 구간\n */\nexport function computePluginWindow(\n  displayTime: TimeRange,\n  timeOffset: TimeRange = [0, 1] // 기본값을 [0, 1]로 변경\n): TimeRange {\n  if (!Array.isArray(displayTime) || displayTime.length !== 2) {\n    throw new TypeError('displayTime must be [start, end] array');\n  }\n\n  if (!Array.isArray(timeOffset) || timeOffset.length !== 2) {\n    throw new TypeError('timeOffset must be [start, end] array');\n  }\n\n  const [nodeStart, nodeEnd] = displayTime;\n  const [offsetStart, offsetEnd] = timeOffset;\n\n  if (!Number.isFinite(nodeStart) || !Number.isFinite(nodeEnd)) {\n    throw new TypeError('displayTime values must be finite numbers');\n  }\n\n  if (!Number.isFinite(offsetStart) || !Number.isFinite(offsetEnd)) {\n    throw new TypeError('timeOffset values must be finite numbers');\n  }\n\n  // time_offset는 0~1 상대 비율로 처리\n  const nodeDuration = nodeEnd - nodeStart;\n  const pluginStart = nodeStart + nodeDuration * offsetStart;\n  const pluginEnd = nodeStart + nodeDuration * offsetEnd;\n\n  return [pluginStart, pluginEnd];\n}\n\n// ============================================================================\n// Extended: Percentage/Absolute offset with explicit base_time\n// ============================================================================\n\n/**\n * 문자열 혹은 숫자 오프셋 값을 base_time 기준 절대 시간으로 변환\n * - \"50%\" → baseStart + baseDuration * 0.5 (양 끝 공통: 비율은 항상 baseStart 기준)\n * - 숫자(또는 숫자 문자열)는 경계별로 다르게 해석\n *   - which = 'start' → baseStart + value\n *   - which = 'end'   → baseEnd   + value\n *   이를 통해 [0, 2] 같이 숫자로 끝 경계에 여유 시간을 더하는 확장 표현을 지원\n */\nfunction resolveOffsetToAbsolute(\n  bound: unknown,\n  baseStart: number,\n  baseEnd: number,\n  baseDuration: number,\n  which: 'start' | 'end'\n): number {\n  if (typeof bound === 'string') {\n    const s = bound.trim();\n    if (s.endsWith('%')) {\n      const n = parseFloat(s.slice(0, -1));\n      const pct = Number.isFinite(n) ? n / 100 : 0;\n      // Percentage offsets are applied relative to each boundary:\n      //  - start: baseStart + baseDuration * pct\n      //  - end:   baseEnd   + baseDuration * pct\n      // This allows negative percentages on the end boundary to shorten the window\n      // from the end (e.g., base [2,4], '-20%' → 4 - 0.4 = 3.6).\n      return which === 'start'\n        ? baseStart + baseDuration * pct\n        : baseEnd + baseDuration * pct;\n    }\n    const n = parseFloat(s);\n    if (Number.isFinite(n)) {\n      return (which === 'start' ? baseStart : baseEnd) + n;\n    }\n    return which === 'start' ? baseStart : baseEnd;\n  }\n  if (typeof bound === 'number') {\n    // 절대 초(오프셋), 음수 허용\n    return (which === 'start' ? baseStart : baseEnd) + bound;\n  }\n  // 안전 폴백: 해당 경계 기본\n  return which === 'start' ? baseStart : baseEnd;\n}\n\n/**\n * base_time을 기준으로 time_offset을 해석하여 절대 실행 창을 계산\n * - time_offset 요소는 절대 초(number) 또는 백분율 문자열(\"50%\") 둘 다 허용\n * - base_time이 [start,end]이며, 백분율은 base_time 길이에 대한 비율\n * - 값이 제공되지 않으면 전체(base_time 전체)를 의미하도록 ['0%','100%']를 권장\n */\nexport function computePluginWindowFromBase(\n  baseTime: TimeRange,\n  timeOffset: [unknown, unknown] = ['0%', '100%']\n): TimeRange {\n  if (!Array.isArray(baseTime) || baseTime.length !== 2) {\n    throw new TypeError('baseTime must be [start, end] array');\n  }\n  const [bStart, bEnd] = baseTime;\n  if (!Number.isFinite(bStart) || !Number.isFinite(bEnd)) {\n    throw new TypeError('baseTime values must be finite numbers');\n  }\n  const bDur = bEnd - bStart;\n  const [o0, o1] = Array.isArray(timeOffset) ? timeOffset : ['0%', '100%'];\n  const abs0 = resolveOffsetToAbsolute(o0, bStart, bEnd, bDur, 'start');\n  const abs1 = resolveOffsetToAbsolute(o1, bStart, bEnd, bDur, 'end');\n  return [abs0, abs1];\n}\n\n// ============================================================================\n// Time Range Utilities\n// ============================================================================\n\n/**\n * 시간 구간을 주어진 범위로 제한\n * @param timeRange - [start, end] 시간 구간\n * @param min - 최소값\n * @param max - 최대값\n * @returns 제한된 시간 구간\n */\nexport function clampTimeRange(\n  timeRange: TimeRange,\n  min: number,\n  max: number\n): TimeRange {\n  if (!Array.isArray(timeRange) || timeRange.length !== 2) {\n    return [min, max];\n  }\n\n  const [start, end] = timeRange;\n\n  // Handle NaN values by replacing with bounds\n  const safeStart = Number.isFinite(start) ? start : min;\n  const safeEnd = Number.isFinite(end) ? end : max;\n\n  const clampedStart = Math.max(min, Math.min(max, safeStart));\n  const clampedEnd = Math.max(min, Math.min(max, safeEnd));\n\n  return [clampedStart, clampedEnd];\n}\n\n/**\n * 시간 구간의 지속 시간 계산\n * @param timeRange - [start, end] 시간 구간\n * @returns 지속 시간 (end - start)\n */\nexport function getTimeRangeDuration(timeRange: TimeRange): number {\n  if (!Array.isArray(timeRange) || timeRange.length !== 2) {\n    return 0;\n  }\n\n  const [start, end] = timeRange;\n  if (!Number.isFinite(start) || !Number.isFinite(end)) {\n    return 0;\n  }\n\n  return Math.max(0, end - start);\n}\n\n/**\n * 두 시간 구간이 겹치는지 확인\n * @param range1 - 첫 번째 시간 구간\n * @param range2 - 두 번째 시간 구간\n * @returns 겹치면 true\n */\nexport function timeRangesOverlap(\n  range1: TimeRange,\n  range2: TimeRange\n): boolean {\n  if (!Array.isArray(range1) || !Array.isArray(range2)) {\n    return false;\n  }\n\n  const [start1, end1] = range1;\n  const [start2, end2] = range2;\n\n  if (\n    !Number.isFinite(start1) ||\n    !Number.isFinite(end1) ||\n    !Number.isFinite(start2) ||\n    !Number.isFinite(end2)\n  ) {\n    return false;\n  }\n\n  return start1 <= end2 && start2 <= end1;\n}\n\n/**\n * 시간 구간들의 합집합 계산\n * @param ranges - 시간 구간 배열\n * @returns 전체를 포함하는 시간 구간\n */\nexport function unionTimeRanges(ranges: TimeRange[]): TimeRange | null {\n  if (!Array.isArray(ranges) || ranges.length === 0) {\n    return null;\n  }\n\n  const validRanges = ranges.filter(\n    (range) =>\n      Array.isArray(range) &&\n      range.length === 2 &&\n      Number.isFinite(range[0]) &&\n      Number.isFinite(range[1])\n  );\n\n  if (validRanges.length === 0) {\n    return null;\n  }\n\n  const allStarts = validRanges.map((range) => range[0]);\n  const allEnds = validRanges.map((range) => range[1]);\n\n  return [Math.min(...allStarts), Math.max(...allEnds)];\n}\n\n// ============================================================================\n// Frame Snapping\n// ============================================================================\n\n/**\n * 시간을 프레임 단위로 스냅\n * @param time - 스냅할 시간\n * @param fps - 프레임률\n * @returns 프레임에 맞춰진 시간\n */\nexport function snapToFrame(time: number, fps?: number): number {\n  if (!fps || fps <= 0 || !Number.isFinite(fps) || !Number.isFinite(time)) {\n    return time;\n  }\n  return Math.round(time * fps) / fps;\n}\n\n/**\n * 시간 구간을 프레임 단위로 스냅\n * @param timeRange - [start, end] 시간 구간\n * @param fps - 프레임률\n * @returns 프레임에 맞춰진 시간 구간\n */\nexport function snapTimeRangeToFrame(\n  timeRange: TimeRange,\n  fps?: number\n): TimeRange {\n  if (!Array.isArray(timeRange) || timeRange.length !== 2 || !fps) {\n    return timeRange;\n  }\n\n  const [start, end] = timeRange;\n  return [snapToFrame(start, fps), snapToFrame(end, fps)];\n}\n\n// ============================================================================\n// Percentage-based Time Calculations\n// ============================================================================\n\n/**\n * 부모 시간 구간 기준으로 상대적 시간 구간 계산\n * @param parentRange - 부모의 [start, end] 시간 구간\n * @param relativeRange - 상대적 [startPct, endPct] (0~1)\n * @returns 절대 시간 구간\n */\nexport function computeRelativeTimeRange(\n  parentRange: TimeRange,\n  relativeRange: [number, number]\n): TimeRange {\n  if (!Array.isArray(parentRange) || parentRange.length !== 2) {\n    throw new TypeError('parentRange must be [start, end] array');\n  }\n\n  if (!Array.isArray(relativeRange) || relativeRange.length !== 2) {\n    throw new TypeError('relativeRange must be [startPct, endPct] array');\n  }\n\n  const [parentStart, parentEnd] = parentRange;\n  const [startPct, endPct] = relativeRange;\n\n  if (!Number.isFinite(parentStart) || !Number.isFinite(parentEnd)) {\n    throw new TypeError('parentRange values must be finite numbers');\n  }\n\n  if (startPct < 0 || startPct > 1 || endPct < 0 || endPct > 1) {\n    throw new RangeError('relative percentages must be between 0 and 1');\n  }\n\n  const parentDuration = parentEnd - parentStart;\n  const absoluteStart = parentStart + parentDuration * startPct;\n  const absoluteEnd = parentStart + parentDuration * endPct;\n\n  return [absoluteStart, absoluteEnd];\n}\n\n// ============================================================================\n// Time Validation\n// ============================================================================\n\n/**\n * 시간 구간 배열의 유효성 검사\n * @param timeRange - 검사할 시간 구간\n * @throws TypeError - 유효하지 않은 형식\n * @throws RangeError - start > end인 경우\n */\nexport function validateTimeRange(\n  timeRange: unknown\n): asserts timeRange is TimeRange {\n  if (!Array.isArray(timeRange)) {\n    throw new TypeError('timeRange must be an array');\n  }\n\n  if (timeRange.length !== 2) {\n    throw new TypeError('timeRange must have exactly 2 elements [start, end]');\n  }\n\n  const [start, end] = timeRange;\n\n  if (!Number.isFinite(start)) {\n    throw new TypeError('timeRange start must be a finite number');\n  }\n\n  if (!Number.isFinite(end)) {\n    throw new TypeError('timeRange end must be a finite number');\n  }\n\n  if (start > end) {\n    throw new RangeError(\n      `timeRange start (${start}) must not be greater than end (${end})`\n    );\n  }\n}\n\n/**\n * 시간 구간이 유효한지 확인 (예외 없이)\n * @param timeRange - 검사할 시간 구간\n * @returns 유효하면 true\n */\nexport function isValidTimeRange(timeRange: unknown): timeRange is TimeRange {\n  try {\n    validateTimeRange(timeRange);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n// ============================================================================\n// Legacy Compatibility Helpers\n// ============================================================================\n\n/**\n * v1.3 스타일 absStart/absEnd를 v2.0 displayTime으로 변환\n * @deprecated v2.0 네이티브 사용 권장\n */\nexport function legacyAbsToDisplayTime(\n  absStart?: number,\n  absEnd?: number\n): TimeRange | undefined {\n  if (absStart != null && absEnd != null) {\n    return [absStart, absEnd];\n  }\n  return undefined;\n}\n\n/**\n * v1.3 스타일 relStart/relEnd를 v2.0 time_offset으로 변환\n * @deprecated v2.0 네이티브 사용 권장\n */\nexport function legacyRelToTimeOffset(\n  relStart?: number,\n  relEnd?: number\n): TimeRange | undefined {\n  if (relStart != null || relEnd != null) {\n    return [relStart ?? 0, relEnd ?? 0];\n  }\n  return undefined;\n}\n","// Validation v2.0 - v2.0 시나리오 검증 로직\n// Reference: context/scenario-json-spec-v-2-0.md\n//\n// v2.0 검증 특징:\n// - displayTime, domLifetime, time_offset 배열 검증\n// - [start, end] 형식 및 start <= end 조건\n// - 노드 ID 필수 및 중복 검사\n// - 트랙 참조 유효성\n// - Define 참조 해결 후 검증\n\nimport type { Scenario, Node } from '../types/scenario-v2';\nimport { validateTimeRange } from '../utils/time-v2';\n\nexport class ValidationError extends Error {\n  constructor(\n    message: string,\n    public readonly path?: string\n  ) {\n    super(path ? `[${path}] ${message}` : message);\n    this.name = 'ValidationError';\n  }\n}\n\n/**\n * v2.0 시나리오 전체 검증\n * @param scenario - 검증할 시나리오\n * @throws ValidationError - 검증 실패 시\n */\nexport function validateScenario(scenario: Scenario): void {\n  // 버전 재확인\n  if (scenario.version !== '2.0') {\n    throw new ValidationError(\n      `Invalid version: expected \"2.0\", got \"${scenario.version}\"`\n    );\n  }\n\n  // 필수 필드 검증\n  validateRequiredFields(scenario);\n\n  // 시간 배열 검증\n  validateTimeArrays(scenario);\n\n  // 노드 ID 검증\n  validateNodeIds(scenario);\n\n  // 트랙 참조 검증\n  validateTrackReferences(scenario);\n\n  // 시간 논리 검증 (displayTime vs domLifetime)\n  validateTimeLogic(scenario);\n}\n\n/**\n * 필수 필드 존재 여부 검증\n */\nfunction validateRequiredFields(scenario: Scenario): void {\n  if (!scenario.timebase) {\n    throw new ValidationError('Missing required field: timebase');\n  }\n\n  if (!scenario.stage) {\n    throw new ValidationError('Missing required field: stage');\n  }\n\n  if (!Array.isArray(scenario.tracks)) {\n    throw new ValidationError('tracks must be an array');\n  }\n\n  if (!Array.isArray(scenario.cues)) {\n    throw new ValidationError('cues must be an array');\n  }\n\n  // timebase 검증\n  if (scenario.timebase.unit !== 'seconds') {\n    throw new ValidationError(\n      `timebase.unit must be \"seconds\", got \"${scenario.timebase.unit}\"`\n    );\n  }\n\n  if (scenario.timebase.fps !== undefined) {\n    if (\n      typeof scenario.timebase.fps !== 'number' ||\n      scenario.timebase.fps <= 0\n    ) {\n      throw new ValidationError('timebase.fps must be a positive number');\n    }\n  }\n\n  // stage 검증\n  const validAspects = ['16:9', '9:16', 'auto'];\n  if (!validAspects.includes(scenario.stage.baseAspect)) {\n    throw new ValidationError(\n      `stage.baseAspect must be one of: ${validAspects.join(', ')}`\n    );\n  }\n}\n\n/**\n * 모든 시간 배열 필드 검증\n */\nfunction validateTimeArrays(scenario: Scenario): void {\n  // Cue의 domLifetime 검증\n  for (let i = 0; i < scenario.cues.length; i++) {\n    const cue = scenario.cues[i];\n    const path = `cues[${i}]`;\n\n    if (cue.domLifetime) {\n      try {\n        validateTimeRange(cue.domLifetime);\n      } catch (error) {\n        throw new ValidationError(\n          `${path}.domLifetime: ${error instanceof Error ? error.message : 'Invalid time range'}`\n        );\n      }\n    }\n\n    // 노드 재귀 검증\n    validateNodeTimeArrays(cue.root, `${path}.root`);\n  }\n}\n\n/**\n * 노드 계층 구조 시간 배열 검증 (재귀)\n */\nfunction validateNodeTimeArrays(node: Node, path: string): void {\n  // displayTime 검증\n  if (node.displayTime) {\n    try {\n      validateTimeRange(node.displayTime);\n    } catch (error) {\n      throw new ValidationError(\n        `${path}.displayTime: ${error instanceof Error ? error.message : 'Invalid time range'}`\n      );\n    }\n  }\n\n  // pluginChain의 time_offset 검증\n  if (node.pluginChain) {\n    for (let i = 0; i < node.pluginChain.length; i++) {\n      const plugin = node.pluginChain[i];\n      const pluginPath = `${path}.pluginChain[${i}]`;\n\n      if (typeof plugin !== 'string' && (plugin as any).time_offset) {\n        const off = (plugin as any).time_offset;\n        // 새 규칙: 숫자(초) 또는 백분율 문자열(\"50%\") 허용\n        if (!Array.isArray(off) || off.length !== 2) {\n          throw new ValidationError(\n            `${pluginPath}.time_offset must be [start, end] array`\n          );\n        }\n        for (let k = 0; k < 2; k++) {\n          const v = off[k];\n          const ok =\n            (typeof v === 'number' && Number.isFinite(v)) ||\n            (typeof v === 'string' && /^\\s*-?\\d+(?:\\.\\d+)?%?\\s*$/.test(v));\n          if (!ok) {\n            throw new ValidationError(\n              `${pluginPath}.time_offset[${k}] must be a number (seconds) or percentage string like '50%'`\n            );\n          }\n        }\n      }\n    }\n  }\n\n  // group 노드의 children 재귀 검증\n  if (node.eType === 'group' && node.children) {\n    for (let i = 0; i < node.children.length; i++) {\n      validateNodeTimeArrays(node.children[i], `${path}.children[${i}]`);\n    }\n  }\n}\n\n/**\n * 노드 ID 필수성 및 중복 검사\n */\nfunction validateNodeIds(scenario: Scenario): void {\n  const allNodeIds = new Set<string>();\n  const nodeIdPaths = new Map<string, string>(); // ID -> path 매핑\n\n  for (let i = 0; i < scenario.cues.length; i++) {\n    const cue = scenario.cues[i];\n    const path = `cues[${i}]`;\n\n    // Cue ID 검증\n    if (!cue.id || typeof cue.id !== 'string') {\n      throw new ValidationError(\n        `${path}.id: Cue ID is required and must be a non-empty string`\n      );\n    }\n\n    if (allNodeIds.has(cue.id)) {\n      throw new ValidationError(\n        `Duplicate cue ID: \"${cue.id}\" (conflicts with ${nodeIdPaths.get(cue.id)})`\n      );\n    }\n\n    allNodeIds.add(cue.id);\n    nodeIdPaths.set(cue.id, path);\n\n    // 노드 ID 재귀 검증\n    validateNodeIdRecursive(cue.root, `${path}.root`, allNodeIds, nodeIdPaths);\n  }\n}\n\n/**\n * 노드 ID 재귀 검증\n */\nfunction validateNodeIdRecursive(\n  node: Node,\n  path: string,\n  allIds: Set<string>,\n  idPaths: Map<string, string>\n): void {\n  // 노드 ID 필수\n  if (!node.id || typeof node.id !== 'string') {\n    throw new ValidationError(\n      `${path}.id: Node ID is required and must be a non-empty string`\n    );\n  }\n\n  // 중복 검사\n  if (allIds.has(node.id)) {\n    throw new ValidationError(\n      `Duplicate node ID: \"${node.id}\" (conflicts with ${idPaths.get(node.id)})`\n    );\n  }\n\n  allIds.add(node.id);\n  idPaths.set(node.id, path);\n\n  // group 노드의 children 재귀\n  if (node.eType === 'group' && node.children) {\n    for (let i = 0; i < node.children.length; i++) {\n      validateNodeIdRecursive(\n        node.children[i],\n        `${path}.children[${i}]`,\n        allIds,\n        idPaths\n      );\n    }\n  }\n}\n\n/**\n * 트랙 참조 유효성 검증\n */\nfunction validateTrackReferences(scenario: Scenario): void {\n  const trackIds = new Set(scenario.tracks.map((track) => track.id));\n\n  // 트랙 ID 중복 검사\n  const trackIdCounts = new Map<string, number>();\n  for (const track of scenario.tracks) {\n    trackIdCounts.set(track.id, (trackIdCounts.get(track.id) || 0) + 1);\n  }\n\n  for (const [id, count] of trackIdCounts) {\n    if (count > 1) {\n      throw new ValidationError(`Duplicate track ID: \"${id}\"`);\n    }\n  }\n\n  // Cue의 track 참조 검증\n  for (let i = 0; i < scenario.cues.length; i++) {\n    const cue = scenario.cues[i];\n    const path = `cues[${i}]`;\n\n    if (!cue.track || typeof cue.track !== 'string') {\n      throw new ValidationError(\n        `${path}.track: Track reference is required and must be a string`\n      );\n    }\n\n    if (!trackIds.has(cue.track)) {\n      throw new ValidationError(\n        `${path}.track: Unknown track ID \"${cue.track}\"`\n      );\n    }\n  }\n}\n\n/**\n * 시간 논리 검증 (displayTime과 domLifetime 관계)\n */\nfunction validateTimeLogic(scenario: Scenario): void {\n  for (let i = 0; i < scenario.cues.length; i++) {\n    const cue = scenario.cues[i];\n    const path = `cues[${i}]`;\n\n    if (cue.domLifetime) {\n      const [domStart, domEnd] = cue.domLifetime as [number, number];\n\n      // domLifetime은 모든 노드의 displayTime을 포함해야 함\n      validateDomLifetimeCoversNodes(\n        cue.root,\n        domStart,\n        domEnd,\n        `${path}.root`\n      );\n    }\n  }\n}\n\n/**\n * domLifetime이 모든 노드의 displayTime을 커버하는지 검증 (재귀)\n */\nfunction validateDomLifetimeCoversNodes(\n  node: Node,\n  domStart: number,\n  domEnd: number,\n  path: string\n): void {\n  if (node.displayTime) {\n    const [nodeStart, nodeEnd] = node.displayTime as [number, number];\n\n    // domLifetime이 노드보다 충분히 앞서 시작해야 함 (preload 고려)\n    if (nodeStart < domStart) {\n      console.warn(\n        `Warning: ${path}.displayTime starts (${nodeStart}) before domLifetime (${domStart}). ` +\n          'This may cause late DOM mounting.'\n      );\n    }\n\n    // domLifetime이 노드 종료 후에도 유지되어야 함 (cleanup 고려)\n    if (nodeEnd > domEnd) {\n      console.warn(\n        `Warning: ${path}.displayTime ends (${nodeEnd}) after domLifetime (${domEnd}). ` +\n          'This may cause early DOM unmounting.'\n      );\n    }\n  }\n\n  // group 노드의 children 재귀\n  if (node.eType === 'group' && node.children) {\n    for (let i = 0; i < node.children.length; i++) {\n      validateDomLifetimeCoversNodes(\n        node.children[i],\n        domStart,\n        domEnd,\n        `${path}.children[${i}]`\n      );\n    }\n  }\n}\n\n/**\n * 개발/디버그용 검증 (경고를 에러로 승격)\n * @param scenario - 검증할 시나리오\n * @param strict - 엄격 모드 (경고도 에러로 처리)\n */\nexport function validateScenarioStrict(\n  scenario: Scenario,\n  strict = false\n): void {\n  // 기본 검증\n  validateScenario(scenario);\n\n  if (strict) {\n    // 엄격 모드에서는 console.warn을 에러로 변환\n    const originalWarn = console.warn;\n    console.warn = (message: string) => {\n      throw new ValidationError(`Strict validation failed: ${message}`);\n    };\n\n    try {\n      validateTimeLogic(scenario);\n    } finally {\n      console.warn = originalWarn;\n    }\n  }\n}\n","// Inheritance v2.0 - v2.0 상속 시스템\n// Reference: context/scenario-json-spec-v-2-0.md\n//\n// v2.0 상속 우선순위:\n// 1. 직접 명시된 값 (최우선)\n// 2. Define 참조 값\n// 3. 부모 노드에서 상속\n// 4. 트랙 기본값\n// 5. 시스템 기본값\n\nimport type {\n  Scenario,\n  Node,\n  Track,\n  TimeRange,\n  ResolvedNodeUnion,\n} from '../types/scenario-v2';\nimport type { TextStyle, BoxStyle } from '../types/layout';\n\n// 상속 우선순위 설정\ntype InheritancePriority = 'direct' | 'parent' | 'track' | 'system';\n\n// 필드별 상속 규칙 설정\ninterface FieldInheritanceRule {\n  priority: InheritancePriority[];\n  merge?: boolean; // 병합이 필요한 필드 (예: style)\n  systemDefault?: any; // 시스템 기본값\n}\n\n// 상속 규칙 구성\nconst INHERITANCE_RULES: Record<string, FieldInheritanceRule> = {\n  displayTime: {\n    priority: ['direct', 'parent', 'system'],\n    systemDefault: [-Infinity, Infinity],\n  },\n  // layout 제거 - 각 요소의 고유한 위치/크기이므로 상속하지 않음\n\n  // 선택적 상속 가능한 layout 속성들\n  anchor: {\n    priority: ['direct', 'parent', 'track'], // anchor만 선택적 상속\n    systemDefault: undefined,\n  },\n  safeAreaClamp: {\n    priority: ['direct', 'parent'],\n    systemDefault: undefined,\n  },\n  style: {\n    priority: ['direct', 'parent', 'track'],\n    merge: true, // 텍스트 스타일은 병합\n    systemDefault: undefined,\n  },\n  boxStyle: {\n    priority: ['direct', 'track'], // 부모에서 상속받지 않음, 그룹 노드만 트랙에서 상속\n    merge: true, // 박스 스타일은 병합\n    systemDefault: undefined,\n  },\n  pluginChain: {\n    priority: ['direct'], // 각 노드별 고유 효과\n    systemDefault: undefined,\n  },\n  effectScope: {\n    priority: ['direct', 'parent'],\n    systemDefault: undefined,\n  },\n};\n\n// 기본 상속 규칙 (등록되지 않은 필드용)\nconst DEFAULT_INHERITANCE_RULE: FieldInheritanceRule = {\n  priority: ['direct', 'parent', 'track', 'system'],\n  systemDefault: undefined,\n};\n\n/**\n * v2.0 시나리오 상속 시스템 적용\n * @param scenario - Define이 해결된 시나리오\n * @returns 상속이 적용된 시나리오\n */\nexport function applyInheritance(scenario: Scenario): Scenario {\n  // 트랙별 기본값을 매핑으로 생성\n  const trackDefaults = new Map<string, Track>();\n  for (const track of scenario.tracks) {\n    trackDefaults.set(track.id, track);\n  }\n\n  const debugMode = (globalThis as any).__MTX_DEBUG_MODE__ || false;\n\n  // 각 Cue에 상속 적용\n  const inheritedCues = scenario.cues.map((cue, index) => {\n    const track = trackDefaults.get(cue.track);\n\n    if (debugMode) {\n      // eslint-disable-next-line no-console\n      console.log(`[Inheritance] Processing cue ${index}: ${cue.id}`, {\n        displayTime: (cue as any).displayTime,\n        domLifetime: cue.domLifetime,\n        track: track?.id,\n      });\n    }\n\n    const inheritedCue = {\n      ...cue,\n      // domLifetime 상속 (Cue 레벨에서는 부모가 없으므로 트랙 기본값만)\n      domLifetime: cue.domLifetime || getDefaultDomLifetime(cue.root),\n      // 루트 노드에 상속 적용 (Cue의 displayTime을 컨텍스트로 전달)\n      root: applyNodeInheritance(\n        cue.root,\n        null,\n        track,\n        (cue as any).displayTime\n      ),\n    };\n\n    if (debugMode) {\n      // eslint-disable-next-line no-console\n      console.log(`[Inheritance] Cue ${cue.id} inheritance completed`, {\n        originalDisplayTime: (cue as any).displayTime,\n        inheritedRoot: inheritedCue.root,\n      });\n    }\n\n    return inheritedCue;\n  });\n\n  return {\n    ...scenario,\n    cues: inheritedCues,\n  };\n}\n\n/**\n * 노드 상속 적용 (재귀)\n * @param node - 현재 노드\n * @param parent - 부모 노드 (없으면 null)\n * @param track - 소속 트랙\n * @param cueDisplayTime - Cue의 displayTime (최상위 컨텍스트)\n * @returns 상속이 적용된 노드\n */\nfunction applyNodeInheritance(\n  node: Node,\n  parent: ResolvedNodeUnion | null,\n  track: Track | undefined,\n  cueDisplayTime?: TimeRange\n): ResolvedNodeUnion {\n  const debugMode = (globalThis as any).__MTX_DEBUG_MODE__ || false;\n\n  // 모든 원본 필드를 복사하여 상속된 노드 생성\n  const inheritedNode = { ...node };\n\n  if (debugMode) {\n    // eslint-disable-next-line no-console\n    console.log(`[Inheritance] Processing node: ${node.id || 'unknown'}`, {\n      nodeType: node.eType,\n      hasDisplayTime: !!(node as any).displayTime,\n      parentDisplayTime: parent?.displayTime,\n      cueDisplayTime,\n      fields: Object.keys(node),\n    });\n  }\n\n  // INHERITANCE_RULES에 정의된 필드들에만 상속 로직 적용\n  for (const fieldName of Object.keys(INHERITANCE_RULES)) {\n    const originalValue = inheritedNode[fieldName as keyof Node];\n    inheritedNode[fieldName as keyof Node] = inheritField(\n      fieldName,\n      node,\n      parent,\n      track,\n      cueDisplayTime\n    );\n\n    if (debugMode && fieldName === 'displayTime') {\n      // eslint-disable-next-line no-console\n      console.log(`[Inheritance] ${fieldName} inheritance:`, {\n        nodeId: node.id || 'unknown',\n        original: originalValue,\n        inherited: inheritedNode[fieldName as keyof Node],\n        parentValue: parent?.[fieldName as keyof ResolvedNodeUnion],\n        cueValue: cueDisplayTime,\n      });\n    }\n  }\n\n  // Backward compatibility: automatically split legacy style into style and boxStyle\n  if (node.eType === 'group') {\n    const groupNode = inheritedNode as any;\n    const legacyStyle = groupNode.style;\n\n    if (legacyStyle && !groupNode.boxStyle) {\n      const {\n        backgroundColor,\n        boxBg,\n        border,\n        padding,\n        borderRadius,\n        opacity,\n        ...textStyle\n      } = legacyStyle;\n\n      // If any box-related properties exist, split them\n      if (\n        backgroundColor ||\n        boxBg ||\n        border ||\n        padding ||\n        borderRadius ||\n        opacity !== undefined\n      ) {\n        groupNode.style = textStyle;\n        groupNode.boxStyle = {\n          backgroundColor,\n          boxBg,\n          border,\n          padding,\n          borderRadius,\n          opacity,\n        };\n      }\n    }\n  }\n\n  // 노드 타입별 처리\n  if (node.eType === 'group') {\n    const groupNode = inheritedNode as ResolvedNodeUnion & {\n      eType: 'group';\n      children?: Node[];\n    };\n\n    return {\n      ...groupNode,\n      children: groupNode.children?.map((child) =>\n        applyNodeInheritance(\n          child,\n          inheritedNode as ResolvedNodeUnion,\n          track,\n          cueDisplayTime\n        )\n      ),\n    };\n  }\n\n  // text, image, video 노드는 추가 처리 없음\n  return inheritedNode as ResolvedNodeUnion;\n}\n\n/**\n * 통합 필드 상속 로직\n * @param fieldName - 상속할 필드명\n * @param node - 현재 노드\n * @param parent - 부모 노드\n * @param track - 소속 트랙\n * @param cueDisplayTime - Cue의 displayTime (최상위 컨텍스트)\n * @returns 상속된 값\n */\nfunction inheritField(\n  fieldName: string,\n  node: Node,\n  parent: ResolvedNodeUnion | null,\n  track: Track | undefined,\n  cueDisplayTime?: TimeRange\n): any {\n  const rule = INHERITANCE_RULES[fieldName] || DEFAULT_INHERITANCE_RULE;\n\n  // 특수 필드 처리 (pluginChain 등)\n  if (fieldName === 'pluginChain') {\n    // pluginChain은 각 노드별 고유하므로 상속되지 않음\n    return (node as any)[fieldName] || rule.systemDefault;\n  }\n\n  const values: any[] = [];\n\n  // 우선순위에 따라 값 수집\n  for (const priority of rule.priority) {\n    let value: any = undefined;\n\n    switch (priority) {\n      case 'direct':\n        value = (node as any)[fieldName];\n        break;\n      case 'parent':\n        value = parent ? (parent as any)[fieldName] : undefined;\n        break;\n      case 'track':\n        // 특정 필드에 대한 트랙 기본값 처리\n        if (fieldName === 'style') {\n          value = track?.defaultStyle;\n        } else if (fieldName === 'boxStyle') {\n          // boxStyle은 그룹 노드일 때만 트랙에서 상속\n          if (node.eType === 'group') {\n            value = track?.defaultBoxStyle;\n          }\n        } else if (\n          fieldName === 'anchor' &&\n          track?.defaultConstraints?.anchor\n        ) {\n          value = track.defaultConstraints.anchor;\n        }\n        // 다른 필드들은 향후 확장 가능\n        break;\n      case 'system':\n        // displayTime의 경우 Cue의 displayTime을 우선 사용\n        if (fieldName === 'displayTime' && cueDisplayTime) {\n          value = cueDisplayTime;\n        } else {\n          value = rule.systemDefault;\n        }\n        break;\n    }\n\n    if (value !== undefined) {\n      values.push(value);\n    }\n  }\n\n  // 값이 없으면 시스템 기본값 또는 undefined\n  if (values.length === 0) {\n    return rule.systemDefault;\n  }\n\n  // 병합이 필요한 필드 처리\n  if (rule.merge) {\n    if (fieldName === 'style') {\n      return mergeStyles(...values.filter((v) => v !== undefined));\n    } else if (fieldName === 'boxStyle') {\n      return mergeBoxStyles(...values.filter((v) => v !== undefined));\n    }\n    // layout 병합 제거 - layout은 더이상 상속하지 않음\n  }\n\n  // 일반 필드는 첫 번째 값 (가장 높은 우선순위) 반환\n  return values[0];\n}\n\n/**\n * 스타일 병합 유틸리티\n * @param styles - 병합할 스타일 배열 (우선순위 순: direct → parent → track)\n * @returns 병합된 스타일 (direct가 최우선)\n */\nfunction mergeStyles(...styles: TextStyle[]): TextStyle {\n  const merged: TextStyle = {};\n\n  // 역순으로 병합하여 첫 번째(direct) 값이 우선하도록\n  for (let i = styles.length - 1; i >= 0; i--) {\n    const style = styles[i];\n    if (style && typeof style === 'object') {\n      Object.assign(merged, style);\n    }\n  }\n\n  return merged;\n}\n\n/**\n * BoxStyle 병합 함수\n * 우선순위에 따라 BoxStyle 객체들을 병합 (direct가 최우선)\n * @param styles - 병합할 BoxStyle 배열 (우선순위 순: direct → track)\n * @returns 병합된 BoxStyle\n */\nfunction mergeBoxStyles(...styles: BoxStyle[]): BoxStyle {\n  const merged: BoxStyle = {};\n\n  // 역순으로 병합하여 첫 번째(direct) 값이 우선하도록\n  for (let i = styles.length - 1; i >= 0; i--) {\n    const style = styles[i];\n    if (style && typeof style === 'object') {\n      Object.assign(merged, style);\n    }\n  }\n\n  return merged;\n}\n\n/**\n * 노드 구조를 분석하여 기본 domLifetime 계산\n * @param rootNode - 루트 노드\n * @returns 계산된 domLifetime\n */\nfunction getDefaultDomLifetime(rootNode: Node): TimeRange {\n  const allDisplayTimes: TimeRange[] = [];\n\n  // 노드 트리에서 모든 displayTime 수집\n  collectDisplayTimes(rootNode, allDisplayTimes);\n\n  if (allDisplayTimes.length === 0) {\n    // displayTime이 없으면 넉넉한 기본값\n    return [-1, Infinity];\n  }\n\n  // 모든 displayTime의 범위 계산\n  const starts = allDisplayTimes\n    .map(([start]) => start)\n    .filter((t) => Number.isFinite(t));\n  const ends = allDisplayTimes\n    .map(([, end]) => end)\n    .filter((t) => Number.isFinite(t));\n\n  const minStart = starts.length > 0 ? Math.min(...starts) : 0;\n  const maxEnd = ends.length > 0 ? Math.max(...ends) : 10;\n\n  // preload를 위해 약간 앞서 시작, cleanup을 위해 약간 늦게 종료\n  return [minStart - 0.5, maxEnd + 0.5];\n}\n\n/**\n * 노드 트리에서 모든 displayTime 수집 (재귀)\n */\nfunction collectDisplayTimes(node: Node, collected: TimeRange[]): void {\n  if (node.displayTime) {\n    const timeRange = node.displayTime as TimeRange;\n    if (Number.isFinite(timeRange[0]) && Number.isFinite(timeRange[1])) {\n      collected.push(timeRange);\n    }\n  }\n\n  if (node.eType === 'group' && node.children) {\n    for (const child of node.children) {\n      collectDisplayTimes(child, collected);\n    }\n  }\n}\n\n/**\n * 상속 결과 디버깅 정보 생성\n * @param original - 원본 시나리오\n * @param inherited - 상속 적용된 시나리오\n * @returns 디버깅 정보\n */\nexport function generateInheritanceDebugInfo(\n  original: Scenario,\n  inherited: Scenario\n): {\n  cueCount: number;\n  nodeCount: number;\n  inheritedDisplayTimes: number;\n  inheritedStyles: number;\n  generatedDomLifetimes: number;\n} {\n  let nodeCount = 0;\n  let inheritedDisplayTimes = 0;\n  let inheritedStyles = 0;\n  let generatedDomLifetimes = 0;\n\n  for (let i = 0; i < inherited.cues.length; i++) {\n    const originalCue = original.cues[i];\n    const inheritedCue = inherited.cues[i];\n\n    // domLifetime 생성 여부 확인\n    if (!originalCue.domLifetime && inheritedCue.domLifetime) {\n      generatedDomLifetimes++;\n    }\n\n    // 노드별 상속 분석\n    analyzeNodeInheritance(\n      originalCue.root,\n      inheritedCue.root as ResolvedNodeUnion,\n      {\n        nodeCount: () => nodeCount++,\n        inheritedDisplayTime: () => inheritedDisplayTimes++,\n        inheritedStyle: () => inheritedStyles++,\n      }\n    );\n  }\n\n  return {\n    cueCount: inherited.cues.length,\n    nodeCount,\n    inheritedDisplayTimes,\n    inheritedStyles,\n    generatedDomLifetimes,\n  };\n}\n\n/**\n * 노드별 상속 분석 (재귀)\n */\nfunction analyzeNodeInheritance(\n  original: Node,\n  inherited: ResolvedNodeUnion,\n  counters: {\n    nodeCount: () => void;\n    inheritedDisplayTime: () => void;\n    inheritedStyle: () => void;\n  }\n): void {\n  counters.nodeCount();\n\n  // displayTime 상속 확인\n  if (!original.displayTime && inherited.displayTime) {\n    counters.inheritedDisplayTime();\n  }\n\n  // style 상속 확인 (병합도 상속으로 카운트)\n  if (\n    (!original.style && inherited.style) ||\n    (original.style && inherited.style && original.style !== inherited.style)\n  ) {\n    counters.inheritedStyle();\n  }\n\n  // group 노드 재귀\n  if (original.eType === 'group' && inherited.eType === 'group') {\n    const originalChildren = original.children || [];\n    const inheritedChildren = inherited.children || [];\n\n    for (\n      let i = 0;\n      i < Math.min(originalChildren.length, inheritedChildren.length);\n      i++\n    ) {\n      analyzeNodeInheritance(\n        originalChildren[i],\n        inheritedChildren[i],\n        counters\n      );\n    }\n  }\n}\n","// ScenarioParser v2.0 Native - v2.0 전용 파서\n// Reference: context/scenario-json-spec-v-2-0.md\n//\n// v1.3 → v2.0 주요 변경사항:\n// - v2.0만 지원 (v1.3 완전 거부)\n// - Define 시스템 완전 통합\n// - 시간 배열 검증 ([start, end])\n// - 노드 ID 필수화\n// - 상속 시스템 적용\n\nimport { DefineResolver } from './DefineResolver';\nimport type { Scenario } from '../types/scenario-v2';\nimport { validateScenario } from './ValidationV2';\nimport { applyInheritance } from './InheritanceV2';\n\n/**\n * v2.0 시나리오 전용 파서\n * @param input - JSON 입력 (v2.0만 지원)\n * @returns 검증되고 해결된 ScenarioV2\n * @throws Error - v1.3 시나리오이거나 유효하지 않은 경우\n */\nexport function parseScenario(input: unknown): Scenario {\n  // 기본 타입 검증\n  if (!input || typeof input !== 'object') {\n    throw new Error('Scenario must be a valid JSON object');\n  }\n\n  const raw = input as Record<string, unknown>;\n\n  // v2.0만 지원\n  if (raw.version !== '2.0') {\n    const version = raw.version || 'unknown';\n    throw new Error(\n      `Only v2.0 scenarios are supported, got version \"${version}\". ` +\n        'Use migration tools to convert v1.3 scenarios to v2.0.'\n    );\n  }\n\n  // DefineResolver로 모든 define 참조 해결\n  const resolver = new DefineResolver(raw.define as any);\n  const resolvedRaw = resolver.resolveScenario(raw as any);\n  const resolved = resolvedRaw as unknown as Scenario;\n\n  // v2.0 검증 (시간 배열, ID, 참조 등)\n  validateScenario(resolved);\n\n  // 상속 시스템 적용\n  const inherited = applyInheritance(resolved);\n\n  return inherited;\n}\n\n/**\n * 시나리오가 v2.0인지 확인\n * @param input - 검사할 객체\n * @returns v2.0이면 true\n */\nexport function isScenarioV2(input: unknown): input is Scenario {\n  try {\n    if (!input || typeof input !== 'object') return false;\n    const obj = input as Record<string, unknown>;\n    return obj.version === '2.0';\n  } catch {\n    return false;\n  }\n}\n\n/**\n * 시나리오 버전 감지\n * @param input - JSON 객체\n * @returns 버전 문자열 또는 null\n */\nexport function detectScenarioVersion(input: unknown): string | null {\n  try {\n    if (!input || typeof input !== 'object') return null;\n    const obj = input as Record<string, unknown>;\n    return typeof obj.version === 'string' ? obj.version : null;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * 안전한 시나리오 파싱 (에러 대신 결과 객체 반환)\n * @param input - JSON 입력\n * @returns 성공/실패 결과\n */\nexport function safeParseScenario(\n  input: unknown\n):\n  | { success: true; data: Scenario; version: '2.0' }\n  | { success: false; error: string; version: string | null } {\n  const version = detectScenarioVersion(input);\n\n  try {\n    const scenario = parseScenario(input);\n    return { success: true, data: scenario, version: '2.0' };\n  } catch (error) {\n    const message =\n      error instanceof Error ? error.message : 'Unknown parsing error';\n    return { success: false, error: message, version };\n  }\n}\n\n/**\n * 개발/디버그용 파서 (더 자세한 에러 정보)\n * @param input - JSON 입력\n * @param debugPath - 디버그 경로 (선택적)\n * @returns 파싱된 시나리오\n */\nexport function parseScenarioDebug(\n  input: unknown,\n  debugPath?: string\n): Scenario {\n  const prefix = debugPath ? `[${debugPath}] ` : '';\n\n  try {\n    return parseScenario(input);\n  } catch (error) {\n    if (error instanceof Error) {\n      throw new Error(`${prefix}${error.message}`);\n    }\n    throw new Error(`${prefix}Unknown parsing error`);\n  }\n}\n","export interface RegisteredPlugin {\n  name: string;\n  version: string;\n  module: any; // may include default + named exports (evalChannels)\n  baseUrl: string; // manifest base URL\n  manifest: any;\n}\n\nexport class DevPluginRegistry {\n  private map = new Map<string, RegisteredPlugin>();\n\n  register(p: RegisteredPlugin) {\n    this.map.set(p.name, p);\n    this.map.set(`${p.name}@${p.version}`, p);\n  }\n\n  resolve(name: string): RegisteredPlugin | undefined {\n    return this.map.get(name);\n  }\n\n  has(name: string): boolean {\n    return this.map.has(name);\n  }\n\n  entries(): RegisteredPlugin[] {\n    return Array.from(new Set(this.map.values()));\n  }\n}\n\nexport const devRegistry = new DevPluginRegistry();\n","// Plugin Context v3.0 (Plugin System v3.0)\n// 시나리오 접근, 채널 시스템, 에셋 관리가 통합된 플러그인 컨텍스트\n\nimport type { Scenario } from '../types/scenario-v2';\nimport type { PluginChannelInterface } from './ChannelComposer';\n\nexport interface ScenarioInfo {\n  /**\n   * 시나리오 버전 (읽기 전용)\n   */\n  readonly version: string;\n}\n\nexport interface AssetManager {\n  /**\n   * 에셋 URL 생성 (베이스 URL 기준)\n   */\n  getUrl(path: string): string;\n\n  /**\n   * 폰트 로드 및 등록 (권한 필요)\n   */\n  loadFont?(fontSpec: {\n    family: string;\n    src: string;\n    weight?: string;\n    style?: string;\n  }): Promise<void>;\n\n  /**\n   * 이미지 프리로드 (권한 필요)\n   */\n  preloadImage?(url: string): Promise<HTMLImageElement>;\n\n  /**\n   * 오디오 프리로드 (권한 필요)\n   */\n  preloadAudio?(url: string): Promise<HTMLAudioElement>;\n}\n\nexport interface AudioSystem {\n  /**\n   * 오디오 재생\n   */\n  play(\n    url: string,\n    options?: {\n      volume?: number;\n      loop?: boolean;\n      startTime?: number;\n    }\n  ): Promise<void>;\n\n  /**\n   * 오디오 일시정지\n   */\n  pause(url: string): void;\n\n  /**\n   * 볼륨 설정\n   */\n  setVolume(url: string, volume: number): void;\n\n  /**\n   * 오디오 정지 및 해제\n   */\n  stop(url: string): void;\n}\n\nexport interface PortalSystem {\n  /**\n   * 플러그인 영역 외부로 탈출 (breakout)\n   * 전체 화면 오버레이 등에 사용\n   */\n  breakout(options: {\n    zIndex?: number;\n    className?: string;\n    appendTo?: 'body' | 'stage';\n  }): HTMLElement;\n\n  /**\n   * 탈출 상태 해제\n   */\n  return(): void;\n\n  /**\n   * 현재 탈출 상태인지 확인\n   */\n  readonly isBreakout: boolean;\n}\n\nexport interface RendererInfo {\n  readonly version: string;\n  readonly currentTime: number;\n  readonly duration: number;\n  readonly timeScale: number;\n  readonly fps?: number;\n}\n\nexport interface PluginUtils {\n  /**\n   * 값 보간 (선형 보간 기본)\n   */\n  interpolate(from: any, to: any, progress: number, easing?: string): any;\n\n  /**\n   * 이징 함수들\n   */\n  readonly easing: Record<string, (t: number) => number>;\n\n  /**\n   * 색상 유틸리티\n   */\n  readonly color: {\n    parse(color: string): { r: number; g: number; b: number; a: number } | null;\n    format(rgba: { r: number; g: number; b: number; a: number }): string;\n    interpolate(from: string, to: string, progress: number): string;\n  };\n}\n\n/**\n * Plugin Context v3.0 완전 인터페이스\n */\nexport interface PluginContextV3 {\n  // DOM 접근 (샌드박스)\n  readonly container: HTMLElement; // effectsRoot\n\n  // 시나리오 정보 (읽기 전용)\n  readonly scenario: ScenarioInfo;\n\n  // 에셋 관리\n  readonly assets: AssetManager;\n\n  // 채널 시스템 (권한 필요: style-vars)\n  readonly channels?: PluginChannelInterface;\n\n  // 포털 시스템 (권한 필요: portal-breakout)\n  readonly portal?: PortalSystem;\n\n  // 오디오 시스템 (권한 필요: asset-loading)\n  readonly audio?: AudioSystem;\n\n  // 렌더러 정보\n  readonly renderer: RendererInfo;\n\n  // 유틸리티\n  readonly utils: PluginUtils;\n\n  // 시간 관련 콜백\n  onSeek?: (callback: (progress: number) => void) => void;\n\n  // 외부 라이브러리 (manifest.json의 peer 기준)\n  readonly gsap?: any;\n  readonly lottie?: any;\n  [peerDep: string]: any;\n}\n\n/**\n * 기본 시나리오 정보 구현\n */\nexport class DefaultScenarioInfo implements ScenarioInfo {\n  constructor(private scenario: Scenario) {}\n\n  get version(): string {\n    return this.scenario.version;\n  }\n}\n\n/**\n * 기본 에셋 매니저 구현\n */\nexport class DefaultAssetManager implements AssetManager {\n  constructor(private baseUrl: string) {}\n\n  getUrl(path: string): string {\n    return new URL(path, this.baseUrl).toString();\n  }\n\n  async loadFont(fontSpec: {\n    family: string;\n    src: string;\n    weight?: string;\n    style?: string;\n  }): Promise<void> {\n    const fontFace = new FontFace(\n      fontSpec.family,\n      `url(\"${this.getUrl(fontSpec.src)}\")`,\n      {\n        weight: fontSpec.weight || 'normal',\n        style: fontSpec.style || 'normal',\n      }\n    );\n\n    await fontFace.load();\n    document.fonts.add(fontFace);\n  }\n\n  async preloadImage(url: string): Promise<HTMLImageElement> {\n    return new Promise((resolve, reject) => {\n      const img = new Image();\n      img.onload = () => resolve(img);\n      img.onerror = reject;\n      img.src = this.getUrl(url);\n    });\n  }\n\n  async preloadAudio(url: string): Promise<HTMLAudioElement> {\n    return new Promise((resolve, reject) => {\n      const audio = new Audio();\n      audio.oncanplaythrough = () => resolve(audio);\n      audio.onerror = reject;\n      audio.src = this.getUrl(url);\n      audio.load();\n    });\n  }\n}\n\n/**\n * 기본 유틸리티 구현\n */\nexport const defaultUtils: PluginUtils = {\n  interpolate(from: any, to: any, progress: number, easing = 'linear'): any {\n    const easingFn = this.easing[easing] || this.easing.linear;\n    const t = easingFn(Math.max(0, Math.min(1, progress)));\n\n    // 숫자 보간\n    if (typeof from === 'number' && typeof to === 'number') {\n      return from + (to - from) * t;\n    }\n\n    // 색상 보간\n    if (typeof from === 'string' && typeof to === 'string') {\n      if (\n        from.startsWith('#') ||\n        to.startsWith('#') ||\n        from.startsWith('rgb') ||\n        to.startsWith('rgb')\n      ) {\n        return this.color.interpolate(from, to, t);\n      }\n    }\n\n    // 배열 보간\n    if (Array.isArray(from) && Array.isArray(to)) {\n      return from.map((f, i) =>\n        this.interpolate(f, to[i] || f, progress, easing)\n      );\n    }\n\n    // 객체 보간\n    if (typeof from === 'object' && typeof to === 'object') {\n      const result: any = {};\n      for (const key in from) {\n        result[key] = this.interpolate(\n          from[key],\n          to[key] || from[key],\n          progress,\n          easing\n        );\n      }\n      return result;\n    }\n\n    // 기본: threshold 방식\n    return t < 0.5 ? from : to;\n  },\n\n  easing: {\n    linear: (t: number) => t,\n    easeIn: (t: number) => t * t,\n    easeOut: (t: number) => t * (2 - t),\n    easeInOut: (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n    backOut: (t: number) => {\n      const c1 = 1.70158;\n      const c3 = c1 + 1;\n      return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);\n    },\n  },\n\n  color: {\n    parse(color: string) {\n      // 간단한 색상 파싱 (hex, rgb만 지원)\n      if (color.startsWith('#')) {\n        const hex = color.slice(1);\n        if (hex.length === 3) {\n          return {\n            r: parseInt(hex[0] + hex[0], 16),\n            g: parseInt(hex[1] + hex[1], 16),\n            b: parseInt(hex[2] + hex[2], 16),\n            a: 1,\n          };\n        } else if (hex.length === 6) {\n          return {\n            r: parseInt(hex.slice(0, 2), 16),\n            g: parseInt(hex.slice(2, 4), 16),\n            b: parseInt(hex.slice(4, 6), 16),\n            a: 1,\n          };\n        }\n      }\n\n      const rgbMatch = color.match(\n        /rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?\\)/\n      );\n      if (rgbMatch) {\n        return {\n          r: parseInt(rgbMatch[1], 10),\n          g: parseInt(rgbMatch[2], 10),\n          b: parseInt(rgbMatch[3], 10),\n          a: rgbMatch[4] ? parseFloat(rgbMatch[4]) : 1,\n        };\n      }\n\n      return null;\n    },\n\n    format(rgba: { r: number; g: number; b: number; a: number }): string {\n      if (rgba.a === 1) {\n        return `rgb(${Math.round(rgba.r)}, ${Math.round(rgba.g)}, ${Math.round(rgba.b)})`;\n      } else {\n        return `rgba(${Math.round(rgba.r)}, ${Math.round(rgba.g)}, ${Math.round(rgba.b)}, ${rgba.a})`;\n      }\n    },\n\n    interpolate(from: string, to: string, progress: number): string {\n      const fromColor = this.parse(from);\n      const toColor = this.parse(to);\n\n      if (!fromColor || !toColor) {\n        return progress < 0.5 ? from : to;\n      }\n\n      return this.format({\n        r: fromColor.r + (toColor.r - fromColor.r) * progress,\n        g: fromColor.g + (toColor.g - fromColor.g) * progress,\n        b: fromColor.b + (toColor.b - fromColor.b) * progress,\n        a: fromColor.a + (toColor.a - fromColor.a) * progress,\n      });\n    },\n  },\n};\n\n/**\n * Plugin Context v3.0 팩토리\n * 권한 시스템을 고려하여 선택적으로 기능을 포함\n */\nexport function createPluginContextV3(config: {\n  container: HTMLElement;\n  scenario: Scenario;\n  baseUrl: string;\n  channels?: PluginChannelInterface;\n  portal?: PortalSystem;\n  audio?: AudioSystem;\n  renderer: RendererInfo;\n  peerDeps?: Record<string, any>;\n  onSeek?: (callback: (progress: number) => void) => void;\n}): PluginContextV3 {\n  const scenarioInfo = new DefaultScenarioInfo(config.scenario);\n  const assetManager = new DefaultAssetManager(config.baseUrl);\n\n  return {\n    container: config.container,\n    scenario: scenarioInfo,\n    assets: assetManager,\n    channels: config.channels,\n    portal: config.portal,\n    audio: config.audio,\n    renderer: config.renderer,\n    utils: defaultUtils,\n    onSeek: config.onSeek,\n    ...config.peerDeps,\n  };\n}\n","// Default layout constraints for different track types\n// Flutter-like constraint system implementation\n\nimport type { LayoutConstraints } from '../types/layout';\nimport type { TrackType } from '../types/scenario-v2';\n\n/**\n * Get default layout constraints for a track type\n */\nexport function getDefaultTrackConstraints(\n  trackType: TrackType\n): LayoutConstraints {\n  switch (trackType) {\n    case 'subtitle':\n      return {\n        mode: 'flow',\n        direction: 'vertical',\n        maxWidth: 0.8, // 80% of stage width\n        maxHeight: 0.4, // 40% of stage height\n        minWidth: 0.1,\n        minHeight: 0.05,\n        gap: 0.02, // 2% gap between subtitle lines\n        padding: { x: 0.02, y: 0.015 }, // internal padding\n        anchor: 'bc', // bottom-center default for subtitles\n        constraintMode: 'flexible', // allow some flexibility\n        breakoutEnabled: false, // subtitles shouldn't escape bounds\n        safeArea: { bottom: 0.075, left: 0.05, right: 0.05 }, // safe area for subtitles\n      };\n\n    case 'free':\n      return {\n        mode: 'absolute',\n        direction: 'vertical',\n        maxWidth: 1.0, // full stage width\n        maxHeight: 1.0, // full stage height\n        minWidth: 0,\n        minHeight: 0,\n        gap: 0,\n        padding: { x: 0, y: 0 },\n        anchor: 'cc', // center-center default for free elements\n        constraintMode: 'breakout', // allow breaking constraints\n        breakoutEnabled: true, // free elements can escape via portal\n        safeArea: undefined, // no safe area restrictions\n      };\n\n    default:\n      // Fallback to free track constraints\n      return getDefaultTrackConstraints('free');\n  }\n}\n\n/**\n * Merge parent constraints with child constraints\n * Child constraints override parent constraints where specified\n */\nexport function mergeConstraints(\n  parentConstraints?: LayoutConstraints,\n  childConstraints?: LayoutConstraints\n): LayoutConstraints {\n  if (!parentConstraints && !childConstraints) {\n    return getDefaultTrackConstraints('free');\n  }\n\n  if (!parentConstraints) {\n    return { ...childConstraints! };\n  }\n\n  if (!childConstraints) {\n    return { ...parentConstraints };\n  }\n\n  // Child overrides parent\n  return {\n    mode: childConstraints.mode ?? parentConstraints.mode,\n    direction: childConstraints.direction ?? parentConstraints.direction,\n    maxWidth: childConstraints.maxWidth ?? parentConstraints.maxWidth,\n    maxHeight: childConstraints.maxHeight ?? parentConstraints.maxHeight,\n    minWidth: childConstraints.minWidth ?? parentConstraints.minWidth,\n    minHeight: childConstraints.minHeight ?? parentConstraints.minHeight,\n    gap: childConstraints.gap ?? parentConstraints.gap,\n    padding: childConstraints.padding ?? parentConstraints.padding,\n    anchor: childConstraints.anchor ?? parentConstraints.anchor,\n    constraintMode:\n      childConstraints.constraintMode ?? parentConstraints.constraintMode,\n    breakoutEnabled:\n      childConstraints.breakoutEnabled ?? parentConstraints.breakoutEnabled,\n    safeArea: childConstraints.safeArea ?? parentConstraints.safeArea,\n  };\n}\n\n/**\n * Check if a layout should break out of parent constraints\n */\nexport function shouldBreakout(\n  constraints: LayoutConstraints,\n  hasEffectScope: boolean\n): boolean {\n  return (\n    constraints.constraintMode === 'breakout' ||\n    (!!constraints.breakoutEnabled && hasEffectScope)\n  );\n}\n\n/**\n * Calculate effective constraints considering parent limits\n */\nexport function calculateEffectiveConstraints(\n  parentConstraints: LayoutConstraints,\n  childConstraints: LayoutConstraints\n): LayoutConstraints {\n  const merged = mergeConstraints(parentConstraints, childConstraints);\n\n  // If parent is strict, child cannot exceed parent limits\n  if (parentConstraints.constraintMode === 'strict') {\n    return {\n      ...merged,\n      maxWidth: Math.min(merged.maxWidth ?? 1, parentConstraints.maxWidth ?? 1),\n      maxHeight: Math.min(\n        merged.maxHeight ?? 1,\n        parentConstraints.maxHeight ?? 1\n      ),\n      constraintMode: 'strict', // inherit strict mode\n    };\n  }\n\n  return merged;\n}\n","// Minimal layout helpers for M2.5: apply normalized position to absolute element.\n// Future: handle flow/grid/override/transform/safeArea.\n\nimport type { Layout, Anchor, LayoutConstraints } from '../types/layout';\nimport { anchorTranslate, anchorFraction } from './utils/anchors';\nimport { mergeConstraints, shouldBreakout } from './DefaultConstraints';\n\ntype SafeArea =\n  | { top?: number; bottom?: number; left?: number; right?: number }\n  | undefined;\n\nexport interface ApplyLayoutOptions {\n  stageSafe?: SafeArea;\n  trackSafe?: SafeArea;\n}\n\nconst clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);\nconst maxN = (a?: number, b?: number) =>\n  a == null ? (b ?? 0) : b == null ? a : Math.max(a, b);\n\n// (anchorTranslate and anchorFraction moved to utils/anchors.ts)\n\nexport function applyNormalizedPosition(\n  el: HTMLElement,\n  layout?: Layout,\n  defaultAnchor: Anchor = 'cc',\n  opts: ApplyLayoutOptions = {}\n) {\n  if (!layout || !layout.position) return;\n  let { x, y } = layout.position;\n  if (typeof x !== 'number' || typeof y !== 'number') return;\n\n  // safeAreaClamp: clamp anchor point within effective safe rectangle\n  if (layout.safeAreaClamp) {\n    const ss = opts.stageSafe ?? {};\n    const ts = opts.trackSafe ?? {};\n    const effLeft = maxN(ss.left, ts.left);\n    const effRight = maxN(ss.right, ts.right);\n    const effTop = maxN(ss.top, ts.top);\n    const effBottom = maxN(ss.bottom, ts.bottom);\n    const xmin = clamp01(effLeft ?? 0);\n    const xmax = 1 - clamp01(effRight ?? 0);\n    const ymin = clamp01(effTop ?? 0);\n    const ymax = 1 - clamp01(effBottom ?? 0);\n    // If size is known, clamp so that the whole element stays within safe box\n    const parent = el.parentElement;\n    const pw = parent?.clientWidth ?? 0;\n    const ph = parent?.clientHeight ?? 0;\n    // normalized size if specified; else measure (when connected)\n    let wN = typeof layout.size?.width === 'number' ? layout.size!.width! : 0;\n    let hN = typeof layout.size?.height === 'number' ? layout.size!.height! : 0;\n    if ((!wN || !hN) && el.isConnected && pw > 0 && ph > 0) {\n      const bw = el.offsetWidth;\n      const bh = el.offsetHeight;\n      if (!wN && bw > 0) wN = bw / pw;\n      if (!hN && bh > 0) hN = bh / ph;\n    }\n    const { ax, ay } = anchorFraction(\n      (layout.anchor as Anchor) || defaultAnchor\n    );\n    // Horizontal clamp\n    if (wN > 0) {\n      // Calculate the bounds for the anchor point position\n      const xmin2 = xmin + (1 - ax) * wN; // minimum anchor position (considering element extends away from anchor)\n      const xmax2 = xmax - ax * wN; // maximum anchor position (considering element extends toward anchor)\n      x = Math.min(Math.max(x, xmin2), xmax2);\n    } else {\n      x = Math.min(Math.max(x, xmin), xmax);\n    }\n    // Vertical clamp\n    if (hN > 0) {\n      // For anchor point clamping, we need to ensure the entire element stays within safe bounds\n      // For bottom anchor (ay = 1.0): anchor point is at element bottom, element extends upward\n      // For top anchor (ay = 0.0): anchor point is at element top, element extends downward\n      // For center anchor (ay = 0.5): anchor point is at element center\n\n      // Calculate the bounds for the anchor point position\n      const ymin2 = ymin + (1 - ay) * hN; // minimum anchor position (considering element extends away from anchor)\n      const ymax2 = ymax - ay * hN; // maximum anchor position (considering element extends toward anchor)\n      y = Math.min(Math.max(y, ymin2), ymax2);\n    } else {\n      y = Math.min(Math.max(y, ymin), ymax);\n    }\n  }\n\n  el.style.position = 'absolute';\n  el.style.left = `${Math.round(x * 10000) / 100}%`;\n  el.style.top = `${Math.round(y * 10000) / 100}%`;\n\n  // size (normalized percent) or auto\n  if (layout.size) {\n    if (layout.size.width != null) {\n      el.style.width =\n        typeof layout.size.width === 'number'\n          ? `${layout.size.width * 100}%`\n          : 'auto';\n    }\n    if (layout.size.height != null) {\n      el.style.height =\n        typeof layout.size.height === 'number'\n          ? `${layout.size.height * 100}%`\n          : 'auto';\n    }\n  }\n\n  if (layout.overflow) el.style.overflow = layout.overflow;\n  if (layout.transformOrigin) el.style.transformOrigin = layout.transformOrigin;\n  const anchor = (layout.anchor as Anchor) || defaultAnchor;\n  const { tx, ty } = anchorTranslate(anchor);\n  // compose additional layout.transform (translate/rotate/scale/skew minimal)\n  const parts: string[] = [`translate(${tx}%, ${ty}%)`];\n  const tr = layout.transform;\n  if (tr?.translate) {\n    const dx = tr.translate.x ?? 0;\n    const dy = tr.translate.y ?? 0;\n    if (dx || dy) parts.push(`translate(${dx * 100}%, ${dy * 100}%)`);\n  }\n  if (tr?.scale) {\n    const sx = tr.scale.x ?? 1;\n    const sy = tr.scale.y ?? 1;\n    if (sx !== 1 || sy !== 1) parts.push(`scale(${sx}, ${sy})`);\n  }\n  if (tr?.rotate?.deg != null) {\n    parts.push(`rotate(${tr.rotate.deg}deg)`);\n  }\n  if (tr?.skew) {\n    const xDeg = tr.skew.xDeg ?? 0;\n    const yDeg = tr.skew.yDeg ?? 0;\n    if (xDeg || yDeg) parts.push(`skew(${xDeg}deg, ${yDeg}deg)`);\n  }\n\n  // override support (absolute offset/transform applied after group rules)\n  const ov = layout.override;\n  if (ov?.mode === 'absolute') {\n    if (ov.offset) {\n      const ox = ov.offset.x ?? 0;\n      const oy = ov.offset.y ?? 0;\n      if (ox || oy) parts.push(`translate(${ox * 100}%, ${oy * 100}%)`);\n    }\n    if (ov.transform) {\n      const ot = ov.transform;\n      if (ot.translate) {\n        const dx = ot.translate.x ?? 0;\n        const dy = ot.translate.y ?? 0;\n        if (dx || dy) parts.push(`translate(${dx * 100}%, ${dy * 100}%)`);\n      }\n      if (ot.scale) {\n        const sx = ot.scale.x ?? 1;\n        const sy = ot.scale.y ?? 1;\n        if (sx !== 1 || sy !== 1) parts.push(`scale(${sx}, ${sy})`);\n      }\n      if (ot.rotate?.deg != null) parts.push(`rotate(${ot.rotate.deg}deg)`);\n      if (ot.skew) {\n        const sx = ot.skew.xDeg ?? 0,\n          sy = ot.skew.yDeg ?? 0;\n        if (sx || sy) parts.push(`skew(${sx}deg, ${sy}deg)`);\n      }\n    }\n  }\n  el.style.transform = parts.join(' ');\n}\n\n// New constraints-based layout system\nexport interface LayoutWithConstraintsOptions extends ApplyLayoutOptions {\n  parentConstraints?: LayoutConstraints;\n  hasEffectScope?: boolean;\n}\n\n/**\n * Apply layout with constraints system (Flutter-like)\n * This is the new unified layout function that handles constraints\n */\nexport function applyLayoutWithConstraints(\n  el: HTMLElement,\n  layout?: Layout,\n  constraints?: LayoutConstraints,\n  defaultAnchor: Anchor = 'cc',\n  opts: LayoutWithConstraintsOptions = {}\n) {\n  // Merge provided constraints with parent constraints\n  const effectiveConstraints = constraints\n    ? mergeConstraints(opts.parentConstraints, constraints)\n    : opts.parentConstraints;\n\n  // Check if this element should breakout of parent constraints\n  if (\n    effectiveConstraints &&\n    shouldBreakout(effectiveConstraints, opts.hasEffectScope || false)\n  ) {\n    // TODO: Implement portal/breakout system\n    // For now, fall back to absolute positioning\n    applyNormalizedPosition(el, layout, defaultAnchor, opts);\n    return;\n  }\n\n  // Determine layout mode from constraints or layout\n  const mode = layout?.mode || effectiveConstraints?.mode || 'absolute';\n\n  switch (mode) {\n    case 'flow':\n      applyFlowContainerWithConstraints(\n        el,\n        layout,\n        effectiveConstraints,\n        defaultAnchor,\n        opts\n      );\n      break;\n    case 'grid':\n      applyGridContainerWithConstraints(\n        el,\n        layout,\n        effectiveConstraints,\n        defaultAnchor,\n        opts\n      );\n      break;\n    case 'absolute':\n    default:\n      applyNormalizedPosition(el, layout, defaultAnchor, opts);\n      break;\n  }\n\n  // Group의 자식 레이아웃 적용 (그룹 자체 positioning과 독립적)\n  if (layout?.childrenLayout) {\n    // If wrapping is requested (explicit or implied for horizontal), constrain container max width\n    const cl: any = layout.childrenLayout as any;\n    const wantWrap =\n      (cl?.wrap ?? cl?.direction === 'horizontal') && !!el.parentElement;\n    if (wantWrap) {\n      const pw = el.parentElement.clientWidth || 0;\n      const safe = constraints?.safeArea || {};\n      const safeL = Math.max(0, Number(safe.left || 0));\n      const safeR = Math.max(0, Number(safe.right || 0));\n      // 1) ratio-based limit\n      const widthFactorFromSafe = Math.max(0, 1 - (safeL + safeR));\n      const widthFactorFromConstraint = constraints?.maxWidth ?? 1;\n      const widthFactorFromLayoutRel =\n        typeof cl.maxWidthRel === 'number' ? Math.max(0, cl.maxWidthRel) : 1;\n      const widthFactorFromPercent =\n        typeof cl.maxWidth === 'string' && /%$/.test(cl.maxWidth)\n          ? Math.max(0, Math.min(1, parseFloat(cl.maxWidth) / 100))\n          : 1;\n      const ratioLimit = Math.min(\n        widthFactorFromSafe,\n        widthFactorFromConstraint,\n        widthFactorFromLayoutRel,\n        widthFactorFromPercent\n      );\n      const ratioPx = Math.round(pw * ratioLimit);\n      // 2) absolute px limit\n      const absPx =\n        typeof cl.maxWidth === 'number' && Number.isFinite(cl.maxWidth)\n          ? Math.max(0, Math.round(cl.maxWidth))\n          : Number.POSITIVE_INFINITY;\n      // 3) final limit\n      const mw = Math.min(ratioPx, absPx);\n      el.style.maxWidth = `${mw}px`;\n      // Allow natural height growth for multi-line\n      el.style.width = 'auto';\n    }\n    applyChildrenLayout(el, layout.childrenLayout);\n  }\n}\n\n/**\n * Apply flow layout with constraints\n */\nfunction applyFlowContainerWithConstraints(\n  el: HTMLElement,\n  layout?: Layout,\n  constraints?: LayoutConstraints,\n  defaultAnchor: Anchor = 'cc',\n  opts: ApplyLayoutOptions = {}\n) {\n  // Apply base positioning first\n  applyNormalizedPosition(el, layout, defaultAnchor, opts);\n\n  // Set up flex container\n  el.style.display = 'flex';\n\n  // Direction from constraints or default vertical\n  const direction = constraints?.direction || 'vertical';\n  el.style.flexDirection = direction === 'vertical' ? 'column' : 'row';\n\n  // Anchor-based alignment\n  const anchor = layout?.anchor || constraints?.anchor || defaultAnchor;\n\n  // Cross-axis alignment (수평 정렬)\n  const alignMap: Record<string, string> = {\n    tl: 'flex-start',\n    tc: 'center',\n    tr: 'flex-end',\n    cl: 'flex-start',\n    cc: 'center',\n    cr: 'flex-end',\n    bl: 'flex-start',\n    bc: 'center',\n    br: 'flex-end',\n  };\n  el.style.alignItems = alignMap[anchor] || 'center';\n\n  // Main-axis alignment (수직 정렬 - flow direction 기준)\n  const justifyMap: Record<string, string> = {\n    tl: 'flex-start',\n    tc: 'flex-start',\n    tr: 'flex-start',\n    cl: 'center',\n    cc: 'center',\n    cr: 'center',\n    bl: 'flex-end',\n    bc: 'flex-end',\n    br: 'flex-end',\n  };\n  el.style.justifyContent = justifyMap[anchor] || 'center';\n\n  // Gap from constraints or layout\n  const gap = constraints?.gap || layout?.gapRel || 0;\n  if (gap && el.parentElement) {\n    const ph = el.parentElement.clientHeight || 0;\n    const gapProp = direction === 'vertical' ? 'rowGap' : 'columnGap';\n    el.style[gapProp] = `${Math.round(ph * gap)}px`;\n  }\n\n  // Apply size constraints\n  applyConstraintSizing(el, constraints);\n}\n\n/**\n * Apply grid layout with constraints\n */\nfunction applyGridContainerWithConstraints(\n  el: HTMLElement,\n  layout?: Layout,\n  constraints?: LayoutConstraints,\n  defaultAnchor: Anchor = 'cc',\n  opts: ApplyLayoutOptions = {}\n) {\n  // Apply base positioning first\n  applyNormalizedPosition(el, layout, defaultAnchor, opts);\n\n  // Set up grid container\n  el.style.display = 'grid';\n  (el.style as any).gridTemplateColumns = 'repeat(auto-fit, minmax(0, 1fr))';\n\n  // Gap from constraints\n  const gap = constraints?.gap || layout?.gapRel || 0;\n  if (gap && el.parentElement) {\n    const ph = el.parentElement.clientHeight || 0;\n    const gapPx = Math.round(ph * gap);\n    (el.style as any).rowGap = `${gapPx}px`;\n    (el.style as any).columnGap = `${gapPx}px`;\n  }\n\n  // Anchor-based grid alignment\n  const anchor = layout?.anchor || constraints?.anchor || defaultAnchor;\n  const hmap: Record<string, string> = {\n    tl: 'start',\n    tc: 'center',\n    tr: 'end',\n    cl: 'start',\n    cc: 'center',\n    cr: 'end',\n    bl: 'start',\n    bc: 'center',\n    br: 'end',\n  };\n  (el.style as any).justifyItems = hmap[anchor] || 'center';\n\n  // Apply size constraints\n  applyConstraintSizing(el, constraints);\n}\n\n/**\n * Apply size constraints to element\n */\nfunction applyConstraintSizing(\n  el: HTMLElement,\n  constraints?: LayoutConstraints\n) {\n  if (!constraints) return;\n\n  // Apply max/min constraints\n  if (constraints.maxWidth !== undefined) {\n    el.style.maxWidth = `${constraints.maxWidth * 100}%`;\n  }\n  if (constraints.maxHeight !== undefined) {\n    el.style.maxHeight = `${constraints.maxHeight * 100}%`;\n  }\n  if (constraints.minWidth !== undefined) {\n    el.style.minWidth = `${constraints.minWidth * 100}%`;\n  }\n  if (constraints.minHeight !== undefined) {\n    el.style.minHeight = `${constraints.minHeight * 100}%`;\n  }\n\n  // Apply padding from constraints\n  if (constraints.padding) {\n    const px = constraints.padding.x || 0;\n    const py = constraints.padding.y || 0;\n    if (el.parentElement) {\n      const pw = el.parentElement.clientWidth || 0;\n      const ph = el.parentElement.clientHeight || 0;\n      el.style.padding = `${Math.round(ph * py)}px ${Math.round(pw * px)}px`;\n    }\n  }\n}\n\n// Apply flow container: absolute place the group, then stack children vertically\nexport function applyFlowContainer(\n  el: HTMLElement,\n  layout?: Layout,\n  defaultAnchor: Anchor = 'cc',\n  opts: ApplyLayoutOptions = {}\n) {\n  // Position the container itself like absolute element\n  applyNormalizedPosition(el, layout, defaultAnchor, opts);\n  // Stack children\n  el.style.display = 'flex';\n  el.style.flexDirection = 'column';\n  const anchor = (layout?.anchor as Anchor) || defaultAnchor;\n  // alignItems based on horizontal anchor\n  const alignMap: Record<string, string> = {\n    tl: 'flex-start',\n    tc: 'center',\n    tr: 'flex-end',\n    cl: 'flex-start',\n    cc: 'center',\n    cr: 'flex-end',\n    bl: 'flex-start',\n    bc: 'center',\n    br: 'flex-end',\n  };\n  el.style.alignItems = alignMap[anchor] || 'center';\n  // gapRel to px using container parent height\n  const gapRel = layout?.gapRel ?? 0;\n  if (gapRel && el.parentElement) {\n    const ph = el.parentElement.clientHeight || 0;\n    el.style.rowGap = `${Math.round(ph * gapRel)}px`;\n  } else {\n    el.style.rowGap = '';\n  }\n}\n\n// Apply grid container (1차: 간단 행 우선, auto-fit 2열 기본)\nexport function applyGridContainer(\n  el: HTMLElement,\n  layout?: Layout,\n  defaultAnchor: Anchor = 'cc',\n  opts: ApplyLayoutOptions = {}\n) {\n  applyNormalizedPosition(el, layout, defaultAnchor, opts);\n  el.style.display = 'grid';\n  // 기본 2열 그리드, 필요한 경우 확장 가능\n  (el.style as any).gridTemplateColumns = 'repeat(auto-fit, minmax(0, 1fr))';\n  const gapRel = layout?.gapRel ?? 0;\n  if (gapRel && el.parentElement) {\n    const ph = el.parentElement.clientHeight || 0;\n    const gapPx = Math.round(ph * gapRel);\n    (el.style as any).rowGap = `${gapPx}px`;\n    (el.style as any).columnGap = `${gapPx}px`;\n  } else {\n    (el.style as any).rowGap = '';\n    (el.style as any).columnGap = '';\n  }\n  // anchor에 따라 그리드 정렬\n  const anchor = (layout?.anchor as Anchor) || defaultAnchor;\n  const hmap: Record<string, string> = {\n    tl: 'start',\n    tc: 'center',\n    tr: 'end',\n    cl: 'start',\n    cc: 'center',\n    cr: 'end',\n    bl: 'start',\n    bc: 'center',\n    br: 'end',\n  };\n  (el.style as any).justifyItems = hmap[anchor] || 'center';\n}\n\n/**\n * Apply children layout to element (for group elements)\n * This allows groups to control how their children are arranged independently of their own positioning\n */\nexport function applyChildrenLayout(\n  el: HTMLElement,\n  childrenLayout: NonNullable<Layout['childrenLayout']>\n): void {\n  // Persist layout spec on the element for later refresh after mutations\n  try {\n    (el as any).__mtxChildrenLayout = childrenLayout;\n  } catch {\n    /* noop */\n  }\n  const cw: any = childrenLayout as any;\n  const {\n    mode = 'flow',\n    direction = 'horizontal',\n    gap = 0,\n    align = 'center',\n    justify = 'center',\n  } = childrenLayout;\n  const wrap = cw?.wrap ?? direction === 'horizontal';\n\n  switch (mode) {\n    case 'flow': {\n      // Set up flexbox for children\n      el.style.display = 'flex';\n      el.style.flexDirection = direction === 'horizontal' ? 'row' : 'column';\n      el.style.flexWrap =\n        wrap && direction === 'horizontal' ? 'wrap' : 'nowrap';\n\n      // Alignment\n      const alignItems =\n        align === 'start'\n          ? 'flex-start'\n          : align === 'end'\n            ? 'flex-end'\n            : 'center';\n      const justifyContent =\n        justify === 'start'\n          ? 'flex-start'\n          : justify === 'end'\n            ? 'flex-end'\n            : justify === 'space-between'\n              ? 'space-between'\n              : 'center';\n\n      el.style.alignItems = alignItems;\n      el.style.justifyContent = justifyContent;\n\n      // Gap: apply per-child right margin for robust spacing and proper wrapping\n      if (gap && el.parentElement) {\n        const containerSize =\n          direction === 'horizontal'\n            ? el.parentElement.clientWidth || 0\n            : el.parentElement.clientHeight || 0;\n        const gapPx = Math.round(containerSize * gap);\n        const gapProp = direction === 'horizontal' ? 'columnGap' : 'rowGap';\n        // Clear native gap to prevent double spacing when margins are applied\n        (el.style as any)[gapProp] = '';\n\n        // Additionally, enforce fixed spacing using margins so visual space stays uniform\n        const children = Array.from(el.children) as HTMLElement[];\n        for (let i = 0; i < children.length; i++) {\n          const c = children[i];\n          // Normalize spacing-related properties to avoid inherited CSS affecting layout\n          c.style.padding = '0px';\n          c.style.flex = '0 0 auto';\n          if (direction === 'horizontal') {\n            c.style.marginLeft = '0px';\n            c.style.marginRight = `${gapPx}px`;\n            c.style.marginTop = '';\n          } else {\n            c.style.marginTop = i === 0 ? '0' : `${gapPx}px`;\n            c.style.marginLeft = '';\n            c.style.marginRight = '';\n          }\n        }\n      }\n      break;\n    }\n\n    case 'grid': {\n      el.style.display = 'grid';\n      el.style.gridTemplateColumns = 'repeat(auto-fit, minmax(0, 1fr))';\n\n      if (gap && el.parentElement) {\n        const containerSize = el.parentElement.clientHeight || 0;\n        const gapPx = Math.round(containerSize * gap);\n        (el.style as any).rowGap = `${gapPx}px`;\n        (el.style as any).columnGap = `${gapPx}px`;\n      }\n      break;\n    }\n\n    case 'stack':\n    default:\n      // Stack children on top of each other (default behavior)\n      // No special CSS needed, children will use their own positioning\n      break;\n  }\n}\n","// Anchor utility functions for layout positioning\nimport type { Anchor } from \"../../types/layout\";\n\nexport function anchorTranslate(anchor: Anchor): { tx: number; ty: number } {\n  switch (anchor) {\n    case \"tl\":\n      return { tx: 0, ty: 0 };\n    case \"tc\":\n      return { tx: -50, ty: 0 };\n    case \"tr\":\n      return { tx: -100, ty: 0 };\n    case \"cl\":\n      return { tx: 0, ty: -50 };\n    case \"cc\":\n      return { tx: -50, ty: -50 };\n    case \"cr\":\n      return { tx: -100, ty: -50 };\n    case \"bl\":\n      return { tx: 0, ty: -100 };\n    case \"bc\":\n      return { tx: -50, ty: -100 };\n    case \"br\":\n      return { tx: -100, ty: -100 };\n    default:\n      return { tx: -50, ty: -50 };\n  }\n}\n\nexport function anchorFraction(anchor: Anchor): { ax: number; ay: number } {\n  switch (anchor) {\n    case 'tl': return { ax: 0, ay: 0 };\n    case 'tc': return { ax: 0.5, ay: 0 };\n    case 'tr': return { ax: 1, ay: 0 };\n    case 'cl': return { ax: 0, ay: 0.5 };\n    case 'cc': return { ax: 0.5, ay: 0.5 };\n    case 'cr': return { ax: 1, ay: 0.5 };\n    case 'bl': return { ax: 0, ay: 1 };\n    case 'bc': return { ax: 0.5, ay: 1 };\n    case 'br': return { ax: 1, ay: 1 };\n    default:   return { ax: 0.5, ay: 0.5 };\n  }\n}","/* eslint-disable no-console */\n// Simple shared logger utility with scoped instances and debug gating\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nlet globalDebugEnabled = false;\n\nexport function setGlobalDebug(enabled: boolean): void {\n  globalDebugEnabled = enabled;\n}\n\nexport interface Logger {\n  debug: (...args: unknown[]) => void;\n  info: (...args: unknown[]) => void;\n  warn: (...args: unknown[]) => void;\n  error: (...args: unknown[]) => void;\n  setEnabled: (enabled: boolean | null) => void;\n  enable: () => void;\n  disable: () => void;\n}\n\nexport function createLogger(\n  scope: string,\n  enabled: boolean | null = null\n): Logger {\n  let localEnabled: boolean | null = enabled; // null means follow global\n\n  const prefix = (level: LogLevel) =>\n    `[${scope}]` + (level === 'debug' ? '' : ` ${level.toUpperCase()}`);\n  const isEnabled = (level: LogLevel) => {\n    if (level === 'warn' || level === 'error') return true; // always log warnings/errors\n    const flag = localEnabled === null ? globalDebugEnabled : localEnabled;\n    return !!flag;\n  };\n\n  return {\n    debug: (...args: unknown[]) => {\n      if (isEnabled('debug')) console.log(prefix('debug'), ...args);\n    },\n    info: (...args: unknown[]) => {\n      if (isEnabled('info')) console.info(prefix('info'), ...args);\n    },\n    warn: (...args: unknown[]) => {\n      if (isEnabled('warn')) console.warn(prefix('warn'), ...args);\n    },\n    error: (...args: unknown[]) => {\n      if (isEnabled('error')) console.error(prefix('error'), ...args);\n    },\n    setEnabled: (enabledVal: boolean | null) => {\n      localEnabled = enabledVal;\n    },\n    enable: () => {\n      localEnabled = true;\n    },\n    disable: () => {\n      localEnabled = false;\n    },\n  };\n}\n","// TimelineController v2.0 - v2.0 타임라인 컨트롤러\n// Reference: context/scenario-json-spec-v-2-0.md\n//\n// v2.0 특징:\n// - requestVideoFrameCallback 기반 고정밀 동기화\n// - TimeRange 배열 처리\n// - 프레임 스냅 지원\n// - 배속 재생 안정성\n\nimport { createLogger } from '../utils/logger';\n\nexport interface TimelineOptions {\n  autoStart?: boolean; // 비디오 재생 시 자동 시작\n  snapToFrame?: boolean; // 프레임 단위 스냅\n  fps?: number; // 프레임률 (스냅 사용시)\n  syncTolerance?: number; // 동기화 허용 오차 (초)\n  debugMode?: boolean; // 디버그 모드\n}\n\nexport interface TimelineState {\n  isPlaying: boolean;\n  currentTime: number;\n  playbackRate: number;\n  frameCount: number;\n  lastVideoTime: number;\n  driftCorrection: number;\n}\n\nexport type TickCallback = (currentTime: number, frameInfo?: FrameInfo) => void;\n\nexport interface FrameInfo {\n  frameNumber: number;\n  timestamp: number;\n  videoTime: number;\n  drift: number;\n  frameRate: number;\n}\n\n/**\n * v2.0 타임라인 컨트롤러\n * 비디오 미디어와 동기화된 고정밀 타임라인 관리\n */\nexport class TimelineControllerV2 {\n  private media: HTMLVideoElement | null = null;\n  private options: Required<TimelineOptions>;\n  private state: TimelineState;\n  private tickCallbacks = new Set<TickCallback>();\n  private rafId: number | null = null;\n  private videoFrameId: number | null = null;\n  private startTime = 0;\n  private pausedTime = 0;\n  private logger = createLogger('TimelineV2', null);\n\n  // requestVideoFrameCallback 지원 감지\n  private supportsVideoFrame =\n    'requestVideoFrameCallback' in HTMLVideoElement.prototype;\n\n  constructor(options: TimelineOptions = {}) {\n    this.options = {\n      autoStart: true,\n      snapToFrame: false,\n      fps: 30,\n      syncTolerance: 0.1,\n      debugMode: false,\n      ...options,\n    };\n\n    this.state = {\n      isPlaying: false,\n      currentTime: 0,\n      playbackRate: 1,\n      frameCount: 0,\n      lastVideoTime: 0,\n      driftCorrection: 0,\n    };\n\n    this.logger.setEnabled(this.options.debugMode);\n    this.logger.debug('Initialized with options:', this.options);\n    this.logger.debug(\n      'requestVideoFrameCallback supported:',\n      this.supportsVideoFrame\n    );\n  }\n\n  /**\n   * 비디오 미디어 연결\n   * @param video - 동기화할 비디오 요소\n   */\n  attachMedia(video: HTMLVideoElement): void {\n    this.detachMedia(); // 기존 연결 해제\n\n    this.media = video;\n    this.setupMediaListeners();\n\n    // 비디오가 이미 재생 중이면 자동 시작\n    if (this.options.autoStart && !video.paused) {\n      this.ensurePlaying();\n    }\n\n    this.state.currentTime = video.currentTime;\n    this.state.playbackRate = video.playbackRate;\n\n    if (this.options.debugMode) {\n      this.logger.debug('Media attached:', {\n        duration: video.duration,\n        currentTime: video.currentTime,\n        playbackRate: video.playbackRate,\n      });\n    }\n  }\n\n  /**\n   * 비디오 미디어 연결 해제\n   */\n  detachMedia(): void {\n    if (!this.media) return;\n\n    this.stop();\n    this.removeMediaListeners();\n    this.media = null;\n\n    this.logger.debug('Media detached');\n  }\n\n  /**\n   * 타임라인 재생 시작\n   */\n  play(): void {\n    if (this.state.isPlaying) return;\n\n    this.state.isPlaying = true;\n    this.startTime = performance.now() - (this.pausedTime || 0);\n\n    if (this.supportsVideoFrame && this.media) {\n      this.startVideoFrameLoop();\n    } else {\n      this.startRafLoop();\n    }\n\n    if (this.options.debugMode) {\n      console.log('[TimelineV2] Play started');\n    }\n  }\n\n  /**\n   * 타임라인 일시정지\n   */\n  pause(): void {\n    if (!this.state.isPlaying) return;\n\n    this.state.isPlaying = false;\n    this.pausedTime = performance.now() - this.startTime;\n\n    this.stopFrameLoop();\n\n    if (this.options.debugMode) {\n      console.log('[TimelineV2] Paused at:', this.state.currentTime);\n    }\n  }\n\n  /**\n   * 타임라인 정지 (처음으로 되돌림)\n   */\n  stop(): void {\n    this.pause();\n    this.state.currentTime = 0;\n    this.state.frameCount = 0;\n    this.pausedTime = 0;\n    this.state.driftCorrection = 0;\n\n    if (this.options.debugMode) {\n      console.log('[TimelineV2] Stopped');\n    }\n  }\n\n  /**\n   * 특정 시간으로 이동\n   * @param time - 이동할 시간 (초)\n   */\n  seek(time: number): void {\n    if (!Number.isFinite(time) || time < 0) return;\n\n    this.state.currentTime = time;\n    this.pausedTime = 0;\n    this.startTime = performance.now();\n\n    // 비디오도 함께 이동\n    if (this.media && Math.abs(this.media.currentTime - time) > 0.1) {\n      this.media.currentTime = time;\n    }\n\n    this.notifyTick();\n\n    if (this.options.debugMode) {\n      console.log('[TimelineV2] Seeked to:', time);\n    }\n  }\n\n  /**\n   * 재생 속도 설정\n   * @param rate - 재생 속도 (1.0 = 정상)\n   */\n  setRate(rate: number): void {\n    if (!Number.isFinite(rate) || rate <= 0) return;\n\n    this.state.playbackRate = rate;\n\n    // 타임라인 시간 조정\n    const now = performance.now();\n    this.pausedTime = (now - this.startTime) * this.state.playbackRate;\n    this.startTime = now;\n\n    if (this.options.debugMode) {\n      console.log('[TimelineV2] Playback rate set to:', rate);\n    }\n  }\n\n  /**\n   * 틱 콜백 등록\n   * @param callback - 매 프레임마다 호출될 콜백\n   * @returns 등록 해제 함수\n   */\n  onTick(callback: TickCallback): () => void {\n    this.tickCallbacks.add(callback);\n    return () => this.tickCallbacks.delete(callback);\n  }\n\n  /**\n   * 비디오가 재생 중이면 타임라인도 시작\n   */\n  ensurePlaying(): void {\n    if (this.media && !this.media.paused && !this.state.isPlaying) {\n      this.play();\n    }\n  }\n\n  /**\n   * 현재 상태 반환\n   */\n  getState(): Readonly<TimelineState> {\n    return { ...this.state };\n  }\n\n  /**\n   * 비디오 프레임 기반 루프 시작\n   */\n  private startVideoFrameLoop(): void {\n    if (!this.media || !this.supportsVideoFrame) return;\n\n    const videoFrameCallback = (now: number, metadata: any) => {\n      if (!this.state.isPlaying) return;\n\n      const videoTime = metadata.mediaTime || this.media!.currentTime;\n      this.updateTimeFromVideo(videoTime);\n\n      const frameInfo: FrameInfo = {\n        frameNumber: this.state.frameCount++,\n        timestamp: now,\n        videoTime,\n        drift: this.state.driftCorrection,\n        frameRate: this.calculateFrameRate(now),\n      };\n\n      this.notifyTick(frameInfo);\n\n      // 다음 프레임 요청\n      this.videoFrameId = (this.media as any).requestVideoFrameCallback(\n        videoFrameCallback\n      );\n    };\n\n    this.videoFrameId = (this.media as any).requestVideoFrameCallback(\n      videoFrameCallback\n    );\n  }\n\n  /**\n   * requestAnimationFrame 기반 루프 시작\n   */\n  private startRafLoop(): void {\n    const rafCallback = (now: number) => {\n      if (!this.state.isPlaying) return;\n\n      this.updateTimeFromPerformance(now);\n\n      const frameInfo: FrameInfo = {\n        frameNumber: this.state.frameCount++,\n        timestamp: now,\n        videoTime: this.media?.currentTime || 0,\n        drift: this.state.driftCorrection,\n        frameRate: this.calculateFrameRate(now),\n      };\n\n      this.notifyTick(frameInfo);\n\n      // 다음 프레임 요청\n      this.rafId = requestAnimationFrame(rafCallback);\n    };\n\n    this.rafId = requestAnimationFrame(rafCallback);\n  }\n\n  /**\n   * 프레임 루프 중지\n   */\n  private stopFrameLoop(): void {\n    if (this.videoFrameId && this.media && this.supportsVideoFrame) {\n      (this.media as any).cancelVideoFrameCallback(this.videoFrameId);\n      this.videoFrameId = null;\n    }\n\n    if (this.rafId) {\n      cancelAnimationFrame(this.rafId);\n      this.rafId = null;\n    }\n  }\n\n  /**\n   * 비디오 시간으로부터 타임라인 시간 업데이트\n   */\n  private updateTimeFromVideo(videoTime: number): void {\n    const drift = Math.abs(this.state.currentTime - videoTime);\n\n    if (drift > this.options.syncTolerance) {\n      // 드리프트가 허용 오차를 초과하면 비디오 시간으로 동기화\n      this.state.currentTime = videoTime;\n      this.state.driftCorrection = drift;\n\n      if (this.options.debugMode) {\n        console.warn(\n          `[TimelineV2] Sync correction: drift=${drift.toFixed(3)}s`\n        );\n      }\n    } else {\n      this.state.currentTime = videoTime;\n      this.state.driftCorrection = drift;\n    }\n\n    this.state.lastVideoTime = videoTime;\n  }\n\n  /**\n   * performance.now()로부터 타임라인 시간 업데이트\n   */\n  private updateTimeFromPerformance(now: number): void {\n    const elapsed = ((now - this.startTime) / 1000) * this.state.playbackRate;\n    this.state.currentTime = elapsed;\n\n    // 비디오와 동기화 확인\n    if (this.media && !this.media.paused) {\n      const videoTime = this.media.currentTime;\n      const drift = Math.abs(this.state.currentTime - videoTime);\n\n      if (drift > this.options.syncTolerance) {\n        this.state.currentTime = videoTime;\n        this.state.driftCorrection = drift;\n      }\n    }\n  }\n\n  /**\n   * 프레임률 계산\n   */\n  private calculateFrameRate(_now: number): number {\n    // 간단한 이동 평균으로 프레임률 계산\n    // 실제 구현에서는 더 정교한 계산이 필요할 수 있음\n    return this.options.fps || 60;\n  }\n\n  /**\n   * 틱 콜백들에게 알림\n   */\n  private notifyTick(frameInfo?: FrameInfo): void {\n    let currentTime = this.state.currentTime;\n\n    // 프레임 스냅 적용\n    if (this.options.snapToFrame) {\n      currentTime = this.snapToFrame(currentTime);\n    }\n\n    for (const callback of this.tickCallbacks) {\n      try {\n        callback(currentTime, frameInfo);\n      } catch (error) {\n        console.error('[TimelineV2] Tick callback error:', error);\n      }\n    }\n  }\n\n  /**\n   * 프레임 단위로 시간 스냅\n   */\n  private snapToFrame(time: number): number {\n    if (!this.options.fps) return time;\n    return Math.round(time * this.options.fps) / this.options.fps;\n  }\n\n  /**\n   * 비디오 이벤트 리스너 설정\n   */\n  private setupMediaListeners(): void {\n    if (!this.media) return;\n\n    this.media.addEventListener('play', this.onVideoPlay);\n    this.media.addEventListener('pause', this.onVideoPause);\n    this.media.addEventListener('seeked', this.onVideoSeeked);\n    this.media.addEventListener('ratechange', this.onVideoRateChange);\n  }\n\n  /**\n   * 비디오 이벤트 리스너 제거\n   */\n  private removeMediaListeners(): void {\n    if (!this.media) return;\n\n    this.media.removeEventListener('play', this.onVideoPlay);\n    this.media.removeEventListener('pause', this.onVideoPause);\n    this.media.removeEventListener('seeked', this.onVideoSeeked);\n    this.media.removeEventListener('ratechange', this.onVideoRateChange);\n  }\n\n  /**\n   * 비디오 이벤트 핸들러들\n   */\n  private onVideoPlay = () => {\n    if (this.options.autoStart) {\n      this.play();\n    }\n  };\n\n  private onVideoPause = () => {\n    this.pause();\n  };\n\n  private onVideoSeeked = () => {\n    if (this.media) {\n      this.seek(this.media.currentTime);\n    }\n  };\n\n  private onVideoRateChange = () => {\n    if (this.media) {\n      this.setRate(this.media.playbackRate);\n    }\n  };\n\n  /**\n   * 정리\n   */\n  dispose(): void {\n    this.detachMedia();\n    this.tickCallbacks.clear();\n\n    if (this.options.debugMode) {\n      console.log('[TimelineV2] Disposed');\n    }\n  }\n}\n","import type { Scenario } from '../types/scenario-v2';\n\nexport interface ContentRect {\n  left: number;\n  top: number;\n  width: number;\n  height: number;\n}\n\n// Pure function for content rect calculation (testable)\nexport function computeContentRect(\n  containerWidth: number,\n  containerHeight: number,\n  videoAspect: number\n): ContentRect {\n  const ca = containerWidth / containerHeight;\n  let width = containerWidth,\n    height = containerHeight,\n    left = 0,\n    top = 0;\n\n  if (ca > videoAspect) {\n    height = containerHeight;\n    width = Math.round(containerHeight * videoAspect);\n    left = Math.round((containerWidth - width) / 2);\n    top = 0;\n  } else {\n    width = containerWidth;\n    height = Math.round(containerWidth / videoAspect);\n    left = 0;\n    top = Math.round((containerHeight - height) / 2);\n  }\n\n  return { left, top, width, height };\n}\n\nexport class Stage {\n  private container: HTMLElement | null = null;\n  private media: HTMLVideoElement | null = null;\n  private scenario: Scenario | null = null;\n  private ro: ResizeObserver | null = null;\n  private onLoadedMetaBound: (() => void) | null = null;\n  private onFullscreenBound: (() => void) | null = null;\n  private _updateTimer: number | null = null;\n  private _lastBox: string | null = null;\n  private _boundsChangeListeners: ((_rect: ContentRect) => void)[] = [];\n  private _boundParent: HTMLElement | null = null;\n  private _boundMedia: HTMLVideoElement | null = null;\n\n  setContainer(container: HTMLElement) {\n    this.container = container;\n    this.installOverlayBinding();\n  }\n\n  setMedia(media: HTMLVideoElement) {\n    this.media = media;\n    this.installOverlayBinding();\n  }\n\n  setScenario(scenario: Scenario) {\n    this.scenario = scenario;\n  }\n\n  onBoundsChange(listener: (_rect: ContentRect) => void): () => void {\n    this._boundsChangeListeners.push(listener);\n    return () => {\n      const idx = this._boundsChangeListeners.indexOf(listener);\n      if (idx >= 0) this._boundsChangeListeners.splice(idx, 1);\n    };\n  }\n\n  getContentRect(): ContentRect | null {\n    if (!this.container?.parentElement) return null;\n    const parent = this.container.parentElement;\n    const cw = parent.clientWidth;\n    const ch = parent.clientHeight;\n    if (!cw || !ch) return null;\n\n    let va: number | null = null;\n    if (this.media && this.media.videoWidth && this.media.videoHeight) {\n      va = this.media.videoWidth / this.media.videoHeight;\n    } else {\n      va = this.parseAspectFromStage() ?? 16 / 9;\n    }\n\n    return computeContentRect(cw, ch, va);\n  }\n\n  dispose() {\n    // 방어적 타이머 정리\n    if (this._updateTimer != null) {\n      clearTimeout(this._updateTimer);\n      this._updateTimer = null;\n    }\n    this.teardownOverlayBinding();\n    this._boundsChangeListeners = [];\n    this.container = null;\n    this.media = null;\n    this.scenario = null;\n  }\n\n  private installOverlayBinding() {\n    if (!('ResizeObserver' in window)) return;\n    const parent = this.container?.parentElement;\n    if (!parent) return;\n\n    // Prevent duplicate binding - only rebind if target changed\n    if (parent === this._boundParent && this.media === this._boundMedia) {\n      return;\n    }\n\n    // Clean up previous bindings completely\n    this.teardownOverlayBinding();\n\n    // Track new binding targets\n    this._boundParent = parent;\n    this._boundMedia = this.media;\n\n    // Set up new bindings\n    // Mark overlay container as stage for plugin runtime sizing\n    // Be defensive: tests may pass a minimal mock without classList\n    if (this.container && (this.container as any).classList?.add) {\n      (this.container as any).classList.add('stage');\n    }\n    this.ro = new ResizeObserver(() => this.scheduleBoundsUpdate());\n    this.ro.observe(parent);\n\n    if (this.media) {\n      this.onLoadedMetaBound = () => this.scheduleBoundsUpdate();\n      this.media.addEventListener('loadedmetadata', this.onLoadedMetaBound);\n    }\n\n    this.onFullscreenBound = () => this.scheduleBoundsUpdate();\n    document.addEventListener('fullscreenchange', this.onFullscreenBound);\n\n    this.updateBounds();\n  }\n\n  private teardownOverlayBinding() {\n    // 타이머 정리\n    if (this._updateTimer != null) {\n      clearTimeout(this._updateTimer);\n      this._updateTimer = null;\n    }\n    if (this.ro) {\n      this.ro.disconnect();\n      this.ro = null;\n    }\n    if (this._boundMedia && this.onLoadedMetaBound) {\n      this._boundMedia.removeEventListener(\n        'loadedmetadata',\n        this.onLoadedMetaBound\n      );\n    }\n    this.onLoadedMetaBound = null;\n    if (this.onFullscreenBound) {\n      document.removeEventListener('fullscreenchange', this.onFullscreenBound);\n    }\n    this.onFullscreenBound = null;\n\n    // Reset tracking\n    this._boundParent = null;\n    this._boundMedia = null;\n    // Remove stage marker class (guard for mocked containers without classList)\n    if (this.container && (this.container as any).classList?.remove) {\n      (this.container as any).classList.remove('stage');\n    }\n  }\n\n  private scheduleBoundsUpdate() {\n    if (this._updateTimer != null) return;\n    const throttleMs = this.scenario?.behavior?.resizeThrottleMs ?? 80;\n    this._updateTimer = window.setTimeout(() => {\n      this._updateTimer = null;\n      this.updateBounds();\n    }, throttleMs);\n  }\n\n  private updateBounds() {\n    const rect = this.getContentRect();\n    if (!rect) return;\n\n    const box = `${rect.left},${rect.top},${rect.width},${rect.height}`;\n    if (this._lastBox === box) return;\n    this._lastBox = box;\n\n    if (this.container) {\n      const s = this.container.style;\n      s.position = 'absolute';\n      s.pointerEvents = 'none';\n      s.left = `${rect.left}px`;\n      s.top = `${rect.top}px`;\n      s.width = `${rect.width}px`;\n      s.height = `${rect.height}px`;\n    }\n\n    this._boundsChangeListeners.forEach((listener) => listener(rect));\n  }\n\n  private parseAspectFromStage(): number | null {\n    const a = this.scenario?.stage?.baseAspect;\n    if (!a || a === 'auto') return null;\n    const m = String(a).match(/^(\\d+)\\s*:\\s*(\\d+)$/);\n    if (!m) return null;\n    const w = Number(m[1]);\n    const h = Number(m[2]);\n    if (!w || !h) return null;\n    return w / h;\n  }\n}\n","import type { Scenario, Track } from '../types/scenario-v2';\n\nexport interface GroupItem {\n  el: HTMLElement;\n  node: any;\n}\n\nexport class TrackManager {\n  private scenario: Scenario | null = null;\n\n  setScenario(scenario: Scenario) {\n    this.scenario = scenario;\n  }\n\n  computeGroupOffsets(\n    items: GroupItem[],\n    gapPx: number = 0\n  ): Map<HTMLElement, number> {\n    const map = new Map<HTMLElement, number>();\n    let yOff = 0;\n\n    for (const { el } of items) {\n      if (el.style.display === 'none') continue;\n      map.set(el, yOff);\n      const h = el.offsetHeight || 0;\n      yOff += h + gapPx;\n    }\n\n    return map;\n  }\n\n  getTrackDefaults(trackId: string): any | undefined {\n    if (!this.scenario) return undefined;\n    const track = this.scenario.tracks.find((t) => t.id === trackId);\n    return track?.defaultStyle;\n  }\n\n  getTrackById(trackId: string): Track | undefined {\n    if (!this.scenario) return undefined;\n    return this.scenario.tracks.find((t) => t.id === trackId);\n  }\n}\n","// DOM 경계 분리 시스템 (Plugin System v3.0)\n// baseWrapper (렌더러 영역) ↔ effectsRoot (플러그인 영역) 분리 관리\n\nexport interface DomSeparationConfig {\n  enableCSSVariables?: boolean; // CSS 변수 채널 활성화 (기본: true)\n  baseWrapperClass?: string; // baseWrapper CSS 클래스\n  effectsRootClass?: string; // effectsRoot CSS 클래스\n  preserveExisting?: boolean; // 기존 DOM 구조 보존 여부\n}\n\nexport interface ChannelValues {\n  tx?: number; // translateX (px)\n  ty?: number; // translateY (px)\n  sx?: number; // scaleX\n  sy?: number; // scaleY\n  rot?: number; // rotate (deg)\n  opacity?: number; // 0~1\n  filter?: string; // CSS filter\n  [key: string]: any; // 커스텀 채널\n}\n\nconst DEFAULT_CONFIG: Required<DomSeparationConfig> = {\n  enableCSSVariables: true,\n  baseWrapperClass: 'mtx-base-wrapper',\n  effectsRootClass: 'mtx-effects-root',\n  preserveExisting: false,\n};\n\n/**\n * DOM 경계 분리를 위한 baseWrapper 생성\n * baseWrapper는 렌더러가 관리하며, CSS 변수를 통해 채널 값을 제공\n */\nexport function createBaseWrapper(\n  originalElement: HTMLElement,\n  config: DomSeparationConfig = {}\n): HTMLElement {\n  const cfg = { ...DEFAULT_CONFIG, ...config };\n\n  // 기존 baseWrapper 찾기\n  let baseWrapper = originalElement.querySelector(\n    `.${cfg.baseWrapperClass}`\n  ) as HTMLElement | null;\n\n  if (!baseWrapper) {\n    baseWrapper = document.createElement('div');\n    baseWrapper.className = cfg.baseWrapperClass;\n\n    // 기본 스타일 설정\n    baseWrapper.style.position = 'relative';\n    baseWrapper.style.display = 'inline-block';\n    baseWrapper.style.pointerEvents = 'none';\n\n    // CSS 변수 기반 transform 초기화\n    if (cfg.enableCSSVariables) {\n      initializeCSSVariables(baseWrapper);\n    }\n\n    // 기존 콘텐츠 이동 또는 새로 생성\n    if (cfg.preserveExisting && originalElement.children.length > 0) {\n      // 기존 자식을 baseWrapper로 이동\n      while (originalElement.firstChild) {\n        baseWrapper.appendChild(originalElement.firstChild);\n      }\n    }\n\n    originalElement.appendChild(baseWrapper);\n  }\n\n  return baseWrapper;\n}\n\n/**\n * 플러그인 전용 effectsRoot 생성\n * effectsRoot는 플러그인이 관리하며, baseWrapper 내부에 위치\n */\nexport function ensureEffectsRoot(\n  baseWrapper: HTMLElement,\n  config: DomSeparationConfig = {}\n): HTMLElement {\n  const cfg = { ...DEFAULT_CONFIG, ...config };\n\n  let effectsRoot = baseWrapper.querySelector(\n    `.${cfg.effectsRootClass}`\n  ) as HTMLElement | null;\n\n  if (!effectsRoot) {\n    effectsRoot = document.createElement('div');\n    effectsRoot.className = cfg.effectsRootClass;\n    effectsRoot.setAttribute('data-mtx-effects-root', '');\n\n    // effectsRoot 기본 스타일\n    effectsRoot.style.position = 'relative';\n    effectsRoot.style.pointerEvents = 'none';\n    effectsRoot.style.width = '100%';\n    effectsRoot.style.height = '100%';\n\n    baseWrapper.appendChild(effectsRoot);\n  }\n\n  return effectsRoot;\n}\n\n/**\n * CSS 변수 기반 채널 시스템 초기화\n */\nfunction initializeCSSVariables(baseWrapper: HTMLElement): void {\n  const defaultChannels: ChannelValues = {\n    tx: 0,\n    ty: 0,\n    sx: 1,\n    sy: 1,\n    rot: 0,\n    opacity: 1,\n  };\n\n  // CSS 변수 설정\n  Object.entries(defaultChannels).forEach(([key, value]) => {\n    const cssVar = `--mtx-${key}`;\n    let cssValue: string;\n\n    switch (key) {\n      case 'tx':\n      case 'ty':\n        cssValue = `${value}px`;\n        break;\n      case 'rot':\n        cssValue = `${value}deg`;\n        break;\n      default:\n        cssValue = String(value);\n    }\n\n    baseWrapper.style.setProperty(cssVar, cssValue);\n  });\n\n  // transform 및 opacity CSS 설정\n  baseWrapper.style.transform =\n    'translateX(var(--mtx-tx, 0px)) ' +\n    'translateY(var(--mtx-ty, 0px)) ' +\n    'scaleX(var(--mtx-sx, 1)) ' +\n    'scaleY(var(--mtx-sy, 1)) ' +\n    'rotate(var(--mtx-rot, 0deg))';\n\n  baseWrapper.style.opacity = 'var(--mtx-opacity, 1)';\n  baseWrapper.style.filter = 'var(--mtx-filter, none)';\n}\n\n/**\n * 채널 값을 CSS 변수로 적용\n */\nexport function applyCSSVariableChannels(\n  baseWrapper: HTMLElement,\n  channels: ChannelValues\n): void {\n  Object.entries(channels).forEach(([key, value]) => {\n    if (value === undefined || value === null) return;\n\n    const cssVar = `--mtx-${key}`;\n    let cssValue: string;\n\n    switch (key) {\n      case 'tx':\n      case 'ty':\n        cssValue = `${value}px`;\n        break;\n      case 'rot':\n        cssValue = `${value}deg`;\n        break;\n      case 'sx':\n      case 'sy':\n      case 'opacity':\n        cssValue = String(value);\n        break;\n      case 'filter':\n        cssValue = String(value);\n        break;\n      default:\n        cssValue = String(value);\n    }\n\n    baseWrapper.style.setProperty(cssVar, cssValue);\n  });\n}\n\n/**\n * 기존 ensureEffectsRoot와의 호환성을 위한 래퍼\n * @deprecated 대신 createBaseWrapper + ensureEffectsRoot 사용\n */\nexport function legacyEnsureEffectsRoot(el: HTMLElement): HTMLElement {\n  // 기존 방식: data-mtx-effects-root 직접 생성\n  let root = el.querySelector(\n    ':scope > [data-mtx-effects-root]'\n  ) as HTMLElement | null;\n\n  if (!root) {\n    root = document.createElement('div');\n    root.setAttribute('data-mtx-effects-root', '');\n    root.style.position = 'relative';\n    root.style.pointerEvents = 'none';\n\n    if (getComputedStyle(el).position === 'static') {\n      el.style.position = 'relative';\n    }\n\n    el.appendChild(root);\n  }\n\n  return root;\n}\n\n/**\n * DOM 분리 아키텍처 적용\n * originalElement → baseWrapper → effectsRoot 구조 생성\n */\nexport function applyDomSeparation(\n  originalElement: HTMLElement,\n  config: DomSeparationConfig = {}\n): { baseWrapper: HTMLElement; effectsRoot: HTMLElement } {\n  const baseWrapper = createBaseWrapper(originalElement, config);\n  const effectsRoot = ensureEffectsRoot(baseWrapper, config);\n\n  return { baseWrapper, effectsRoot };\n}\n\n/**\n * 플러그인이 접근할 수 있는 DOM 영역인지 검증\n */\nexport function validatePluginDomAccess(\n  element: HTMLElement,\n  effectsRoot: HTMLElement\n): boolean {\n  // effectsRoot 자신이거나 그 하위 요소인지 확인\n  return element === effectsRoot || effectsRoot.contains(element);\n}\n","// Renderer v2.0 Native - v2.0 네이티브 렌더러 코어\n// Reference: context/scenario-json-spec-v-2-0.md\n//\n// v1.3 → v2.0 주요 변경사항:\n// - displayTime: [start, end] 배열 직접 처리\n// - v1.3 필드 완전 제거 (absStart/absEnd, relStart/relEnd)\n// - isWithinTimeRange, progressInTimeRange 사용\n// - 플러그인 v3.0 지원\n// - domLifetime 기반 DOM 생명주기\n\nimport type {\n  Scenario,\n  Cue,\n  ResolvedNodeUnion,\n  TimeRange,\n} from '../types/scenario-v2';\nimport type { PluginSpec, TimelineLike, SeekApplier } from '../types/plugin-v3';\nimport type { Channels } from '../types/plugin-v3';\nimport {\n  isWithinTimeRange,\n  progressInTimeRange,\n  computePluginWindowFromBase,\n} from '../utils/time-v2';\nimport { DefineResolver } from '../parser/DefineResolver';\nimport { devRegistry } from '../loader/dev/DevPluginRegistry';\nimport { applyTextStyle, applyGroupStyle } from '../runtime/StyleApply';\nimport {\n  createPluginContextV3,\n  type PluginContextV3,\n} from '../runtime/PluginContextV3';\nimport {\n  applyLayoutWithConstraints,\n  applyChildrenLayout,\n} from '../layout/LayoutEngine';\nimport { getDefaultTrackConstraints } from '../layout/DefaultConstraints';\nimport {\n  TimelineControllerV2,\n  type TimelineOptions,\n  type TickCallback,\n} from './TimelineControllerV2';\nimport { Stage } from './Stage';\nimport { TrackManager } from './TrackManager';\nimport {\n  applyDomSeparation,\n  applyCSSVariableChannels,\n} from '../runtime/DomSeparation';\nimport { createLogger, setGlobalDebug } from '../utils/logger';\n\n/**\n * DOM 플러그인 상태 관리 인터페이스\n */\ninterface DomPluginState {\n  plugin: PluginSpec;\n  initialized: boolean;\n  seekFunction?: SeekApplier | TimelineLike;\n  context?: PluginContextV3;\n}\n\nexport interface RendererOptions {\n  container: HTMLElement;\n  preloadMs?: number; // default 300ms\n  snapToFrame?: boolean;\n  fps?: number;\n  debugMode?: boolean;\n}\n\nexport interface MountedElement {\n  node: ResolvedNodeUnion;\n  element: HTMLElement;\n  cueId: string;\n  mountTime: number;\n  parentNodeId?: string;\n  parentElement?: HTMLElement | null;\n}\n\n/**\n * MotionText Renderer v2.0 Native\n * v2.0 시나리오를 네이티브로 처리하는 렌더러\n */\nexport class RendererV2 {\n  private container: HTMLElement;\n  private scenario: Scenario | null = null;\n  private mountedElements = new Map<string, MountedElement>();\n\n  // DOM 플러그인 상태 관리: nodeId → pluginName → DomPluginState\n  private domPluginStates = new Map<string, Map<string, DomPluginState>>();\n  private options: Required<RendererOptions>;\n  private lastUpdateTime = 0;\n  private defineResolver: DefineResolver = new DefineResolver();\n  private timeline: TimelineControllerV2;\n  private stage: Stage;\n  private trackManager: TrackManager;\n  private logger = createLogger('RendererV2', null);\n\n  constructor(options: RendererOptions) {\n    this.container = options.container;\n    this.options = {\n      preloadMs: 300,\n      snapToFrame: false,\n      fps: 30,\n      debugMode: false,\n      ...options,\n    };\n\n    // Initialize TimelineController\n    const timelineOptions: TimelineOptions = {\n      autoStart: true,\n      snapToFrame: this.options.snapToFrame,\n      fps: this.options.fps,\n      debugMode: this.options.debugMode,\n    };\n    this.timeline = new TimelineControllerV2(timelineOptions);\n\n    // Configure logger based on debug option\n    setGlobalDebug(this.options.debugMode);\n    this.logger.setEnabled(this.options.debugMode);\n\n    // Register update callback\n    this.timeline.onTick(this.onTimelineUpdate.bind(this));\n\n    // Initialize Stage\n    this.stage = new Stage();\n    this.stage.setContainer(this.container);\n\n    // Initialize TrackManager\n    this.trackManager = new TrackManager();\n\n    this.setupContainer();\n  }\n\n  /**\n   * 현재 시나리오를 비동기적으로 정리 (DOM 정리 완료 보장)\n   */\n  async clearAsync(): Promise<void> {\n    this.logger.debug('clearAsync called - removing current scenario');\n    await this.unmountAllAsync(); // 비동기 정리로 DOM 완전 제거 보장\n    this.scenario = null;\n    this.defineResolver = new DefineResolver(); // 빈 DefineResolver로 초기화\n  }\n\n  /**\n   * v2.0 시나리오 비동기 로드 (DOM 정리 완료 보장)\n   * @param scenario - 파싱된 v2.0 시나리오\n   */\n  async setScenarioAsync(scenario: Scenario): Promise<void> {\n    // 상속 시스템에서 사용할 수 있도록 전역 변수에 debugMode 설정\n    (globalThis as any).__MTX_DEBUG_MODE__ = this.options.debugMode;\n\n    this.logger.debug('setScenarioAsync called', {\n      version: scenario.version,\n      cuesCount: scenario.cues?.length || 0,\n      tracksCount: scenario.tracks?.length || 0,\n      hasDefine: !!scenario.define,\n    });\n\n    if (scenario.version !== '2.0') {\n      throw new Error(\n        `RendererV2 only supports v2.0 scenarios, got version \"${scenario.version}\"`\n      );\n    }\n\n    // 기존 시나리오가 있다면 비동기 정리 (DOM 완전 제거 보장)\n    if (this.scenario !== null) {\n      await this.unmountAllAsync();\n    }\n\n    this.scenario = scenario;\n\n    // DefineResolver 초기화 - scenario의 define 섹션으로 새로 생성\n    if (scenario.define) {\n      this.defineResolver = new DefineResolver(scenario.define);\n    }\n\n    // Stage에 시나리오 설정\n    this.stage.setScenario(scenario);\n\n    // TrackManager에 시나리오 설정\n    this.trackManager.setScenario(scenario);\n\n    this.logger.debug('setScenarioAsync completed');\n  }\n\n  /**\n   * v2.0 시나리오 로드 (동기)\n   * @param scenario - 파싱된 v2.0 시나리오\n   */\n  setScenario(scenario: Scenario): void {\n    // 상속 시스템에서 사용할 수 있도록 전역 변수에 debugMode 설정\n    (globalThis as any).__MTX_DEBUG_MODE__ = this.options.debugMode;\n\n    this.logger.debug('setScenario called', {\n      version: scenario.version,\n      cuesCount: scenario.cues?.length || 0,\n      tracksCount: scenario.tracks?.length || 0,\n      hasDefine: !!scenario.define,\n    });\n\n    if (scenario.version !== '2.0') {\n      throw new Error(\n        `RendererV2 only supports v2.0 scenarios, got version \"${scenario.version}\"`\n      );\n    }\n\n    // 기존 시나리오가 있다면 정리 (clear() 호출하지 않은 경우)\n    if (this.scenario !== null) {\n      this.unmountAll(); // 기존 요소들 정리\n    }\n\n    this.scenario = scenario;\n\n    // DefineResolver 초기화 - scenario의 define 섹션으로 새로 생성\n    if (scenario.define) {\n      this.defineResolver = new DefineResolver(scenario.define);\n    }\n\n    // Stage에 시나리오 설정\n    this.stage.setScenario(scenario);\n\n    // TrackManager에 시나리오 설정\n    this.trackManager.setScenario(scenario);\n\n    this.logger.debug('Scenario loaded:', {\n      version: scenario.version,\n      cueCount: scenario.cues.length,\n      trackCount: scenario.tracks.length,\n      container: {\n        width: this.container.clientWidth,\n        height: this.container.clientHeight,\n        styles: {\n          position: this.container.style.position,\n          overflow: this.container.style.overflow,\n          visibility: getComputedStyle(this.container).visibility,\n          display: getComputedStyle(this.container).display,\n        },\n      },\n    });\n  }\n\n  /**\n   * TimelineController에서 호출되는 업데이트 콜백\n   * @param currentTime - 현재 재생 시간 (초)\n   */\n  private onTimelineUpdate: TickCallback = (currentTime) => {\n    if (!this.scenario) {\n      this.logger.debug('Timeline update but no scenario loaded');\n      return;\n    }\n\n    this.lastUpdateTime = currentTime;\n\n    this.logger.debug(\n      `Timeline update: t=${currentTime.toFixed(3)}s, cues=${this.scenario.cues.length}`\n    );\n\n    // Cue별 처리\n    for (const cue of this.scenario.cues) {\n      this.processCue(cue, currentTime);\n    }\n\n    this.logDebugInfo(currentTime);\n  };\n\n  /**\n   * 현재 시간에 따른 렌더링 업데이트 (수동 호출용)\n   * @param currentTime - 현재 재생 시간 (초)\n   */\n  update(currentTime: number): void {\n    this.onTimelineUpdate(currentTime);\n  }\n\n  /**\n   * 개별 Cue 처리\n   */\n  private processCue(cue: Cue, currentTime: number): void {\n    const track = this.getTrack(cue.track);\n    if (!track) {\n      console.warn(`[RendererV2] Unknown track: ${cue.track}`);\n      return;\n    }\n\n    // domLifetime 체크 (DOM 생명주기 관리)\n    if (cue.domLifetime) {\n      const [domStart, domEnd] = cue.domLifetime as TimeRange;\n\n      if (!this.isWithinDomLifetime(currentTime, domStart, domEnd)) {\n        this.unmountCue(cue);\n        return;\n      }\n    }\n\n    // 루트 노드 처리\n    this.processNode(cue.root as ResolvedNodeUnion, cue, track, currentTime);\n  }\n\n  /**\n   * 개별 노드 처리 (재귀)\n   */\n  private processNode(\n    node: ResolvedNodeUnion,\n    cue: Cue,\n    track: any,\n    currentTime: number,\n    parentNodeId?: string\n  ): void {\n    const nodeId = `${cue.id}:${node.id}`;\n\n    // displayTime 체크\n    const [displayStart, displayEnd] = node.displayTime || [\n      -Infinity,\n      Infinity,\n    ];\n    const isActive = isWithinTimeRange(currentTime, [displayStart, displayEnd]);\n\n    if (isActive) {\n      this.ensureMounted(node, cue, nodeId, track, parentNodeId);\n      this.updateNode(node, currentTime, nodeId, track);\n    } else {\n      this.unmountNode(nodeId);\n    }\n\n    // group 노드의 children 재귀 처리\n    if (node.eType === 'group' && node.children) {\n      for (const child of node.children) {\n        this.processNode(child, cue, track, currentTime, nodeId);\n      }\n    }\n  }\n\n  /**\n   * 노드가 마운트되어 있는지 확인하고 필요시 마운트\n   */\n  private ensureMounted(\n    node: ResolvedNodeUnion,\n    cue: Cue,\n    nodeId: string,\n    track: any,\n    parentNodeId?: string\n  ): void {\n    if (this.mountedElements.has(nodeId)) return;\n\n    const element = this.createElement(node);\n    // Annotate element with the composite node key (cueId:nodeId)\n    try {\n      (element as any).dataset = (element as any).dataset || {};\n      element.dataset.nodeKey = nodeId;\n      element.dataset.cueId = cue.id;\n    } catch (_e) {\n      void 0;\n    }\n    // Determine parent DOM element (group nesting support)\n    let parentEl: HTMLElement | null = null;\n    if (parentNodeId) {\n      const parentMounted = this.mountedElements.get(parentNodeId);\n      parentEl = parentMounted?.element || null;\n    }\n\n    this.mountedElements.set(nodeId, {\n      node,\n      element,\n      cueId: cue.id,\n      mountTime: this.lastUpdateTime,\n      parentNodeId,\n      parentElement: parentEl || this.container,\n    });\n\n    // Fallback to root container when no valid parent\n    (parentEl || this.container).appendChild(element);\n\n    // 기본 스타일 적용 (레이아웃 + 트랙 기본값)\n    this.applyBaseStyle(element, node, track);\n\n    // If appended under a group with childrenLayout, re-apply children layout\n    if (parentNodeId) {\n      const parentMounted = this.mountedElements.get(parentNodeId);\n      const parentLayout = (parentMounted?.node as any)?.layout;\n      const cl = parentLayout?.childrenLayout;\n      if (parentMounted?.element && cl) {\n        applyChildrenLayout(parentMounted.element, cl);\n      }\n    }\n\n    this.logger.debug(`[RendererV2] Mounted node: ${nodeId}`, {\n      element: element,\n      nodetype: node.eType,\n      text: node.eType === 'text' ? (node as any).text : undefined,\n      layout: node.layout,\n      displayTime: node.displayTime,\n      trackDefaults: track?.defaultStyle,\n      styles: {\n        position: element.style.position,\n        left: element.style.left,\n        top: element.style.top,\n        transform: element.style.transform,\n        fontSize: element.style.fontSize,\n        color: element.style.color,\n        visibility: element.style.visibility,\n        display: element.style.display,\n      },\n    });\n  }\n\n  /**\n   * 노드 업데이트 (플러그인 효과 적용)\n   */\n  private updateNode(\n    node: ResolvedNodeUnion,\n    currentTime: number,\n    nodeId: string,\n    _track: any\n  ): void {\n    const mounted = this.mountedElements.get(nodeId);\n    if (!mounted) return;\n\n    // 플러그인 체인 처리\n    if (node.pluginChain && node.pluginChain.length > 0) {\n      const channels = this.processPluginChain(\n        node,\n        node.pluginChain as PluginSpec[],\n        currentTime,\n        mounted.element\n      );\n\n      this.applyChannels(mounted.element, channels);\n    }\n  }\n\n  /**\n   * 플러그인 체인 처리\n   */\n  private processPluginChain(\n    node: ResolvedNodeUnion,\n    pluginChain: PluginSpec[],\n    currentTime: number,\n    element: HTMLElement\n  ): Channels {\n    const channels: Channels = {};\n\n    for (const plugin of pluginChain) {\n      // baseTime 우선순위: plugin.baseTime → node.baseTime → node.displayTime\n      const rawBase: any = plugin.baseTime ?? node.baseTime ?? node.displayTime;\n      const baseTime = this.resolveAllDefines(rawBase) as TimeRange;\n\n      // timeOffset은 절대 초 또는 백분율('%') 허용\n      const rawOffset = plugin.timeOffset ?? ['0%', '100%'];\n      const resolvedOffset = this.resolveAllDefines(rawOffset) as [\n        unknown,\n        unknown,\n      ];\n\n      // 실행 창 계산\n      const pluginWindow = computePluginWindowFromBase(\n        baseTime,\n        resolvedOffset\n      );\n\n      const active = isWithinTimeRange(currentTime, pluginWindow);\n      if (active) {\n        const progress = progressInTimeRange(currentTime, pluginWindow);\n\n        const pluginChannels = this.evaluatePlugin(plugin, progress, element);\n\n        // 채널 병합 (compose 모드에 따라)\n        this.mergeChannels(\n          channels,\n          pluginChannels,\n          plugin.compose || 'replace'\n        );\n      } else {\n        // DOM 플러그인은 윈도우를 벗어났을 때도 상태가 잔류하지 않도록\n        // 이미 초기화된 경우에만 p=0 또는 p=1을 적용한다 (새 초기화는 하지 않음)\n        const registered = devRegistry.resolve(plugin.name);\n        if (\n          registered?.module &&\n          typeof registered.module.animate === 'function'\n        ) {\n          const before = currentTime < pluginWindow[0];\n          const p = before ? 0 : 1;\n          this.applyDomPluginProgressIfInitialized(plugin, element, p);\n        }\n      }\n    }\n\n    return channels;\n  }\n\n  /**\n   * 이미 초기화된 DOM 플러그인에만 진행도를 적용한다.\n   * 플러그인을 새로 초기화하지 않는다 (메모리/DOM 누수 방지).\n   */\n  private applyDomPluginProgressIfInitialized(\n    plugin: PluginSpec,\n    element: HTMLElement,\n    progress: number\n  ): void {\n    const nodeId = (element.dataset.nodeKey || element.dataset.nodeId)!;\n    if (!nodeId) return;\n    const nodeStates =\n      this.domPluginStates.get(nodeId) ||\n      (() => {\n        // Backward compatibility: prior to fix, keys used bare node.id\n        const fallbackId = nodeId.includes(':')\n          ? nodeId.split(':')[1]\n          : undefined;\n        return fallbackId ? this.domPluginStates.get(fallbackId) : undefined;\n      })();\n    if (!nodeStates) return;\n    const state = nodeStates.get(plugin.name);\n    if (!state || !state.initialized || !state.seekFunction) return;\n\n    try {\n      const fn = state.seekFunction as any;\n      if (typeof fn === 'function') {\n        fn(progress);\n      } else if (fn && typeof fn.progress === 'function') {\n        fn.progress(progress);\n      }\n    } catch (_e) {\n      // non-fatal; skip\n    }\n  }\n\n  /**\n   * 모든 Define 참조를 재귀적으로 해석\n   */\n  private resolveAllDefines(value: unknown): unknown {\n    if (typeof value === 'string' && value.startsWith('define.')) {\n      try {\n        // Define 참조 문자열 해석을 위해 임시 시나리오 생성\n        const tempScenario = {\n          version: '2.0' as const,\n          timebase: { unit: 'seconds' as const },\n          stage: { baseAspect: '16:9' as const },\n          tracks: [],\n          cues: [],\n          temp: value,\n        };\n        const resolved = this.defineResolver.resolveScenario(tempScenario);\n        return (resolved as any).temp;\n      } catch (error) {\n        if (this.options.debugMode) {\n          console.warn(\n            `[RendererV2] Failed to resolve define \"${value}\":`,\n            error\n          );\n        }\n        return value; // 해석 실패 시 원본 값 반환\n      }\n    }\n\n    if (Array.isArray(value)) {\n      return value.map((v) => this.resolveAllDefines(v));\n    }\n\n    if (typeof value === 'object' && value !== null) {\n      const resolved: Record<string, unknown> = {};\n      for (const [key, val] of Object.entries(value)) {\n        resolved[key] = this.resolveAllDefines(val);\n      }\n      return resolved;\n    }\n\n    return value;\n  }\n\n  /**\n   * 플러그인 평가 (내장 + 외부)\n   */\n  private evaluatePlugin(\n    plugin: PluginSpec,\n    progress: number,\n    element?: HTMLElement\n  ): Channels {\n    // Define 참조를 사전 해석\n    const resolvedParams = this.resolveAllDefines(plugin.params || {});\n\n    // 해석된 params로 플러그인 스펙 생성\n    const resolvedPlugin: PluginSpec = {\n      ...plugin,\n      params: resolvedParams as Record<string, any>,\n    };\n\n    // 플러그인 타입 감지 (하이브리드 지원)\n    const pluginTypes = this.detectPluginType(resolvedPlugin);\n\n    if (pluginTypes.size === 0) {\n      console.warn(`[RendererV2] Unknown plugin: ${plugin.name}`);\n      return {};\n    }\n\n    let channels: Channels = {};\n\n    // 채널 기반 플러그인\n    if (pluginTypes.has('channel')) {\n      const channelResult = this.evaluateChannelPlugin(\n        resolvedPlugin,\n        progress,\n        element\n      );\n      channels = { ...channels, ...channelResult };\n    }\n\n    // DOM 기반 플러그인 (채널을 반환하지 않지만 DOM 직접 조작)\n    if (pluginTypes.has('dom') && element) {\n      this.evaluateDomPlugin(resolvedPlugin, progress, element);\n      // DOM 플러그인은 채널 반환하지 않음\n    }\n\n    return channels;\n  }\n\n  /**\n   * 플러그인 타입 감지 - v3.0 하이브리드 지원\n   */\n  private detectPluginType(plugin: PluginSpec): Set<'channel' | 'dom'> {\n    const types = new Set<'channel' | 'dom'>();\n\n    // 외부 플러그인 확인\n    const registeredPlugin = devRegistry.resolve(plugin.name);\n    if (!registeredPlugin) {\n      return types; // 빈 Set 반환\n    }\n\n    // 1순위: 시나리오에 명시된 타입\n    if (plugin.type) {\n      if (plugin.type === 'channel') {\n        types.add('channel');\n      } else if (plugin.type === 'dom') {\n        types.add('dom');\n      } else if (plugin.type === 'hybrid') {\n        types.add('channel');\n        types.add('dom');\n      }\n      return types;\n    }\n\n    // 2순위: Manifest에 명시된 타입\n    if (registeredPlugin.manifest?.type) {\n      const manifestType = registeredPlugin.manifest.type;\n      if (manifestType === 'channel') {\n        types.add('channel');\n      } else if (manifestType === 'dom') {\n        types.add('dom');\n      } else if (manifestType === 'hybrid') {\n        types.add('channel');\n        types.add('dom');\n      }\n      return types;\n    }\n\n    // 3순위: 자동 감지 (폴백)\n    if (typeof registeredPlugin.module?.evalChannels === 'function') {\n      types.add('channel');\n    }\n    if (typeof registeredPlugin.module?.animate === 'function') {\n      types.add('dom');\n    }\n\n    return types;\n  }\n\n  /**\n   * 채널 기반 플러그인 평가 (v3.0 API)\n   */\n  private evaluateChannelPlugin(\n    plugin: PluginSpec,\n    progress: number,\n    _element?: HTMLElement\n  ): Channels {\n    const registeredPlugin = devRegistry.resolve(plugin.name);\n    if (!registeredPlugin) {\n      console.warn(\n        `[RendererV2] Plugin not found in registry: \"${plugin.name}\"`\n      );\n      return {};\n    }\n\n    if (this.options.debugMode) {\n      // keep existing gate but delegate to logger to avoid no-console\n      this.logger.debug(`Plugin found:`, {\n        name: plugin.name,\n        hasModule: !!registeredPlugin.module,\n        hasEvalChannels:\n          typeof registeredPlugin.module?.evalChannels === 'function',\n      });\n    }\n\n    // Plugin API v3.0 - evalChannels 함수\n    if (typeof registeredPlugin.module?.evalChannels === 'function') {\n      try {\n        // PluginContextV3 생성\n        const context = this.createPluginContext(registeredPlugin.baseUrl);\n\n        if (this.options.debugMode) {\n          this.logger.debug(`Calling evalChannels for \"${plugin.name}\" with:`, {\n            plugin,\n            progress,\n            context,\n          });\n        }\n\n        const channels = registeredPlugin.module.evalChannels(\n          plugin,\n          progress,\n          context\n        );\n\n        if (this.options.debugMode) {\n          this.logger.debug(`evalChannels returned:`, channels);\n        }\n\n        return channels || {};\n      } catch (error) {\n        console.error(\n          `[RendererV2] Channel plugin \"${plugin.name}\" evalChannels failed:`,\n          error\n        );\n        return {};\n      }\n    }\n\n    console.warn(\n      `[RendererV2] Plugin \"${plugin.name}\" has no evalChannels function`\n    );\n    return {};\n  }\n\n  /**\n   * DOM 기반 플러그인 평가 (v2.1 API)\n   */\n  private evaluateDomPlugin(\n    plugin: PluginSpec,\n    progress: number,\n    element?: HTMLElement\n  ): Channels {\n    if (!element) {\n      console.warn(`[RendererV2] DOM plugin \"${plugin.name}\" requires element`);\n      return {};\n    }\n\n    // Prefer composite node key if present; fallback to legacy nodeId (node-only)\n    const nodeId = (element.dataset.nodeKey || element.dataset.nodeId)!;\n    if (!nodeId) {\n      console.warn(\n        `[RendererV2] Missing nodeKey/nodeId on element for plugin ${plugin.name}`\n      );\n      return {};\n    }\n\n    if (this.options.debugMode) {\n      this.logger.debug(`DOM plugin \"${plugin.name}\" element structure:`, {\n        nodeId,\n        outerHTML: element.outerHTML,\n        children: Array.from(element.children).map((c) => c.className),\n        effectsRootSelector: '.mtx-effects-root, [data-mtx-effects-root]',\n        effectsRootFound: !!element.querySelector(\n          '.mtx-effects-root, [data-mtx-effects-root]'\n        ),\n      });\n    }\n\n    const effectsRoot = element.querySelector(\n      '.mtx-effects-root, [data-mtx-effects-root]'\n    ) as HTMLElement;\n\n    if (!effectsRoot) {\n      console.warn(\n        `[RendererV2] DOM plugin \"${plugin.name}\" requires effectsRoot`\n      );\n      return {};\n    }\n\n    // DOM 플러그인 상태 관리 (M3에서 구현 예정)\n    this.manageDomPluginState(nodeId, plugin, effectsRoot, progress);\n\n    // DOM 플러그인은 채널 값을 반환하지 않음 (DOM 직접 조작)\n    return {};\n  }\n\n  /**\n   * DOM 플러그인 상태 관리\n   */\n  private manageDomPluginState(\n    nodeId: string,\n    plugin: PluginSpec,\n    effectsRoot: HTMLElement,\n    progress: number\n  ): void {\n    if (this.options.debugMode) {\n      console.log(\n        `[RendererV2] manageDomPluginState: plugin=\"${plugin.name}\", nodeId=\"${nodeId}\", progress=${progress}`\n      );\n    }\n\n    let nodeStates = this.domPluginStates.get(nodeId);\n    if (!nodeStates) {\n      nodeStates = new Map();\n      this.domPluginStates.set(nodeId, nodeStates);\n      if (this.options.debugMode) {\n        this.logger.debug(`Created new nodeStates for nodeId: ${nodeId}`);\n      }\n    }\n\n    let state = nodeStates.get(plugin.name);\n    if (!state) {\n      state = {\n        plugin,\n        initialized: false,\n      };\n      nodeStates.set(plugin.name, state);\n    }\n\n    // 초기화가 안 된 경우 또는 DOM 요소가 없는 경우 초기화\n    if (!state.initialized) {\n      if (this.options.debugMode) {\n        this.logger.debug(`Initializing DOM plugin: ${plugin.name}`);\n      }\n      this.initializeDomPlugin(state, plugin, effectsRoot);\n    } else {\n      // 플러그인이 초기화되었다고 되어 있지만 DOM 요소가 실제로 존재하는지 확인\n      const needsReinitialization = this.checkDomPluginElements(\n        plugin.name,\n        effectsRoot\n      );\n      if (needsReinitialization) {\n        if (this.options.debugMode) {\n          this.logger.debug(\n            `DOM elements missing for ${plugin.name}, reinitializing...`\n          );\n        }\n        // 상태 리셋 후 재초기화\n        state.initialized = false;\n        state.seekFunction = undefined;\n        this.initializeDomPlugin(state, plugin, effectsRoot);\n      } else {\n        if (this.options.debugMode) {\n          this.logger.debug(\n            `Plugin ${plugin.name} already initialized and DOM elements exist`\n          );\n        }\n      }\n    }\n\n    // seek 함수가 있으면 진행도 적용\n    if (state.seekFunction) {\n      if (this.options.debugMode) {\n        this.logger.debug(`Applying progress ${progress} to ${plugin.name}`);\n      }\n      if (typeof state.seekFunction === 'function') {\n        // SeekApplier 타입: (progress: number) => void\n        if (this.options.debugMode) {\n          this.logger.debug(`Calling SeekApplier for ${plugin.name}`);\n        }\n        state.seekFunction(progress);\n      } else if (\n        state.seekFunction &&\n        typeof state.seekFunction.progress === 'function'\n      ) {\n        // TimelineLike 타입: GSAP Timeline 등\n        if (this.options.debugMode) {\n          this.logger.debug(`Calling Timeline.progress for ${plugin.name}`);\n        }\n        state.seekFunction.progress(progress);\n      } else {\n        console.warn(\n          `[RendererV2] Invalid seekFunction type for plugin ${plugin.name}:`,\n          typeof state.seekFunction\n        );\n      }\n    } else {\n      console.warn(\n        `[RendererV2] No seekFunction found for plugin ${plugin.name}, state:`,\n        state\n      );\n    }\n  }\n\n  /**\n   * DOM 플러그인의 예상 DOM 요소가 존재하는지 확인\n   * @returns true if reinitialization is needed (elements are missing)\n   */\n  private checkDomPluginElements(\n    pluginName: string,\n    effectsRoot: HTMLElement\n  ): boolean {\n    switch (pluginName) {\n      case 'glow':\n        return !effectsRoot.querySelector('[data-glow]');\n      case 'flames':\n        return !effectsRoot.querySelector('img[data-flames]');\n      case 'glitch':\n        return !effectsRoot.querySelector('[data-glitch]');\n      case 'magnetic':\n        return !effectsRoot.querySelector('[data-magnetic]');\n      // 다른 DOM 플러그인들도 필요에 따라 추가\n      default:\n        // 알려지지 않은 플러그인의 경우 재초기화가 필요하다고 가정하지 않음\n        return false;\n    }\n  }\n\n  /**\n   * DOM 플러그인 초기화\n   */\n  private initializeDomPlugin(\n    state: DomPluginState,\n    plugin: PluginSpec,\n    effectsRoot: HTMLElement\n  ): void {\n    const registeredPlugin = devRegistry.resolve(plugin.name);\n    if (!registeredPlugin?.module) {\n      console.warn(`[RendererV2] DOM plugin not found: ${plugin.name}`);\n      return;\n    }\n\n    try {\n      // 플러그인 컨텍스트 생성\n      const context = createPluginContextV3({\n        container: effectsRoot,\n        scenario: this.scenario!,\n        baseUrl: registeredPlugin.baseUrl,\n        renderer: {\n          version: '2.0.0',\n          currentTime: 0,\n          duration: 1,\n          timeScale: 1,\n          fps: this.options.fps,\n        },\n        peerDeps: {\n          gsap: (window as any).gsap,\n        },\n      });\n      const resolvedParams = this.resolveAllDefines(plugin.params || {});\n\n      // init 호출 (있는 경우)\n      if (typeof registeredPlugin.module.init === 'function') {\n        registeredPlugin.module.init(effectsRoot, resolvedParams, context);\n      }\n\n      // animate 호출하여 seek 함수 생성\n      if (typeof registeredPlugin.module.animate === 'function') {\n        const seekFn = registeredPlugin.module.animate(\n          effectsRoot,\n          resolvedParams,\n          context,\n          1.0 // duration은 1.0으로 정규화 (progress 0~1 사용)\n        );\n\n        state.seekFunction = seekFn;\n        // Safety: if plugin returned a GSAP-like timeline, pause it.\n        if (\n          seekFn &&\n          typeof seekFn === 'object' &&\n          typeof (seekFn as any).pause === 'function'\n        ) {\n          try {\n            (seekFn as any).pause(0);\n          } catch (_e) {\n            void 0;\n          }\n        }\n      }\n\n      state.initialized = true;\n      state.context = context;\n\n      if (this.options.debugMode) {\n        console.log(`[RendererV2] DOM plugin initialized: ${plugin.name}`);\n      }\n    } catch (error) {\n      console.warn(\n        `[RendererV2] DOM plugin init failed: ${plugin.name}`,\n        error\n      );\n    }\n  }\n\n  /**\n   * DOM 플러그인 정리 (노드 언마운트 시)\n   */\n  private cleanupDomPlugin(nodeId: string, pluginName?: string): void {\n    let nodeStates = this.domPluginStates.get(nodeId);\n    // Backward compatibility: prior to fix, keys used bare node.id. Fallback to that.\n    if (!nodeStates) {\n      const fallbackId = nodeId.includes(':')\n        ? nodeId.split(':')[1]\n        : undefined;\n      if (fallbackId) nodeStates = this.domPluginStates.get(fallbackId);\n    }\n    if (!nodeStates) return;\n\n    if (pluginName) {\n      // 특정 플러그인만 정리\n      const state = nodeStates.get(pluginName);\n      if (state?.context && state.initialized) {\n        // Timeline 정리\n        if (\n          state.seekFunction &&\n          typeof state.seekFunction === 'object' &&\n          state.seekFunction.kill\n        ) {\n          try {\n            state.seekFunction.kill();\n          } catch (error) {\n            console.warn(\n              `[RendererV2] Timeline cleanup failed for ${pluginName}:`,\n              error\n            );\n          }\n        }\n\n        // 플러그인 정리\n        const registeredPlugin = devRegistry.resolve(pluginName);\n        if (registeredPlugin?.module?.cleanup) {\n          try {\n            registeredPlugin.module.cleanup(\n              state.context.targetElement || document.createElement('div')\n            );\n          } catch (error) {\n            console.warn(\n              `[RendererV2] DOM plugin cleanup failed: ${pluginName}`,\n              error\n            );\n          }\n        }\n\n        // 상태 리셋: 재진입 시 재초기화되도록 함\n        state.initialized = false;\n        state.seekFunction = undefined;\n      }\n      nodeStates.delete(pluginName);\n    } else {\n      // 모든 플러그인 정리\n      for (const [name, state] of nodeStates) {\n        if (state.context && state.initialized) {\n          // Timeline 정리\n          if (\n            state.seekFunction &&\n            typeof state.seekFunction === 'object' &&\n            state.seekFunction.kill\n          ) {\n            try {\n              state.seekFunction.kill();\n            } catch (error) {\n              console.warn(\n                `[RendererV2] Timeline cleanup failed for ${name}:`,\n                error\n              );\n            }\n          }\n\n          // 플러그인 정리\n          const registeredPlugin = devRegistry.resolve(name);\n          if (registeredPlugin?.module?.cleanup) {\n            try {\n              registeredPlugin.module.cleanup(\n                state.context.targetElement || document.createElement('div')\n              );\n            } catch (error) {\n              console.warn(\n                `[RendererV2] DOM plugin cleanup failed: ${name}`,\n                error\n              );\n            }\n          }\n\n          // 상태 리셋: 재진입 시 재초기화되도록 함\n          state.initialized = false;\n          state.seekFunction = undefined;\n        }\n      }\n      this.domPluginStates.delete(nodeId);\n    }\n  }\n\n  /**\n   * PluginContextV3 생성\n   */\n  private createPluginContext(baseUrl: string): PluginContextV3 {\n    if (!this.scenario) {\n      throw new Error('Scenario not loaded');\n    }\n\n    // effectsRoot DOM을 플러그인 컨테이너로 사용\n    // 실제 구현에서는 현재 처리 중인 요소의 effectsRoot를 사용해야 함\n    const container = this.container; // 임시로 메인 컨테이너 사용\n\n    return createPluginContextV3({\n      container,\n      scenario: this.scenario,\n      baseUrl,\n      renderer: {\n        version: '2.0',\n        currentTime: this.lastUpdateTime,\n        duration: 0, // 미구현\n        timeScale: 1, // 미구현\n        fps: this.options.fps,\n      },\n      peerDeps: {\n        gsap: (window as any).gsap,\n      },\n    });\n  }\n\n  /**\n   * 채널 병합\n   */\n  private mergeChannels(\n    target: Channels,\n    source: Channels,\n    mode: string\n  ): void {\n    for (const [key, value] of Object.entries(source)) {\n      if (value === undefined) continue;\n\n      switch (mode) {\n        case 'add':\n          if (typeof value === 'number' && typeof target[key] === 'number') {\n            target[key] = (target[key] as number) + value;\n          } else {\n            target[key] = value;\n          }\n          break;\n\n        case 'multiply':\n          if (typeof value === 'number' && typeof target[key] === 'number') {\n            target[key] = (target[key] as number) * value;\n          } else {\n            target[key] = value;\n          }\n          break;\n\n        case 'replace':\n        default:\n          target[key] = value;\n          break;\n      }\n    }\n  }\n\n  /**\n   * 채널을 CSS로 적용\n   */\n  private applyChannels(element: HTMLElement, channels: Channels): void {\n    if (this.options.debugMode) {\n      console.log(`[RendererV2] applyChannels called`, {\n        hasChannels: !!channels,\n        channelCount: Object.keys(channels || {}).length,\n        channels: channels,\n      });\n    }\n\n    // 적용할 채널이 없으면 스킵\n    if (!channels || Object.keys(channels).length === 0) {\n      if (this.options.debugMode) {\n        console.log(`[RendererV2] No channels to apply, skipping`);\n      }\n      return;\n    }\n\n    // baseWrapper를 찾아서 채널 적용\n    const baseWrapper = element.querySelector(\n      '.mtx-base-wrapper'\n    ) as HTMLElement;\n    if (!baseWrapper) {\n      console.warn(\n        '[RendererV2] No baseWrapper found for channel application',\n        {\n          element,\n          innerHTML: element.innerHTML,\n          classList: Array.from(element.classList),\n          children: Array.from(element.children).map((child) => ({\n            tagName: child.tagName,\n            classList: Array.from(child.classList),\n          })),\n          channels,\n        }\n      );\n      return;\n    }\n\n    if (this.options.debugMode) {\n      console.log(`[RendererV2] Applying channels to baseWrapper`, {\n        baseWrapper: baseWrapper,\n        channels: channels,\n      });\n    }\n\n    // CSS 변수를 통한 채널 적용 (DomSeparation의 CSS 변수 시스템 사용)\n    applyCSSVariableChannels(baseWrapper, channels);\n\n    if (this.options.debugMode) {\n      console.log(`[RendererV2] Channels applied successfully`);\n    }\n  }\n\n  /**\n   * HTML 요소 생성 - baseWrapper/effectsRoot 구조 적용\n   */\n  private createElement(node: ResolvedNodeUnion): HTMLElement {\n    // 1. 기본 요소 생성\n    const originalElement = this.createOriginalElement(node);\n\n    // 2. DOM 분리 아키텍처 적용\n    const { baseWrapper, effectsRoot } = applyDomSeparation(originalElement, {\n      enableCSSVariables: true,\n      preserveExisting: true,\n    });\n\n    // 3. 콘텐츠를 effectsRoot로 이동\n    this.moveContentToEffectsRoot(node, originalElement, effectsRoot);\n\n    // 4. 메타데이터 저장\n    // Keep legacy nodeId (node-local id) for compatibility; composite is set in ensureMounted\n    originalElement.dataset.nodeId = node.id;\n    baseWrapper.dataset.baseWrapper = 'true';\n    effectsRoot.dataset.effectsRoot = 'true';\n\n    if (this.options.debugMode) {\n      console.log('[RendererV2] createElement completed', {\n        nodeId: node.id,\n        element: originalElement,\n        outerHTML: originalElement.outerHTML,\n        hasBaseWrapper: !!originalElement.querySelector('.mtx-base-wrapper'),\n        hasEffectsRoot: !!originalElement.querySelector('.mtx-effects-root'),\n        innerHTML: originalElement.innerHTML,\n      });\n    }\n\n    return originalElement;\n  }\n\n  /**\n   * 기본 HTML 요소 생성 (DOM 분리 전)\n   */\n  private createOriginalElement(node: ResolvedNodeUnion): HTMLElement {\n    let element: HTMLElement;\n\n    switch (node.eType) {\n      case 'text':\n        element = document.createElement('div');\n        element.textContent = node.text || '';\n        element.className = 'mtx-text';\n        break;\n\n      case 'image':\n        element = document.createElement('img') as HTMLImageElement;\n        (element as HTMLImageElement).src = node.src || '';\n        (element as HTMLImageElement).alt = node.alt || '';\n        element.className = 'mtx-image';\n        break;\n\n      case 'video':\n        element = document.createElement('video') as HTMLVideoElement;\n        (element as HTMLVideoElement).src = node.src || '';\n        (element as HTMLVideoElement).muted = node.muted ?? true;\n        element.className = 'mtx-video';\n        break;\n\n      case 'group':\n        element = document.createElement('div');\n        element.className = 'mtx-group';\n        break;\n\n      default:\n        element = document.createElement('div');\n        element.className = 'mtx-unknown';\n    }\n\n    return element;\n  }\n\n  /**\n   * 콘텐츠를 effectsRoot로 이동\n   */\n  private moveContentToEffectsRoot(\n    node: ResolvedNodeUnion,\n    originalElement: HTMLElement,\n    effectsRoot: HTMLElement\n  ): void {\n    switch (node.eType) {\n      case 'text':\n        // 텍스트를 effectsRoot로 이동\n        effectsRoot.textContent = node.text || '';\n        // originalElement의 직접 자식 텍스트 노드만 제거 (baseWrapper는 보존)\n        for (const child of Array.from(originalElement.childNodes)) {\n          if (child.nodeType === Node.TEXT_NODE) {\n            originalElement.removeChild(child);\n          }\n        }\n        break;\n\n      case 'image':\n        {\n          // 이미지 속성을 effectsRoot의 img로 이동\n          const imgElement = document.createElement('img');\n          imgElement.src = (originalElement as HTMLImageElement).src;\n          imgElement.alt = (originalElement as HTMLImageElement).alt;\n          imgElement.style.width = '100%';\n          imgElement.style.height = '100%';\n          effectsRoot.appendChild(imgElement);\n          // 원본은 빈 상태로 유지\n          (originalElement as HTMLImageElement).src = '';\n        }\n        break;\n\n      case 'video':\n        {\n          // 비디오 속성을 effectsRoot의 video로 이동\n          const videoElement = document.createElement('video');\n          videoElement.src = (originalElement as HTMLVideoElement).src;\n          videoElement.muted = (originalElement as HTMLVideoElement).muted;\n          videoElement.style.width = '100%';\n          videoElement.style.height = '100%';\n          effectsRoot.appendChild(videoElement);\n          // 원본은 빈 상태로 유지\n          (originalElement as HTMLVideoElement).src = '';\n        }\n        break;\n\n      case 'group':\n        // 그룹은 effectsRoot를 컨테이너로 사용\n        break;\n    }\n  }\n\n  /**\n   * 기본 스타일 적용 (새로운 constraints 시스템 사용)\n   */\n  private applyBaseStyle(\n    element: HTMLElement,\n    node: ResolvedNodeUnion,\n    track?: any\n  ): void {\n    // Get track constraints if available\n    const trackConstraints =\n      track?.defaultConstraints ||\n      (track?.type ? getDefaultTrackConstraints(track.type) : undefined);\n\n    // Apply layout with constraints system\n    applyLayoutWithConstraints(\n      element,\n      node.layout,\n      trackConstraints,\n      'cc', // default anchor\n      {\n        hasEffectScope: !!node.effectScope,\n        stageSafe: this.scenario?.stage?.safeArea,\n        trackSafe: track?.safeArea,\n      }\n    );\n\n    // Apply text styles: track defaults → node styles (node takes precedence)\n    const trackTextDefaults = track?.defaultStyle || {};\n    const nodeTextStyle = node.style || {};\n    const mergedTextStyle = { ...trackTextDefaults, ...nodeTextStyle };\n\n    // Apply text-related styles using StyleApply function\n    const containerHeight = this.container.clientHeight || 720;\n    applyTextStyle(\n      element,\n      containerHeight,\n      mergedTextStyle,\n      trackTextDefaults\n    );\n\n    // Apply box styles for group nodes only\n    if (node.eType === 'group') {\n      const groupNode = node as any; // Cast to access boxStyle\n      const trackBoxDefaults = track?.defaultBoxStyle || {};\n      const nodeBoxStyle = groupNode.boxStyle || {};\n      const mergedBoxStyle = { ...trackBoxDefaults, ...nodeBoxStyle };\n\n      // Apply box-related styles using StyleApply function\n      applyGroupStyle(element, containerHeight, mergedBoxStyle, node.layout);\n    }\n\n    // Apply default text styling for text nodes without explicit style\n    if (\n      node.eType === 'text' &&\n      !mergedTextStyle.fontSizeRel &&\n      !mergedTextStyle.fontSize\n    ) {\n      element.style.fontSize = '2rem'; // Default visible size\n      element.style.color = element.style.color || '#ffffff'; // Default white text\n      element.style.fontWeight = element.style.fontWeight || 'bold';\n    }\n  }\n\n  /**\n   * 유틸리티 메서드들\n   */\n  private isWithinDomLifetime(\n    time: number,\n    start: number,\n    end: number\n  ): boolean {\n    return time >= start && time <= end;\n  }\n\n  private getTrack(trackId: string) {\n    return this.trackManager.getTrackById(trackId);\n  }\n\n  private unmountNode(nodeId: string): void {\n    const mounted = this.mountedElements.get(nodeId);\n    if (!mounted) return;\n\n    // DOM 플러그인 정리\n    this.cleanupDomPlugin(nodeId);\n\n    // Capture parent info before removal\n    const parentNodeId = mounted.parentNodeId;\n    const parentEl = mounted.element.parentElement;\n\n    mounted.element.remove();\n    this.mountedElements.delete(nodeId);\n\n    if (this.options.debugMode) {\n      this.logger.debug(`Unmounted node: ${nodeId}`);\n    }\n\n    // Re-apply parent children layout to normalize spacing after removal\n    if (parentNodeId) {\n      const parentMounted = this.mountedElements.get(parentNodeId);\n      const cl = (parentMounted?.node as any)?.layout?.childrenLayout;\n      if (parentMounted?.element && cl) {\n        try {\n          applyChildrenLayout(parentMounted.element, cl);\n        } catch (_e) {\n          /* noop */\n        }\n      } else if (parentEl) {\n        // Fallback: try from DOM parent if it is still present\n        const maybeCl = (parentEl as any).__mtxChildrenLayout;\n        if (maybeCl) {\n          try {\n            applyChildrenLayout(parentEl as HTMLElement, maybeCl);\n          } catch (_e) {\n            /* noop */\n          }\n        }\n      }\n    }\n  }\n\n  private unmountCue(cue: Cue): void {\n    const toRemove = Array.from(this.mountedElements.keys()).filter((nodeId) =>\n      nodeId.startsWith(`${cue.id}:`)\n    );\n\n    toRemove.forEach((nodeId) => this.unmountNode(nodeId));\n  }\n\n  private unmountAll(): void {\n    for (const [nodeId] of this.mountedElements) {\n      this.unmountNode(nodeId);\n    }\n  }\n\n  private async unmountAllAsync(): Promise<void> {\n    const nodeIds = Array.from(this.mountedElements.keys());\n\n    // 모든 노드 언마운트\n    for (const nodeId of nodeIds) {\n      this.unmountNode(nodeId);\n    }\n\n    // DOM 업데이트가 완료될 때까지 다음 프레임 대기\n    await new Promise<void>((resolve) => {\n      requestAnimationFrame(() => {\n        // 추가로 한 프레임 더 대기하여 완전한 DOM 정리 보장\n        requestAnimationFrame(() => resolve());\n      });\n    });\n  }\n\n  private setupContainer(): void {\n    this.container.style.position = 'relative';\n    this.container.style.overflow = 'hidden';\n\n    // Ensure container has dimensions\n    if (this.container.clientWidth === 0 || this.container.clientHeight === 0) {\n      this.container.style.width = this.container.style.width || '100%';\n      this.container.style.height = this.container.style.height || '100%';\n      this.container.style.minHeight =\n        this.container.style.minHeight || '400px';\n    }\n\n    if (this.options.debugMode) {\n      this.logger.debug('Container setup:', {\n        width: this.container.clientWidth,\n        height: this.container.clientHeight,\n        styles: {\n          position: this.container.style.position,\n          overflow: this.container.style.overflow,\n          width: this.container.style.width,\n          height: this.container.style.height,\n          minHeight: this.container.style.minHeight,\n        },\n      });\n    }\n  }\n\n  private logDebugInfo(currentTime: number): void {\n    const mountedCount = this.mountedElements.size;\n\n    if (mountedCount > 0) {\n      this.logger.debug(\n        `t=${currentTime.toFixed(3)}s, mounted=${mountedCount}`\n      );\n    }\n  }\n\n  /**\n   * 비디오 미디어 연결\n   * @param media - 비디오 엘리먼트\n   */\n  attachMedia(media: HTMLVideoElement): void {\n    this.timeline.attachMedia(media);\n    this.stage.setMedia(media);\n\n    if (this.options.debugMode) {\n      console.log('[RendererV2] Media attached:', {\n        duration: media.duration,\n        readyState: media.readyState,\n        videoWidth: media.videoWidth,\n        videoHeight: media.videoHeight,\n      });\n    }\n  }\n\n  /**\n   * 미디어 분리\n   */\n  detachMedia(): void {\n    this.timeline.detachMedia();\n\n    if (this.options.debugMode) {\n      console.log('[RendererV2] Media detached');\n    }\n  }\n\n  /**\n   * 정리\n   */\n  dispose(): void {\n    this.timeline.dispose();\n    this.stage.dispose();\n    this.unmountAll();\n    this.scenario = null;\n  }\n}\n","// Applies composed style/layout transforms and CSS variables into DOM elements.\nimport type { Channels } from '../composer/PluginChainComposer';\nimport type { TextStyle, BoxStyle, Layout } from '../types/layout';\n\nexport function buildTransform(base: string | undefined, ch: Channels): string {\n  const parts: string[] = [];\n  if (base && base.length) parts.push(base);\n  const sx = ch.sx ?? 1;\n  const sy = ch.sy ?? 1;\n  if (sx !== 1 || sy !== 1) parts.push(`scale(${sx}, ${sy})`);\n  const tx = ch.tx ?? 0;\n  const ty = ch.ty ?? 0;\n  if (tx !== 0 || ty !== 0)\n    parts.push(\n      `translate(${Math.round(tx * 100) / 100}px, ${Math.round(ty * 100) / 100}px)`\n    );\n  const rot = ch.rot ?? 0;\n  if (rot !== 0) parts.push(`rotate(${rot}deg)`);\n  return parts.join(' ');\n}\n\nexport function applyChannels(\n  el: HTMLElement,\n  baseTransform: string | undefined,\n  ch: Channels\n) {\n  el.style.transform = buildTransform(baseTransform, ch);\n  if (ch.opacity != null) el.style.opacity = String(ch.opacity);\n}\n\nexport function applyTextStyle(\n  el: HTMLElement,\n  containerHeight: number,\n  style?: TextStyle,\n  trackDefault?: TextStyle\n) {\n  const s = { ...(trackDefault || {}), ...(style || {}) };\n  if (s.color) el.style.color = String(s.color);\n  if (s.textShadow) el.style.textShadow = String(s.textShadow);\n  // Typography\n  if (s.fontFamily) el.style.fontFamily = String(s.fontFamily);\n  if (s.fontWeight) el.style.fontWeight = String(s.fontWeight);\n  if (s.lineHeight != null) el.style.lineHeight = String(s.lineHeight);\n  // fontSize: support absolute(px, em, rem) via fontSize, or relative via fontSizeRel\n  if (s.fontSize) el.style.fontSize = String(s.fontSize);\n  else if (typeof s.fontSizeRel === 'number') {\n    const px = Math.max(1, Math.round(containerHeight * s.fontSizeRel));\n    el.style.fontSize = `${px}px`;\n  }\n  // Stroke: try -webkit-text-stroke if width provided, else fallback via stronger shadow if not present\n  if (s.stroke && typeof s.stroke.widthRel === 'number') {\n    const px = Math.max(1, Math.round(containerHeight * s.stroke.widthRel));\n    (el.style as any).webkitTextStrokeWidth = `${px}px`;\n    (el.style as any).webkitTextStrokeColor = String(s.stroke.color || '#000');\n    (el.style as any).webkitTextFillColor = el.style.color || '#fff';\n  } else if (!s.textShadow) {\n    // Default readable outline if none specified\n    el.style.textShadow = '0 0 2px #000, 0 0 4px #000, 0 0 8px #000';\n  }\n  // Text alignment\n  if (s.align) el.style.textAlign = s.align;\n  if (s.whiteSpace)\n    el.style.whiteSpace = s.whiteSpace === 'wrap' ? 'normal' : s.whiteSpace;\n}\n\n// Apply box/container styles for group nodes only\nexport function applyGroupStyle(\n  el: HTMLElement,\n  containerHeight: number,\n  boxStyle?: BoxStyle,\n  layout?: Layout\n) {\n  if (!boxStyle) return;\n\n  // Background color\n  if (boxStyle.backgroundColor) {\n    el.style.backgroundColor = String(boxStyle.backgroundColor);\n  } else if (boxStyle.boxBg) {\n    // Backward compatibility\n    el.style.backgroundColor = String(boxStyle.boxBg);\n  }\n\n  // Border\n  if (boxStyle.border) {\n    const wpx = Math.max(\n      0,\n      Math.round(containerHeight * (boxStyle.border.widthRel || 0))\n    );\n    el.style.borderStyle = 'solid';\n    el.style.borderWidth = `${wpx}px`;\n    el.style.borderColor = String(boxStyle.border.color || '#000');\n    if (boxStyle.border.radiusRel != null) {\n      const rpx = Math.max(\n        0,\n        Math.round(containerHeight * boxStyle.border.radiusRel)\n      );\n      el.style.borderRadius = `${rpx}px`;\n    }\n  }\n\n  // Border radius (direct CSS value)\n  if (boxStyle.borderRadius) {\n    if (typeof boxStyle.borderRadius === 'string') {\n      el.style.borderRadius = boxStyle.borderRadius;\n    } else {\n      const rpx = Math.max(\n        0,\n        Math.round(containerHeight * boxStyle.borderRadius)\n      );\n      el.style.borderRadius = `${rpx}px`;\n    }\n  }\n\n  // Padding\n  if (boxStyle.padding) {\n    if (typeof boxStyle.padding === 'string') {\n      el.style.padding = boxStyle.padding;\n    } else {\n      // Vec2Rel format\n      const px = Math.max(\n        0,\n        Math.round(containerHeight * (boxStyle.padding.x || 0))\n      );\n      const py = Math.max(\n        0,\n        Math.round(containerHeight * (boxStyle.padding.y || 0))\n      );\n      el.style.padding = `${py}px ${px}px`;\n    }\n  } else if (layout?.padding) {\n    // Fallback to layout padding\n    const px = Math.max(\n      0,\n      Math.round(containerHeight * (layout.padding.x || 0))\n    );\n    const py = Math.max(\n      0,\n      Math.round(containerHeight * (layout.padding.y || 0))\n    );\n    el.style.padding = `${py}px ${px}px`;\n  }\n\n  // Opacity\n  if (boxStyle.opacity != null) {\n    el.style.opacity = String(boxStyle.opacity);\n  }\n}\n","import type { DefineAsset, AssetContext } from '../types/scenario-v2';\n\n/**\n * AssetManager\n *\n * 기본 에셋 관리 (보안 검증 제외)\n * - 에셋 타입별 URL 해석 (font, image, video, audio)\n * - FontFace 기본 등록/해제 (무결성 검증 없이)\n * - Define 섹션의 에셋 참조 처리\n *\n * 참고: M7에서 SHA-384 해시 및 서명 검증 추가 예정\n */\nexport class AssetManager {\n  private readonly context: AssetContext;\n  private readonly loadedFonts = new Map<string, FontFace>();\n  private readonly baseUrl: string;\n\n  constructor(baseUrl: string = '') {\n    this.baseUrl = baseUrl;\n    this.context = {\n      baseUrl,\n      fonts: new Map(),\n      images: new Map(),\n      videos: new Map(),\n      audios: new Map(),\n    };\n  }\n\n  /**\n   * Define 섹션에서 에셋을 추출하고 로드\n   * @param defines Define 섹션 객체\n   * @returns Promise<void>\n   */\n  async loadAssetsFromDefines(defines: Record<string, any>): Promise<void> {\n    const assets = this.extractAssetsFromDefines(defines);\n    await this.loadAssets(assets);\n  }\n\n  /**\n   * Define 섹션에서 에셋 정의 추출\n   * @param defines Define 섹션 객체\n   * @returns 추출된 에셋 배열\n   */\n  private extractAssetsFromDefines(\n    defines: Record<string, any>\n  ): Array<{ key: string; asset: DefineAsset }> {\n    const assets: Array<{ key: string; asset: DefineAsset }> = [];\n\n    for (const [key, value] of Object.entries(defines)) {\n      if (this.isAssetDefinition(value)) {\n        assets.push({ key, asset: value as DefineAsset });\n      }\n    }\n\n    return assets;\n  }\n\n  /**\n   * 값이 에셋 정의인지 확인\n   * @param value 확인할 값\n   * @returns 에셋 정의인 경우 true\n   */\n  private isAssetDefinition(value: any): boolean {\n    return (\n      typeof value === 'object' &&\n      value !== null &&\n      typeof value.type === 'string' &&\n      ['font', 'image', 'video', 'audio'].includes(value.type) &&\n      typeof value.url === 'string'\n    );\n  }\n\n  /**\n   * 에셋 배열을 순차적으로 로드\n   * @param assets 로드할 에셋 배열\n   */\n  private async loadAssets(\n    assets: Array<{ key: string; asset: DefineAsset }>\n  ): Promise<void> {\n    for (const { key, asset } of assets) {\n      try {\n        await this.loadSingleAsset(key, asset);\n      } catch (error) {\n        console.warn(`Failed to load asset \"${key}\":`, error);\n        // 에셋 로드 실패는 에러를 발생시키지 않고 경고만 출력\n        // 프로덕션에서는 로깅 시스템으로 대체\n      }\n    }\n  }\n\n  /**\n   * 개별 에셋을 로드\n   * @param key 에셋 키\n   * @param asset 에셋 정의\n   */\n  private async loadSingleAsset(\n    key: string,\n    asset: DefineAsset\n  ): Promise<void> {\n    const resolvedUrl = this.resolveUrl(asset.url || asset.src);\n\n    switch (asset.type) {\n      case 'font':\n        await this.loadFont(key, resolvedUrl);\n        break;\n      case 'image':\n        await this.preloadImage(key, resolvedUrl);\n        break;\n      case 'video':\n        this.registerVideo(key, resolvedUrl);\n        break;\n      case 'audio':\n        this.registerAudio(key, resolvedUrl);\n        break;\n      default:\n        throw new Error(`Unsupported asset type: ${(asset as any).type}`);\n    }\n  }\n\n  /**\n   * 폰트 로드 및 등록\n   * @param key 폰트 키\n   * @param url 폰트 URL\n   */\n  private async loadFont(key: string, url: string): Promise<void> {\n    // M7에서 무결성 검증 추가 예정\n    // if (asset.integrity) {\n    //   await this.verifyIntegrity(url, asset.integrity);\n    // }\n\n    try {\n      const fontFace = new FontFace(key, `url(${url})`);\n      await fontFace.load();\n\n      // 브라우저 폰트 컨테이너에 추가\n      document.fonts.add(fontFace);\n\n      // 컨텍스트에 등록\n      this.context.fonts.set(key, fontFace);\n      this.loadedFonts.set(key, fontFace);\n    } catch (error) {\n      throw new Error(`Failed to load font \"${key}\" from ${url}: ${error}`);\n    }\n  }\n\n  /**\n   * 이미지 프리로드\n   * @param key 이미지 키\n   * @param url 이미지 URL\n   */\n  private async preloadImage(key: string, url: string): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const img = new Image();\n\n      img.onload = () => {\n        this.context.images.set(key, url);\n        resolve();\n      };\n\n      img.onerror = () => {\n        reject(new Error(`Failed to preload image: ${url}`));\n      };\n\n      img.src = url;\n    });\n  }\n\n  /**\n   * 비디오 URL 등록 (프리로드 없음)\n   * @param key 비디오 키\n   * @param url 비디오 URL\n   */\n  private registerVideo(key: string, url: string): void {\n    this.context.videos.set(key, url);\n  }\n\n  /**\n   * 오디오 URL 등록 (프리로드 없음)\n   * @param key 오디오 키\n   * @param url 오디오 URL\n   */\n  private registerAudio(key: string, url: string): void {\n    this.context.audios.set(key, url);\n  }\n\n  /**\n   * 상대 URL을 절대 URL로 해석\n   * @param url 해석할 URL\n   * @returns 절대 URL\n   */\n  private resolveUrl(url: string): string {\n    // 이미 절대 URL인 경우 그대로 반환\n    if (\n      url.startsWith('http://') ||\n      url.startsWith('https://') ||\n      url.startsWith('data:')\n    ) {\n      return url;\n    }\n\n    // 상대 URL인 경우 baseUrl과 결합\n    if (this.baseUrl) {\n      return new URL(url, this.baseUrl).href;\n    }\n\n    // baseUrl이 없으면 현재 페이지 기준\n    return new URL(url, window.location.href).href;\n  }\n\n  /**\n   * 에셋 URL 조회 (플러그인에서 사용)\n   * @param key 에셋 키\n   * @param type 에셋 타입 (선택적)\n   * @returns 에셋 URL 또는 null\n   */\n  getAssetUrl(key: string, type?: DefineAsset['type']): string | null {\n    // 타입별 우선 검색\n    if (type) {\n      switch (type) {\n        case 'font':\n          return this.context.fonts.has(key) ? key : null; // 폰트는 family name으로 사용\n        case 'image':\n          return this.context.images.get(key) || null;\n        case 'video':\n          return this.context.videos.get(key) || null;\n        case 'audio':\n          return this.context.audios.get(key) || null;\n      }\n    }\n\n    // 타입 지정 없이 모든 타입에서 검색\n    return (\n      this.context.images.get(key) ||\n      this.context.videos.get(key) ||\n      this.context.audios.get(key) ||\n      (this.context.fonts.has(key) ? key : null)\n    );\n  }\n\n  /**\n   * 폰트 로드 상태 확인\n   * @param key 폰트 키\n   * @returns 로드된 경우 true\n   */\n  isFontLoaded(key: string): boolean {\n    return this.loadedFonts.has(key);\n  }\n\n  /**\n   * 모든 로드된 폰트 제거 (클린업용)\n   */\n  dispose(): void {\n    // 로드된 폰트들을 브라우저에서 제거\n    for (const fontFace of this.loadedFonts.values()) {\n      document.fonts.delete(fontFace);\n    }\n\n    // 컨텍스트 클리어\n    this.context.fonts.clear();\n    this.context.images.clear();\n    this.context.videos.clear();\n    this.context.audios.clear();\n    this.loadedFonts.clear();\n  }\n\n  /**\n   * 에셋 컨텍스트 반환 (플러그인 컨텍스트에서 사용)\n   * @returns 에셋 컨텍스트\n   */\n  getContext(): AssetContext {\n    return this.context;\n  }\n\n  /**\n   * 로드 통계 반환\n   * @returns 로드된 에셋 통계\n   */\n  getLoadStats(): {\n    fonts: number;\n    images: number;\n    videos: number;\n    audios: number;\n    total: number;\n  } {\n    const stats = {\n      fonts: this.context.fonts.size,\n      images: this.context.images.size,\n      videos: this.context.videos.size,\n      audios: this.context.audios.size,\n      total: 0,\n    };\n\n    stats.total = stats.fonts + stats.images + stats.videos + stats.audios;\n    return stats;\n  }\n\n  /**\n   * 모든 에셋 키 목록 반환\n   * @param type 특정 타입만 필터링 (선택적)\n   * @returns 에셋 키 배열\n   */\n  getAllAssetKeys(type?: DefineAsset['type']): string[] {\n    if (type) {\n      switch (type) {\n        case 'font':\n          return Array.from(this.context.fonts.keys());\n        case 'image':\n          return Array.from(this.context.images.keys());\n        case 'video':\n          return Array.from(this.context.videos.keys());\n        case 'audio':\n          return Array.from(this.context.audios.keys());\n        default:\n          return [];\n      }\n    }\n\n    // 모든 타입의 키 합계\n    return [\n      ...this.context.fonts.keys(),\n      ...this.context.images.keys(),\n      ...this.context.videos.keys(),\n      ...this.context.audios.keys(),\n    ];\n  }\n}\n","// Controller UI overlay for MotionTextRenderer\n// YouTube-style UI with react-icons SVG integration\n\nimport type { MotionTextRenderer } from '../index';\n\n// Inline SVG icons from react-icons/md\nconst ICONS = {\n  play: '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 24 24\" height=\"1em\" width=\"1em\"><path d=\"M8 5v14l11-7z\"></path></svg>',\n  pause:\n    '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 24 24\" height=\"1em\" width=\"1em\"><path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\"></path></svg>',\n  volumeUp:\n    '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 24 24\" height=\"1em\" width=\"1em\"><path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\"></path></svg>',\n  volumeOff:\n    '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 24 24\" height=\"1em\" width=\"1em\"><path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\"></path></svg>',\n  closedCaption:\n    '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 24 24\" height=\"1em\" width=\"1em\"><path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z\"></path></svg>',\n  closedCaptionDisabled:\n    '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 24 24\" height=\"1em\" width=\"1em\"><path d=\"M6.83 4H19c1.1 0 2 .9 2 2v12c0 .05-.01.1-.02.16L19 16.17V6H8.83L6.83 4zM20.49 23.31L17.17 20H5c-1.11 0-2-.9-2-2V6c0-.05.02-.1.02-.15L1.39 4.22 2.8 2.81l18.38 18.38-1.31 1.31-.68-.67zM9.5 10.5v.5h-2v1.17L6 10.67V10c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v.17l-1.5-1.5v.33zm7 0v.5h-2v1.17L13 10.67V10c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v.17l-1.5-1.5v.33z\"></path></svg>',\n  fullscreen:\n    '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 24 24\" height=\"1em\" width=\"1em\"><path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\"></path></svg>',\n  fullscreenExit:\n    '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 24 24\" height=\"1em\" width=\"1em\"><path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\"></path></svg>',\n};\n\nexport interface MotionTextControllerOptions {\n  captionsVisible?: boolean;\n}\n\nexport class MotionTextController {\n  private video: HTMLVideoElement;\n  private renderer: MotionTextRenderer;\n  private container: HTMLElement; // container wrapping video + overlay\n  private root: HTMLElement | null = null;\n  private styleEl: HTMLStyleElement | null = null;\n  private captionsOn = true;\n  private timeLabel: HTMLElement | null = null;\n  private seekEl: HTMLInputElement | null = null;\n  private volumeEl: HTMLInputElement | null = null;\n  private cleanupFns: Array<() => void> = [];\n  private prevVideoStyle: Partial<CSSStyleDeclaration> | null = null;\n  private idleTimer: number | null = null;\n  private idleMs = 2200;\n  private volumePopupTimer: number | null = null;\n\n  constructor(\n    video: HTMLVideoElement,\n    renderer: MotionTextRenderer,\n    container: HTMLElement,\n    opts: MotionTextControllerOptions = {}\n  ) {\n    this.video = video;\n    this.renderer = renderer;\n    this.container = container;\n    if (opts.captionsVisible != null) this.captionsOn = !!opts.captionsVisible;\n  }\n\n  mount() {\n    if (this.root) return;\n    this.injectStyles();\n    const root = document.createElement('div');\n    root.setAttribute('data-mtx-controls', '');\n    root.style.outline = 'none';\n    root.style.position = 'absolute';\n    root.style.left = '0';\n    root.style.right = '0';\n    root.style.bottom = '0';\n    root.style.pointerEvents = 'auto';\n    root.style.zIndex = '30';\n    root.style.color = '#fff';\n    root.style.fontFamily = 'Roboto, Arial, sans-serif';\n    root.style.display = 'flex';\n    root.style.flexDirection = 'column';\n\n    // Progress bar (YouTube-style, at the very bottom)\n    const progressContainer = document.createElement('div');\n    progressContainer.className = 'mtx-progress-container';\n\n    const seek = document.createElement('input');\n    seek.type = 'range';\n    seek.min = '0';\n    seek.max = '0';\n    seek.step = '0.01';\n    seek.value = '0';\n    seek.className = 'mtx-progress-bar';\n    seek.oninput = () => {\n      const v = Number(seek.value);\n      if (Number.isFinite(v)) {\n        const dur = this.video.duration;\n        if (Number.isFinite(dur) && dur > 0) {\n          this.video.currentTime = Math.max(0, Math.min(dur, v));\n        }\n      }\n    };\n    this.seekEl = seek;\n    progressContainer.appendChild(seek);\n\n    // Control bar (YouTube-style layout)\n    const controlBar = document.createElement('div');\n    controlBar.className = 'mtx-control-bar';\n\n    // Left side controls\n    const leftControls = document.createElement('div');\n    leftControls.className = 'mtx-controls-left';\n\n    // Play/pause button\n    const btnPlay = document.createElement('button');\n    btnPlay.className = 'mtx-btn mtx-play-btn';\n    btnPlay.innerHTML = this.video.paused ? ICONS.play : ICONS.pause;\n    btnPlay.setAttribute('aria-label', '재생/일시정지');\n    btnPlay.onclick = () => {\n      if (this.video.paused) this.video.play();\n      else this.video.pause();\n    };\n\n    // Volume group (button + slider)\n    const volumeGroup = document.createElement('div');\n    volumeGroup.className = 'mtx-volume-group';\n\n    const btnVolume = document.createElement('button');\n    btnVolume.className = 'mtx-btn mtx-volume-btn';\n    btnVolume.innerHTML =\n      this.video.volume > 0 ? ICONS.volumeUp : ICONS.volumeOff;\n    btnVolume.setAttribute('aria-label', '음소거');\n    btnVolume.onclick = () => {\n      this.video.muted = !this.video.muted;\n      if (this.video.muted) {\n        btnVolume.innerHTML = ICONS.volumeOff;\n        this.video.volume = 0;\n      } else {\n        btnVolume.innerHTML = ICONS.volumeUp;\n        this.video.volume = this.volumeEl?.value\n          ? Number(this.volumeEl.value)\n          : 1;\n      }\n    };\n\n    const vol = document.createElement('input');\n    vol.type = 'range';\n    vol.min = '0';\n    vol.max = '1';\n    vol.step = '0.01';\n    vol.value = String(this.video.volume ?? 1);\n    vol.className = 'mtx-volume-slider';\n    vol.oninput = () => {\n      const v = Number(vol.value);\n      if (Number.isFinite(v)) {\n        this.video.volume = Math.max(0, Math.min(1, v));\n        this.video.muted = v === 0;\n        btnVolume.innerHTML = v > 0 ? ICONS.volumeUp : ICONS.volumeOff;\n      }\n    };\n    this.volumeEl = vol;\n\n    // Volume popup\n    const volumePopup = document.createElement('div');\n    volumePopup.className = 'mtx-volume-popup';\n\n    const volumeTrack = document.createElement('div');\n    volumeTrack.className = 'mtx-volume-track';\n\n    const volVertical = document.createElement('input');\n    volVertical.type = 'range';\n    volVertical.min = '0';\n    volVertical.max = '1';\n    volVertical.step = '0.01';\n    volVertical.value = String(this.video.volume ?? 1);\n    volVertical.className = 'mtx-volume-vertical';\n    volVertical.setAttribute('orient', 'vertical');\n    volVertical.oninput = () => {\n      const v = Number(volVertical.value);\n      if (Number.isFinite(v)) {\n        this.video.volume = Math.max(0, Math.min(1, v));\n        this.video.muted = v === 0;\n        btnVolume.innerHTML = v > 0 ? ICONS.volumeUp : ICONS.volumeOff;\n        // Update horizontal slider for consistency (if it exists)\n        if (this.volumeEl) this.volumeEl.value = String(v);\n        // Update visual progress with red gradient\n        this.updateVolumeSliderVisual(volVertical, v);\n      }\n    };\n\n    // Initialize volume slider visual\n    this.updateVolumeSliderVisual(volVertical, Number(volVertical.value));\n\n    volumeTrack.appendChild(volVertical);\n    volumePopup.appendChild(volumeTrack);\n\n    volumeGroup.appendChild(btnVolume);\n    volumeGroup.appendChild(volumePopup);\n\n    // Keep the old horizontal slider for backward compatibility but hide it\n    vol.style.display = 'none';\n    volumeGroup.appendChild(vol);\n\n    // Update volumeEl to point to the vertical slider\n    this.volumeEl = volVertical;\n\n    // Add precise hover events for volume popup control\n    const onVolumeButtonEnter = () => this.showVolumePopup();\n    const onVolumeGroupLeave = () => this.scheduleHideVolumePopup();\n    const onVolumePopupEnter = () => this.cancelHideVolumePopup();\n\n    btnVolume.addEventListener('mouseenter', onVolumeButtonEnter);\n    volumeGroup.addEventListener('mouseleave', onVolumeGroupLeave);\n    volumePopup.addEventListener('mouseenter', onVolumePopupEnter);\n\n    this.cleanupFns.push(() => {\n      btnVolume.removeEventListener('mouseenter', onVolumeButtonEnter);\n      volumeGroup.removeEventListener('mouseleave', onVolumeGroupLeave);\n      volumePopup.removeEventListener('mouseenter', onVolumePopupEnter);\n    });\n\n    // Time display\n    const time = document.createElement('span');\n    time.className = 'mtx-time';\n    this.timeLabel = time;\n\n    leftControls.appendChild(btnPlay);\n    leftControls.appendChild(volumeGroup);\n    leftControls.appendChild(time);\n\n    // Right side controls\n    const rightControls = document.createElement('div');\n    rightControls.className = 'mtx-controls-right';\n\n    // Captions button\n    const btnSub = document.createElement('button');\n    btnSub.className = 'mtx-btn mtx-caption-btn';\n    btnSub.innerHTML = this.captionsOn\n      ? ICONS.closedCaption\n      : ICONS.closedCaptionDisabled;\n    btnSub.setAttribute('aria-pressed', String(this.captionsOn));\n    btnSub.setAttribute('aria-label', '자막 토글');\n    btnSub.onclick = () => {\n      this.captionsOn = !this.captionsOn;\n      this.renderer.setCaptionsVisible(this.captionsOn);\n      btnSub.innerHTML = this.captionsOn\n        ? ICONS.closedCaption\n        : ICONS.closedCaptionDisabled;\n      btnSub.setAttribute('aria-pressed', String(this.captionsOn));\n    };\n\n    // Fullscreen button\n    const btnFs = document.createElement('button');\n    btnFs.className = 'mtx-btn mtx-fullscreen-btn';\n    btnFs.innerHTML = document.fullscreenElement\n      ? ICONS.fullscreenExit\n      : ICONS.fullscreen;\n    btnFs.setAttribute('aria-label', '전체화면');\n    btnFs.onclick = () => {\n      this.requestFullscreen();\n    };\n\n    rightControls.appendChild(btnSub);\n    rightControls.appendChild(btnFs);\n\n    controlBar.appendChild(leftControls);\n    controlBar.appendChild(rightControls);\n\n    root.appendChild(progressContainer);\n    root.appendChild(controlBar);\n    this.container.appendChild(root);\n    this.root = root;\n\n    // Initial captions visibility\n    this.renderer.setCaptionsVisible(this.captionsOn);\n\n    // Events to keep UI in sync\n    const onTime = () => this.updateUI();\n    const onMeta = () => this.updateUI(true);\n    this.video.addEventListener('timeupdate', onTime);\n    this.video.addEventListener('seeked', onTime);\n    this.video.addEventListener('durationchange', onMeta);\n    this.video.addEventListener('loadedmetadata', onMeta);\n    this.video.addEventListener('volumechange', onMeta);\n    document.addEventListener('fullscreenchange', onMeta);\n    this.cleanupFns.push(() => {\n      this.video.removeEventListener('timeupdate', onTime);\n      this.video.removeEventListener('seeked', onTime);\n      this.video.removeEventListener('durationchange', onMeta);\n      this.video.removeEventListener('loadedmetadata', onMeta);\n      this.video.removeEventListener('volumechange', onMeta);\n      document.removeEventListener('fullscreenchange', onMeta);\n    });\n\n    // Keyboard accessibility\n    const onKey = (e: KeyboardEvent) => {\n      if (!this.root) return;\n\n      // Only handle keyboard events when controller or its children have focus\n      if (\n        !this.root.contains(document.activeElement) &&\n        document.activeElement !== this.root\n      ) {\n        return; // Ignore keyboard events when controller is not focused\n      }\n\n      // Show controls and reset hide timer on keyboard interaction\n      this.showControls();\n      this.scheduleHide();\n\n      // Prevent event bubbling to avoid conflicts with other keyboard handlers\n      e.stopPropagation();\n\n      if (e.key === ' ') {\n        // Space\n        e.preventDefault();\n        if (this.video.paused) this.video.play();\n        else this.video.pause();\n      } else if (e.key === 'ArrowRight') {\n        this.video.currentTime = Math.min(\n          this.video.duration || Infinity,\n          this.video.currentTime + 5\n        );\n      } else if (e.key === 'ArrowLeft') {\n        this.video.currentTime = Math.max(0, this.video.currentTime - 5);\n      } else if (e.key === 'Escape') {\n        if (document.fullscreenElement) document.exitFullscreen();\n      }\n    };\n    root.tabIndex = 0; // focusable for key events\n    root.style.outline = 'none'; // remove focus outline\n    root.addEventListener('keydown', onKey);\n    this.cleanupFns.push(() => root.removeEventListener('keydown', onKey));\n    // Focus root on enter/click so keys work also in non-fullscreen when interacting\n    const focusRoot = () => root.focus({ preventScroll: true });\n    const onEnter = () => {\n      this.showControls();\n      focusRoot();\n    };\n    const onLeave = () => {\n      this.scheduleHide();\n    };\n    const onMove = () => {\n      this.showControls();\n      this.scheduleHide();\n    };\n\n    // Video click to play/pause (YouTube-style)\n    const onVideoClick = (e: MouseEvent) => {\n      // Prevent play/pause when clicking on controls\n      if (this.root && this.root.contains(e.target as Node)) {\n        // Clicked on controls, only focus\n        focusRoot();\n        return;\n      }\n\n      // Clicked on video area, toggle play/pause\n      if (this.video.paused) {\n        this.video.play();\n      } else {\n        this.video.pause();\n      }\n\n      // Also show controls briefly and focus for keyboard access\n      this.showControls();\n      this.scheduleHide();\n      focusRoot();\n    };\n\n    // Double-click fullscreen toggle (YouTube UX)\n    const onVideoDoubleClick = (e: MouseEvent) => {\n      // Prevent when clicking on controls\n      if (this.root && this.root.contains(e.target as Node)) return;\n\n      this.requestFullscreen();\n    };\n\n    this.container.addEventListener('mouseenter', onEnter);\n    this.container.addEventListener('mouseleave', onLeave);\n    this.container.addEventListener('mousemove', onMove);\n    this.container.addEventListener('click', onVideoClick);\n    this.container.addEventListener('dblclick', onVideoDoubleClick);\n    this.cleanupFns.push(() => {\n      this.container.removeEventListener('mouseenter', onEnter);\n      this.container.removeEventListener('mouseleave', onLeave);\n      this.container.removeEventListener('mousemove', onMove);\n      this.container.removeEventListener('click', onVideoClick);\n      this.container.removeEventListener('dblclick', onVideoDoubleClick);\n    });\n\n    // reflect play/pause icon\n    const onPlay = () => {\n      btnPlay.innerHTML = ICONS.pause;\n    };\n    const onPause = () => {\n      btnPlay.innerHTML = ICONS.play;\n    };\n\n    // reflect fullscreen icon\n    const onFsChange = () => {\n      btnFs.innerHTML = document.fullscreenElement\n        ? ICONS.fullscreenExit\n        : ICONS.fullscreen;\n    };\n    document.addEventListener('fullscreenchange', onFsChange);\n    this.cleanupFns.push(() =>\n      document.removeEventListener('fullscreenchange', onFsChange)\n    );\n    this.video.addEventListener('play', onPlay);\n    this.video.addEventListener('pause', onPause);\n    this.cleanupFns.push(() => {\n      this.video.removeEventListener('play', onPlay);\n      this.video.removeEventListener('pause', onPause);\n    });\n\n    // First paint and reserve safe bottom for captions\n    this.updateUI(true);\n    this.reserveCaptionSafeArea();\n\n    // Apply native-like fit behavior only in fullscreen so video centers with symmetric letterboxing\n    const applyFsFit = () => {\n      const fs = !!document.fullscreenElement;\n      if (fs) {\n        if (!this.prevVideoStyle) {\n          this.prevVideoStyle = {\n            width: this.video.style.width,\n            height: this.video.style.height,\n            objectFit: this.video.style.objectFit as any,\n            backgroundColor: this.video.style.backgroundColor,\n          } as Partial<CSSStyleDeclaration>;\n        }\n        this.video.style.width = '100%';\n        this.video.style.height = '100%';\n        (this.video.style as any).objectFit = 'contain';\n        this.video.style.backgroundColor = '#000';\n      } else if (this.prevVideoStyle) {\n        this.video.style.width = this.prevVideoStyle.width ?? '';\n        this.video.style.height = this.prevVideoStyle.height ?? '';\n        (this.video.style as any).objectFit =\n          (this.prevVideoStyle as any).objectFit ?? '';\n        this.video.style.backgroundColor =\n          this.prevVideoStyle.backgroundColor ?? '';\n      }\n    };\n    applyFsFit();\n    const onFs = () => applyFsFit();\n    document.addEventListener('fullscreenchange', onFs);\n    this.cleanupFns.push(() =>\n      document.removeEventListener('fullscreenchange', onFs)\n    );\n\n    // start with controls visible then schedule hide\n    this.showControls();\n    this.scheduleHide();\n  }\n\n  unmount() {\n    // Clear volume popup timer\n    this.cancelHideVolumePopup();\n\n    if (this.root && this.root.parentElement)\n      this.root.parentElement.removeChild(this.root);\n    this.root = null;\n    if (this.styleEl && this.styleEl.parentNode)\n      this.styleEl.parentNode.removeChild(this.styleEl);\n    this.styleEl = null;\n    this.cleanupFns.forEach((fn) => {\n      try {\n        fn();\n      } catch (e) {\n        /* noop */\n      }\n    });\n    this.cleanupFns = [];\n  }\n\n  setCaptionsVisible(visible: boolean) {\n    this.captionsOn = !!visible;\n    this.renderer.setCaptionsVisible(this.captionsOn);\n  }\n\n  async requestFullscreen() {\n    const el: any = this.container as any;\n    if (document.fullscreenElement) {\n      await document.exitFullscreen?.();\n      return;\n    }\n    await el.requestFullscreen?.();\n  }\n\n  private injectStyles() {\n    if (this.styleEl) return;\n    // legacy css (unused)\n    const el = document.createElement('style');\n    el.setAttribute('data-mtx-controls-style', '');\n    // overwrite with white-themed controls and full-width seek bar\n    el.textContent = `\n      /* YouTube-style controls */\n      [data-mtx-controls] {\n        background: linear-gradient(transparent, rgba(0,0,0,0.7));\n        padding: 0;\n        transition: opacity 0.2s ease;\n      }\n      \n      /* Remove all focus outlines and tap highlights */\n      [data-mtx-controls]:focus,\n      [data-mtx-controls] *:focus,\n      [data-mtx-controls] *:focus-visible {\n        outline: none !important;\n        box-shadow: none !important;\n      }\n      \n      [data-mtx-controls],\n      [data-mtx-controls] * {\n        -webkit-tap-highlight-color: transparent;\n        -webkit-focus-ring-color: transparent;\n      }\n      \n      .mtx-progress-container {\n        position: relative;\n        height: 5px;\n        padding: 35px 12px 20px;\n      }\n      \n      .mtx-progress-bar {\n        width: 100%;\n        height: 3px;\n        background: transparent;\n        cursor: pointer;\n        -webkit-appearance: none;\n        appearance: none;\n        transition: height 0.1s ease;\n        outline: none;\n      }\n      \n      .mtx-progress-bar:focus {\n        outline: none;\n        box-shadow: none;\n      }\n      \n      .mtx-progress-bar::-webkit-slider-track {\n        background: rgba(255,255,255,0.3);\n        height: 3px;\n        border-radius: 2px;\n      }\n      \n      .mtx-progress-bar::-webkit-slider-thumb {\n        -webkit-appearance: none;\n        appearance: none;\n        width: 12px;\n        height: 12px;\n        border-radius: 50%;\n        background: #ff0000;\n        cursor: pointer;\n        opacity: 0;\n        transition: opacity 0.1s ease;\n      }\n      \n      .mtx-progress-bar::-moz-range-track {\n        background: rgba(255,255,255,0.3);\n        height: 3px;\n        border-radius: 2px;\n        border: none;\n      }\n      \n      .mtx-progress-bar::-moz-range-thumb {\n        width: 12px;\n        height: 12px;\n        border-radius: 50%;\n        background: #ff0000;\n        cursor: pointer;\n        border: none;\n        opacity: 0;\n        transition: opacity 0.1s ease;\n      }\n      \n      .mtx-progress-bar::-moz-range-progress {\n        background: #ff0000;\n        height: 3px;\n        border-radius: 2px;\n      }\n      \n      .mtx-progress-container:hover .mtx-progress-bar {\n        height: 5px;\n      }\n      \n      .mtx-progress-container:hover .mtx-progress-bar::-webkit-slider-thumb,\n      .mtx-progress-container:hover .mtx-progress-bar::-moz-range-thumb {\n        opacity: 1;\n      }\n      \n      .mtx-control-bar {\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n        padding: 0 12px 12px;\n        min-height: 40px;\n      }\n      \n      .mtx-controls-left,\n      .mtx-controls-right {\n        display: flex;\n        align-items: center;\n        gap: 8px;\n      }\n      \n      .mtx-btn {\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        width: 36px;\n        height: 36px;\n        padding: 0;\n        background: transparent;\n        border: none;\n        border-radius: 50%;\n        color: #fff;\n        cursor: pointer;\n        transition: background-color 0.1s ease, transform 0.1s ease;\n        font-size: 18px;\n      }\n      \n      .mtx-btn:hover {\n        background: rgba(255,255,255,0.2);\n        transform: scale(1.05);\n      }\n      \n      .mtx-btn:active {\n        transform: scale(0.95);\n      }\n      \n      .mtx-btn:focus,\n      .mtx-btn:focus-visible {\n        outline: none !important;\n        box-shadow: none !important;\n      }\n      \n      .mtx-btn[aria-pressed=\"true\"]:not(.mtx-caption-btn) {\n        background: rgba(255,255,255,0.3);\n      }\n      \n      .mtx-volume-group {\n        position: relative;\n        display: flex;\n        align-items: center;\n      }\n      \n      .mtx-volume-popup {\n        position: absolute;\n        bottom: 100%;\n        left: 50%;\n        transform: translateX(-50%) translateY(10px);\n        opacity: 0;\n        pointer-events: none;\n        transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n        z-index: 50;\n        margin-bottom: 5px;\n      }\n      \n      .mtx-volume-track {\n        position: relative;\n        width: 32px;\n        height: 100px;\n        background: rgba(0,0,0,0.8);\n        border-radius: 6px;\n        padding: 8px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n      }\n      \n      .mtx-volume-vertical {\n        -webkit-appearance: none;\n        -moz-appearance: none;\n        appearance: none;\n        writing-mode: vertical-lr;\n        direction: rtl;\n        width: 4px;\n        height: 84px;\n        background: transparent;\n        outline: none;\n        border: none;\n        border-radius: 2px;\n        cursor: pointer;\n      }\n      \n      .mtx-volume-vertical:focus {\n        outline: none;\n        box-shadow: none;\n      }\n      \n      .mtx-volume-vertical::-webkit-slider-track {\n        -webkit-appearance: none;\n        width: 4px;\n        height: 84px;\n        background: rgba(255,255,255,0.3);\n        border-radius: 2px;\n        border: none;\n      }\n      \n      .mtx-volume-vertical::-webkit-slider-thumb {\n        -webkit-appearance: none;\n        appearance: none;\n        width: 12px;\n        height: 12px;\n        border-radius: 50%;\n        background: #fff;\n        cursor: pointer;\n        border: none;\n        margin: 0;\n      }\n      \n      .mtx-volume-vertical::-moz-range-track {\n        width: 4px;\n        height: 84px;\n        background: rgba(255,255,255,0.3);\n        border-radius: 2px;\n        border: none;\n      }\n      \n      .mtx-volume-vertical::-moz-range-thumb {\n        width: 12px;\n        height: 12px;\n        border-radius: 50%;\n        background: #fff;\n        cursor: pointer;\n        border: none;\n        margin: 0;\n      }\n      \n      .mtx-volume-vertical::-moz-range-progress {\n        background: #ff0000;\n        border-radius: 2px;\n      }\n      \n      .mtx-time {\n        font-size: 13px;\n        font-weight: 400;\n        color: #fff;\n        margin-left: 8px;\n        white-space: nowrap;\n      }\n    `;\n    document.head.appendChild(el);\n    this.styleEl = el;\n  }\n\n  destroy() {\n    this.unmount();\n  }\n\n  private updateUI(reset = false) {\n    const dur = this.video.duration;\n    const cur = this.video.currentTime;\n    if (this.timeLabel)\n      this.timeLabel.textContent = `${this.fmt(cur)} / ${Number.isFinite(dur) ? this.fmt(dur) : '0:00'}`;\n    if (this.seekEl) {\n      if (reset && Number.isFinite(dur)) this.seekEl.max = String(dur);\n      if (Number.isFinite(cur)) this.seekEl.value = String(cur);\n      // Update progress bar visual fill (for webkit)\n      if (Number.isFinite(dur) && dur > 0) {\n        const progress = (cur / dur) * 100;\n        this.seekEl.style.background = `linear-gradient(to right, #ff0000 0%, #ff0000 ${progress}%, rgba(255,255,255,0.3) ${progress}%, rgba(255,255,255,0.3) 100%)`;\n      }\n    }\n    if (this.volumeEl) this.volumeEl.value = String(this.video.volume ?? 1);\n    // Keep caption safe area updated (controller height may vary in fullscreen)\n    this.reserveCaptionSafeArea();\n  }\n\n  private fmt(t: number): string {\n    if (!Number.isFinite(t)) return '0:00';\n    const m = Math.floor(t / 60);\n    const s = Math.floor(t % 60);\n    return `${m}:${String(s).padStart(2, '0')}`;\n  }\n\n  private reserveCaptionSafeArea() {\n    if (!this.root) return;\n    const rect = this.root.getBoundingClientRect();\n    // Pass the current controller height to renderer so it can shrink caption overlay\n    this.renderer.setControlSafeBottom(rect.height);\n  }\n\n  private updateVolumeSliderVisual(slider: HTMLInputElement, value: number) {\n    const percent = (1 - value) * 100;\n    // Create YouTube-style red gradient for vertical slider\n    // Since direction:rtl and writing-mode:vertical-lr, we need to reverse the gradient\n    slider.style.background = `linear-gradient(to bottom, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0.3) ${percent}%, #ff0000 ${percent}%, #ff0000 100%)`;\n\n    // Force override any browser default styles\n    slider.style.setProperty(\n      'background',\n      `linear-gradient(to bottom, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0.3) ${percent}%, #ff0000 ${percent}%, #ff0000 100%)`,\n      'important'\n    );\n  }\n\n  private showVolumePopup() {\n    this.cancelHideVolumePopup();\n    const popup = this.root?.querySelector('.mtx-volume-popup') as HTMLElement;\n    if (popup) {\n      popup.style.opacity = '1';\n      popup.style.transform = 'translateX(-50%) translateY(0)';\n      popup.style.pointerEvents = 'auto';\n    }\n  }\n\n  private scheduleHideVolumePopup() {\n    this.cancelHideVolumePopup();\n    this.volumePopupTimer = window.setTimeout(() => {\n      this.hideVolumePopup();\n    }, 1000); // 1초 딜레이\n  }\n\n  private cancelHideVolumePopup() {\n    if (this.volumePopupTimer) {\n      window.clearTimeout(this.volumePopupTimer);\n      this.volumePopupTimer = null;\n    }\n  }\n\n  private hideVolumePopup() {\n    const popup = this.root?.querySelector('.mtx-volume-popup') as HTMLElement;\n    if (popup) {\n      popup.style.opacity = '0';\n      popup.style.transform = 'translateX(-50%) translateY(10px)';\n      popup.style.pointerEvents = 'none';\n    }\n  }\n\n  private showControls() {\n    if (!this.root) return;\n    this.root.style.opacity = '1';\n    this.root.style.pointerEvents = 'auto';\n    // Show mouse cursor again\n    this.container.style.cursor = 'auto';\n  }\n\n  private scheduleHide() {\n    if (this.idleTimer != null) window.clearTimeout(this.idleTimer);\n    this.idleTimer = window.setTimeout(() => this.hideControls(), this.idleMs);\n  }\n\n  private hideControls() {\n    if (!this.root) return;\n    this.root.style.opacity = '0';\n    this.root.style.pointerEvents = 'none';\n    // Hide cursor only in fullscreen\n    if (document.fullscreenElement) this.container.style.cursor = 'none';\n  }\n}\n","export type DevPluginMode = 'server' | 'local' | 'auto';\n\nexport interface DevPluginConfig {\n  mode: DevPluginMode;\n  serverBase: string; // e.g., http://localhost:3300\n  localBase: string;  // e.g., ./demo/plugin-server/plugins/\n}\n\nlet cfg: DevPluginConfig | null = null;\n\nfunction env(key: string, fallback: string): string {\n  const v = (import.meta as any)?.env?.[key];\n  return (v ?? fallback) as string;\n}\n\nfunction ensureTrailingSlash(s: string): string {\n  return s.endsWith('/') ? s : s + '/';\n}\n\nexport function getDevPluginConfig(): DevPluginConfig {\n  if (cfg) return cfg;\n  // Defaults: auto mode, localhost server, repo-local plugin folder\n  const mode = (env('VITE_PLUGIN_MODE', 'auto') as DevPluginMode);\n  const serverBase = env('VITE_PLUGIN_ORIGIN', 'http://localhost:3300');\n  // Dev server root is 'demo', so default local base is './plugin-server/plugins/'\n  const localBase = env('VITE_PLUGIN_LOCAL_BASE', './plugin-server/plugins/');\n  cfg = {\n    mode: mode === 'server' || mode === 'local' || mode === 'auto' ? mode : 'auto',\n    serverBase: serverBase.replace(/\\/$/, ''),\n    localBase: ensureTrailingSlash(localBase),\n  };\n  return cfg;\n}\n\nexport function configureDevPlugins(opts: Partial<DevPluginConfig> & { mode?: DevPluginMode }): void {\n  const cur = getDevPluginConfig();\n  cfg = {\n    mode: opts.mode ?? cur.mode,\n    serverBase: (opts.serverBase ?? cur.serverBase).replace(/\\/$/, ''),\n    localBase: ensureTrailingSlash(opts.localBase ?? cur.localBase),\n  };\n}\n","// MotionText Renderer v2.0 Native - Public API\nimport type { Scenario } from \"./types/scenario-v2\";\nimport { parseScenario } from \"./parser/ScenarioParserV2\";\nimport { RendererV2 } from \"./core/RendererV2\";\nimport { AssetManager } from \"./assets/AssetManager\";\nexport { MotionTextController } from \"./controller\";\n// Public plugin configuration/registration API (for external consumers)\nexport { configureDevPlugins as configurePluginSource, getDevPluginConfig } from './loader/dev/DevPluginConfig';\nimport { devRegistry } from './loader/dev/DevPluginRegistry';\nimport { configureDevPlugins } from './loader/dev/DevPluginConfig';\n\n// Global renderer configuration interface\nexport interface MotionTextRendererConfig {\n  debugMode?: boolean;\n  pluginServer?: {\n    mode?: 'server' | 'local' | 'auto';\n    serverBase?: string;\n    localBase?: string;\n  };\n  fps?: number;\n  snapToFrame?: boolean;\n  preloadMs?: number;\n}\n\n// Global configuration storage\nlet globalConfig: MotionTextRendererConfig = {\n  debugMode: false,  // Default to false for production\n  fps: 30,\n  snapToFrame: false,\n  preloadMs: 300\n};\n\n// Global configuration function\nexport function configureMotionTextRenderer(config: MotionTextRendererConfig): void {\n  // Update global config\n  globalConfig = { ...globalConfig, ...config };\n  \n  // Configure plugin server if provided\n  if (config.pluginServer) {\n    configureDevPlugins(config.pluginServer);\n  }\n}\n\nexport class MotionTextRenderer {\n  private renderer: RendererV2;\n  private assetManager: AssetManager;\n  private scenario: Scenario | null = null;\n\n  constructor(container: HTMLElement, options?: Partial<MotionTextRendererConfig>) {\n    // Merge global config with constructor options\n    const config = { ...globalConfig, ...options };\n    \n    this.renderer = new RendererV2({\n      container,\n      preloadMs: config.preloadMs ?? 300,\n      snapToFrame: config.snapToFrame ?? false,\n      fps: config.fps ?? 30,\n      debugMode: config.debugMode ?? false\n    });\n    this.assetManager = new AssetManager();\n  }\n\n  async loadConfig(config: any) {\n    // Parse v2.0 scenario\n    this.scenario = parseScenario(config);\n\n    // Load assets from define section if present\n    if (this.scenario.define) {\n      await this.assetManager.loadAssetsFromDefines(this.scenario.define);\n    }\n\n    // Set scenario to renderer\n    this.renderer.setScenario(this.scenario);\n  }\n\n  async loadConfigAsync(config: any) {\n    // Parse v2.0 scenario\n    this.scenario = parseScenario(config);\n\n    // Load assets from define section if present\n    if (this.scenario.define) {\n      await this.assetManager.loadAssetsFromDefines(this.scenario.define);\n    }\n\n    // Set scenario to renderer with async cleanup guarantee\n    await this.renderer.setScenarioAsync(this.scenario);\n  }\n\n  attachMedia(video: HTMLVideoElement) {\n    // Delegate to RendererV2 which uses TimelineControllerV2 for proper sync\n    this.renderer.attachMedia(video);\n  }\n\n  play() {\n    // V2 renderer doesn't manage playback directly\n    // This is handled by the video element\n  }\n\n  pause() {\n    // V2 renderer doesn't manage playback directly\n    // This is handled by the video element\n  }\n\n  seek(timeSec: number) {\n    this.renderer.update(timeSec);\n  }\n\n  async clearAsync() {\n    // Clear current scenario while keeping renderer alive (DOM cleanup guaranteed)\n    await this.renderer.clearAsync();\n    this.scenario = null;\n  }\n\n  dispose() {\n    this.renderer.dispose();\n    this.assetManager.dispose();\n    this.scenario = null;\n  }\n\n  setCaptionsVisible(visible: boolean) {\n    // This would need to be implemented in RendererV2\n    // For now, we can modify container visibility\n    this.renderer['container'].style.visibility = visible ? \"visible\" : \"hidden\";\n  }\n\n  // Allow controller to reserve bottom safe area in pixels (e.g., controller height)\n  setControlSafeBottom(px: number) {\n    const controlSafeBottomPx = Math.max(0, Math.floor(px || 0));\n    // Expose to CSS so layout can offset bottom-anchored items\n    this.renderer['container'].style.setProperty('--mtx-safe-bottom-px', `${controlSafeBottomPx}px`);\n  }\n}\n\nexport type { Scenario };\n\n// Register a plugin module programmatically (external/custom plugins)\nexport function registerExternalPlugin(params: {\n  name: string;\n  version: string;\n  module: any;               // plugin module (default export + optional named)\n  baseUrl: string;           // base URL for assets.getUrl resolution\n  manifest?: any;            // optional manifest-like metadata\n}): void {\n  const { name, version, module, baseUrl, manifest } = params;\n  devRegistry.register({\n    name,\n    version,\n    module,\n    baseUrl,\n    manifest: manifest ?? { name, version, entry: 'index.mjs' },\n  });\n}\n\n// Bulk-register plugins from a path→import-function map (e.g., Vite's import.meta.glob)\nexport async function registerExternalPluginsFromGlob(\n  globMap: Record<string, () => Promise<any>>,\n  parse?: (_path: string) => { name: string; version: string; baseUrl: string } | null\n): Promise<void> {\n  const entries = Object.entries(globMap || {});\n  for (const [path, loader] of entries) {\n    try {\n      const info = (typeof parse === 'function') ? parse(path) : defaultParsePluginPath(path);\n      if (!info) continue;\n      const mod = await loader();\n      devRegistry.register({\n        name: info.name,\n        version: info.version,\n        module: mod,\n        baseUrl: info.baseUrl,\n        manifest: { name: info.name, version: info.version, entry: 'index.mjs' },\n      });\n    } catch { /* ignore single entry failures */ }\n  }\n}\n\nfunction defaultParsePluginPath(path: string): { name: string; version: string; baseUrl: string } | null {\n  // Match \".../<name>@<version>/index.mjs\" at end of path\n  const m = String(path).match(/\\/(?<folder>[^/]+)\\/(?:index\\.mjs)$/);\n  const folder = m?.groups?.folder;\n  if (!folder) return null;\n  const at = folder.lastIndexOf('@');\n  const name = at > 0 ? folder.slice(0, at) : folder;\n  const version = at > 0 ? folder.slice(at + 1) : '0.0.0';\n  try {\n    const u = (typeof window !== 'undefined') ? new URL(path, window.location.origin) : new URL(path, 'http://localhost/');\n    const baseUrl = new URL('./', u).toString();\n    return { name, version, baseUrl };\n  } catch {\n    return null;\n  }\n}\n"],"names":["DefineResolver","constructor","defines","this","visited","Set","resolutionPath","resolveScenario","scenario","clear","length","combinedDefines","define","resolveObject","obj","keyPath","resolveDefineReference","Array","isArray","map","item","index","resolved","key","value","Object","entries","newKeyPath","startsWith","fullPath","substring","pathParts","split","Error","rootKey","fullRefPath","includes","cyclePath","join","add","push","i","part","delete","pop","getAvailableKeys","keys","getKeyType","isWithinTimeRange","t","timeRange","start","end","Number","isFinite","progressInTimeRange","currentTime","duration","progress","Math","max","min","resolveOffsetToAbsolute","bound","baseStart","baseEnd","baseDuration","which","s","trim","endsWith","n","parseFloat","slice","pct","computePluginWindowFromBase","baseTime","timeOffset","TypeError","bStart","bEnd","bDur","o0","o1","validateTimeRange","RangeError","ValidationError","message","path","super","name","validateScenario","version","timebase","stage","tracks","cues","unit","fps","validAspects","baseAspect","validateRequiredFields","cue","domLifetime","error","validateNodeTimeArrays","root","validateTimeArrays","allNodeIds","nodeIdPaths","Map","id","has","get","set","validateNodeIdRecursive","validateNodeIds","trackIds","track","trackIdCounts","count","validateTrackReferences","domStart","domEnd","validateDomLifetimeCoversNodes","validateTimeLogic","node","displayTime","pluginChain","plugin","pluginPath","time_offset","off","k","v","test","eType","children","allIds","idPaths","nodeStart","nodeEnd","console","warn","INHERITANCE_RULES","priority","systemDefault","Infinity","anchor","safeAreaClamp","style","merge","boxStyle","effectScope","DEFAULT_INHERITANCE_RULE","applyNodeInheritance","parent","cueDisplayTime","debugMode","globalThis","__MTX_DEBUG_MODE__","inheritedNode","log","nodeType","hasDisplayTime","parentDisplayTime","fields","fieldName","originalValue","inheritField","nodeId","original","inherited","parentValue","cueValue","groupNode","legacyStyle","backgroundColor","boxBg","border","padding","borderRadius","opacity","textStyle","_a","child","rule","values","defaultStyle","defaultBoxStyle","defaultConstraints","styles","merged","assign","mergeStyles","filter","mergeBoxStyles","getDefaultDomLifetime","rootNode","allDisplayTimes","collectDisplayTimes","starts","ends","collected","parseScenario","input","raw","trackDefaults","inheritedCues","inheritedCue","originalDisplayTime","inheritedRoot","applyInheritance","devRegistry","register","p","resolve","from","DefaultScenarioInfo","DefaultAssetManager","baseUrl","getUrl","URL","toString","loadFont","fontSpec","fontFace","FontFace","family","src","weight","load","document","fonts","preloadImage","url","Promise","reject","img","Image","onload","onerror","preloadAudio","audio","Audio","oncanplaythrough","defaultUtils","interpolate","to","easing","linear","color","f","result","easeIn","easeOut","easeInOut","backOut","c1","pow","parse","hex","r","parseInt","g","b","a","rgbMatch","match","format","rgba","round","fromColor","toColor","createPluginContextV3","config","scenarioInfo","assetManager","container","assets","channels","portal","renderer","utils","onSeek","peerDeps","getDefaultTrackConstraints","trackType","mode","direction","maxWidth","maxHeight","minWidth","minHeight","gap","x","y","constraintMode","breakoutEnabled","safeArea","bottom","left","right","clamp01","maxN","applyNormalizedPosition","el","layout","defaultAnchor","opts","position","ss","stageSafe","ts","trackSafe","effLeft","effRight","effTop","top","effBottom","xmin","xmax","ymin","ymax","parentElement","pw","clientWidth","ph","clientHeight","wN","size","width","hN","_b","height","isConnected","bw","offsetWidth","bh","offsetHeight","ax","ay","anchorFraction","xmin2","xmax2","ymin2","ymax2","overflow","transformOrigin","tx","ty","anchorTranslate","parts","tr","transform","translate","dx","dy","scale","sx","sy","_c","rotate","deg","skew","xDeg","yDeg","ov","override","offset","ox","oy","ot","_d","applyLayoutWithConstraints","constraints","effectiveConstraints","parentConstraints","childConstraints","hasEffectScope","shouldBreakout","display","flexDirection","alignMap","tl","tc","cl","cc","cr","bl","bc","br","alignItems","justifyMap","justifyContent","gapRel","gapProp","applyConstraintSizing","applyFlowContainerWithConstraints","gridTemplateColumns","gapPx","rowGap","columnGap","hmap","justifyItems","applyGridContainerWithConstraints","childrenLayout","wrap","safe","safeL","safeR","widthFactorFromSafe","widthFactorFromConstraint","widthFactorFromLayoutRel","maxWidthRel","widthFactorFromPercent","ratioLimit","ratioPx","absPx","POSITIVE_INFINITY","mw","applyChildrenLayout","px","py","__mtxChildrenLayout","cw","align","justify","flexWrap","containerSize","c","flex","marginLeft","marginRight","marginTop","globalDebugEnabled","createLogger","scope","enabled","localEnabled","prefix","level","toUpperCase","isEnabled","debug","args","info","setEnabled","enabledVal","enable","disable","TimelineControllerV2","options","media","tickCallbacks","rafId","videoFrameId","startTime","pausedTime","logger","supportsVideoFrame","HTMLVideoElement","prototype","onVideoPlay","autoStart","play","onVideoPause","pause","onVideoSeeked","seek","onVideoRateChange","setRate","playbackRate","snapToFrame","syncTolerance","state","isPlaying","frameCount","lastVideoTime","driftCorrection","attachMedia","video","detachMedia","setupMediaListeners","paused","ensurePlaying","stop","removeMediaListeners","performance","now","startVideoFrameLoop","startRafLoop","stopFrameLoop","time","abs","notifyTick","rate","onTick","callback","getState","videoFrameCallback","metadata","videoTime","mediaTime","updateTimeFromVideo","frameInfo","frameNumber","timestamp","drift","frameRate","calculateFrameRate","requestVideoFrameCallback","rafCallback","updateTimeFromPerformance","requestAnimationFrame","cancelVideoFrameCallback","cancelAnimationFrame","toFixed","elapsed","_now","addEventListener","removeEventListener","dispose","Stage","ro","onLoadedMetaBound","onFullscreenBound","_updateTimer","_lastBox","_boundsChangeListeners","_boundParent","_boundMedia","setContainer","installOverlayBinding","setMedia","setScenario","onBoundsChange","listener","idx","indexOf","splice","getContentRect","ch","va","videoWidth","videoHeight","parseAspectFromStage","containerWidth","containerHeight","videoAspect","computeContentRect","clearTimeout","teardownOverlayBinding","window","classList","ResizeObserver","scheduleBoundsUpdate","observe","updateBounds","disconnect","remove","throttleMs","behavior","resizeThrottleMs","setTimeout","rect","box","pointerEvents","forEach","m","String","w","h","TrackManager","computeGroupOffsets","items","yOff","getTrackDefaults","trackId","find","getTrackById","DEFAULT_CONFIG","enableCSSVariables","baseWrapperClass","effectsRootClass","preserveExisting","createBaseWrapper","originalElement","cfg","baseWrapper","querySelector","createElement","className","defaultChannels","rot","cssVar","cssValue","setProperty","initializeCSSVariables","firstChild","appendChild","applyDomSeparation","effectsRoot","setAttribute","ensureEffectsRoot","RendererV2","mountedElements","domPluginStates","lastUpdateTime","defineResolver","onTimelineUpdate","processCue","logDebugInfo","preloadMs","timelineOptions","timeline","bind","trackManager","setupContainer","clearAsync","unmountAllAsync","setScenarioAsync","cuesCount","tracksCount","hasDefine","unmountAll","cueCount","trackCount","visibility","getComputedStyle","update","getTrack","isWithinDomLifetime","unmountCue","processNode","parentNodeId","displayStart","displayEnd","ensureMounted","updateNode","unmountNode","element","dataset","nodeKey","cueId","_e","parentEl","parentMounted","mountTime","applyBaseStyle","parentLayout","nodetype","text","fontSize","_track","mounted","processPluginChain","applyChannels","rawBase","resolveAllDefines","rawOffset","pluginWindow","pluginChannels","evaluatePlugin","mergeChannels","compose","registered","module","animate","applyDomPluginProgressIfInitialized","nodeStates","fallbackId","initialized","seekFunction","fn","tempScenario","temp","val","resolvedParams","params","resolvedPlugin","pluginTypes","detectPluginType","channelResult","evaluateChannelPlugin","evaluateDomPlugin","types","registeredPlugin","type","manifest","manifestType","evalChannels","_element","hasModule","hasEvalChannels","context","createPluginContext","outerHTML","effectsRootSelector","effectsRootFound","manageDomPluginState","checkDomPluginElements","initializeDomPlugin","pluginName","timeScale","gsap","init","seekFn","cleanupDomPlugin","kill","cleanup","targetElement","target","source","hasChannels","channelCount","applyCSSVariableChannels","innerHTML","tagName","createOriginalElement","moveContentToEffectsRoot","hasBaseWrapper","hasEffectsRoot","textContent","alt","muted","childNodes","Node","TEXT_NODE","removeChild","imgElement","videoElement","trackConstraints","trackTextDefaults","nodeTextStyle","mergedTextStyle","trackDefault","textShadow","fontFamily","fontWeight","lineHeight","fontSizeRel","stroke","widthRel","webkitTextStrokeWidth","webkitTextStrokeColor","webkitTextFillColor","textAlign","whiteSpace","applyTextStyle","wpx","borderStyle","borderWidth","borderColor","radiusRel","rpx","applyGroupStyle","maybeCl","nodeIds","mountedCount","readyState","AssetManager","loadedFonts","images","videos","audios","loadAssetsFromDefines","extractAssetsFromDefines","loadAssets","isAssetDefinition","asset","loadSingleAsset","resolvedUrl","resolveUrl","registerVideo","registerAudio","href","location","getAssetUrl","isFontLoaded","getContext","getLoadStats","stats","total","getAllAssetKeys","ICONS","env","fallback","ensureTrailingSlash","getDevPluginConfig","serverBase","localBase","replace","configureDevPlugins","cur","globalConfig","defaultParsePluginPath","folder","groups","at","lastIndexOf","u","origin","styleEl","captionsOn","timeLabel","seekEl","volumeEl","cleanupFns","prevVideoStyle","idleTimer","idleMs","volumePopupTimer","captionsVisible","mount","injectStyles","outline","zIndex","progressContainer","step","oninput","dur","controlBar","leftControls","btnPlay","onclick","volumeGroup","btnVolume","volume","vol","volumePopup","volumeTrack","volVertical","updateVolumeSliderVisual","onVolumeButtonEnter","showVolumePopup","onVolumeGroupLeave","scheduleHideVolumePopup","onVolumePopupEnter","cancelHideVolumePopup","rightControls","btnSub","setCaptionsVisible","btnFs","fullscreenElement","requestFullscreen","onTime","updateUI","onMeta","onKey","e","contains","activeElement","showControls","scheduleHide","stopPropagation","preventDefault","exitFullscreen","tabIndex","focusRoot","focus","preventScroll","onEnter","onLeave","onMove","onVideoClick","onVideoDoubleClick","onPlay","onPause","onFsChange","reserveCaptionSafeArea","applyFsFit","objectFit","onFs","unmount","parentNode","visible","call","head","destroy","reset","fmt","background","floor","padStart","getBoundingClientRect","setControlSafeBottom","slider","percent","popup","hideVolumePopup","cursor","hideControls","loadConfig","loadConfigAsync","timeSec","controlSafeBottomPx","pluginServer","entry","async","globMap","loader","mod"],"mappings":"gFAcO,MAAMA,EAKX,WAAAC,CAAYC,EAAyB,IAHrCC,KAAiBC,YAA2BC,IAC5CF,KAAiBG,eAA2B,GAG1CH,KAAKD,QAAUA,CACjB,CAOA,eAAAK,CAAgBC,GAEdL,KAAKC,QAAQK,QACbN,KAAKG,eAAeI,OAAS,EAG7B,MAAMC,EAAkB,IAAKR,KAAKD,WAAYM,EAASI,QAQvD,OALqB,IAAIZ,EAAeW,GAGVE,cAAcL,EAG9C,CAQQ,aAAAK,CAAcC,EAAUC,EAAkB,IAChD,GAAID,QACF,OAAOA,EAIT,GAAmB,iBAARA,EACT,OAAOX,KAAKa,uBAAuBF,EAAKC,GAI1C,GAAIE,MAAMC,QAAQJ,GAChB,OAAOA,EAAIK,IAAI,CAACC,EAAMC,IACpBlB,KAAKU,cAAcO,EAAM,GAAGL,KAAWM,OAK3C,GAAmB,iBAARP,EAAkB,CAC3B,MAAMQ,EAAgB,CAAA,EACtB,IAAA,MAAYC,EAAKC,KAAUC,OAAOC,QAAQZ,GAAM,CAC9C,MAAMa,EAAaZ,EAAU,GAAGA,KAAWQ,IAAQA,EACnDD,EAASC,GAAOpB,KAAKU,cAAcW,EAAOG,EAC5C,CACA,OAAOL,CACT,CAGA,OAAOR,CACT,CAQQ,sBAAAE,CAAuBQ,EAAeT,GAE5C,IAAKS,EAAMI,WAAW,WACpB,OAAOJ,EAGT,MAAMK,EAAWL,EAAMM,UAAU,GAC3BC,EAAYF,EAASG,MAAM,KAGjC,GAAyB,IAArBD,EAAUrB,SAAiBqB,EAAU,GACvC,MAAM,IAAIE,MAAM,8BAA8BT,SAAaT,KAG7D,MAAMmB,EAAUH,EAAU,GAG1B,KAAMG,KAAW/B,KAAKD,SACpB,MAAM,IAAI+B,MACR,0BAA0BC,oBAA0BnB,KAKxD,MAAMoB,EAAc,UAAUN,IAC9B,GAAI1B,KAAKG,eAAe8B,SAASD,GAAc,CAC7C,MAAME,EAAY,IAAIlC,KAAKG,eAAgB6B,GAAaG,KAAK,QAC7D,MAAM,IAAIL,MAAM,0CAA0CI,IAC5D,CAGAlC,KAAKC,QAAQmC,IAAIL,GACjB/B,KAAKG,eAAekC,KAAKL,GAEzB,IAEE,IAAIb,EAAWnB,KAAKU,cAClBV,KAAKD,QAAQgC,GACb,UAAUA,KAIZ,IAAA,IAASO,EAAI,EAAGA,EAAIV,EAAUrB,OAAQ+B,IAAK,CACzC,MAAMC,EAAOX,EAAUU,GACvB,GAAInB,QACF,MAAM,IAAIW,MACR,kBAAkBS,gBAAmBpB,QAAeP,KAGxD,GAAwB,iBAAbO,EACT,MAAM,IAAIW,MACR,2BAA2BS,uBAA0B3B,KAGzD,KAAM2B,KAAQpB,GACZ,MAAM,IAAIW,MACR,aAAaS,6BAAgC3B,KAGjDO,EAAWA,EAASoB,EACtB,CAEA,OAAOpB,CACT,CAAA,QAEEnB,KAAKC,QAAQuC,OAAOT,GACpB/B,KAAKG,eAAesC,KACtB,CACF,CAMA,gBAAAC,GACE,OAAOpB,OAAOqB,KAAK3C,KAAKD,QAC1B,CAOA,UAAA6C,CAAWxB,GACT,KAAMA,KAAOpB,KAAKD,SAChB,OAAO,KAET,MAAMsB,EAAQrB,KAAKD,QAAQqB,GAC3B,OAAIN,MAAMC,QAAQM,GACT,eAEKA,CAChB,EC/JK,SAASwB,EAAkBC,EAAWC,GAC3C,IAAKjC,MAAMC,QAAQgC,IAAmC,IAArBA,EAAUxC,OACzC,OAAO,EAGT,MAAOyC,EAAOC,GAAOF,EACrB,SAAKG,OAAOC,SAASL,IAAOI,OAAOC,SAASH,IAAWE,OAAOC,SAASF,MAIhEH,GAAKE,GAASF,GAAKG,EAC5B,CAQO,SAASG,EACdC,EACAN,GAEA,IAAKjC,MAAMC,QAAQgC,IAAmC,IAArBA,EAAUxC,OACzC,OAAO,EAGT,MAAOyC,EAAOC,GAAOF,EACrB,IACGG,OAAOC,SAASE,KAChBH,OAAOC,SAASH,KAChBE,OAAOC,SAASF,GAEjB,OAAO,EAGT,MAAMK,EAAWL,EAAMD,EACvB,GAAIM,GAAY,EACd,OAAO,EAGT,MAAMC,GAAYF,EAAcL,GAASM,EACzC,OAAOE,KAAKC,IAAI,EAAGD,KAAKE,IAAI,EAAGH,GACjC,CAmDA,SAASI,EACPC,EACAC,EACAC,EACAC,EACAC,GAEA,GAAqB,iBAAVJ,EAAoB,CAC7B,MAAMK,EAAIL,EAAMM,OAChB,GAAID,EAAEE,SAAS,KAAM,CACnB,MAAMC,EAAIC,WAAWJ,EAAEK,MAAM,OACvBC,EAAMrB,OAAOC,SAASiB,GAAKA,EAAI,IAAM,EAM3C,MAAiB,UAAVJ,EACHH,EAAYE,EAAeQ,EAC3BT,EAAUC,EAAeQ,CAC/B,CACA,MAAMH,EAAIC,WAAWJ,GACrB,OAAIf,OAAOC,SAASiB,IACA,UAAVJ,EAAoBH,EAAYC,GAAWM,EAEpC,UAAVJ,EAAoBH,EAAYC,CACzC,CACA,MAAqB,iBAAVF,GAES,UAAVI,EAAoBH,EAAYC,GAAWF,EAGpC,UAAVI,EAAoBH,EAAYC,CACzC,CAQO,SAASU,EACdC,EACAC,EAAiC,CAAC,KAAM,SAExC,IAAK5D,MAAMC,QAAQ0D,IAAiC,IAApBA,EAASlE,OACvC,MAAM,IAAIoE,UAAU,uCAEtB,MAAOC,EAAQC,GAAQJ,EACvB,IAAKvB,OAAOC,SAASyB,KAAY1B,OAAOC,SAAS0B,GAC/C,MAAM,IAAIF,UAAU,0CAEtB,MAAMG,EAAOD,EAAOD,GACbG,EAAIC,GAAMlE,MAAMC,QAAQ2D,GAAcA,EAAa,CAAC,KAAM,QAGjE,MAAO,CAFMf,EAAwBoB,EAAIH,EAAQC,EAAMC,EAAM,SAChDnB,EAAwBqB,EAAIJ,EAAQC,EAAMC,EAAM,OAE/D,CAkMO,SAASG,EACdlC,GAEA,IAAKjC,MAAMC,QAAQgC,GACjB,MAAM,IAAI4B,UAAU,8BAGtB,GAAyB,IAArB5B,EAAUxC,OACZ,MAAM,IAAIoE,UAAU,uDAGtB,MAAO3B,EAAOC,GAAOF,EAErB,IAAKG,OAAOC,SAASH,GACnB,MAAM,IAAI2B,UAAU,2CAGtB,IAAKzB,OAAOC,SAASF,GACnB,MAAM,IAAI0B,UAAU,yCAGtB,GAAI3B,EAAQC,EACV,MAAM,IAAIiC,WACR,oBAAoBlC,oCAAwCC,KAGlE,CC3XO,MAAMkC,UAAwBrD,MACnC,WAAAhC,CACEsF,EACgBC,GAEhBC,MAAMD,EAAO,IAAIA,MAASD,IAAYA,GAFtBpF,KAAAqF,KAAAA,EAGhBrF,KAAKuF,KAAO,iBACd,EAQK,SAASC,EAAiBnF,GAE/B,GAAyB,QAArBA,EAASoF,QACX,MAAM,IAAIN,EACR,yCAAyC9E,EAASoF,aAuBxD,SAAgCpF,GAC9B,IAAKA,EAASqF,SACZ,MAAM,IAAIP,EAAgB,oCAG5B,IAAK9E,EAASsF,MACZ,MAAM,IAAIR,EAAgB,iCAG5B,IAAKrE,MAAMC,QAAQV,EAASuF,QAC1B,MAAM,IAAIT,EAAgB,2BAG5B,IAAKrE,MAAMC,QAAQV,EAASwF,MAC1B,MAAM,IAAIV,EAAgB,yBAI5B,GAA+B,YAA3B9E,EAASqF,SAASI,KACpB,MAAM,IAAIX,EACR,yCAAyC9E,EAASqF,SAASI,SAI/D,QAA8B,IAA1BzF,EAASqF,SAASK,MAEe,iBAA1B1F,EAASqF,SAASK,KACzB1F,EAASqF,SAASK,KAAO,GAEzB,MAAM,IAAIZ,EAAgB,0CAK9B,MAAMa,EAAe,CAAC,OAAQ,OAAQ,QACtC,IAAKA,EAAa/D,SAAS5B,EAASsF,MAAMM,YACxC,MAAM,IAAId,EACR,oCAAoCa,EAAa7D,KAAK,QAG5D,CA1DE+D,CAAuB7F,GA+DzB,SAA4BA,GAE1B,IAAA,IAASiC,EAAI,EAAGA,EAAIjC,EAASwF,KAAKtF,OAAQ+B,IAAK,CAC7C,MAAM6D,EAAM9F,EAASwF,KAAKvD,GACpB+C,EAAO,QAAQ/C,KAErB,GAAI6D,EAAIC,YACN,IACEnB,EAAkBkB,EAAIC,YACxB,OAASC,GACP,MAAM,IAAIlB,EACR,GAAGE,kBAAqBgB,aAAiBvE,MAAQuE,EAAMjB,QAAU,uBAErE,CAIFkB,EAAuBH,EAAII,KAAM,GAAGlB,SACtC,CACF,CA/EEmB,CAAmBnG,GAwIrB,SAAyBA,GACvB,MAAMoG,MAAiBvG,IACjBwG,MAAkBC,IAExB,IAAA,IAASrE,EAAI,EAAGA,EAAIjC,EAASwF,KAAKtF,OAAQ+B,IAAK,CAC7C,MAAM6D,EAAM9F,EAASwF,KAAKvD,GACpB+C,EAAO,QAAQ/C,KAGrB,IAAK6D,EAAIS,IAAwB,iBAAXT,EAAIS,GACxB,MAAM,IAAIzB,EACR,GAAGE,2DAIP,GAAIoB,EAAWI,IAAIV,EAAIS,IACrB,MAAM,IAAIzB,EACR,sBAAsBgB,EAAIS,uBAAuBF,EAAYI,IAAIX,EAAIS,QAIzEH,EAAWrE,IAAI+D,EAAIS,IACnBF,EAAYK,IAAIZ,EAAIS,GAAIvB,GAGxB2B,EAAwBb,EAAII,KAAM,GAAGlB,SAAaoB,EAAYC,EAChE,CACF,CAhKEO,CAAgB5G,GA4MlB,SAAiCA,GAC/B,MAAM6G,EAAW,IAAIhH,IAAIG,EAASuF,OAAO5E,IAAKmG,GAAUA,EAAMP,KAGxDQ,MAAoBT,IAC1B,IAAA,MAAWQ,KAAS9G,EAASuF,OAC3BwB,EAAcL,IAAII,EAAMP,IAAKQ,EAAcN,IAAIK,EAAMP,KAAO,GAAK,GAGnE,IAAA,MAAYA,EAAIS,KAAUD,EACxB,GAAIC,EAAQ,EACV,MAAM,IAAIlC,EAAgB,wBAAwByB,MAKtD,IAAA,IAAStE,EAAI,EAAGA,EAAIjC,EAASwF,KAAKtF,OAAQ+B,IAAK,CAC7C,MAAM6D,EAAM9F,EAASwF,KAAKvD,GACpB+C,EAAO,QAAQ/C,KAErB,IAAK6D,EAAIgB,OAA8B,iBAAdhB,EAAIgB,MAC3B,MAAM,IAAIhC,EACR,GAAGE,6DAIP,IAAK6B,EAASL,IAAIV,EAAIgB,OACpB,MAAM,IAAIhC,EACR,GAAGE,8BAAiCc,EAAIgB,SAG9C,CACF,CAzOEG,CAAwBjH,GA8O1B,SAA2BA,GACzB,IAAA,IAASiC,EAAI,EAAGA,EAAIjC,EAASwF,KAAKtF,OAAQ+B,IAAK,CAC7C,MAAM6D,EAAM9F,EAASwF,KAAKvD,GACpB+C,EAAO,QAAQ/C,KAErB,GAAI6D,EAAIC,YAAa,CACnB,MAAOmB,EAAUC,GAAUrB,EAAIC,YAG/BqB,EACEtB,EAAII,KACJgB,EACAC,EACA,GAAGnC,SAEP,CACF,CACF,CA5PEqC,CAAkBrH,EACpB,CA0EA,SAASiG,EAAuBqB,EAAYtC,GAE1C,GAAIsC,EAAKC,YACP,IACE3C,EAAkB0C,EAAKC,YACzB,OAASvB,GACP,MAAM,IAAIlB,EACR,GAAGE,kBAAqBgB,aAAiBvE,MAAQuE,EAAMjB,QAAU,uBAErE,CAIF,GAAIuC,EAAKE,YACP,IAAA,IAASvF,EAAI,EAAGA,EAAIqF,EAAKE,YAAYtH,OAAQ+B,IAAK,CAChD,MAAMwF,EAASH,EAAKE,YAAYvF,GAC1ByF,EAAa,GAAG1C,iBAAoB/C,KAE1C,GAAsB,iBAAXwF,GAAwBA,EAAeE,YAAa,CAC7D,MAAMC,EAAOH,EAAeE,YAE5B,IAAKlH,MAAMC,QAAQkH,IAAuB,IAAfA,EAAI1H,OAC7B,MAAM,IAAI4E,EACR,GAAG4C,4CAGP,IAAA,IAASG,EAAI,EAAGA,EAAI,EAAGA,IAAK,CAC1B,MAAMC,EAAIF,EAAIC,GAId,KAFgB,iBAANC,GAAkBjF,OAAOC,SAASgF,IAC5B,iBAANA,GAAkB,4BAA4BC,KAAKD,IAE3D,MAAM,IAAIhD,EACR,GAAG4C,iBAA0BG,gEAGnC,CACF,CACF,CAIF,GAAmB,UAAfP,EAAKU,OAAqBV,EAAKW,SACjC,IAAA,IAAShG,EAAI,EAAGA,EAAIqF,EAAKW,SAAS/H,OAAQ+B,IACxCgE,EAAuBqB,EAAKW,SAAShG,GAAI,GAAG+C,cAAiB/C,KAGnE,CAqCA,SAAS0E,EACPW,EACAtC,EACAkD,EACAC,GAGA,IAAKb,EAAKf,IAAyB,iBAAZe,EAAKf,GAC1B,MAAM,IAAIzB,EACR,GAAGE,4DAKP,GAAIkD,EAAO1B,IAAIc,EAAKf,IAClB,MAAM,IAAIzB,EACR,uBAAuBwC,EAAKf,uBAAuB4B,EAAQ1B,IAAIa,EAAKf,QAQxE,GAJA2B,EAAOnG,IAAIuF,EAAKf,IAChB4B,EAAQzB,IAAIY,EAAKf,GAAIvB,GAGF,UAAfsC,EAAKU,OAAqBV,EAAKW,SACjC,IAAA,IAAShG,EAAI,EAAGA,EAAIqF,EAAKW,SAAS/H,OAAQ+B,IACxC0E,EACEW,EAAKW,SAAShG,GACd,GAAG+C,cAAiB/C,KACpBiG,EACAC,EAIR,CAgEA,SAASf,EACPE,EACAJ,EACAC,EACAnC,GAEA,GAAIsC,EAAKC,YAAa,CACpB,MAAOa,EAAWC,GAAWf,EAAKC,YAG9Ba,EAAYlB,GACdoB,QAAQC,KACN,YAAYvD,yBAA4BoD,0BAAkClB,yCAM1EmB,EAAUlB,GACZmB,QAAQC,KACN,YAAYvD,uBAA0BqD,yBAA+BlB,2CAI3E,CAGA,GAAmB,UAAfG,EAAKU,OAAqBV,EAAKW,SACjC,IAAA,IAAShG,EAAI,EAAGA,EAAIqF,EAAKW,SAAS/H,OAAQ+B,IACxCmF,EACEE,EAAKW,SAAShG,GACdiF,EACAC,EACA,GAAGnC,cAAiB/C,KAI5B,CCzTA,MAAMuG,EAA0D,CAC9DjB,YAAa,CACXkB,SAAU,CAAC,SAAU,SAAU,UAC/BC,cAAe,EAACC,IAAWA,MAK7BC,OAAQ,CACNH,SAAU,CAAC,SAAU,SAAU,SAC/BC,mBAAe,GAEjBG,cAAe,CACbJ,SAAU,CAAC,SAAU,UACrBC,mBAAe,GAEjBI,MAAO,CACLL,SAAU,CAAC,SAAU,SAAU,SAC/BM,OAAO,EACPL,mBAAe,GAEjBM,SAAU,CACRP,SAAU,CAAC,SAAU,SACrBM,OAAO,EACPL,mBAAe,GAEjBlB,YAAa,CACXiB,SAAU,CAAC,UACXC,mBAAe,GAEjBO,YAAa,CACXR,SAAU,CAAC,SAAU,UACrBC,mBAAe,IAKbQ,EAAiD,CACrDT,SAAU,CAAC,SAAU,SAAU,QAAS,UACxCC,mBAAe,GAoEjB,SAASS,EACP7B,EACA8B,EACAtC,EACAuC,SAEA,MAAMC,EAAaC,WAAmBC,qBAAsB,EAGtDC,EAAgB,IAAKnC,GAEvBgC,GAEFhB,QAAQoB,IAAI,kCAAkCpC,EAAKf,IAAM,YAAa,CACpEoD,SAAUrC,EAAKU,MACf4B,iBAAmBtC,EAAaC,YAChCsC,kBAAmB,MAAAT,OAAA,EAAAA,EAAQ7B,YAC3B8B,iBACAS,OAAQ7I,OAAOqB,KAAKgF,KAKxB,IAAA,MAAWyC,KAAa9I,OAAOqB,KAAKkG,GAAoB,CACtD,MAAMwB,EAAgBP,EAAcM,GACpCN,EAAcM,GAA2BE,EACvCF,EACAzC,EACA8B,EACAtC,EACAuC,GAGEC,GAA2B,gBAAdS,GAEfzB,QAAQoB,IAAI,iBAAiBK,iBAA0B,CACrDG,OAAQ5C,EAAKf,IAAM,UACnB4D,SAAUH,EACVI,UAAWX,EAAcM,GACzBM,YAAa,MAAAjB,OAAA,EAAAA,EAASW,GACtBO,SAAUjB,GAGhB,CAGA,GAAmB,UAAf/B,EAAKU,MAAmB,CAC1B,MAAMuC,EAAYd,EACZe,EAAcD,EAAUzB,MAE9B,GAAI0B,IAAgBD,EAAUvB,SAAU,CACtC,MAAMyB,gBACJA,EAAAC,MACAA,EAAAC,OACAA,EAAAC,QACAA,EAAAC,aACAA,EAAAC,QACAA,KACGC,GACDP,GAIFC,GACAC,GACAC,GACAC,GACAC,QACY,IAAZC,KAEAP,EAAUzB,MAAQiC,EAClBR,EAAUvB,SAAW,CACnByB,kBACAC,QACAC,SACAC,UACAC,eACAC,WAGN,CACF,CAGA,GAAmB,UAAfxD,EAAKU,MAAmB,CAC1B,MAAMuC,EAAYd,EAKlB,MAAO,IACFc,EACHtC,SAAU,OAAA+C,EAAAT,EAAUtC,eAAV,EAAA+C,EAAoBrK,IAAKsK,GACjC9B,EACE8B,EACAxB,EACA3C,EACAuC,IAIR,CAGA,OAAOI,CACT,CAWA,SAASQ,EACPF,EACAzC,EACA8B,EACAtC,EACAuC,SAEA,MAAM6B,EAAO1C,EAAkBuB,IAAcb,EAG7C,GAAkB,gBAAda,EAEF,OAAQzC,EAAayC,IAAcmB,EAAKxC,cAG1C,MAAMyC,EAAgB,GAGtB,IAAA,MAAW1C,KAAYyC,EAAKzC,SAAU,CACpC,IAAIzH,EAEJ,OAAQyH,GACN,IAAK,SACHzH,EAASsG,EAAayC,GACtB,MACF,IAAK,SACH/I,EAAQoI,EAAUA,EAAeW,QAAa,EAC9C,MACF,IAAK,QAEe,UAAdA,EACF/I,EAAQ,MAAA8F,OAAA,EAAAA,EAAOsE,aACQ,aAAdrB,EAEU,UAAfzC,EAAKU,QACPhH,EAAQ,MAAA8F,OAAA,EAAAA,EAAOuE,iBAGH,WAAdtB,IACA,OAAAiB,EAAA,MAAAlE,OAAA,EAAAA,EAAOwE,6BAAoB1C,UAE3B5H,EAAQ8F,EAAMwE,mBAAmB1C,QAGnC,MACF,IAAK,SAGD5H,EADgB,gBAAd+I,GAA+BV,EACzBA,EAEA6B,EAAKxC,mBAKL,IAAV1H,GACFmK,EAAOnJ,KAAKhB,EAEhB,CAGA,GAAsB,IAAlBmK,EAAOjL,OACT,OAAOgL,EAAKxC,cAId,GAAIwC,EAAKnC,MAAO,CACd,GAAkB,UAAdgB,EACF,OAgBN,YAAwBwB,GACtB,MAAMC,EAAoB,CAAA,EAG1B,IAAA,IAASvJ,EAAIsJ,EAAOrL,OAAS,EAAG+B,GAAK,EAAGA,IAAK,CAC3C,MAAM6G,EAAQyC,EAAOtJ,GACjB6G,GAA0B,iBAAVA,GAClB7H,OAAOwK,OAAOD,EAAQ1C,EAE1B,CAEA,OAAO0C,CACT,CA5BaE,IAAeP,EAAOQ,OAAQ7D,QAAY,IAANA,IAC7C,GAAyB,aAAdiC,EACT,OAkCN,YAA2BwB,GACzB,MAAMC,EAAmB,CAAA,EAGzB,IAAA,IAASvJ,EAAIsJ,EAAOrL,OAAS,EAAG+B,GAAK,EAAGA,IAAK,CAC3C,MAAM6G,EAAQyC,EAAOtJ,GACjB6G,GAA0B,iBAAVA,GAClB7H,OAAOwK,OAAOD,EAAQ1C,EAE1B,CAEA,OAAO0C,CACT,CA9CaI,IAAkBT,EAAOQ,OAAQ7D,QAAY,IAANA,GAGlD,CAGA,OAAOqD,EAAO,EAChB,CA8CA,SAASU,EAAsBC,GAC7B,MAAMC,EAA+B,GAKrC,GAFAC,EAAoBF,EAAUC,GAEC,IAA3BA,EAAgB7L,OAElB,MAAO,IAAKyI,KAId,MAAMsD,EAASF,EACZpL,IAAI,EAAEgC,KAAWA,GACjBgJ,OAAQlJ,GAAMI,OAAOC,SAASL,IAC3ByJ,EAAOH,EACVpL,IAAI,EAAC,CAAGiC,KAASA,GACjB+I,OAAQlJ,GAAMI,OAAOC,SAASL,IAMjC,MAAO,EAJUwJ,EAAO/L,OAAS,EAAIiD,KAAKE,OAAO4I,GAAU,GAIxC,IAHJC,EAAKhM,OAAS,EAAIiD,KAAKC,OAAO8I,GAAQ,IAGpB,GACnC,CAKA,SAASF,EAAoB1E,EAAY6E,GACvC,GAAI7E,EAAKC,YAAa,CACpB,MAAM7E,EAAY4E,EAAKC,YACnB1E,OAAOC,SAASJ,EAAU,KAAOG,OAAOC,SAASJ,EAAU,KAC7DyJ,EAAUnK,KAAKU,EAEnB,CAEA,GAAmB,UAAf4E,EAAKU,OAAqBV,EAAKW,SACjC,IAAA,MAAWgD,KAAS3D,EAAKW,SACvB+D,EAAoBf,EAAOkB,EAGjC,CC7YO,SAASC,EAAcC,GAE5B,IAAKA,GAA0B,iBAAVA,EACnB,MAAM,IAAI5K,MAAM,wCAGlB,MAAM6K,EAAMD,EAGZ,GAAoB,QAAhBC,EAAIlH,QAAmB,CACzB,MAAMA,EAAUkH,EAAIlH,SAAW,UAC/B,MAAM,IAAI3D,MACR,mDAAmD2D,6DAGvD,CAGA,MAEMtE,EAFW,IAAItB,EAAe8M,EAAIlM,QACXL,gBAAgBuM,GAI7CnH,EAAiBrE,GAKjB,OD4BK,SAA0Bd,GAE/B,MAAMuM,MAAoBjG,IAC1B,IAAA,MAAWQ,KAAS9G,EAASuF,OAC3BgH,EAAc7F,IAAII,EAAMP,GAAIO,GAG9B,MAAMwC,EAAaC,WAAmBC,qBAAsB,EAGtDgD,EAAgBxM,EAASwF,KAAK7E,IAAI,CAACmF,EAAKjF,KAC5C,MAAMiG,EAAQyF,EAAc9F,IAAIX,EAAIgB,OAEhCwC,GAEFhB,QAAQoB,IAAI,gCAAgC7I,MAAUiF,EAAIS,KAAM,CAC9DgB,YAAczB,EAAYyB,YAC1BxB,YAAaD,EAAIC,YACjBe,MAAO,MAAAA,OAAA,EAAAA,EAAOP,KAIlB,MAAMkG,EAAe,IAChB3G,EAEHC,YAAaD,EAAIC,aAAe8F,EAAsB/F,EAAII,MAE1DA,KAAMiD,EACJrD,EAAII,KACJ,KACAY,EACChB,EAAYyB,cAYjB,OARI+B,GAEFhB,QAAQoB,IAAI,qBAAqB5D,EAAIS,2BAA4B,CAC/DmG,oBAAsB5G,EAAYyB,YAClCoF,cAAeF,EAAavG,OAIzBuG,IAGT,MAAO,IACFzM,EACHwF,KAAMgH,EAEV,CChFoBI,CAAiB9L,EAGrC,CCrBO,MAAM+L,EAAc,IArBpB,MAAA,WAAApN,GACLE,KAAQgB,QAAU2F,GAA8B,CAEhD,QAAAwG,CAASC,GACPpN,KAAKgB,IAAI+F,IAAIqG,EAAE7H,KAAM6H,GACrBpN,KAAKgB,IAAI+F,IAAI,GAAGqG,EAAE7H,QAAQ6H,EAAE3H,UAAW2H,EACzC,CAEA,OAAAC,CAAQ9H,GACN,OAAOvF,KAAKgB,IAAI8F,IAAIvB,EACtB,CAEA,GAAAsB,CAAItB,GACF,OAAOvF,KAAKgB,IAAI6F,IAAItB,EACtB,CAEA,OAAAhE,GACE,OAAOT,MAAMwM,KAAK,IAAIpN,IAAIF,KAAKgB,IAAIwK,UACrC,GCsIK,MAAM+B,EACX,WAAAzN,CAAoBO,GAAAL,KAAAK,SAAAA,CAAqB,CAEzC,WAAIoF,GACF,OAAOzF,KAAKK,SAASoF,OACvB,EAMK,MAAM+H,EACX,WAAA1N,CAAoB2N,GAAAzN,KAAAyN,QAAAA,CAAkB,CAEtC,MAAAC,CAAOrI,GACL,OAAO,IAAIsI,IAAItI,EAAMrF,KAAKyN,SAASG,UACrC,CAEA,cAAMC,CAASC,GAMb,MAAMC,EAAW,IAAIC,SACnBF,EAASG,OACT,QAAQjO,KAAK0N,OAAOI,EAASI,SAC7B,CACEC,OAAQL,EAASK,QAAU,SAC3BhF,MAAO2E,EAAS3E,OAAS,iBAIvB4E,EAASK,OACfC,SAASC,MAAMlM,IAAI2L,EACrB,CAEA,kBAAMQ,CAAaC,GACjB,OAAO,IAAIC,QAAQ,CAACpB,EAASqB,KAC3B,MAAMC,EAAM,IAAIC,MAChBD,EAAIE,OAAS,IAAMxB,EAAQsB,GAC3BA,EAAIG,QAAUJ,EACdC,EAAIT,IAAMlO,KAAK0N,OAAOc,IAE1B,CAEA,kBAAMO,CAAaP,GACjB,OAAO,IAAIC,QAAQ,CAACpB,EAASqB,KAC3B,MAAMM,EAAQ,IAAIC,MAClBD,EAAME,iBAAmB,IAAM7B,EAAQ2B,GACvCA,EAAMF,QAAUJ,EAChBM,EAAMd,IAAMlO,KAAK0N,OAAOc,GACxBQ,EAAMZ,QAEV,EAMK,MAAMe,EAA4B,CACvC,WAAAC,CAAY9B,EAAW+B,EAAS9L,EAAkB+L,EAAS,UACzD,MACMxM,GADW9C,KAAKsP,OAAOA,IAAWtP,KAAKsP,OAAOC,QACjC/L,KAAKC,IAAI,EAAGD,KAAKE,IAAI,EAAGH,KAG3C,GAAoB,iBAAT+J,GAAmC,iBAAP+B,EACrC,OAAO/B,GAAQ+B,EAAK/B,GAAQxK,EAI9B,GAAoB,iBAATwK,GAAmC,iBAAP+B,IAEnC/B,EAAK7L,WAAW,MAChB4N,EAAG5N,WAAW,MACd6L,EAAK7L,WAAW,QAChB4N,EAAG5N,WAAW,QAEd,OAAOzB,KAAKwP,MAAMJ,YAAY9B,EAAM+B,EAAIvM,GAK5C,GAAIhC,MAAMC,QAAQuM,IAASxM,MAAMC,QAAQsO,GACvC,OAAO/B,EAAKtM,IAAI,CAACyO,EAAGnN,IAClBtC,KAAKoP,YAAYK,EAAGJ,EAAG/M,IAAMmN,EAAGlM,EAAU+L,IAK9C,GAAoB,iBAAThC,GAAmC,iBAAP+B,EAAiB,CACtD,MAAMK,EAAc,CAAA,EACpB,IAAA,MAAWtO,KAAOkM,EAChBoC,EAAOtO,GAAOpB,KAAKoP,YACjB9B,EAAKlM,GACLiO,EAAGjO,IAAQkM,EAAKlM,GAChBmC,EACA+L,GAGJ,OAAOI,CACT,CAGA,OAAO5M,EAAI,GAAMwK,EAAO+B,CAC1B,EAEAC,OAAQ,CACNC,OAASzM,GAAcA,EACvB6M,OAAS7M,GAAcA,EAAIA,EAC3B8M,QAAU9M,GAAcA,GAAK,EAAIA,GACjC+M,UAAY/M,GAAeA,EAAI,GAAM,EAAIA,EAAIA,GAAU,EAAI,EAAIA,GAAKA,EAAnB,EACjDgN,QAAUhN,IACR,MAAMiN,EAAK,QAEX,OAAO,EADIA,QACKvM,KAAKwM,IAAIlN,EAAI,EAAG,GAAKiN,EAAKvM,KAAKwM,IAAIlN,EAAI,EAAG,KAI9D0M,MAAO,CACL,KAAAS,CAAMT,GAEJ,GAAIA,EAAM/N,WAAW,KAAM,CACzB,MAAMyO,EAAMV,EAAMlL,MAAM,GACxB,GAAmB,IAAf4L,EAAI3P,OACN,MAAO,CACL4P,EAAGC,SAASF,EAAI,GAAKA,EAAI,GAAI,IAC7BG,EAAGD,SAASF,EAAI,GAAKA,EAAI,GAAI,IAC7BI,EAAGF,SAASF,EAAI,GAAKA,EAAI,GAAI,IAC7BK,EAAG,GAEP,GAA0B,IAAfL,EAAI3P,OACb,MAAO,CACL4P,EAAGC,SAASF,EAAI5L,MAAM,EAAG,GAAI,IAC7B+L,EAAGD,SAASF,EAAI5L,MAAM,EAAG,GAAI,IAC7BgM,EAAGF,SAASF,EAAI5L,MAAM,EAAG,GAAI,IAC7BiM,EAAG,EAGT,CAEA,MAAMC,EAAWhB,EAAMiB,MACrB,qDAEF,OAAID,EACK,CACLL,EAAGC,SAASI,EAAS,GAAI,IACzBH,EAAGD,SAASI,EAAS,GAAI,IACzBF,EAAGF,SAASI,EAAS,GAAI,IACzBD,EAAGC,EAAS,GAAKnM,WAAWmM,EAAS,IAAM,GAIxC,IACT,EAEAE,OAAOC,GACU,IAAXA,EAAKJ,EACA,OAAO/M,KAAKoN,MAAMD,EAAKR,OAAO3M,KAAKoN,MAAMD,EAAKN,OAAO7M,KAAKoN,MAAMD,EAAKL,MAErE,QAAQ9M,KAAKoN,MAAMD,EAAKR,OAAO3M,KAAKoN,MAAMD,EAAKN,OAAO7M,KAAKoN,MAAMD,EAAKL,OAAOK,EAAKJ,KAI7F,WAAAnB,CAAY9B,EAAc+B,EAAY9L,GACpC,MAAMsN,EAAY7Q,KAAKiQ,MAAM3C,GACvBwD,EAAU9Q,KAAKiQ,MAAMZ,GAE3B,OAAKwB,GAAcC,EAIZ9Q,KAAK0Q,OAAO,CACjBP,EAAGU,EAAUV,GAAKW,EAAQX,EAAIU,EAAUV,GAAK5M,EAC7C8M,EAAGQ,EAAUR,GAAKS,EAAQT,EAAIQ,EAAUR,GAAK9M,EAC7C+M,EAAGO,EAAUP,GAAKQ,EAAQR,EAAIO,EAAUP,GAAK/M,EAC7CgN,EAAGM,EAAUN,GAAKO,EAAQP,EAAIM,EAAUN,GAAKhN,IAPtCA,EAAW,GAAM+J,EAAO+B,CASnC,IAQG,SAAS0B,EAAsBC,GAWpC,MAAMC,EAAe,IAAI1D,EAAoByD,EAAO3Q,UAC9C6Q,EAAe,IAAI1D,EAAoBwD,EAAOvD,SAEpD,MAAO,CACL0D,UAAWH,EAAOG,UAClB9Q,SAAU4Q,EACVG,OAAQF,EACRG,SAAUL,EAAOK,SACjBC,OAAQN,EAAOM,OACftC,MAAOgC,EAAOhC,MACduC,SAAUP,EAAOO,SACjBC,MAAOrC,EACPsC,OAAQT,EAAOS,UACZT,EAAOU,SAEd,CC3WO,SAASC,EACdC,GAEA,OAAQA,GACN,IAAK,WACH,MAAO,CACLC,KAAM,OACNC,UAAW,WACXC,SAAU,GACVC,UAAW,GACXC,SAAU,GACVC,UAAW,IACXC,IAAK,IACLlH,QAAS,CAAEmH,EAAG,IAAMC,EAAG,MACvBpJ,OAAQ,KACRqJ,eAAgB,WAChBC,iBAAiB,EACjBC,SAAU,CAAEC,OAAQ,KAAOC,KAAM,IAAMC,MAAO,MAGlD,IAAK,OACH,MAAO,CACLd,KAAM,WACNC,UAAW,WACXC,SAAU,EACVC,UAAW,EACXC,SAAU,EACVC,UAAW,EACXC,IAAK,EACLlH,QAAS,CAAEmH,EAAG,EAAGC,EAAG,GACpBpJ,OAAQ,KACRqJ,eAAgB,WAChBC,iBAAiB,EACjBC,cAAU,GAGd,QAEE,OAAOb,EAA2B,QAExC,CCjCA,MAAMiB,EAAWzK,GAAeA,EAAI,EAAI,EAAIA,EAAI,EAAI,EAAIA,EAClD0K,EAAO,CAACtC,EAAYD,IACnB,MAALC,EAAaD,GAAK,EAAU,MAALA,EAAYC,EAAI/M,KAAKC,IAAI8M,EAAGD,GAI9C,SAASwC,EACdC,EACAC,EACAC,EAAwB,KACxBC,EAA2B,gBAE3B,IAAKF,IAAWA,EAAOG,SAAU,OACjC,IAAIf,EAAEA,EAAAC,EAAGA,GAAMW,EAAOG,SACtB,GAAiB,iBAANf,GAA+B,iBAANC,EAAgB,OAGpD,GAAIW,EAAO9J,cAAe,CACxB,MAAMkK,EAAKF,EAAKG,WAAa,CAAA,EACvBC,EAAKJ,EAAKK,WAAa,CAAA,EACvBC,EAAUX,EAAKO,EAAGV,KAAMY,EAAGZ,MAC3Be,EAAWZ,EAAKO,EAAGT,MAAOW,EAAGX,OAC7Be,EAASb,EAAKO,EAAGO,IAAKL,EAAGK,KACzBC,EAAYf,EAAKO,EAAGX,OAAQa,EAAGb,QAC/BoB,EAAOjB,EAAQY,GAAW,GAC1BM,EAAO,EAAIlB,EAAQa,GAAY,GAC/BM,EAAOnB,EAAQc,GAAU,GACzBM,EAAO,EAAIpB,EAAQgB,GAAa,GAEhCnK,EAASsJ,EAAGkB,cACZC,SAAKzK,WAAQ0K,cAAe,EAC5BC,SAAK3K,WAAQ4K,eAAgB,EAEnC,IAAIC,EAAmC,iBAAvB,OAAAjJ,EAAA2H,EAAOuB,eAAMC,OAAqBxB,EAAOuB,KAAMC,MAAS,EACpEC,EAAoC,iBAAxB,OAAAC,EAAA1B,EAAOuB,eAAMI,QAAsB3B,EAAOuB,KAAMI,OAAU,EAC1E,KAAML,IAAOG,IAAO1B,EAAG6B,aAAeV,EAAK,GAAKE,EAAK,EAAG,CACtD,MAAMS,EAAK9B,EAAG+B,YACRC,EAAKhC,EAAGiC,cACTV,GAAMO,EAAK,MAAQA,EAAKX,IACxBO,GAAMM,EAAK,MAAQA,EAAKX,EAC/B,CACA,MAAMa,GAAEA,EAAAC,GAAIA,GC7BT,SAAwBjM,GAC7B,OAAQA,GACN,IAAK,KAAM,MAAO,CAAEgM,GAAI,EAAGC,GAAI,GAC/B,IAAK,KAAM,MAAO,CAAED,GAAI,GAAKC,GAAI,GACjC,IAAK,KAAM,MAAO,CAAED,GAAI,EAAGC,GAAI,GAC/B,IAAK,KAAM,MAAO,CAAED,GAAI,EAAGC,GAAI,IAC/B,IAAK,KAKL,QAAW,MAAO,CAAED,GAAI,GAAKC,GAAI,IAJjC,IAAK,KAAM,MAAO,CAAED,GAAI,EAAGC,GAAI,IAC/B,IAAK,KAAM,MAAO,CAAED,GAAI,EAAGC,GAAI,GAC/B,IAAK,KAAM,MAAO,CAAED,GAAI,GAAKC,GAAI,GACjC,IAAK,KAAM,MAAO,CAAED,GAAI,EAAGC,GAAI,GAGnC,CDgBuBC,CAChBnC,EAAO/J,QAAqBgK,GAG/B,GAAIqB,EAAK,EAAG,CAEV,MAAMc,EAAQvB,GAAQ,EAAIoB,GAAMX,EAC1Be,EAAQvB,EAAOmB,EAAKX,EAC1BlC,EAAI5O,KAAKE,IAAIF,KAAKC,IAAI2O,EAAGgD,GAAQC,EACnC,MACEjD,EAAI5O,KAAKE,IAAIF,KAAKC,IAAI2O,EAAGyB,GAAOC,GAGlC,GAAIW,EAAK,EAAG,CAOV,MAAMa,EAAQvB,GAAQ,EAAImB,GAAMT,EAC1Bc,EAAQvB,EAAOkB,EAAKT,EAC1BpC,EAAI7O,KAAKE,IAAIF,KAAKC,IAAI4O,EAAGiD,GAAQC,EACnC,MACElD,EAAI7O,KAAKE,IAAIF,KAAKC,IAAI4O,EAAG0B,GAAOC,EAEpC,CAEAjB,EAAG5J,MAAMgK,SAAW,WACpBJ,EAAG5J,MAAMuJ,KAAUlP,KAAKoN,MAAU,IAAJwB,GAAa,IAA3B,IAChBW,EAAG5J,MAAMwK,IAASnQ,KAAKoN,MAAU,IAAJyB,GAAa,IAA3B,IAGXW,EAAOuB,OACgB,MAArBvB,EAAOuB,KAAKC,QACdzB,EAAG5J,MAAMqL,MACsB,iBAAtBxB,EAAOuB,KAAKC,MACQ,IAApBxB,EAAOuB,KAAKC,MAAf,IACA,QAEkB,MAAtBxB,EAAOuB,KAAKI,SACd5B,EAAG5J,MAAMwL,OACuB,iBAAvB3B,EAAOuB,KAAKI,OACS,IAArB3B,EAAOuB,KAAKI,OAAf,IACA,SAIN3B,EAAOwC,WAAUzC,EAAG5J,MAAMqM,SAAWxC,EAAOwC,UAC5CxC,EAAOyC,kBAAiB1C,EAAG5J,MAAMsM,gBAAkBzC,EAAOyC,iBAC9D,MAAMxM,EAAU+J,EAAO/J,QAAqBgK,GACtCyC,GAAEA,EAAAC,GAAIA,GCzGP,SAAyB1M,GAC9B,OAAQA,GACN,IAAK,KACH,MAAO,CAAEyM,GAAI,EAAGC,GAAI,GACtB,IAAK,KACH,MAAO,CAAED,IAAI,GAAKC,GAAI,GACxB,IAAK,KACH,MAAO,CAAED,IAAI,IAAMC,GAAI,GACzB,IAAK,KACH,MAAO,CAAED,GAAI,EAAGC,IAAI,IACtB,IAAK,KAUL,QACE,MAAO,CAAED,IAAI,GAAKC,IAAI,IATxB,IAAK,KACH,MAAO,CAAED,IAAI,IAAMC,IAAI,IACzB,IAAK,KACH,MAAO,CAAED,GAAI,EAAGC,IAAI,KACtB,IAAK,KACH,MAAO,CAAED,IAAI,GAAKC,IAAI,KACxB,IAAK,KACH,MAAO,CAAED,IAAI,IAAMC,IAAI,KAI7B,CDkFqBC,CAAgB3M,GAE7B4M,EAAkB,CAAC,aAAaH,OAAQC,OACxCG,EAAK9C,EAAO+C,UAClB,SAAID,WAAIE,UAAW,CACjB,MAAMC,EAAKH,EAAGE,UAAU5D,GAAK,EACvB8D,EAAKJ,EAAGE,UAAU3D,GAAK,GACzB4D,GAAMC,IAAIL,EAAMxT,KAAK,aAAkB,IAAL4T,OAAmB,IAALC,MACtD,CACA,SAAIJ,WAAIK,MAAO,CACb,MAAMC,EAAKN,EAAGK,MAAM/D,GAAK,EACnBiE,EAAKP,EAAGK,MAAM9D,GAAK,EACd,IAAP+D,GAAmB,IAAPC,GAAUR,EAAMxT,KAAK,SAAS+T,MAAOC,KACvD,CAIA,GAHuB,OAAnB,OAAAC,EAAA,MAAAR,OAAA,EAAAA,EAAIS,aAAJ,EAAAD,EAAYE,MACdX,EAAMxT,KAAK,UAAUyT,EAAGS,OAAOC,iBAE7BV,WAAIW,KAAM,CACZ,MAAMC,EAAOZ,EAAGW,KAAKC,MAAQ,EACvBC,EAAOb,EAAGW,KAAKE,MAAQ,GACzBD,GAAQC,IAAMd,EAAMxT,KAAK,QAAQqU,SAAYC,QACnD,CAGA,MAAMC,EAAK5D,EAAO6D,SAClB,GAAiB,cAAb,MAAAD,OAAA,EAAAA,EAAI/E,MAAqB,CAC3B,GAAI+E,EAAGE,OAAQ,CACb,MAAMC,EAAKH,EAAGE,OAAO1E,GAAK,EACpB4E,EAAKJ,EAAGE,OAAOzE,GAAK,GACtB0E,GAAMC,IAAInB,EAAMxT,KAAK,aAAkB,IAAL0U,OAAmB,IAALC,MACtD,CACA,GAAIJ,EAAGb,UAAW,CAChB,MAAMkB,EAAKL,EAAGb,UACd,GAAIkB,EAAGjB,UAAW,CAChB,MAAMC,EAAKgB,EAAGjB,UAAU5D,GAAK,EACvB8D,EAAKe,EAAGjB,UAAU3D,GAAK,GACzB4D,GAAMC,IAAIL,EAAMxT,KAAK,aAAkB,IAAL4T,OAAmB,IAALC,MACtD,CACA,GAAIe,EAAGd,MAAO,CACZ,MAAMC,EAAKa,EAAGd,MAAM/D,GAAK,EACnBiE,EAAKY,EAAGd,MAAM9D,GAAK,EACd,IAAP+D,GAAmB,IAAPC,GAAUR,EAAMxT,KAAK,SAAS+T,MAAOC,KACvD,CAEA,GADsB,OAAlB,OAAAa,EAAAD,EAAGV,aAAH,EAAAW,EAAWV,MAAaX,EAAMxT,KAAK,UAAU4U,EAAGV,OAAOC,WACvDS,EAAGR,KAAM,CACX,MAAML,EAAKa,EAAGR,KAAKC,MAAQ,EACzBL,EAAKY,EAAGR,KAAKE,MAAQ,GACnBP,GAAMC,IAAIR,EAAMxT,KAAK,QAAQ+T,SAAUC,QAC7C,CACF,CACF,CACAtD,EAAG5J,MAAM4M,UAAYF,EAAM1T,KAAK,IAClC,CAYO,SAASgV,EACdpE,EACAC,EACAoE,EACAnE,EAAwB,KACxBC,EAAqC,IAGrC,MAAMmE,EAAuBD,GD5H7BE,EC6HqBpE,EAAKoE,kBD5H1BC,EC4H6CH,ED1HxCE,GAAsBC,EAItBD,EAIAC,EAKE,CACL1F,KAAM0F,EAAiB1F,MAAQyF,EAAkBzF,KACjDC,UAAWyF,EAAiBzF,WAAawF,EAAkBxF,UAC3DC,SAAUwF,EAAiBxF,UAAYuF,EAAkBvF,SACzDC,UAAWuF,EAAiBvF,WAAasF,EAAkBtF,UAC3DC,SAAUsF,EAAiBtF,UAAYqF,EAAkBrF,SACzDC,UAAWqF,EAAiBrF,WAAaoF,EAAkBpF,UAC3DC,IAAKoF,EAAiBpF,KAAOmF,EAAkBnF,IAC/ClH,QAASsM,EAAiBtM,SAAWqM,EAAkBrM,QACvDhC,OAAQsO,EAAiBtO,QAAUqO,EAAkBrO,OACrDqJ,eACEiF,EAAiBjF,gBAAkBgF,EAAkBhF,eACvDC,gBACEgF,EAAiBhF,iBAAmB+E,EAAkB/E,gBACxDC,SAAU+E,EAAiB/E,UAAY8E,EAAkB9E,UAlBlD,IAAK8E,GAJL,IAAKC,GAJL5F,EAA2B,SC0HhCuB,EAAKoE,kBD/HJ,IACLA,EACAC,ECgIA,GACEF,GD7FG,SACLD,EACAI,GAEA,MACiC,aAA/BJ,EAAY9E,kBACT8E,EAAY7E,iBAAmBiF,CAEtC,CCsFIC,CAAeJ,EAAsBnE,EAAKsE,iBAAkB,GAK5D,YADA1E,EAAwBC,EAAIC,EAAQC,EAAeC,GAOrD,QAFa,MAAAF,OAAA,EAAAA,EAAQnB,QAAQ,MAAAwF,OAAA,EAAAA,EAAsBxF,OAAQ,YAGzD,IAAK,QAqET,SACEkB,EACAC,EACAoE,EACAnE,EAAwB,KACxBC,EAA2B,IAG3BJ,EAAwBC,EAAIC,EAAQC,EAAeC,GAGnDH,EAAG5J,MAAMuO,QAAU,OAGnB,MAAM5F,SAAYsF,WAAatF,YAAa,WAC5CiB,EAAG5J,MAAMwO,cAA8B,aAAd7F,EAA2B,SAAW,MAG/D,MAAM7I,GAAS,MAAA+J,OAAA,EAAAA,EAAQ/J,UAAU,MAAAmO,OAAA,EAAAA,EAAanO,SAAUgK,EAGlD2E,EAAmC,CACvCC,GAAI,aACJC,GAAI,SACJhC,GAAI,WACJiC,GAAI,aACJC,GAAI,SACJC,GAAI,WACJC,GAAI,aACJC,GAAI,SACJC,GAAI,YAENrF,EAAG5J,MAAMkP,WAAaT,EAAS3O,IAAW,SAG1C,MAAMqP,EAAqC,CACzCT,GAAI,aACJC,GAAI,aACJhC,GAAI,aACJiC,GAAI,SACJC,GAAI,SACJC,GAAI,SACJC,GAAI,WACJC,GAAI,WACJC,GAAI,YAENrF,EAAG5J,MAAMoP,eAAiBD,EAAWrP,IAAW,SAGhD,MAAMkJ,GAAM,MAAAiF,OAAA,EAAAA,EAAajF,OAAO,MAAAa,OAAA,EAAAA,EAAQwF,SAAU,EAClD,GAAIrG,GAAOY,EAAGkB,cAAe,CAC3B,MAAMG,EAAKrB,EAAGkB,cAAcI,cAAgB,EACtCoE,EAAwB,aAAd3G,EAA2B,SAAW,YACtDiB,EAAG5J,MAAMsP,GAAW,GAAGjV,KAAKoN,MAAMwD,EAAKjC,MACzC,CAGAuG,EAAsB3F,EAAIqE,EAC5B,CA9HMuB,CACE5F,EACAC,EACAqE,EACApE,EACAC,GAEF,MACF,IAAK,QA2HT,SACEH,EACAC,EACAoE,EACAnE,EAAwB,KACxBC,EAA2B,IAG3BJ,EAAwBC,EAAIC,EAAQC,EAAeC,GAGnDH,EAAG5J,MAAMuO,QAAU,OAClB3E,EAAG5J,MAAcyP,oBAAsB,mCAGxC,MAAMzG,GAAM,MAAAiF,OAAA,EAAAA,EAAajF,OAAO,MAAAa,OAAA,EAAAA,EAAQwF,SAAU,EAClD,GAAIrG,GAAOY,EAAGkB,cAAe,CAC3B,MAAMG,EAAKrB,EAAGkB,cAAcI,cAAgB,EACtCwE,EAAQrV,KAAKoN,MAAMwD,EAAKjC,GAC7BY,EAAG5J,MAAc2P,OAAS,GAAGD,MAC7B9F,EAAG5J,MAAc4P,UAAY,GAAGF,KACnC,CAGA,MAAM5P,GAAS,MAAA+J,OAAA,EAAAA,EAAQ/J,UAAU,MAAAmO,OAAA,EAAAA,EAAanO,SAAUgK,EAClD+F,EAA+B,CACnCnB,GAAI,QACJC,GAAI,SACJhC,GAAI,MACJiC,GAAI,QACJC,GAAI,SACJC,GAAI,MACJC,GAAI,QACJC,GAAI,SACJC,GAAI,OAELrF,EAAG5J,MAAc8P,aAAeD,EAAK/P,IAAW,SAGjDyP,EAAsB3F,EAAIqE,EAC5B,CAlKM8B,CACEnG,EACAC,EACAqE,EACApE,EACAC,GAEF,MAEF,QACEJ,EAAwBC,EAAIC,EAAQC,EAAeC,GAKvD,SAAIF,WAAQmG,eAAgB,CAE1B,MAAMpB,EAAU/E,EAAOmG,eAGvB,WADGpB,WAAIqB,OAA0B,gBAAlB,MAAArB,OAAA,EAAAA,EAAIjG,eAAiCiB,EAAGkB,cACzC,CACZ,MAAMC,EAAKnB,EAAGkB,cAAcE,aAAe,EACrCkF,GAAO,MAAAjC,OAAA,EAAAA,EAAa5E,WAAY,CAAA,EAChC8G,EAAQ9V,KAAKC,IAAI,EAAGP,OAAOmW,EAAK3G,MAAQ,IACxC6G,EAAQ/V,KAAKC,IAAI,EAAGP,OAAOmW,EAAK1G,OAAS,IAEzC6G,EAAsBhW,KAAKC,IAAI,EAAG,GAAK6V,EAAQC,IAC/CE,SAA4BrC,WAAarF,WAAY,EACrD2H,EACsB,iBAAnB3B,EAAG4B,YAA2BnW,KAAKC,IAAI,EAAGsU,EAAG4B,aAAe,EAC/DC,EACmB,iBAAhB7B,EAAGhG,UAAyB,KAAK3J,KAAK2P,EAAGhG,UAC5CvO,KAAKC,IAAI,EAAGD,KAAKE,IAAI,EAAGW,WAAW0T,EAAGhG,UAAY,MAClD,EACA8H,EAAarW,KAAKE,IACtB8V,EACAC,EACAC,EACAE,GAEIE,EAAUtW,KAAKoN,MAAMsD,EAAK2F,GAE1BE,EACmB,iBAAhBhC,EAAGhG,UAAyB7O,OAAOC,SAAS4U,EAAGhG,UAClDvO,KAAKC,IAAI,EAAGD,KAAKoN,MAAMmH,EAAGhG,WAC1B7O,OAAO8W,kBAEPC,EAAKzW,KAAKE,IAAIoW,EAASC,GAC7BhH,EAAG5J,MAAM4I,SAAW,GAAGkI,MAEvBlH,EAAG5J,MAAMqL,MAAQ,MACnB,CACA0F,EAAoBnH,EAAIC,EAAOmG,eACjC,CACF,CAiHA,SAAST,EACP3F,EACAqE,GAEA,GAAKA,SAGwB,IAAzBA,EAAYrF,WACdgB,EAAG5J,MAAM4I,SAAqC,IAAvBqF,EAAYrF,SAAf,UAEQ,IAA1BqF,EAAYpF,YACde,EAAG5J,MAAM6I,UAAuC,IAAxBoF,EAAYpF,UAAf,UAEM,IAAzBoF,EAAYnF,WACdc,EAAG5J,MAAM8I,SAAqC,IAAvBmF,EAAYnF,SAAf,UAEQ,IAA1BmF,EAAYlF,YACda,EAAG5J,MAAM+I,UAAuC,IAAxBkF,EAAYlF,UAAf,KAInBkF,EAAYnM,SAAS,CACvB,MAAMkP,EAAK/C,EAAYnM,QAAQmH,GAAK,EAC9BgI,EAAKhD,EAAYnM,QAAQoH,GAAK,EACpC,GAAIU,EAAGkB,cAAe,CACpB,MAAMC,EAAKnB,EAAGkB,cAAcE,aAAe,EACrCC,EAAKrB,EAAGkB,cAAcI,cAAgB,EAC5CtB,EAAG5J,MAAM8B,QAAU,GAAGzH,KAAKoN,MAAMwD,EAAKgG,QAAS5W,KAAKoN,MAAMsD,EAAKiG,MACjE,CACF,CACF,CA+EO,SAASD,EACdnH,EACAoG,GAGA,IACGpG,EAAWsH,oBAAsBlB,CACpC,CAAA,MAEA,CACA,MAAMmB,EAAUnB,GACVtH,KACJA,EAAO,OAAAC,UACPA,EAAY,aAAAK,IACZA,EAAM,EAAAoI,MACNA,EAAQ,SAAAC,QACRA,EAAU,UACRrB,EACEC,GAAO,MAAAkB,OAAA,EAAAA,EAAIlB,OAAsB,eAAdtH,EAEzB,OAAQD,GACN,IAAK,OAAQ,CAEXkB,EAAG5J,MAAMuO,QAAU,OACnB3E,EAAG5J,MAAMwO,cAA8B,eAAd7F,EAA6B,MAAQ,SAC9DiB,EAAG5J,MAAMsR,SACPrB,GAAsB,eAAdtH,EAA6B,OAAS,SAGhD,MAAMuG,EACM,UAAVkC,EACI,aACU,QAAVA,EACE,WACA,SACFhC,EACQ,UAAZiC,EACI,aACY,QAAZA,EACE,WACY,kBAAZA,EACE,gBACA,SAMV,GAJAzH,EAAG5J,MAAMkP,WAAaA,EACtBtF,EAAG5J,MAAMoP,eAAiBA,EAGtBpG,GAAOY,EAAGkB,cAAe,CAC3B,MAAMyG,EACU,eAAd5I,EACIiB,EAAGkB,cAAcE,aAAe,EAChCpB,EAAGkB,cAAcI,cAAgB,EACjCwE,EAAQrV,KAAKoN,MAAM8J,EAAgBvI,GACnCsG,EAAwB,eAAd3G,EAA6B,YAAc,SAE1DiB,EAAG5J,MAAcsP,GAAW,GAG7B,MAAMnQ,EAAWxH,MAAMwM,KAAKyF,EAAGzK,UAC/B,IAAA,IAAShG,EAAI,EAAGA,EAAIgG,EAAS/H,OAAQ+B,IAAK,CACxC,MAAMqY,EAAIrS,EAAShG,GAEnBqY,EAAExR,MAAM8B,QAAU,MAClB0P,EAAExR,MAAMyR,KAAO,WACG,eAAd9I,GACF6I,EAAExR,MAAM0R,WAAa,MACrBF,EAAExR,MAAM2R,YAAc,GAAGjC,MACzB8B,EAAExR,MAAM4R,UAAY,KAEpBJ,EAAExR,MAAM4R,UAAkB,IAANzY,EAAU,IAAM,GAAGuW,MACvC8B,EAAExR,MAAM0R,WAAa,GACrBF,EAAExR,MAAM2R,YAAc,GAE1B,CACF,CACA,KACF,CAEA,IAAK,OAIH,GAHA/H,EAAG5J,MAAMuO,QAAU,OACnB3E,EAAG5J,MAAMyP,oBAAsB,mCAE3BzG,GAAOY,EAAGkB,cAAe,CAC3B,MAAMyG,EAAgB3H,EAAGkB,cAAcI,cAAgB,EACjDwE,EAAQrV,KAAKoN,MAAM8J,EAAgBvI,GACxCY,EAAG5J,MAAc2P,OAAS,GAAGD,MAC7B9F,EAAG5J,MAAc4P,UAAY,GAAGF,KACnC,EAUN,CElkBA,IAAImC,GAAqB,EAgBlB,SAASC,EACdC,EACAC,EAA0B,MAE1B,IAAIC,EAA+BD,EAEnC,MAAME,EAAUC,GACd,IAAIJ,MAAsB,UAAVI,EAAoB,GAAK,IAAIA,EAAMC,iBAC/CC,EAAaF,IACjB,GAAc,SAAVA,GAA8B,UAAVA,EAAmB,OAAO,EAElD,SAD8B,OAAjBF,EAAwBJ,EAAqBI,IAI5D,MAAO,CACLK,MAAO,IAAIC,KACLF,EAAU,UAAU7S,QAAQoB,IAAIsR,EAAO,YAAaK,IAE1DC,KAAM,IAAID,KACJF,EAAU,SAAS7S,QAAQgT,KAAKN,EAAO,WAAYK,IAEzD9S,KAAM,IAAI8S,KACJF,EAAU,SAAS7S,QAAQC,KAAKyS,EAAO,WAAYK,IAEzDrV,MAAO,IAAIqV,KACLF,EAAU,UAAU7S,QAAQtC,MAAMgV,EAAO,YAAaK,IAE5DE,WAAaC,IACXT,EAAeS,GAEjBC,OAAQ,KACNV,GAAe,GAEjBW,QAAS,KACPX,GAAe,GAGrB,CChBO,MAAMY,EAeX,WAAAlc,CAAYmc,EAA2B,IAdvCjc,KAAQkc,MAAiC,KAGzClc,KAAQmc,kBAAoBjc,IAC5BF,KAAQoc,MAAuB,KAC/Bpc,KAAQqc,aAA8B,KACtCrc,KAAQsc,UAAY,EACpBtc,KAAQuc,WAAa,EACrBvc,KAAQwc,OAASvB,EAAa,aAAc,MAG5Cjb,KAAQyc,mBACN,8BAA+BC,iBAAiBC,UAiXlD3c,KAAQ4c,YAAc,KAChB5c,KAAKic,QAAQY,WACf7c,KAAK8c,QAIT9c,KAAQ+c,aAAe,KACrB/c,KAAKgd,SAGPhd,KAAQid,cAAgB,KAClBjd,KAAKkc,OACPlc,KAAKkd,KAAKld,KAAKkc,MAAM7Y,cAIzBrD,KAAQmd,kBAAoB,KACtBnd,KAAKkc,OACPlc,KAAKod,QAAQpd,KAAKkc,MAAMmB,eAhY1Brd,KAAKic,QAAU,CACbY,WAAW,EACXS,aAAa,EACbvX,IAAK,GACLwX,cAAe,GACf5T,WAAW,KACRsS,GAGLjc,KAAKwd,MAAQ,CACXC,WAAW,EACXpa,YAAa,EACbga,aAAc,EACdK,WAAY,EACZC,cAAe,EACfC,gBAAiB,GAGnB5d,KAAKwc,OAAOZ,WAAW5b,KAAKic,QAAQtS,WACpC3J,KAAKwc,OAAOf,MAAM,4BAA6Bzb,KAAKic,SACpDjc,KAAKwc,OAAOf,MACV,uCACAzb,KAAKyc,mBAET,CAMA,WAAAoB,CAAYC,GACV9d,KAAK+d,cAEL/d,KAAKkc,MAAQ4B,EACb9d,KAAKge,sBAGDhe,KAAKic,QAAQY,YAAciB,EAAMG,QACnCje,KAAKke,gBAGPle,KAAKwd,MAAMna,YAAcya,EAAMza,YAC/BrD,KAAKwd,MAAMH,aAAeS,EAAMT,aAE5Brd,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,kBAAmB,CACnCnY,SAAUwa,EAAMxa,SAChBD,YAAaya,EAAMza,YACnBga,aAAcS,EAAMT,cAG1B,CAKA,WAAAU,GACO/d,KAAKkc,QAEVlc,KAAKme,OACLne,KAAKoe,uBACLpe,KAAKkc,MAAQ,KAEblc,KAAKwc,OAAOf,MAAM,kBACpB,CAKA,IAAAqB,GACM9c,KAAKwd,MAAMC,YAEfzd,KAAKwd,MAAMC,WAAY,EACvBzd,KAAKsc,UAAY+B,YAAYC,OAASte,KAAKuc,YAAc,GAErDvc,KAAKyc,oBAAsBzc,KAAKkc,MAClClc,KAAKue,sBAELve,KAAKwe,eAGHxe,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,6BAEhB,CAKA,KAAAiT,GACOhd,KAAKwd,MAAMC,YAEhBzd,KAAKwd,MAAMC,WAAY,EACvBzd,KAAKuc,WAAa8B,YAAYC,MAAQte,KAAKsc,UAE3Ctc,KAAKye,gBAEDze,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,0BAA2B/J,KAAKwd,MAAMna,aAEtD,CAKA,IAAA8a,GACEne,KAAKgd,QACLhd,KAAKwd,MAAMna,YAAc,EACzBrD,KAAKwd,MAAME,WAAa,EACxB1d,KAAKuc,WAAa,EAClBvc,KAAKwd,MAAMI,gBAAkB,EAEzB5d,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,uBAEhB,CAMA,IAAAmT,CAAKwB,IACExb,OAAOC,SAASub,IAASA,EAAO,IAErC1e,KAAKwd,MAAMna,YAAcqb,EACzB1e,KAAKuc,WAAa,EAClBvc,KAAKsc,UAAY+B,YAAYC,MAGzBte,KAAKkc,OAAS1Y,KAAKmb,IAAI3e,KAAKkc,MAAM7Y,YAAcqb,GAAQ,KAC1D1e,KAAKkc,MAAM7Y,YAAcqb,GAG3B1e,KAAK4e,aAED5e,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,0BAA2B2U,GAE3C,CAMA,OAAAtB,CAAQyB,GACN,IAAK3b,OAAOC,SAAS0b,IAASA,GAAQ,EAAG,OAEzC7e,KAAKwd,MAAMH,aAAewB,EAG1B,MAAMP,EAAMD,YAAYC,MACxBte,KAAKuc,YAAc+B,EAAMte,KAAKsc,WAAatc,KAAKwd,MAAMH,aACtDrd,KAAKsc,UAAYgC,EAEbte,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,qCAAsC8U,EAEtD,CAOA,MAAAC,CAAOC,GAEL,OADA/e,KAAKmc,cAAc/Z,IAAI2c,GAChB,IAAM/e,KAAKmc,cAAc3Z,OAAOuc,EACzC,CAKA,aAAAb,IACMle,KAAKkc,OAAUlc,KAAKkc,MAAM+B,QAAWje,KAAKwd,MAAMC,WAClDzd,KAAK8c,MAET,CAKA,QAAAkC,GACE,MAAO,IAAKhf,KAAKwd,MACnB,CAKQ,mBAAAe,GACN,IAAKve,KAAKkc,QAAUlc,KAAKyc,mBAAoB,OAE7C,MAAMwC,EAAqB,CAACX,EAAaY,KACvC,IAAKlf,KAAKwd,MAAMC,UAAW,OAE3B,MAAM0B,EAAYD,EAASE,WAAapf,KAAKkc,MAAO7Y,YACpDrD,KAAKqf,oBAAoBF,GAEzB,MAAMG,EAAuB,CAC3BC,YAAavf,KAAKwd,MAAME,aACxB8B,UAAWlB,EACXa,YACAM,MAAOzf,KAAKwd,MAAMI,gBAClB8B,UAAW1f,KAAK2f,mBAAmBrB,IAGrCte,KAAK4e,WAAWU,GAGhBtf,KAAKqc,aAAgBrc,KAAKkc,MAAc0D,0BACtCX,IAIJjf,KAAKqc,aAAgBrc,KAAKkc,MAAc0D,0BACtCX,EAEJ,CAKQ,YAAAT,GACN,MAAMqB,EAAevB,UACnB,IAAKte,KAAKwd,MAAMC,UAAW,OAE3Bzd,KAAK8f,0BAA0BxB,GAE/B,MAAMgB,EAAuB,CAC3BC,YAAavf,KAAKwd,MAAME,aACxB8B,UAAWlB,EACXa,WAAW,OAAA9T,EAAArL,KAAKkc,YAAL,EAAA7Q,EAAYhI,cAAe,EACtCoc,MAAOzf,KAAKwd,MAAMI,gBAClB8B,UAAW1f,KAAK2f,mBAAmBrB,IAGrCte,KAAK4e,WAAWU,GAGhBtf,KAAKoc,MAAQ2D,sBAAsBF,IAGrC7f,KAAKoc,MAAQ2D,sBAAsBF,EACrC,CAKQ,aAAApB,GACFze,KAAKqc,cAAgBrc,KAAKkc,OAASlc,KAAKyc,qBACzCzc,KAAKkc,MAAc8D,yBAAyBhgB,KAAKqc,cAClDrc,KAAKqc,aAAe,MAGlBrc,KAAKoc,QACP6D,qBAAqBjgB,KAAKoc,OAC1Bpc,KAAKoc,MAAQ,KAEjB,CAKQ,mBAAAiD,CAAoBF,GAC1B,MAAMM,EAAQjc,KAAKmb,IAAI3e,KAAKwd,MAAMna,YAAc8b,GAE5CM,EAAQzf,KAAKic,QAAQsB,eAEvBvd,KAAKwd,MAAMna,YAAc8b,EACzBnf,KAAKwd,MAAMI,gBAAkB6B,EAEzBzf,KAAKic,QAAQtS,WACfhB,QAAQC,KACN,uCAAuC6W,EAAMS,QAAQ,SAIzDlgB,KAAKwd,MAAMna,YAAc8b,EACzBnf,KAAKwd,MAAMI,gBAAkB6B,GAG/Bzf,KAAKwd,MAAMG,cAAgBwB,CAC7B,CAKQ,yBAAAW,CAA0BxB,GAChC,MAAM6B,GAAY7B,EAAMte,KAAKsc,WAAa,IAAQtc,KAAKwd,MAAMH,aAI7D,GAHArd,KAAKwd,MAAMna,YAAc8c,EAGrBngB,KAAKkc,QAAUlc,KAAKkc,MAAM+B,OAAQ,CACpC,MAAMkB,EAAYnf,KAAKkc,MAAM7Y,YACvBoc,EAAQjc,KAAKmb,IAAI3e,KAAKwd,MAAMna,YAAc8b,GAE5CM,EAAQzf,KAAKic,QAAQsB,gBACvBvd,KAAKwd,MAAMna,YAAc8b,EACzBnf,KAAKwd,MAAMI,gBAAkB6B,EAEjC,CACF,CAKQ,kBAAAE,CAAmBS,GAGzB,OAAOpgB,KAAKic,QAAQlW,KAAO,EAC7B,CAKQ,UAAA6Y,CAAWU,GACjB,IAAIjc,EAAcrD,KAAKwd,MAAMna,YAGzBrD,KAAKic,QAAQqB,cACfja,EAAcrD,KAAKsd,YAAYja,IAGjC,IAAA,MAAW0b,KAAY/e,KAAKmc,cAC1B,IACE4C,EAAS1b,EAAaic,EACxB,OAASjZ,GACPsC,QAAQtC,MAAM,oCAAqCA,EACrD,CAEJ,CAKQ,WAAAiX,CAAYoB,GAClB,OAAK1e,KAAKic,QAAQlW,IACXvC,KAAKoN,MAAM8N,EAAO1e,KAAKic,QAAQlW,KAAO/F,KAAKic,QAAQlW,IAD5B2Y,CAEhC,CAKQ,mBAAAV,GACDhe,KAAKkc,QAEVlc,KAAKkc,MAAMmE,iBAAiB,OAAQrgB,KAAK4c,aACzC5c,KAAKkc,MAAMmE,iBAAiB,QAASrgB,KAAK+c,cAC1C/c,KAAKkc,MAAMmE,iBAAiB,SAAUrgB,KAAKid,eAC3Cjd,KAAKkc,MAAMmE,iBAAiB,aAAcrgB,KAAKmd,mBACjD,CAKQ,oBAAAiB,GACDpe,KAAKkc,QAEVlc,KAAKkc,MAAMoE,oBAAoB,OAAQtgB,KAAK4c,aAC5C5c,KAAKkc,MAAMoE,oBAAoB,QAAStgB,KAAK+c,cAC7C/c,KAAKkc,MAAMoE,oBAAoB,SAAUtgB,KAAKid,eAC9Cjd,KAAKkc,MAAMoE,oBAAoB,aAActgB,KAAKmd,mBACpD,CA8BA,OAAAoD,GACEvgB,KAAK+d,cACL/d,KAAKmc,cAAc7b,QAEfN,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,wBAEhB,ECpaK,MAAMyW,EAAN,WAAA1gB,GACLE,KAAQmR,UAAgC,KACxCnR,KAAQkc,MAAiC,KACzClc,KAAQK,SAA4B,KACpCL,KAAQygB,GAA4B,KACpCzgB,KAAQ0gB,kBAAyC,KACjD1gB,KAAQ2gB,kBAAyC,KACjD3gB,KAAQ4gB,aAA8B,KACtC5gB,KAAQ6gB,SAA0B,KAClC7gB,KAAQ8gB,uBAA2D,GACnE9gB,KAAQ+gB,aAAmC,KAC3C/gB,KAAQghB,YAAuC,IAAA,CAE/C,YAAAC,CAAa9P,GACXnR,KAAKmR,UAAYA,EACjBnR,KAAKkhB,uBACP,CAEA,QAAAC,CAASjF,GACPlc,KAAKkc,MAAQA,EACblc,KAAKkhB,uBACP,CAEA,WAAAE,CAAY/gB,GACVL,KAAKK,SAAWA,CAClB,CAEA,cAAAghB,CAAeC,GAEb,OADAthB,KAAK8gB,uBAAuBze,KAAKif,GAC1B,KACL,MAAMC,EAAMvhB,KAAK8gB,uBAAuBU,QAAQF,GAC5CC,GAAO,GAAGvhB,KAAK8gB,uBAAuBW,OAAOF,EAAK,GAE1D,CAEA,cAAAG,SACE,KAAK,OAAArW,EAAArL,KAAKmR,gBAAL,EAAA9F,EAAgB4I,eAAe,OAAO,KAC3C,MAAMxK,EAASzJ,KAAKmR,UAAU8C,cACxBqG,EAAK7Q,EAAO0K,YACZwN,EAAKlY,EAAO4K,aAClB,IAAKiG,IAAOqH,EAAI,OAAO,KAEvB,IAAIC,EAAoB,KAOxB,OALEA,EADE5hB,KAAKkc,OAASlc,KAAKkc,MAAM2F,YAAc7hB,KAAKkc,MAAM4F,YAC/C9hB,KAAKkc,MAAM2F,WAAa7hB,KAAKkc,MAAM4F,YAEnC9hB,KAAK+hB,wBAA0B,GAAK,EAxExC,SACLC,EACAC,EACAC,GAGA,IAAI1N,EAAQwN,EACVrN,EAASsN,EACTvP,EAAO,EACPiB,EAAM,EAcR,OAlBWqO,EAAiBC,EAMnBC,GACPvN,EAASsN,EACTzN,EAAQhR,KAAKoN,MAAMqR,EAAkBC,GACrCxP,EAAOlP,KAAKoN,OAAOoR,EAAiBxN,GAAS,GAC7Cb,EAAM,IAENa,EAAQwN,EACRrN,EAASnR,KAAKoN,MAAMoR,EAAiBE,GACrCxP,EAAO,EACPiB,EAAMnQ,KAAKoN,OAAOqR,EAAkBtN,GAAU,IAGzC,CAAEjC,OAAMiB,MAAKa,QAAOG,SAC7B,CAmDWwN,CAAmB7H,EAAIqH,EAAIC,EACpC,CAEA,OAAArB,GAE2B,MAArBvgB,KAAK4gB,eACPwB,aAAapiB,KAAK4gB,cAClB5gB,KAAK4gB,aAAe,MAEtB5gB,KAAKqiB,yBACLriB,KAAK8gB,uBAAyB,GAC9B9gB,KAAKmR,UAAY,KACjBnR,KAAKkc,MAAQ,KACblc,KAAKK,SAAW,IAClB,CAEQ,qBAAA6gB,WACN,KAAM,mBAAoBoB,QAAS,OACnC,MAAM7Y,EAAS,OAAA4B,EAAArL,KAAKmR,gBAAL,EAAA9F,EAAgB4I,cAC1BxK,IAGDA,IAAWzJ,KAAK+gB,cAAgB/gB,KAAKkc,QAAUlc,KAAKghB,cAKxDhhB,KAAKqiB,yBAGLriB,KAAK+gB,aAAetX,EACpBzJ,KAAKghB,YAAchhB,KAAKkc,MAKpBlc,KAAKmR,YAAc,OAAAuD,EAAA1U,KAAKmR,UAAkBoR,oBAAWngB,MACtDpC,KAAKmR,UAAkBoR,UAAUngB,IAAI,SAExCpC,KAAKygB,GAAK,IAAI+B,eAAe,IAAMxiB,KAAKyiB,wBACxCziB,KAAKygB,GAAGiC,QAAQjZ,GAEZzJ,KAAKkc,QACPlc,KAAK0gB,kBAAoB,IAAM1gB,KAAKyiB,uBACpCziB,KAAKkc,MAAMmE,iBAAiB,iBAAkBrgB,KAAK0gB,oBAGrD1gB,KAAK2gB,kBAAoB,IAAM3gB,KAAKyiB,uBACpCpU,SAASgS,iBAAiB,mBAAoBrgB,KAAK2gB,mBAEnD3gB,KAAK2iB,gBACP,CAEQ,sBAAAN,SAEmB,MAArBriB,KAAK4gB,eACPwB,aAAapiB,KAAK4gB,cAClB5gB,KAAK4gB,aAAe,MAElB5gB,KAAKygB,KACPzgB,KAAKygB,GAAGmC,aACR5iB,KAAKygB,GAAK,MAERzgB,KAAKghB,aAAehhB,KAAK0gB,mBAC3B1gB,KAAKghB,YAAYV,oBACf,iBACAtgB,KAAK0gB,mBAGT1gB,KAAK0gB,kBAAoB,KACrB1gB,KAAK2gB,mBACPtS,SAASiS,oBAAoB,mBAAoBtgB,KAAK2gB,mBAExD3gB,KAAK2gB,kBAAoB,KAGzB3gB,KAAK+gB,aAAe,KACpB/gB,KAAKghB,YAAc,KAEfhhB,KAAKmR,YAAc,OAAA9F,EAAArL,KAAKmR,UAAkBoR,oBAAWM,SACtD7iB,KAAKmR,UAAkBoR,UAAUM,OAAO,QAE7C,CAEQ,oBAAAJ,WACN,GAAyB,MAArBziB,KAAK4gB,aAAsB,OAC/B,MAAMkC,GAAa,OAAApO,EAAA,OAAArJ,EAAArL,KAAKK,eAAL,EAAAgL,EAAe0X,mBAAUC,mBAAoB,GAChEhjB,KAAK4gB,aAAe0B,OAAOW,WAAW,KACpCjjB,KAAK4gB,aAAe,KACpB5gB,KAAK2iB,gBACJG,EACL,CAEQ,YAAAH,GACN,MAAMO,EAAOljB,KAAK0hB,iBAClB,IAAKwB,EAAM,OAEX,MAAMC,EAAM,GAAGD,EAAKxQ,QAAQwQ,EAAKvP,OAAOuP,EAAK1O,SAAS0O,EAAKvO,SAC3D,GAAI3U,KAAK6gB,WAAasC,EAAtB,CAGA,GAFAnjB,KAAK6gB,SAAWsC,EAEZnjB,KAAKmR,UAAW,CAClB,MAAMlN,EAAIjE,KAAKmR,UAAUhI,MACzBlF,EAAEkP,SAAW,WACblP,EAAEmf,cAAgB,OAClBnf,EAAEyO,KAAO,GAAGwQ,EAAKxQ,SACjBzO,EAAE0P,IAAM,GAAGuP,EAAKvP,QAChB1P,EAAEuQ,MAAQ,GAAG0O,EAAK1O,UAClBvQ,EAAE0Q,OAAS,GAAGuO,EAAKvO,UACrB,CAEA3U,KAAK8gB,uBAAuBuC,QAAS/B,GAAaA,EAAS4B,GAbhC,CAc7B,CAEQ,oBAAAnB,WACN,MAAMxR,EAAI,OAAAmE,EAAA,OAAArJ,EAAArL,KAAKK,eAAL,EAAAgL,EAAe1F,YAAf,EAAA+O,EAAsBzO,WAChC,IAAKsK,GAAW,SAANA,EAAc,OAAO,KAC/B,MAAM+S,EAAIC,OAAOhT,GAAGE,MAAM,uBAC1B,IAAK6S,EAAG,OAAO,KACf,MAAME,EAAItgB,OAAOogB,EAAE,IACbG,EAAIvgB,OAAOogB,EAAE,IACnB,OAAKE,GAAMC,EACJD,EAAIC,EADU,IAEvB,ECzMK,MAAMC,EAAN,WAAA5jB,GACLE,KAAQK,SAA4B,IAAA,CAEpC,WAAA+gB,CAAY/gB,GACVL,KAAKK,SAAWA,CAClB,CAEA,mBAAAsjB,CACEC,EACA/K,EAAgB,GAEhB,MAAM7X,MAAU2F,IAChB,IAAIkd,EAAO,EAEX,IAAA,MAAW9Q,GAAEA,KAAQ6Q,EAAO,CAC1B,GAAyB,SAArB7Q,EAAG5J,MAAMuO,QAAoB,SACjC1W,EAAI+F,IAAIgM,EAAI8Q,GAEZA,IADU9Q,EAAGiC,cAAgB,GACjB6D,CACd,CAEA,OAAO7X,CACT,CAEA,gBAAA8iB,CAAiBC,GACf,IAAK/jB,KAAKK,SAAU,OACpB,MAAM8G,EAAQnH,KAAKK,SAASuF,OAAOoe,KAAMlhB,GAAMA,EAAE8D,KAAOmd,GACxD,OAAO,MAAA5c,OAAA,EAAAA,EAAOsE,YAChB,CAEA,YAAAwY,CAAaF,GACX,GAAK/jB,KAAKK,SACV,OAAOL,KAAKK,SAASuF,OAAOoe,KAAMlhB,GAAMA,EAAE8D,KAAOmd,EACnD,ECnBF,MAAMG,EAAgD,CACpDC,oBAAoB,EACpBC,iBAAkB,mBAClBC,iBAAkB,mBAClBC,kBAAkB,GAOb,SAASC,EACdC,EACAxT,EAA8B,IAE9B,MAAMyT,EAAM,IAAKP,KAAmBlT,GAGpC,IAAI0T,EAAcF,EAAgBG,cAChC,IAAIF,EAAIL,oBAGV,IAAKM,EAAa,CAehB,GAdAA,EAAcrW,SAASuW,cAAc,OACrCF,EAAYG,UAAYJ,EAAIL,iBAG5BM,EAAYvb,MAAMgK,SAAW,WAC7BuR,EAAYvb,MAAMuO,QAAU,eAC5BgN,EAAYvb,MAAMia,cAAgB,OAG9BqB,EAAIN,oBAoDZ,SAAgCO,GAC9B,MAAMI,EAAiC,CACrCpP,GAAI,EACJC,GAAI,EACJS,GAAI,EACJC,GAAI,EACJ0O,IAAK,EACL5Z,QAAS,GAIX7J,OAAOC,QAAQujB,GAAiBzB,QAAQ,EAAEjiB,EAAKC,MAC7C,MAAM2jB,EAAS,SAAS5jB,IACxB,IAAI6jB,EAEJ,OAAQ7jB,GACN,IAAK,KACL,IAAK,KACH6jB,EAAW,GAAG5jB,MACd,MACF,IAAK,MACH4jB,EAAW,GAAG5jB,OACd,MACF,QACE4jB,EAAW1B,OAAOliB,GAGtBqjB,EAAYvb,MAAM+b,YAAYF,EAAQC,KAIxCP,EAAYvb,MAAM4M,UAChB,+IAMF2O,EAAYvb,MAAMgC,QAAU,wBAC5BuZ,EAAYvb,MAAM6C,OAAS,yBAC7B,CA3FMmZ,CAAuBT,GAIrBD,EAAIH,kBAAoBE,EAAgBlc,SAAS/H,OAAS,EAE5D,KAAOikB,EAAgBY,YACrBV,EAAYW,YAAYb,EAAgBY,YAI5CZ,EAAgBa,YAAYX,EAC9B,CAEA,OAAOA,CACT,CAiJO,SAASY,EACdd,EACAxT,EAA8B,IAE9B,MAAM0T,EAAcH,EAAkBC,EAAiBxT,GACjDuU,EAhJD,SACLb,EACA1T,EAA8B,IAE9B,MAAMyT,EAAM,IAAKP,KAAmBlT,GAEpC,IAAIuU,EAAcb,EAAYC,cAC5B,IAAIF,EAAIJ,oBAiBV,OAdKkB,IACHA,EAAclX,SAASuW,cAAc,OACrCW,EAAYV,UAAYJ,EAAIJ,iBAC5BkB,EAAYC,aAAa,wBAAyB,IAGlDD,EAAYpc,MAAMgK,SAAW,WAC7BoS,EAAYpc,MAAMia,cAAgB,OAClCmC,EAAYpc,MAAMqL,MAAQ,OAC1B+Q,EAAYpc,MAAMwL,OAAS,OAE3B+P,EAAYW,YAAYE,IAGnBA,CACT,CAuHsBE,CAAkBf,EAAa1T,GAEnD,MAAO,CAAE0T,cAAaa,cACxB,CC/IO,MAAMG,EAeX,WAAA5lB,CAAYmc,GAbZjc,KAAQK,SAA4B,KACpCL,KAAQ2lB,oBAAsBhf,IAG9B3G,KAAQ4lB,oBAAsBjf,IAE9B3G,KAAQ6lB,eAAiB,EACzB7lB,KAAQ8lB,eAAiC,IAAIjmB,EAI7CG,KAAQwc,OAASvB,EAAa,aAAc,MAsJ5Cjb,KAAQ+lB,iBAAkC1iB,IACxC,GAAKrD,KAAKK,SAAV,CAKAL,KAAK6lB,eAAiBxiB,EAEtBrD,KAAKwc,OAAOf,MACV,sBAAsBpY,EAAY6c,QAAQ,aAAalgB,KAAKK,SAASwF,KAAKtF,UAI5E,IAAA,MAAW4F,KAAOnG,KAAKK,SAASwF,KAC9B7F,KAAKgmB,WAAW7f,EAAK9C,GAGvBrD,KAAKimB,aAAa5iB,EAblB,MAFErD,KAAKwc,OAAOf,MAAM,2CArJpBzb,KAAKmR,UAAY8K,EAAQ9K,UACzBnR,KAAKic,QAAU,CACbiK,UAAW,IACX5I,aAAa,EACbvX,IAAK,GACL4D,WAAW,KACRsS,GAIL,MAAMkK,EAAmC,CACvCtJ,WAAW,EACXS,YAAatd,KAAKic,QAAQqB,YAC1BvX,IAAK/F,KAAKic,QAAQlW,IAClB4D,UAAW3J,KAAKic,QAAQtS,WLtGvB,IAAwBwR,EKwG3Bnb,KAAKomB,SAAW,IAAIpK,EAAqBmK,GLxGdhL,EK2GZnb,KAAKic,QAAQtS,UL1G9BqR,EAAqBG,EK2GnBnb,KAAKwc,OAAOZ,WAAW5b,KAAKic,QAAQtS,WAGpC3J,KAAKomB,SAAStH,OAAO9e,KAAK+lB,iBAAiBM,KAAKrmB,OAGhDA,KAAK2F,MAAQ,IAAI6a,EACjBxgB,KAAK2F,MAAMsb,aAAajhB,KAAKmR,WAG7BnR,KAAKsmB,aAAe,IAAI5C,EAExB1jB,KAAKumB,gBACP,CAKA,gBAAMC,GACJxmB,KAAKwc,OAAOf,MAAM,uDACZzb,KAAKymB,kBACXzmB,KAAKK,SAAW,KAChBL,KAAK8lB,eAAiB,IAAIjmB,CAC5B,CAMA,sBAAM6mB,CAAiBrmB,WAWrB,GATCuJ,WAAmBC,mBAAqB7J,KAAKic,QAAQtS,UAEtD3J,KAAKwc,OAAOf,MAAM,0BAA2B,CAC3ChW,QAASpF,EAASoF,QAClBkhB,WAAW,OAAAtb,EAAAhL,EAASwF,WAAT,EAAAwF,EAAe9K,SAAU,EACpCqmB,aAAa,OAAAlS,EAAArU,EAASuF,aAAT,EAAA8O,EAAiBnU,SAAU,EACxCsmB,YAAaxmB,EAASI,SAGC,QAArBJ,EAASoF,QACX,MAAM,IAAI3D,MACR,yDAAyDzB,EAASoF,YAKhD,OAAlBzF,KAAKK,gBACDL,KAAKymB,kBAGbzmB,KAAKK,SAAWA,EAGZA,EAASI,SACXT,KAAK8lB,eAAiB,IAAIjmB,EAAeQ,EAASI,SAIpDT,KAAK2F,MAAMyb,YAAY/gB,GAGvBL,KAAKsmB,aAAalF,YAAY/gB,GAE9BL,KAAKwc,OAAOf,MAAM,6BACpB,CAMA,WAAA2F,CAAY/gB,WAWV,GATCuJ,WAAmBC,mBAAqB7J,KAAKic,QAAQtS,UAEtD3J,KAAKwc,OAAOf,MAAM,qBAAsB,CACtChW,QAASpF,EAASoF,QAClBkhB,WAAW,OAAAtb,EAAAhL,EAASwF,WAAT,EAAAwF,EAAe9K,SAAU,EACpCqmB,aAAa,OAAAlS,EAAArU,EAASuF,aAAT,EAAA8O,EAAiBnU,SAAU,EACxCsmB,YAAaxmB,EAASI,SAGC,QAArBJ,EAASoF,QACX,MAAM,IAAI3D,MACR,yDAAyDzB,EAASoF,YAKhD,OAAlBzF,KAAKK,UACPL,KAAK8mB,aAGP9mB,KAAKK,SAAWA,EAGZA,EAASI,SACXT,KAAK8lB,eAAiB,IAAIjmB,EAAeQ,EAASI,SAIpDT,KAAK2F,MAAMyb,YAAY/gB,GAGvBL,KAAKsmB,aAAalF,YAAY/gB,GAE9BL,KAAKwc,OAAOf,MAAM,mBAAoB,CACpChW,QAASpF,EAASoF,QAClBshB,SAAU1mB,EAASwF,KAAKtF,OACxBymB,WAAY3mB,EAASuF,OAAOrF,OAC5B4Q,UAAW,CACTqD,MAAOxU,KAAKmR,UAAUgD,YACtBQ,OAAQ3U,KAAKmR,UAAUkD,aACvBzI,OAAQ,CACNuH,SAAUnT,KAAKmR,UAAUhI,MAAMgK,SAC/BqC,SAAUxV,KAAKmR,UAAUhI,MAAMqM,SAC/ByR,WAAYC,iBAAiBlnB,KAAKmR,WAAW8V,WAC7CvP,QAASwP,iBAAiBlnB,KAAKmR,WAAWuG,WAIlD,CA8BA,MAAAyP,CAAO9jB,GACLrD,KAAK+lB,iBAAiB1iB,EACxB,CAKQ,UAAA2iB,CAAW7f,EAAU9C,GAC3B,MAAM8D,EAAQnH,KAAKonB,SAASjhB,EAAIgB,OAChC,GAAKA,EAAL,CAMA,GAAIhB,EAAIC,YAAa,CACnB,MAAOmB,EAAUC,GAAUrB,EAAIC,YAE/B,IAAKpG,KAAKqnB,oBAAoBhkB,EAAakE,EAAUC,GAEnD,YADAxH,KAAKsnB,WAAWnhB,EAGpB,CAGAnG,KAAKunB,YAAYphB,EAAII,KAA2BJ,EAAKgB,EAAO9D,EAb5D,MAFEsF,QAAQC,KAAK,+BAA+BzC,EAAIgB,QAgBpD,CAKQ,WAAAogB,CACN5f,EACAxB,EACAgB,EACA9D,EACAmkB,GAEA,MAAMjd,EAAS,GAAGpE,EAAIS,MAAMe,EAAKf,MAG1B6gB,EAAcC,GAAc/f,EAAKC,aAAe,EACrDoB,IACAA,KAYF,GAViBnG,EAAkBQ,EAAa,CAACokB,EAAcC,KAG7D1nB,KAAK2nB,cAAchgB,EAAMxB,EAAKoE,EAAQpD,EAAOqgB,GAC7CxnB,KAAK4nB,WAAWjgB,EAAMtE,EAAakH,EAAQpD,IAE3CnH,KAAK6nB,YAAYtd,GAIA,UAAf5C,EAAKU,OAAqBV,EAAKW,SACjC,IAAA,MAAWgD,KAAS3D,EAAKW,SACvBtI,KAAKunB,YAAYjc,EAAOnF,EAAKgB,EAAO9D,EAAakH,EAGvD,CAKQ,aAAAod,CACNhgB,EACAxB,EACAoE,EACApD,EACAqgB,SAEA,GAAIxnB,KAAK2lB,gBAAgB9e,IAAI0D,GAAS,OAEtC,MAAMud,EAAU9nB,KAAK4kB,cAAcjd,GAEnC,IACGmgB,EAAgBC,QAAWD,EAAgBC,SAAW,CAAA,EACvDD,EAAQC,QAAQC,QAAUzd,EAC1Bud,EAAQC,QAAQE,MAAQ9hB,EAAIS,EAC9B,OAASshB,GAET,CAEA,IAAIC,EAA+B,KACnC,GAAIX,EAAc,CAChB,MAAMY,EAAgBpoB,KAAK2lB,gBAAgB7e,IAAI0gB,GAC/CW,SAAWC,WAAeN,UAAW,IACvC,CAkBA,GAhBA9nB,KAAK2lB,gBAAgB5e,IAAIwD,EAAQ,CAC/B5C,OACAmgB,UACAG,MAAO9hB,EAAIS,GACXyhB,UAAWroB,KAAK6lB,eAChB2B,eACAvT,cAAekU,GAAYnoB,KAAKmR,aAIjCgX,GAAYnoB,KAAKmR,WAAWkU,YAAYyC,GAGzC9nB,KAAKsoB,eAAeR,EAASngB,EAAMR,GAG/BqgB,EAAc,CAChB,MAAMY,EAAgBpoB,KAAK2lB,gBAAgB7e,IAAI0gB,GACzCe,EAAgB,OAAAld,EAAA,MAAA+c,OAAA,EAAAA,EAAezgB,WAAf,EAAA0D,EAA6B2H,OAC7C+E,EAAK,MAAAwQ,OAAA,EAAAA,EAAcpP,gBACrB,MAAAiP,OAAA,EAAAA,EAAeN,UAAW/P,GAC5BmC,EAAoBkO,EAAcN,QAAS/P,EAE/C,CAEA/X,KAAKwc,OAAOf,MAAM,8BAA8BlR,IAAU,CACxDud,UACAU,SAAU7gB,EAAKU,MACfogB,KAAqB,SAAf9gB,EAAKU,MAAoBV,EAAa8gB,UAAO,EACnDzV,OAAQrL,EAAKqL,OACbpL,YAAaD,EAAKC,YAClBgF,cAAe,MAAAzF,OAAA,EAAAA,EAAOsE,aACtBG,OAAQ,CACNuH,SAAU2U,EAAQ3e,MAAMgK,SACxBT,KAAMoV,EAAQ3e,MAAMuJ,KACpBiB,IAAKmU,EAAQ3e,MAAMwK,IACnBoC,UAAW+R,EAAQ3e,MAAM4M,UACzB2S,SAAUZ,EAAQ3e,MAAMuf,SACxBlZ,MAAOsY,EAAQ3e,MAAMqG,MACrByX,WAAYa,EAAQ3e,MAAM8d,WAC1BvP,QAASoQ,EAAQ3e,MAAMuO,UAG7B,CAKQ,UAAAkQ,CACNjgB,EACAtE,EACAkH,EACAoe,GAEA,MAAMC,EAAU5oB,KAAK2lB,gBAAgB7e,IAAIyD,GACzC,GAAKqe,GAGDjhB,EAAKE,aAAeF,EAAKE,YAAYtH,OAAS,EAAG,CACnD,MAAM8Q,EAAWrR,KAAK6oB,mBACpBlhB,EACAA,EAAKE,YACLxE,EACAulB,EAAQd,SAGV9nB,KAAK8oB,cAAcF,EAAQd,QAASzW,EACtC,CACF,CAKQ,kBAAAwX,CACNlhB,EACAE,EACAxE,EACAykB,GAEA,MAAMzW,EAAqB,CAAA,EAE3B,IAAA,MAAWvJ,KAAUD,EAAa,CAEhC,MAAMkhB,EAAejhB,EAAOrD,UAAYkD,EAAKlD,UAAYkD,EAAKC,YACxDnD,EAAWzE,KAAKgpB,kBAAkBD,GAGlCE,EAAYnhB,EAAOpD,YAAc,CAAC,KAAM,QAOxCwkB,EAAe1kB,EACnBC,EAPqBzE,KAAKgpB,kBAAkBC,IAY9C,GADepmB,EAAkBQ,EAAa6lB,GAClC,CACV,MAAM3lB,EAAWH,EAAoBC,EAAa6lB,GAE5CC,EAAiBnpB,KAAKopB,eAAethB,EAAQvE,EAAUukB,GAG7D9nB,KAAKqpB,cACHhY,EACA8X,EACArhB,EAAOwhB,SAAW,UAEtB,KAAO,CAGL,MAAMC,EAAarc,EAAYG,QAAQvF,EAAOvC,MAC9C,UACEgkB,WAAYC,SACyB,mBAA9BD,EAAWC,OAAOC,QACzB,CACA,MACMrc,EADS/J,EAAc6lB,EAAa,GACvB,EAAI,EACvBlpB,KAAK0pB,oCAAoC5hB,EAAQggB,EAAS1a,EAC5D,CACF,CACF,CAEA,OAAOiE,CACT,CAMQ,mCAAAqY,CACN5hB,EACAggB,EACAvkB,GAEA,MAAMgH,EAAUud,EAAQC,QAAQC,SAAWF,EAAQC,QAAQxd,OAC3D,IAAKA,EAAQ,OACb,MAAMof,EACJ3pB,KAAK4lB,gBAAgB9e,IAAIyD,UAGvB,MAAMqf,EAAarf,EAAOtI,SAAS,KAC/BsI,EAAO1I,MAAM,KAAK,QAClB,EACJ,OAAO+nB,EAAa5pB,KAAK4lB,gBAAgB9e,IAAI8iB,QAAc,CAC7D,KACF,IAAKD,EAAY,OACjB,MAAMnM,EAAQmM,EAAW7iB,IAAIgB,EAAOvC,MACpC,GAAKiY,GAAUA,EAAMqM,aAAgBrM,EAAMsM,aAE3C,IACE,MAAMC,EAAKvM,EAAMsM,aACC,mBAAPC,EACTA,EAAGxmB,GACMwmB,GAA6B,mBAAhBA,EAAGxmB,UACzBwmB,EAAGxmB,SAASA,EAEhB,OAAS2kB,GAET,CACF,CAKQ,iBAAAc,CAAkB3nB,GACxB,GAAqB,iBAAVA,GAAsBA,EAAMI,WAAW,WAChD,IAEE,MAAMuoB,EAAe,CACnBvkB,QAAS,MACTC,SAAU,CAAEI,KAAM,WAClBH,MAAO,CAAEM,WAAY,QACrBL,OAAQ,GACRC,KAAM,GACNokB,KAAM5oB,GAGR,OADiBrB,KAAK8lB,eAAe1lB,gBAAgB4pB,GAC5BC,IAC3B,OAAS5jB,GAOP,OANIrG,KAAKic,QAAQtS,WACfhB,QAAQC,KACN,0CAA0CvH,MAC1CgF,GAGGhF,CACT,CAGF,GAAIP,MAAMC,QAAQM,GAChB,OAAOA,EAAML,IAAKmH,GAAMnI,KAAKgpB,kBAAkB7gB,IAGjD,GAAqB,iBAAV9G,GAAgC,OAAVA,EAAgB,CAC/C,MAAMF,EAAoC,CAAA,EAC1C,IAAA,MAAYC,EAAK8oB,KAAQ5oB,OAAOC,QAAQF,GACtCF,EAASC,GAAOpB,KAAKgpB,kBAAkBkB,GAEzC,OAAO/oB,CACT,CAEA,OAAOE,CACT,CAKQ,cAAA+nB,CACNthB,EACAvE,EACAukB,GAGA,MAAMqC,EAAiBnqB,KAAKgpB,kBAAkBlhB,EAAOsiB,QAAU,CAAA,GAGzDC,EAA6B,IAC9BviB,EACHsiB,OAAQD,GAIJG,EAActqB,KAAKuqB,iBAAiBF,GAE1C,GAAyB,IAArBC,EAAY/V,KAEd,OADA5L,QAAQC,KAAK,gCAAgCd,EAAOvC,QAC7C,CAAA,EAGT,IAAI8L,EAAqB,CAAA,EAGzB,GAAIiZ,EAAYzjB,IAAI,WAAY,CAC9B,MAAM2jB,EAAgBxqB,KAAKyqB,sBACzBJ,EACA9mB,EACAukB,GAEFzW,EAAW,IAAKA,KAAamZ,EAC/B,CAQA,OALIF,EAAYzjB,IAAI,QAAUihB,GAC5B9nB,KAAK0qB,kBAAkBL,EAAgB9mB,EAAUukB,GAI5CzW,CACT,CAKQ,gBAAAkZ,CAAiBziB,aACvB,MAAM6iB,MAAYzqB,IAGZ0qB,EAAmB1d,EAAYG,QAAQvF,EAAOvC,MACpD,IAAKqlB,EACH,OAAOD,EAIT,GAAI7iB,EAAO+iB,KAST,MARoB,YAAhB/iB,EAAO+iB,KACTF,EAAMvoB,IAAI,WACe,QAAhB0F,EAAO+iB,KAChBF,EAAMvoB,IAAI,OACe,WAAhB0F,EAAO+iB,OAChBF,EAAMvoB,IAAI,WACVuoB,EAAMvoB,IAAI,QAELuoB,EAIT,GAAI,OAAAtf,EAAAuf,EAAiBE,eAAjB,EAAAzf,EAA2Bwf,KAAM,CACnC,MAAME,EAAeH,EAAiBE,SAASD,KAS/C,MARqB,YAAjBE,EACFJ,EAAMvoB,IAAI,WACgB,QAAjB2oB,EACTJ,EAAMvoB,IAAI,OACgB,WAAjB2oB,IACTJ,EAAMvoB,IAAI,WACVuoB,EAAMvoB,IAAI,QAELuoB,CACT,CAUA,MAPqD,mBAA1C,OAAAjW,EAAAkW,EAAiBpB,aAAjB,EAAA9U,EAAyBsW,eAClCL,EAAMvoB,IAAI,WAEoC,mBAArC,OAAAkU,EAAAsU,EAAiBpB,aAAjB,EAAAlT,EAAyBmT,UAClCkB,EAAMvoB,IAAI,OAGLuoB,CACT,CAKQ,qBAAAF,CACN3iB,EACAvE,EACA0nB,WAEA,MAAML,EAAmB1d,EAAYG,QAAQvF,EAAOvC,MACpD,IAAKqlB,EAIH,OAHAjiB,QAAQC,KACN,+CAA+Cd,EAAOvC,SAEjD,CAAA,EAcT,GAXIvF,KAAKic,QAAQtS,WAEf3J,KAAKwc,OAAOf,MAAM,gBAAiB,CACjClW,KAAMuC,EAAOvC,KACb2lB,YAAaN,EAAiBpB,OAC9B2B,gBACmD,mBAA1C,OAAA9f,EAAAuf,EAAiBpB,iBAAQwB,gBAKe,mBAA1C,OAAAtW,EAAAkW,EAAiBpB,aAAjB,EAAA9U,EAAyBsW,cAClC,IAEE,MAAMI,EAAUprB,KAAKqrB,oBAAoBT,EAAiBnd,SAEtDzN,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,6BAA6B3T,EAAOvC,cAAe,CACnEuC,SACAvE,WACA6nB,YAIJ,MAAM/Z,EAAWuZ,EAAiBpB,OAAOwB,aACvCljB,EACAvE,EACA6nB,GAOF,OAJIprB,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,yBAA0BpK,GAGvCA,GAAY,CAAA,CACrB,OAAShL,GAKP,OAJAsC,QAAQtC,MACN,gCAAgCyB,EAAOvC,6BACvCc,GAEK,CAAA,CACT,CAMF,OAHAsC,QAAQC,KACN,wBAAwBd,EAAOvC,sCAE1B,CAAA,CACT,CAKQ,iBAAAmlB,CACN5iB,EACAvE,EACAukB,GAEA,IAAKA,EAEH,OADAnf,QAAQC,KAAK,4BAA4Bd,EAAOvC,0BACzC,CAAA,EAIT,MAAMgF,EAAUud,EAAQC,QAAQC,SAAWF,EAAQC,QAAQxd,OAC3D,IAAKA,EAIH,OAHA5B,QAAQC,KACN,6DAA6Dd,EAAOvC,QAE/D,CAAA,EAGLvF,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,eAAe3T,EAAOvC,2BAA4B,CAClEgF,SACA+gB,UAAWxD,EAAQwD,UACnBhjB,SAAUxH,MAAMwM,KAAKwa,EAAQxf,UAAUtH,IAAK2Z,GAAMA,EAAEkK,WACpD0G,oBAAqB,6CACrBC,mBAAoB1D,EAAQnD,cAC1B,gDAKN,MAAMY,EAAcuC,EAAQnD,cAC1B,8CAGF,OAAKY,GAQLvlB,KAAKyrB,qBAAqBlhB,EAAQzC,EAAQyd,EAAahiB,GAGhD,CAAA,IAVLoF,QAAQC,KACN,4BAA4Bd,EAAOvC,8BAE9B,CAAA,EAQX,CAKQ,oBAAAkmB,CACNlhB,EACAzC,EACAyd,EACAhiB,GAEIvD,KAAKic,QAAQtS,WACfhB,QAAQoB,IACN,8CAA8CjC,EAAOvC,kBAAkBgF,gBAAqBhH,KAIhG,IAAIomB,EAAa3pB,KAAK4lB,gBAAgB9e,IAAIyD,GACrCof,IACHA,MAAiBhjB,IACjB3G,KAAK4lB,gBAAgB7e,IAAIwD,EAAQof,GAC7B3pB,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,sCAAsClR,MAI5D,IAAIiT,EAAQmM,EAAW7iB,IAAIgB,EAAOvC,MAUlC,GATKiY,IACHA,EAAQ,CACN1V,SACA+hB,aAAa,GAEfF,EAAW5iB,IAAIe,EAAOvC,KAAMiY,IAIzBA,EAAMqM,YAKJ,CAEyB7pB,KAAK0rB,uBACjC5jB,EAAOvC,KACPggB,IAGIvlB,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MACV,4BAA4B3T,EAAOvC,2BAIvCiY,EAAMqM,aAAc,EACpBrM,EAAMsM,kBAAe,EACrB9pB,KAAK2rB,oBAAoBnO,EAAO1V,EAAQyd,IAEpCvlB,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MACV,UAAU3T,EAAOvC,kDAIzB,MA3BMvF,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,4BAA4B3T,EAAOvC,QAEvDvF,KAAK2rB,oBAAoBnO,EAAO1V,EAAQyd,GA2BtC/H,EAAMsM,cACJ9pB,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,qBAAqBlY,QAAeuE,EAAOvC,QAE7B,mBAAvBiY,EAAMsM,cAEX9pB,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,2BAA2B3T,EAAOvC,QAEtDiY,EAAMsM,aAAavmB,IAEnBia,EAAMsM,cACiC,mBAAhCtM,EAAMsM,aAAavmB,UAGtBvD,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,iCAAiC3T,EAAOvC,QAE5DiY,EAAMsM,aAAavmB,SAASA,IAE5BoF,QAAQC,KACN,qDAAqDd,EAAOvC,eACrDiY,EAAMsM,eAIjBnhB,QAAQC,KACN,iDAAiDd,EAAOvC,eACxDiY,EAGN,CAMQ,sBAAAkO,CACNE,EACArG,GAEA,OAAQqG,GACN,IAAK,OACH,OAAQrG,EAAYZ,cAAc,eACpC,IAAK,SACH,OAAQY,EAAYZ,cAAc,oBACpC,IAAK,SACH,OAAQY,EAAYZ,cAAc,iBACpC,IAAK,WACH,OAAQY,EAAYZ,cAAc,mBAEpC,QAEE,OAAO,EAEb,CAKQ,mBAAAgH,CACNnO,EACA1V,EACAyd,GAEA,MAAMqF,EAAmB1d,EAAYG,QAAQvF,EAAOvC,MACpD,SAAKqlB,WAAkBpB,OAKvB,IAEE,MAAM4B,EAAUra,EAAsB,CACpCI,UAAWoU,EACXllB,SAAUL,KAAKK,SACfoN,QAASmd,EAAiBnd,QAC1B8D,SAAU,CACR9L,QAAS,QACTpC,YAAa,EACbC,SAAU,EACVuoB,UAAW,EACX9lB,IAAK/F,KAAKic,QAAQlW,KAEpB2L,SAAU,CACRoa,KAAOxJ,OAAewJ,QAGpB3B,EAAiBnqB,KAAKgpB,kBAAkBlhB,EAAOsiB,QAAU,CAAA,GAQ/D,GAL4C,mBAAjCQ,EAAiBpB,OAAOuC,MACjCnB,EAAiBpB,OAAOuC,KAAKxG,EAAa4E,EAAgBiB,GAIb,mBAApCR,EAAiBpB,OAAOC,QAAwB,CACzD,MAAMuC,EAASpB,EAAiBpB,OAAOC,QACrClE,EACA4E,EACAiB,EACA,GAKF,GAFA5N,EAAMsM,aAAekC,EAGnBA,GACkB,iBAAXA,GAC0B,mBAAzBA,EAAehP,MAEvB,IACGgP,EAAehP,MAAM,EACxB,OAASkL,GAET,CAEJ,CAEA1K,EAAMqM,aAAc,EACpBrM,EAAM4N,QAAUA,EAEZprB,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,wCAAwCjC,EAAOvC,OAE/D,OAASc,GACPsC,QAAQC,KACN,wCAAwCd,EAAOvC,OAC/Cc,EAEJ,MA/DEsC,QAAQC,KAAK,sCAAsCd,EAAOvC,OAgE9D,CAKQ,gBAAA0mB,CAAiB1hB,EAAgBqhB,WACvC,IAAIjC,EAAa3pB,KAAK4lB,gBAAgB9e,IAAIyD,GAE1C,IAAKof,EAAY,CACf,MAAMC,EAAarf,EAAOtI,SAAS,KAC/BsI,EAAO1I,MAAM,KAAK,QAClB,EACA+nB,IAAYD,EAAa3pB,KAAK4lB,gBAAgB9e,IAAI8iB,GACxD,CACA,GAAKD,EAEL,GAAIiC,EAAY,CAEd,MAAMpO,EAAQmM,EAAW7iB,IAAI8kB,GAC7B,IAAI,MAAApO,OAAA,EAAAA,EAAO4N,UAAW5N,EAAMqM,YAAa,CAEvC,GACErM,EAAMsM,cACwB,iBAAvBtM,EAAMsM,cACbtM,EAAMsM,aAAaoC,KAEnB,IACE1O,EAAMsM,aAAaoC,MACrB,OAAS7lB,GACPsC,QAAQC,KACN,4CAA4CgjB,KAC5CvlB,EAEJ,CAIF,MAAMukB,EAAmB1d,EAAYG,QAAQue,GAC7C,GAAI,OAAAvgB,EAAA,MAAAuf,OAAA,EAAAA,EAAkBpB,aAAlB,EAAAne,EAA0B8gB,QAC5B,IACEvB,EAAiBpB,OAAO2C,QACtB3O,EAAM4N,QAAQgB,eAAiB/d,SAASuW,cAAc,OAE1D,OAASve,GACPsC,QAAQC,KACN,2CAA2CgjB,IAC3CvlB,EAEJ,CAIFmX,EAAMqM,aAAc,EACpBrM,EAAMsM,kBAAe,CACvB,CACAH,EAAWnnB,OAAOopB,EACpB,KAAO,CAEL,IAAA,MAAYrmB,EAAMiY,KAAUmM,EAC1B,GAAInM,EAAM4N,SAAW5N,EAAMqM,YAAa,CAEtC,GACErM,EAAMsM,cACwB,iBAAvBtM,EAAMsM,cACbtM,EAAMsM,aAAaoC,KAEnB,IACE1O,EAAMsM,aAAaoC,MACrB,OAAS7lB,GACPsC,QAAQC,KACN,4CAA4CrD,KAC5Cc,EAEJ,CAIF,MAAMukB,EAAmB1d,EAAYG,QAAQ9H,GAC7C,GAAI,OAAAmP,EAAA,MAAAkW,OAAA,EAAAA,EAAkBpB,aAAlB,EAAA9U,EAA0ByX,QAC5B,IACEvB,EAAiBpB,OAAO2C,QACtB3O,EAAM4N,QAAQgB,eAAiB/d,SAASuW,cAAc,OAE1D,OAASve,GACPsC,QAAQC,KACN,2CAA2CrD,IAC3Cc,EAEJ,CAIFmX,EAAMqM,aAAc,EACpBrM,EAAMsM,kBAAe,CACvB,CAEF9pB,KAAK4lB,gBAAgBpjB,OAAO+H,EAC9B,CACF,CAKQ,mBAAA8gB,CAAoB5d,GAC1B,IAAKzN,KAAKK,SACR,MAAM,IAAIyB,MAAM,uBAOlB,OAAOiP,EAAsB,CAC3BI,UAHgBnR,KAAKmR,UAIrB9Q,SAAUL,KAAKK,SACfoN,UACA8D,SAAU,CACR9L,QAAS,MACTpC,YAAarD,KAAK6lB,eAClBviB,SAAU,EACVuoB,UAAW,EACX9lB,IAAK/F,KAAKic,QAAQlW,KAEpB2L,SAAU,CACRoa,KAAOxJ,OAAewJ,OAG5B,CAKQ,aAAAzC,CACNgD,EACAC,EACAza,GAEA,IAAA,MAAYzQ,EAAKC,KAAUC,OAAOC,QAAQ+qB,GACxC,QAAc,IAAVjrB,EAEJ,OAAQwQ,GACN,IAAK,MACkB,iBAAVxQ,GAA6C,iBAAhBgrB,EAAOjrB,GAC7CirB,EAAOjrB,GAAQirB,EAAOjrB,GAAkBC,EAExCgrB,EAAOjrB,GAAOC,EAEhB,MAEF,IAAK,WACkB,iBAAVA,GAA6C,iBAAhBgrB,EAAOjrB,GAC7CirB,EAAOjrB,GAAQirB,EAAOjrB,GAAkBC,EAExCgrB,EAAOjrB,GAAOC,EAEhB,MAGF,QACEgrB,EAAOjrB,GAAOC,EAItB,CAKQ,aAAAynB,CAAchB,EAAsBzW,GAU1C,GATIrR,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,oCAAqC,CAC/CwiB,cAAelb,EACfmb,aAAclrB,OAAOqB,KAAK0O,GAAY,CAAA,GAAI9Q,OAC1C8Q,cAKCA,GAA6C,IAAjC/P,OAAOqB,KAAK0O,GAAU9Q,OAIrC,YAHIP,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,gDAMhB,MAAM2a,EAAcoD,EAAQnD,cAC1B,qBAEGD,GAiBD1kB,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,gDAAiD,CAC3D2a,cACArT,aDzgCD,SACLqT,EACArT,GAEA/P,OAAOC,QAAQ8P,GAAUgS,QAAQ,EAAEjiB,EAAKC,MACtC,GAAIA,QAAuC,OAE3C,MAAM2jB,EAAS,SAAS5jB,IACxB,IAAI6jB,EAEJ,OAAQ7jB,GACN,IAAK,KACL,IAAK,KACH6jB,EAAW,GAAG5jB,MACd,MACF,IAAK,MACH4jB,EAAW,GAAG5jB,OACd,MASF,QACE4jB,EAAW1B,OAAOliB,GAGtBqjB,EAAYvb,MAAM+b,YAAYF,EAAQC,IAE1C,CC8+BIwH,CAAyB/H,EAAarT,GAElCrR,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,+CA3BZpB,QAAQC,KACN,4DACA,CACEkf,UACA4E,UAAW5E,EAAQ4E,UACnBnK,UAAWzhB,MAAMwM,KAAKwa,EAAQvF,WAC9Bja,SAAUxH,MAAMwM,KAAKwa,EAAQxf,UAAUtH,IAAKsK,IAAA,CAC1CqhB,QAASrhB,EAAMqhB,QACfpK,UAAWzhB,MAAMwM,KAAKhC,EAAMiX,cAE9BlR,YAmBR,CAKQ,aAAAuT,CAAcjd,GAEpB,MAAM6c,EAAkBxkB,KAAK4sB,sBAAsBjlB,IAG7C+c,YAAEA,EAAAa,YAAaA,GAAgBD,EAAmBd,EAAiB,CACvEL,oBAAoB,EACpBG,kBAAkB,IAuBpB,OAnBAtkB,KAAK6sB,yBAAyBllB,EAAM6c,EAAiBe,GAIrDf,EAAgBuD,QAAQxd,OAAS5C,EAAKf,GACtC8d,EAAYqD,QAAQrD,YAAc,OAClCa,EAAYwC,QAAQxC,YAAc,OAE9BvlB,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,uCAAwC,CAClDQ,OAAQ5C,EAAKf,GACbkhB,QAAStD,EACT8G,UAAW9G,EAAgB8G,UAC3BwB,iBAAkBtI,EAAgBG,cAAc,qBAChDoI,iBAAkBvI,EAAgBG,cAAc,qBAChD+H,UAAWlI,EAAgBkI,YAIxBlI,CACT,CAKQ,qBAAAoI,CAAsBjlB,GAC5B,IAAImgB,EAEJ,OAAQngB,EAAKU,OACX,IAAK,OACHyf,EAAUzZ,SAASuW,cAAc,OACjCkD,EAAQkF,YAAcrlB,EAAK8gB,MAAQ,GACnCX,EAAQjD,UAAY,WACpB,MAEF,IAAK,QACHiD,EAAUzZ,SAASuW,cAAc,OAChCkD,EAA6B5Z,IAAMvG,EAAKuG,KAAO,GAC/C4Z,EAA6BmF,IAAMtlB,EAAKslB,KAAO,GAChDnF,EAAQjD,UAAY,YACpB,MAEF,IAAK,QACHiD,EAAUzZ,SAASuW,cAAc,SAChCkD,EAA6B5Z,IAAMvG,EAAKuG,KAAO,GAC/C4Z,EAA6BoF,MAAQvlB,EAAKulB,QAAS,EACpDpF,EAAQjD,UAAY,YACpB,MAEF,IAAK,QACHiD,EAAUzZ,SAASuW,cAAc,OACjCkD,EAAQjD,UAAY,YACpB,MAEF,QACEiD,EAAUzZ,SAASuW,cAAc,OACjCkD,EAAQjD,UAAY,cAGxB,OAAOiD,CACT,CAKQ,wBAAA+E,CACNllB,EACA6c,EACAe,GAEA,OAAQ5d,EAAKU,OACX,IAAK,OAEHkd,EAAYyH,YAAcrlB,EAAK8gB,MAAQ,GAEvC,IAAA,MAAWnd,KAASxK,MAAMwM,KAAKkX,EAAgB2I,YACzC7hB,EAAMtB,WAAaojB,KAAKC,WAC1B7I,EAAgB8I,YAAYhiB,GAGhC,MAEF,IAAK,QACH,CAEE,MAAMiiB,EAAalf,SAASuW,cAAc,OAC1C2I,EAAWrf,IAAOsW,EAAqCtW,IACvDqf,EAAWN,IAAOzI,EAAqCyI,IACvDM,EAAWpkB,MAAMqL,MAAQ,OACzB+Y,EAAWpkB,MAAMwL,OAAS,OAC1B4Q,EAAYF,YAAYkI,GAEvB/I,EAAqCtW,IAAM,EAC9C,CACA,MAEF,IAAK,QACH,CAEE,MAAMsf,EAAenf,SAASuW,cAAc,SAC5C4I,EAAatf,IAAOsW,EAAqCtW,IACzDsf,EAAaN,MAAS1I,EAAqC0I,MAC3DM,EAAarkB,MAAMqL,MAAQ,OAC3BgZ,EAAarkB,MAAMwL,OAAS,OAC5B4Q,EAAYF,YAAYmI,GAEvBhJ,EAAqCtW,IAAM,EAC9C,EAON,CAKQ,cAAAoa,CACNR,EACAngB,EACAR,WAGA,MAAMsmB,SACJtmB,WAAOwE,uBACN,MAAAxE,OAAA,EAAAA,EAAO0jB,MAAOlZ,EAA2BxK,EAAM0jB,WAAQ,GAG1D1T,EACE2Q,EACAngB,EAAKqL,OACLya,EACA,KACA,CACEjW,iBAAkB7P,EAAK2B,YACvB+J,UAAW,OAAAqB,EAAA,OAAArJ,EAAArL,KAAKK,eAAL,EAAAgL,EAAe1F,YAAf,EAAA+O,EAAsBlC,SACjCe,UAAW,MAAApM,OAAA,EAAAA,EAAOqL,WAKtB,MAAMkb,GAAoB,MAAAvmB,OAAA,EAAAA,EAAOsE,eAAgB,CAAA,EAC3CkiB,EAAgBhmB,EAAKwB,OAAS,CAAA,EAC9BykB,EAAkB,IAAKF,KAAsBC,GAG7C1L,EAAkBjiB,KAAKmR,UAAUkD,cAAgB,IASvD,GCxzCG,SACLtB,EACAkP,EACA9Y,EACA0kB,GAEA,MAAM5pB,EAAI,IAAM4pB,GAAgB,CAAA,KAAS1kB,GAAS,IAQlD,GAPIlF,EAAEuL,QAAOuD,EAAG5J,MAAMqG,MAAQ+T,OAAOtf,EAAEuL,QACnCvL,EAAE6pB,aAAY/a,EAAG5J,MAAM2kB,WAAavK,OAAOtf,EAAE6pB,aAE7C7pB,EAAE8pB,aAAYhb,EAAG5J,MAAM4kB,WAAaxK,OAAOtf,EAAE8pB,aAC7C9pB,EAAE+pB,aAAYjb,EAAG5J,MAAM6kB,WAAazK,OAAOtf,EAAE+pB,aAC7B,MAAhB/pB,EAAEgqB,aAAoBlb,EAAG5J,MAAM8kB,WAAa1K,OAAOtf,EAAEgqB,aAErDhqB,EAAEykB,SAAU3V,EAAG5J,MAAMuf,SAAWnF,OAAOtf,EAAEykB,eAAQ,GACnB,iBAAlBzkB,EAAEiqB,YAA0B,CAC1C,MAAM/T,EAAK3W,KAAKC,IAAI,EAAGD,KAAKoN,MAAMqR,EAAkBhe,EAAEiqB,cACtDnb,EAAG5J,MAAMuf,SAAW,GAAGvO,KACzB,CAEA,GAAIlW,EAAEkqB,QAAuC,iBAAtBlqB,EAAEkqB,OAAOC,SAAuB,CACrD,MAAMjU,EAAK3W,KAAKC,IAAI,EAAGD,KAAKoN,MAAMqR,EAAkBhe,EAAEkqB,OAAOC,WAC5Drb,EAAG5J,MAAcklB,sBAAwB,GAAGlU,MAC5CpH,EAAG5J,MAAcmlB,sBAAwB/K,OAAOtf,EAAEkqB,OAAO3e,OAAS,QAClEuD,EAAG5J,MAAcolB,oBAAsBxb,EAAG5J,MAAMqG,OAAS,MAC5D,MAAYvL,EAAE6pB,aAEZ/a,EAAG5J,MAAM2kB,WAAa,4CAGpB7pB,EAAEsW,QAAOxH,EAAG5J,MAAMqlB,UAAYvqB,EAAEsW,OAChCtW,EAAEwqB,aACJ1b,EAAG5J,MAAMslB,WAA8B,SAAjBxqB,EAAEwqB,WAAwB,SAAWxqB,EAAEwqB,WACjE,CD+wCIC,CACE5G,EACA7F,EACA2L,EACAF,GAIiB,UAAf/lB,EAAKU,MAAmB,CAC1B,MAAMuC,EAAYjD,GCrxCjB,SACLoL,EACAkP,EACA5Y,EACA2J,GAEA,GAAK3J,EAAL,CAWA,GARIA,EAASyB,gBACXiI,EAAG5J,MAAM2B,gBAAkByY,OAAOla,EAASyB,iBAClCzB,EAAS0B,QAElBgI,EAAG5J,MAAM2B,gBAAkByY,OAAOla,EAAS0B,QAIzC1B,EAAS2B,OAAQ,CACnB,MAAM2jB,EAAMnrB,KAAKC,IACf,EACAD,KAAKoN,MAAMqR,GAAmB5Y,EAAS2B,OAAOojB,UAAY,KAK5D,GAHArb,EAAG5J,MAAMylB,YAAc,QACvB7b,EAAG5J,MAAM0lB,YAAc,GAAGF,MAC1B5b,EAAG5J,MAAM2lB,YAAcvL,OAAOla,EAAS2B,OAAOwE,OAAS,QACtB,MAA7BnG,EAAS2B,OAAO+jB,UAAmB,CACrC,MAAMC,EAAMxrB,KAAKC,IACf,EACAD,KAAKoN,MAAMqR,EAAkB5Y,EAAS2B,OAAO+jB,YAE/Chc,EAAG5J,MAAM+B,aAAe,GAAG8jB,KAC7B,CACF,CAGA,GAAI3lB,EAAS6B,aACX,GAAqC,iBAA1B7B,EAAS6B,aAClB6H,EAAG5J,MAAM+B,aAAe7B,EAAS6B,iBAC5B,CACL,MAAM8jB,EAAMxrB,KAAKC,IACf,EACAD,KAAKoN,MAAMqR,EAAkB5Y,EAAS6B,eAExC6H,EAAG5J,MAAM+B,aAAe,GAAG8jB,KAC7B,CAIF,GAAI3lB,EAAS4B,QACX,GAAgC,iBAArB5B,EAAS4B,QAClB8H,EAAG5J,MAAM8B,QAAU5B,EAAS4B,YACvB,CAEL,MAAMkP,EAAK3W,KAAKC,IACd,EACAD,KAAKoN,MAAMqR,GAAmB5Y,EAAS4B,QAAQmH,GAAK,KAEhDgI,EAAK5W,KAAKC,IACd,EACAD,KAAKoN,MAAMqR,GAAmB5Y,EAAS4B,QAAQoH,GAAK,KAEtDU,EAAG5J,MAAM8B,QAAU,GAAGmP,OAAQD,KAChC,MACF,SAAWnH,WAAQ/H,QAAS,CAE1B,MAAMkP,EAAK3W,KAAKC,IACd,EACAD,KAAKoN,MAAMqR,GAAmBjP,EAAO/H,QAAQmH,GAAK,KAE9CgI,EAAK5W,KAAKC,IACd,EACAD,KAAKoN,MAAMqR,GAAmBjP,EAAO/H,QAAQoH,GAAK,KAEpDU,EAAG5J,MAAM8B,QAAU,GAAGmP,OAAQD,KAChC,CAGwB,MAApB9Q,EAAS8B,UACX4H,EAAG5J,MAAMgC,QAAUoY,OAAOla,EAAS8B,SAxEtB,CA0EjB,CD2sCM8jB,CAAgBnH,EAAS7F,EAHF,KAFE,MAAA9a,OAAA,EAAAA,EAAOuE,kBAAmB,CAAA,KAC9Bd,EAAUvB,UAAY,CAAA,GAIe1B,EAAKqL,OACjE,CAIiB,SAAfrL,EAAKU,OACJulB,EAAgBM,aAChBN,EAAgBlF,WAEjBZ,EAAQ3e,MAAMuf,SAAW,OACzBZ,EAAQ3e,MAAMqG,MAAQsY,EAAQ3e,MAAMqG,OAAS,UAC7CsY,EAAQ3e,MAAM6kB,WAAalG,EAAQ3e,MAAM6kB,YAAc,OAE3D,CAKQ,mBAAA3G,CACN3I,EACA1b,EACAC,GAEA,OAAOyb,GAAQ1b,GAAS0b,GAAQzb,CAClC,CAEQ,QAAAmkB,CAASrD,GACf,OAAO/jB,KAAKsmB,aAAarC,aAAaF,EACxC,CAEQ,WAAA8D,CAAYtd,WAClB,MAAMqe,EAAU5oB,KAAK2lB,gBAAgB7e,IAAIyD,GACzC,IAAKqe,EAAS,OAGd5oB,KAAKisB,iBAAiB1hB,GAGtB,MAAMid,EAAeoB,EAAQpB,aACvBW,EAAWS,EAAQd,QAAQ7T,cAUjC,GARA2U,EAAQd,QAAQjF,SAChB7iB,KAAK2lB,gBAAgBnjB,OAAO+H,GAExBvK,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,mBAAmBlR,KAInCid,EAAc,CAChB,MAAMY,EAAgBpoB,KAAK2lB,gBAAgB7e,IAAI0gB,GACzCzP,EAAM,OAAArD,EAAA,OAAArJ,EAAA,MAAA+c,OAAA,EAAAA,EAAezgB,WAAf,EAAA0D,EAA6B2H,aAA7B,EAAA0B,EAAqCyE,eACjD,IAAI,MAAAiP,OAAA,EAAAA,EAAeN,UAAW/P,EAC5B,IACEmC,EAAoBkO,EAAcN,QAAS/P,EAC7C,OAASmQ,GAET,SACSC,EAAU,CAEnB,MAAM+G,EAAW/G,EAAiB9N,oBAClC,GAAI6U,EACF,IACEhV,EAAoBiO,EAAyB+G,EAC/C,OAAShH,GAET,CAEJ,CACF,CACF,CAEQ,UAAAZ,CAAWnhB,GACArF,MAAMwM,KAAKtN,KAAK2lB,gBAAgBhjB,QAAQqJ,OAAQzB,GAC/DA,EAAO9I,WAAW,GAAG0E,EAAIS,QAGlByc,QAAS9Y,GAAWvK,KAAK6nB,YAAYtd,GAChD,CAEQ,UAAAuc,GACN,IAAA,MAAYvc,KAAWvK,KAAK2lB,gBAC1B3lB,KAAK6nB,YAAYtd,EAErB,CAEA,qBAAckc,GACZ,MAAM0I,EAAUruB,MAAMwM,KAAKtN,KAAK2lB,gBAAgBhjB,QAGhD,IAAA,MAAW4H,KAAU4kB,EACnBnvB,KAAK6nB,YAAYtd,SAIb,IAAIkE,QAAepB,IACvB0S,sBAAsB,KAEpBA,sBAAsB,IAAM1S,QAGlC,CAEQ,cAAAkZ,GACNvmB,KAAKmR,UAAUhI,MAAMgK,SAAW,WAChCnT,KAAKmR,UAAUhI,MAAMqM,SAAW,SAGG,IAA/BxV,KAAKmR,UAAUgD,aAAqD,IAAhCnU,KAAKmR,UAAUkD,eACrDrU,KAAKmR,UAAUhI,MAAMqL,MAAQxU,KAAKmR,UAAUhI,MAAMqL,OAAS,OAC3DxU,KAAKmR,UAAUhI,MAAMwL,OAAS3U,KAAKmR,UAAUhI,MAAMwL,QAAU,OAC7D3U,KAAKmR,UAAUhI,MAAM+I,UACnBlS,KAAKmR,UAAUhI,MAAM+I,WAAa,SAGlClS,KAAKic,QAAQtS,WACf3J,KAAKwc,OAAOf,MAAM,mBAAoB,CACpCjH,MAAOxU,KAAKmR,UAAUgD,YACtBQ,OAAQ3U,KAAKmR,UAAUkD,aACvBzI,OAAQ,CACNuH,SAAUnT,KAAKmR,UAAUhI,MAAMgK,SAC/BqC,SAAUxV,KAAKmR,UAAUhI,MAAMqM,SAC/BhB,MAAOxU,KAAKmR,UAAUhI,MAAMqL,MAC5BG,OAAQ3U,KAAKmR,UAAUhI,MAAMwL,OAC7BzC,UAAWlS,KAAKmR,UAAUhI,MAAM+I,YAIxC,CAEQ,YAAA+T,CAAa5iB,GACnB,MAAM+rB,EAAepvB,KAAK2lB,gBAAgBpR,KAEtC6a,EAAe,GACjBpvB,KAAKwc,OAAOf,MACV,KAAKpY,EAAY6c,QAAQ,gBAAgBkP,IAG/C,CAMA,WAAAvR,CAAY3B,GACVlc,KAAKomB,SAASvI,YAAY3B,GAC1Blc,KAAK2F,MAAMwb,SAASjF,GAEhBlc,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,+BAAgC,CAC1CzG,SAAU4Y,EAAM5Y,SAChB+rB,WAAYnT,EAAMmT,WAClBxN,WAAY3F,EAAM2F,WAClBC,YAAa5F,EAAM4F,aAGzB,CAKA,WAAA/D,GACE/d,KAAKomB,SAASrI,cAEV/d,KAAKic,QAAQtS,WACfhB,QAAQoB,IAAI,8BAEhB,CAKA,OAAAwW,GACEvgB,KAAKomB,SAAS7F,UACdvgB,KAAK2F,MAAM4a,UACXvgB,KAAK8mB,aACL9mB,KAAKK,SAAW,IAClB,EElgDK,MAAMivB,EAKX,WAAAxvB,CAAY2N,EAAkB,IAH9BzN,KAAiBuvB,gBAAkB5oB,IAIjC3G,KAAKyN,QAAUA,EACfzN,KAAKorB,QAAU,CACb3d,UACAa,UAAW3H,IACX6oB,WAAY7oB,IACZ8oB,WAAY9oB,IACZ+oB,WAAY/oB,IAEhB,CAOA,2BAAMgpB,CAAsB5vB,GAC1B,MAAMqR,EAASpR,KAAK4vB,yBAAyB7vB,SACvCC,KAAK6vB,WAAWze,EACxB,CAOQ,wBAAAwe,CACN7vB,GAEA,MAAMqR,EAAqD,GAE3D,IAAA,MAAYhQ,EAAKC,KAAUC,OAAOC,QAAQxB,GACpCC,KAAK8vB,kBAAkBzuB,IACzB+P,EAAO/O,KAAK,CAAEjB,MAAK2uB,MAAO1uB,IAI9B,OAAO+P,CACT,CAOQ,iBAAA0e,CAAkBzuB,GACxB,MACmB,iBAAVA,GACG,OAAVA,GACsB,iBAAfA,EAAMwpB,MACb,CAAC,OAAQ,QAAS,QAAS,SAAS5oB,SAASZ,EAAMwpB,OAC9B,iBAAdxpB,EAAMmN,GAEjB,CAMA,gBAAcqhB,CACZze,GAEA,IAAA,MAAWhQ,IAAEA,EAAA2uB,MAAKA,KAAW3e,EAC3B,UACQpR,KAAKgwB,gBAAgB5uB,EAAK2uB,EAClC,OAAS1pB,GACPsC,QAAQC,KAAK,yBAAyBxH,MAASiF,EAGjD,CAEJ,CAOA,qBAAc2pB,CACZ5uB,EACA2uB,GAEA,MAAME,EAAcjwB,KAAKkwB,WAAWH,EAAMvhB,KAAOuhB,EAAM7hB,KAEvD,OAAQ6hB,EAAMlF,MACZ,IAAK,aACG7qB,KAAK6N,SAASzM,EAAK6uB,GACzB,MACF,IAAK,cACGjwB,KAAKuO,aAAanN,EAAK6uB,GAC7B,MACF,IAAK,QACHjwB,KAAKmwB,cAAc/uB,EAAK6uB,GACxB,MACF,IAAK,QACHjwB,KAAKowB,cAAchvB,EAAK6uB,GACxB,MACF,QACE,MAAM,IAAInuB,MAAM,2BAA4BiuB,EAAclF,QAEhE,CAOA,cAAchd,CAASzM,EAAaoN,GAMlC,IACE,MAAMT,EAAW,IAAIC,SAAS5M,EAAK,OAAOoN,YACpCT,EAASK,OAGfC,SAASC,MAAMlM,IAAI2L,GAGnB/N,KAAKorB,QAAQ9c,MAAMvH,IAAI3F,EAAK2M,GAC5B/N,KAAKuvB,YAAYxoB,IAAI3F,EAAK2M,EAC5B,OAAS1H,GACP,MAAM,IAAIvE,MAAM,wBAAwBV,WAAaoN,MAAQnI,IAC/D,CACF,CAOA,kBAAckI,CAAanN,EAAaoN,GACtC,OAAO,IAAIC,QAAQ,CAACpB,EAASqB,KAC3B,MAAMC,EAAM,IAAIC,MAEhBD,EAAIE,OAAS,KACX7O,KAAKorB,QAAQoE,OAAOzoB,IAAI3F,EAAKoN,GAC7BnB,KAGFsB,EAAIG,QAAU,KACZJ,EAAO,IAAI5M,MAAM,4BAA4B0M,OAG/CG,EAAIT,IAAMM,GAEd,CAOQ,aAAA2hB,CAAc/uB,EAAaoN,GACjCxO,KAAKorB,QAAQqE,OAAO1oB,IAAI3F,EAAKoN,EAC/B,CAOQ,aAAA4hB,CAAchvB,EAAaoN,GACjCxO,KAAKorB,QAAQsE,OAAO3oB,IAAI3F,EAAKoN,EAC/B,CAOQ,UAAA0hB,CAAW1hB,GAEjB,OACEA,EAAI/M,WAAW,YACf+M,EAAI/M,WAAW,aACf+M,EAAI/M,WAAW,SAER+M,EAILxO,KAAKyN,QACA,IAAIE,IAAIa,EAAKxO,KAAKyN,SAAS4iB,KAI7B,IAAI1iB,IAAIa,EAAK8T,OAAOgO,SAASD,MAAMA,IAC5C,CAQA,WAAAE,CAAYnvB,EAAaypB,GAEvB,GAAIA,EACF,OAAQA,GACN,IAAK,OACH,OAAO7qB,KAAKorB,QAAQ9c,MAAMzH,IAAIzF,GAAOA,EAAM,KAC7C,IAAK,QACH,OAAOpB,KAAKorB,QAAQoE,OAAO1oB,IAAI1F,IAAQ,KACzC,IAAK,QACH,OAAOpB,KAAKorB,QAAQqE,OAAO3oB,IAAI1F,IAAQ,KACzC,IAAK,QACH,OAAOpB,KAAKorB,QAAQsE,OAAO5oB,IAAI1F,IAAQ,KAK7C,OACEpB,KAAKorB,QAAQoE,OAAO1oB,IAAI1F,IACxBpB,KAAKorB,QAAQqE,OAAO3oB,IAAI1F,IACxBpB,KAAKorB,QAAQsE,OAAO5oB,IAAI1F,KACvBpB,KAAKorB,QAAQ9c,MAAMzH,IAAIzF,GAAOA,EAAM,KAEzC,CAOA,YAAAovB,CAAapvB,GACX,OAAOpB,KAAKuvB,YAAY1oB,IAAIzF,EAC9B,CAKA,OAAAmf,GAEE,IAAA,MAAWxS,KAAY/N,KAAKuvB,YAAY/jB,SACtC6C,SAASC,MAAM9L,OAAOuL,GAIxB/N,KAAKorB,QAAQ9c,MAAMhO,QACnBN,KAAKorB,QAAQoE,OAAOlvB,QACpBN,KAAKorB,QAAQqE,OAAOnvB,QACpBN,KAAKorB,QAAQsE,OAAOpvB,QACpBN,KAAKuvB,YAAYjvB,OACnB,CAMA,UAAAmwB,GACE,OAAOzwB,KAAKorB,OACd,CAMA,YAAAsF,GAOE,MAAMC,EAAQ,CACZriB,MAAOtO,KAAKorB,QAAQ9c,MAAMiG,KAC1Bib,OAAQxvB,KAAKorB,QAAQoE,OAAOjb,KAC5Bkb,OAAQzvB,KAAKorB,QAAQqE,OAAOlb,KAC5Bmb,OAAQ1vB,KAAKorB,QAAQsE,OAAOnb,KAC5Bqc,MAAO,GAIT,OADAD,EAAMC,MAAQD,EAAMriB,MAAQqiB,EAAMnB,OAASmB,EAAMlB,OAASkB,EAAMjB,OACzDiB,CACT,CAOA,eAAAE,CAAgBhG,GACd,GAAIA,EACF,OAAQA,GACN,IAAK,OACH,OAAO/pB,MAAMwM,KAAKtN,KAAKorB,QAAQ9c,MAAM3L,QACvC,IAAK,QACH,OAAO7B,MAAMwM,KAAKtN,KAAKorB,QAAQoE,OAAO7sB,QACxC,IAAK,QACH,OAAO7B,MAAMwM,KAAKtN,KAAKorB,QAAQqE,OAAO9sB,QACxC,IAAK,QACH,OAAO7B,MAAMwM,KAAKtN,KAAKorB,QAAQsE,OAAO/sB,QACxC,QACE,MAAO,GAKb,MAAO,IACF3C,KAAKorB,QAAQ9c,MAAM3L,UACnB3C,KAAKorB,QAAQoE,OAAO7sB,UACpB3C,KAAKorB,QAAQqE,OAAO9sB,UACpB3C,KAAKorB,QAAQsE,OAAO/sB,OAE3B,EC9TF,MAAMmuB,EACE,qJADFA,EAGF,uKAHEA,EAKF,mUALEA,EAOF,ueAPEA,EASF,maATEA,EAWF,8dAXEA,EAaF,sNAbEA,EAeF,qNCbJ,IAAIrM,EAA8B,KAElC,SAASsM,EAAI3vB,EAAa4vB,GAExB,OAAaA,CACf,CAEA,SAASC,EAAoBhtB,GAC3B,OAAOA,EAAEE,SAAS,KAAOF,EAAIA,EAAI,GACnC,CAEO,SAASitB,IACd,GAAIzM,EAAK,OAAOA,EAEhB,MAAM5S,EAAQkf,EAAI,EAAoB,QAChCI,EAAaJ,EAAI,EAAsB,yBAEvCK,EAAYL,EAAI,EAA0B,4BAMhD,OALAtM,EAAM,CACJ5S,KAAe,WAATA,GAA8B,UAATA,GAA6B,SAATA,EAAkBA,EAAO,OACxEsf,WAAYA,EAAWE,QAAQ,MAAO,IACtCD,UAAWH,EAAoBG,IAE1B3M,CACT,CAEO,SAAS6M,EAAoBpe,GAClC,MAAMqe,EAAML,IACZzM,EAAM,CACJ5S,KAAMqB,EAAKrB,MAAQ0f,EAAI1f,KACvBsf,YAAaje,EAAKie,YAAcI,EAAIJ,YAAYE,QAAQ,MAAO,IAC/DD,UAAWH,EAAoB/d,EAAKke,WAAaG,EAAIH,WAEzD,CChBA,IAAII,EAAyC,CAC3C7nB,WAAW,EACX5D,IAAK,GACLuX,aAAa,EACb4I,UAAW,KAkJb,SAASuL,GAAuBpsB,SAE9B,MAAMie,EAAIC,OAAOle,GAAMoL,MAAM,uCACvBihB,EAAS,OAAArmB,EAAA,MAAAiY,OAAA,EAAAA,EAAGqO,aAAH,EAAAtmB,EAAWqmB,OAC1B,IAAKA,EAAQ,OAAO,KACpB,MAAME,EAAKF,EAAOG,YAAY,KACxBtsB,EAAOqsB,EAAK,EAAIF,EAAOptB,MAAM,EAAGstB,GAAMF,EACtCjsB,EAAUmsB,EAAK,EAAIF,EAAOptB,MAAMstB,EAAK,GAAK,QAChD,IACE,MAAME,EAAuB,oBAAXxP,OAA0B,IAAI3U,IAAItI,EAAMid,OAAOgO,SAASyB,QAAU,IAAIpkB,IAAItI,EAAM,qBAElG,MAAO,CAAEE,OAAME,UAASgI,QADR,IAAIE,IAAI,KAAMmkB,GAAGlkB,WAEnC,CAAA,MACE,OAAO,IACT,CACF,8BFlKO,MAgBL,WAAA9N,CACEge,EACAvM,EACAJ,EACA+B,EAAoC,CAAA,GAhBtClT,KAAQuG,KAA2B,KACnCvG,KAAQgyB,QAAmC,KAC3ChyB,KAAQiyB,YAAa,EACrBjyB,KAAQkyB,UAAgC,KACxClyB,KAAQmyB,OAAkC,KAC1CnyB,KAAQoyB,SAAoC,KAC5CpyB,KAAQqyB,WAAgC,GACxCryB,KAAQsyB,eAAsD,KAC9DtyB,KAAQuyB,UAA2B,KACnCvyB,KAAQwyB,OAAS,KACjBxyB,KAAQyyB,iBAAkC,KAQxCzyB,KAAK8d,MAAQA,EACb9d,KAAKuR,SAAWA,EAChBvR,KAAKmR,UAAYA,EACW,MAAxB+B,EAAKwf,uBAA8BT,aAAe/e,EAAKwf,gBAC7D,CAEA,KAAAC,GACE,GAAI3yB,KAAKuG,KAAM,OACfvG,KAAK4yB,eACL,MAAMrsB,EAAO8H,SAASuW,cAAc,OACpCre,EAAKif,aAAa,oBAAqB,IACvCjf,EAAK4C,MAAM0pB,QAAU,OACrBtsB,EAAK4C,MAAMgK,SAAW,WACtB5M,EAAK4C,MAAMuJ,KAAO,IAClBnM,EAAK4C,MAAMwJ,MAAQ,IACnBpM,EAAK4C,MAAMsJ,OAAS,IACpBlM,EAAK4C,MAAMia,cAAgB,OAC3B7c,EAAK4C,MAAM2pB,OAAS,KACpBvsB,EAAK4C,MAAMqG,MAAQ,OACnBjJ,EAAK4C,MAAM4kB,WAAa,4BACxBxnB,EAAK4C,MAAMuO,QAAU,OACrBnR,EAAK4C,MAAMwO,cAAgB,SAG3B,MAAMob,EAAoB1kB,SAASuW,cAAc,OACjDmO,EAAkBlO,UAAY,yBAE9B,MAAM3H,EAAO7O,SAASuW,cAAc,SACpC1H,EAAK2N,KAAO,QACZ3N,EAAKxZ,IAAM,IACXwZ,EAAKzZ,IAAM,IACXyZ,EAAK8V,KAAO,OACZ9V,EAAK7b,MAAQ,IACb6b,EAAK2H,UAAY,mBACjB3H,EAAK+V,QAAU,KACb,MAAM9qB,EAAIjF,OAAOga,EAAK7b,OACtB,GAAI6B,OAAOC,SAASgF,GAAI,CACtB,MAAM+qB,EAAMlzB,KAAK8d,MAAMxa,SACnBJ,OAAOC,SAAS+vB,IAAQA,EAAM,IAChClzB,KAAK8d,MAAMza,YAAcG,KAAKC,IAAI,EAAGD,KAAKE,IAAIwvB,EAAK/qB,IAEvD,GAEFnI,KAAKmyB,OAASjV,EACd6V,EAAkB1N,YAAYnI,GAG9B,MAAMiW,EAAa9kB,SAASuW,cAAc,OAC1CuO,EAAWtO,UAAY,kBAGvB,MAAMuO,EAAe/kB,SAASuW,cAAc,OAC5CwO,EAAavO,UAAY,oBAGzB,MAAMwO,EAAUhlB,SAASuW,cAAc,UACvCyO,EAAQxO,UAAY,uBACpBwO,EAAQ3G,UAAY1sB,KAAK8d,MAAMG,OAAS6S,EAAaA,EACrDuC,EAAQ7N,aAAa,aAAc,WACnC6N,EAAQC,QAAU,KACZtzB,KAAK8d,MAAMG,OAAQje,KAAK8d,MAAMhB,OAC7B9c,KAAK8d,MAAMd,SAIlB,MAAMuW,EAAcllB,SAASuW,cAAc,OAC3C2O,EAAY1O,UAAY,mBAExB,MAAM2O,EAAYnlB,SAASuW,cAAc,UACzC4O,EAAU3O,UAAY,yBACtB2O,EAAU9G,UACR1sB,KAAK8d,MAAM2V,OAAS,EAAI3C,EAAiBA,EAC3C0C,EAAUhO,aAAa,aAAc,OACrCgO,EAAUF,QAAU,WAClBtzB,KAAK8d,MAAMoP,OAASltB,KAAK8d,MAAMoP,MAC3BltB,KAAK8d,MAAMoP,OACbsG,EAAU9G,UAAYoE,EACtB9wB,KAAK8d,MAAM2V,OAAS,IAEpBD,EAAU9G,UAAYoE,EACtB9wB,KAAK8d,MAAM2V,QAAS,OAAApoB,EAAArL,KAAKoyB,eAAL,EAAA/mB,EAAehK,OAC/B6B,OAAOlD,KAAKoyB,SAAS/wB,OACrB,IAIR,MAAMqyB,EAAMrlB,SAASuW,cAAc,SACnC8O,EAAI7I,KAAO,QACX6I,EAAIhwB,IAAM,IACVgwB,EAAIjwB,IAAM,IACViwB,EAAIV,KAAO,OACXU,EAAIryB,MAAQkiB,OAAOvjB,KAAK8d,MAAM2V,QAAU,GACxCC,EAAI7O,UAAY,oBAChB6O,EAAIT,QAAU,KACZ,MAAM9qB,EAAIjF,OAAOwwB,EAAIryB,OACjB6B,OAAOC,SAASgF,KAClBnI,KAAK8d,MAAM2V,OAASjwB,KAAKC,IAAI,EAAGD,KAAKE,IAAI,EAAGyE,IAC5CnI,KAAK8d,MAAMoP,MAAc,IAAN/kB,EACnBqrB,EAAU9G,UAAYvkB,EAAI,EAAI2oB,EAAiBA,IAGnD9wB,KAAKoyB,SAAWsB,EAGhB,MAAMC,EAActlB,SAASuW,cAAc,OAC3C+O,EAAY9O,UAAY,mBAExB,MAAM+O,EAAcvlB,SAASuW,cAAc,OAC3CgP,EAAY/O,UAAY,mBAExB,MAAMgP,EAAcxlB,SAASuW,cAAc,SAC3CiP,EAAYhJ,KAAO,QACnBgJ,EAAYnwB,IAAM,IAClBmwB,EAAYpwB,IAAM,IAClBowB,EAAYb,KAAO,OACnBa,EAAYxyB,MAAQkiB,OAAOvjB,KAAK8d,MAAM2V,QAAU,GAChDI,EAAYhP,UAAY,sBACxBgP,EAAYrO,aAAa,SAAU,YACnCqO,EAAYZ,QAAU,KACpB,MAAM9qB,EAAIjF,OAAO2wB,EAAYxyB,OACzB6B,OAAOC,SAASgF,KAClBnI,KAAK8d,MAAM2V,OAASjwB,KAAKC,IAAI,EAAGD,KAAKE,IAAI,EAAGyE,IAC5CnI,KAAK8d,MAAMoP,MAAc,IAAN/kB,EACnBqrB,EAAU9G,UAAYvkB,EAAI,EAAI2oB,EAAiBA,EAE3C9wB,KAAKoyB,WAAUpyB,KAAKoyB,SAAS/wB,MAAQkiB,OAAOpb,IAEhDnI,KAAK8zB,yBAAyBD,EAAa1rB,KAK/CnI,KAAK8zB,yBAAyBD,EAAa3wB,OAAO2wB,EAAYxyB,QAE9DuyB,EAAYvO,YAAYwO,GACxBF,EAAYtO,YAAYuO,GAExBL,EAAYlO,YAAYmO,GACxBD,EAAYlO,YAAYsO,GAGxBD,EAAIvqB,MAAMuO,QAAU,OACpB6b,EAAYlO,YAAYqO,GAGxB1zB,KAAKoyB,SAAWyB,EAGhB,MAAME,EAAsB,IAAM/zB,KAAKg0B,kBACjCC,EAAqB,IAAMj0B,KAAKk0B,0BAChCC,EAAqB,IAAMn0B,KAAKo0B,wBAEtCZ,EAAUnT,iBAAiB,aAAc0T,GACzCR,EAAYlT,iBAAiB,aAAc4T,GAC3CN,EAAYtT,iBAAiB,aAAc8T,GAE3Cn0B,KAAKqyB,WAAWhwB,KAAK,KACnBmxB,EAAUlT,oBAAoB,aAAcyT,GAC5CR,EAAYjT,oBAAoB,aAAc2T,GAC9CN,EAAYrT,oBAAoB,aAAc6T,KAIhD,MAAMzV,EAAOrQ,SAASuW,cAAc,QACpClG,EAAKmG,UAAY,WACjB7kB,KAAKkyB,UAAYxT,EAEjB0U,EAAa/N,YAAYgO,GACzBD,EAAa/N,YAAYkO,GACzBH,EAAa/N,YAAY3G,GAGzB,MAAM2V,EAAgBhmB,SAASuW,cAAc,OAC7CyP,EAAcxP,UAAY,qBAG1B,MAAMyP,EAASjmB,SAASuW,cAAc,UACtC0P,EAAOzP,UAAY,0BACnByP,EAAO5H,UAAY1sB,KAAKiyB,WACpBnB,EACAA,EACJwD,EAAO9O,aAAa,eAAgBjC,OAAOvjB,KAAKiyB,aAChDqC,EAAO9O,aAAa,aAAc,SAClC8O,EAAOhB,QAAU,KACftzB,KAAKiyB,YAAcjyB,KAAKiyB,WACxBjyB,KAAKuR,SAASgjB,mBAAmBv0B,KAAKiyB,YACtCqC,EAAO5H,UAAY1sB,KAAKiyB,WACpBnB,EACAA,EACJwD,EAAO9O,aAAa,eAAgBjC,OAAOvjB,KAAKiyB,cAIlD,MAAMuC,EAAQnmB,SAASuW,cAAc,UACrC4P,EAAM3P,UAAY,6BAClB2P,EAAM9H,UAAYre,SAASomB,kBACvB3D,EACAA,EACJ0D,EAAMhP,aAAa,aAAc,QACjCgP,EAAMlB,QAAU,KACdtzB,KAAK00B,qBAGPL,EAAchP,YAAYiP,GAC1BD,EAAchP,YAAYmP,GAE1BrB,EAAW9N,YAAY+N,GACvBD,EAAW9N,YAAYgP,GAEvB9tB,EAAK8e,YAAY0N,GACjBxsB,EAAK8e,YAAY8N,GACjBnzB,KAAKmR,UAAUkU,YAAY9e,GAC3BvG,KAAKuG,KAAOA,EAGZvG,KAAKuR,SAASgjB,mBAAmBv0B,KAAKiyB,YAGtC,MAAM0C,EAAS,IAAM30B,KAAK40B,WACpBC,EAAS,IAAM70B,KAAK40B,UAAS,GACnC50B,KAAK8d,MAAMuC,iBAAiB,aAAcsU,GAC1C30B,KAAK8d,MAAMuC,iBAAiB,SAAUsU,GACtC30B,KAAK8d,MAAMuC,iBAAiB,iBAAkBwU,GAC9C70B,KAAK8d,MAAMuC,iBAAiB,iBAAkBwU,GAC9C70B,KAAK8d,MAAMuC,iBAAiB,eAAgBwU,GAC5CxmB,SAASgS,iBAAiB,mBAAoBwU,GAC9C70B,KAAKqyB,WAAWhwB,KAAK,KACnBrC,KAAK8d,MAAMwC,oBAAoB,aAAcqU,GAC7C30B,KAAK8d,MAAMwC,oBAAoB,SAAUqU,GACzC30B,KAAK8d,MAAMwC,oBAAoB,iBAAkBuU,GACjD70B,KAAK8d,MAAMwC,oBAAoB,iBAAkBuU,GACjD70B,KAAK8d,MAAMwC,oBAAoB,eAAgBuU,GAC/CxmB,SAASiS,oBAAoB,mBAAoBuU,KAInD,MAAMC,EAASC,IACR/0B,KAAKuG,OAIPvG,KAAKuG,KAAKyuB,SAAS3mB,SAAS4mB,gBAC7B5mB,SAAS4mB,gBAAkBj1B,KAAKuG,QAMlCvG,KAAKk1B,eACLl1B,KAAKm1B,eAGLJ,EAAEK,kBAEY,MAAVL,EAAE3zB,KAEJ2zB,EAAEM,iBACEr1B,KAAK8d,MAAMG,OAAQje,KAAK8d,MAAMhB,OAC7B9c,KAAK8d,MAAMd,SACG,eAAV+X,EAAE3zB,IACXpB,KAAK8d,MAAMza,YAAcG,KAAKE,IAC5B1D,KAAK8d,MAAMxa,UAAY0F,IACvBhJ,KAAK8d,MAAMza,YAAc,GAER,cAAV0xB,EAAE3zB,IACXpB,KAAK8d,MAAMza,YAAcG,KAAKC,IAAI,EAAGzD,KAAK8d,MAAMza,YAAc,GAC3C,WAAV0xB,EAAE3zB,KACPiN,SAASomB,mBAAmBpmB,SAASinB,mBAG7C/uB,EAAKgvB,SAAW,EAChBhvB,EAAK4C,MAAM0pB,QAAU,OACrBtsB,EAAK8Z,iBAAiB,UAAWyU,GACjC90B,KAAKqyB,WAAWhwB,KAAK,IAAMkE,EAAK+Z,oBAAoB,UAAWwU,IAE/D,MAAMU,EAAY,IAAMjvB,EAAKkvB,MAAM,CAAEC,eAAe,IAC9CC,EAAU,KACd31B,KAAKk1B,eACLM,KAEII,EAAU,KACd51B,KAAKm1B,gBAEDU,EAAS,KACb71B,KAAKk1B,eACLl1B,KAAKm1B,gBAIDW,EAAgBf,IAEhB/0B,KAAKuG,MAAQvG,KAAKuG,KAAKyuB,SAASD,EAAE1I,UAOlCrsB,KAAK8d,MAAMG,OACbje,KAAK8d,MAAMhB,OAEX9c,KAAK8d,MAAMd,QAIbhd,KAAKk1B,eACLl1B,KAAKm1B,gBAbHK,KAkBEO,EAAsBhB,IAEtB/0B,KAAKuG,MAAQvG,KAAKuG,KAAKyuB,SAASD,EAAE1I,SAEtCrsB,KAAK00B,qBAGP10B,KAAKmR,UAAUkP,iBAAiB,aAAcsV,GAC9C31B,KAAKmR,UAAUkP,iBAAiB,aAAcuV,GAC9C51B,KAAKmR,UAAUkP,iBAAiB,YAAawV,GAC7C71B,KAAKmR,UAAUkP,iBAAiB,QAASyV,GACzC91B,KAAKmR,UAAUkP,iBAAiB,WAAY0V,GAC5C/1B,KAAKqyB,WAAWhwB,KAAK,KACnBrC,KAAKmR,UAAUmP,oBAAoB,aAAcqV,GACjD31B,KAAKmR,UAAUmP,oBAAoB,aAAcsV,GACjD51B,KAAKmR,UAAUmP,oBAAoB,YAAauV,GAChD71B,KAAKmR,UAAUmP,oBAAoB,QAASwV,GAC5C91B,KAAKmR,UAAUmP,oBAAoB,WAAYyV,KAIjD,MAAMC,EAAS,KACb3C,EAAQ3G,UAAYoE,GAEhBmF,EAAU,KACd5C,EAAQ3G,UAAYoE,GAIhBoF,EAAa,KACjB1B,EAAM9H,UAAYre,SAASomB,kBACvB3D,EACAA,GAENziB,SAASgS,iBAAiB,mBAAoB6V,GAC9Cl2B,KAAKqyB,WAAWhwB,KAAK,IACnBgM,SAASiS,oBAAoB,mBAAoB4V,IAEnDl2B,KAAK8d,MAAMuC,iBAAiB,OAAQ2V,GACpCh2B,KAAK8d,MAAMuC,iBAAiB,QAAS4V,GACrCj2B,KAAKqyB,WAAWhwB,KAAK,KACnBrC,KAAK8d,MAAMwC,oBAAoB,OAAQ0V,GACvCh2B,KAAK8d,MAAMwC,oBAAoB,QAAS2V,KAI1Cj2B,KAAK40B,UAAS,GACd50B,KAAKm2B,yBAGL,MAAMC,EAAa,OACJ/nB,SAASomB,mBAEfz0B,KAAKsyB,iBACRtyB,KAAKsyB,eAAiB,CACpB9d,MAAOxU,KAAK8d,MAAM3U,MAAMqL,MACxBG,OAAQ3U,KAAK8d,MAAM3U,MAAMwL,OACzB0hB,UAAWr2B,KAAK8d,MAAM3U,MAAMktB,UAC5BvrB,gBAAiB9K,KAAK8d,MAAM3U,MAAM2B,kBAGtC9K,KAAK8d,MAAM3U,MAAMqL,MAAQ,OACzBxU,KAAK8d,MAAM3U,MAAMwL,OAAS,OACzB3U,KAAK8d,MAAM3U,MAAcktB,UAAY,UACtCr2B,KAAK8d,MAAM3U,MAAM2B,gBAAkB,QAC1B9K,KAAKsyB,iBACdtyB,KAAK8d,MAAM3U,MAAMqL,MAAQxU,KAAKsyB,eAAe9d,OAAS,GACtDxU,KAAK8d,MAAM3U,MAAMwL,OAAS3U,KAAKsyB,eAAe3d,QAAU,GACvD3U,KAAK8d,MAAM3U,MAAcktB,UACvBr2B,KAAKsyB,eAAuB+D,WAAa,GAC5Cr2B,KAAK8d,MAAM3U,MAAM2B,gBACf9K,KAAKsyB,eAAexnB,iBAAmB,KAG7CsrB,IACA,MAAME,EAAO,IAAMF,IACnB/nB,SAASgS,iBAAiB,mBAAoBiW,GAC9Ct2B,KAAKqyB,WAAWhwB,KAAK,IACnBgM,SAASiS,oBAAoB,mBAAoBgW,IAInDt2B,KAAKk1B,eACLl1B,KAAKm1B,cACP,CAEA,OAAAoB,GAEEv2B,KAAKo0B,wBAEDp0B,KAAKuG,MAAQvG,KAAKuG,KAAK0N,eACzBjU,KAAKuG,KAAK0N,cAAcqZ,YAAYttB,KAAKuG,MAC3CvG,KAAKuG,KAAO,KACRvG,KAAKgyB,SAAWhyB,KAAKgyB,QAAQwE,YAC/Bx2B,KAAKgyB,QAAQwE,WAAWlJ,YAAYttB,KAAKgyB,SAC3ChyB,KAAKgyB,QAAU,KACfhyB,KAAKqyB,WAAWhP,QAAS0G,IACvB,IACEA,GACF,OAASgL,GAET,IAEF/0B,KAAKqyB,WAAa,EACpB,CAEA,kBAAAkC,CAAmBkC,GACjBz2B,KAAKiyB,aAAewE,EACpBz2B,KAAKuR,SAASgjB,mBAAmBv0B,KAAKiyB,WACxC,CAEA,uBAAMyC,WACJ,MAAM3hB,EAAU/S,KAAKmR,UACjB9C,SAASomB,wBACL,OAAAppB,WAASiqB,qBAAT,EAAAjqB,EAAAqrB,KAAAroB,iBAGF,OAAAqG,IAAGggB,wBAAH,EAAAhgB,EAAAgiB,KAAA3jB,GACR,CAEQ,YAAA6f,GACN,GAAI5yB,KAAKgyB,QAAS,OAElB,MAAMjf,EAAK1E,SAASuW,cAAc,SAClC7R,EAAGyS,aAAa,0BAA2B,IAE3CzS,EAAGia,YAAc,4oMAuPjB3e,SAASsoB,KAAKtR,YAAYtS,GAC1B/S,KAAKgyB,QAAUjf,CACjB,CAEA,OAAA6jB,GACE52B,KAAKu2B,SACP,CAEQ,QAAA3B,CAASiC,GAAQ,GACvB,MAAM3D,EAAMlzB,KAAK8d,MAAMxa,SACjBiuB,EAAMvxB,KAAK8d,MAAMza,YAGvB,GAFIrD,KAAKkyB,YACPlyB,KAAKkyB,UAAUlF,YAAc,GAAGhtB,KAAK82B,IAAIvF,QAAUruB,OAAOC,SAAS+vB,GAAOlzB,KAAK82B,IAAI5D,GAAO,UACxFlzB,KAAKmyB,SACH0E,GAAS3zB,OAAOC,SAAS+vB,KAAMlzB,KAAKmyB,OAAO1uB,IAAM8f,OAAO2P,IACxDhwB,OAAOC,SAASouB,UAAWY,OAAO9wB,MAAQkiB,OAAOgO,IAEjDruB,OAAOC,SAAS+vB,IAAQA,EAAM,GAAG,CACnC,MAAM3vB,EAAYguB,EAAM2B,EAAO,IAC/BlzB,KAAKmyB,OAAOhpB,MAAM4tB,WAAa,iDAAiDxzB,6BAAoCA,iCACtH,CAEEvD,KAAKoyB,WAAUpyB,KAAKoyB,SAAS/wB,MAAQkiB,OAAOvjB,KAAK8d,MAAM2V,QAAU,IAErEzzB,KAAKm2B,wBACP,CAEQ,GAAAW,CAAIh0B,GACV,IAAKI,OAAOC,SAASL,GAAI,MAAO,OAChC,MAAMwgB,EAAI9f,KAAKwzB,MAAMl0B,EAAI,IACnBmB,EAAIT,KAAKwzB,MAAMl0B,EAAI,IACzB,MAAO,GAAGwgB,KAAKC,OAAOtf,GAAGgzB,SAAS,EAAG,MACvC,CAEQ,sBAAAd,GACN,IAAKn2B,KAAKuG,KAAM,OAChB,MAAM2c,EAAOljB,KAAKuG,KAAK2wB,wBAEvBl3B,KAAKuR,SAAS4lB,qBAAqBjU,EAAKvO,OAC1C,CAEQ,wBAAAmf,CAAyBsD,EAA0B/1B,GACzD,MAAMg2B,EAAwB,KAAb,EAAIh2B,GAGrB+1B,EAAOjuB,MAAM4tB,WAAa,8EAA8EM,eAAqBA,oBAG7HD,EAAOjuB,MAAM+b,YACX,aACA,8EAA8EmS,eAAqBA,oBACnG,YAEJ,CAEQ,eAAArD,SACNh0B,KAAKo0B,wBACL,MAAMkD,EAAQ,OAAAjsB,EAAArL,KAAKuG,WAAL,EAAA8E,EAAWsZ,cAAc,qBACnC2S,IACFA,EAAMnuB,MAAMgC,QAAU,IACtBmsB,EAAMnuB,MAAM4M,UAAY,iCACxBuhB,EAAMnuB,MAAMia,cAAgB,OAEhC,CAEQ,uBAAA8Q,GACNl0B,KAAKo0B,wBACLp0B,KAAKyyB,iBAAmBnQ,OAAOW,WAAW,KACxCjjB,KAAKu3B,mBACJ,IACL,CAEQ,qBAAAnD,GACFp0B,KAAKyyB,mBACPnQ,OAAOF,aAAapiB,KAAKyyB,kBACzBzyB,KAAKyyB,iBAAmB,KAE5B,CAEQ,eAAA8E,SACN,MAAMD,EAAQ,OAAAjsB,EAAArL,KAAKuG,WAAL,EAAA8E,EAAWsZ,cAAc,qBACnC2S,IACFA,EAAMnuB,MAAMgC,QAAU,IACtBmsB,EAAMnuB,MAAM4M,UAAY,oCACxBuhB,EAAMnuB,MAAMia,cAAgB,OAEhC,CAEQ,YAAA8R,GACDl1B,KAAKuG,OACVvG,KAAKuG,KAAK4C,MAAMgC,QAAU,IAC1BnL,KAAKuG,KAAK4C,MAAMia,cAAgB,OAEhCpjB,KAAKmR,UAAUhI,MAAMquB,OAAS,OAChC,CAEQ,YAAArC,GACgB,MAAlBn1B,KAAKuyB,WAAmBjQ,OAAOF,aAAapiB,KAAKuyB,WACrDvyB,KAAKuyB,UAAYjQ,OAAOW,WAAW,IAAMjjB,KAAKy3B,eAAgBz3B,KAAKwyB,OACrE,CAEQ,YAAAiF,GACDz3B,KAAKuG,OACVvG,KAAKuG,KAAK4C,MAAMgC,QAAU,IAC1BnL,KAAKuG,KAAK4C,MAAMia,cAAgB,OAE5B/U,SAASomB,oBAAmBz0B,KAAKmR,UAAUhI,MAAMquB,OAAS,QAChE,8BE9xBK,MAKL,WAAA13B,CAAYqR,EAAwB8K,GAFpCjc,KAAQK,SAA4B,KAIlC,MAAM2Q,EAAS,IAAKwgB,KAAiBvV,GAErCjc,KAAKuR,SAAW,IAAImU,EAAW,CAC7BvU,YACA+U,UAAWlV,EAAOkV,WAAa,IAC/B5I,YAAatM,EAAOsM,cAAe,EACnCvX,IAAKiL,EAAOjL,KAAO,GACnB4D,UAAWqH,EAAOrH,YAAa,IAEjC3J,KAAKkR,aAAe,IAAIoe,CAC1B,CAEA,gBAAMoI,CAAW1mB,GAEfhR,KAAKK,SAAWoM,EAAcuE,GAG1BhR,KAAKK,SAASI,cACVT,KAAKkR,aAAaye,sBAAsB3vB,KAAKK,SAASI,QAI9DT,KAAKuR,SAAS6P,YAAYphB,KAAKK,SACjC,CAEA,qBAAMs3B,CAAgB3mB,GAEpBhR,KAAKK,SAAWoM,EAAcuE,GAG1BhR,KAAKK,SAASI,cACVT,KAAKkR,aAAaye,sBAAsB3vB,KAAKK,SAASI,cAIxDT,KAAKuR,SAASmV,iBAAiB1mB,KAAKK,SAC5C,CAEA,WAAAwd,CAAYC,GAEV9d,KAAKuR,SAASsM,YAAYC,EAC5B,CAEA,IAAAhB,GAGA,CAEA,KAAAE,GAGA,CAEA,IAAAE,CAAK0a,GACH53B,KAAKuR,SAAS4V,OAAOyQ,EACvB,CAEA,gBAAMpR,SAEExmB,KAAKuR,SAASiV,aACpBxmB,KAAKK,SAAW,IAClB,CAEA,OAAAkgB,GACEvgB,KAAKuR,SAASgP,UACdvgB,KAAKkR,aAAaqP,UAClBvgB,KAAKK,SAAW,IAClB,CAEA,kBAAAk0B,CAAmBkC,GAGjBz2B,KAAKuR,SAAoB,UAAEpI,MAAM8d,WAAawP,EAAU,UAAY,QACtE,CAGA,oBAAAU,CAAqBhd,GACnB,MAAM0d,EAAsBr0B,KAAKC,IAAI,EAAGD,KAAKwzB,MAAM7c,GAAM,IAEzDna,KAAKuR,SAAoB,UAAEpI,MAAM+b,YAAY,uBAAwB,GAAG2S,MAC1E,uCAjGK,SAAqC7mB,GAE1CwgB,EAAe,IAAKA,KAAiBxgB,GAGjCA,EAAO8mB,cACTxG,EAAoBtgB,EAAO8mB,aAE/B,8FA+FO,SAAgC1N,GAOrC,MAAM7kB,KAAEA,EAAAE,QAAMA,EAAS+jB,OAAAA,EAAAA,QAAQ/b,EAAAqd,SAASA,GAAaV,EACrDld,EAAYC,SAAS,CACnB5H,OACAE,UACA+jB,OAAAA,EACA/b,UACAqd,SAAUA,GAAY,CAAEvlB,OAAME,UAASsyB,MAAO,cAElD,0CAGAC,eACEC,EACAhoB,GAEA,MAAM1O,EAAUD,OAAOC,QAAQ02B,GAAW,CAAA,GAC1C,IAAA,MAAY5yB,EAAM6yB,KAAW32B,EAC3B,IACE,MAAMoa,EAAyB,mBAAV1L,EAAwBA,EAAM5K,GAAQosB,GAAuBpsB,GAClF,IAAKsW,EAAM,SACX,MAAMwc,QAAYD,IAClBhrB,EAAYC,SAAS,CACnB5H,KAAMoW,EAAKpW,KACXE,QAASkW,EAAKlW,QACd+jB,OAAQ2O,EACR1qB,QAASkO,EAAKlO,QACdqd,SAAU,CAAEvlB,KAAMoW,EAAKpW,KAAME,QAASkW,EAAKlW,QAASsyB,MAAO,cAE/D,CAAA,MAA6C,CAEjD"}