cursedbelt 3.0.2 → 4.0.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.
Files changed (250) hide show
  1. package/dist/react/components/ListToolbar.d.ts +94 -0
  2. package/dist/react/components/ListToolbar.d.ts.map +1 -0
  3. package/dist/react/components/ListToolbar.js +66 -0
  4. package/dist/react/components/ListToolbar.js.map +1 -0
  5. package/dist/react/file-tree/FileTree.d.ts +282 -3
  6. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  7. package/dist/react/file-tree/FileTree.js +799 -131
  8. package/dist/react/file-tree/FileTree.js.map +1 -1
  9. package/dist/react/file-tree/fileTree.css +151 -0
  10. package/dist/react/file-tree/fileTreeSelection.d.ts +187 -0
  11. package/dist/react/file-tree/fileTreeSelection.d.ts.map +1 -0
  12. package/dist/react/file-tree/fileTreeSelection.js +264 -0
  13. package/dist/react/file-tree/fileTreeSelection.js.map +1 -0
  14. package/dist/react/file-tree/index.d.ts +1 -0
  15. package/dist/react/file-tree/index.d.ts.map +1 -1
  16. package/dist/react/file-tree/index.js +8 -0
  17. package/dist/react/file-tree/index.js.map +1 -1
  18. package/dist/react/index.d.ts +1 -0
  19. package/dist/react/index.d.ts.map +1 -1
  20. package/dist/react/index.js +1 -0
  21. package/dist/react/index.js.map +1 -1
  22. package/dist/react/lib/form.d.ts +3 -0
  23. package/dist/react/lib/form.d.ts.map +1 -1
  24. package/dist/react/lib/form.js +43 -5
  25. package/dist/react/lib/form.js.map +1 -1
  26. package/dist/react/media/DenseGalleryShell.d.ts +19 -3
  27. package/dist/react/media/DenseGalleryShell.d.ts.map +1 -1
  28. package/dist/react/media/DenseGalleryShell.js +55 -15
  29. package/dist/react/media/DenseGalleryShell.js.map +1 -1
  30. package/dist/react/media/FocusViewer.d.ts.map +1 -1
  31. package/dist/react/media/FocusViewer.js +14 -14
  32. package/dist/react/media/FocusViewer.js.map +1 -1
  33. package/dist/react/media/FrameGrabber.d.ts +23 -0
  34. package/dist/react/media/FrameGrabber.d.ts.map +1 -1
  35. package/dist/react/media/FrameGrabber.js +67 -1
  36. package/dist/react/media/FrameGrabber.js.map +1 -1
  37. package/dist/react/media/MediaResize.d.ts +51 -19
  38. package/dist/react/media/MediaResize.d.ts.map +1 -1
  39. package/dist/react/media/MediaResize.js +20 -13
  40. package/dist/react/media/MediaResize.js.map +1 -1
  41. package/dist/react/media/VideoChapterEditor.d.ts +67 -0
  42. package/dist/react/media/VideoChapterEditor.d.ts.map +1 -0
  43. package/dist/react/media/VideoChapterEditor.js +148 -0
  44. package/dist/react/media/VideoChapterEditor.js.map +1 -0
  45. package/dist/react/media/VideoPlayer.d.ts +46 -4
  46. package/dist/react/media/VideoPlayer.d.ts.map +1 -1
  47. package/dist/react/media/VideoPlayer.js +124 -11
  48. package/dist/react/media/VideoPlayer.js.map +1 -1
  49. package/dist/react/media/VideoTurner.d.ts +15 -2
  50. package/dist/react/media/VideoTurner.d.ts.map +1 -1
  51. package/dist/react/media/VideoTurner.js +29 -2
  52. package/dist/react/media/VideoTurner.js.map +1 -1
  53. package/dist/react/media/denseGallery.css +104 -46
  54. package/dist/react/media/hlsSource.d.ts +57 -0
  55. package/dist/react/media/hlsSource.d.ts.map +1 -1
  56. package/dist/react/media/hlsSource.js +177 -1
  57. package/dist/react/media/hlsSource.js.map +1 -1
  58. package/dist/react/media/index.d.ts +2 -0
  59. package/dist/react/media/index.d.ts.map +1 -1
  60. package/dist/react/media/index.js +6 -0
  61. package/dist/react/media/index.js.map +1 -1
  62. package/dist/react/media/mediaResize.css +127 -54
  63. package/dist/react/media/pictureEditor.css +52 -6
  64. package/dist/react/media/videoChapterEditor.css +203 -0
  65. package/dist/react/media/videoChapters.d.ts +117 -0
  66. package/dist/react/media/videoChapters.d.ts.map +1 -0
  67. package/dist/react/media/videoChapters.js +95 -0
  68. package/dist/react/media/videoChapters.js.map +1 -0
  69. package/dist/react/media-gallery/GalleryTable.d.ts +52 -1
  70. package/dist/react/media-gallery/GalleryTable.d.ts.map +1 -1
  71. package/dist/react/media-gallery/GalleryTable.js +40 -9
  72. package/dist/react/media-gallery/GalleryTable.js.map +1 -1
  73. package/dist/react/media-gallery/MediaGallery.d.ts +107 -4
  74. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  75. package/dist/react/media-gallery/MediaGallery.js +484 -104
  76. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  77. package/dist/react/media-gallery/MediaMetaEditor.d.ts +48 -0
  78. package/dist/react/media-gallery/MediaMetaEditor.d.ts.map +1 -0
  79. package/dist/react/media-gallery/MediaMetaEditor.js +66 -0
  80. package/dist/react/media-gallery/MediaMetaEditor.js.map +1 -0
  81. package/dist/react/media-gallery/index.d.ts +2 -0
  82. package/dist/react/media-gallery/index.d.ts.map +1 -1
  83. package/dist/react/media-gallery/index.js +5 -0
  84. package/dist/react/media-gallery/index.js.map +1 -1
  85. package/dist/react/media-gallery/mediaGallery.css +180 -4
  86. package/dist/react/media-gallery/mediaMeta.d.ts +62 -0
  87. package/dist/react/media-gallery/mediaMeta.d.ts.map +1 -0
  88. package/dist/react/media-gallery/mediaMeta.js +56 -0
  89. package/dist/react/media-gallery/mediaMeta.js.map +1 -0
  90. package/dist/react/media-gallery/playbackPreferences.d.ts +77 -0
  91. package/dist/react/media-gallery/playbackPreferences.d.ts.map +1 -1
  92. package/dist/react/media-gallery/playbackPreferences.js +42 -0
  93. package/dist/react/media-gallery/playbackPreferences.js.map +1 -1
  94. package/dist/react/media-gallery/types.d.ts +34 -0
  95. package/dist/react/media-gallery/types.d.ts.map +1 -1
  96. package/dist/scripts/guardrailsEnforce.d.ts +29 -0
  97. package/dist/scripts/guardrailsEnforce.d.ts.map +1 -0
  98. package/dist/styles-areas/activity-bar.css +30 -0
  99. package/dist/styles-areas/analytics.css +672 -0
  100. package/dist/styles-areas/auth.css +154 -0
  101. package/dist/styles-areas/calendar.css +416 -0
  102. package/dist/styles-areas/chart-table.css +41 -0
  103. package/dist/styles-areas/charts.css +89 -0
  104. package/dist/styles-areas/chat.css +364 -0
  105. package/dist/styles-areas/clipboard.css +26 -0
  106. package/dist/styles-areas/code-editor.css +130 -0
  107. package/dist/styles-areas/companion-link.css +70 -0
  108. package/dist/styles-areas/core.css +2503 -0
  109. package/dist/styles-areas/dashboard-grid.css +84 -0
  110. package/dist/styles-areas/data-table.css +657 -0
  111. package/dist/styles-areas/deep-link.css +26 -0
  112. package/dist/styles-areas/diff-viewer.css +30 -0
  113. package/dist/styles-areas/disk-usage.css +262 -0
  114. package/dist/styles-areas/emoji.css +154 -0
  115. package/dist/styles-areas/fields.css +582 -0
  116. package/dist/styles-areas/file-tree.css +33 -0
  117. package/dist/styles-areas/filter-rail.css +235 -0
  118. package/dist/styles-areas/folder-tree.css +219 -0
  119. package/dist/styles-areas/keep-awake.css +26 -0
  120. package/dist/styles-areas/layout-engine.css +239 -0
  121. package/dist/styles-areas/markdown.css +87 -0
  122. package/dist/styles-areas/master-detail.css +239 -0
  123. package/dist/styles-areas/master-lock.css +26 -0
  124. package/dist/styles-areas/media-gallery.css +351 -0
  125. package/dist/styles-areas/media.css +658 -0
  126. package/dist/styles-areas/nav.css +488 -0
  127. package/dist/styles-areas/notifications.css +245 -0
  128. package/dist/styles-areas/overlays.css +133 -0
  129. package/dist/styles-areas/palette.css +140 -0
  130. package/dist/styles-areas/pdf-viewer.css +47 -0
  131. package/dist/styles-areas/problem-detail.css +97 -0
  132. package/dist/styles-areas/rich-text.css +167 -0
  133. package/dist/styles-areas/sharing.css +153 -0
  134. package/dist/styles-areas/spreadsheet.css +37 -0
  135. package/dist/styles-areas/stats.css +281 -0
  136. package/dist/styles-areas/test-report.css +133 -0
  137. package/dist/styles-areas/virtual.css +30 -0
  138. package/dist/styles-areas/wizard.css +381 -0
  139. package/dist/styles-areas/workbench.css +543 -0
  140. package/dist/styles-areas/workbook-viewer.css +44 -0
  141. package/dist/styles-static.css +34 -23
  142. package/dist/styles.css +34 -23
  143. package/package.json +111 -31
  144. package/scripts/checkDistExports.ts +67 -0
  145. package/scripts/cssRules.ts +125 -0
  146. package/scripts/fixtureAppCss.ts +344 -0
  147. package/scripts/generateAreaStyles.ts +253 -0
  148. package/scripts/guardrailsEnforce.spec.ts +62 -0
  149. package/scripts/guardrailsEnforce.ts +67 -3
  150. package/scripts/styleAreas.ts +585 -0
  151. package/scripts/verify.ts +5 -0
  152. package/src/barrelsReachNoOptionalPeer.spec.ts +117 -9
  153. package/src/docsMatchTheSplit.spec.ts +98 -0
  154. package/src/fixtureAppCss.spec.ts +177 -0
  155. package/src/namedSubpathsResolve.spec.ts +35 -0
  156. package/src/publishShape.spec.ts +67 -0
  157. package/src/react/components/ListToolbar.spec.tsx +172 -0
  158. package/src/react/components/ListToolbar.tsx +151 -0
  159. package/src/react/file-tree/FileTree.spec.tsx +1009 -0
  160. package/src/react/file-tree/FileTree.tsx +1459 -309
  161. package/src/react/file-tree/fileTree.css +151 -0
  162. package/src/react/file-tree/fileTreeSelection.spec.ts +327 -0
  163. package/src/react/file-tree/fileTreeSelection.ts +321 -0
  164. package/src/react/file-tree/index.ts +21 -0
  165. package/src/react/index.ts +1 -0
  166. package/src/react/lib/form.ts +51 -8
  167. package/src/react/media/DenseGalleryShell.tsx +108 -36
  168. package/src/react/media/FocusViewer.tsx +15 -8
  169. package/src/react/media/FrameGrabber.spec.tsx +175 -0
  170. package/src/react/media/FrameGrabber.tsx +67 -0
  171. package/src/react/media/MediaResize.spec.tsx +84 -14
  172. package/src/react/media/MediaResize.tsx +51 -19
  173. package/src/react/media/VideoChapterEditor.tsx +428 -0
  174. package/src/react/media/VideoPlayer.spec.tsx +111 -1
  175. package/src/react/media/VideoPlayer.tsx +158 -11
  176. package/src/react/media/VideoTurner.spec.tsx +67 -0
  177. package/src/react/media/VideoTurner.tsx +70 -15
  178. package/src/react/media/denseGallery.css +104 -46
  179. package/src/react/media/hlsSource.recovery.spec.ts +213 -0
  180. package/src/react/media/hlsSource.spec.ts +8 -0
  181. package/src/react/media/hlsSource.ts +189 -1
  182. package/src/react/media/index.ts +14 -0
  183. package/src/react/media/mediaResize.css +127 -54
  184. package/src/react/media/pictureEditor.css +52 -6
  185. package/src/react/media/videoChapterEditor.css +203 -0
  186. package/src/react/media/videoChapters.spec.ts +176 -0
  187. package/src/react/media/videoChapters.ts +181 -0
  188. package/src/react/media-gallery/GalleryTable.tsx +93 -3
  189. package/src/react/media-gallery/MediaGallery.spec.tsx +1156 -10
  190. package/src/react/media-gallery/MediaGallery.tsx +813 -95
  191. package/src/react/media-gallery/MediaMetaEditor.tsx +237 -0
  192. package/src/react/media-gallery/galleryTable.spec.ts +41 -1
  193. package/src/react/media-gallery/index.ts +7 -0
  194. package/src/react/media-gallery/mediaGallery.css +180 -4
  195. package/src/react/media-gallery/mediaMeta.spec.ts +95 -0
  196. package/src/react/media-gallery/mediaMeta.ts +101 -0
  197. package/src/react/media-gallery/playbackPreferences.spec.ts +172 -0
  198. package/src/react/media-gallery/playbackPreferences.ts +100 -0
  199. package/src/react/media-gallery/types.ts +34 -0
  200. package/src/shippedFilesAreTracked.spec.ts +69 -0
  201. package/src/styles-areas/activity-bar.css +30 -0
  202. package/src/styles-areas/analytics.css +672 -0
  203. package/src/styles-areas/auth.css +154 -0
  204. package/src/styles-areas/calendar.css +416 -0
  205. package/src/styles-areas/chart-table.css +41 -0
  206. package/src/styles-areas/charts.css +89 -0
  207. package/src/styles-areas/chat.css +364 -0
  208. package/src/styles-areas/clipboard.css +26 -0
  209. package/src/styles-areas/code-editor.css +130 -0
  210. package/src/styles-areas/companion-link.css +70 -0
  211. package/src/styles-areas/core.css +2503 -0
  212. package/src/styles-areas/dashboard-grid.css +84 -0
  213. package/src/styles-areas/data-table.css +657 -0
  214. package/src/styles-areas/deep-link.css +26 -0
  215. package/src/styles-areas/diff-viewer.css +30 -0
  216. package/src/styles-areas/disk-usage.css +262 -0
  217. package/src/styles-areas/emoji.css +154 -0
  218. package/src/styles-areas/fields.css +582 -0
  219. package/src/styles-areas/file-tree.css +33 -0
  220. package/src/styles-areas/filter-rail.css +235 -0
  221. package/src/styles-areas/folder-tree.css +219 -0
  222. package/src/styles-areas/keep-awake.css +26 -0
  223. package/src/styles-areas/layout-engine.css +239 -0
  224. package/src/styles-areas/markdown.css +87 -0
  225. package/src/styles-areas/master-detail.css +239 -0
  226. package/src/styles-areas/master-lock.css +26 -0
  227. package/src/styles-areas/media-gallery.css +351 -0
  228. package/src/styles-areas/media.css +658 -0
  229. package/src/styles-areas/nav.css +488 -0
  230. package/src/styles-areas/notifications.css +245 -0
  231. package/src/styles-areas/overlays.css +133 -0
  232. package/src/styles-areas/palette.css +140 -0
  233. package/src/styles-areas/pdf-viewer.css +47 -0
  234. package/src/styles-areas/problem-detail.css +97 -0
  235. package/src/styles-areas/rich-text.css +167 -0
  236. package/src/styles-areas/sharing.css +153 -0
  237. package/src/styles-areas/spreadsheet.css +37 -0
  238. package/src/styles-areas/stats.css +281 -0
  239. package/src/styles-areas/test-report.css +133 -0
  240. package/src/styles-areas/virtual.css +30 -0
  241. package/src/styles-areas/wizard.css +381 -0
  242. package/src/styles-areas/workbench.css +543 -0
  243. package/src/styles-areas/workbook-viewer.css +44 -0
  244. package/src/styles-static.css +34 -23
  245. package/src/styles.css +34 -23
  246. package/src/stylesAreas.spec.ts +247 -0
  247. package/src/stylesUtilitiesMatches.spec.ts +6 -77
  248. package/src/testFilesRunInParallel.spec.ts +79 -0
  249. package/src/typecheckCachesAreSeparate.spec.ts +128 -0
  250. package/src/verifyGraph.spec.ts +6 -0
@@ -222,6 +222,20 @@ export { VideoTrimmer } from './VideoTrimmer';
222
222
  // that a server executes, which is the only shape that works on a phone master. See
223
223
  // `videoCuts.ts` for the owner's reasoning.
224
224
  export { VideoCutEditor, type VideoCutEditorProps } from './VideoCutEditor';
225
+ // Naming points in time and KEEPING them — the other half of the same gesture, and a separate
226
+ // model on purpose: a cut list is a request that costs a re-encode, a chapter list is metadata
227
+ // that applies on save. `videoChapters.ts`'s header says why the model is in this package
228
+ // rather than in `cursedbelt-core` beside `videoCuts.ts`.
229
+ export { VideoChapterEditor, type VideoChapterEditorProps } from './VideoChapterEditor';
230
+ export {
231
+ type ChapteredVideo,
232
+ type ChapterSpan,
233
+ chapterProblem,
234
+ chapterSpans,
235
+ MIN_CHAPTER_SECONDS,
236
+ normalizeChapters,
237
+ type VideoChapter,
238
+ } from './videoChapters';
225
239
  // Re-exported from `core/media`, where the model lives so a SERVER can import it without
226
240
  // dragging React in — see the header of `videoCuts.ts`.
227
241
  export {
@@ -9,44 +9,103 @@
9
9
  * and the owner reported the feature as absent while prod was serving it. So both grips are
10
10
  * legible without hover, and both brighten further on hover/focus rather than appearing on
11
11
  * it. Do not "quieten" either back to a hover-only affordance.
12
+ *
13
+ * ── 🔴 2026-09-16: the grips live OUTSIDE the picture ──────────────────────────────────
14
+ * Owner, `ideas/collections-09-14.md`: *"Fix the drag to resize area to no longer overlap
15
+ * part of the horizontal play timeline area that shows what part of the video is playing. It
16
+ * should only be draggable when my mouse pointer is at the bottom right corner of the video
17
+ * player, just like mac windows for other apps work."*
18
+ *
19
+ * The corner grip used to be a 64×64 overlay `position: absolute; right: 0; bottom: 0` INSIDE
20
+ * the clipped frame, riding at `z-index: 12` over Vidstack's control layer, and the gallery
21
+ * reserved a matching `padding-inline-end` gutter in the player's last two control groups so
22
+ * the buttons would not lay out underneath it.
23
+ *
24
+ * 🔴 MEASURED before anything was changed, with that geometry restored in a real browser
25
+ * (`e2e/resize-grip-rail.spec.ts` has the numbers): the gutter DID keep the corner's rectangle
26
+ * off `.vds-time-slider`'s. It is not the thing that was broken, and widening it would have
27
+ * fixed nothing. Three things were, and none of them is reachable from a gutter:
28
+ *
29
+ * 1. `.cbmr-bar` — a full-width strip under the frame whose `padding: 11px 0` with
30
+ * `margin-block: -11px` put 11px of drag surface back inside the picture, spanning the
31
+ * whole scrub bar. Horizontal padding is BESIDE the slider; the bar is UNDER it.
32
+ * 2. A gutter can only shorten a control bar this package styles. A `nativeVideoStage`
33
+ * gallery puts a plain `<video controls>` in that frame and its UA bar is full-width and
34
+ * untouchable, so a grip in the corner lands on the native timeline with no recourse.
35
+ * 3. It was paid for in the thing the owner was complaining about: 64px of scrub bar,
36
+ * surrendered so the grip could sit beside it.
37
+ *
38
+ * So the grips moved off the picture entirely, into `.cbmr-rail` — a strip of chrome BELOW the
39
+ * frame, which is where a Mac window's resize corner is: the window's outside edge, not a
40
+ * patch of its content. Nothing of either grip is inside the frame at any size, and that is
41
+ * the only shape in which "does not overlap the timeline" is a property of the layout rather
42
+ * than a tuning against whatever the current player happens to draw. The gutter rule in
43
+ * `denseGallery.css` went with it, so the time slider is full width again — which is the rest
44
+ * of the same sentence.
12
45
  */
13
46
 
14
47
  /*
15
- * 🔴 ONE number, read from two files.
48
+ * 🔴 ONE number: the grip's reach AND the height of the rail that holds it.
16
49
  *
17
- * The corner grip's size is also the width of the gutter `denseGallery.css` reserves at the
18
- * end of a video player's control bar, so the player's own buttons never lay out underneath
19
- * it. Two literals would desync on the first tweak and the symptom — a grip that covers the
20
- * fullscreen button, or a gutter with nothing in it — is a thing you can only see by hand.
21
- * Declared on `:root` because the gutter rule applies to an ANCESTOR of the grip, and a
22
- * custom property set on the grip itself does not reach back up the tree.
50
+ * 64px is the owner's 2026-08-25 *"make the corner expansion grabbable area bigger … for some
51
+ * reason it's hard to grab"* and it may not go down — see `.cbmr-corner`. The rail is exactly
52
+ * that tall because the rail exists to hold the grip; two literals would desync on the first
53
+ * tweak and the symptom is a grip clipped by its own strip, which only shows up by hand.
54
+ * Declared on `:root` because the coarse-pointer block at the bottom raises it for both.
23
55
  */
24
56
  :root {
25
57
  --cbmr-corner-size: 64px;
26
58
  }
27
59
 
60
+ /* ── The rail: the chrome the grips live in ──────────────────────────────────────────── */
61
+
62
+ /*
63
+ * 🔴 A sibling of the frame, never a child of it. Every consumer renders it directly after
64
+ * the picture's box (`DenseGalleryShell`, `FocusViewer`), and `MediaResize.spec.tsx` fails if
65
+ * a player stops doing so — a grip that drifts back inside the frame is exactly the
66
+ * 2026-09-14 report, and it is invisible to everything except a rendered rectangle.
67
+ *
68
+ * It is the positioning context for the corner, which is what keeps the grip's 64px box
69
+ * bounded by this strip instead of hanging up over the picture.
70
+ */
71
+ .cbmr-rail {
72
+ position: relative;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ inline-size: 100%;
77
+ block-size: var(--cbmr-corner-size, 64px);
78
+ /* Both consumers stack it in a flex COLUMN (`.cbgd-watch-player`, `.inspector`), where a
79
+ default `flex-shrink: 1` would let a short container squeeze the strip and clip the grip
80
+ out of its own bottom-right corner. The picture is what gives way, never the handle. */
81
+ flex: none;
82
+ }
83
+
28
84
  /* ── The corner ─────────────────────────────────────────────────────────────────────── */
29
85
 
30
86
  .cbmr-corner {
31
87
  position: absolute;
32
- right: 0;
33
- bottom: 0;
88
+ inset-inline-end: 0;
89
+ inset-block-end: 0;
34
90
  /*
35
91
  * 🔴 ABOVE the video player's control layer, which is `z-index: 10` (Vidstack's
36
- * `[data-view-type='video'] .vds-controls`) in THIS same stacking context —
37
- * `[data-media-player]` sets `contain: style`, which opens no stacking context, so
38
- * nothing isolates the player's chrome from the frame around it. At the old `3` the
39
- * grip was painted over and, worse, click-blocked by the bottom `.vds-controls-group`
40
- * (`width: 100%`, `pointer-events: auto`) whenever the bar was visible — which is on
41
- * hover, which is precisely when a person reaches for it. That is the owner's
42
- * 2026-08-25 *"for some reason it's hard to grab"*, and it is why this number may not
43
- * go back down without also moving the grip out of the frame.
92
+ * `[data-view-type='video'] .vds-controls`) — `[data-media-player]` sets `contain:
93
+ * style`, which opens no stacking context, so nothing isolates the player's chrome
94
+ * from the frame around it. At the old `3` the grip was painted over and, worse,
95
+ * click-blocked by the bottom `.vds-controls-group` (`width: 100%`, `pointer-events:
96
+ * auto`) whenever the bar was visible — which is on hover, which is precisely when a
97
+ * person reaches for it. That is the owner's 2026-08-25 *"for some reason it's hard to
98
+ * grab"*, and it is why this number may not go back down.
99
+ *
100
+ * Since 2026-09-16 the grip is in `.cbmr-rail`, outside the frame, so it no longer has
101
+ * to win that fight — but a player whose control layer grows a `bottom: -Npx` overhang
102
+ * would re-open it, and 12 costs nothing. Keep it.
44
103
  *
45
104
  * `12` and not more: it must stay UNDER Vidstack's menus (50) and its fullscreen
46
105
  * surface, so opening the settings cog still covers the grip rather than the grip
47
106
  * punching a hole through an open menu.
48
107
  *
49
- * LOCAL stacking only. This lifts the grip inside its own positioned frame and must
108
+ * LOCAL stacking only. This lifts the grip inside its own positioned rail and must
50
109
  * never reach the app-wide bands — a `--z-` band here would raise the grip over the
51
110
  * sticky header and every overlay above it.
52
111
  * guardrails-ignore no-raw-zindex: local stacking inside a positioned media frame. */
@@ -54,35 +113,37 @@
54
113
  display: flex;
55
114
  align-items: flex-end;
56
115
  justify-content: flex-end;
57
- /* The target, and the gutter the control bar leaves for it — see `--cbmr-corner-size`.
58
- Well over DESIGN.md §5's 44px floor, deliberately: the owner asked for a bigger grab
59
- area in the same breath as reporting he could not hit it. The padding is the reach,
60
- the glyph is the mark. */
116
+ /* The target. Well over DESIGN.md §5's 44px floor, deliberately: the owner asked for a
117
+ bigger grab area in the same breath as reporting he could not hit it. The padding is
118
+ the reach, the glyph is the mark. It fills the rail exactly — see `--cbmr-corner-size`. */
61
119
  inline-size: var(--cbmr-corner-size, 64px);
62
120
  block-size: var(--cbmr-corner-size, 64px);
63
- padding: 0 4px 4px 0;
121
+ padding: 0 6px 6px 0;
64
122
  box-sizing: border-box;
65
123
  cursor: nwse-resize;
66
124
  touch-action: none;
67
125
  border: 0;
68
126
  background: none;
69
- /* A soft scrim rather than a filled chip: the grip must be legible over a white sky and
70
- over black letterboxing, and a corner-anchored gradient does that without drawing a
71
- box on top of the picture. */
72
- border-end-end-radius: inherit;
73
127
  }
74
128
 
129
+ /*
130
+ * The hover plate.
131
+ *
132
+ * 🔴 A TOKEN, where this used to be a hardcoded black radial scrim. The scrim was right while
133
+ * the grip rode on arbitrary media — a photograph has no theme, so app colours tinted it. In
134
+ * the rail the grip rides on app chrome like every other control, so it takes the app's
135
+ * colours and the two `guardrails-ignore no-hardcoded-colors` suppressions this rule used to
136
+ * need are gone with it.
137
+ */
75
138
  .cbmr-corner::before {
76
139
  content: '';
77
140
  position: absolute;
78
- inset: 0;
79
- border-end-end-radius: inherit;
80
- /* Black, not a surface token, and for the same reason `.inspector__viewport` and
81
- the gallery's action pills hardcode theirs: this scrim rides on ARBITRARY MEDIA, which has no
82
- theme. A token here would tint the grip with app chrome over somebody's photograph.
83
- guardrails-ignore no-hardcoded-colors: see above. */
84
- /* guardrails-ignore no-hardcoded-colors: one line, because the excuse covers the LINE. */
85
- background: radial-gradient(circle at 100% 100%, rgb(0 0 0 / 0.55) 0%, rgb(0 0 0 / 0.34) 52%, rgb(0 0 0 / 0) 80%);
141
+ inset-inline-end: 2px;
142
+ inset-block-end: 2px;
143
+ inline-size: 30px;
144
+ block-size: 30px;
145
+ border-radius: var(--radius-md, 0.5rem);
146
+ background: transparent;
86
147
  pointer-events: none;
87
148
  transition: background 120ms ease;
88
149
  }
@@ -93,10 +154,9 @@
93
154
  block-size: 16px;
94
155
  overflow: visible;
95
156
  fill: none;
96
- /* White, not a token: this rides on arbitrary media, which has no theme — the same
97
- reasoning the stage's overlay pills use.
98
- guardrails-ignore no-hardcoded-colors: see above. */
99
- stroke: rgb(255 255 255 / 0.92);
157
+ /* Legible at rest — see the file header. `--muted-foreground`, never `--border`, which is
158
+ the colour of a hairline nobody is meant to look at. */
159
+ stroke: var(--muted-foreground, currentColor);
100
160
  stroke-width: 1.6;
101
161
  stroke-linecap: round;
102
162
  transition: stroke 120ms ease, transform 120ms ease;
@@ -104,15 +164,13 @@
104
164
 
105
165
  .cbmr-corner:hover .cbmr-corner-glyph,
106
166
  .cbmr-corner:focus-visible .cbmr-corner-glyph {
107
- /* guardrails-ignore no-hardcoded-colors: the hover state of the white glyph above. */
108
- stroke: rgb(255 255 255 / 1);
167
+ stroke: var(--foreground, var(--muted-foreground));
109
168
  transform: scale(1.15);
110
169
  }
111
170
 
112
171
  .cbmr-corner:hover::before,
113
172
  .cbmr-corner:focus-visible::before {
114
- /* guardrails-ignore no-hardcoded-colors: the hover state of the scrim above. */
115
- background: radial-gradient(circle at 100% 100%, rgb(0 0 0 / 0.7) 0%, rgb(0 0 0 / 0.45) 45%, rgb(0 0 0 / 0) 75%);
173
+ background: var(--border);
116
174
  }
117
175
 
118
176
  .cbmr-corner:focus-visible {
@@ -122,18 +180,27 @@
122
180
 
123
181
  /* ── The bottom edge ────────────────────────────────────────────────────────────────── */
124
182
 
183
+ /*
184
+ * 🔴 COARSE POINTERS ONLY, and that is a decision, not a tidy-up — see `MediaResize.tsx`'s
185
+ * header for the whole of it. *"It should only be draggable when my mouse pointer is at the
186
+ * bottom right corner"* is a verdict on this bar as much as on the corner's old position; the
187
+ * one reason it was ever kept beside the corner is about a THUMB, so it survives exactly where
188
+ * a thumb is.
189
+ */
125
190
  .cbmr-bar {
191
+ display: none;
126
192
  inline-size: 100%;
127
193
  block-size: 22px;
128
- display: flex;
129
194
  align-items: center;
130
195
  justify-content: center;
131
196
  cursor: ns-resize;
132
197
  touch-action: none;
133
- /* The generous half of the 44px target, without 44px of visible gutter. */
198
+ /* The generous half of the 44px target, without 44px of visible gutter. 🔴 No negative
199
+ margin pulling it back out of the rail: that is how invisible padding came to hang over
200
+ the picture's bottom edge and over the transport below it, which is the overlap the
201
+ 2026-09-14 report is about. The rail is tall enough to hold the reach. */
134
202
  padding: 11px 0;
135
203
  box-sizing: content-box;
136
- margin-block: -11px;
137
204
  border: 0;
138
205
  background: none;
139
206
  }
@@ -172,15 +239,17 @@
172
239
  /*
173
240
  * ── A finger is not a mouse ──────────────────────────────────────────────────────────
174
241
  * Owner, 2026-08-25 and again 2026-09-08: *"make the corner expansion grabbable area
175
- * bigger … for some reason it's hard to grab"*. The z-index half of that is above; this is
176
- * the other half, and it is the one a desktop measurement cannot see. 64px clears
177
- * `DESIGN.md` §5's 44px floor for a POINTER; a fingertip's contact patch is ~9mm, which on a
178
- * phone is most of that box, and the grip sits in the one corner a thumb reaches across the
179
- * whole screen to get to.
242
+ * bigger … for some reason it's hard to grab"*. 64px clears `DESIGN.md` §5's 44px floor for a
243
+ * POINTER; a fingertip's contact patch is ~9mm, which on a phone is most of that box, and the
244
+ * grip sits in the one corner a thumb reaches across the whole screen to get to.
180
245
  *
181
246
  * Coarse pointers only, so the desktop look — which the owner explicitly said was good — is
182
- * untouched. `--cbmr-corner-size` is read by `denseGallery.css` for the gutter it reserves in
183
- * the player's control bar, so widening it here widens that too and the two cannot desync.
247
+ * untouched. Raising `--cbmr-corner-size` raises the rail with it, so the bigger grip still
248
+ * fits inside its strip rather than growing back up over the picture.
249
+ *
250
+ * 🔴 And this is where `.cbmr-bar` exists at all: the corner is a small target for a thumb,
251
+ * and on a portrait clip the picture's bottom edge is the one part of the stage whose position
252
+ * does not move with the file.
184
253
  */
185
254
  @media (pointer: coarse) {
186
255
  :root {
@@ -192,9 +261,13 @@
192
261
  inline-size: 22px;
193
262
  block-size: 22px;
194
263
  }
264
+ .cbmr-corner::before {
265
+ inline-size: 40px;
266
+ block-size: 40px;
267
+ }
195
268
  /* Same reasoning for the bar: the ink stays a hairline, the reach grows. */
196
269
  .cbmr-bar {
270
+ display: flex;
197
271
  padding: 15px 0;
198
- margin-block: -15px;
199
272
  }
200
273
  }
@@ -263,12 +263,58 @@
263
263
  cursor: default;
264
264
  }
265
265
 
266
- /* A quarter-turned clip is still laid out in its ORIGINAL box, so its width is bounded by the
267
- container's HEIGHT and vice versa. Container query units are the only way to say that. Set
268
- only for the ODD turns — a half turn does not change the box and pays for none of this. */
269
- .cbpe-frame-film[data-turned='true'] video {
266
+ /* The film, sized exactly like the `<img>` the picture editor puts in the same frame: contained,
267
+ centred, never overflowing the box it is previewed in. Written out here because `.cbpe-frame
268
+ img` cannot say it for an element that is not an image, and without it the preview is whatever
269
+ size the browser's default happens to make it — which for a phone clip is a postage stamp in
270
+ the middle of a black frame, i.e. the thing this dialog is for is invisible. */
271
+ .cbpe-frame video {
272
+ max-width: 100%;
273
+ max-height: 100%;
274
+ object-fit: contain;
275
+ transform-origin: center;
276
+ }
277
+
278
+ /* A quarter-turned clip is still laid out in its ORIGINAL box, so its width has to be read from
279
+ the container's HEIGHT and vice versa. Container query units are the only way to say that.
280
+ Set only for the ODD turns — a half turn does not change the box and pays for none of this.
281
+
282
+ 🔴 An explicit SIZE, not a `max-*` bound. The same correction, for the same reason, as
283
+ `.cbmg-stage-turn[data-turned='true']` in `mediaGallery.css` (which documents the measurement):
284
+ a bound can only shrink, so a turned clip stayed whatever size it already was and the preview
285
+ showed a pillar the stage was about to show full-width. The preview and the stage have to
286
+ agree — a preview that under-sells the turn is a preview somebody rejects a correct edit on.
287
+ `img` as well as `video`: a clip that will not play falls back to its poster, and the poster
288
+ is turned by the same transform. */
289
+ .cbpe-frame-film[data-turned='true'] video,
290
+ .cbpe-frame-film[data-turned='true'] img {
270
291
  max-width: none;
271
292
  max-height: none;
272
- max-block-size: 100cqi;
273
- max-inline-size: 100cqb;
293
+ max-block-size: none;
294
+ max-inline-size: none;
295
+ width: 100cqb;
296
+ height: 100cqi;
297
+ aspect-ratio: auto;
298
+ object-fit: contain;
299
+ }
300
+
301
+ /* Why there is no moving picture, said ON the frame that would otherwise be black — the owner's
302
+ *"I see a black screen"*, which he could only report as a black screen because the dialog said
303
+ nothing. Centred over the ground rather than under the frame so it is read as being about the
304
+ picture, and `pointer-events: none` so it never eats a press meant for the film. */
305
+ .cbpe-frame-note {
306
+ position: absolute;
307
+ inset-inline: 1.5rem;
308
+ top: 50%;
309
+ transform: translateY(-50%);
310
+ margin: 0;
311
+ text-align: center;
312
+ text-wrap: balance;
313
+ font-size: 13px;
314
+ line-height: 1.5;
315
+ /* guardrails-ignore no-hardcoded-colors: sits ON the picture ground, which is true black in
316
+ both themes — the same argument as `.cbpe-hint` above. */
317
+ color: rgb(255 255 255 / 0.82);
318
+ text-shadow: 0 1px 2px rgb(0 0 0 / 0.6);
319
+ pointer-events: none;
274
320
  }
@@ -0,0 +1,203 @@
1
+ /* Plain CSS, colocated with `VideoChapterEditor.tsx` and imported by it — the same
2
+ convention, and the same reason, as `videoCutEditor.css`: the editor is lazily imported
3
+ from the gallery, so these bytes ride the gallery's chunk instead of landing in whichever
4
+ ENTRY stylesheet happened to scan a utility class. Every colour is a theme token. */
5
+
6
+ .cbch {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: 0.75rem;
10
+ min-height: 0;
11
+ }
12
+
13
+ .cbch-video {
14
+ inline-size: 100%;
15
+ max-block-size: 44vh;
16
+ background: var(--surface-2, var(--background));
17
+ border-radius: var(--radius-md, 8px);
18
+ }
19
+
20
+ /* The one authoring row: name the section, then mark it. In that order, because the button
21
+ is disabled until there is a name — see the component. */
22
+ .cbch-add {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 0.5rem;
26
+ flex-wrap: wrap;
27
+ }
28
+
29
+ .cbch-add-field {
30
+ flex: 1 1 14rem;
31
+ min-inline-size: 0;
32
+ }
33
+
34
+ /* Tabular figures, and this is not cosmetic: the playhead readout changes ten times a second,
35
+ and proportional digits make the whole row shuffle sideways under the controls beside it
36
+ while a clip plays. */
37
+ .cbch-playhead {
38
+ font-variant-numeric: tabular-nums;
39
+ font-size: 0.875rem;
40
+ color: var(--muted-foreground);
41
+ }
42
+
43
+ /* ── The strip ───────────────────────────────────────────────────────────────
44
+ What the scrub bar will look like once this is saved: the named sections, in place and to
45
+ scale. Not a scrub bar itself — the `<video>`'s own controls are that, and a second
46
+ draggable timeline would be two controls fighting for one gesture. */
47
+ .cbch-strip {
48
+ position: relative;
49
+ display: flex;
50
+ list-style: none;
51
+ margin: 0;
52
+ padding: 0;
53
+ inline-size: 100%;
54
+ block-size: 40px;
55
+ border-radius: var(--radius-md, 8px);
56
+ background: var(--surface-3, var(--surface-2));
57
+ border: 1px solid var(--border);
58
+ overflow: hidden;
59
+ }
60
+
61
+ /* The list item carries the WIDTH — the section's duration — and the button fills it. Split
62
+ in two so the width lives on the flex child rather than on a control that also has padding. */
63
+ .cbch-span-item {
64
+ min-inline-size: 0;
65
+ flex: 0 0 auto;
66
+ block-size: 100%;
67
+ }
68
+
69
+ /* The unnamed opening, when the first chapter is not at 0:00. Drawn, but not a control: there
70
+ is nothing to jump to and nothing to rename — it is the part that has no section yet. */
71
+ .cbch-lead {
72
+ flex: 0 0 auto;
73
+ block-size: 100%;
74
+ border-inline-end: 1px solid var(--border);
75
+ background: repeating-linear-gradient(
76
+ 135deg,
77
+ transparent 0 6px,
78
+ color-mix(in srgb, var(--muted-foreground) 14%, transparent) 6px 12px
79
+ );
80
+ }
81
+
82
+ .cbch-span {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 0.25rem;
86
+ /* `min-inline-size: 0` and no flex-grow: the inline width set per section is the truth. */
87
+ inline-size: 100%;
88
+ block-size: 100%;
89
+ padding-inline: 0.375rem;
90
+ border: 0;
91
+ /* A hairline between sections, so two adjacent ones read as two rather than one. */
92
+ border-inline-end: 1px solid var(--border);
93
+ background: var(--surface-1, var(--background));
94
+ color: var(--foreground);
95
+ font-size: 0.75rem;
96
+ white-space: nowrap;
97
+ overflow: hidden;
98
+ cursor: pointer;
99
+ transition: background 120ms ease;
100
+ }
101
+
102
+ .cbch-span-item:last-of-type .cbch-span {
103
+ border-inline-end: 0;
104
+ }
105
+
106
+ .cbch-span:hover {
107
+ background: var(--surface-2);
108
+ }
109
+
110
+ .cbch-span:focus-visible {
111
+ outline: 2px solid var(--ring, var(--muted-foreground));
112
+ outline-offset: -2px;
113
+ }
114
+
115
+ .cbch-span-label {
116
+ overflow: hidden;
117
+ text-overflow: ellipsis;
118
+ }
119
+
120
+ .cbch-strip-head {
121
+ position: absolute;
122
+ inset-block: 0;
123
+ inline-size: 2px;
124
+ background: var(--foreground);
125
+ /* Over the sections, but it must never eat a click meant for one. */
126
+ pointer-events: none;
127
+ }
128
+
129
+ .cbch-summary {
130
+ font-size: 0.875rem;
131
+ color: var(--muted-foreground);
132
+ margin: 0;
133
+ }
134
+
135
+ .cbch-list {
136
+ list-style: none;
137
+ margin: 0;
138
+ padding: 0;
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 0.25rem;
142
+ max-block-size: 24vh;
143
+ /* guardrails-ignore no-raw-overflow: a Tailwind-free `.cbch-` list inside a lazily
144
+ imported editor, not a ScrollArea consumer — same as `videoCutEditor.css`. */
145
+ overflow-y: auto;
146
+ }
147
+
148
+ .cbch-row {
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 0.5rem;
152
+ padding-inline: 0.25rem;
153
+ border-radius: var(--radius-sm, 6px);
154
+ }
155
+
156
+ .cbch-row:hover {
157
+ background: var(--surface-2);
158
+ }
159
+
160
+ .cbch-row-goto {
161
+ flex: 0 0 auto;
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 0.375rem;
165
+ /* DESIGN.md §5's touch floor — a row you jump the playhead with is a real control. */
166
+ min-block-size: 44px;
167
+ padding-inline: 0.25rem;
168
+ border: 0;
169
+ background: none;
170
+ color: var(--muted-foreground);
171
+ font-variant-numeric: tabular-nums;
172
+ font-size: 0.875rem;
173
+ text-align: start;
174
+ cursor: pointer;
175
+ }
176
+
177
+ .cbch-row-goto:hover {
178
+ color: var(--foreground);
179
+ }
180
+
181
+ .cbch-row-goto:focus-visible {
182
+ outline: 2px solid var(--ring, var(--muted-foreground));
183
+ outline-offset: -2px;
184
+ border-radius: var(--radius-sm, 6px);
185
+ }
186
+
187
+ .cbch-row-title {
188
+ flex: 1 1 auto;
189
+ min-inline-size: 0;
190
+ }
191
+
192
+ .cbch-empty {
193
+ margin: 0;
194
+ padding-block: 0.75rem;
195
+ text-align: center;
196
+ font-size: 0.875rem;
197
+ color: var(--muted-foreground);
198
+ }
199
+
200
+ .cbch-note {
201
+ font-size: 0.875rem;
202
+ color: var(--muted-foreground);
203
+ }