import { TWMixin } from "../TWMixin2.js";
import { currentTimeContext } from "../currentTimeContext.js";
import { durationContext } from "../durationContext.js";
import { loopContext, playingContext } from "../playingContext.js";
import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
import { isEFTemporal } from "../../elements/EFTemporal.js";
import { targetTemporalContext } from "../ContextMixin.js";
import { TargetController } from "../../elements/TargetController.js";
import { selectionContext } from "../../canvas/selection/selectionContext.js";
import { findRootTemporal } from "../../elements/findRootTemporal.js";
import { shouldRenderElement } from "../hierarchy/EFHierarchyItem.js";
import { createDirectTemporalSubscription } from "../Controllable.js";
import { createTimelineEditingContext, timelineEditingContext } from "./timelineEditingContext.js";
import { DEFAULT_PIXELS_PER_MS, pixelsPerMsToZoom, pxToTime, timeToPx, timelineStateContext } from "./timelineStateContext.js";
import "./tracks/preloadTracks.js";
import { flattenHierarchy } from "./flattenHierarchy.js";
import "./EFTimelineRow.js";
import { calculateFrameIntervalMs, calculatePixelsPerFrame, quantizeToFrameTimeMs, shouldShowFrameMarkers } from "../EFTimelineRuler.js";
import { EFTimegroup } from "../../elements/EFTimegroup.js";
import { consume, provide } from "@lit/context";
import { LitElement, css, html, nothing } from "lit";
import { customElement, eventOptions, property, state } from "lit/decorators.js";
import { styleMap } from "lit/directives/style-map.js";
import { createRef, ref } from "lit/directives/ref.js";
import { repeat } from "lit/directives/repeat.js";

//#region src/gui/timeline/EFTimeline.ts
var _EFTimeline;
let EFTimeline = class EFTimeline$1 extends TWMixin(LitElement) {
	static {
		_EFTimeline = this;
	}
	constructor(..._args) {
		super(..._args);
		this.target = "";
		this.pixelsPerMs = DEFAULT_PIXELS_PER_MS;
		this.minZoom = .1;
		this.maxZoom = 10;
		this.enableTrim = false;
		this.showControls = true;
		this.showRuler = true;
		this.showHierarchy = true;
		this.showPlayhead = true;
		this.showPlaybackControls = true;
		this.showZoomControls = true;
		this.showTimeDisplay = true;
		this.controlTarget = "";
		this.hide = "";
		this.show = "";
		this.targetElement = null;
		this.currentTimeMs = 0;
		this.isPlaying = false;
		this.isLooping = false;
		this.viewportScrollLeft = 0;
		this._timelineState = {
			pixelsPerMs: DEFAULT_PIXELS_PER_MS,
			currentTimeMs: 0,
			durationMs: 0,
			viewportScrollLeft: 0,
			viewportWidth: 800,
			seek: () => {},
			zoomIn: () => {},
			zoomOut: () => {}
		};
		this._editingContext = createTimelineEditingContext();
		this.tracksScrollRef = createRef();
		this.containerRef = createRef();
		this.playheadRef = createRef();
		this.playheadHandleRef = createRef();
		this.frameHighlightRef = createRef();
		this.isDraggingPlayhead = false;
		this.cachedViewportWidth = 800;
		this.saveZoomScrollDebounceTimer = null;
		this.lastContextUpdateTime = 0;
		this.lastPlayheadPx = 0;
		this.isFollowingPlayhead = false;
	}
	static {
		this.styles = [css`
      :host {
        display: block;
        width: 100%;
        height: 100%;
        min-height: 100px;
        
        /* Layout coordination via CSS custom properties */
        --timeline-hierarchy-width: var(--ef-hierarchy-width, 200px);
        --timeline-row-height: var(--ef-row-height, 24px);
        --timeline-track-height: var(--ef-track-height, 24px);
        
        /* Component tokens (reference globals from ef-theme.css) */
        --timeline-bg: var(--ef-color-bg);
        --timeline-border: var(--ef-color-border);
        --timeline-header-bg: var(--ef-color-bg-panel);
        --timeline-text: var(--ef-color-text);
        --timeline-ruler-bg: var(--ef-color-bg-panel);
        --timeline-track-bg: var(--ef-color-bg-inset);
        --timeline-track-hover: var(--ef-color-hover);
        --timeline-playhead: var(--ef-color-playhead);
      }
      
      .timeline-container {
        display: flex;
        flex-direction: column;
        width: 100%;
        height: 100%;
        background: var(--timeline-bg);
        color: var(--timeline-text);
        overflow: hidden;
      }
      
      /* === HEADER / CONTROLS === */
      .header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 16px;
        padding: 12px 16px;
        background: var(--timeline-header-bg);
        border-bottom: 1px solid var(--timeline-border);
        flex-shrink: 0;
      }
      
      .controls {
        display: flex;
        align-items: center;
        gap: 12px;
      }
      
      .playback-controls {
        display: flex;
        align-items: center;
        gap: 8px;
        padding-right: 12px;
        border-right: 1px solid var(--timeline-border);
      }
      
      .control-btn {
        min-width: 32px;
        height: 32px;
        padding: 6px 10px;
        background: var(--ef-color-bg-inset);
        border: 1px solid var(--ef-color-border-subtle);
        border-radius: 6px;
        color: inherit;
        font-size: 14px;
        cursor: pointer;
        transition: all 0.2s ease;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      
      .control-btn:hover:not(:disabled) {
        background: var(--ef-color-hover);
        border-color: var(--ef-color-border);
      }
      
      .control-btn:disabled {
        opacity: 0.5;
        cursor: not-allowed;
      }
      
      .control-btn.active {
        background: var(--ef-color-primary-subtle);
        border-color: var(--ef-color-primary);
      }
      
      .time-display {
        font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
        font-size: 13px;
        font-weight: 500;
        padding: 6px 12px;
        background: var(--ef-color-bg-elevated);
        border-radius: 6px;
        letter-spacing: 0.5px;
      }
      
      .zoom-controls {
        display: flex;
        align-items: center;
        gap: 8px;
      }
      
      .zoom-btn {
        width: 32px;
        height: 32px;
        display: flex;
        align-items: center;
        justify-content: center;
        background: var(--ef-color-bg-inset);
        border: 1px solid var(--ef-color-border-subtle);
        border-radius: 6px;
        color: inherit;
        font-size: 18px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s ease;
      }
      
      .zoom-btn:hover {
        background: var(--ef-color-hover);
        border-color: var(--ef-color-border);
        transform: scale(1.05);
      }
      
      .zoom-label {
        font-size: 12px;
        min-width: 48px;
        text-align: center;
        font-weight: 500;
      }
      
      /* === TIMELINE LAYOUT === */
      .timeline-area {
        flex: 1;
        display: flex;
        flex-direction: column;
        position: relative;
        overflow: hidden;
      }
      
      .ruler-row {
        display: flex;
        height: 24px;
        background: var(--timeline-ruler-bg);
        border-bottom: 1px solid var(--timeline-border);
        flex-shrink: 0;
        /* Sticky positioning for native scroll sync */
        position: sticky;
        top: 0;
        z-index: 10;
      }
      
      .ruler-spacer {
        width: var(--timeline-hierarchy-width);
        flex-shrink: 0;
        background: var(--timeline-header-bg);
        border-right: 1px solid var(--timeline-border);
        /* Sticky positioning to stay fixed during horizontal scroll */
        position: sticky;
        left: 0;
        z-index: 11;
      }
      
      .ruler-content {
        flex: 1;
        position: relative;
        overflow: hidden;
        cursor: ew-resize;
      }
      
      .ruler-content ef-timeline-ruler {
        width: 100%;
        height: 100%;
      }
      
      .ruler-playhead-handle {
        position: absolute;
        bottom: -6px;
        transform: translateX(-50%);
        width: 12px;
        height: 12px;
        background: var(--timeline-playhead);
        border-radius: 50%;
        pointer-events: auto;
        cursor: ew-resize;
        z-index: 101;
      }
      
      /* Thumbnail strip row */
      .thumbnail-row {
        display: flex;
        height: 48px;
        background: var(--timeline-bg);
        border-bottom: 1px solid var(--timeline-border);
        flex-shrink: 0;
      }
      
      .thumbnail-spacer {
        width: var(--timeline-hierarchy-width);
        flex-shrink: 0;
        background: var(--timeline-header-bg);
        border-right: 1px solid var(--timeline-border);
      }
      
      .thumbnail-content {
        flex: 1;
        position: relative;
        overflow: hidden;
      }
      
      .thumbnail-strip .thumbnail {
        flex-shrink: 0;
        border-radius: 2px;
        overflow: hidden;
        background: var(--timeline-track-bg);
      }
      
      .tracks-viewport {
        flex: 1;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        position: relative;
      }
      
      .tracks-scroll {
        flex: 1;
        overflow: auto;
        position: relative;
        background: var(--timeline-track-bg);
      }
      
      /* === TRACKS CONTENT uses grid to layer playhead over tracks === */
      .tracks-content {
        position: relative;
        min-height: 100%;
        display: grid;
        grid-template-columns: 1fr;
        grid-template-rows: 1fr;
      }
      
      .tracks-content > * {
        grid-area: 1 / 1;
      }
      
      .tracks-rows-layer {
        display: flex;
        flex-direction: column;
      }
      
      /* === PLAYHEAD (sticky layer that stays visible during vertical scroll) === */
      .playhead-layer {
        position: sticky;
        top: 0;
        height: 100%;
        pointer-events: none;
        /* Below sticky labels (z-index 10-11) but above tracks */
        z-index: 5;
        overflow: hidden;
      }
      
      .playhead {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 2px;
        background: var(--timeline-playhead);
        pointer-events: none;
      }
      
      .playhead-drag-target {
        position: absolute;
        top: 0;
        bottom: 0;
        left: 50%;
        transform: translateX(-50%);
        width: 16px;
        cursor: ew-resize;
        pointer-events: auto;
      }
      
      /* === FRAME HIGHLIGHT === */
      .frame-highlight {
        position: absolute;
        top: 0;
        bottom: 0;
        background: var(--ef-color-primary-subtle);
        border-left: 2px solid var(--ef-color-primary);
        pointer-events: none;
      }
      
      .empty-state {
        display: flex;
        align-items: center;
        justify-content: center;
        height: 100%;
        color: var(--ef-color-text-subtle);
        font-style: italic;
      }
    `];
	}
	get hideSelectors() {
		if (!this.hide) return void 0;
		return this.hide.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
	}
	get showSelectors() {
		if (!this.show) return void 0;
		return this.show.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
	}
	static {
		this.CONTEXT_UPDATE_INTERVAL_MS = 100;
	}
	#playbackSubscription = null;
	#wheelHandler = null;
	get providedPlaying() {
		return this.isPlaying;
	}
	get providedLoop() {
		return this.isLooping;
	}
	get providedCurrentTime() {
		return this.currentTimeMs;
	}
	get providedDuration() {
		return this.targetTemporal?.durationMs ?? 0;
	}
	get providedTargetTemporal() {
		return this.targetTemporal;
	}
	/** Get timeline state (for external access) */
	get timelineState() {
		return this._timelineState;
	}
	/** Update timeline state when any constituent value changes */
	updateTimelineState() {
		const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
		const viewportWidth = this.cachedViewportWidth - hierarchyWidth;
		const newState = {
			pixelsPerMs: this.pixelsPerMs,
			currentTimeMs: this.currentTimeMs,
			durationMs: this.durationMs,
			viewportScrollLeft: this.viewportScrollLeft,
			viewportWidth,
			seek: (ms) => this.handleSeek(ms),
			zoomIn: () => this.handleZoomIn(),
			zoomOut: () => this.handleZoomOut()
		};
		const pixelsPerMsChanged = this._timelineState.pixelsPerMs !== newState.pixelsPerMs;
		const currentTimeMsChanged = this._timelineState.currentTimeMs !== newState.currentTimeMs;
		const durationMsChanged = this._timelineState.durationMs !== newState.durationMs;
		const viewportScrollLeftChanged = this._timelineState.viewportScrollLeft !== newState.viewportScrollLeft;
		const viewportWidthChanged = this._timelineState.viewportWidth !== newState.viewportWidth;
		const shouldSkipScrollUpdate = viewportScrollLeftChanged && !pixelsPerMsChanged && !currentTimeMsChanged && !durationMsChanged && !viewportWidthChanged && this.isPlaying;
		if (pixelsPerMsChanged || currentTimeMsChanged || durationMsChanged || viewportWidthChanged || viewportScrollLeftChanged && !shouldSkipScrollUpdate) {
			this._timelineState = newState;
			this.requestUpdate();
		}
	}
	/**
	* Get the target canvas element.
	* The canvas is the source of truth for selection and highlight state.
	*/
	getCanvas() {
		if (this.targetElement && this.targetElement.selectionContext) return this.targetElement;
		if (this.target) {
			const target = document.getElementById(this.target);
			if (target && target.selectionContext) return target;
		}
		return document.querySelector("ef-canvas");
	}
	getCanvasSelectionContext() {
		if (this.selectionContext) return this.selectionContext;
		return this.getCanvas()?.selectionContext;
	}
	/**
	* Get the currently highlighted element from the canvas.
	*/
	getHighlightedElement() {
		return this.getCanvas()?.highlightedElement ?? null;
	}
	/**
	* Set the highlighted element on the canvas.
	* Called when user hovers a row in the timeline.
	*/
	setHighlightedElement(element) {
		this.getCanvas()?.setHighlightedElement(element);
	}
	get targetTemporal() {
		if (this.controlTarget && this.controlTarget !== "" && this.controlTarget !== "selection") {
			const element = document.getElementById(this.controlTarget);
			if (element && isEFTemporal(element)) return element;
		}
		const selectionCtx = this.getCanvasSelectionContext();
		if (selectionCtx) {
			const selectedIds = Array.from(selectionCtx.selectedIds);
			if (selectedIds.length > 0 && selectedIds[0]) {
				const element = document.getElementById(selectedIds[0]);
				if (element) {
					const rootTemporal = findRootTemporal(element);
					if (rootTemporal) return rootTemporal;
				}
			}
		}
		return null;
	}
	get durationMs() {
		return this.targetTemporal?.durationMs ?? 0;
	}
	/** Content width in pixels (derived from duration and pixelsPerMs) */
	get contentWidthPx() {
		return timeToPx(this.durationMs, this.pixelsPerMs);
	}
	/** Current zoom as percentage (for display) */
	get zoomPercent() {
		return Math.round(pixelsPerMsToZoom(this.pixelsPerMs) * 100);
	}
	/** Derive fps from target temporal (defaults to 30) */
	get fps() {
		const target = this.targetTemporal;
		if (target && "fps" in target) return target.fps ?? 30;
		return 30;
	}
	/** Whether frame markers should be visible at current zoom */
	get showFrameMarkers() {
		return shouldShowFrameMarkers(calculatePixelsPerFrame(calculateFrameIntervalMs(this.fps), this.pixelsPerMs));
	}
	/**
	* Get the root timegroup ID for localStorage key generation.
	* Returns null if no root timegroup is found or it has no ID.
	*/
	getRootTimegroupId() {
		if (!this.targetTemporal) return null;
		const rootTemporal = findRootTemporal(this.targetTemporal);
		if (rootTemporal instanceof EFTimegroup && rootTemporal.id) return rootTemporal.id;
		return null;
	}
	/**
	* Get localStorage key for timeline state (zoom and scroll).
	*/
	getTimelineStorageKey() {
		const rootId = this.getRootTimegroupId();
		return rootId ? `ef-timeline-${rootId}` : null;
	}
	/**
	* Save timeline zoom and scroll to localStorage.
	*/
	saveTimelineState() {
		const storageKey = this.getTimelineStorageKey();
		if (!storageKey) return;
		try {
			const state$1 = {
				pixelsPerMs: this.pixelsPerMs,
				viewportScrollLeft: this.viewportScrollLeft
			};
			localStorage.setItem(storageKey, JSON.stringify(state$1));
		} catch (error) {
			console.warn("Failed to save timeline state to localStorage", error);
		}
	}
	/**
	* Restore timeline zoom and scroll from localStorage.
	*/
	restoreTimelineState() {
		const storageKey = this.getTimelineStorageKey();
		if (!storageKey) return;
		try {
			const stored = localStorage.getItem(storageKey);
			if (!stored) return;
			const state$1 = JSON.parse(stored);
			if (typeof state$1.pixelsPerMs === "number" && state$1.pixelsPerMs > 0) this.pixelsPerMs = Math.max(this.minZoom * DEFAULT_PIXELS_PER_MS, Math.min(this.maxZoom * DEFAULT_PIXELS_PER_MS, state$1.pixelsPerMs));
			if (typeof state$1.viewportScrollLeft === "number" && state$1.viewportScrollLeft >= 0) requestAnimationFrame(() => {
				const tracksScroll = this.tracksScrollRef.value;
				if (tracksScroll) {
					tracksScroll.scrollLeft = state$1.viewportScrollLeft;
					this.viewportScrollLeft = state$1.viewportScrollLeft;
				}
			});
		} catch (error) {
			console.warn("Failed to restore timeline state from localStorage", error);
		}
	}
	/**
	* Debounced save of timeline state to avoid excessive localStorage writes.
	*/
	debouncedSaveTimelineState() {
		if (this.saveZoomScrollDebounceTimer !== null) clearTimeout(this.saveZoomScrollDebounceTimer);
		this.saveZoomScrollDebounceTimer = window.setTimeout(() => {
			this.saveZoomScrollDebounceTimer = null;
			this.saveTimelineState();
		}, 200);
	}
	connectedCallback() {
		super.connectedCallback();
		this.startTimeUpdate();
		this.setupSelectionListener();
		this.setupKeyboardListener();
		this.updateTimelineState();
		this.subscribeToPlaybackController();
	}
	disconnectedCallback() {
		super.disconnectedCallback();
		this.stopTimeUpdate();
		this.removeSelectionListener();
		this.removeScrollListener();
		this.removeKeyboardListener();
		this.targetObserver?.disconnect();
		this.resizeObserver?.disconnect();
		this.unsubscribeFromPlaybackController();
		if (this.saveZoomScrollDebounceTimer !== null) {
			clearTimeout(this.saveZoomScrollDebounceTimer);
			this.saveZoomScrollDebounceTimer = null;
		}
		this.saveTimelineState();
	}
	/**
	* Setup MutationObserver to watch target element for ANY changes.
	* Re-registers when target changes.
	*/
	setupTargetObserver() {
		this.targetObserver?.disconnect();
		const target = this.targetTemporal;
		if (target && target instanceof Element) {
			this.targetObserver = new MutationObserver(() => this.requestUpdate());
			this.targetObserver.observe(target, {
				childList: true,
				subtree: true,
				attributes: true
			});
		}
	}
	willUpdate(changedProperties) {
		if (changedProperties.has("target") && this.target && !this.targetController) this.targetController = new TargetController(this);
		this.setupSelectionListener();
		this.updateTimelineState();
		super.willUpdate(changedProperties);
	}
	firstUpdated() {
		this.setupResizeObserver();
	}
	setupResizeObserver() {
		const tracksScroll = this.tracksScrollRef.value;
		if (!tracksScroll) return;
		if (this.resizeObserver) this.resizeObserver.disconnect();
		const initialWidth = tracksScroll.clientWidth;
		if (initialWidth > 0) this.cachedViewportWidth = initialWidth;
		this.resizeObserver = new ResizeObserver((entries) => {
			for (const entry of entries) {
				const width = entry.contentRect.width;
				if (width > 0) {
					this.cachedViewportWidth = width;
					this.updateTimelineState();
					this.requestUpdate();
				}
			}
		});
		this.resizeObserver.observe(tracksScroll);
	}
	updated(changedProperties) {
		super.updated(changedProperties);
		if (changedProperties.has("showHierarchy")) if (this.showHierarchy) {
			this.style.removeProperty("--timeline-hierarchy-width");
			this.removeAttribute("hide-hierarchy");
		} else {
			this.style.setProperty("--timeline-hierarchy-width", "0px");
			this.setAttribute("hide-hierarchy", "");
		}
		if (changedProperties.has("targetTemporal") || changedProperties.has("controlTarget")) this.subscribeToPlaybackController();
		if (changedProperties.has("targetTemporal") || changedProperties.has("target")) requestAnimationFrame(() => {
			this.restoreTimelineState();
		});
		if (changedProperties.has("pixelsPerMs") || changedProperties.has("viewportScrollLeft")) this.debouncedSaveTimelineState();
		if (changedProperties.has("targetElement") || changedProperties.has("target")) {
			this.setupTargetObserver();
			this.removeSelectionListener();
			this.selectionChangeHandler = void 0;
			this.setupSelectionListener();
		}
		if (this.tracksScrollRef.value && !this.scrollHandler) this.setupScrollListener();
		else if (!this.tracksScrollRef.value && this.scrollHandler) this.removeScrollListener();
		if (this.tracksScrollRef.value && !this.resizeObserver) {
			this.setupResizeObserver();
			this.updateTimelineState();
		} else if (!this.tracksScrollRef.value && this.resizeObserver) {
			this.resizeObserver.disconnect();
			this.resizeObserver = void 0;
		}
	}
	setupSelectionListener() {
		if (this.selectionChangeHandler) return;
		const selectionCtx = this.getCanvasSelectionContext();
		if (selectionCtx && "addEventListener" in selectionCtx) {
			this.selectionChangeHandler = () => this.requestUpdate();
			selectionCtx.addEventListener("selectionchange", this.selectionChangeHandler);
			this.requestUpdate();
		}
		const canvas = this.getCanvas();
		if (canvas && !this.canvasActiveRootTemporalChangeHandler) {
			this.canvasActiveRootTemporalChangeHandler = () => {
				this.requestUpdate();
			};
			canvas.addEventListener("activeroottemporalchange", this.canvasActiveRootTemporalChangeHandler);
		}
	}
	removeSelectionListener() {
		const selectionCtx = this.getCanvasSelectionContext();
		if (selectionCtx && "removeEventListener" in selectionCtx && this.selectionChangeHandler) {
			selectionCtx.removeEventListener("selectionchange", this.selectionChangeHandler);
			this.selectionChangeHandler = void 0;
		}
		const canvas = document.querySelector("ef-canvas");
		if (canvas && this.canvasActiveRootTemporalChangeHandler) {
			canvas.removeEventListener("activeroottemporalchange", this.canvasActiveRootTemporalChangeHandler);
			this.canvasActiveRootTemporalChangeHandler = void 0;
		}
	}
	setupScrollListener() {
		if (this.tracksScrollRef.value) {
			this.scrollHandler = () => {
				if (this.tracksScrollRef.value) {
					const newScrollLeft = this.tracksScrollRef.value.scrollLeft;
					if (newScrollLeft !== this.viewportScrollLeft) {
						this.viewportScrollLeft = newScrollLeft;
						this.updateTimelineState();
						this.debouncedSaveTimelineState();
					}
				}
			};
			this.tracksScrollRef.value.addEventListener("scroll", this.scrollHandler, { passive: true });
			this.scrollHandler();
			this.#wheelHandler = (e) => this.handleWheel(e);
			this.tracksScrollRef.value.addEventListener("wheel", this.#wheelHandler, { passive: false });
		}
	}
	removeScrollListener() {
		if (this.tracksScrollRef.value) {
			if (this.scrollHandler) this.tracksScrollRef.value.removeEventListener("scroll", this.scrollHandler);
			if (this.#wheelHandler) {
				this.tracksScrollRef.value.removeEventListener("wheel", this.#wheelHandler);
				this.#wheelHandler = null;
			}
		}
	}
	setupKeyboardListener() {
		this.keydownHandler = (e) => this.handleKeyDown(e);
		this.addEventListener("keydown", this.keydownHandler);
	}
	removeKeyboardListener() {
		if (this.keydownHandler) this.removeEventListener("keydown", this.keydownHandler);
	}
	static {
		this.PLAYHEAD_MARGIN = 100;
	}
	startTimeUpdate() {
		const update = () => {
			if (this.targetTemporal) {
				if (!this.targetTemporal.captureInProgress) {
					const rawTime = this.targetTemporal.currentTimeMs ?? 0;
					const duration = this.targetTemporal.durationMs ?? 0;
					const newTimeMs = Math.max(0, Math.min(rawTime, duration));
					this.updatePlayheadPositionDirect(newTimeMs);
					const newIsPlaying = this.targetTemporal.playing ?? false;
					const newIsLooping = this.targetTemporal.loop ?? false;
					if (newIsPlaying !== this.isPlaying || newIsLooping !== this.isLooping) {
						if (!this.#playbackSubscription) {
							const wasPlaying = this.isPlaying;
							this.isPlaying = newIsPlaying;
							this.isLooping = newIsLooping;
							if (wasPlaying && !newIsPlaying) this.updateTimelineState();
						}
					}
					const now = performance.now();
					if ((!newIsPlaying || now - this.lastContextUpdateTime >= _EFTimeline.CONTEXT_UPDATE_INTERVAL_MS) && this.currentTimeMs !== newTimeMs) {
						this.currentTimeMs = newTimeMs;
						this.lastContextUpdateTime = now;
					}
					if (this.isPlaying && !this.isDraggingPlayhead) this.followPlayhead(newTimeMs);
					else this.isFollowingPlayhead = false;
				}
			} else if (this.isPlaying) {
				console.warn("[EFTimeline] Lost targetTemporal during playback. Stopping playback state.", "controlTarget:", this.controlTarget, "target:", this.target);
				this.isPlaying = false;
				this.isLooping = false;
				this.updateTimelineState();
			}
			this.animationFrameId = requestAnimationFrame(update);
		};
		update();
	}
	/**
	* Update playhead position directly via DOM manipulation.
	* This bypasses the Lit render cycle for smooth 60fps playhead movement.
	*/
	updatePlayheadPositionDirect(timeMs) {
		const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
		const playheadPx = timeToPx(timeMs, this.pixelsPerMs);
		const playheadLeft = hierarchyWidth + playheadPx;
		const playhead = this.playheadRef.value;
		if (playhead) playhead.style.left = `${playheadLeft - 1}px`;
		const handle = this.playheadHandleRef.value;
		if (handle) handle.style.left = `${playheadPx}px`;
		if (this.showFrameMarkers && this.durationMs > 0) {
			const frameHighlight = this.frameHighlightRef.value;
			if (frameHighlight) {
				const fps = this.fps;
				const frameDurationMs = 1e3 / fps;
				const frameStartMs = quantizeToFrameTimeMs(timeMs, fps);
				const frameEndMs = Math.min(frameStartMs + frameDurationMs, this.durationMs);
				const startPx = timeToPx(frameStartMs, this.pixelsPerMs);
				const widthPx = timeToPx(frameEndMs, this.pixelsPerMs) - startPx;
				if (widthPx > 0 && startPx >= 0) {
					frameHighlight.style.left = `${hierarchyWidth + startPx}px`;
					frameHighlight.style.width = `${widthPx}px`;
					frameHighlight.style.display = "block";
				} else frameHighlight.style.display = "none";
			}
		}
	}
	/**
	* Smooth playhead following - scrolls to keep playhead at a fixed screen position.
	* This eliminates jitter by scrolling exactly as much as the playhead moves.
	*
	* PERFORMANCE NOTE: We DO update viewportScrollLeft state here, but the context
	* cascade is prevented in updateTimelineState() during playback. This means:
	* - State stays in sync (for non-context consumers)
	* - But context consumers don't re-render during auto-scroll
	* - Components inside the scroll container scroll natively
	*/
	followPlayhead(currentTimeMs) {
		const tracksScroll = this.tracksScrollRef.value;
		if (!tracksScroll) return;
		const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
		const playheadPx = timeToPx(currentTimeMs, this.pixelsPerMs);
		const viewportWidth = tracksScroll.clientWidth - hierarchyWidth;
		const maxScroll = tracksScroll.scrollWidth - tracksScroll.clientWidth;
		const playheadInViewport = playheadPx - tracksScroll.scrollLeft;
		const rightThreshold = viewportWidth - _EFTimeline.PLAYHEAD_MARGIN;
		const leftThreshold = _EFTimeline.PLAYHEAD_MARGIN;
		let newScrollLeft = tracksScroll.scrollLeft;
		let scrollChanged = false;
		if (this.isFollowingPlayhead) {
			const delta = playheadPx - this.lastPlayheadPx;
			if (delta !== 0) {
				newScrollLeft = Math.max(0, Math.min(maxScroll, tracksScroll.scrollLeft + delta));
				tracksScroll.scrollLeft = newScrollLeft;
				scrollChanged = true;
			}
		} else if (playheadInViewport > rightThreshold) this.isFollowingPlayhead = true;
		else if (playheadInViewport < leftThreshold && tracksScroll.scrollLeft > 0) {
			newScrollLeft = Math.max(0, playheadPx - leftThreshold);
			tracksScroll.scrollLeft = newScrollLeft;
			scrollChanged = true;
		}
		if (scrollChanged && this.viewportScrollLeft !== newScrollLeft) this.viewportScrollLeft = newScrollLeft;
		this.lastPlayheadPx = playheadPx;
	}
	stopTimeUpdate() {
		if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId);
	}
	/**
	* Subscribe to playback controller events for playing/loop state.
	* This avoids race conditions from polling when targetTemporal changes.
	*/
	subscribeToPlaybackController() {
		this.unsubscribeFromPlaybackController();
		const temporal = this.targetTemporal;
		if (!temporal) return;
		if (!temporal.playbackController) {
			temporal.updateComplete?.then(() => {
				if (temporal === this.targetTemporal && temporal.playbackController) this.#playbackSubscription = createDirectTemporalSubscription(temporal, {
					onPlayingChange: (value) => {
						if (temporal === this.targetTemporal) {
							const wasPlaying = this.isPlaying;
							this.isPlaying = value;
							if (wasPlaying && !value) this.updateTimelineState();
						}
					},
					onLoopChange: (value) => {
						if (temporal === this.targetTemporal) this.isLooping = value;
					},
					onCurrentTimeMsChange: () => {},
					onDurationMsChange: () => {},
					onTargetTemporalChange: () => {}
				});
			});
			return;
		}
		this.#playbackSubscription = createDirectTemporalSubscription(temporal, {
			onPlayingChange: (value) => {
				if (temporal === this.targetTemporal) {
					const wasPlaying = this.isPlaying;
					this.isPlaying = value;
					if (wasPlaying && !value) this.updateTimelineState();
				}
			},
			onLoopChange: (value) => {
				if (temporal === this.targetTemporal) this.isLooping = value;
			},
			onCurrentTimeMsChange: () => {},
			onDurationMsChange: () => {},
			onTargetTemporalChange: () => {}
		});
	}
	/**
	* Unsubscribe from playback controller events.
	*/
	unsubscribeFromPlaybackController() {
		if (this.#playbackSubscription) {
			this.#playbackSubscription.unsubscribe();
			this.#playbackSubscription = null;
		}
	}
	handlePlay() {
		if (!this.targetTemporal) {
			console.warn("[EFTimeline] Cannot play: targetTemporal is null. controlTarget:", this.controlTarget, "target:", this.target);
			return;
		}
		this.targetTemporal.play();
	}
	handlePause() {
		if (!this.targetTemporal) {
			console.warn("[EFTimeline] Cannot pause: targetTemporal is null. controlTarget:", this.controlTarget, "target:", this.target);
			return;
		}
		this.targetTemporal.pause();
	}
	handleToggleLoop() {
		if (!this.targetTemporal) {
			console.warn("[EFTimeline] Cannot toggle loop: targetTemporal is null. controlTarget:", this.controlTarget, "target:", this.target);
			return;
		}
		this.targetTemporal.loop = !this.targetTemporal.loop;
		this.isLooping = this.targetTemporal.loop;
	}
	handleZoomIn() {
		const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);
		this.pixelsPerMs = Math.min(this.maxZoom, currentZoom * 1.25) * DEFAULT_PIXELS_PER_MS;
	}
	handleZoomOut() {
		const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);
		this.pixelsPerMs = Math.max(this.minZoom, currentZoom / 1.25) * DEFAULT_PIXELS_PER_MS;
	}
	/**
	* Handle wheel events for gestural zoom.
	* Cmd/Ctrl + wheel zooms toward the cursor position.
	* Without modifier, native scroll behavior is preserved.
	*/
	handleWheel(e) {
		if (!(e.metaKey || e.ctrlKey)) return;
		e.preventDefault();
		e.stopPropagation();
		const tracksScroll = this.tracksScrollRef.value;
		if (!tracksScroll) return;
		const rect = tracksScroll.getBoundingClientRect();
		const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
		const cursorXInViewport = e.clientX - rect.left - hierarchyWidth;
		if (cursorXInViewport < 0) return;
		const timeAtCursor = pxToTime(cursorXInViewport + tracksScroll.scrollLeft, this.pixelsPerMs);
		const zoomFactor = e.deltaY > 0 ? .95 : 1.05;
		const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);
		const newPixelsPerMs = Math.max(this.minZoom, Math.min(this.maxZoom, currentZoom * zoomFactor)) * DEFAULT_PIXELS_PER_MS;
		const newScrollLeft = timeToPx(timeAtCursor, newPixelsPerMs) - cursorXInViewport;
		this.pixelsPerMs = newPixelsPerMs;
		const maxScroll = Math.max(0, timeToPx(this.durationMs, newPixelsPerMs) - (rect.width - hierarchyWidth));
		tracksScroll.scrollLeft = Math.max(0, Math.min(maxScroll, newScrollLeft));
		this.viewportScrollLeft = tracksScroll.scrollLeft;
		this.updateTimelineState();
	}
	/**
	* Seek to a specific time, optionally quantizing to frame boundaries.
	* @param timeMs The raw time to seek to
	* @param snapToFrame Whether to quantize to the nearest frame boundary (default: true when frame markers visible)
	*/
	handleSeek(timeMs, snapToFrame = this.showFrameMarkers) {
		if (!this.targetTemporal) {
			console.warn("[EFTimeline] Cannot seek: targetTemporal is null. controlTarget:", this.controlTarget, "target:", this.target);
			return;
		}
		let seekTime = timeMs;
		if (snapToFrame && this.fps > 0) seekTime = quantizeToFrameTimeMs(seekTime, this.fps);
		const clampedTime = Math.max(0, Math.min(seekTime, this.durationMs));
		this.targetTemporal.currentTimeMs = clampedTime;
		this.currentTimeMs = clampedTime;
	}
	/**
	* Handle keyboard navigation for frame-by-frame or second-by-second movement.
	* - Arrow Left/Right: move by one frame
	* - Shift+Arrow Left/Right: move by one second
	*/
	handleKeyDown(e) {
		if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
		const activeElement = document.activeElement;
		if (activeElement?.tagName === "INPUT" || activeElement?.tagName === "TEXTAREA" || activeElement?.isContentEditable) return;
		e.preventDefault();
		const isShiftPressed = e.shiftKey;
		const isRightArrow = e.key === "ArrowRight";
		const fps = this.fps;
		const durationMs = this.durationMs;
		let newTime;
		if (isShiftPressed) {
			const deltaMs = isRightArrow ? 1e3 : -1e3;
			newTime = this.currentTimeMs + deltaMs;
		} else {
			const frameIntervalMs = fps > 0 ? 1e3 / fps : 1e3 / 30;
			const deltaMs = isRightArrow ? frameIntervalMs : -frameIntervalMs;
			newTime = this.currentTimeMs + deltaMs;
			newTime = quantizeToFrameTimeMs(newTime, fps);
		}
		newTime = Math.max(0, Math.min(newTime, durationMs));
		this.handleSeek(newTime);
	}
	handleRulerPointerDown(e) {
		const rect = e.currentTarget.getBoundingClientRect();
		const timeMs = pxToTime(e.clientX - rect.left, this.pixelsPerMs);
		this.handleSeek(timeMs);
		this.startPlayheadDrag(e);
		e.preventDefault();
	}
	handlePlayheadPointerDown(e) {
		e.stopPropagation();
		this.startPlayheadDrag(e);
	}
	static {
		this.HIERARCHY_WIDTH = 200;
	}
	handleTracksPointerDown(e) {
		if (e.target === e.currentTarget || e.target.classList.contains("tracks-content")) {
			const target = e.currentTarget;
			const rect = target.getBoundingClientRect();
			const scrollLeft = target.scrollLeft;
			const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
			const x = e.clientX - rect.left + scrollLeft - hierarchyWidth;
			if (x >= 0) {
				const timeMs = pxToTime(x, this.pixelsPerMs);
				this.handleSeek(timeMs);
				this.startPlayheadDrag(e);
			}
			e.preventDefault();
		}
	}
	static {
		this.EDGE_SCROLL_ZONE = 50;
	}
	static {
		this.EDGE_SCROLL_SPEED = 8;
	}
	startPlayheadDrag(e) {
		this.isDraggingPlayhead = true;
		this._editingContext.setState({
			mode: "scrubbing",
			startTimeMs: this.currentTimeMs
		});
		const tracksScroll = this.tracksScrollRef.value;
		if (tracksScroll) this.viewportScrollLeft = tracksScroll.scrollLeft;
		const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
		let lastClientX = e.clientX;
		let edgeScrollAnimationId = null;
		const updatePlayheadFromMouse = () => {
			const tracksScroll$1 = this.tracksScrollRef.value;
			if (!tracksScroll$1) return;
			const rect = tracksScroll$1.getBoundingClientRect();
			const scrollLeft = tracksScroll$1.scrollLeft;
			const x = lastClientX - rect.left + scrollLeft - hierarchyWidth;
			const timeMs = pxToTime(Math.max(0, x), this.pixelsPerMs);
			this.handleSeek(Math.min(timeMs, this.durationMs));
		};
		const edgeScrollLoop = () => {
			if (!this.isDraggingPlayhead) return;
			const tracksScroll$1 = this.tracksScrollRef.value;
			if (!tracksScroll$1) return;
			const rect = tracksScroll$1.getBoundingClientRect();
			const trackAreaLeft = rect.left + hierarchyWidth;
			const trackAreaRight = rect.right;
			trackAreaRight - trackAreaLeft;
			const distanceFromLeft = lastClientX - trackAreaLeft;
			const distanceFromRight = trackAreaRight - lastClientX;
			let scrollDelta = 0;
			if (distanceFromLeft < _EFTimeline.EDGE_SCROLL_ZONE && distanceFromLeft >= 0) {
				const intensity = 1 - distanceFromLeft / _EFTimeline.EDGE_SCROLL_ZONE;
				scrollDelta = -_EFTimeline.EDGE_SCROLL_SPEED * intensity;
			} else if (distanceFromRight < _EFTimeline.EDGE_SCROLL_ZONE && distanceFromRight >= 0) {
				const intensity = 1 - distanceFromRight / _EFTimeline.EDGE_SCROLL_ZONE;
				scrollDelta = _EFTimeline.EDGE_SCROLL_SPEED * intensity;
			} else if (lastClientX < trackAreaLeft) scrollDelta = -_EFTimeline.EDGE_SCROLL_SPEED;
			else if (lastClientX > trackAreaRight) scrollDelta = _EFTimeline.EDGE_SCROLL_SPEED;
			if (scrollDelta !== 0) {
				const maxScroll = tracksScroll$1.scrollWidth - tracksScroll$1.clientWidth;
				const newScrollLeft = Math.max(0, Math.min(maxScroll, tracksScroll$1.scrollLeft + scrollDelta));
				tracksScroll$1.scrollLeft = newScrollLeft;
				this.viewportScrollLeft = newScrollLeft;
				updatePlayheadFromMouse();
			}
			edgeScrollAnimationId = requestAnimationFrame(edgeScrollLoop);
		};
		const onMove = (moveEvent) => {
			if (!this.isDraggingPlayhead) return;
			lastClientX = moveEvent.clientX;
			updatePlayheadFromMouse();
		};
		const onUp = () => {
			this.isDraggingPlayhead = false;
			this._editingContext.setState({ mode: "idle" });
			if (edgeScrollAnimationId) cancelAnimationFrame(edgeScrollAnimationId);
			window.removeEventListener("pointermove", onMove);
			window.removeEventListener("pointerup", onUp);
		};
		window.addEventListener("pointermove", onMove);
		window.addEventListener("pointerup", onUp);
		edgeScrollAnimationId = requestAnimationFrame(edgeScrollLoop);
	}
	formatTime(ms) {
		const seconds = Math.floor(ms / 1e3);
		const minutes = Math.floor(seconds / 60);
		const remainingSeconds = seconds % 60;
		const frames = Math.floor(ms % 1e3 / (1e3 / 30));
		return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}:${frames.toString().padStart(2, "0")}`;
	}
	renderPlaybackControls() {
		if (!this.showPlaybackControls) return nothing;
		return html`
      <div class="playback-controls">
        ${this.isPlaying ? html`<button class="control-btn" @click=${this.handlePause} title="Pause">
                <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
                  <rect x="4" y="3" width="3" height="10" rx="0.5"/>
                  <rect x="9" y="3" width="3" height="10" rx="0.5"/>
                </svg>
              </button>` : html`<button class="control-btn" @click=${this.handlePlay} title="Play">
                <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
                  <path d="M5 3.5v9l7-4.5z"/>
                </svg>
              </button>`}
        <button class="control-btn ${this.isLooping ? "active" : ""}" @click=${this.handleToggleLoop} title="Loop">
          <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
            <path d="M13.5 7.5H4.707l2.147-2.146a.5.5 0 0 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L4.707 8.5H13.5a1.5 1.5 0 0 1 0 3h-2a.5.5 0 0 0 0 1h2a2.5 2.5 0 0 0 0-5z"/>
            <path d="M2.5 8.5a1.5 1.5 0 0 1 1.5-1.5h2a.5.5 0 0 0 0-1H4a2.5 2.5 0 0 0 0 5h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L12.793 9.5H4a1.5 1.5 0 0 1-1.5-1.5z"/>
          </svg>
        </button>
      </div>
    `;
	}
	renderTimeDisplay() {
		if (!this.showTimeDisplay) return nothing;
		return html`<span class="time-display">${this.formatTime(this.currentTimeMs)} / ${this.formatTime(this.durationMs)}</span>`;
	}
	renderZoomControls() {
		if (!this.showZoomControls) return nothing;
		return html`
      <div class="zoom-controls">
        <button class="zoom-btn" @click=${this.handleZoomOut} title="Zoom out">−</button>
        <span class="zoom-label">${this.zoomPercent}%</span>
        <button class="zoom-btn" @click=${this.handleZoomIn} title="Zoom in">+</button>
      </div>
    `;
	}
	/**
	* Calculate frame highlight bounds (semantics).
	* Returns null if frame markers aren't visible or duration is invalid.
	*/
	calculateFrameHighlightBounds() {
		if (!this.showFrameMarkers || this.durationMs <= 0) return null;
		const fps = this.fps;
		const frameDurationMs = 1e3 / fps;
		const frameStartMs = quantizeToFrameTimeMs(this.currentTimeMs, fps);
		const frameEndMs = Math.min(frameStartMs + frameDurationMs, this.durationMs);
		const startPx = timeToPx(frameStartMs, this.pixelsPerMs);
		const widthPx = timeToPx(frameEndMs, this.pixelsPerMs) - startPx;
		if (widthPx <= 0 || startPx < 0) return null;
		return {
			startPx,
			widthPx
		};
	}
	/**
	* Render frame highlight (mechanism).
	* Shows the current frame as a rectangle to indicate frames have duration.
	* Only rendered when frame markers are visible (zoom level high enough).
	*/
	renderFrameHighlight() {
		if (!this.showFrameMarkers) return nothing;
		const bounds = this.calculateFrameHighlightBounds();
		if (!bounds) return nothing;
		const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
		return html`
      <div 
        ${ref(this.frameHighlightRef)}
        class="frame-highlight" 
        style=${styleMap({
			left: `${hierarchyWidth + bounds.startPx}px`,
			width: `${bounds.widthPx}px`
		})}
      ></div>
    `;
	}
	renderControls() {
		if (!this.showControls) return nothing;
		return html`
      <div class="header" part="header">
        <div class="controls">
          ${this.renderPlaybackControls()}
          ${this.renderTimeDisplay()}
        </div>
        ${this.renderZoomControls()}
      </div>
    `;
	}
	handleTrimChange(e) {
		const { elementId, value } = e.detail;
		const element = this.targetElement?.querySelector(`#${elementId}`);
		if (element) {
			element.trimStartMs = value.startMs;
			element.trimEndMs = value.endMs;
		}
	}
	/**
	* Handle row hover events - update canvas highlighted element.
	*/
	handleRowHover(e) {
		this.setHighlightedElement(e.detail.element);
	}
	/**
	* Handle row selection events - update selection context.
	*/
	handleRowSelect(e) {
		const selectionCtx = this.getCanvasSelectionContext();
		if (selectionCtx && e.detail.elementId) selectionCtx.select(e.detail.elementId);
	}
	/**
	* Render timeline rows using flattened hierarchy.
	* Each row is a unified component with both label and track.
	*/
	renderRows(target) {
		const rows = flattenHierarchy(target).filter((row) => shouldRenderElement(row.element, this.hideSelectors, this.showSelectors));
		const selectionCtx = this.getCanvasSelectionContext();
		const selectedIds = new Set(selectionCtx?.selectedIds ?? []);
		const highlightedElement = this.getHighlightedElement();
		return html`
      <div
        class="tracks-rows"
        @track-trim-change=${this.handleTrimChange}
        @row-hover=${this.handleRowHover}
        @row-select=${this.handleRowSelect}
      >
        ${repeat(rows, (row) => {
			const el = row.element;
			return el && el.id ? el.id : row.element;
		}, (row) => html`
            <ef-timeline-row
              .element=${row.element}
              depth=${row.depth}
              pixels-per-ms=${this.pixelsPerMs}
              ?enable-trim=${this.enableTrim}
              ?hide-label=${!this.showHierarchy}
              .hideSelectors=${this.hideSelectors}
              .showSelectors=${this.showSelectors}
              .highlightedElement=${highlightedElement}
              .selectedIds=${selectedIds}
            ></ef-timeline-row>
          `)}
      </div>
    `;
	}
	render() {
		const target = this.targetTemporal;
		if (!target) return html`
        <div class="timeline-container">
          ${this.renderControls()}
          <div class="empty-state">No target element selected</div>
        </div>
      `;
		const playheadPx = timeToPx(this.currentTimeMs, this.pixelsPerMs);
		const hierarchyWidth = this.showHierarchy ? _EFTimeline.HIERARCHY_WIDTH : 0;
		const playheadLeft = hierarchyWidth + playheadPx;
		return html`
      <div class="timeline-container" tabindex="0" ${ref(this.containerRef)}>
        ${this.renderControls()}
        <div class="timeline-area">
          <!-- Tracks Viewport - Single scrollable container -->
          <div class="tracks-viewport" part="tracks">
            <div 
              class="tracks-scroll" 
              ${ref(this.tracksScrollRef)} 
              @pointerdown=${this.handleTracksPointerDown}
            >
              <!-- Ruler Row - Inside scroll container with sticky positioning -->
              ${this.showRuler ? html`
                <div class="ruler-row" style="width: ${this.contentWidthPx + hierarchyWidth}px;">
                  ${this.showHierarchy ? html`<div class="ruler-spacer"></div>` : nothing}
                  <div 
                    class="ruler-content"
                    part="ruler"
                    @pointerdown=${this.handleRulerPointerDown}
                  >
                    <ef-timeline-ruler
                      duration-ms=${this.durationMs}
                      fps=${this.fps}
                      content-width=${this.contentWidthPx}
                    ></ef-timeline-ruler>
                    ${this.showPlayhead ? html`
                      <div 
                        ${ref(this.playheadHandleRef)}
                        class="ruler-playhead-handle" 
                        style="left: ${playheadPx}px;"
                        @pointerdown=${this.handlePlayheadPointerDown}
                      ></div>
                    ` : nothing}
                  </div>
                </div>
              ` : nothing}
              <div class="tracks-content" style="min-width: ${this.contentWidthPx + hierarchyWidth}px;">
                <!-- Track rows layer -->
                <div class="tracks-rows-layer">
                  ${this.renderRows(target)}
                </div>
                
                <!-- Playhead layer - sticky to stay visible during vertical scroll -->
                <div class="playhead-layer">
                  ${this.renderFrameHighlight()}
                  ${this.showPlayhead ? html`
                    <div ${ref(this.playheadRef)} class="playhead" part="playhead" style="left: ${playheadLeft - 1}px;">
                      <div class="playhead-drag-target" @pointerdown=${this.handlePlayheadPointerDown}></div>
                    </div>
                  ` : nothing}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    `;
	}
};
__decorate([property({ type: String })], EFTimeline.prototype, "target", void 0);
__decorate([property({
	type: Number,
	attribute: "pixels-per-ms"
})], EFTimeline.prototype, "pixelsPerMs", void 0);
__decorate([property({
	type: Number,
	attribute: "min-zoom"
})], EFTimeline.prototype, "minZoom", void 0);
__decorate([property({
	type: Number,
	attribute: "max-zoom"
})], EFTimeline.prototype, "maxZoom", void 0);
__decorate([property({
	type: Boolean,
	attribute: "enable-trim"
})], EFTimeline.prototype, "enableTrim", void 0);
__decorate([property({
	type: Boolean,
	attribute: "show-controls"
})], EFTimeline.prototype, "showControls", void 0);
__decorate([property({
	type: Boolean,
	attribute: "show-ruler"
})], EFTimeline.prototype, "showRuler", void 0);
__decorate([property({
	type: Boolean,
	attribute: "show-hierarchy"
})], EFTimeline.prototype, "showHierarchy", void 0);
__decorate([property({
	type: Boolean,
	attribute: "show-playhead"
})], EFTimeline.prototype, "showPlayhead", void 0);
__decorate([property({
	type: Boolean,
	attribute: "show-playback-controls"
})], EFTimeline.prototype, "showPlaybackControls", void 0);
__decorate([property({
	type: Boolean,
	attribute: "show-zoom-controls"
})], EFTimeline.prototype, "showZoomControls", void 0);
__decorate([property({
	type: Boolean,
	attribute: "show-time-display"
})], EFTimeline.prototype, "showTimeDisplay", void 0);
__decorate([property({
	type: String,
	attribute: "control-target"
})], EFTimeline.prototype, "controlTarget", void 0);
__decorate([property({ type: String })], EFTimeline.prototype, "hide", void 0);
__decorate([property({ type: String })], EFTimeline.prototype, "show", void 0);
__decorate([state()], EFTimeline.prototype, "targetElement", void 0);
__decorate([state()], EFTimeline.prototype, "currentTimeMs", void 0);
__decorate([state()], EFTimeline.prototype, "isPlaying", void 0);
__decorate([state()], EFTimeline.prototype, "isLooping", void 0);
__decorate([state()], EFTimeline.prototype, "viewportScrollLeft", void 0);
__decorate([provide({ context: timelineStateContext }), state()], EFTimeline.prototype, "_timelineState", void 0);
__decorate([provide({ context: timelineEditingContext }), state()], EFTimeline.prototype, "_editingContext", void 0);
__decorate([consume({
	context: selectionContext,
	subscribe: true
})], EFTimeline.prototype, "selectionContext", void 0);
__decorate([provide({ context: playingContext })], EFTimeline.prototype, "providedPlaying", null);
__decorate([provide({ context: loopContext })], EFTimeline.prototype, "providedLoop", null);
__decorate([provide({ context: currentTimeContext })], EFTimeline.prototype, "providedCurrentTime", null);
__decorate([provide({ context: durationContext })], EFTimeline.prototype, "providedDuration", null);
__decorate([provide({ context: targetTemporalContext })], EFTimeline.prototype, "providedTargetTemporal", null);
__decorate([eventOptions({ passive: false })], EFTimeline.prototype, "handleRulerPointerDown", null);
__decorate([eventOptions({ passive: false })], EFTimeline.prototype, "handlePlayheadPointerDown", null);
__decorate([eventOptions({ passive: false })], EFTimeline.prototype, "handleTracksPointerDown", null);
EFTimeline = _EFTimeline = __decorate([customElement("ef-timeline")], EFTimeline);

//#endregion
export { EFTimeline };
//# sourceMappingURL=EFTimeline.js.map