@zkmake/three-cameras 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-BAkbs7K2.js","names":["ICONS","svg","icon","trim","escape","SNAP","DRAG_PX","svg"],"sources":["../src/ui/styles.ts","../src/ui/panel.ts","../src/ui/path-editor.ts","../src/ui/ease-editor.ts","../src/ui/graph.ts","../src/ui/timeline.ts","../src/ui/mount.ts"],"sourcesContent":["/** The panel's stylesheet, injected once. Palettes by `data-theme` on `.tcm`. */\nexport const CAMERA_PANEL_STYLES = /* css */ `\n/* Inside a dev-panel card the tokens come from three-meter's (\\`--perf-*\\`), so the panels match;\n on its own (in a host's tab) the same palette is the fallback. */\n.tcm {\n --tcm-bg: var(--perf-bg, rgba(22, 24, 29, 0.92));\n --tcm-fg: var(--perf-fg, #e6e8eb);\n --tcm-dim: var(--perf-muted, #8b909a);\n --tcm-line: var(--perf-border, rgba(255, 255, 255, 0.12));\n --tcm-field: var(--perf-row, rgba(255, 255, 255, 0.04));\n --tcm-hover: var(--perf-row, rgba(255, 255, 255, 0.04));\n --tcm-accent: var(--perf-accent, #60a5fa);\n --tcm-accent-soft: rgba(96, 165, 250, 0.14);\n --tcm-live: #34d399;\n box-sizing: border-box;\n color: var(--tcm-fg);\n font: 11px/1.35 ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n}\n.tcm[data-theme=\"light\"] {\n --tcm-bg: rgba(250, 250, 252, 0.94);\n --tcm-fg: #1a1c21;\n --tcm-dim: #6b7079;\n --tcm-line: rgba(0, 0, 0, 0.12);\n --tcm-field: rgba(0, 0, 0, 0.05);\n --tcm-hover: rgba(0, 0, 0, 0.05);\n --tcm-accent: #2563eb;\n --tcm-accent-soft: rgba(37, 99, 235, 0.1);\n --tcm-live: #047857;\n}\n.tcm *, .tcm *::before, .tcm *::after { box-sizing: inherit; }\n.tcm button { font: inherit; color: inherit; }\n\n.tcm-panel { display: flex; flex-direction: column; gap: 6px; min-height: 0; padding: 8px; }\n/* In the dev-panel card: a fixed width (the frame's cap raised to fit), the list scrolling\n inside the card's height. */\n.perf-hud.tcm-frame { max-width: min(23rem, calc(100vw - 48px)); }\n/* With the timeline: one panel across the bottom of the screen, anchored (no drag grip), the\n camera list as the timeline's sidebar. Compact, the list alone. */\n.perf-hud.tcm-combined-frame .perf-hud__drag { display: none; }\n/* Full width: the control discs go in a row on the open side, above the panel on the bottom\n edge and below it on the top, so the panel can reach both sides of the screen. */\n.perf-hud.tcm-combined-frame:not(.tcm-compact) { width: calc(100vw - 20px); max-width: none; }\n.perf-hud.tcm-combined-frame:not(.tcm-compact) .perf-hud__tools { left: auto; right: 4px; flex-direction: row; }\n.perf-hud.tcm-combined-frame:not(.tcm-compact)[data-edge=\"bottom\"] .perf-hud__tools { top: auto; bottom: calc(100% + 6px); }\n.perf-hud.tcm-combined-frame:not(.tcm-compact)[data-edge=\"top\"] .perf-hud__tools { top: calc(100% + 6px); }\n.perf-hud.tcm-combined-frame:not(.tcm-compact) .perf-hud__hotspot { right: -10px; left: -10px; }\n.perf-hud.tcm-combined-frame:not(.tcm-compact)[data-edge=\"bottom\"] .perf-hud__hotspot { top: -40px; bottom: -10px; }\n.perf-hud.tcm-combined-frame:not(.tcm-compact)[data-edge=\"top\"] .perf-hud__hotspot { top: -10px; bottom: -40px; }\n.tcm-shell { display: flex; flex-direction: column; min-height: 0; }\n.tcm-shell > .tcm-timeline.is-combined { width: 100%; max-height: min(28rem, 40vh); }\n.tcm-shell > .tcm-panel { width: 22rem; max-width: 100%; }\n.tcm-timeline.is-combined { min-height: 0; padding-bottom: 6px; }\n.tcm-timeline.is-combined .tcm-tl { flex: 1; min-height: 0; grid-template-columns: 21rem minmax(0, 1fr); overflow-y: auto; }\n.tcm-timeline.is-combined .tcm-tl-names { display: block; border-right: 1px solid var(--tcm-line); }\n.tcm-timeline.is-combined .tcm-tl-names > .tcm-panel { padding: 0 6px 0 0; }\n.tcm-timeline.is-combined .tcm-panel .tcm-list { overflow: visible; }\n.tcm-timeline.is-combined .tcm-panel .tcm-toolbar,\n.tcm-timeline.is-combined .tcm-ruler { position: sticky; top: 0; z-index: 2; height: 30px; background: var(--tcm-bg); }\n.tcm-timeline.is-combined .tcm-panel .tcm-toolbar { align-items: center; }\n.tcm-timeline.is-combined .tcm-tl-area { margin-left: 0; }\n.tcm-timeline.is-combined .tcm-lanes { position: relative; display: block; }\n.tcm-timeline.is-combined .tcm-lane { position: absolute; left: 0; right: 0; border-bottom: 0; border-top: 1px solid var(--tcm-line); }\n.tcm-timeline.is-combined .tcm-lane[hidden] { display: none; }\n.tcm-timeline.is-combined .tcm-lane.is-inert { cursor: default; background: repeating-linear-gradient(135deg, transparent 0 6px, var(--tcm-field) 6px 7px); }\n.tcm-timeline.is-combined .tcm-key { top: calc(var(--tcm-head, 24px) / 2); }\n.tcm-timeline.is-combined .tcm-graph-side { padding: 6px 0 4px; }\n\n/* Compact, the list alone: the brand row, with the count. */\n/* Collapsed: a small widget with the picked camera, and nothing else. */\n.perf-hud.tcm-compact .tcm-shell > :not(.tcm-mini) { display: none; }\n.perf-hud.tcm-frame.tcm-compact .perf-hud__brand-detail { display: none; }\n.tcm-mini { display: none; }\n.perf-hud.tcm-compact .tcm-mini {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 10rem;\n max-width: 18rem;\n margin: 0 6px 6px;\n padding: 3px 8px 3px 4px;\n border: 1px solid var(--tcm-line);\n border-radius: 5px;\n background: var(--tcm-field);\n text-align: left;\n cursor: pointer;\n}\n.tcm-mini:hover { border-color: var(--tcm-accent); }\n.tcm-mini:focus-visible { outline: 1px solid var(--tcm-accent); }\n.tcm-mini-kind { display: grid; place-items: center; color: var(--tcm-dim); }\n.tcm-mini.is-live .tcm-mini-kind { color: var(--tcm-live); }\n.tcm-mini.is-viewing .tcm-mini-kind { color: var(--tcm-accent); }\n.tcm-mini-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }\n.tcm-mini-clock { margin-left: auto; color: var(--tcm-dim); font-variant-numeric: tabular-nums; }\n.perf-hud.tcm-compact .perf-hud__brand { padding-bottom: 6px; }\n\n.tcm-toolbar { display: flex; gap: 6px; }\n.tcm-search {\n flex: 1;\n min-width: 0;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n padding: 4px 6px;\n color: inherit;\n font: inherit;\n}\n.tcm-search::placeholder { color: var(--tcm-dim); }\n.tcm-search:focus { outline: 1px solid var(--tcm-accent); outline-offset: 0; }\n\n.tcm-icon {\n display: inline-grid;\n place-items: center;\n width: 26px;\n height: 26px;\n flex: none;\n padding: 0;\n border: 1px solid transparent;\n border-radius: 5px;\n background: transparent;\n color: var(--tcm-dim);\n cursor: pointer;\n}\n.tcm-icon:hover:not(:disabled) { background: var(--tcm-hover); color: var(--tcm-fg); }\n.tcm-icon:focus-visible { outline: 1px solid var(--tcm-accent); }\n.tcm-icon:disabled { visibility: hidden; }\n.tcm-icon[aria-pressed=\"true\"] { color: var(--tcm-accent); background: var(--tcm-accent-soft); }\n/* Line icons; the graph and the ease editor draw their own. */\n.tcm svg:not(.tcm-graph-svg, .tcm-ease svg) {\n width: 16px;\n height: 16px;\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n.tcm-list { margin: 0; padding: 0; list-style: none; overflow-y: auto; min-height: 0; }\n.tcm-row { border-radius: 5px; border: 1px solid transparent; }\n.tcm-row.is-hidden { display: none; }\n.tcm-row.is-open { border-color: var(--tcm-line); background: var(--tcm-field); }\n.tcm-head {\n display: grid;\n grid-template-columns: 32px 1fr auto;\n align-items: center;\n gap: 8px;\n padding: 4px;\n border-radius: 5px;\n}\n.tcm-row:not(.is-open) .tcm-head:hover { background: var(--tcm-hover); }\n.tcm-kind {\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n border: 1px solid var(--tcm-line);\n border-radius: 5px;\n color: var(--tcm-dim);\n}\n.tcm-row.is-live .tcm-kind { color: var(--tcm-live); border-color: color-mix(in srgb, var(--tcm-live) 45%, transparent); }\n.tcm-text {\n min-width: 0;\n padding: 0;\n border: 0;\n background: none;\n text-align: left;\n cursor: pointer;\n}\n.tcm-name-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }\n.tcm-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }\n.tcm-badge { flex: none; color: var(--tcm-dim); }\n.tcm-badge:empty { display: none; }\n.tcm-badge.is-live, .tcm-badge.is-viewing { display: inline-flex; align-items: center; gap: 4px; }\n.tcm-badge.is-live { color: var(--tcm-live); }\n.tcm-badge.is-viewing { color: var(--tcm-accent); }\n.tcm-badge.is-live::before {\n content: \"\";\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentColor;\n animation: tcm-pulse 1.6s ease-in-out infinite;\n}\n@keyframes tcm-pulse { 50% { opacity: 0.25; } }\n@media (prefers-reduced-motion: reduce) { .tcm-badge.is-live::before { animation: none; } }\n.tcm-row.is-viewing .tcm-kind { color: var(--tcm-accent); border-color: color-mix(in srgb, var(--tcm-accent) 50%, transparent); }\n.tcm-meta { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--tcm-dim); }\n.tcm-actions { display: flex; gap: 2px; }\n.tcm-chevron svg { transition: rotate 120ms; }\n.tcm-row.is-open .tcm-chevron svg { rotate: 180deg; }\n\n.tcm-details {\n display: none;\n gap: 8px;\n padding: 2px 8px 8px;\n}\n.tcm-row.is-open .tcm-details { display: grid; }\n.tcm-group { display: grid; gap: 3px; }\n.tcm-term { color: var(--tcm-dim); }\n.tcm-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; }\n.tcm-fields--lens { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n.tcm-num {\n display: flex;\n align-items: center;\n min-width: 0;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n}\n.tcm-num:focus-within { border-color: var(--tcm-accent); }\n.tcm-scrub {\n flex: none;\n padding: 0 5px;\n color: var(--tcm-dim);\n cursor: ew-resize;\n user-select: none;\n touch-action: none;\n}\n.tcm-scrub:hover { color: var(--tcm-fg); }\n.tcm-num input {\n width: 100%;\n min-width: 0;\n padding: 3px 5px 3px 0;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n font-variant-numeric: tabular-nums;\n text-align: right;\n -moz-appearance: textfield;\n appearance: textfield;\n}\n.tcm-num input::-webkit-inner-spin-button, .tcm-num input::-webkit-outer-spin-button { appearance: none; margin: 0; }\n.tcm-num input:focus { outline: none; }\n.tcm-readout { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 0; }\n.tcm-readout dt { color: var(--tcm-dim); }\n.tcm-readout dd { margin: 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }\n.tcm-buttons { display: flex; gap: 6px; }\n.tcm-text-button {\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n padding: 3px 8px;\n cursor: pointer;\n white-space: nowrap;\n}\n.tcm-text-button:hover { border-color: var(--tcm-accent); }\n.tcm-text-button:focus-visible { outline: 1px solid var(--tcm-accent); }\n.tcm-views { display: flex; flex-wrap: wrap; gap: 4px; margin: 0; padding: 0; list-style: none; }\n.tcm-views:empty { display: none; }\n.tcm-view { display: flex; align-items: center; border: 1px solid var(--tcm-line); border-radius: 4px; }\n.tcm-view .tcm-icon { width: 20px; height: 20px; }\n.tcm-view .tcm-icon svg { width: 12px; height: 12px; }\n.tcm-view-go {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 4px 2px 6px;\n border: 0;\n background: none;\n cursor: pointer;\n}\n.tcm-view-go svg { width: 12px; height: 12px; }\n.tcm-view-go:hover { color: var(--tcm-accent); }\n.tcm-banner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 4px 4px 8px;\n border-radius: 5px;\n background: var(--tcm-accent-soft);\n color: var(--tcm-accent);\n}\n.tcm-banner[hidden] { display: none; }\n.tcm-status { color: var(--tcm-dim); padding: 0 4px; }\n.tcm-status:empty { display: none; }\n.tcm-empty { color: var(--tcm-dim); padding: 6px 4px; }\n.tcm-row.is-selected:not(.is-open) .tcm-head { background: var(--tcm-hover); }\n.tcm-keys { color: var(--tcm-accent); }\n\n/* Timeline */\n.tcm-timeline { display: flex; flex-direction: column; gap: 6px; padding: 8px; outline: none; }\n.tcm-transport, .tcm-inspector { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }\n.tcm-spacer { flex: 1; }\n.tcm-clock { min-width: 9.5em; padding: 0 6px; font-variant-numeric: tabular-nums; }\n.tcm-timeline.is-engaged .tcm-clock { color: var(--tcm-accent); }\n.tcm-length { display: inline-flex; align-items: center; gap: 4px; color: var(--tcm-dim); }\n.tcm-length input, .tcm-inspector select {\n width: 4.5em;\n padding: 2px 4px;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n color: var(--tcm-fg);\n font: inherit;\n font-variant-numeric: tabular-nums;\n}\n.tcm-inspector select { width: auto; }\n.tcm-length input:focus, .tcm-inspector select:focus { outline: 1px solid var(--tcm-accent); }\n.tcm-add-key { display: inline-flex; align-items: center; gap: 5px; max-width: 14em; }\n.tcm-add-key span { overflow: hidden; text-overflow: ellipsis; }\n.tcm-add-key svg { width: 12px; height: 12px; color: var(--tcm-accent); }\n.tcm-add-key:disabled { opacity: 0.4; cursor: default; }\n.tcm-inspector { padding: 4px 6px; border-radius: 5px; background: var(--tcm-field); }\n.tcm-inspector[hidden] { display: none; }\n.tcm-inspector-title { font-weight: 600; margin-right: 4px; }\n\n.tcm-tl { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); }\n.tcm-tl-names { display: grid; grid-auto-rows: 24px; grid-template-rows: 20px; }\n.tcm-tl-corner { border-bottom: 1px solid var(--tcm-line); }\n.tcm-tl-name {\n overflow: hidden;\n padding: 0 8px 0 2px;\n border: 0;\n border-bottom: 1px solid var(--tcm-line);\n background: none;\n color: var(--tcm-dim);\n text-align: left;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: pointer;\n}\n.tcm-tl-name:hover, .tcm-tl-name.is-selected { color: var(--tcm-fg); }\n.tcm-tl-name.is-selected { font-weight: 600; }\n.tcm-tl-area { position: relative; min-width: 0; margin-right: 8px; }\n.tcm-ruler {\n position: relative;\n height: 20px;\n border-bottom: 1px solid var(--tcm-line);\n cursor: ew-resize;\n touch-action: none;\n}\n.tcm-tick { position: absolute; bottom: 0; width: 1px; height: 4px; background: var(--tcm-line); }\n.tcm-tick.is-major { height: 8px; background: var(--tcm-dim); }\n.tcm-tick b {\n position: absolute;\n bottom: 9px;\n left: 0;\n transform: translateX(-50%);\n color: var(--tcm-dim);\n font-weight: 400;\n font-size: 10px;\n}\n.tcm-lanes { display: grid; grid-auto-rows: 24px; }\n.tcm-lane { position: relative; border-bottom: 1px solid var(--tcm-line); cursor: crosshair; touch-action: none; }\n.tcm-lane.is-selected { background: var(--tcm-hover); }\n.tcm-key {\n position: absolute;\n top: 50%;\n width: 11px;\n height: 11px;\n padding: 0;\n border: 1px solid var(--tcm-bg);\n border-radius: 2px;\n background: var(--tcm-accent);\n transform: translate(-50%, -50%) rotate(45deg);\n cursor: grab;\n touch-action: none;\n}\n.tcm-key.is-hold { border-radius: 0; background: var(--tcm-dim); }\n.tcm-key.is-picked { outline: 2px solid var(--tcm-fg); outline-offset: 1px; }\n.tcm-key:active { cursor: grabbing; }\n.tcm-key:focus-visible { outline: 2px solid var(--tcm-accent); outline-offset: 2px; }\n.tcm-playhead {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 1px;\n margin-left: -0.5px;\n background: #f87171;\n pointer-events: none;\n}\n.tcm-playhead::before {\n content: \"\";\n position: absolute;\n top: 0;\n left: -4px;\n border: 4.5px solid transparent;\n border-top-color: #f87171;\n}\n.tcm-tl-empty { padding: 2px 4px; }\n\n/* Keys | Graph */\n.tcm-modes { display: inline-flex; margin-left: 4px; border: 1px solid var(--tcm-line); border-radius: 5px; overflow: hidden; }\n.tcm-modes button { padding: 2px 8px; border: 0; background: none; color: var(--tcm-dim); cursor: pointer; }\n.tcm-modes button[aria-checked=\"true\"] { background: var(--tcm-accent-soft); color: var(--tcm-accent); }\n.tcm-modes button:focus-visible { outline: 1px solid var(--tcm-accent); outline-offset: -2px; }\n\n/* Lane names with the motion-path toggle */\n.tcm-tl-row { display: flex; align-items: center; min-width: 0; border-bottom: 1px solid var(--tcm-line); }\n.tcm-tl-row .tcm-tl-name { flex: 1; min-width: 0; height: 100%; border-bottom: 0; }\n.tcm-trail { width: 20px; height: 20px; margin-right: 4px; }\n.tcm-trail svg { width: 13px; height: 13px; }\n.tcm-trail:disabled { visibility: hidden; }\n\n/* Graph view: the channel list in the names column, the plot under the ruler. */\n.tcm-graph, .tcm-graph-side { display: none; }\n.tcm-timeline.is-graph .tcm-lanes, .tcm-timeline.is-graph .tcm-tl-row { display: none; }\n.tcm-timeline.is-graph .tcm-tl-names { grid-auto-rows: auto; }\n.tcm-timeline.is-graph .tcm-graph { display: block; }\n.tcm-timeline.is-graph .tcm-graph-side { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 3px; padding: 6px 6px 0 0; }\n.tcm-graph-svg { display: block; touch-action: none; overflow: visible; }\n.tcm-grid { stroke: var(--tcm-line); stroke-width: 1; }\n.tcm-key-line { stroke: var(--tcm-line); stroke-dasharray: 2 3; }\n.tcm-curve { fill: none; stroke-width: 1.6; stroke-linejoin: round; }\n.tcm-dot { stroke: var(--tcm-bg); stroke-width: 1.5; cursor: grab; }\n.tcm-dot:hover { stroke: var(--tcm-fg); }\n.tcm-dot.is-picked { stroke: var(--tcm-fg); stroke-width: 2; }\n.tcm-graph-empty { fill: var(--tcm-dim); font: inherit; }\n.tcm-graph-axis { fill: var(--tcm-dim); font: 9px ui-monospace, SFMono-Regular, Menlo, monospace; }\n.tcm-chan {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 1px 6px;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: none;\n color: var(--tcm-dim);\n cursor: pointer;\n}\n.tcm-chan::before { content: \"\"; width: 7px; height: 7px; border-radius: 50%; background: var(--tcm-chan, currentColor); opacity: 0.35; }\n.tcm-chan[aria-pressed=\"true\"] { color: var(--tcm-fg); border-color: color-mix(in srgb, var(--tcm-chan, var(--tcm-accent)) 55%, transparent); }\n.tcm-chan[aria-pressed=\"true\"]::before { opacity: 1; }\n.tcm-chan--mode::before { display: none; }\n.tcm-chan:focus-visible { outline: 1px solid var(--tcm-accent); }\n\n/* Ease curve editor, in the key inspector */\n.tcm-ease { display: flex; }\n.tcm-ease svg { display: block; overflow: visible; touch-action: none; }\n.tcm-ease:has(svg:empty) { display: none; }\n.tcm-ease-box { fill: none; stroke: var(--tcm-line); }\n.tcm-ease-curve { fill: none; stroke: var(--tcm-accent); stroke-width: 2; }\n.tcm-ease-arm { stroke: var(--tcm-dim); stroke-width: 1; }\n.tcm-ease-handle { fill: var(--tcm-accent); stroke: var(--tcm-bg); stroke-width: 1.5; cursor: grab; }\n.tcm-ease-handle:hover { stroke: var(--tcm-fg); }\n`;\n\nconst STYLE_ID = \"zkmake-three-cameras-styles\";\n\n/** Add the stylesheet to `document.head` once. */\nexport const injectStyles = () => {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) {\n return;\n }\n\n const style = document.createElement(\"style\");\n\n style.id = STYLE_ID;\n style.textContent = CAMERA_PANEL_STYLES;\n document.head.append(style);\n};\n","/**\n * The camera list: one row per camera with its kind, name, whether it's drawing right now and how\n * often, and its projection at a glance. Pick a row and it opens into the camera's controls: its\n * position, rotation and lens as fields you can type in or scrub (drag a field's label), copy as\n * code, and saved views to fly back to. Per row: look through it, and draw its frustum.\n */\nimport type { CameraKind } from \"../core/discover.ts\";\nimport type { CameraDetails, CameraEntry, CameraLab, CameraPatch } from \"../core/lab.ts\";\nimport { capturePose, poseToCode } from \"../core/pose.ts\";\nimport { injectStyles } from \"./styles.ts\";\n\nexport type CameraPanel = {\n element: HTMLElement;\n /** Paint the panel when it's not in a dev-panel card, `dark` or `light`. Default dark. */\n setTheme(theme: \"dark\" | \"light\"): void;\n /** Re-read the scene now (the panel also does a few times a second while shown). */\n refresh(): void;\n /** Pause or resume the live updates, e.g. while a host tab is hidden. */\n setActive(active: boolean): void;\n /** A camera's row, for a host that lines things up with it (the timeline's lanes). */\n row(id: string): HTMLElement | null;\n /** Hear each redraw of the rows. */\n onRender(listener: () => void): () => void;\n dispose(): void;\n};\n\nexport type CameraPanelOptions = {\n /** Put a motion-path toggle on each row, for when the rows are a timeline's sidebar. */\n paths?: boolean;\n};\n\ntype FieldKey = \"px\" | \"py\" | \"pz\" | \"rx\" | \"ry\" | \"rz\" | \"fov\" | \"near\" | \"far\" | \"zoom\";\n\ntype Controls = {\n fields: Map<FieldKey, HTMLInputElement>;\n readout: HTMLElement;\n views: HTMLUListElement;\n copy: HTMLButtonElement;\n /** What the saved-view list was drawn from, to redraw only when it changes. */\n viewsDrawn: string;\n};\n\ntype Row = {\n li: HTMLLIElement;\n name: HTMLElement;\n badge: HTMLElement;\n meta: HTMLElement;\n look: HTMLButtonElement;\n helper: HTMLButtonElement;\n path: HTMLButtonElement | null;\n details: HTMLElement;\n controls: Controls | null;\n open: boolean;\n};\n\n/** Live badges and open rows update this often, ms. */\nconst TICK_MS = 250;\n\n/** Per field: the step a typed arrow key takes and what a pixel of scrubbing adds. */\nconst FIELDS: Record<FieldKey, { label: string; step: number; perPixel: number; min?: number }> = {\n px: { label: \"x\", step: 0.01, perPixel: 0.02 },\n py: { label: \"y\", step: 0.01, perPixel: 0.02 },\n pz: { label: \"z\", step: 0.01, perPixel: 0.02 },\n rx: { label: \"x\", step: 0.1, perPixel: 0.5 },\n ry: { label: \"y\", step: 0.1, perPixel: 0.5 },\n rz: { label: \"z\", step: 0.1, perPixel: 0.5 },\n fov: { label: \"fov\", step: 0.1, perPixel: 0.2, min: 1 },\n near: { label: \"near\", step: 0.01, perPixel: 0.005, min: 0.0001 },\n far: { label: \"far\", step: 1, perPixel: 0.5, min: 0.001 },\n zoom: { label: \"zoom\", step: 0.01, perPixel: 0.005, min: 0.01 },\n};\n\n/** Line icons on a 24 grid, stroked in the text colour (styles.ts). */\nexport const KIND_ICONS: Record<CameraKind, string> = {\n perspective: '<rect x=\"2\" y=\"7\" width=\"12\" height=\"10\" rx=\"2\"/><path d=\"m14 10.5 7-4v11l-7-4\"/>',\n orthographic: '<rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"M3 12h18\"/>',\n array:\n '<rect x=\"3\" y=\"3\" width=\"8\" height=\"8\" rx=\"1.5\"/><rect x=\"13\" y=\"3\" width=\"8\" height=\"8\" rx=\"1.5\"/><rect x=\"3\" y=\"13\" width=\"8\" height=\"8\" rx=\"1.5\"/><rect x=\"13\" y=\"13\" width=\"8\" height=\"8\" rx=\"1.5\"/>',\n cube: '<path d=\"M12 3 20 7.5v9L12 21l-8-4.5v-9Z\"/><path d=\"m4 7.5 8 4.5 8-4.5M12 12v9\"/>',\n other: '<circle cx=\"12\" cy=\"12\" r=\"8\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n};\n\nconst ICONS = {\n look: '<path d=\"M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7Z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n frustum: '<path d=\"M3 12 20 5v14Z\"/><path d=\"M20 5v14\"/><circle cx=\"3\" cy=\"12\" r=\"1\"/>',\n chevron: '<path d=\"m6 9 6 6 6-6\"/>',\n refresh: '<path d=\"M21 12a9 9 0 1 1-3-6.7L21 8\"/><path d=\"M21 3v5h-5\"/>',\n go: '<path d=\"M5 12h14\"/><path d=\"m13 6 6 6-6 6\"/>',\n path: '<path d=\"M3 18c3-8 7-1 10-7s5-5 8-5\"/><circle cx=\"3\" cy=\"18\" r=\"1.5\"/><circle cx=\"21\" cy=\"6\" r=\"1.5\"/>',\n remove: '<path d=\"M6 6l12 12M18 6 6 18\"/>',\n};\n\nexport const svg = (paths: string) => `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">${paths}</svg>`;\n\nconst icon = (name: keyof typeof ICONS, label: string, extra = \"\") =>\n `<button type=\"button\" class=\"tcm-icon ${extra}\" data-action=\"${name}\" title=\"${label}\" aria-label=\"${label}\">${svg(ICONS[name])}</button>`;\n\nconst trim = (value: number, digits = 2) => {\n const fixed = value.toFixed(digits);\n\n return fixed.includes(\".\") ? fixed.replace(/\\.?0+$/, \"\") : fixed;\n};\n\nconst range = (near: number, far: number) => `${trim(near)}–${trim(far)}`;\n\nconst triple = (values: readonly number[], digits = 2) =>\n values.map((value) => trim(value, digits)).join(\" \");\n\nconst escape = (text: string) => text.replace(/[&<>\"]/g, (char) => `&#${char.charCodeAt(0)};`);\n\n/** A camera's name as a JS identifier, for copied code: `orbit view` → `orbitView`. */\nconst identifier = (name: string) => {\n const words = name.split(/[^A-Za-z0-9]+/).filter(Boolean);\n const joined = words\n .map((word, index) =>\n index === 0 ? word.toLowerCase() : word[0]!.toUpperCase() + word.slice(1),\n )\n .join(\"\");\n\n return /^[A-Za-z_]/.test(joined) ? joined : `camera${joined}`;\n};\n\n/** The row's second line: how it projects, at a glance. */\nconst describe = (entry: CameraEntry, details: CameraDetails | null) => {\n const projection = details?.projection;\n const parts: string[] = [];\n\n if (entry.kind === \"cube\") {\n parts.push(\"cube · 6 faces\");\n } else if (projection?.fov !== undefined) {\n parts.push(`fov ${trim(projection.fov, 1)}°`, range(projection.near, projection.far));\n\n if (projection.aspect !== undefined) {\n parts.push(trim(projection.aspect));\n }\n } else if (projection) {\n parts.push(\"ortho\", `zoom ${trim(projection.zoom)}`, range(projection.near, projection.far));\n } else {\n parts.push(entry.kind);\n }\n\n if (entry.keys > 0) {\n parts.push(`${entry.keys} key${entry.keys === 1 ? \"\" : \"s\"}`);\n } else if (entry.moving && entry.inScene) {\n parts.push(\"moving\");\n }\n\n if (!entry.inScene) {\n parts.push(\"not in scene\");\n }\n\n return parts.join(\" · \");\n};\n\n/** Facts that aren't edited here, under the fields. */\nconst readoutOf = (entry: CameraEntry, details: CameraDetails) => {\n const { projection } = details;\n const rows: [string, string][] = [[\"world\", triple(details.position)]];\n\n if (projection?.aspect !== undefined) {\n rows.push([\"aspect\", trim(projection.aspect, 3)]);\n }\n\n if (projection?.left !== undefined) {\n rows.push([\n \"frustum\",\n `l ${trim(projection.left)} r ${trim(projection.right!)} t ${trim(projection.top!)} b ${trim(projection.bottom!)}`,\n ]);\n }\n\n rows.push([\"drawing\", entry.live ? `${entry.fps} fps` : \"no\"]);\n rows.push([\n \"in\",\n entry.inScene ? entry.path.join(\" › \") || \"scene\" : \"the renderer only (not in the scene)\",\n ]);\n\n return rows.map(([term, value]) => `<dt>${term}</dt><dd>${escape(value)}</dd>`).join(\"\");\n};\n\nconst numberField = (key: FieldKey) => {\n const { label, step, min } = FIELDS[key];\n\n return `<label class=\"tcm-num\"><span class=\"tcm-scrub\" data-scrub=\"${key}\" title=\"Drag to change: Shift for fine, Alt for coarse\">${label}</span><input type=\"number\" data-field=\"${key}\" step=\"${step}\"${min === undefined ? \"\" : ` min=\"${min}\"`} inputmode=\"decimal\" /></label>`;\n};\n\n/** The values a field shows, from the camera now. */\nconst valuesOf = (details: CameraDetails): Partial<Record<FieldKey, number>> => {\n const [px, py, pz] = details.local.position;\n const [rx, ry, rz] = details.local.rotation;\n const projection = details.projection;\n\n return {\n px,\n py,\n pz,\n rx,\n ry,\n rz,\n ...(projection\n ? { near: projection.near, far: projection.far, zoom: projection.zoom, fov: projection.fov }\n : {}),\n };\n};\n\nconst formatField = (key: FieldKey, value: number) =>\n trim(value, key.startsWith(\"r\") || key === \"fov\" ? 2 : 4);\n\nexport const createCameraPanel = (\n lab: CameraLab,\n { paths = false }: CameraPanelOptions = {},\n): CameraPanel => {\n injectStyles();\n\n const element = document.createElement(\"div\");\n\n element.className = \"tcm tcm-panel\";\n element.innerHTML = `\n <div class=\"tcm-toolbar\">\n <input type=\"search\" class=\"tcm-search\" placeholder=\"Filter by name…\" autocomplete=\"off\" spellcheck=\"false\" aria-label=\"Filter cameras\" />\n ${icon(\"refresh\", \"Look for new cameras\")}\n </div>\n <div class=\"tcm-banner\" hidden><span></span><button type=\"button\" class=\"tcm-text-button\" data-action=\"look-back\">Back</button></div>\n <ul class=\"tcm-list\"></ul>\n <div class=\"tcm-empty\" hidden>No cameras yet. The one the renderer draws with shows up on its first frame.</div>\n <div class=\"tcm-status\" role=\"status\"></div>\n `;\n\n const search = element.querySelector<HTMLInputElement>(\".tcm-search\")!;\n const list = element.querySelector<HTMLUListElement>(\".tcm-list\")!;\n const empty = element.querySelector<HTMLElement>(\".tcm-empty\")!;\n const status = element.querySelector<HTMLElement>(\".tcm-status\")!;\n const banner = element.querySelector<HTMLElement>(\".tcm-banner\")!;\n const bannerText = banner.querySelector(\"span\")!;\n const rows = new Map<string, Row>();\n const renderListeners = new Set<() => void>();\n let timer: ReturnType<typeof setInterval> | null = null;\n /** The field being scrubbed: the tick leaves it alone. */\n let scrubbing: HTMLInputElement | null = null;\n\n const say = (message: string) => {\n status.textContent = message;\n };\n\n const attempt = (work: () => void) => {\n try {\n say(\"\");\n work();\n } catch (error) {\n say((error as Error).message.replace(/^three-cameras: /, \"\"));\n }\n };\n\n /** Read one field (or its x/y/z group) into a patch and set it. */\n const apply = (id: string, controls: Controls, key: FieldKey) => {\n const read = (field: FieldKey) => Number(controls.fields.get(field)?.value);\n const patch: CameraPatch = {};\n\n if (key.startsWith(\"p\")) {\n const values = [read(\"px\"), read(\"py\"), read(\"pz\")] as const;\n\n if (values.every(Number.isFinite)) {\n patch.position = values;\n }\n } else if (key.startsWith(\"r\")) {\n const values = [read(\"rx\"), read(\"ry\"), read(\"rz\")] as const;\n\n if (values.every(Number.isFinite)) {\n patch.rotation = values;\n }\n } else {\n const value = read(key);\n const min = FIELDS[key].min;\n\n if (Number.isFinite(value) && (min === undefined || value >= min)) {\n patch[key as \"fov\" | \"near\" | \"far\" | \"zoom\"] = value;\n }\n }\n\n attempt(() => lab.set(id, patch));\n };\n\n const buildControls = (row: Row, entry: CameraEntry): Controls => {\n const lens =\n entry.kind === \"perspective\"\n ? ([\"fov\", \"near\", \"far\", \"zoom\"] as const)\n : entry.kind === \"orthographic\"\n ? ([\"zoom\", \"near\", \"far\"] as const)\n : [];\n\n row.details.innerHTML = `\n <div class=\"tcm-group\"><span class=\"tcm-term\">position</span><div class=\"tcm-fields\">${numberField(\"px\")}${numberField(\"py\")}${numberField(\"pz\")}</div></div>\n <div class=\"tcm-group\"><span class=\"tcm-term\">rotation °</span><div class=\"tcm-fields\">${numberField(\"rx\")}${numberField(\"ry\")}${numberField(\"rz\")}</div></div>\n ${lens.length > 0 ? `<div class=\"tcm-group\"><span class=\"tcm-term\">lens</span><div class=\"tcm-fields tcm-fields--lens\">${lens.map(numberField).join(\"\")}</div></div>` : \"\"}\n <dl class=\"tcm-readout\"></dl>\n ${\n entry.projects\n ? `<div class=\"tcm-buttons\">\n <button type=\"button\" class=\"tcm-text-button\" data-action=\"copy\" title=\"Copy three.js that sets this camera up as it is now\">Copy as code</button>\n <button type=\"button\" class=\"tcm-text-button\" data-action=\"save\" title=\"Save where it is and how it projects, to fly back to\">Save view</button>\n <button type=\"button\" class=\"tcm-text-button\" data-action=\"bake\" title=\"Turn where it's moved lately into keys on its track, to edit the path\" hidden>Bake to keys</button>\n </div>\n <ul class=\"tcm-views\" aria-label=\"Saved views\"></ul>`\n : \"\"\n }\n `;\n\n const fields = new Map<FieldKey, HTMLInputElement>();\n\n for (const input of row.details.querySelectorAll<HTMLInputElement>(\"input[data-field]\")) {\n fields.set(input.dataset.field as FieldKey, input);\n }\n\n const controls: Controls = {\n fields,\n readout: row.details.querySelector(\".tcm-readout\")!,\n views: row.details.querySelector(\".tcm-views\") ?? document.createElement(\"ul\"),\n copy: row.details.querySelector('[data-action=\"copy\"]') ?? document.createElement(\"button\"),\n viewsDrawn: \"\",\n };\n const id = entry.id;\n\n for (const [key, input] of fields) {\n input.addEventListener(\"input\", () => apply(id, controls, key));\n // Put the camera's real value back once the edit is done, e.g. after an invalid entry.\n input.addEventListener(\"blur\", () => {\n const details = lab.details(id);\n const value = details ? valuesOf(details)[key] : undefined;\n\n if (value !== undefined) {\n input.value = formatField(key, value);\n }\n });\n }\n\n for (const handle of row.details.querySelectorAll<HTMLElement>(\"[data-scrub]\")) {\n const key = handle.dataset.scrub as FieldKey;\n const input = fields.get(key)!;\n\n handle.addEventListener(\"pointerdown\", (event) => {\n event.preventDefault();\n handle.setPointerCapture(event.pointerId);\n scrubbing = input;\n\n const startX = event.clientX;\n const startValue = Number(input.value) || 0;\n const { perPixel, min } = FIELDS[key];\n\n const onMove = (move: PointerEvent) => {\n const scale = move.shiftKey ? 0.1 : move.altKey ? 10 : 1;\n let value = startValue + (move.clientX - startX) * perPixel * scale;\n\n if (min !== undefined) {\n value = Math.max(min, value);\n }\n\n input.value = formatField(key, value);\n apply(id, controls, key);\n };\n const onUp = () => {\n scrubbing = null;\n handle.removeEventListener(\"pointermove\", onMove);\n handle.removeEventListener(\"pointerup\", onUp);\n handle.removeEventListener(\"pointercancel\", onUp);\n };\n\n handle.addEventListener(\"pointermove\", onMove);\n handle.addEventListener(\"pointerup\", onUp);\n handle.addEventListener(\"pointercancel\", onUp);\n });\n }\n\n return controls;\n };\n\n const drawViews = (row: Row, entry: CameraEntry) => {\n const controls = row.controls!;\n const views = lab.views(entry.id);\n const drawn = views.map((view) => `${view.id}:${view.name}`).join(\"|\");\n\n if (drawn === controls.viewsDrawn) {\n return;\n }\n\n controls.viewsDrawn = drawn;\n controls.views.innerHTML = views\n .map(\n (view) => `<li class=\"tcm-view\" data-view=\"${escape(view.id)}\">\n <button type=\"button\" class=\"tcm-view-go\" data-action=\"go\" title=\"Fly back to ${escape(view.name)}\">${svg(ICONS.go)}<span>${escape(view.name)}</span></button>\n ${icon(\"remove\", `Forget ${escape(view.name)}`)}\n </li>`,\n )\n .join(\"\");\n };\n\n const makeRow = (entry: CameraEntry): Row => {\n const li = document.createElement(\"li\");\n const id = entry.id;\n\n li.className = \"tcm-row\";\n li.innerHTML = `\n <div class=\"tcm-head\">\n <span class=\"tcm-kind\" title=\"${entry.kind} camera\">${svg(KIND_ICONS[entry.kind])}</span>\n <button type=\"button\" class=\"tcm-text\" data-action=\"open\" aria-expanded=\"false\" title=\"Pick to edit\">\n <span class=\"tcm-name-line\"><span class=\"tcm-name\"></span><span class=\"tcm-badge\"></span></span>\n <span class=\"tcm-meta\"></span>\n </button>\n <div class=\"tcm-actions\">\n ${paths ? icon(\"path\", \"Show its motion path in the scene\") : \"\"}\n ${icon(\"look\", \"Look through it\")}\n ${icon(\"frustum\", \"Show its frustum in the scene\")}\n ${icon(\"chevron\", \"Edit\", \"tcm-chevron\")}\n </div>\n </div>\n <div class=\"tcm-details\"></div>\n `;\n\n const row: Row = {\n li,\n name: li.querySelector(\".tcm-name\")!,\n badge: li.querySelector(\".tcm-badge\")!,\n meta: li.querySelector(\".tcm-meta\")!,\n look: li.querySelector<HTMLButtonElement>('[data-action=\"look\"]')!,\n helper: li.querySelector<HTMLButtonElement>('[data-action=\"frustum\"]')!,\n path: li.querySelector<HTMLButtonElement>('[data-action=\"path\"]'),\n details: li.querySelector(\".tcm-details\")!,\n controls: null,\n open: false,\n };\n\n row.name.textContent = id;\n row.name.title = id;\n li.addEventListener(\"click\", (event) => {\n const target = (event.target as HTMLElement).closest<HTMLElement>(\"[data-action]\");\n const action = target?.dataset.action;\n const current = lab.entry(id);\n\n if (!current || !action) {\n return;\n }\n\n if (action === \"open\" || action === \"chevron\") {\n row.open = !row.open;\n\n if (row.open) {\n lab.select(id);\n }\n\n if (row.open && !row.controls) {\n row.controls = buildControls(row, current);\n }\n\n update(row, current);\n } else if (action === \"look\") {\n attempt(() => lab.lookThrough(current.viewing ? null : id));\n } else if (action === \"path\") {\n attempt(() => lab.setTrail(id, !current.trail));\n } else if (action === \"frustum\") {\n attempt(() => lab.setHelper(id, !current.helper));\n } else if (action === \"copy\") {\n void copyCode(row, current);\n } else if (action === \"bake\") {\n if (\n current.keys === 0 ||\n window.confirm(`Replace ${id}'s ${current.keys} keys with its recorded move?`)\n ) {\n attempt(() => {\n const keys = lab.bakeMotion(id);\n\n say(\n `Baked ${keys.length} keys from ${id}'s last few seconds: drag their dots in the scene.`,\n );\n });\n }\n } else if (action === \"save\") {\n attempt(() => lab.saveView(id));\n } else if (action === \"go\" || action === \"remove\") {\n const viewId = target.closest<HTMLElement>(\"[data-view]\")?.dataset.view;\n\n if (viewId) {\n attempt(() => (action === \"go\" ? lab.goToView(id, viewId) : lab.deleteView(id, viewId)));\n }\n }\n });\n\n return row;\n };\n\n const copyCode = async (row: Row, entry: CameraEntry) => {\n const code = poseToCode(capturePose(entry.camera as never), identifier(entry.id));\n const button = row.controls!.copy;\n\n try {\n await navigator.clipboard.writeText(code);\n button.textContent = \"Copied\";\n } catch {\n say(\"The clipboard is blocked here. The code is logged to the console instead.\");\n // oxlint-disable-next-line no-console -- the fallback when the clipboard is blocked\n console.log(code);\n }\n\n setTimeout(() => {\n button.textContent = \"Copy as code\";\n }, 1200);\n };\n\n const update = (row: Row, entry: CameraEntry) => {\n const open = row.open;\n const details = lab.details(entry.id);\n\n row.li.classList.toggle(\"is-live\", entry.live);\n row.li.classList.toggle(\"is-open\", open);\n row.li.classList.toggle(\"is-viewing\", entry.viewing);\n row.li.classList.toggle(\"is-selected\", entry.selected);\n row.badge.className = `tcm-badge${entry.viewing ? \" is-viewing\" : entry.live ? \" is-live\" : \"\"}`;\n row.badge.textContent = entry.viewing\n ? \"viewing\"\n : entry.standingIn\n ? \"replaced\"\n : entry.live\n ? `live · ${entry.fps} fps`\n : \"\";\n row.meta.textContent = describe(entry, details);\n row.meta.title = row.meta.textContent;\n row.look.disabled = !entry.projects || !lab.watching;\n row.look.setAttribute(\"aria-pressed\", String(entry.viewing));\n row.look.title = entry.viewing ? \"Stop looking through it\" : \"Look through it\";\n row.look.setAttribute(\"aria-label\", row.look.title);\n row.helper.disabled = !entry.canHelp;\n row.helper.setAttribute(\"aria-pressed\", String(entry.helper));\n row.helper.title = entry.helper ? \"Hide its frustum\" : \"Show its frustum in the scene\";\n row.helper.setAttribute(\"aria-label\", row.helper.title);\n\n if (row.path) {\n row.path.disabled = entry.keys === 0;\n row.path.setAttribute(\"aria-pressed\", String(entry.trail));\n row.path.title = entry.trail ? \"Hide its motion path\" : \"Show its motion path in the scene\";\n row.path.setAttribute(\"aria-label\", row.path.title);\n }\n\n for (const toggle of row.li.querySelectorAll('[data-action=\"open\"], [data-action=\"chevron\"]')) {\n toggle.setAttribute(\"aria-expanded\", String(open));\n }\n\n if (!open || !details || !row.controls) {\n return;\n }\n\n const values = valuesOf(details);\n\n for (const [key, input] of row.controls.fields) {\n const value = values[key];\n\n // Leave a field alone while it's being typed in or scrubbed.\n if (value !== undefined && input !== document.activeElement && input !== scrubbing) {\n input.value = formatField(key, value);\n }\n }\n\n row.controls.readout.innerHTML = readoutOf(entry, details);\n\n const bake = row.details.querySelector<HTMLButtonElement>('[data-action=\"bake\"]');\n\n if (bake) {\n bake.hidden = !entry.recorded;\n }\n drawViews(row, entry);\n };\n\n const filter = () => {\n const query = search.value.trim().toLowerCase();\n\n for (const [id, row] of rows) {\n row.li.classList.toggle(\"is-hidden\", query.length > 0 && !id.toLowerCase().includes(query));\n }\n };\n\n /** Rows in, rows out, then every row's live state. */\n const render = () => {\n const entries = lab.entries();\n const ids = new Set(entries.map((entry) => entry.id));\n\n for (const [id, row] of rows) {\n if (!ids.has(id)) {\n row.li.remove();\n rows.delete(id);\n }\n }\n\n for (const entry of entries) {\n let row = rows.get(entry.id);\n\n if (!row) {\n row = makeRow(entry);\n rows.set(entry.id, row);\n list.append(row.li);\n }\n\n update(row, entry);\n }\n\n const viewing = entries.find((entry) => entry.viewing);\n const standIn = entries.find((entry) => entry.standingIn);\n\n banner.hidden = !viewing;\n bannerText.textContent = viewing\n ? `Looking through ${viewing.id}${standIn ? ` in place of ${standIn.id}` : \"\"}`\n : \"\";\n empty.hidden = entries.length > 0;\n filter();\n element.dataset.count = String(entries.length);\n element.dispatchEvent(new CustomEvent(\"tcm-count\", { detail: entries.length }));\n\n for (const listener of renderListeners) {\n listener();\n }\n };\n\n element.querySelector('[data-action=\"refresh\"]')!.addEventListener(\"click\", () => lab.refresh());\n banner\n .querySelector(\"button\")!\n .addEventListener(\"click\", () => attempt(() => lab.lookThrough(null)));\n search.addEventListener(\"input\", filter);\n\n const unsubscribe = lab.subscribe(render);\n\n const setActive = (active: boolean) => {\n if (active && !timer) {\n lab.refresh();\n render();\n timer = setInterval(() => {\n lab.refresh();\n render();\n }, TICK_MS);\n } else if (!active && timer) {\n clearInterval(timer);\n timer = null;\n }\n };\n\n render();\n setActive(true);\n\n return {\n element,\n setTheme: (theme) => {\n // In a dev-panel card the frame's palette (`--perf-*`) applies; elsewhere the panel's own.\n if (element.closest(\".perf-hud__card\")) {\n delete element.dataset.theme;\n } else {\n element.dataset.theme = theme;\n }\n },\n refresh: () => lab.refresh(),\n setActive,\n row: (id) => rows.get(id)?.li ?? null,\n onRender: (listener) => {\n renderListeners.add(listener);\n\n return () => {\n renderListeners.delete(listener);\n };\n },\n dispose: () => {\n setActive(false);\n unsubscribe();\n renderListeners.clear();\n element.remove();\n },\n };\n};\n","/**\n * Editing motion paths in the scene: the key dots on a camera's path are handles. Hover one and\n * the cursor becomes a hand; drag it and the key moves, on a plane facing the view through where it\n * was (Shift: straight up or down), so the path and the camera's move reshape as you go.\n *\n * It listens on the renderer's canvas in the capture phase and takes only presses that land on a\n * dot, so orbit controls and the app's own handlers get every other press.\n */\nimport { Matrix4, Plane, Ray, Vector3 } from \"three\";\n\nimport type { CameraLab } from \"../core/lab.ts\";\n\n/** A dot within this many CSS pixels of the pointer is under it. */\nconst HIT_PX = 10;\n\ntype Hit = { camera: string; key: string; position: Vector3 };\n\nexport const createPathEditor = (\n lab: CameraLab,\n canvas: HTMLCanvasElement,\n { onPick }: { onPick?: (camera: string, key: string) => void } = {},\n) => {\n const inverseProjection = new Matrix4();\n const point = new Vector3();\n let restoreCursor: string | null = null;\n let dragging = false;\n\n /** The main view's rectangle on the canvas, in CSS pixels from its top left. */\n const viewRect = (view: NonNullable<ReturnType<CameraLab[\"viewTransform\"]>>) => {\n const box = canvas.getBoundingClientRect();\n const scale = canvas.width > 0 ? box.width / canvas.width : 1;\n const { x, y, z: width, w: height } = view.viewport;\n\n return {\n left: box.left + x * scale,\n top: box.top + box.height - (y + height) * scale,\n width: width * scale,\n height: height * scale,\n };\n };\n\n /** The dot under the pointer, nearest first. */\n const hitAt = (clientX: number, clientY: number): Hit | null => {\n const view = lab.viewTransform();\n\n if (!view) {\n return null;\n }\n\n const rect = viewRect(view);\n const toView = view.world.clone().invert();\n let best: Hit | null = null;\n let bestDistance = HIT_PX;\n\n for (const handle of lab.keyHandles()) {\n point.copy(handle.position).applyMatrix4(toView).applyMatrix4(view.projection);\n\n if (point.z < -1 || point.z > 1) {\n continue;\n }\n\n const x = rect.left + ((point.x + 1) / 2) * rect.width;\n const y = rect.top + ((1 - point.y) / 2) * rect.height;\n const distance = Math.hypot(x - clientX, y - clientY);\n\n if (distance < bestDistance) {\n bestDistance = distance;\n best = handle;\n }\n }\n\n return best;\n };\n\n /** The ray from the view through a screen point. */\n const rayAt = (clientX: number, clientY: number) => {\n const view = lab.viewTransform();\n\n if (!view) {\n return null;\n }\n\n const rect = viewRect(view);\n const ndcX = ((clientX - rect.left) / rect.width) * 2 - 1;\n const ndcY = -(((clientY - rect.top) / rect.height) * 2 - 1);\n\n inverseProjection.copy(view.projection).invert();\n\n const near = new Vector3(ndcX, ndcY, -1)\n .applyMatrix4(inverseProjection)\n .applyMatrix4(view.world);\n const far = new Vector3(ndcX, ndcY, 1).applyMatrix4(inverseProjection).applyMatrix4(view.world);\n\n return { ray: new Ray(near, far.sub(near).normalize()), view };\n };\n\n const setCursor = (cursor: string | null) => {\n if (cursor) {\n restoreCursor ??= canvas.style.cursor;\n canvas.style.cursor = cursor;\n } else if (restoreCursor !== null) {\n canvas.style.cursor = restoreCursor;\n restoreCursor = null;\n }\n };\n\n const onHover = (event: PointerEvent) => {\n if (!dragging) {\n setCursor(hitAt(event.clientX, event.clientY) ? \"grab\" : null);\n }\n };\n\n const onPointerDown = (event: PointerEvent) => {\n if (event.button !== 0) {\n return;\n }\n\n const hit = hitAt(event.clientX, event.clientY);\n const start = hit ? rayAt(event.clientX, event.clientY) : null;\n\n if (!hit || !start) {\n return;\n }\n\n // Ours: keep it from orbit controls and the app.\n event.stopImmediatePropagation();\n event.preventDefault();\n canvas.setPointerCapture(event.pointerId);\n dragging = true;\n setCursor(\"grabbing\");\n onPick?.(hit.camera, hit.key);\n lab.select(hit.camera);\n\n const key = lab.keys(hit.camera).find((other) => other.id === hit.key);\n\n if (key) {\n lab.seek(key.time);\n }\n\n const origin = hit.position.clone();\n const facing = new Vector3(0, 0, -1).transformDirection(start.view.world);\n const plane = new Plane().setFromNormalAndCoplanarPoint(facing, origin);\n // For Shift: a vertical plane facing the view, to move straight up or down.\n const level = facing.clone().setY(0);\n const upright = new Plane().setFromNormalAndCoplanarPoint(\n level.lengthSq() > 1e-6 ? level.normalize() : new Vector3(0, 0, 1),\n origin,\n );\n const grabbed = start.ray.intersectPlane(plane, new Vector3()) ?? origin.clone();\n const grabbedUpright = start.ray.intersectPlane(upright, new Vector3()) ?? origin.clone();\n\n const onMove = (move: PointerEvent) => {\n const next = rayAt(move.clientX, move.clientY);\n\n if (!next) {\n return;\n }\n\n let target: Vector3 | null;\n\n if (move.shiftKey) {\n const hitUpright = next.ray.intersectPlane(upright, new Vector3());\n\n target = hitUpright\n ? origin.clone().setY(origin.y + hitUpright.y - grabbedUpright.y)\n : null;\n } else {\n const hitPlane = next.ray.intersectPlane(plane, new Vector3());\n\n target = hitPlane ? origin.clone().add(hitPlane.sub(grabbed)) : null;\n }\n\n if (target) {\n try {\n lab.moveKeyTo(hit.camera, hit.key, target);\n } catch {\n // The key went away mid-drag (deleted in the panel): nothing to move.\n }\n }\n };\n\n const onUp = () => {\n canvas.removeEventListener(\"pointermove\", onMove);\n canvas.removeEventListener(\"pointerup\", onUp);\n canvas.removeEventListener(\"pointercancel\", onUp);\n dragging = false;\n setCursor(null);\n };\n\n canvas.addEventListener(\"pointermove\", onMove);\n canvas.addEventListener(\"pointerup\", onUp);\n canvas.addEventListener(\"pointercancel\", onUp);\n };\n\n canvas.addEventListener(\"pointermove\", onHover);\n canvas.addEventListener(\"pointerdown\", onPointerDown, { capture: true });\n\n return {\n /** For tests and hosts: the dot under a screen point, if any. */\n hitAt,\n dispose: () => {\n canvas.removeEventListener(\"pointermove\", onHover);\n canvas.removeEventListener(\"pointerdown\", onPointerDown, { capture: true });\n setCursor(null);\n },\n };\n};\n","/**\n * The ease curve editor: the move from the picked key to the next as a curve of progress over\n * time, CSS `cubic-bezier` style, with its two handles to drag. Reshaping a preset turns it into\n * a custom curve starting from the preset's shape. Handles go outside 0–1 upward and downward for\n * overshoot and anticipation; time stays in order (0–1).\n */\nimport { applyEase, type Bezier, type Keyframe, PRESET_BEZIERS } from \"../core/track.ts\";\n\nconst W = 132;\nconst H = 104;\n/** The unit square's corners in the editor. */\nconst LEFT = 22;\nconst RIGHT = W - 12;\nconst TOP = 22;\nconst BOTTOM = H - 22;\n\nconst px = (x: number) => LEFT + x * (RIGHT - LEFT);\nconst py = (y: number) => BOTTOM - y * (BOTTOM - TOP);\nconst unx = (value: number) => (value - LEFT) / (RIGHT - LEFT);\nconst uny = (value: number) => (BOTTOM - value) / (BOTTOM - TOP);\n\nconst handlesOf = (key: Keyframe): Bezier | null => {\n if (key.ease === \"hold\") {\n return null;\n }\n\n return key.ease === \"custom\" ? (key.bezier ?? PRESET_BEZIERS.linear) : PRESET_BEZIERS[key.ease];\n};\n\nexport const createEaseEditor = ({ onCommit }: { onCommit: (bezier: Bezier) => void }) => {\n const element = document.createElement(\"div\");\n\n element.className = \"tcm-ease\";\n element.innerHTML = `<svg viewBox=\"0 0 ${W} ${H}\" width=\"${W}\" height=\"${H}\" role=\"img\" aria-label=\"Ease into the next key\"></svg>`;\n\n const svg = element.querySelector(\"svg\")!;\n let key: Keyframe | null = null;\n let hasNext = false;\n /** Handles being dragged, before they're committed. */\n let draft: Bezier | null = null;\n\n const paint = () => {\n if (!key) {\n svg.innerHTML = \"\";\n\n return;\n }\n\n if (!hasNext) {\n svg.innerHTML = `<text x=\"${W / 2}\" y=\"${H / 2}\" text-anchor=\"middle\" class=\"tcm-graph-empty\">last key: no move after it</text>`;\n\n return;\n }\n\n const handles = draft ?? handlesOf(key);\n const ease = draft ? \"custom\" : key.ease;\n const bezier = draft ?? key.bezier;\n const curve: string[] = [];\n\n for (let i = 0; i <= 48; i += 1) {\n const t = i / 48;\n\n curve.push(\n `${i === 0 ? \"M\" : \"L\"}${px(t).toFixed(1)} ${py(applyEase(ease, t, bezier)).toFixed(1)}`,\n );\n }\n\n // `hold` jumps at the end of its segment.\n if (ease === \"hold\") {\n curve.push(`L${px(1)} ${py(1)}`);\n }\n\n const box = `<rect class=\"tcm-ease-box\" x=\"${LEFT}\" y=\"${TOP}\" width=\"${RIGHT - LEFT}\" height=\"${BOTTOM - TOP}\"/><line class=\"tcm-grid\" x1=\"${px(0)}\" y1=\"${py(0)}\" x2=\"${px(1)}\" y2=\"${py(1)}\"/>`;\n const labels = `<text class=\"tcm-graph-axis\" x=\"${LEFT}\" y=\"${H - 8}\">time</text><text class=\"tcm-graph-axis\" x=\"4\" y=\"${TOP - 8}\">progress</text>`;\n const arms = handles\n ? `<line class=\"tcm-ease-arm\" x1=\"${px(0)}\" y1=\"${py(0)}\" x2=\"${px(handles[0])}\" y2=\"${py(handles[1])}\"/><line class=\"tcm-ease-arm\" x1=\"${px(1)}\" y1=\"${py(1)}\" x2=\"${px(handles[2])}\" y2=\"${py(handles[3])}\"/><circle class=\"tcm-ease-handle\" data-handle=\"0\" cx=\"${px(handles[0])}\" cy=\"${py(handles[1])}\" r=\"5\"><title>Drag to reshape</title></circle><circle class=\"tcm-ease-handle\" data-handle=\"1\" cx=\"${px(handles[2])}\" cy=\"${py(handles[3])}\" r=\"5\"><title>Drag to reshape</title></circle>`\n : \"\";\n\n svg.innerHTML = `${box}${labels}<path class=\"tcm-ease-curve\" d=\"${curve.join(\"\")}\"/>${arms}`;\n };\n\n svg.addEventListener(\"pointerdown\", (event) => {\n const handle = (event.target as SVGElement).dataset?.handle;\n\n if (!key || handle === undefined) {\n return;\n }\n\n const start = handlesOf(key);\n\n if (!start) {\n return;\n }\n\n event.preventDefault();\n svg.setPointerCapture(event.pointerId);\n draft = [...start];\n\n const box = svg.getBoundingClientRect();\n const scaleX = W / Math.max(box.width, 1);\n const scaleY = H / Math.max(box.height, 1);\n const index = handle === \"0\" ? 0 : 2;\n\n const onMove = (move: PointerEvent) => {\n const hx = unx((move.clientX - box.left) * scaleX);\n const hy = uny((move.clientY - box.top) * scaleY);\n\n draft![index] = Math.min(1, Math.max(0, hx));\n draft![index + 1] = Math.min(2, Math.max(-1, hy));\n paint();\n };\n const onUp = () => {\n svg.removeEventListener(\"pointermove\", onMove);\n svg.removeEventListener(\"pointerup\", onUp);\n svg.removeEventListener(\"pointercancel\", onUp);\n\n const committed = draft!.map((value) => Math.round(value * 1000) / 1000) as Bezier;\n\n draft = null;\n onCommit(committed);\n };\n\n svg.addEventListener(\"pointermove\", onMove);\n svg.addEventListener(\"pointerup\", onUp);\n svg.addEventListener(\"pointercancel\", onUp);\n });\n\n return {\n element,\n /** Whether a handle is being dragged: the timeline holds its rebuilds meanwhile. */\n get dragging() {\n return draft !== null;\n },\n set: (next: Keyframe | null, nextExists: boolean) => {\n key = next;\n hasNext = nextExists;\n\n if (!draft) {\n paint();\n }\n },\n };\n};\n","/**\n * The graph editor: the picked camera's channels over time, drawn from the track as it plays (so\n * the path's bends and the eases show), with a dot per key per channel. Drag a dot up or down to\n * change that value in the key, or sideways to retime the key (the first few pixels pick which).\n * Click between keys to pick the segment there, double-click to add a key.\n *\n * Channels are stretched to their own ranges by default, so metres and degrees share one plot;\n * \"values\" draws them on one shared scale instead.\n */\nimport {\n CHANNEL_LABELS,\n type Channel,\n CHANNELS,\n readChannel,\n sampleChannel,\n writeChannel,\n} from \"../core/channels.ts\";\nimport type { CameraLab } from \"../core/lab.ts\";\nimport type { Pose } from \"../core/pose.ts\";\nimport type { Keyframe } from \"../core/track.ts\";\n\nexport type PickedKey = { camera: string; key: string };\n\ntype GraphOptions = {\n /** The camera whose track is drawn. */\n camera: () => string | null;\n picked: () => PickedKey | null;\n onPick: (key: PickedKey | null) => void;\n /** A drag started or ended: the timeline holds its own rebuilds meanwhile. */\n onDrag: (dragging: boolean) => void;\n say: (message: string) => void;\n};\n\ntype Scale = { min: number; max: number };\n\nconst HEIGHT = 150;\nconst PAD = 12;\nconst SNAP = 0.1;\nconst DRAG_PX = 4;\n\nconst COLORS: Record<Channel, string> = {\n px: \"#f87171\",\n py: \"#4ade80\",\n pz: \"#60a5fa\",\n rx: \"#fca5a5\",\n ry: \"#86efac\",\n rz: \"#93c5fd\",\n fov: \"#fbbf24\",\n zoom: \"#fbbf24\",\n near: \"#c4b5fd\",\n far: \"#f0abfc\",\n};\n\nconst SHORT: Record<Channel, string> = {\n px: \"x\",\n py: \"y\",\n pz: \"z\",\n rx: \"rx\",\n ry: \"ry\",\n rz: \"rz\",\n fov: \"fov\",\n zoom: \"zoom\",\n near: \"near\",\n far: \"far\",\n};\n\nconst trim = (value: number) => {\n const text = value.toFixed(Math.abs(value) >= 100 ? 0 : 2);\n\n return text.includes(\".\") ? text.replace(/\\.?0+$/, \"\") : text;\n};\n\n/** An angle moved by whole turns to sit nearest `near`, so a key dot lands on its unwrapped curve. */\nconst nearestTurn = (value: number, near: number) => value + Math.round((near - value) / 360) * 360;\n\nexport const createGraph = (lab: CameraLab, options: GraphOptions) => {\n const side = document.createElement(\"div\");\n const plot = document.createElement(\"div\");\n\n side.className = \"tcm-graph-side\";\n plot.className = \"tcm-graph\";\n plot.innerHTML = `<svg class=\"tcm-graph-svg\" height=\"${HEIGHT}\" role=\"img\" aria-label=\"Camera channels over time\"></svg>`;\n\n const svg = plot.querySelector(\"svg\")!;\n const visible = new Set<Channel>([\"px\", \"py\", \"pz\", \"fov\"]);\n let normalize = true;\n /** Scales held still during a drag, so the value under the pointer doesn't slide. */\n let frozen: Map<Channel, Scale> | null = null;\n let lastScales = new Map<Channel, Scale>();\n let width = 600;\n\n const keysOf = (): Keyframe[] => {\n const camera = options.camera();\n\n return camera ? lab.keys(camera) : [];\n };\n\n /** Channels this track has, fov only for a perspective camera. */\n const available = (keys: readonly Keyframe[]) =>\n CHANNELS.filter((channel) => keys.some((key) => readChannel(key.pose, channel) !== undefined));\n\n const shown = (keys: readonly Keyframe[]) => {\n const channels = available(keys).filter((channel) => visible.has(channel));\n\n return channels.length > 0 ? channels : available(keys).filter((channel) => channel === \"zoom\");\n };\n\n const x = (time: number) => (time / Math.max(lab.timeline().duration, 0.001)) * width;\n const y = (value: number, scale: Scale) =>\n HEIGHT -\n PAD -\n ((value - scale.min) / Math.max(scale.max - scale.min, 1e-9)) * (HEIGHT - 2 * PAD);\n\n const rangeOf = (values: number[]): Scale => {\n let min = Math.min(...values);\n let max = Math.max(...values);\n\n if (max - min < 1e-6) {\n min -= 1;\n max += 1;\n }\n\n const pad = (max - min) * 0.08;\n\n return { min: min - pad, max: max + pad };\n };\n\n const paintSide = (keys: readonly Keyframe[]) => {\n const channels = available(keys);\n\n side.innerHTML = `${channels\n .map(\n (channel) =>\n `<button type=\"button\" class=\"tcm-chan\" data-channel=\"${channel}\" aria-pressed=\"${visible.has(channel)}\" style=\"--tcm-chan:${COLORS[channel]}\" title=\"${CHANNEL_LABELS[channel]}\">${SHORT[channel]}</button>`,\n )\n .join(\n \"\",\n )}<button type=\"button\" class=\"tcm-chan tcm-chan--mode\" data-graph-mode aria-pressed=\"${!normalize}\" title=\"${normalize ? \"Each channel stretched to its own range. Click for true values on one scale.\" : \"True values on one scale. Click to stretch each channel to its own range.\"}\">values</button>`;\n };\n\n const paint = () => {\n const keys = keysOf();\n const duration = lab.timeline().duration;\n\n width = Math.max(plot.clientWidth, 100);\n svg.setAttribute(\"width\", String(width));\n svg.setAttribute(\"viewBox\", `0 0 ${width} ${HEIGHT}`);\n paintSide(keys);\n\n if (keys.length === 0) {\n svg.innerHTML = `<text x=\"${width / 2}\" y=\"${HEIGHT / 2}\" text-anchor=\"middle\" class=\"tcm-graph-empty\">${options.camera() ? \"No keys on this camera yet: press Key, or double-click here\" : \"Pick a camera to see its track\"}</text>`;\n\n return;\n }\n\n const channels = shown(keys);\n const samples = Math.min(400, Math.max(40, Math.round(width / 3)));\n const curves = new Map(\n channels.map((channel) => [channel, sampleChannel(keys, channel, 0, duration, samples)]),\n );\n const scales = new Map<Channel, Scale>();\n\n if (frozen) {\n for (const [channel, scale] of frozen) {\n scales.set(channel, scale);\n }\n } else if (normalize) {\n for (const [channel, points] of curves) {\n scales.set(channel, rangeOf(points.map(([, value]) => value)));\n }\n } else {\n const all = rangeOf(\n [...curves.values()].flatMap((points) => points.map(([, value]) => value)),\n );\n\n for (const channel of channels) {\n scales.set(channel, all);\n }\n }\n\n lastScales = scales;\n\n const picked = options.picked();\n const grid = [0.25, 0.5, 0.75]\n .map((share) => {\n const at = PAD + share * (HEIGHT - 2 * PAD);\n\n return `<line class=\"tcm-grid\" x1=\"0\" x2=\"${width}\" y1=\"${at}\" y2=\"${at}\"/>`;\n })\n .join(\"\");\n const shared = !normalize && channels[0] ? scales.get(channels[0]) : null;\n const axis = shared\n ? `<text class=\"tcm-graph-axis\" x=\"4\" y=\"${PAD + 9}\">${trim(shared.max)}</text><text class=\"tcm-graph-axis\" x=\"4\" y=\"${HEIGHT - PAD - 3}\">${trim(shared.min)}</text>`\n : \"\";\n const keyLines = keys\n .map(\n (key) =>\n `<line class=\"tcm-key-line\" x1=\"${x(key.time)}\" x2=\"${x(key.time)}\" y1=\"0\" y2=\"${HEIGHT}\"/>`,\n )\n .join(\"\");\n const lines = channels\n .map((channel) => {\n const scale = scales.get(channel)!;\n const d = curves\n .get(channel)!\n .map(\n ([time, value], index) =>\n `${index === 0 ? \"M\" : \"L\"}${x(time).toFixed(1)} ${y(value, scale).toFixed(1)}`,\n )\n .join(\"\");\n\n return `<path class=\"tcm-curve\" d=\"${d}\" stroke=\"${COLORS[channel]}\"/>`;\n })\n .join(\"\");\n const dots = channels\n .flatMap((channel) => {\n const scale = scales.get(channel)!;\n const points = curves.get(channel)!;\n\n return keys.map((key) => {\n let value = readChannel(key.pose, channel)!;\n\n if (channel.startsWith(\"r\")) {\n const near = points.reduce((best, point) =>\n Math.abs(point[0] - key.time) < Math.abs(best[0] - key.time) ? point : best,\n );\n\n value = nearestTurn(value, near[1]);\n }\n\n const isPicked = picked?.key === key.id;\n\n return `<circle class=\"tcm-dot${isPicked ? \" is-picked\" : \"\"}\" cx=\"${x(key.time)}\" cy=\"${y(value, scale)}\" r=\"${isPicked ? 5 : 4}\" fill=\"${COLORS[channel]}\" data-key=\"${key.id}\" data-channel=\"${channel}\" data-value=\"${value}\"><title>${CHANNEL_LABELS[channel]} ${trim(value)} at ${key.time.toFixed(2)} s</title></circle>`;\n });\n })\n .join(\"\");\n\n svg.innerHTML = grid + axis + keyLines + lines + dots;\n };\n\n side.addEventListener(\"click\", (event) => {\n const button = (event.target as HTMLElement).closest<HTMLElement>(\"button\");\n\n if (!button) {\n return;\n }\n\n if (button.dataset.graphMode !== undefined) {\n normalize = !normalize;\n } else {\n const channel = button.dataset.channel as Channel;\n\n if (visible.has(channel)) {\n visible.delete(channel);\n } else {\n visible.add(channel);\n }\n }\n\n paint();\n });\n\n const timeAt = (clientX: number) => {\n const box = svg.getBoundingClientRect();\n\n return (\n Math.min(1, Math.max(0, (clientX - box.left) / Math.max(box.width, 1))) *\n lab.timeline().duration\n );\n };\n\n svg.addEventListener(\"pointerdown\", (event) => {\n const camera = options.camera();\n const target = event.target as Element;\n\n if (!camera) {\n return;\n }\n\n const keys = lab.keys(camera);\n\n if (!target.classList.contains(\"tcm-dot\")) {\n const time = timeAt(event.clientX);\n const segment = [...keys].reverse().find((key) => key.time <= time) ?? null;\n\n options.onPick(segment ? { camera, key: segment.id } : null);\n lab.seek(time);\n\n return;\n }\n\n const keyId = (target as SVGElement).dataset.key!;\n const channel = (target as SVGElement).dataset.channel as Channel;\n const startValue = Number((target as SVGElement).dataset.value);\n const key = keys.find((other) => other.id === keyId);\n\n if (!key) {\n return;\n }\n\n event.preventDefault();\n svg.setPointerCapture(event.pointerId);\n options.onPick({ camera, key: keyId });\n options.onDrag(true);\n frozen = new Map(lastScales);\n lab.seek(key.time);\n\n const startX = event.clientX;\n const startY = event.clientY;\n const startPose: Pose = key.pose;\n const duration = lab.timeline().duration;\n const scale = frozen.get(channel) ?? { min: 0, max: 1 };\n let axis: \"time\" | \"value\" | null = null;\n\n const onMove = (move: PointerEvent) => {\n const dx = move.clientX - startX;\n const dy = move.clientY - startY;\n\n if (!axis) {\n if (Math.hypot(dx, dy) < DRAG_PX) {\n return;\n }\n\n axis = Math.abs(dx) > Math.abs(dy) ? \"time\" : \"value\";\n }\n\n try {\n if (axis === \"time\") {\n const raw = key.time + (dx / width) * duration;\n const time = Math.min(\n duration,\n Math.max(0, move.shiftKey ? raw : Math.round(raw / SNAP) * SNAP),\n );\n\n lab.updateKey(camera, keyId, { time });\n lab.seek(time);\n } else {\n const perPixel = (scale.max - scale.min) / (HEIGHT - 2 * PAD);\n const value = startValue - dy * perPixel * (move.shiftKey ? 0.1 : 1);\n\n lab.updateKey(camera, keyId, { pose: writeChannel(startPose, channel, value) });\n }\n\n paint();\n } catch (error) {\n options.say((error as Error).message.replace(/^three-cameras: /, \"\"));\n }\n };\n const onUp = () => {\n svg.removeEventListener(\"pointermove\", onMove);\n svg.removeEventListener(\"pointerup\", onUp);\n svg.removeEventListener(\"pointercancel\", onUp);\n frozen = null;\n options.onDrag(false);\n paint();\n };\n\n svg.addEventListener(\"pointermove\", onMove);\n svg.addEventListener(\"pointerup\", onUp);\n svg.addEventListener(\"pointercancel\", onUp);\n });\n\n svg.addEventListener(\"dblclick\", (event) => {\n const camera = options.camera();\n\n if (camera && !(event.target as Element).classList.contains(\"tcm-dot\")) {\n try {\n const key = lab.addKey(camera, { time: Math.round(timeAt(event.clientX) / SNAP) * SNAP });\n\n options.onPick({ camera, key: key.id });\n } catch (error) {\n options.say((error as Error).message.replace(/^three-cameras: /, \"\"));\n }\n }\n });\n\n const resize = typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(() => paint());\n\n resize?.observe(plot);\n\n return {\n side,\n plot,\n paint,\n dispose: () => {\n resize?.disconnect();\n },\n };\n};\n","/**\n * The keyframe timeline: transport (to start, play/pause, release, loop, length), a ruler to\n * scrub, and two views. **Keys**: a lane per camera with its keys; drag a key to retime it (snaps\n * to 0.1 s, Shift for free), click one to inspect it, double-click a lane to key that camera\n * there, and toggle a camera's motion path in the scene. **Graph**: the picked camera's channels\n * over time (graph.ts). The inspector shows the picked key: its time, its ease into the next key\n * as a curve with handles to drag (ease-editor.ts), and update / delete.\n *\n * \"Key\" keys the picked camera at the playhead. Space plays and pauses while the timeline has\n * focus; Delete removes the picked key.\n *\n * Given a camera list (`sidebar`), the list is the timeline's left column: each camera's row sits\n * beside its lane, and a row opened into its controls stretches its lane to match.\n */\nimport type { CameraEntry, CameraLab } from \"../core/lab.ts\";\nimport { EASES, type Ease, type Keyframe } from \"../core/track.ts\";\nimport { createEaseEditor } from \"./ease-editor.ts\";\nimport { createGraph } from \"./graph.ts\";\nimport type { CameraPanel } from \"./panel.ts\";\nimport { injectStyles } from \"./styles.ts\";\n\nexport type TimelinePanel = {\n element: HTMLElement;\n setTheme(theme: \"dark\" | \"light\"): void;\n dispose(): void;\n};\n\ntype PickedKey = { camera: string; key: string };\n\n/** Keys snap to this, s, unless Shift is held. */\nconst SNAP = 0.1;\n/** A press that moves less than this, px, is a click. */\nconst DRAG_PX = 3;\n\nconst ICONS = {\n start: '<path d=\"M6 5v14\"/><path d=\"M18 5 9 12l9 7Z\"/>',\n play: '<path d=\"M7 4v16l13-8Z\"/>',\n pause: '<path d=\"M8 5v14M16 5v14\"/>',\n stop: '<rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"1.5\"/>',\n loop: '<path d=\"M17 2l4 4-4 4\"/><path d=\"M3 11V9a3 3 0 0 1 3-3h15\"/><path d=\"m7 22-4-4 4-4\"/><path d=\"M21 13v2a3 3 0 0 1-3 3H3\"/>',\n key: '<path d=\"M12 3 21 12 12 21 3 12Z\"/>',\n trail:\n '<path d=\"M3 18c3-8 7-1 10-7s5-5 8-5\"/><circle cx=\"3\" cy=\"18\" r=\"1.5\"/><circle cx=\"21\" cy=\"6\" r=\"1.5\"/>',\n};\n\nconst EASE_LABELS: Record<Ease, string> = {\n \"ease-in-out\": \"ease in-out\",\n linear: \"linear\",\n \"ease-in\": \"ease in\",\n \"ease-out\": \"ease out\",\n hold: \"hold\",\n custom: \"custom curve\",\n};\n\nconst svg = (paths: string) => `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">${paths}</svg>`;\n\nconst icon = (action: string, paths: string, label: string) =>\n `<button type=\"button\" class=\"tcm-icon\" data-action=\"${action}\" title=\"${label}\" aria-label=\"${label}\">${svg(paths)}</button>`;\n\nconst escape = (text: string) => text.replace(/[&<>\"]/g, (char) => `&#${char.charCodeAt(0)};`);\n\nconst seconds = (value: number) => value.toFixed(2);\n\n/** Ruler steps: labelled every `major` s, a tick every `minor`. */\nconst rulerSteps = (duration: number) => {\n const major = duration <= 12 ? 1 : duration <= 30 ? 5 : duration <= 120 ? 10 : 30;\n\n return { major, minor: major / (major === 1 ? 4 : 5) };\n};\n\nexport type TimelinePanelOptions = {\n /** A camera list to be the timeline's left column, its rows beside their lanes. */\n sidebar?: CameraPanel;\n};\n\nexport const createTimelinePanel = (\n lab: CameraLab,\n { sidebar }: TimelinePanelOptions = {},\n): TimelinePanel => {\n injectStyles();\n\n const element = document.createElement(\"div\");\n\n element.className = \"tcm tcm-timeline\";\n element.tabIndex = -1;\n element.innerHTML = `\n <div class=\"tcm-transport\">\n ${icon(\"start\", ICONS.start, \"To the start\")}\n <button type=\"button\" class=\"tcm-icon tcm-play\" data-action=\"play\" title=\"Play (Space)\" aria-label=\"Play\">${svg(ICONS.play)}</button>\n ${icon(\"stop\", ICONS.stop, \"Stop, and let the cameras go\")}\n ${icon(\"loop\", ICONS.loop, \"Loop\")}\n <div class=\"tcm-modes\" role=\"radiogroup\" aria-label=\"View\">\n <button type=\"button\" role=\"radio\" aria-checked=\"true\" data-action=\"mode\" data-mode=\"keys\" title=\"Keys: a lane per camera\">Keys</button>\n <button type=\"button\" role=\"radio\" aria-checked=\"false\" data-action=\"mode\" data-mode=\"graph\" title=\"Graph: the picked camera's channels over time\">Graph</button>\n </div>\n <span class=\"tcm-clock\"></span>\n <label class=\"tcm-length\" title=\"The timeline's length\">length <input type=\"number\" min=\"1\" step=\"1\" inputmode=\"decimal\" /> s</label>\n <span class=\"tcm-spacer\"></span>\n <button type=\"button\" class=\"tcm-text-button tcm-add-key\" data-action=\"add-key\">${svg(ICONS.key)}<span>Key</span></button>\n </div>\n <div class=\"tcm-tl\">\n <div class=\"tcm-tl-names\"><div class=\"tcm-tl-corner\"></div></div>\n <div class=\"tcm-tl-area\">\n <div class=\"tcm-ruler\" title=\"Drag to scrub\"></div>\n <div class=\"tcm-lanes\"></div>\n <div class=\"tcm-playhead\"></div>\n </div>\n </div>\n <div class=\"tcm-inspector\" hidden>\n <span class=\"tcm-inspector-title\"></span>\n <label class=\"tcm-length\">at <input type=\"number\" min=\"0\" step=\"0.1\" data-key-field=\"time\" inputmode=\"decimal\" /> s</label>\n <span class=\"tcm-ease-slot\"></span>\n <select data-key-field=\"ease\" aria-label=\"Ease into the next key\">${EASES.map((ease) => `<option value=\"${ease}\">${EASE_LABELS[ease]}</option>`).join(\"\")}</select>\n <span class=\"tcm-spacer\"></span>\n <button type=\"button\" class=\"tcm-text-button\" data-action=\"key-update\" title=\"Re-key it from the camera as it is now\">Update</button>\n <button type=\"button\" class=\"tcm-text-button\" data-action=\"key-delete\" title=\"Delete the key (Delete)\">Delete</button>\n </div>\n <div class=\"tcm-empty tcm-tl-empty\" hidden>Pick a camera and press Key, or double-click its lane, to start a track.</div>\n <div class=\"tcm-status\" role=\"status\"></div>\n `;\n\n const $ = <T extends Element>(selector: string) => element.querySelector<T>(selector)!;\n const playButton = $<HTMLButtonElement>('[data-action=\"play\"]');\n const loopButton = $<HTMLButtonElement>('[data-action=\"loop\"]');\n const clock = $<HTMLElement>(\".tcm-clock\");\n const lengthInput = $<HTMLInputElement>(\".tcm-transport .tcm-length input\");\n const addKey = $<HTMLButtonElement>('[data-action=\"add-key\"]');\n const addKeyLabel = addKey.querySelector(\"span\")!;\n const names = $<HTMLElement>(\".tcm-tl-names\");\n const area = $<HTMLElement>(\".tcm-tl-area\");\n const ruler = $<HTMLElement>(\".tcm-ruler\");\n const lanes = $<HTMLElement>(\".tcm-lanes\");\n const playhead = $<HTMLElement>(\".tcm-playhead\");\n const inspector = $<HTMLElement>(\".tcm-inspector\");\n const inspectorTitle = $<HTMLElement>(\".tcm-inspector-title\");\n const keyTime = $<HTMLInputElement>('[data-key-field=\"time\"]');\n const keyEase = $<HTMLSelectElement>('[data-key-field=\"ease\"]');\n const empty = $<HTMLElement>(\".tcm-tl-empty\");\n const status = $<HTMLElement>(\".tcm-status\");\n\n let picked: PickedKey | null = null;\n let dragging = false;\n let frame = 0;\n let rulerFor = -1;\n let mode: \"keys\" | \"graph\" = \"keys\";\n\n const say = (message: string) => {\n status.textContent = message;\n };\n\n const attempt = (work: () => void) => {\n try {\n say(\"\");\n work();\n } catch (error) {\n say((error as Error).message.replace(/^three-cameras: /, \"\"));\n }\n };\n\n const tracked = () => lab.entries().filter((entry) => entry.projects);\n\n const graph = createGraph(lab, {\n camera: () => target(tracked())?.id ?? null,\n picked: () => picked,\n onPick: (next) => {\n picked = next;\n paintInspector();\n },\n onDrag: (active) => {\n dragging = active;\n\n if (!active) {\n render();\n }\n },\n say: (message) => say(message),\n });\n const ease = createEaseEditor({\n onCommit: (bezier) => {\n if (picked) {\n const { camera, key } = picked;\n\n attempt(() => lab.updateKey(camera, key, { bezier }));\n }\n },\n });\n\n if (sidebar) {\n // The list is the left column; the channel toggles go over the plot instead.\n element.classList.add(\"is-combined\");\n names.replaceChildren(sidebar.element);\n area.insertBefore(graph.side, playhead);\n } else {\n names.append(graph.side);\n }\n\n area.insertBefore(graph.plot, playhead);\n $<HTMLElement>(\".tcm-ease-slot\").replaceWith(ease.element);\n\n /** The camera \"Key\" acts on: the picked one, else the first that can be keyed. */\n const target = (entries: CameraEntry[]) =>\n entries.find((entry) => entry.selected) ?? entries[0] ?? null;\n\n const timeAt = (clientX: number) => {\n // The ruler spans the time axis in both views.\n const box = ruler.getBoundingClientRect();\n const share = box.width > 0 ? (clientX - box.left) / box.width : 0;\n\n return Math.min(1, Math.max(0, share)) * lab.timeline().duration;\n };\n\n const percent = (time: number) => `${(time / Math.max(lab.timeline().duration, 0.001)) * 100}%`;\n\n /** Playhead, clock and play button: every frame while playing, else on changes. */\n const paintClock = () => {\n const state = lab.timeline();\n\n playhead.style.left = percent(state.time);\n clock.textContent = `${seconds(state.time)} / ${seconds(state.duration)} s`;\n playButton.innerHTML = svg(state.playing ? ICONS.pause : ICONS.play);\n playButton.title = state.playing ? \"Pause (Space)\" : \"Play (Space)\";\n playButton.setAttribute(\"aria-label\", state.playing ? \"Pause\" : \"Play\");\n element.classList.toggle(\"is-engaged\", state.engaged);\n element.dispatchEvent(\n new CustomEvent(\"tcm-clock\", {\n detail: `${seconds(state.time)} / ${seconds(state.duration)} s`,\n }),\n );\n\n if (state.playing && !frame) {\n const tick = () => {\n paintClock();\n frame = lab.timeline().playing ? requestAnimationFrame(tick) : 0;\n };\n\n frame = requestAnimationFrame(tick);\n }\n };\n\n const paintRuler = (duration: number) => {\n if (rulerFor === duration) {\n return;\n }\n\n rulerFor = duration;\n\n const { major, minor } = rulerSteps(duration);\n const ticks: string[] = [];\n\n for (let at = 0; at <= duration + 1e-6; at += minor) {\n const isMajor = Math.abs(at / major - Math.round(at / major)) < 1e-6;\n\n ticks.push(\n `<span class=\"tcm-tick${isMajor ? \" is-major\" : \"\"}\" style=\"left:${(at / duration) * 100}%\">${isMajor ? `<b>${Math.round(at)}</b>` : \"\"}</span>`,\n );\n }\n\n ruler.innerHTML = ticks.join(\"\");\n };\n\n const pickedKey = (): Keyframe | null =>\n picked ? (lab.keys(picked.camera).find((key) => key.id === picked!.key) ?? null) : null;\n\n const paintInspector = () => {\n const key = pickedKey();\n\n inspector.hidden = !key;\n\n if (!key || !picked) {\n ease.set(null, false);\n\n return;\n }\n\n const keys = lab.keys(picked.camera);\n\n ease.set(key, keys.indexOf(key) < keys.length - 1);\n inspectorTitle.textContent = `${picked.camera} key`;\n\n if (document.activeElement !== keyTime) {\n keyTime.value = seconds(key.time);\n }\n\n keyEase.value = key.ease;\n };\n\n /** Lanes and keys, from the lab. Skipped mid-drag so the dragged key isn't rebuilt under the pointer. */\n const render = () => {\n if (dragging || ease.dragging) {\n return;\n }\n\n const state = lab.timeline();\n const entries = tracked();\n const aim = target(entries);\n\n paintRuler(state.duration);\n\n if (document.activeElement !== lengthInput) {\n lengthInput.value = String(Math.round(state.duration * 100) / 100);\n }\n\n loopButton.setAttribute(\"aria-pressed\", String(state.loop));\n addKey.disabled = !aim;\n addKeyLabel.textContent = aim ? `Key ${aim.id}` : \"Key\";\n addKey.title = aim ? `Key ${aim.id} as it is now, at the playhead` : \"No camera to key\";\n\n element.classList.toggle(\"is-graph\", mode === \"graph\");\n\n for (const button of element.querySelectorAll<HTMLElement>('[data-action=\"mode\"]')) {\n button.setAttribute(\"aria-checked\", String(button.dataset.mode === mode));\n }\n\n const laneEntries = sidebar ? lab.entries() : entries;\n\n if (!sidebar) {\n names.querySelectorAll(\".tcm-tl-row\").forEach((node) => node.remove());\n names.insertBefore(\n document.createRange().createContextualFragment(\n entries\n .map(\n (entry) => `<div class=\"tcm-tl-row\">\n <button type=\"button\" class=\"tcm-tl-name${entry.selected ? \" is-selected\" : \"\"}\" data-camera=\"${escape(entry.id)}\" title=\"Pick ${escape(entry.id)}\">${escape(entry.id)}</button>\n <button type=\"button\" class=\"tcm-icon tcm-trail\" data-action=\"trail\" data-camera=\"${escape(entry.id)}\" aria-pressed=\"${entry.trail}\" title=\"${entry.trail ? \"Hide\" : \"Show\"} its motion path in the scene\" aria-label=\"${entry.trail ? \"Hide\" : \"Show\"} ${escape(entry.id)}'s motion path\"${entry.keys === 0 ? \" disabled\" : \"\"}>${svg(ICONS.trail)}</button>\n </div>`,\n )\n .join(\"\"),\n ),\n graph.side,\n );\n }\n\n lanes.innerHTML = laneEntries\n .map((entry) => {\n const keys = lab\n .keys(entry.id)\n .map((key) => {\n const isPicked = picked?.camera === entry.id && picked.key === key.id;\n\n return `<button type=\"button\" class=\"tcm-key${isPicked ? \" is-picked\" : \"\"}${key.ease === \"hold\" ? \" is-hold\" : \"\"}\" data-key=\"${escape(key.id)}\" style=\"left:${percent(key.time)}\" title=\"${seconds(key.time)} s · ${EASE_LABELS[key.ease]}\" aria-label=\"${escape(entry.id)} key at ${seconds(key.time)} s\"></button>`;\n })\n .join(\"\");\n\n return `<div class=\"tcm-lane${entry.selected ? \" is-selected\" : \"\"}${entry.projects ? \"\" : \" is-inert\"}\" data-camera=\"${escape(entry.id)}\"${entry.projects ? ` title=\"Double-click to key ${escape(entry.id)} here\"` : \"\"}>${keys}</div>`;\n })\n .join(\"\");\n\n if (picked && !pickedKey()) {\n picked = null;\n }\n\n empty.hidden = mode === \"graph\" || entries.some((entry) => entry.keys > 0);\n paintInspector();\n paintClock();\n\n if (mode === \"graph\") {\n graph.paint();\n }\n\n syncLanes();\n };\n\n /** With a sidebar: each lane at its camera row's height and place, keys on the row's header. */\n const syncLanes = () => {\n if (!sidebar || mode === \"graph\") {\n return;\n }\n\n const origin = lanes.getBoundingClientRect().top;\n let bottom = 0;\n\n for (const lane of lanes.querySelectorAll<HTMLElement>(\".tcm-lane\")) {\n const row = sidebar.row(lane.dataset.camera ?? \"\");\n\n if (!row || row.offsetParent === null || row.classList.contains(\"is-hidden\")) {\n lane.hidden = true;\n continue;\n }\n\n const box = row.getBoundingClientRect();\n const head = row.querySelector(\".tcm-head\")?.getBoundingClientRect().height ?? box.height;\n\n lane.hidden = false;\n lane.style.top = `${box.top - origin}px`;\n lane.style.height = `${box.height}px`;\n lane.style.setProperty(\"--tcm-head\", `${head}px`);\n bottom = Math.max(bottom, box.bottom - origin);\n }\n\n lanes.style.height = `${bottom}px`;\n };\n\n // Scrub on the ruler: press and drag.\n ruler.addEventListener(\"pointerdown\", (event) => {\n ruler.setPointerCapture(event.pointerId);\n lab.seek(timeAt(event.clientX));\n\n const onMove = (move: PointerEvent) => lab.seek(timeAt(move.clientX));\n const onUp = () => {\n ruler.removeEventListener(\"pointermove\", onMove);\n ruler.removeEventListener(\"pointerup\", onUp);\n ruler.removeEventListener(\"pointercancel\", onUp);\n };\n\n ruler.addEventListener(\"pointermove\", onMove);\n ruler.addEventListener(\"pointerup\", onUp);\n ruler.addEventListener(\"pointercancel\", onUp);\n });\n\n // Keys: press to pick, drag to retime; a click also moves the playhead there.\n lanes.addEventListener(\"pointerdown\", (event) => {\n const keyButton = (event.target as HTMLElement).closest<HTMLElement>(\".tcm-key\");\n const lane = (event.target as HTMLElement).closest<HTMLElement>(\".tcm-lane\");\n const camera = lane?.dataset.camera;\n\n if (!lane || !camera) {\n return;\n }\n\n if (!keyButton) {\n lab.select(camera);\n lab.seek(timeAt(event.clientX));\n\n return;\n }\n\n const keyId = keyButton.dataset.key!;\n const key = lab.keys(camera).find((other) => other.id === keyId);\n\n if (!key) {\n return;\n }\n\n // Hold rebuilds until the press ends, so the key under the pointer stays put.\n dragging = true;\n event.preventDefault();\n picked = { camera, key: keyId };\n lab.select(camera);\n keyButton.setPointerCapture(event.pointerId);\n\n const startX = event.clientX;\n const box = lanes.getBoundingClientRect();\n const duration = lab.timeline().duration;\n let moved = false;\n let time = key.time;\n\n const onMove = (move: PointerEvent) => {\n if (!moved && Math.abs(move.clientX - startX) < DRAG_PX) {\n return;\n }\n\n moved = true;\n\n const raw = key.time + ((move.clientX - startX) / Math.max(box.width, 1)) * duration;\n const snapped = move.shiftKey ? raw : Math.round(raw / SNAP) * SNAP;\n\n time = Math.min(duration, Math.max(0, snapped));\n keyButton.style.left = percent(time);\n keyButton.title = `${seconds(time)} s`;\n };\n const onUp = () => {\n keyButton.removeEventListener(\"pointermove\", onMove);\n keyButton.removeEventListener(\"pointerup\", onUp);\n keyButton.removeEventListener(\"pointercancel\", onUp);\n dragging = false;\n\n if (moved) {\n attempt(() => lab.updateKey(camera, keyId, { time }));\n } else {\n lab.seek(key.time);\n }\n\n render();\n };\n\n keyButton.addEventListener(\"pointermove\", onMove);\n keyButton.addEventListener(\"pointerup\", onUp);\n keyButton.addEventListener(\"pointercancel\", onUp);\n });\n\n lanes.addEventListener(\"dblclick\", (event) => {\n const lane = (event.target as HTMLElement).closest<HTMLElement>(\".tcm-lane\");\n const camera = lane?.dataset.camera;\n\n if (camera && !(event.target as HTMLElement).closest(\".tcm-key\")) {\n attempt(() => {\n const key = lab.addKey(camera, { time: Math.round(timeAt(event.clientX) / SNAP) * SNAP });\n\n picked = { camera, key: key.id };\n });\n }\n });\n\n names.addEventListener(\"click\", (event) => {\n const camera = (event.target as HTMLElement).closest<HTMLElement>(\".tcm-tl-name\")?.dataset\n .camera;\n\n if (camera) {\n lab.select(camera);\n }\n });\n\n element.addEventListener(\"click\", (event) => {\n const action = (event.target as HTMLElement).closest<HTMLElement>(\"[data-action]\")?.dataset\n .action;\n const state = lab.timeline();\n\n if (action === \"start\") {\n lab.seek(0);\n } else if (action === \"play\") {\n if (state.playing) {\n lab.pause();\n } else {\n lab.play();\n }\n } else if (action === \"stop\") {\n lab.stop();\n } else if (action === \"loop\") {\n lab.setLoop(!state.loop);\n } else if (action === \"mode\") {\n const next = (event.target as HTMLElement).closest<HTMLElement>(\"[data-mode]\")?.dataset.mode;\n\n mode = next === \"graph\" ? \"graph\" : \"keys\";\n render();\n } else if (action === \"trail\") {\n const camera = (event.target as HTMLElement).closest<HTMLElement>(\"[data-camera]\")?.dataset\n .camera;\n const entry = camera ? lab.entry(camera) : null;\n\n if (camera && entry) {\n attempt(() => lab.setTrail(camera, !entry.trail));\n }\n } else if (action === \"add-key\") {\n const aim = target(tracked());\n\n if (aim) {\n attempt(() => {\n const key = lab.addKey(aim.id);\n\n picked = { camera: aim.id, key: key.id };\n });\n }\n } else if (action === \"key-update\" && picked) {\n const { camera, key } = picked;\n\n attempt(() => lab.updateKey(camera, key, { recapture: true }));\n } else if (action === \"key-delete\" && picked) {\n const { camera, key } = picked;\n\n picked = null;\n attempt(() => lab.deleteKey(camera, key));\n }\n });\n\n lengthInput.addEventListener(\"change\", () => {\n const value = Number(lengthInput.value);\n\n if (Number.isFinite(value) && value > 0) {\n lab.setDuration(value);\n }\n });\n\n keyTime.addEventListener(\"change\", () => {\n const value = Number(keyTime.value);\n\n if (picked && Number.isFinite(value) && value >= 0) {\n const { camera, key } = picked;\n\n attempt(() => lab.updateKey(camera, key, { time: value }));\n }\n });\n\n keyEase.addEventListener(\"change\", () => {\n if (picked) {\n const { camera, key } = picked;\n\n attempt(() => lab.updateKey(camera, key, { ease: keyEase.value as Ease }));\n }\n });\n\n element.addEventListener(\"keydown\", (event) => {\n const typing = (event.target as HTMLElement).closest(\"input, select, textarea\");\n\n if (typing) {\n return;\n }\n\n if (event.key === \" \") {\n event.preventDefault();\n\n if (lab.timeline().playing) {\n lab.pause();\n } else {\n lab.play();\n }\n } else if ((event.key === \"Delete\" || event.key === \"Backspace\") && picked) {\n const { camera, key } = picked;\n\n picked = null;\n attempt(() => lab.deleteKey(camera, key));\n }\n });\n\n // Clicking empty space in the panel gives it focus, so Space and Delete reach it.\n area.addEventListener(\"pointerdown\", () => element.focus({ preventScroll: true }));\n\n const unsubscribe = lab.subscribe(render);\n const unsubscribeSidebar = sidebar?.onRender(syncLanes);\n const resize =\n sidebar && typeof ResizeObserver !== \"undefined\" ? new ResizeObserver(syncLanes) : null;\n\n if (sidebar) {\n resize?.observe(sidebar.element);\n }\n\n render();\n\n return {\n element,\n setTheme: (theme) => {\n if (element.closest(\".perf-hud__card\")) {\n delete element.dataset.theme;\n } else {\n element.dataset.theme = theme;\n }\n },\n dispose: () => {\n cancelAnimationFrame(frame);\n graph.dispose();\n resize?.disconnect();\n unsubscribeSidebar?.();\n unsubscribe();\n element.remove();\n },\n };\n};\n","/**\n * The panel in the zkmake dev-panel frame (three-meter's `mountDevPanel`), so it docks, wakes,\n * dims and toggles exactly like the three-meter HUD: drag grip, expand/compact and dim-on-leave\n * discs beside a card with the brand label on top. One call for a vanilla three app.\n *\n * With the timeline (the default) it's one full-width panel anchored to the bottom of the screen:\n * the camera list as the timeline's left sidebar, each camera's row beside its lane. Without the\n * timeline, it's the camera list, docked and draggable like the other dev panels. Collapsed, either\n * is a small widget with the picked camera (else the one looked through, else a live one); a click\n * on it expands the panel again.\n */\nimport {\n type DefaultPlacement,\n HudTheme,\n mountDevPanel,\n type ThemeMode,\n} from \"@zkmake/three-meter/ui\";\n\nimport {\n CameraLab,\n type CameraLabOptions,\n type SavedView,\n type TrackStore,\n type ViewStore,\n} from \"../core/lab.ts\";\nimport { createCameraPanel, KIND_ICONS, svg } from \"./panel.ts\";\nimport { createPathEditor } from \"./path-editor.ts\";\nimport { createTimelinePanel } from \"./timeline.ts\";\n\nexport type MountCameraPanelOptions = CameraLabOptions & {\n /** localStorage key prefix for the dock, compact state, saved views and tracks. `null` keeps them for this page only. Default `three-cameras`. */\n storageKey?: string | null;\n /** Where the camera list docks on a first visit, without the timeline. Default the right edge, top. With the timeline, the panel spans the bottom of the screen. */\n defaultPlacement?: DefaultPlacement;\n /** The keyframe timeline, with the camera list as its sidebar: `false` for the list alone. Default on. */\n timeline?: boolean;\n /** `dark`, `light` or `system` (default). */\n theme?: ThemeMode;\n /** Start compact: the brand row and the count. The choice is remembered. */\n compact?: boolean;\n /** Where to mount. Default `document.body`. */\n container?: HTMLElement;\n};\n\nexport type CameraPanelHandle = {\n lab: CameraLab;\n element: HTMLElement;\n setCompact(compact: boolean): void;\n /** `dark`, `light` or `system`: the panels follow it. */\n setTheme(mode: ThemeMode): void;\n dispose(): void;\n};\n\nconst readFlag = (key: string) => {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n};\n\nconst writeFlag = (key: string, value: string) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Private mode: the choice lasts this page.\n }\n};\n\n/** A JSON value in localStorage under one key, with a fallback when it's missing or unreadable. */\nconst localJson = <T>(key: string, fallback: T, valid: (value: unknown) => boolean) => ({\n load: (): T => {\n try {\n const parsed: unknown = JSON.parse(localStorage.getItem(key) ?? \"null\");\n\n return valid(parsed) ? (parsed as T) : fallback;\n } catch {\n return fallback;\n }\n },\n save: (value: T) => {\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n // Private mode or full: it lasts this page.\n }\n },\n});\n\nconst isObject = (value: unknown) => value !== null && typeof value === \"object\";\n\nexport const mountCameraPanel = (options: MountCameraPanelOptions): CameraPanelHandle => {\n const key = options.storageKey === null ? null : (options.storageKey ?? \"three-cameras\");\n const store: ViewStore | undefined =\n options.store ??\n (key ? localJson<Record<string, SavedView[]>>(`${key}:views`, {}, isObject) : undefined);\n const trackStore: TrackStore | undefined =\n options.trackStore ??\n (key\n ? localJson<ReturnType<TrackStore[\"load\"]>>(\n `${key}:tracks`,\n { tracks: {} },\n (value) => isObject(value) && isObject((value as { tracks?: unknown }).tracks),\n )\n : undefined);\n const lab = new CameraLab({\n ...options,\n ...(store ? { store } : {}),\n ...(trackStore ? { trackStore } : {}),\n });\n const theme = new HudTheme(options.theme ?? \"system\");\n const withTimeline = options.timeline !== false;\n const panel = createCameraPanel(lab, { paths: withTimeline });\n const timeline = withTimeline ? createTimelinePanel(lab, { sidebar: panel }) : null;\n const content = document.createElement(\"div\");\n const mini = document.createElement(\"button\");\n\n content.className = \"tcm-shell\";\n mini.type = \"button\";\n mini.className = \"tcm tcm-mini\";\n content.append(mini, timeline?.element ?? panel.element);\n\n const compactKey = key ? `${key}:compact` : null;\n let count = Number(panel.element.dataset.count ?? 0);\n let clock = timeline\n ? `${lab.timeline().time.toFixed(2)} / ${lab.timeline().duration.toFixed(2)} s`\n : \"\";\n\n const paintDetail = () => {\n const cameras = `${count} camera${count === 1 ? \"\" : \"s\"}`;\n\n frame.detail.textContent = clock ? `${cameras} · ${clock}` : cameras;\n };\n\n const escape = (text: string) => text.replace(/[&<>\"]/g, (char) => `&#${char.charCodeAt(0)};`);\n\n /** The collapsed widget: the picked camera, else the one looked through, else a live one. */\n const paintMini = () => {\n if (!frame.element.classList.contains(\"tcm-compact\")) {\n return;\n }\n\n const entries = lab.entries();\n const shown =\n entries.find((entry) => entry.selected) ??\n entries.find((entry) => entry.viewing) ??\n entries.find((entry) => entry.live) ??\n entries[0];\n const state = lab.timeline();\n\n mini.classList.toggle(\"is-live\", Boolean(shown?.live));\n mini.classList.toggle(\"is-viewing\", Boolean(shown?.viewing));\n mini.title = shown ? `${shown.id}: expand the panel` : \"Expand the panel\";\n mini.setAttribute(\"aria-label\", mini.title);\n mini.innerHTML = shown\n ? `<span class=\"tcm-mini-kind\">${svg(KIND_ICONS[shown.kind])}</span><span class=\"tcm-mini-name\">${escape(shown.id)}</span><span class=\"tcm-badge${shown.viewing ? \" is-viewing\" : shown.live ? \" is-live\" : \"\"}\">${shown.viewing ? \"viewing\" : shown.live ? `${shown.fps} fps` : \"\"}</span>${timeline && state.playing ? `<span class=\"tcm-mini-clock\">${state.time.toFixed(1)} s</span>` : \"\"}`\n : `<span class=\"tcm-mini-name\">no cameras yet</span>`;\n };\n\n let miniTimer: ReturnType<typeof setInterval> | null = null;\n\n const setCompact = (compact: boolean) => {\n frame.element.classList.toggle(\"tcm-compact\", compact);\n // Collapsed, the list stops its own ticks; the widget keeps a slower one for its badge.\n panel.setActive(!compact);\n\n if (compact && !miniTimer) {\n miniTimer = setInterval(paintMini, 500);\n } else if (!compact && miniTimer) {\n clearInterval(miniTimer);\n miniTimer = null;\n }\n\n paintMini();\n\n if (compactKey) {\n writeFlag(compactKey, compact ? \"1\" : \"0\");\n }\n\n frame.refresh();\n };\n\n const frame = mountDevPanel({\n brand: \"three-cameras\",\n content,\n // With the timeline: across the bottom, and it stays there (styles.ts hides the drag grip).\n defaultPlacement: timeline\n ? { edge: \"bottom\", align: \"start\" }\n : (options.defaultPlacement ?? { edge: \"right\", align: \"start\" }),\n label: \"Cameras\",\n onToggle: () => setCompact(!frame.element.classList.contains(\"tcm-compact\")),\n storageKey: key ? `${key}:${timeline ? \"dock\" : \"panel\"}` : null,\n theme,\n toggleLabel: \"Collapse or expand the panel\",\n ...(options.container ? { parent: options.container } : {}),\n });\n\n frame.element.classList.add(\"tcm-frame\");\n\n if (timeline) {\n frame.element.classList.add(\"tcm-combined-frame\");\n }\n\n mini.addEventListener(\"click\", () => setCompact(false));\n\n // Key dots on motion paths are draggable in the scene, on the renderer's canvas.\n const canvas = (options.renderer as { domElement?: unknown } | undefined)?.domElement;\n const paths =\n timeline && typeof HTMLCanvasElement !== \"undefined\" && canvas instanceof HTMLCanvasElement\n ? createPathEditor(lab, canvas)\n : null;\n\n const unsubscribeMini = lab.subscribe(paintMini);\n const onCount = (event: Event) => {\n count = Number((event as CustomEvent).detail);\n paintDetail();\n };\n const onClock = (event: Event) => {\n clock = String((event as CustomEvent).detail);\n paintDetail();\n };\n\n panel.element.addEventListener(\"tcm-count\", onCount);\n timeline?.element.addEventListener(\"tcm-clock\", onClock);\n paintDetail();\n\n const stored = compactKey ? readFlag(compactKey) : null;\n\n setCompact(stored === null ? options.compact === true : stored === \"1\");\n\n const paintTheme = () => {\n panel.setTheme(theme.resolved);\n timeline?.setTheme(theme.resolved);\n };\n const unsubscribeTheme = theme.subscribe(paintTheme);\n\n paintTheme();\n\n return {\n lab,\n element: frame.element,\n setCompact,\n setTheme: (mode) => theme.setMode(mode),\n dispose: () => {\n if (miniTimer) {\n clearInterval(miniTimer);\n }\n\n unsubscribeMini();\n paths?.dispose();\n unsubscribeTheme();\n theme.dispose();\n panel.element.removeEventListener(\"tcm-count\", onCount);\n timeline?.element.removeEventListener(\"tcm-clock\", onClock);\n panel.dispose();\n timeline?.dispose();\n frame.dispose();\n lab.dispose();\n },\n };\n};\n"],"mappings":"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"}
@@ -1,11 +1,13 @@
1
- import { a as CameraLabOptions, i as CameraLab } from "./lab-AaEFBp_R.js";
1
+ import { a as CameraLabOptions, i as CameraLab } from "./lab-DuEV-i3c.js";
2
2
  import { DefaultPlacement, ThemeMode } from "@zkmake/three-meter/ui";
3
3
  //#region src/ui/mount.d.ts
4
4
  type MountCameraPanelOptions = CameraLabOptions & {
5
- /** localStorage key prefix for the dock, compact state and saved views. `null` keeps them for this page only. Default `three-cameras`. */
5
+ /** localStorage key prefix for the dock, compact state, saved views and tracks. `null` keeps them for this page only. Default `three-cameras`. */
6
6
  storageKey?: string | null;
7
- /** Where it docks on a first visit. Default the right edge, top. */
7
+ /** Where the camera list docks on a first visit, without the timeline. Default the right edge, top. With the timeline, the panel spans the bottom of the screen. */
8
8
  defaultPlacement?: DefaultPlacement;
9
+ /** The keyframe timeline, with the camera list as its sidebar: `false` for the list alone. Default on. */
10
+ timeline?: boolean;
9
11
  /** `dark`, `light` or `system` (default). */
10
12
  theme?: ThemeMode;
11
13
  /** Start compact: the brand row and the count. The choice is remembered. */
@@ -17,11 +19,11 @@ type CameraPanelHandle = {
17
19
  lab: CameraLab;
18
20
  element: HTMLElement;
19
21
  setCompact(compact: boolean): void;
20
- /** `dark`, `light` or `system`: the panel follows it. */
22
+ /** `dark`, `light` or `system`: the panels follow it. */
21
23
  setTheme(mode: ThemeMode): void;
22
24
  dispose(): void;
23
25
  };
24
26
  declare const mountCameraPanel: (options: MountCameraPanelOptions) => CameraPanelHandle;
25
27
  //#endregion
26
28
  export { MountCameraPanelOptions as n, mountCameraPanel as r, CameraPanelHandle as t };
27
- //# sourceMappingURL=mount-Gf7I61p2.d.ts.map
29
+ //# sourceMappingURL=mount-DPlwtFv9.d.ts.map
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { i as CameraLab } from "./lab-AaEFBp_R.js";
2
- import { n as MountCameraPanelOptions } from "./mount-Gf7I61p2.js";
1
+ import { i as CameraLab } from "./lab-DuEV-i3c.js";
2
+ import { n as MountCameraPanelOptions } from "./mount-DPlwtFv9.js";
3
3
  //#region src/react/index.d.ts
4
4
  export type CameraPanelProps = Omit<MountCameraPanelOptions, "scene" | "renderer" | "invalidate"> & {
5
5
  /** The lab, once mounted: for listing cameras or toggling helpers from code. */
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as mountCameraPanel } from "./mount-DBjcu7if.js";
1
+ import { t as mountCameraPanel } from "./mount-BAkbs7K2.js";
2
2
  import { useThree } from "@react-three/fiber";
3
3
  import { useEffect, useRef } from "react";
4
4
  //#region src/react/index.tsx
package/dist/ui.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { i as CameraLab } from "./lab-AaEFBp_R.js";
2
- import { n as MountCameraPanelOptions, r as mountCameraPanel, t as CameraPanelHandle } from "./mount-Gf7I61p2.js";
1
+ import { i as CameraLab } from "./lab-DuEV-i3c.js";
2
+ import { n as MountCameraPanelOptions, r as mountCameraPanel, t as CameraPanelHandle } from "./mount-DPlwtFv9.js";
3
+ import { Vector3 } from "three";
3
4
  //#region src/ui/panel.d.ts
4
5
  type CameraPanel = {
5
6
  element: HTMLElement;
@@ -9,15 +10,49 @@ type CameraPanel = {
9
10
  refresh(): void;
10
11
  /** Pause or resume the live updates, e.g. while a host tab is hidden. */
11
12
  setActive(active: boolean): void;
13
+ /** A camera's row, for a host that lines things up with it (the timeline's lanes). */
14
+ row(id: string): HTMLElement | null;
15
+ /** Hear each redraw of the rows. */
16
+ onRender(listener: () => void): () => void;
12
17
  dispose(): void;
13
18
  };
14
- export declare const createCameraPanel: (lab: CameraLab) => CameraPanel;
19
+ type CameraPanelOptions = {
20
+ /** Put a motion-path toggle on each row, for when the rows are a timeline's sidebar. */
21
+ paths?: boolean;
22
+ };
23
+ export declare const createCameraPanel: (lab: CameraLab, { paths }?: CameraPanelOptions) => CameraPanel;
15
24
  //#endregion
16
25
  //#region src/ui/styles.d.ts
17
26
  /** The panel's stylesheet, injected once. Palettes by `data-theme` on `.tcm`. */
18
- export declare const CAMERA_PANEL_STYLES = "\n/* Inside a dev-panel card the tokens come from three-meter's (`--perf-*`), so the panels match;\n on its own (in a host's tab) the same palette is the fallback. */\n.tcm {\n --tcm-bg: var(--perf-bg, rgba(22, 24, 29, 0.92));\n --tcm-fg: var(--perf-fg, #e6e8eb);\n --tcm-dim: var(--perf-muted, #8b909a);\n --tcm-line: var(--perf-border, rgba(255, 255, 255, 0.12));\n --tcm-field: var(--perf-row, rgba(255, 255, 255, 0.04));\n --tcm-hover: var(--perf-row, rgba(255, 255, 255, 0.04));\n --tcm-accent: var(--perf-accent, #60a5fa);\n --tcm-accent-soft: rgba(96, 165, 250, 0.14);\n --tcm-live: #34d399;\n box-sizing: border-box;\n color: var(--tcm-fg);\n font: 11px/1.35 ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n}\n.tcm[data-theme=\"light\"] {\n --tcm-bg: rgba(250, 250, 252, 0.94);\n --tcm-fg: #1a1c21;\n --tcm-dim: #6b7079;\n --tcm-line: rgba(0, 0, 0, 0.12);\n --tcm-field: rgba(0, 0, 0, 0.05);\n --tcm-hover: rgba(0, 0, 0, 0.05);\n --tcm-accent: #2563eb;\n --tcm-accent-soft: rgba(37, 99, 235, 0.1);\n --tcm-live: #047857;\n}\n.tcm *, .tcm *::before, .tcm *::after { box-sizing: inherit; }\n.tcm button { font: inherit; color: inherit; }\n\n.tcm-panel { display: flex; flex-direction: column; gap: 6px; min-height: 0; padding: 8px; }\n/* In the dev-panel card: a fixed width (the frame's cap raised to fit), the list scrolling\n inside the card's height. */\n.perf-hud.tcm-frame { max-width: min(23rem, calc(100vw - 48px)); }\n.perf-hud__card > .tcm-panel { width: 22rem; max-width: 100%; flex: 1; }\n/* Compact: the brand row alone, with the count. */\n.perf-hud.tcm-compact .tcm-panel { display: none; }\n.perf-hud.tcm-compact .perf-hud__brand { padding-bottom: 6px; }\n\n.tcm-toolbar { display: flex; gap: 6px; }\n.tcm-search {\n flex: 1;\n min-width: 0;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n padding: 4px 6px;\n color: inherit;\n font: inherit;\n}\n.tcm-search::placeholder { color: var(--tcm-dim); }\n.tcm-search:focus { outline: 1px solid var(--tcm-accent); outline-offset: 0; }\n\n.tcm-icon {\n display: inline-grid;\n place-items: center;\n width: 26px;\n height: 26px;\n flex: none;\n padding: 0;\n border: 1px solid transparent;\n border-radius: 5px;\n background: transparent;\n color: var(--tcm-dim);\n cursor: pointer;\n}\n.tcm-icon:hover:not(:disabled) { background: var(--tcm-hover); color: var(--tcm-fg); }\n.tcm-icon:focus-visible { outline: 1px solid var(--tcm-accent); }\n.tcm-icon:disabled { visibility: hidden; }\n.tcm-icon[aria-pressed=\"true\"] { color: var(--tcm-accent); background: var(--tcm-accent-soft); }\n.tcm svg {\n width: 16px;\n height: 16px;\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n.tcm-list { margin: 0; padding: 0; list-style: none; overflow-y: auto; min-height: 0; }\n.tcm-row { border-radius: 5px; border: 1px solid transparent; }\n.tcm-row.is-hidden { display: none; }\n.tcm-row.is-open { border-color: var(--tcm-line); background: var(--tcm-field); }\n.tcm-head {\n display: grid;\n grid-template-columns: 32px 1fr auto;\n align-items: center;\n gap: 8px;\n padding: 4px;\n border-radius: 5px;\n}\n.tcm-row:not(.is-open) .tcm-head:hover { background: var(--tcm-hover); }\n.tcm-kind {\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n border: 1px solid var(--tcm-line);\n border-radius: 5px;\n color: var(--tcm-dim);\n}\n.tcm-row.is-live .tcm-kind { color: var(--tcm-live); border-color: color-mix(in srgb, var(--tcm-live) 45%, transparent); }\n.tcm-text {\n min-width: 0;\n padding: 0;\n border: 0;\n background: none;\n text-align: left;\n cursor: pointer;\n}\n.tcm-name-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }\n.tcm-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }\n.tcm-badge { flex: none; color: var(--tcm-dim); }\n.tcm-badge:empty { display: none; }\n.tcm-badge.is-live, .tcm-badge.is-viewing { display: inline-flex; align-items: center; gap: 4px; }\n.tcm-badge.is-live { color: var(--tcm-live); }\n.tcm-badge.is-viewing { color: var(--tcm-accent); }\n.tcm-badge.is-live::before {\n content: \"\";\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentColor;\n animation: tcm-pulse 1.6s ease-in-out infinite;\n}\n@keyframes tcm-pulse { 50% { opacity: 0.25; } }\n@media (prefers-reduced-motion: reduce) { .tcm-badge.is-live::before { animation: none; } }\n.tcm-row.is-viewing .tcm-kind { color: var(--tcm-accent); border-color: color-mix(in srgb, var(--tcm-accent) 50%, transparent); }\n.tcm-meta { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--tcm-dim); }\n.tcm-actions { display: flex; gap: 2px; }\n.tcm-chevron svg { transition: rotate 120ms; }\n.tcm-row.is-open .tcm-chevron svg { rotate: 180deg; }\n\n.tcm-details {\n display: none;\n gap: 8px;\n padding: 2px 8px 8px;\n}\n.tcm-row.is-open .tcm-details { display: grid; }\n.tcm-group { display: grid; gap: 3px; }\n.tcm-term { color: var(--tcm-dim); }\n.tcm-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; }\n.tcm-fields--lens { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n.tcm-num {\n display: flex;\n align-items: center;\n min-width: 0;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n}\n.tcm-num:focus-within { border-color: var(--tcm-accent); }\n.tcm-scrub {\n flex: none;\n padding: 0 5px;\n color: var(--tcm-dim);\n cursor: ew-resize;\n user-select: none;\n touch-action: none;\n}\n.tcm-scrub:hover { color: var(--tcm-fg); }\n.tcm-num input {\n width: 100%;\n min-width: 0;\n padding: 3px 5px 3px 0;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n font-variant-numeric: tabular-nums;\n text-align: right;\n -moz-appearance: textfield;\n appearance: textfield;\n}\n.tcm-num input::-webkit-inner-spin-button, .tcm-num input::-webkit-outer-spin-button { appearance: none; margin: 0; }\n.tcm-num input:focus { outline: none; }\n.tcm-readout { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 0; }\n.tcm-readout dt { color: var(--tcm-dim); }\n.tcm-readout dd { margin: 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }\n.tcm-buttons { display: flex; gap: 6px; }\n.tcm-text-button {\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n padding: 3px 8px;\n cursor: pointer;\n white-space: nowrap;\n}\n.tcm-text-button:hover { border-color: var(--tcm-accent); }\n.tcm-text-button:focus-visible { outline: 1px solid var(--tcm-accent); }\n.tcm-views { display: flex; flex-wrap: wrap; gap: 4px; margin: 0; padding: 0; list-style: none; }\n.tcm-views:empty { display: none; }\n.tcm-view { display: flex; align-items: center; border: 1px solid var(--tcm-line); border-radius: 4px; }\n.tcm-view .tcm-icon { width: 20px; height: 20px; }\n.tcm-view .tcm-icon svg { width: 12px; height: 12px; }\n.tcm-view-go {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 4px 2px 6px;\n border: 0;\n background: none;\n cursor: pointer;\n}\n.tcm-view-go svg { width: 12px; height: 12px; }\n.tcm-view-go:hover { color: var(--tcm-accent); }\n.tcm-banner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 4px 4px 8px;\n border-radius: 5px;\n background: var(--tcm-accent-soft);\n color: var(--tcm-accent);\n}\n.tcm-banner[hidden] { display: none; }\n.tcm-status { color: var(--tcm-dim); padding: 0 4px; }\n.tcm-status:empty { display: none; }\n.tcm-empty { color: var(--tcm-dim); padding: 6px 4px; }\n";
27
+ export declare const CAMERA_PANEL_STYLES = "\n/* Inside a dev-panel card the tokens come from three-meter's (`--perf-*`), so the panels match;\n on its own (in a host's tab) the same palette is the fallback. */\n.tcm {\n --tcm-bg: var(--perf-bg, rgba(22, 24, 29, 0.92));\n --tcm-fg: var(--perf-fg, #e6e8eb);\n --tcm-dim: var(--perf-muted, #8b909a);\n --tcm-line: var(--perf-border, rgba(255, 255, 255, 0.12));\n --tcm-field: var(--perf-row, rgba(255, 255, 255, 0.04));\n --tcm-hover: var(--perf-row, rgba(255, 255, 255, 0.04));\n --tcm-accent: var(--perf-accent, #60a5fa);\n --tcm-accent-soft: rgba(96, 165, 250, 0.14);\n --tcm-live: #34d399;\n box-sizing: border-box;\n color: var(--tcm-fg);\n font: 11px/1.35 ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n}\n.tcm[data-theme=\"light\"] {\n --tcm-bg: rgba(250, 250, 252, 0.94);\n --tcm-fg: #1a1c21;\n --tcm-dim: #6b7079;\n --tcm-line: rgba(0, 0, 0, 0.12);\n --tcm-field: rgba(0, 0, 0, 0.05);\n --tcm-hover: rgba(0, 0, 0, 0.05);\n --tcm-accent: #2563eb;\n --tcm-accent-soft: rgba(37, 99, 235, 0.1);\n --tcm-live: #047857;\n}\n.tcm *, .tcm *::before, .tcm *::after { box-sizing: inherit; }\n.tcm button { font: inherit; color: inherit; }\n\n.tcm-panel { display: flex; flex-direction: column; gap: 6px; min-height: 0; padding: 8px; }\n/* In the dev-panel card: a fixed width (the frame's cap raised to fit), the list scrolling\n inside the card's height. */\n.perf-hud.tcm-frame { max-width: min(23rem, calc(100vw - 48px)); }\n/* With the timeline: one panel across the bottom of the screen, anchored (no drag grip), the\n camera list as the timeline's sidebar. Compact, the list alone. */\n.perf-hud.tcm-combined-frame .perf-hud__drag { display: none; }\n/* Full width: the control discs go in a row on the open side, above the panel on the bottom\n edge and below it on the top, so the panel can reach both sides of the screen. */\n.perf-hud.tcm-combined-frame:not(.tcm-compact) { width: calc(100vw - 20px); max-width: none; }\n.perf-hud.tcm-combined-frame:not(.tcm-compact) .perf-hud__tools { left: auto; right: 4px; flex-direction: row; }\n.perf-hud.tcm-combined-frame:not(.tcm-compact)[data-edge=\"bottom\"] .perf-hud__tools { top: auto; bottom: calc(100% + 6px); }\n.perf-hud.tcm-combined-frame:not(.tcm-compact)[data-edge=\"top\"] .perf-hud__tools { top: calc(100% + 6px); }\n.perf-hud.tcm-combined-frame:not(.tcm-compact) .perf-hud__hotspot { right: -10px; left: -10px; }\n.perf-hud.tcm-combined-frame:not(.tcm-compact)[data-edge=\"bottom\"] .perf-hud__hotspot { top: -40px; bottom: -10px; }\n.perf-hud.tcm-combined-frame:not(.tcm-compact)[data-edge=\"top\"] .perf-hud__hotspot { top: -10px; bottom: -40px; }\n.tcm-shell { display: flex; flex-direction: column; min-height: 0; }\n.tcm-shell > .tcm-timeline.is-combined { width: 100%; max-height: min(28rem, 40vh); }\n.tcm-shell > .tcm-panel { width: 22rem; max-width: 100%; }\n.tcm-timeline.is-combined { min-height: 0; padding-bottom: 6px; }\n.tcm-timeline.is-combined .tcm-tl { flex: 1; min-height: 0; grid-template-columns: 21rem minmax(0, 1fr); overflow-y: auto; }\n.tcm-timeline.is-combined .tcm-tl-names { display: block; border-right: 1px solid var(--tcm-line); }\n.tcm-timeline.is-combined .tcm-tl-names > .tcm-panel { padding: 0 6px 0 0; }\n.tcm-timeline.is-combined .tcm-panel .tcm-list { overflow: visible; }\n.tcm-timeline.is-combined .tcm-panel .tcm-toolbar,\n.tcm-timeline.is-combined .tcm-ruler { position: sticky; top: 0; z-index: 2; height: 30px; background: var(--tcm-bg); }\n.tcm-timeline.is-combined .tcm-panel .tcm-toolbar { align-items: center; }\n.tcm-timeline.is-combined .tcm-tl-area { margin-left: 0; }\n.tcm-timeline.is-combined .tcm-lanes { position: relative; display: block; }\n.tcm-timeline.is-combined .tcm-lane { position: absolute; left: 0; right: 0; border-bottom: 0; border-top: 1px solid var(--tcm-line); }\n.tcm-timeline.is-combined .tcm-lane[hidden] { display: none; }\n.tcm-timeline.is-combined .tcm-lane.is-inert { cursor: default; background: repeating-linear-gradient(135deg, transparent 0 6px, var(--tcm-field) 6px 7px); }\n.tcm-timeline.is-combined .tcm-key { top: calc(var(--tcm-head, 24px) / 2); }\n.tcm-timeline.is-combined .tcm-graph-side { padding: 6px 0 4px; }\n\n/* Compact, the list alone: the brand row, with the count. */\n/* Collapsed: a small widget with the picked camera, and nothing else. */\n.perf-hud.tcm-compact .tcm-shell > :not(.tcm-mini) { display: none; }\n.perf-hud.tcm-frame.tcm-compact .perf-hud__brand-detail { display: none; }\n.tcm-mini { display: none; }\n.perf-hud.tcm-compact .tcm-mini {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 10rem;\n max-width: 18rem;\n margin: 0 6px 6px;\n padding: 3px 8px 3px 4px;\n border: 1px solid var(--tcm-line);\n border-radius: 5px;\n background: var(--tcm-field);\n text-align: left;\n cursor: pointer;\n}\n.tcm-mini:hover { border-color: var(--tcm-accent); }\n.tcm-mini:focus-visible { outline: 1px solid var(--tcm-accent); }\n.tcm-mini-kind { display: grid; place-items: center; color: var(--tcm-dim); }\n.tcm-mini.is-live .tcm-mini-kind { color: var(--tcm-live); }\n.tcm-mini.is-viewing .tcm-mini-kind { color: var(--tcm-accent); }\n.tcm-mini-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }\n.tcm-mini-clock { margin-left: auto; color: var(--tcm-dim); font-variant-numeric: tabular-nums; }\n.perf-hud.tcm-compact .perf-hud__brand { padding-bottom: 6px; }\n\n.tcm-toolbar { display: flex; gap: 6px; }\n.tcm-search {\n flex: 1;\n min-width: 0;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n padding: 4px 6px;\n color: inherit;\n font: inherit;\n}\n.tcm-search::placeholder { color: var(--tcm-dim); }\n.tcm-search:focus { outline: 1px solid var(--tcm-accent); outline-offset: 0; }\n\n.tcm-icon {\n display: inline-grid;\n place-items: center;\n width: 26px;\n height: 26px;\n flex: none;\n padding: 0;\n border: 1px solid transparent;\n border-radius: 5px;\n background: transparent;\n color: var(--tcm-dim);\n cursor: pointer;\n}\n.tcm-icon:hover:not(:disabled) { background: var(--tcm-hover); color: var(--tcm-fg); }\n.tcm-icon:focus-visible { outline: 1px solid var(--tcm-accent); }\n.tcm-icon:disabled { visibility: hidden; }\n.tcm-icon[aria-pressed=\"true\"] { color: var(--tcm-accent); background: var(--tcm-accent-soft); }\n/* Line icons; the graph and the ease editor draw their own. */\n.tcm svg:not(.tcm-graph-svg, .tcm-ease svg) {\n width: 16px;\n height: 16px;\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n.tcm-list { margin: 0; padding: 0; list-style: none; overflow-y: auto; min-height: 0; }\n.tcm-row { border-radius: 5px; border: 1px solid transparent; }\n.tcm-row.is-hidden { display: none; }\n.tcm-row.is-open { border-color: var(--tcm-line); background: var(--tcm-field); }\n.tcm-head {\n display: grid;\n grid-template-columns: 32px 1fr auto;\n align-items: center;\n gap: 8px;\n padding: 4px;\n border-radius: 5px;\n}\n.tcm-row:not(.is-open) .tcm-head:hover { background: var(--tcm-hover); }\n.tcm-kind {\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n border: 1px solid var(--tcm-line);\n border-radius: 5px;\n color: var(--tcm-dim);\n}\n.tcm-row.is-live .tcm-kind { color: var(--tcm-live); border-color: color-mix(in srgb, var(--tcm-live) 45%, transparent); }\n.tcm-text {\n min-width: 0;\n padding: 0;\n border: 0;\n background: none;\n text-align: left;\n cursor: pointer;\n}\n.tcm-name-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }\n.tcm-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }\n.tcm-badge { flex: none; color: var(--tcm-dim); }\n.tcm-badge:empty { display: none; }\n.tcm-badge.is-live, .tcm-badge.is-viewing { display: inline-flex; align-items: center; gap: 4px; }\n.tcm-badge.is-live { color: var(--tcm-live); }\n.tcm-badge.is-viewing { color: var(--tcm-accent); }\n.tcm-badge.is-live::before {\n content: \"\";\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentColor;\n animation: tcm-pulse 1.6s ease-in-out infinite;\n}\n@keyframes tcm-pulse { 50% { opacity: 0.25; } }\n@media (prefers-reduced-motion: reduce) { .tcm-badge.is-live::before { animation: none; } }\n.tcm-row.is-viewing .tcm-kind { color: var(--tcm-accent); border-color: color-mix(in srgb, var(--tcm-accent) 50%, transparent); }\n.tcm-meta { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--tcm-dim); }\n.tcm-actions { display: flex; gap: 2px; }\n.tcm-chevron svg { transition: rotate 120ms; }\n.tcm-row.is-open .tcm-chevron svg { rotate: 180deg; }\n\n.tcm-details {\n display: none;\n gap: 8px;\n padding: 2px 8px 8px;\n}\n.tcm-row.is-open .tcm-details { display: grid; }\n.tcm-group { display: grid; gap: 3px; }\n.tcm-term { color: var(--tcm-dim); }\n.tcm-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; }\n.tcm-fields--lens { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n.tcm-num {\n display: flex;\n align-items: center;\n min-width: 0;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n}\n.tcm-num:focus-within { border-color: var(--tcm-accent); }\n.tcm-scrub {\n flex: none;\n padding: 0 5px;\n color: var(--tcm-dim);\n cursor: ew-resize;\n user-select: none;\n touch-action: none;\n}\n.tcm-scrub:hover { color: var(--tcm-fg); }\n.tcm-num input {\n width: 100%;\n min-width: 0;\n padding: 3px 5px 3px 0;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n font-variant-numeric: tabular-nums;\n text-align: right;\n -moz-appearance: textfield;\n appearance: textfield;\n}\n.tcm-num input::-webkit-inner-spin-button, .tcm-num input::-webkit-outer-spin-button { appearance: none; margin: 0; }\n.tcm-num input:focus { outline: none; }\n.tcm-readout { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 0; }\n.tcm-readout dt { color: var(--tcm-dim); }\n.tcm-readout dd { margin: 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }\n.tcm-buttons { display: flex; gap: 6px; }\n.tcm-text-button {\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n padding: 3px 8px;\n cursor: pointer;\n white-space: nowrap;\n}\n.tcm-text-button:hover { border-color: var(--tcm-accent); }\n.tcm-text-button:focus-visible { outline: 1px solid var(--tcm-accent); }\n.tcm-views { display: flex; flex-wrap: wrap; gap: 4px; margin: 0; padding: 0; list-style: none; }\n.tcm-views:empty { display: none; }\n.tcm-view { display: flex; align-items: center; border: 1px solid var(--tcm-line); border-radius: 4px; }\n.tcm-view .tcm-icon { width: 20px; height: 20px; }\n.tcm-view .tcm-icon svg { width: 12px; height: 12px; }\n.tcm-view-go {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 4px 2px 6px;\n border: 0;\n background: none;\n cursor: pointer;\n}\n.tcm-view-go svg { width: 12px; height: 12px; }\n.tcm-view-go:hover { color: var(--tcm-accent); }\n.tcm-banner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 4px 4px 8px;\n border-radius: 5px;\n background: var(--tcm-accent-soft);\n color: var(--tcm-accent);\n}\n.tcm-banner[hidden] { display: none; }\n.tcm-status { color: var(--tcm-dim); padding: 0 4px; }\n.tcm-status:empty { display: none; }\n.tcm-empty { color: var(--tcm-dim); padding: 6px 4px; }\n.tcm-row.is-selected:not(.is-open) .tcm-head { background: var(--tcm-hover); }\n.tcm-keys { color: var(--tcm-accent); }\n\n/* Timeline */\n.tcm-timeline { display: flex; flex-direction: column; gap: 6px; padding: 8px; outline: none; }\n.tcm-transport, .tcm-inspector { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }\n.tcm-spacer { flex: 1; }\n.tcm-clock { min-width: 9.5em; padding: 0 6px; font-variant-numeric: tabular-nums; }\n.tcm-timeline.is-engaged .tcm-clock { color: var(--tcm-accent); }\n.tcm-length { display: inline-flex; align-items: center; gap: 4px; color: var(--tcm-dim); }\n.tcm-length input, .tcm-inspector select {\n width: 4.5em;\n padding: 2px 4px;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: var(--tcm-field);\n color: var(--tcm-fg);\n font: inherit;\n font-variant-numeric: tabular-nums;\n}\n.tcm-inspector select { width: auto; }\n.tcm-length input:focus, .tcm-inspector select:focus { outline: 1px solid var(--tcm-accent); }\n.tcm-add-key { display: inline-flex; align-items: center; gap: 5px; max-width: 14em; }\n.tcm-add-key span { overflow: hidden; text-overflow: ellipsis; }\n.tcm-add-key svg { width: 12px; height: 12px; color: var(--tcm-accent); }\n.tcm-add-key:disabled { opacity: 0.4; cursor: default; }\n.tcm-inspector { padding: 4px 6px; border-radius: 5px; background: var(--tcm-field); }\n.tcm-inspector[hidden] { display: none; }\n.tcm-inspector-title { font-weight: 600; margin-right: 4px; }\n\n.tcm-tl { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); }\n.tcm-tl-names { display: grid; grid-auto-rows: 24px; grid-template-rows: 20px; }\n.tcm-tl-corner { border-bottom: 1px solid var(--tcm-line); }\n.tcm-tl-name {\n overflow: hidden;\n padding: 0 8px 0 2px;\n border: 0;\n border-bottom: 1px solid var(--tcm-line);\n background: none;\n color: var(--tcm-dim);\n text-align: left;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: pointer;\n}\n.tcm-tl-name:hover, .tcm-tl-name.is-selected { color: var(--tcm-fg); }\n.tcm-tl-name.is-selected { font-weight: 600; }\n.tcm-tl-area { position: relative; min-width: 0; margin-right: 8px; }\n.tcm-ruler {\n position: relative;\n height: 20px;\n border-bottom: 1px solid var(--tcm-line);\n cursor: ew-resize;\n touch-action: none;\n}\n.tcm-tick { position: absolute; bottom: 0; width: 1px; height: 4px; background: var(--tcm-line); }\n.tcm-tick.is-major { height: 8px; background: var(--tcm-dim); }\n.tcm-tick b {\n position: absolute;\n bottom: 9px;\n left: 0;\n transform: translateX(-50%);\n color: var(--tcm-dim);\n font-weight: 400;\n font-size: 10px;\n}\n.tcm-lanes { display: grid; grid-auto-rows: 24px; }\n.tcm-lane { position: relative; border-bottom: 1px solid var(--tcm-line); cursor: crosshair; touch-action: none; }\n.tcm-lane.is-selected { background: var(--tcm-hover); }\n.tcm-key {\n position: absolute;\n top: 50%;\n width: 11px;\n height: 11px;\n padding: 0;\n border: 1px solid var(--tcm-bg);\n border-radius: 2px;\n background: var(--tcm-accent);\n transform: translate(-50%, -50%) rotate(45deg);\n cursor: grab;\n touch-action: none;\n}\n.tcm-key.is-hold { border-radius: 0; background: var(--tcm-dim); }\n.tcm-key.is-picked { outline: 2px solid var(--tcm-fg); outline-offset: 1px; }\n.tcm-key:active { cursor: grabbing; }\n.tcm-key:focus-visible { outline: 2px solid var(--tcm-accent); outline-offset: 2px; }\n.tcm-playhead {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 1px;\n margin-left: -0.5px;\n background: #f87171;\n pointer-events: none;\n}\n.tcm-playhead::before {\n content: \"\";\n position: absolute;\n top: 0;\n left: -4px;\n border: 4.5px solid transparent;\n border-top-color: #f87171;\n}\n.tcm-tl-empty { padding: 2px 4px; }\n\n/* Keys | Graph */\n.tcm-modes { display: inline-flex; margin-left: 4px; border: 1px solid var(--tcm-line); border-radius: 5px; overflow: hidden; }\n.tcm-modes button { padding: 2px 8px; border: 0; background: none; color: var(--tcm-dim); cursor: pointer; }\n.tcm-modes button[aria-checked=\"true\"] { background: var(--tcm-accent-soft); color: var(--tcm-accent); }\n.tcm-modes button:focus-visible { outline: 1px solid var(--tcm-accent); outline-offset: -2px; }\n\n/* Lane names with the motion-path toggle */\n.tcm-tl-row { display: flex; align-items: center; min-width: 0; border-bottom: 1px solid var(--tcm-line); }\n.tcm-tl-row .tcm-tl-name { flex: 1; min-width: 0; height: 100%; border-bottom: 0; }\n.tcm-trail { width: 20px; height: 20px; margin-right: 4px; }\n.tcm-trail svg { width: 13px; height: 13px; }\n.tcm-trail:disabled { visibility: hidden; }\n\n/* Graph view: the channel list in the names column, the plot under the ruler. */\n.tcm-graph, .tcm-graph-side { display: none; }\n.tcm-timeline.is-graph .tcm-lanes, .tcm-timeline.is-graph .tcm-tl-row { display: none; }\n.tcm-timeline.is-graph .tcm-tl-names { grid-auto-rows: auto; }\n.tcm-timeline.is-graph .tcm-graph { display: block; }\n.tcm-timeline.is-graph .tcm-graph-side { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 3px; padding: 6px 6px 0 0; }\n.tcm-graph-svg { display: block; touch-action: none; overflow: visible; }\n.tcm-grid { stroke: var(--tcm-line); stroke-width: 1; }\n.tcm-key-line { stroke: var(--tcm-line); stroke-dasharray: 2 3; }\n.tcm-curve { fill: none; stroke-width: 1.6; stroke-linejoin: round; }\n.tcm-dot { stroke: var(--tcm-bg); stroke-width: 1.5; cursor: grab; }\n.tcm-dot:hover { stroke: var(--tcm-fg); }\n.tcm-dot.is-picked { stroke: var(--tcm-fg); stroke-width: 2; }\n.tcm-graph-empty { fill: var(--tcm-dim); font: inherit; }\n.tcm-graph-axis { fill: var(--tcm-dim); font: 9px ui-monospace, SFMono-Regular, Menlo, monospace; }\n.tcm-chan {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 1px 6px;\n border: 1px solid var(--tcm-line);\n border-radius: 4px;\n background: none;\n color: var(--tcm-dim);\n cursor: pointer;\n}\n.tcm-chan::before { content: \"\"; width: 7px; height: 7px; border-radius: 50%; background: var(--tcm-chan, currentColor); opacity: 0.35; }\n.tcm-chan[aria-pressed=\"true\"] { color: var(--tcm-fg); border-color: color-mix(in srgb, var(--tcm-chan, var(--tcm-accent)) 55%, transparent); }\n.tcm-chan[aria-pressed=\"true\"]::before { opacity: 1; }\n.tcm-chan--mode::before { display: none; }\n.tcm-chan:focus-visible { outline: 1px solid var(--tcm-accent); }\n\n/* Ease curve editor, in the key inspector */\n.tcm-ease { display: flex; }\n.tcm-ease svg { display: block; overflow: visible; touch-action: none; }\n.tcm-ease:has(svg:empty) { display: none; }\n.tcm-ease-box { fill: none; stroke: var(--tcm-line); }\n.tcm-ease-curve { fill: none; stroke: var(--tcm-accent); stroke-width: 2; }\n.tcm-ease-arm { stroke: var(--tcm-dim); stroke-width: 1; }\n.tcm-ease-handle { fill: var(--tcm-accent); stroke: var(--tcm-bg); stroke-width: 1.5; cursor: grab; }\n.tcm-ease-handle:hover { stroke: var(--tcm-fg); }\n";
19
28
  /** Add the stylesheet to `document.head` once. */
20
29
  export declare const injectStyles: () => void;
21
30
  //#endregion
22
- export { type CameraPanel, type CameraPanelHandle, type MountCameraPanelOptions, mountCameraPanel };
31
+ //#region src/ui/timeline.d.ts
32
+ type TimelinePanel = {
33
+ element: HTMLElement;
34
+ setTheme(theme: "dark" | "light"): void;
35
+ dispose(): void;
36
+ };
37
+ type TimelinePanelOptions = {
38
+ /** A camera list to be the timeline's left column, its rows beside their lanes. */
39
+ sidebar?: CameraPanel;
40
+ };
41
+ export declare const createTimelinePanel: (lab: CameraLab, { sidebar }?: TimelinePanelOptions) => TimelinePanel;
42
+ //#endregion
43
+ //#region src/ui/path-editor.d.ts
44
+ type Hit = {
45
+ camera: string;
46
+ key: string;
47
+ position: Vector3;
48
+ };
49
+ export declare const createPathEditor: (lab: CameraLab, canvas: HTMLCanvasElement, { onPick }?: {
50
+ onPick?: (camera: string, key: string) => void;
51
+ }) => {
52
+ /** For tests and hosts: the dot under a screen point, if any. */
53
+ hitAt: (clientX: number, clientY: number) => Hit | null;
54
+ dispose: () => void;
55
+ };
56
+ //#endregion
57
+ export { type CameraPanel, type CameraPanelHandle, type MountCameraPanelOptions, type TimelinePanel, mountCameraPanel };
23
58
  //# sourceMappingURL=ui.d.ts.map
package/dist/ui.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as injectStyles, n as createCameraPanel, r as CAMERA_PANEL_STYLES, t as mountCameraPanel } from "./mount-DBjcu7if.js";
2
- export { CAMERA_PANEL_STYLES, createCameraPanel, injectStyles, mountCameraPanel };
1
+ import { a as CAMERA_PANEL_STYLES, i as createCameraPanel, n as createTimelinePanel, o as injectStyles, r as createPathEditor, t as mountCameraPanel } from "./mount-BAkbs7K2.js";
2
+ export { CAMERA_PANEL_STYLES, createCameraPanel, createPathEditor, createTimelinePanel, injectStyles, mountCameraPanel };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkmake/three-cameras",
3
- "version": "0.0.0",
3
+ "version": "0.2.0",
4
4
  "description": "A dev panel for the cameras in a three.js scene: every camera, the ones rendering right now, their settings live, and frustum helpers. Vanilla three and React Three Fiber.",
5
5
  "keywords": [
6
6
  "camera",
@@ -45,7 +45,7 @@
45
45
  "typecheck": "tsc"
46
46
  },
47
47
  "dependencies": {
48
- "@zkmake/three-meter": "^0.11.1"
48
+ "@zkmake/three-meter": "^0.11.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@arethetypeswrong/core": "catalog:",