my-you-eye 0.5.0 → 0.6.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.
package/COMPONENTS.md CHANGED
@@ -579,7 +579,7 @@ Also accepts everything from `Omit<HTMLAttributes<HTMLUListElement>, "children">
579
579
  | Component | Tier | Variants (**default**) | Demos |
580
580
  |---|---|---|---|
581
581
  | `CellType` | `my-you-eye` | — | Data Types, New data types, Numeric types, Column alignment |
582
- | `DataList` | `my-you-eye` | striped: false / true | Density (normal vs compact), Striped, Label width, Alignment, Scrolling |
582
+ | `DataList` | `my-you-eye` | striped: **false** / true | Density (normal vs compact), Striped, Label width, Alignment, Scrolling |
583
583
  | `DataTable` | `my-you-eye` | variant: **default** / striped<br>density: compact / **normal** | Default, Striped, Scrolling + sticky header, Alignment, Truncation |
584
584
  | `Table` | `my-you-eye` | variant: **default** / striped | Composition, Variants, Density, Truncation & expand, Sticky header |
585
585
  | `Timeline` | `my-you-eye` | — | Horizontal — single lane, Horizontal — lanes, Spans — events with a duration, Shared scale across lanes, Label placement, Density, Progress (playhead reveal), Vertical — single lane, Vertical — lanes |
@@ -718,7 +718,7 @@ Also accepts everything from `HTMLAttributes<HTMLPreElement>`, `VariantProps<typ
718
718
  | `highlightColor?` | `CodeBlockHighlightGroup["color"]` | Color for highlightLines (default "primary"). |
719
719
  | `highlightGroups?` | `CodeBlockHighlightGroup[]` | Multi-color highlight groups. |
720
720
  | `highlightRanges?` | `HighlightRangeDef[]` | Substring-level highlight ranges (0-indexed char positions within each line). |
721
- | `focusRange?` | `[number, number]` | 1-based line numbers outside this `[start, end]` range get a reduced opacity (the `opacity-muted` token) instead of full contrast — the "focus on this range, dim the rest" treatment a code walkthrough needs. |
721
+ | `focusRange?` | `[number, number]` | 1-based line numbers outside this `[start, end]` range get a reduced opacity (the `opacity-focus-dim` token) instead of full contrast — the "focus on this range, dim the rest" treatment a code walkthrough needs. |
722
722
  | `lineId?` | `(lineNumber: number) => string` | Assigns an `id` to each rendered line's row element, keyed by its 1-based line number. |
723
723
  | `bare?` | `boolean` | Strips the persistent header bar (filename/language badge) and the block's own opaque background/border, leaving only a hover-revealed copy button in the corner. |
724
724
 
@@ -1231,7 +1231,7 @@ Also accepts everything from `Timing`.
1231
1231
  | Component | Tier | Variants (**default**) | Demos |
1232
1232
  |---|---|---|---|
1233
1233
  | `Breadcrumbs` | `my-you-eye` | — | Default, Custom separator |
1234
- | `Link` | `my-you-eye` | variant: muted / **primary** | Variants, In a sentence |
1234
+ | `Link` | `my-you-eye` | variant: muted / **primary**<br>underline: false / **true** | Variants, underline={false}, In a sentence |
1235
1235
  | `Pagination` | `my-you-eye` | — | Default (10 pages), Few pages |
1236
1236
  | `Tabs` | `my-you-eye` | variant: filing / pills / **underline** | Underline, Pills, Filing |
1237
1237
 
@@ -1250,6 +1250,10 @@ Also accepts everything from `HTMLAttributes<HTMLElement>`.
1250
1250
 
1251
1251
  Also accepts everything from `AnchorHTMLAttributes<HTMLAnchorElement>`, `VariantProps<typeof linkVariants>`.
1252
1252
 
1253
+ | Prop | Type | Description |
1254
+ |---|---|---|
1255
+ | `underline?` | `boolean` | Underline the label on hover. |
1256
+
1253
1257
  #### `Pagination`
1254
1258
 
1255
1259
  Also accepts everything from `HTMLAttributes<HTMLElement>`.
package/components.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "demos": [
29
29
  "Hold between reveals"
30
30
  ],
31
- "description": "A no-op hold, so a deliberate pause reads as intentional rather than a missing animation."
31
+ "description": "A no-op hold, so a deliberate pause reads as intentional instead of as a missing animation."
32
32
  },
33
33
  {
34
34
  "name": "Camera",
@@ -137,7 +137,7 @@
137
137
  "Formats",
138
138
  "Signed & duration"
139
139
  ],
140
- "description": "Numeric tween that reuses src/lib/format.ts for every formatting mode — never reimplements Intl formatting.",
140
+ "description": "Numeric tween that reuses src/lib/format.ts for every formatting mode instead of reimplementing Intl formatting.",
141
141
  "extends": [
142
142
  "Timing"
143
143
  ]
@@ -185,7 +185,7 @@
185
185
  "Custom cursor node",
186
186
  "Click effect variants"
187
187
  ],
188
- "description": "A fake pointer for simulated UI walkthroughs — eased movement, four shapes, Ripple click feedback.",
188
+ "description": "A fake pointer for simulated UI walkthroughs, with eased movement, four shapes and Ripple click feedback.",
189
189
  "extends": [
190
190
  "Timing"
191
191
  ]
@@ -306,7 +306,7 @@
306
306
  "Cross-fade two elements",
307
307
  "Row expanding into a panel"
308
308
  ],
309
- "description": "Lerps position/size/opacity between two caller-supplied snapshots — a simplified FLIP, not shape morphing.",
309
+ "description": "Lerps position, size and opacity between two caller-supplied snapshots. It is a simplified FLIP rather than shape morphing.",
310
310
  "extends": [
311
311
  "Timing"
312
312
  ]
@@ -325,7 +325,7 @@
325
325
  "Easing vs spring",
326
326
  "Seek & scrub"
327
327
  ],
328
- "description": "The foundation every primitive builds on: useTimeline() / useProgress(), reading from whichever driver is mounted."
328
+ "description": "The foundation every primitive builds on: useTimeline() and useProgress(), which read from whichever driver is mounted."
329
329
  },
330
330
  {
331
331
  "name": "Pulse",
@@ -362,7 +362,7 @@
362
362
  "Loop=3, then settles",
363
363
  "Pulse a non-text sibling"
364
364
  ],
365
- "description": "Looping scale/opacity breathing, driven by frame % period — never CSS animation.",
365
+ "description": "Looping scale and opacity breathing, driven by frame % period rather than by a CSS animation.",
366
366
  "extends": [
367
367
  "Timing"
368
368
  ]
@@ -408,7 +408,7 @@
408
408
  "from variants",
409
409
  "asChild (no layout box)"
410
410
  ],
411
- "description": "Generic entrance animation — fade, directional slide, scale or blur — driven entirely by useProgress().",
411
+ "description": "Generic entrance animation (fade, directional slide, scale or blur), driven entirely by useProgress().",
412
412
  "extends": [
413
413
  "Timing"
414
414
  ]
@@ -501,7 +501,7 @@
501
501
  "demos": [
502
502
  "Axis variants"
503
503
  ],
504
- "description": "Decaying-amplitude oscillation, deterministic via a seeded PRNG — never an unseeded random source.",
504
+ "description": "Decaying-amplitude oscillation, made deterministic by a seeded PRNG rather than an unseeded random source.",
505
505
  "extends": [
506
506
  "Timing"
507
507
  ]
@@ -579,7 +579,7 @@
579
579
  "One node in a diagram",
580
580
  "One card in a dashboard"
581
581
  ],
582
- "description": "Dims everything except a focused rect via a box-shadow cut-out — never backdrop-filter.",
582
+ "description": "Dims everything except a focused rect with a box-shadow cut-out rather than a backdrop-filter.",
583
583
  "extends": [
584
584
  "Timing"
585
585
  ]
@@ -656,7 +656,7 @@
656
656
  "Fade",
657
657
  "Roll"
658
658
  ],
659
- "description": "Cross-fade or roll between two strings without a layout jump — a hidden sizer reserves the space.",
659
+ "description": "Cross-fade or roll between two strings without a layout jump, because a hidden sizer reserves the space.",
660
660
  "extends": [
661
661
  "Timing"
662
662
  ]
@@ -716,7 +716,7 @@
716
716
  "Single token, looping",
717
717
  "Multiple tokens, spaced"
718
718
  ],
719
- "description": "Tokens travelling along an SVG path — the data-flow primitive for architecture diagrams.",
719
+ "description": "Tokens travel along an SVG path. This is the data-flow primitive for architecture diagrams.",
720
720
  "extends": [
721
721
  "Timing"
722
722
  ]
@@ -804,7 +804,7 @@
804
804
  "demos": [
805
805
  "Directions & softness"
806
806
  ],
807
- "description": "Soft-edged mask sweep — a gentler alternative to Wipe's hard clip-path edge, for headings and pull-quotes.",
807
+ "description": "Soft-edged mask sweep, a gentler alternative to Wipe's hard clip-path edge for headings and pull-quotes.",
808
808
  "extends": [
809
809
  "Timing"
810
810
  ]
@@ -840,7 +840,7 @@
840
840
  "Linear — 4 directions",
841
841
  "Radial"
842
842
  ],
843
- "description": "clip-path reveal — linear (hard edge, 4 directions) or radial (circle growing from the direction's edge).",
843
+ "description": "clip-path reveal, either linear (hard edge, four directions) or radial (a circle that grows from the direction's edge).",
844
844
  "extends": [
845
845
  "Timing"
846
846
  ]
@@ -870,7 +870,7 @@
870
870
  "demos": [
871
871
  "Click-through presenter"
872
872
  ],
873
- "description": "Click-through presentation of a Video: → or Space advances, ← reverses, Esc overviews, f fullscreens."
873
+ "description": "Step-through presentation of a Video: Space or → advances, ← reverses, Esc opens the overview, f fullscreens. Clicking the stage does nothing, so a live scene keeps its own pointer events."
874
874
  },
875
875
  {
876
876
  "name": "SpeakerView",
@@ -915,7 +915,7 @@
915
915
  "demos": [
916
916
  "Custom controls built from useSteps"
917
917
  ],
918
- "description": "Headless step-navigation hook — the flattened (scene, step) list across a whole Video."
918
+ "description": "Headless step-navigation hook that flattens a whole Video into one (scene, step) list."
919
919
  },
920
920
  {
921
921
  "name": "BulletScene",
@@ -954,7 +954,7 @@
954
954
  "Bar — playing",
955
955
  "Gauge — no steps (single-beat draw-on)"
956
956
  ],
957
- "description": "Wraps whichever chart ChartSpec.type selects, driving its progress prop step by step."
957
+ "description": "Wraps whichever chart ChartSpec.type selects and drives its progress prop step by step."
958
958
  },
959
959
  {
960
960
  "name": "CodeDiff",
@@ -1073,7 +1073,7 @@
1073
1073
  "Pinned mid-stagger (frame 10/90)",
1074
1074
  "Pinned at rest (frame 90/90)"
1075
1075
  ],
1076
- "description": "End card — title, subtitle, links and a closing call to action, staggering in as one beat."
1076
+ "description": "End card with a title, subtitle, links and a closing call to action, all staggered in as one beat."
1077
1077
  },
1078
1078
  {
1079
1079
  "name": "SceneRenderer",
@@ -1093,7 +1093,7 @@
1093
1093
  "Bullets",
1094
1094
  "Diagram, chart, stat"
1095
1095
  ],
1096
- "description": "The single switch(scene.kind) consumers never touch. All eleven SceneKinds render for real — there is no placeholder branch."
1096
+ "description": "The single switch(scene.kind) that consumers never touch. All eleven SceneKinds render for real; the switch has no placeholder branch."
1097
1097
  },
1098
1098
  {
1099
1099
  "name": "SequenceScene",
@@ -1111,7 +1111,7 @@
1111
1111
  "demos": [
1112
1112
  "Playing"
1113
1113
  ],
1114
- "description": "Wraps SequenceDiagram, one message or note per step. Activation bars are derived, never authored."
1114
+ "description": "Wraps SequenceDiagram with one message or note per step. Activation bars are derived rather than authored."
1115
1115
  },
1116
1116
  {
1117
1117
  "name": "StatScene",
@@ -1129,7 +1129,7 @@
1129
1129
  "demos": [
1130
1130
  "Playing"
1131
1131
  ],
1132
- "description": "StatGrid with per-tile staggered CountUp, delta arrows and inline Sparklines — one step per tile."
1132
+ "description": "StatGrid with per-tile staggered CountUp, delta arrows and inline Sparklines, one step per tile."
1133
1133
  },
1134
1134
  {
1135
1135
  "name": "TerminalScene",
@@ -1152,7 +1152,7 @@
1152
1152
  "Pinned at rest (frame 160/160)",
1153
1153
  "Prompt change (rows + scheme)"
1154
1154
  ],
1155
- "description": "Wraps Terminal, revealing one entry per step: command types in, spinner cycles, output and exit status land."
1155
+ "description": "Wraps Terminal and reveals one entry per step: the command types in, the spinner cycles, then output and exit status land."
1156
1156
  },
1157
1157
  {
1158
1158
  "name": "TitleScene",
@@ -1173,7 +1173,7 @@
1173
1173
  "Pinned mid-stagger (frame 12/90)",
1174
1174
  "Pinned at rest (frame 90/90)"
1175
1175
  ],
1176
- "description": "Opening/chapter card — chapter eyebrow, title and subtitle stagger in together, timed off the scene's own content-derived duration."
1176
+ "description": "Opening or chapter card. The chapter eyebrow, title and subtitle stagger in together, timed off the scene's own content-derived duration."
1177
1177
  },
1178
1178
  {
1179
1179
  "name": "Validation",
@@ -1187,7 +1187,7 @@
1187
1187
  "Deliberately broken video",
1188
1188
  "Valid, minimal video"
1189
1189
  ],
1190
- "description": "validateVideo(video) run against a deliberately broken Video — every runtime check, made visible."
1190
+ "description": "validateVideo(video) run against a deliberately broken Video, so every runtime check is visible."
1191
1191
  },
1192
1192
  {
1193
1193
  "name": "WalkthroughScene",
@@ -1205,7 +1205,7 @@
1205
1205
  "demos": [
1206
1206
  "Playing"
1207
1207
  ],
1208
- "description": "DeviceFrame, Cursor and Spotlight over a static screenshot — the alternative to a screen recording."
1208
+ "description": "DeviceFrame, Cursor and Spotlight over a static screenshot, as an alternative to a screen recording."
1209
1209
  },
1210
1210
  {
1211
1211
  "name": "Annotation",
@@ -1265,7 +1265,7 @@
1265
1265
  "Vertical sides (top/bottom)",
1266
1266
  "Progress reveal"
1267
1267
  ],
1268
- "description": "A callout/leader-line pointing at a target point — for highlighting part of a diagram or screenshot in a video. Leader-line path math (and the arrow marker's angle) come straight from connection-line/geometry.ts; no new path math.",
1268
+ "description": "A callout with a leader line that points at a target, for picking out part of a diagram or screenshot in a video. Its path math and arrow-marker angle come straight from connection-line/geometry.ts.",
1269
1269
  "extends": [
1270
1270
  "Omit<HTMLAttributes<HTMLDivElement>, \"children\">"
1271
1271
  ]
@@ -1362,7 +1362,7 @@
1362
1362
  "Parallel edges (automatic bundling)",
1363
1363
  "Edge states share the same visual language as ConnectionLine"
1364
1364
  ],
1365
- "description": "One shared <svg> for N edges — path, arrowhead and label rendering reused from ConnectionLine's ConnectionPath, not duplicated. Prefer this over stacking many standalone ConnectionLines for diagrams with lots of edges.",
1365
+ "description": "One shared <svg> for N edges, with path, arrowhead and label rendering reused from ConnectionLine's ConnectionPath. Prefer it over stacking many standalone ConnectionLines when a diagram has a lot of edges.",
1366
1366
  "extends": [
1367
1367
  "Omit<SVGAttributes<SVGSVGElement>, \"id\">"
1368
1368
  ]
@@ -1487,7 +1487,7 @@
1487
1487
  "demos": [
1488
1488
  "Pipeline editor (drag nodes, connect ports, delete selected)"
1489
1489
  ],
1490
- "description": "A composed pattern combining Canvas, GraphNode, and Edge into a drag-and-drop pipeline editor."
1490
+ "description": "A composed pattern that combines Canvas, GraphNode and Edge into a drag-and-drop pipeline editor."
1491
1491
  },
1492
1492
  {
1493
1493
  "name": "GraphGroup",
@@ -1542,7 +1542,7 @@
1542
1542
  "Architecture boundaries",
1543
1543
  "Label placement"
1544
1544
  ],
1545
- "description": "A labelled boundary region — VPC, cluster, service boundary — drawn behind the nodes and edges it encloses. Render groups before nodes in JSX: stacking is DOM order, not z-index.",
1545
+ "description": "A labelled boundary region (a VPC, a cluster, a service boundary) drawn behind the nodes and edges it encloses. Render groups before nodes in JSX, because stacking here is DOM order rather than z-index.",
1546
1546
  "variantDefaults": {
1547
1547
  "border": "dashed"
1548
1548
  },
@@ -1700,7 +1700,7 @@
1700
1700
  "Socket shape — all states",
1701
1701
  "Circle vs socket, side by side"
1702
1702
  ],
1703
- "description": "shape=\"circle\" is a freestanding full disc. shape=\"socket\" is a true half-disc (SVG geometry, not clipping) meant to sit exactly on a node's border — see GraphNode's row ports.",
1703
+ "description": "shape=\"circle\" is a freestanding full disc; shape=\"socket\" is a true half-disc built from SVG geometry rather than by clipping, meant to sit exactly on a node's border. See GraphNode's row ports.",
1704
1704
  "variantDefaults": {
1705
1705
  "side": "in",
1706
1706
  "state": "default"
@@ -1865,7 +1865,7 @@
1865
1865
  "Loading",
1866
1866
  "Empty"
1867
1867
  ],
1868
- "description": "Shared chart chrome — measurement, axes, gridlines, legend, tooltip layer, empty/loading states. Every chart in this library composes it instead of re-implementing axis code."
1868
+ "description": "Shared chart chrome: measurement, axes, gridlines, legend, tooltip layer and empty/loading states. Every chart in this library composes it instead of re-implementing axis code."
1869
1869
  },
1870
1870
  {
1871
1871
  "name": "Funnel",
@@ -2020,7 +2020,7 @@
2020
2020
  "Empty",
2021
2021
  "Draw-on progress (50%)"
2022
2022
  ],
2023
- "description": "Matrix on the sequential ramp — latency grids, activity calendars. Cells fade in by rank (lowest value first) as `progress` sweeps 0→1."
2023
+ "description": "Matrix on the sequential ramp, for latency grids and activity calendars. Cells fade in by rank, lowest value first, as `progress` sweeps 0→1."
2024
2024
  },
2025
2025
  {
2026
2026
  "name": "Legend",
@@ -2293,7 +2293,7 @@
2293
2293
  "Empty",
2294
2294
  "Draw-on progress (50%)"
2295
2295
  ],
2296
- "description": "Inline micro-chart, no axes — feeds StatCard's trend slot. Draws left-to-right as `progress` sweeps 0→1."
2296
+ "description": "Inline micro-chart with no axes, which feeds StatCard's trend slot. It draws left to right as `progress` sweeps 0→1."
2297
2297
  },
2298
2298
  {
2299
2299
  "name": "CellType",
@@ -2414,8 +2414,11 @@
2414
2414
  "Alignment",
2415
2415
  "Scrolling"
2416
2416
  ],
2417
- "description": "A label/value list (definition list) for record-detail views — the non-tabular counterpart to Table/DataTable.",
2417
+ "description": "A label/value list (a definition list) for record-detail views, the non-tabular counterpart to Table and DataTable.",
2418
2418
  "parent": "Table",
2419
+ "variantDefaults": {
2420
+ "striped": "false"
2421
+ },
2419
2422
  "extends": [
2420
2423
  "HTMLAttributes<HTMLDListElement>",
2421
2424
  "VariantProps<typeof dataListVariants>"
@@ -2503,7 +2506,7 @@
2503
2506
  "Truncation & expand",
2504
2507
  "Sticky header"
2505
2508
  ],
2506
- "description": "The raw compositional table primitives (Table/TableHeader/TableBody/TableRow/TableHead/TableCell) — DataTable is the data-driven pattern built on top of these for typical use. Reach for Table directly when you need bespoke markup a data-driven API can't express.",
2509
+ "description": "The raw compositional table primitives: Table, TableHeader, TableBody, TableRow, TableHead and TableCell. DataTable is the data-driven pattern built on them for typical use; reach for Table directly when you need markup a data-driven API cannot express.",
2507
2510
  "parent": "Table",
2508
2511
  "variantDefaults": {
2509
2512
  "variant": "default"
@@ -2567,7 +2570,7 @@
2567
2570
  "Vertical — single lane",
2568
2571
  "Vertical — lanes"
2569
2572
  ],
2570
- "description": "Data-driven horizontal/vertical event sequences with optional lanes — roadmaps, request traces, git history. Events can be instants or spans (`until`). Draws no graph edges.",
2573
+ "description": "Data-driven horizontal or vertical event sequences with optional lanes, for roadmaps, request traces and git history. Events can be instants or spans (`until`), and the component draws no graph edges.",
2571
2574
  "extends": [
2572
2575
  "HTMLAttributes<HTMLDivElement>"
2573
2576
  ]
@@ -2793,7 +2796,7 @@
2793
2796
  "focusRange": {
2794
2797
  "type": "[number, number]",
2795
2798
  "optional": true,
2796
- "doc": "1-based line numbers outside this `[start, end]` range get a reduced opacity (the `opacity-muted` token) instead of full contrast — the \"focus on this range, dim the rest\" treatment a code walkthrough needs."
2799
+ "doc": "1-based line numbers outside this `[start, end]` range get a reduced opacity (the `opacity-focus-dim` token) instead of full contrast — the \"focus on this range, dim the rest\" treatment a code walkthrough needs."
2797
2800
  },
2798
2801
  "lineId": {
2799
2802
  "type": "(lineNumber: number) => string",
@@ -2866,7 +2869,7 @@
2866
2869
  "Window",
2867
2870
  "Phone"
2868
2871
  ],
2869
- "description": "Browser / window / phone chrome around arbitrary children — for showing UI on screen without a real screen recording.",
2872
+ "description": "Browser, window or phone chrome around arbitrary children, for putting UI on screen without a real screen recording.",
2870
2873
  "variantDefaults": {
2871
2874
  "variant": "browser"
2872
2875
  },
@@ -2924,7 +2927,7 @@
2924
2927
  "Split — word diff",
2925
2928
  "Elevated"
2926
2929
  ],
2927
- "description": "Unified and side-by-side diff views, reusing CodeBlock's tokenizer for syntax color and a token-level LCS for optional word diff.",
2930
+ "description": "Unified and side-by-side diff views. They reuse CodeBlock's tokenizer for syntax color and a token-level LCS for the optional word diff.",
2928
2931
  "variantDefaults": {
2929
2932
  "variant": "default"
2930
2933
  },
@@ -3230,7 +3233,7 @@
3230
3233
  "Fixed height, scrolls as content grows",
3231
3234
  "Prompt segments, changed mid-session"
3232
3235
  ],
3233
- "description": "Prompt/command/output/exit-status sequences, composing CodeBlock for output bodies. Data-driven via a typed entries array.",
3236
+ "description": "Prompt, command, output and exit-status sequences, driven by a typed entries array. Output renders as plain terminal text, colorized by the shared code tokenizer when an entry names a language.",
3234
3237
  "variantDefaults": {
3235
3238
  "variant": "default",
3236
3239
  "scheme": "default",
@@ -3852,16 +3855,28 @@
3852
3855
  "variant": [
3853
3856
  "muted",
3854
3857
  "primary"
3858
+ ],
3859
+ "underline": [
3860
+ "false",
3861
+ "true"
3855
3862
  ]
3856
3863
  },
3857
- "props": {},
3864
+ "props": {
3865
+ "underline": {
3866
+ "type": "boolean",
3867
+ "optional": true,
3868
+ "doc": "Underline the label on hover."
3869
+ }
3870
+ },
3858
3871
  "demos": [
3859
3872
  "Variants",
3873
+ "underline={false}",
3860
3874
  "In a sentence"
3861
3875
  ],
3862
- "description": "A styled <a> — the primitive underneath any real, navigable link (OutroScene's link list, prose, cards).",
3876
+ "description": "A styled <a>, the primitive underneath any real navigable link: OutroScene's link list, prose, cards.",
3863
3877
  "variantDefaults": {
3864
- "variant": "primary"
3878
+ "variant": "primary",
3879
+ "underline": "true"
3865
3880
  },
3866
3881
  "extends": [
3867
3882
  "AnchorHTMLAttributes<HTMLAnchorElement>",
@@ -4123,7 +4138,7 @@
4123
4138
  "Wipe (draggable)",
4124
4139
  "Wipe — progress-driven"
4125
4140
  ],
4126
- "description": "Before/after — side-by-side two-column or an overlay wipe with a draggable divider. Divider position is controlled/uncontrolled, or driven by an animation-friendly `progress` prop (0→1) without importing src/motion/.",
4141
+ "description": "Before and after, either as two side-by-side columns or as an overlay wipe with a draggable divider. The divider position can be controlled, uncontrolled, or driven by a `progress` prop (0→1) so a scene can animate it without importing src/motion/.",
4127
4142
  "extends": [
4128
4143
  "Omit<HTMLAttributes<HTMLDivElement>, \"onChange\">"
4129
4144
  ]
@@ -4194,7 +4209,7 @@
4194
4209
  "Project tree with git status",
4195
4210
  "Compact density"
4196
4211
  ],
4197
- "description": "A file/folder tree for \"here's the project structure\" moments in coding videos — a pattern over TreeView, not a fork of it. Shapes FileTreeNode[] into TreeNode[] (icons, a git-status badge, a trailing metadata readout) and renders TreeView.",
4212
+ "description": "A file and folder tree for the \"here's the project structure\" moment in a coding video, built as a pattern over TreeView rather than a fork of it. It shapes FileTreeNode[] into TreeNode[] with icons, a git-status badge and a trailing metadata readout, then renders TreeView.",
4198
4213
  "extends": [
4199
4214
  "Pick<TreeViewProps, \"defaultExpandedDepth\" | \"expandedKeys\" | \"onToggle\" | \"density\" | \"indent\">"
4200
4215
  ]
@@ -4304,7 +4319,7 @@
4304
4319
  "Self-messages, spanning note, error path",
4305
4320
  "Progress reveal"
4306
4321
  ],
4307
- "description": "UML-style sequence diagram — participant lanes, dashed lifelines, messages as ConnectionLayer edges, activation bars, and notes. Every coordinate is a GRID (16) multiple; message arrows are never hand-drawn <path>s.",
4322
+ "description": "UML-style sequence diagram with participant lanes, dashed lifelines, messages as ConnectionLayer edges, activation bars and notes. Every coordinate is a multiple of GRID (16), and message arrows come from ConnectionLayer rather than from hand-drawn <path>s.",
4308
4323
  "extends": [
4309
4324
  "Omit<HTMLAttributes<HTMLDivElement>, \"children\">"
4310
4325
  ]
@@ -4384,7 +4399,7 @@
4384
4399
  "Size",
4385
4400
  "positiveIsGood"
4386
4401
  ],
4387
- "description": "A KPI row as one component — data-driven StatGrid over StatCard, instead of a call-site grid of individually placed StatCards.",
4402
+ "description": "A KPI row as one component: a data-driven StatGrid over StatCard, instead of a call-site grid of individually placed StatCards.",
4388
4403
  "extends": [
4389
4404
  "HTMLAttributes<HTMLDivElement>"
4390
4405
  ]
@@ -40,7 +40,7 @@ import {
40
40
  snap,
41
41
  splitTokensByLine,
42
42
  tokenize
43
- } from "./chunk-OBWYV42U.js";
43
+ } from "./chunk-E2GYVGDJ.js";
44
44
  import {
45
45
  Camera,
46
46
  CountUp,
@@ -58,7 +58,7 @@ import {
58
58
  resolveBeatFrames,
59
59
  useProgress,
60
60
  useSequence
61
- } from "./chunk-YWX2ZFH4.js";
61
+ } from "./chunk-NQR657Q6.js";
62
62
  import {
63
63
  formatBytesParts,
64
64
  formatCurrencyParts,
@@ -931,11 +931,22 @@ function codeStepContent(step, sceneCode, isFirst) {
931
931
  if (step.highlight && step.highlight.length > 0) return joinText(step.highlight);
932
932
  return void 0;
933
933
  }
934
+ var CODE_STEP_MIN_SECONDS = 1.5;
935
+ var CODE_TRANSITION_SHARE = 0.55;
936
+ var CODE_TRANSITION_MAX_SECONDS = 1.2;
937
+ var CODE_TRANSITION_MIN_SECONDS = 0.6;
938
+ function codeTransitionFrames(stepFrames, fps) {
939
+ const share = Math.round(stepFrames * CODE_TRANSITION_SHARE);
940
+ const capped = Math.min(share, Math.round(CODE_TRANSITION_MAX_SECONDS * fps));
941
+ const floored = Math.max(capped, Math.round(CODE_TRANSITION_MIN_SECONDS * fps));
942
+ return Math.max(1, Math.min(floored, stepFrames - 1));
943
+ }
934
944
  function codeSteps(scene) {
935
945
  return scene.steps.map((step, i) => ({
936
946
  name: stepName(step.id, i),
937
947
  content: codeStepContent(step, scene.code, i === 0),
938
- hold: step.hold
948
+ hold: step.hold,
949
+ minSeconds: CODE_STEP_MIN_SECONDS
939
950
  }));
940
951
  }
941
952
  function terminalSteps(scene) {
@@ -1325,6 +1336,7 @@ var MAX_ZOOM = 1.6;
1325
1336
  function useCodeCameraKeyframes(scene, ranges, blockId) {
1326
1337
  const containerRef = useRef3(null);
1327
1338
  const [keyframes, setKeyframes] = useState3([]);
1339
+ const { fps } = useTimeline();
1328
1340
  useLayoutEffect3(() => {
1329
1341
  const container = containerRef.current;
1330
1342
  if (!container) return void 0;
@@ -1345,14 +1357,25 @@ function useCodeCameraKeyframes(scene, ranges, blockId) {
1345
1357
  const focus = union(els);
1346
1358
  return { at, focus, zoom: zoomFor(focus) };
1347
1359
  };
1348
- const next = scene.steps.map((step, i) => {
1349
- const at = ranges[stepName(step.id, i)].startFrame;
1350
- if (!step.focus) return frameFor(at, allLines());
1360
+ const targetFor = (step) => {
1361
+ if (!step.focus) return allLines();
1351
1362
  const [start, end] = step.focus;
1352
1363
  const startEl = document.getElementById(lineElementId(blockId, start));
1353
1364
  const endEl = document.getElementById(lineElementId(blockId, end));
1354
- if (!startEl || !endEl) return frameFor(at, allLines());
1355
- return frameFor(at, [startEl, endEl]);
1365
+ return startEl && endEl ? [startEl, endEl] : allLines();
1366
+ };
1367
+ const next = [];
1368
+ scene.steps.forEach((step, i) => {
1369
+ const range = ranges[stepName(step.id, i)];
1370
+ const arrival = frameFor(range.startFrame, targetFor(step));
1371
+ if (i === 0) {
1372
+ next.push(arrival);
1373
+ return;
1374
+ }
1375
+ const held = next[next.length - 1];
1376
+ next.push({ at: range.startFrame, focus: held.focus, zoom: held.zoom });
1377
+ const move = codeTransitionFrames(range.endFrame - range.startFrame, fps);
1378
+ next.push({ ...arrival, at: range.startFrame + move });
1356
1379
  });
1357
1380
  setKeyframes((prev) => keyframesEqual(prev, next) ? prev : next);
1358
1381
  };
@@ -1360,7 +1383,7 @@ function useCodeCameraKeyframes(scene, ranges, blockId) {
1360
1383
  const observer = new ResizeObserver(measure);
1361
1384
  observer.observe(container);
1362
1385
  return () => observer.disconnect();
1363
- }, [scene, ranges, blockId]);
1386
+ }, [scene, ranges, blockId, fps]);
1364
1387
  return { containerRef, keyframes };
1365
1388
  }
1366
1389
 
@@ -1409,7 +1432,6 @@ function useCodeAnnotations(annotations, containerRef, blockId) {
1409
1432
 
1410
1433
  // src/scenes/code-scene/CodeScene.tsx
1411
1434
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1412
- var DIFF_SHARE = 0.55;
1413
1435
  function typedSlice(source, progress) {
1414
1436
  const cut = Math.floor(source.length * progress);
1415
1437
  return source.slice(0, cut) + source.slice(cut).replace(/[^\n]/g, "");
@@ -1423,7 +1445,7 @@ function currentStepIndex(scene, ranges, frame) {
1423
1445
  }
1424
1446
  function CodeScene({ scene }) {
1425
1447
  const ranges = useSequence(sceneSteps(scene), scene.pace);
1426
- const { frame } = useTimeline();
1448
+ const { frame, fps } = useTimeline();
1427
1449
  const blockId = `code-${useId().replace(/:/g, "")}`;
1428
1450
  const { before, after } = useMemo3(() => runningSources(scene), [scene]);
1429
1451
  const hasSteps = scene.steps.length > 0;
@@ -1441,7 +1463,7 @@ function CodeScene({ scene }) {
1441
1463
  () => buildHighlightRanges(nextSource, step.focus, step.highlight),
1442
1464
  [nextSource, step.focus, step.highlight]
1443
1465
  );
1444
- const diffFrames = Math.max(1, Math.round((range.endFrame - range.startFrame) * DIFF_SHARE));
1466
+ const diffFrames = codeTransitionFrames(range.endFrame - range.startFrame, fps);
1445
1467
  const isDiff = !step.typed && nextSource !== prevSource && frame < range.startFrame + diffFrames;
1446
1468
  const isTyped = Boolean(step.typed);
1447
1469
  const typedCode = isTyped ? typedSlice(nextSource, stepProgress) : nextSource;
@@ -4,16 +4,16 @@ import {
4
4
  import {
5
5
  SceneRenderer,
6
6
  sceneDuration
7
- } from "./chunk-AAPYU7IQ.js";
7
+ } from "./chunk-4H6HZ7AV.js";
8
8
  import {
9
9
  Badge,
10
10
  Progress,
11
11
  cn
12
- } from "./chunk-OBWYV42U.js";
12
+ } from "./chunk-E2GYVGDJ.js";
13
13
  import {
14
14
  MotionRoot,
15
15
  resolveBeatFrames
16
- } from "./chunk-YWX2ZFH4.js";
16
+ } from "./chunk-NQR657Q6.js";
17
17
 
18
18
  // src/video/VideoRoot.tsx
19
19
  import { Fragment as Fragment2 } from "react";