rvlib-shipkit 2.2.0 → 2.4.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 (140) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/package.json +1 -1
  3. package/src/agent-time/mask.ts +43 -0
  4. package/src/agent-time/render.ts +187 -0
  5. package/src/agent-time/report.ts +335 -0
  6. package/src/agent-time/shape.ts +271 -0
  7. package/src/agent-time/signals.ts +84 -0
  8. package/src/agent-time/store.ts +379 -0
  9. package/src/agent-time/timeline.ts +218 -0
  10. package/src/agent-time/turns.ts +135 -0
  11. package/src/assets/kits/identity-kit.ts +5 -2
  12. package/src/cli/cmd_Commands.ts +9 -0
  13. package/src/cli/cmd_Review.ts +974 -0
  14. package/src/cli/cmd_Sound.ts +209 -0
  15. package/src/cli/cmd_Time.ts +112 -0
  16. package/src/cli/cmd_Trailer.ts +264 -6
  17. package/src/cli/kit/verb.ts +22 -4
  18. package/src/cli.ts +8 -4
  19. package/src/collections/collection-types.ts +6 -1
  20. package/src/collections/providers.ts +3 -0
  21. package/src/collections/review.ts +70 -0
  22. package/src/core/app-icon.ts +10 -6
  23. package/src/core/layout.ts +2 -0
  24. package/src/reflect/prompt-corpus.ts +15 -119
  25. package/src/reflect/transcript.ts +254 -0
  26. package/src/repo-config.ts +3 -0
  27. package/src/review/anim.ts +228 -0
  28. package/src/review/auto.ts +141 -0
  29. package/src/review/format.ts +158 -0
  30. package/src/review/http.ts +23 -0
  31. package/src/review/marks.ts +43 -0
  32. package/src/review/media.ts +45 -0
  33. package/src/review/model.ts +896 -0
  34. package/src/review/project.ts +126 -0
  35. package/src/review/role.ts +48 -0
  36. package/src/review/routes.ts +239 -0
  37. package/src/review/store.ts +1011 -0
  38. package/src/review/threads.ts +237 -0
  39. package/src/review/trailer-desk.ts +385 -0
  40. package/src/review/trailer-model.ts +183 -0
  41. package/src/review/trailer-routes.ts +136 -0
  42. package/src/review/unit.ts +183 -0
  43. package/src/review/watchers.ts +54 -0
  44. package/src/serve/embed.ts +35 -0
  45. package/src/serve/hub-app/activity-view.tsx +1 -1
  46. package/src/serve/hub-app/activity.css +0 -4
  47. package/src/serve/hub-app/admin-svc.ts +1 -1
  48. package/src/serve/hub-app/admin-view.tsx +1 -1
  49. package/src/serve/hub-app/dock-view.tsx +5 -4
  50. package/src/serve/hub-app/hash-route.ts +36 -1
  51. package/src/serve/hub-app/header-bar.tsx +3 -1
  52. package/src/serve/hub-app/home-st.ts +17 -3
  53. package/src/serve/hub-app/home-strip.tsx +3 -2
  54. package/src/serve/hub-app/home.html +1 -0
  55. package/src/serve/hub-app/hub-app.tsx +80 -11
  56. package/src/serve/hub-app/hub.css +83 -14
  57. package/src/serve/hub-app/icons.tsx +15 -0
  58. package/src/serve/hub-app/index.html +1 -0
  59. package/src/serve/hub-app/insights-view.tsx +4 -3
  60. package/src/serve/hub-app/keys.ts +14 -10
  61. package/src/serve/hub-app/layout-st.ts +63 -2
  62. package/src/serve/hub-app/md.ts +32 -0
  63. package/src/serve/hub-app/planned-view.tsx +3 -2
  64. package/src/serve/hub-app/planned.css +0 -1
  65. package/src/serve/hub-app/project-list.css +26 -6
  66. package/src/serve/hub-app/project-list.tsx +46 -10
  67. package/src/serve/hub-app/projects-svc.ts +7 -2
  68. package/src/serve/hub-app/repo-sections.ts +15 -3
  69. package/src/serve/hub-app/repo-st.ts +61 -4
  70. package/src/serve/hub-app/review/clip-st.ts +258 -0
  71. package/src/serve/hub-app/review/kind-view.tsx +240 -0
  72. package/src/serve/hub-app/review/kit.tsx +113 -0
  73. package/src/serve/hub-app/review/review-answer.tsx +269 -0
  74. package/src/serve/hub-app/review/review-board.tsx +46 -0
  75. package/src/serve/hub-app/review/review-list.tsx +156 -0
  76. package/src/serve/hub-app/review/review-page.tsx +96 -0
  77. package/src/serve/hub-app/review/review-st.ts +902 -0
  78. package/src/serve/hub-app/review/review-views.tsx +648 -0
  79. package/src/serve/hub-app/review/trailer-page.tsx +416 -0
  80. package/src/serve/hub-app/review/trailer-st.ts +602 -0
  81. package/src/serve/hub-app/review/trailer-timeline.tsx +207 -0
  82. package/src/serve/hub-app/review.css +389 -0
  83. package/src/serve/hub-app/theme-st.ts +56 -0
  84. package/src/serve/hub-app/tree-build.ts +4 -0
  85. package/src/serve/hub-app/tree-row.tsx +1 -1
  86. package/src/serve/hub-app/tree-svc.ts +4 -0
  87. package/src/serve/hub-app/wiki-st.ts +190 -0
  88. package/src/serve/hub-app/wikis-view.tsx +134 -5
  89. package/src/serve/hub-runtime.ts +9 -7
  90. package/src/serve/project-gutter-route.ts +25 -1
  91. package/src/serve/project-gutter.ts +1 -1
  92. package/src/serve/same-origin.ts +5 -1
  93. package/src/serve/serve-hub.ts +15 -0
  94. package/src/shipkit-files.ts +9 -5
  95. package/src/shot/client.ts +17 -5
  96. package/src/shot/shooter.ts +19 -3
  97. package/src/shot/shot-ask.ts +9 -1
  98. package/src/shot/stepped-clip.ts +53 -0
  99. package/src/trailer/adapter.ts +90 -6
  100. package/src/trailer/build.ts +459 -59
  101. package/src/trailer/ffmpeg.ts +22 -0
  102. package/src/trailer/fonts.ts +55 -0
  103. package/src/trailer/footage.ts +112 -43
  104. package/src/trailer/grammar.ts +864 -127
  105. package/src/trailer/graph.ts +308 -49
  106. package/src/trailer/holes.ts +72 -0
  107. package/src/trailer/load-unit.ts +20 -3
  108. package/src/trailer/look.ts +20 -3
  109. package/src/trailer/motion.ts +207 -0
  110. package/src/trailer/passage.ts +129 -0
  111. package/src/trailer/plan.ts +82 -23
  112. package/src/trailer/prune.ts +204 -0
  113. package/src/trailer/score/compose.ts +256 -0
  114. package/src/trailer/score/dsp.ts +272 -0
  115. package/src/trailer/score/gm-render.ts +43 -0
  116. package/src/trailer/score/gm.ts +90 -0
  117. package/src/trailer/score/midi.ts +143 -0
  118. package/src/trailer/score/palette.ts +188 -0
  119. package/src/trailer/score/pattern.ts +98 -0
  120. package/src/trailer/score/score.ts +228 -0
  121. package/src/trailer/score/sound.ts +123 -0
  122. package/src/trailer/score/synth.ts +383 -0
  123. package/src/trailer/sound/audio.ts +148 -0
  124. package/src/trailer/sound/brand.ts +42 -0
  125. package/src/trailer/sound/comfy.ts +38 -0
  126. package/src/trailer/sound/eleven.ts +72 -0
  127. package/src/trailer/sound/fluid.ts +71 -0
  128. package/src/trailer/sound/gen.ts +139 -0
  129. package/src/trailer/sound/index.ts +60 -0
  130. package/src/trailer/sound/presets.ts +69 -0
  131. package/src/trailer/sound/run.ts +189 -0
  132. package/src/trailer/states.ts +76 -0
  133. package/src/trailer/stitch.ts +92 -9
  134. package/src/trailer/titles.ts +471 -150
  135. package/src/trailer/url.ts +18 -3
  136. package/src/trailer/vocab.ts +34 -4
  137. package/src/trailer/words.ts +275 -0
  138. package/src/wiki/graph-mount.ts +3 -0
  139. package/src/wiki/graph-page.ts +45 -9
  140. package/src/wiki/graph-pages.ts +128 -0
@@ -0,0 +1,207 @@
1
+ // the trailer page's timeline: the sections, the shots, the captions and the notes of the render on screen, a
2
+ // playhead, a click or a drag to seek, a hover with the shot's frame from the contact strip; then every shot as a card
3
+ import { observer } from 'mobx-react-lite'
4
+ import { useEffect, useRef, useState, type CSSProperties, type RefObject } from 'react'
5
+ import { clock, shotAt, type ShotSpan, type Timeline } from '../../../review/trailer-model.ts'
6
+ import { captionText, type PlacedNote, type TrailerSt } from './trailer-st.ts'
7
+
8
+ const RULER = 18
9
+ const SECTIONS = 22
10
+ const SHOTS = 46
11
+ const CAPTIONS = 22
12
+ const GAP = 3
13
+ const HEIGHT = RULER + SECTIONS + SHOTS + CAPTIONS + GAP * 3
14
+ const ROW = { ruler: 0, sections: RULER + GAP, shots: RULER + SECTIONS + GAP * 2, captions: RULER + SECTIONS + SHOTS + GAP * 3 }
15
+
16
+ /** a section's hue, one of the page's kind hues by its order: the shot blocks of a section share it */
17
+ export const hueOf = (tl: Timeline, section: string | null): string => {
18
+ const i = section === null ? -1 : tl.sections.findIndex((s) => s.name === section)
19
+ return i < 0 ? 'hue-none' : `hue-k${(i % 6) + 1}`
20
+ }
21
+
22
+ /** a shot's frame from the render's contact strip, `w` pixels wide; a plain tile when there is no strip */
23
+ export const Thumb = observer(({ st, index, w, style }: { st: TrailerSt; index: number; w: number; style?: CSSProperties }) => {
24
+ const s = st.shown
25
+ const url = st.stripUrl
26
+ const g = s?.strip
27
+ const h = Math.round((w * (s?.timeline.size[1] ?? 9)) / (s?.timeline.size[0] ?? 16))
28
+ const base: CSSProperties = { width: w, height: h, ...style }
29
+ if (!url || !g || !s) return <div className="tr-thumb" style={base} />
30
+ const n = s.timeline.shots.length
31
+ const rows = Math.ceil(n / g.cols)
32
+ const k = w / g.tileW
33
+ const col = index % g.cols
34
+ const row = Math.floor(index / g.cols)
35
+ return (
36
+ <div className="tr-thumb" style={{
37
+ ...base,
38
+ backgroundImage: `url(${url})`,
39
+ backgroundSize: `${(g.cols * g.tileW + (g.cols - 1) * g.pad) * k}px ${(rows * g.tileH + (rows - 1) * g.pad) * k}px`,
40
+ backgroundPosition: `${-col * (g.tileW + g.pad) * k}px ${-row * (g.tileH + g.pad) * k}px`,
41
+ }} />
42
+ )
43
+ })
44
+
45
+ /** the ruler's step: the smallest that leaves room for a label */
46
+ function stepOf(pps: number): number {
47
+ for (const s of [0.5, 1, 2, 5, 10, 15, 30, 60]) if (s * pps >= 56) return s
48
+ return 120
49
+ }
50
+
51
+ export const TrailerTimeline = observer(({ st }: { st: TrailerSt }) => {
52
+ const tl = st.timeline
53
+ const scroller = useRef<HTMLDivElement>(null)
54
+ const [width, setWidth] = useState(800)
55
+ const [hover, setHover] = useState<{ t: number; x: number } | null>(null)
56
+ useEffect(() => {
57
+ const el = scroller.current
58
+ if (!el) return
59
+ const ro = new ResizeObserver(() => setWidth(el.clientWidth))
60
+ ro.observe(el)
61
+ setWidth(el.clientWidth)
62
+ return () => ro.disconnect()
63
+ }, [])
64
+ // a zoom keeps the playhead where the eye is
65
+ useEffect(() => {
66
+ const el = scroller.current
67
+ if (!el || !tl) return
68
+ el.scrollLeft = Math.max(0, (st.time / tl.total) * width * st.zoom - width / 2)
69
+ }, [st.zoom, width, tl, st])
70
+ if (!tl) return null
71
+ const W = Math.max(width, width * st.zoom)
72
+ const xOf = (t: number): number => (t / tl.total) * W
73
+ const tOf = (clientX: number): number => {
74
+ const el = scroller.current
75
+ if (!el) return 0
76
+ const r = el.getBoundingClientRect()
77
+ return Math.max(0, Math.min(tl.total, ((clientX - r.left + el.scrollLeft) / W) * tl.total))
78
+ }
79
+ const step = stepOf(W / tl.total)
80
+ const ticks: number[] = []
81
+ for (let t = 0; t <= tl.total + 1e-6; t += step) ticks.push(t)
82
+ const cur = st.shot
83
+ const lit = new Set(st.captionsKey.split('\n'))
84
+ return (
85
+ <div className="tr-timelinewrap">
86
+ <div ref={scroller} className="tr-timeline"
87
+ onPointerDown={(e) => {
88
+ if ((e.target as Element).closest('[data-note]')) return
89
+ e.currentTarget.setPointerCapture(e.pointerId)
90
+ st.seekPaused(tOf(e.clientX))
91
+ }}
92
+ onPointerMove={(e) => {
93
+ const t = tOf(e.clientX)
94
+ if (e.buttons & 1 && e.currentTarget.hasPointerCapture(e.pointerId)) st.seekPaused(t)
95
+ setHover({ t, x: e.clientX })
96
+ }}
97
+ onPointerLeave={() => setHover(null)}>
98
+ <div className="tr-track" style={{ width: W, height: HEIGHT }}>
99
+ {ticks.map((t) => (
100
+ <div key={t} className="tr-tick" style={{ left: xOf(t), top: ROW.ruler, height: HEIGHT }}>
101
+ <span>{t >= 60 ? `${Math.floor(t / 60)}:${String(Math.floor(t % 60)).padStart(2, '0')}` : `${t}s`}</span>
102
+ </div>
103
+ ))}
104
+ {tl.sections.map((s) => (
105
+ <div key={`${s.name}@${s.start}`} className={`tr-section ${hueOf(tl, s.name)}${st.section?.name === s.name ? ' on' : ''}`} title={`${s.name} · ${clock(s.start)} → ${clock(s.end)}`}
106
+ style={{ left: xOf(s.start) + 1, width: Math.max(2, xOf(s.end) - xOf(s.start) - 2), top: ROW.sections, height: SECTIONS, lineHeight: `${SECTIONS}px` }}>
107
+ {s.name}
108
+ </div>
109
+ ))}
110
+ {tl.shots.map((s, i) => <ShotBlock key={`${s.name}@${i}`} s={s} i={i} x={xOf(s.start)} w={xOf(s.end) - xOf(s.start)} on={cur === s} hue={hueOf(tl, s.section)} />)}
111
+ {tl.shots.flatMap((s) =>
112
+ s.captions.map((c, j) => (
113
+ <div key={`${s.name}-${j}`} className={`tr-caption${lit.has(`${c.start}|${c.text}`) ? ' on' : ''}`} title={captionText(c)}
114
+ style={{ left: xOf(c.start), width: Math.max(2, xOf(Math.min(c.end, tl.total)) - xOf(c.start)), top: ROW.captions, height: CAPTIONS, lineHeight: `${CAPTIONS}px` }}>
115
+ {captionText(c).replaceAll('\n', ' ')}
116
+ </div>
117
+ )),
118
+ )}
119
+ {st.here.map((n) => <NotePin key={n.note.id} st={st} n={n} x={xOf(n.at ?? 0)} />)}
120
+ {st.draft ? <div className="tr-draftmark" style={{ left: xOf(st.draft.time) - 1, height: HEIGHT }} /> : null}
121
+ <Playhead st={st} W={W} total={tl.total} scroller={scroller} />
122
+ </div>
123
+ </div>
124
+ {hover ? <HoverCard st={st} t={hover.t} x={hover.x} scroller={scroller} /> : null}
125
+ </div>
126
+ )
127
+ })
128
+
129
+ const ShotBlock = ({ s, i, x, w, on, hue }: { s: ShotSpan; i: number; x: number; w: number; on: boolean; hue: string }) => (
130
+ <div className={`tr-shot ${hue}${on ? ' on' : ''}${i % 2 ? ' odd' : ''}`} title={`${s.name} · ${clock(s.start)} · ${s.length} s`}
131
+ style={{ left: x + 0.5, width: Math.max(1, w - 1), top: ROW.shots, height: SHOTS }}>
132
+ {w > 26 ? s.name : ''}
133
+ {w > 44 ? <div className="tr-shottime">{clock(s.start)}</div> : null}
134
+ </div>
135
+ )
136
+
137
+ const NotePin = observer(({ st, n, x }: { st: TrailerSt; n: PlacedNote; x: number }) => {
138
+ const on = st.selected === n.note.id
139
+ return (
140
+ <>
141
+ <div className={`tr-noteline ns-${n.note.state}${on ? ' on' : ''}`} style={{ left: x, top: ROW.sections, height: HEIGHT - ROW.sections }} />
142
+ <div data-note className={`tr-notepin ns-${n.note.state}${on ? ' on' : ''}`} title={`#${n.note.n} · ${n.note.text}`} style={{ left: x - 8 }}
143
+ onPointerDown={(e) => e.stopPropagation()} onClick={() => st.goTo(n)} />
144
+ </>
145
+ )
146
+ })
147
+
148
+ /** the playhead; while it plays, the timeline scrolls to keep it in view */
149
+ const Playhead = observer(({ st, W, total, scroller }: { st: TrailerSt; W: number; total: number; scroller: RefObject<HTMLDivElement | null> }) => {
150
+ const x = (st.time / total) * W
151
+ useEffect(() => {
152
+ const el = scroller.current
153
+ if (!el) return
154
+ if (x < el.scrollLeft + 8 || x > el.scrollLeft + el.clientWidth - 24) el.scrollLeft = Math.max(0, x - el.clientWidth * 0.2)
155
+ }, [x, scroller])
156
+ return <div className="tr-playhead" style={{ left: x - 1, height: HEIGHT }}><div className="tr-playknob" /></div>
157
+ })
158
+
159
+ /** under the mouse: the shot's frame, its name, the time */
160
+ const HoverCard = observer(({ st, t, x, scroller }: { st: TrailerSt; t: number; x: number; scroller: RefObject<HTMLDivElement | null> }) => {
161
+ const tl = st.timeline
162
+ if (!tl) return null
163
+ const s = shotAt(tl, t)
164
+ if (!s) return null
165
+ const i = tl.shots.indexOf(s)
166
+ const top = scroller.current?.getBoundingClientRect().top ?? 0
167
+ return (
168
+ <div className="tr-hover" style={{ left: Math.max(8, Math.min(window.innerWidth - 216, x - 104)), bottom: window.innerHeight - (top - 8) }}>
169
+ <Thumb st={st} index={i} w={192} />
170
+ <div className="tr-hoverrow"><b>{s.name}</b><span>{clock(t)}</span></div>
171
+ <div className="tr-dim">{s.section ?? 'opening'} · shot {i + 1}/{tl.shots.length} · {s.length} s</div>
172
+ </div>
173
+ )
174
+ })
175
+
176
+ /** every shot as a card: its frame, its name, its start and length, its open notes; the one on screen lit and kept in
177
+ * view */
178
+ export const ShotCards = observer(({ st }: { st: TrailerSt }) => {
179
+ const tl = st.timeline
180
+ const box = useRef<HTMLDivElement>(null)
181
+ const cur = st.shotIndex
182
+ useEffect(() => {
183
+ const el = box.current
184
+ const card = el?.children[cur] as HTMLElement | undefined
185
+ if (!el || !card) return
186
+ const left = card.offsetLeft - el.offsetLeft
187
+ if (left < el.scrollLeft || left + card.offsetWidth > el.scrollLeft + el.clientWidth) el.scrollTo({ left: left - el.clientWidth / 2 + card.offsetWidth / 2, behavior: 'smooth' })
188
+ }, [cur])
189
+ if (!tl) return null
190
+ const counts = new Map<string, number>()
191
+ for (const n of st.here) if (n.note.state === 'open') counts.set(n.note.shot, (counts.get(n.note.shot) ?? 0) + 1)
192
+ return (
193
+ <div ref={box} className="tr-shotcards">
194
+ {tl.shots.map((s, i) => {
195
+ const n = counts.get(s.name) ?? 0
196
+ return (
197
+ <button type="button" key={`${s.name}@${i}`} className={`tr-shotcard ${hueOf(tl, s.section)}${i === cur ? ' on' : ''}`} onMouseDown={(e) => e.preventDefault()} onClick={() => st.seek(s.start)}>
198
+ <Thumb st={st} index={i} w={136} />
199
+ <div className="tr-shotname"><span className="tr-dim">{i + 1} </span>{s.name}</div>
200
+ <div className="tr-dim tr-small">{clock(s.start)} · {s.length} s{s.captions.length > 0 ? ` · ${s.captions.length} title${s.captions.length > 1 ? 's' : ''}` : ''}</div>
201
+ {n > 0 ? <span className="tr-shotnotes">{n}</span> : null}
202
+ </button>
203
+ )
204
+ })}
205
+ </div>
206
+ )
207
+ })
@@ -0,0 +1,389 @@
1
+ /* the review page and the trailer page (hub-app/review/): pkm-multi's Revue and Trailer tabs on the hub's tokens.
2
+ A status, a reaction, a note state and a section each set --tone or --hue, and the parts read it, so no rule here
3
+ writes a colour (tests/hub-css-invariants.test.ts) */
4
+ .st-pending{--tone:var(--blue)}
5
+ .st-approved,.re-approved{--tone:var(--ok)}
6
+ .st-approved-with-fix,.re-approved-with-fix,.st-raised{--tone:var(--warn)}
7
+ .st-rejected,.re-rejected{--tone:var(--red)}
8
+ .st-archived,.st-superseded,.st-withdrawn,.st-wontfix,.st-rated{--tone:var(--dim)}
9
+ .st-wip{--tone:var(--k2)}
10
+ .st-fixed{--tone:var(--k3)}
11
+ .ns-open{--tone:var(--warn)}
12
+ .ns-done{--tone:var(--ok)}
13
+ .ns-wontfix{--tone:var(--dim)}
14
+ .hue-k1{--hue:var(--k1)}.hue-k2{--hue:var(--k2)}.hue-k3{--hue:var(--k3)}.hue-k4{--hue:var(--k4)}.hue-k5{--hue:var(--k5)}.hue-k6{--hue:var(--k6)}
15
+ .hue-none{--hue:var(--dim)}
16
+
17
+ /* ── the three columns ── */
18
+ .rv-cols{flex:1;min-height:0}
19
+ .rv-pane{height:100%;min-width:0;display:flex;flex-direction:column}
20
+ .rv-scroll{flex:1;min-height:0;overflow-y:auto}
21
+ .rv-dim,.tr-dim{color:var(--dim)}
22
+ .rv-small,.tr-small{font-size:11px}
23
+ .rv-mono,.tr-mono{font:11.5px ui-monospace,Menlo,monospace}
24
+ .rv-pre{white-space:pre-wrap;overflow-wrap:anywhere}
25
+ .rv-center{text-align:center}
26
+ .rv-empty{padding:var(--s3);color:var(--dim);font-size:12.5px;display:flex;gap:var(--s2);align-items:center}
27
+ .rv-err{padding:var(--s1) var(--s3);color:var(--errink);font-size:12px;white-space:pre-wrap}
28
+ .rv-lanehead .sp,.rv-viewhead .sp,.tr-cutrow .sp,.tr-transport .sp,.tr-noteshead .sp,.tr-noteacts .sp{flex:1}
29
+ .rv-divider{display:flex;align-items:center;gap:var(--s2);margin:var(--s2) 0 var(--s1);color:var(--dim);font-size:11px}
30
+ .rv-divider::after{content:'';flex:1;border-top:1px solid var(--line)}
31
+ .rv-cols textarea,.tr-notescol textarea,.rv-phone textarea{min-height:0;font:12.5px/1.45 system-ui,sans-serif;padding:var(--s1) var(--s2)}
32
+
33
+ /* ── the list ── */
34
+ .rv-listcol{height:100%;display:flex;flex-direction:column;min-height:0;background:var(--panel);container-type:inline-size}
35
+ .rv-filters{display:flex;flex-direction:column;gap:var(--s2);padding:var(--s2) var(--s3);border-bottom:1px solid var(--line)}
36
+ .rv-search{background:var(--bg);border:1px solid var(--line);border-radius:4px;color:var(--ink);padding:var(--s1) var(--s2);font-size:12.5px;outline:none}
37
+ .rv-search:focus{border-color:var(--accent)}
38
+ .rv-selects{display:flex;gap:var(--s1)}
39
+ .rv-selects select{flex:1;min-width:0}
40
+ .rv-whose{align-self:flex-start}
41
+ .rv-switches{display:flex;gap:var(--s3);font-size:12px}
42
+ .rv-switch{display:inline-flex;align-items:center;gap:var(--s1);cursor:pointer;color:var(--ink)}
43
+ .rv-count{font-size:11px;color:var(--dim)}
44
+ .rv-listscroll{flex:1;min-height:0;overflow-y:auto;padding:var(--s1) 0 var(--s4)}
45
+ .rv-lanehead{display:flex;align-items:center;gap:var(--s2);margin:var(--s2) var(--s2) var(--s0);padding:var(--s1) var(--s2);border-radius:4px;border-left:4px solid var(--lane,var(--dim));background:var(--panel2);font-size:12px}
46
+ .rv-lanehead b{color:var(--lane,var(--dim))}
47
+ .rv-row{display:flex;gap:var(--s2);align-items:center;margin:var(--s1) var(--s2);padding:var(--s1) var(--s2);border-radius:6px;cursor:pointer;
48
+ border:1px solid color-mix(in srgb,var(--tone) 45%,var(--line));background:color-mix(in srgb,var(--tone) 9%,var(--panel))}
49
+ .rv-row:hover{background:color-mix(in srgb,var(--tone) 16%,var(--panel))}
50
+ .rv-row.on{background:color-mix(in srgb,var(--tone) 22%,var(--panel));box-shadow:inset 3px 0 0 var(--accent)}
51
+ .rv-row.laned,.rv-vrow.laned{border-left:4px solid var(--lane)}
52
+ .rv-row.muted .rv-thumb,.rv-row.muted .rv-rowbody{opacity:.55}
53
+ .rv-thumb{width:56px;height:56px;flex:none;border-radius:4px;overflow:hidden;background:repeating-conic-gradient(var(--chk1) 0 25%,var(--chk2) 0 50%) 0 0/12px 12px}
54
+ .rv-thumb.sm{width:32px;height:32px}
55
+ .rv-thumb.xs{width:36px;height:36px;cursor:pointer}
56
+ .rv-thumbimg{width:100%;height:100%;object-fit:cover;display:block}
57
+ .rv-thumbnone{width:100%;height:100%;display:grid;place-items:center;color:var(--dim);background:var(--panel2)}
58
+ .rv-rowbody{flex:1;min-width:0}
59
+ .rv-rowtitle{font-size:13px;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
60
+ .rv-rowtitle.strong{font-weight:700}
61
+ .rv-unread{display:inline-block;width:8px;height:8px;border-radius:4px;background:var(--blue);margin-right:var(--s1);vertical-align:middle}
62
+ .rv-rowmeta{display:flex;gap:var(--s1);align-items:center;margin-top:var(--s0);min-width:0}
63
+ .rv-badge{flex:none;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:0 var(--s1);border-radius:3px;line-height:16px;
64
+ color:var(--bg);background:var(--tone,var(--dim));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}
65
+ .rv-badge.outline{background:none;color:var(--ink);border:1px solid var(--line)}
66
+ .rv-about{font-size:11px;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
67
+ .rv-fold{flex:none;display:grid;place-items:center;width:24px;height:24px;padding:0;border:1px solid var(--line);border-radius:999px;color:var(--dim)}
68
+ .rv-fold .cvt{width:14px;height:14px;transform:rotate(90deg)}
69
+ .rv-fold .cvt.open{transform:rotate(-90deg)}
70
+ .rv-fold:hover{color:var(--ink);border-color:var(--accent)}
71
+ /* a narrow list: the title on its own line on top, a smaller image under it beside the badges */
72
+ @container (max-width:250px){
73
+ .rv-row{display:grid;grid-template-columns:40px minmax(0,1fr) auto;grid-template-areas:"title title fold" "thumb meta meta" "rework rework rework";align-items:center;column-gap:var(--s2);row-gap:var(--s1)}
74
+ .rv-row .rv-rowbody{display:contents}
75
+ .rv-row .rv-rowtitle{grid-area:title}
76
+ .rv-row .rv-thumb{grid-area:thumb;width:40px;height:40px}
77
+ .rv-row .rv-rowmeta{grid-area:meta;flex-wrap:wrap;margin-top:0}
78
+ .rv-row .rv-fold{grid-area:fold}
79
+ .rv-row > .rv-rowbody > :not(.rv-rowtitle):not(.rv-rowmeta){grid-area:rework}
80
+ .rv-row.muted .rv-rowtitle,.rv-row.muted .rv-rowmeta{opacity:.55}
81
+ /* the filters: one select a line, the whose bar across the width, the switches one a line */
82
+ .rv-filters{padding:var(--s2)}
83
+ .rv-selects{flex-direction:column}
84
+ .rv-whose{align-self:stretch}
85
+ .seg.rv-whose button{flex:1 1 auto;justify-content:center;padding:0 var(--s1);font-size:11px;white-space:nowrap}
86
+ .rv-switches{flex-direction:column;gap:var(--s1)}
87
+ }
88
+ .rv-vrow{display:flex;gap:var(--s1);align-items:center;margin:var(--s0) var(--s2) var(--s0) var(--s4);padding:var(--s0) var(--s1);border-radius:4px;cursor:pointer;opacity:.8;
89
+ border:1px solid color-mix(in srgb,var(--tone) 40%,var(--line));background:color-mix(in srgb,var(--tone) 8%,var(--panel));font-size:11.5px}
90
+ .rv-vrow.on{opacity:1;box-shadow:inset 3px 0 0 var(--accent)}
91
+ .rv-vn{flex:none}
92
+ .rv-vtitle{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
93
+ .rv-lane{flex:none;font-size:10px;font-weight:700;color:var(--bg);background:var(--lane);border-radius:3px;padding:0 var(--s1);line-height:16px}
94
+ .rv-auto{flex:none;font-size:10px;font-weight:700;color:var(--bg);background:var(--warn);border-radius:3px;padding:0 var(--s1);line-height:16px}
95
+ .rv-reworkrow{display:flex;margin-top:var(--s1)}
96
+ .rv-rework{display:inline-flex;align-items:center;gap:var(--s1);font-size:11px;font-weight:700;color:var(--lane);border:1px solid var(--lane);border-radius:4px;padding:0 var(--s1);white-space:nowrap}
97
+ .rv-spin{width:10px;height:10px;border-radius:50%;border:2px solid var(--lane);border-top-color:transparent;animation:rot 2.4s linear infinite}
98
+ .rv-listen{display:inline-flex;align-items:center;gap:var(--s1);font-size:11px;font-weight:700;color:var(--dim);border:1px solid var(--line);border-radius:10px;padding:0 var(--s2);white-space:nowrap;line-height:18px}
99
+ .rv-listen.on{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 55%,var(--line))}
100
+
101
+ /* ── the views ── */
102
+ .rv-views{display:flex;flex-direction:column;gap:var(--s4);padding:var(--s3)}
103
+ .rv-viewsbar{display:flex;flex-wrap:wrap;gap:var(--s2);justify-content:space-between;align-items:center}
104
+ .rv-viewsctl{display:inline-flex;gap:var(--s2);align-items:center}
105
+ .rv-jumps{display:flex;flex-wrap:wrap;gap:var(--s1);flex:1;min-width:0}
106
+ .rv-jump{font-size:11.5px;padding:var(--s0) var(--s2);border-color:transparent;color:var(--dim);display:inline-flex;align-items:center;gap:var(--s1)}
107
+ .rv-jump.on{color:var(--ink);border-color:var(--line);background:var(--panel2)}
108
+ .rv-jdot{width:8px;height:8px;border-radius:4px;background:var(--tone)}
109
+ .rv-view{scroll-margin-top:8px;padding-left:var(--s2);border-left:4px solid transparent}
110
+ .rv-view.active{border-left-color:var(--line)}
111
+ .rv-view[class*=' re-']{border-left-color:var(--tone)}
112
+ .rv-viewhead{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:center;margin-bottom:var(--s2)}
113
+ .rv-viewname{font-size:14px}
114
+ .rv-reacts{display:inline-flex;gap:var(--s1)}
115
+ .rv-react{font-size:11.5px;padding:0 var(--s2);line-height:20px;color:var(--tone);border-color:color-mix(in srgb,var(--tone) 45%,var(--line));background:color-mix(in srgb,var(--tone) 10%,transparent)}
116
+ .rv-react.on{color:var(--bg);background:var(--tone);border-color:var(--tone)}
117
+ .rv-react.off{opacity:.45}
118
+ .rv-word{margin-top:var(--s2)}
119
+ .rv-raised{padding:var(--s2) var(--s3);border:1px solid var(--line);border-left:4px solid var(--warn);border-radius:4px;background:var(--panel)}
120
+ .rv-raisedimg{display:block;max-width:100%;max-height:320px;margin-top:var(--s2);border-radius:4px}
121
+ .rv-pairgrid{display:grid;gap:var(--s2)}
122
+ .rv-cell{display:flex;flex-direction:column;align-items:center;gap:var(--s0);min-width:0}
123
+ .rv-frame{position:relative;width:100%;max-width:100%;border-radius:4px;border:1px solid var(--line);overflow:hidden;
124
+ background:repeating-conic-gradient(var(--chk1) 0 25%,var(--chk2) 0 50%) 0 0/16px 16px}
125
+ .rv-frame.swipe{cursor:ew-resize;touch-action:none}
126
+ .rv-fill{position:absolute;inset:0}
127
+ .rv-caption{font-size:11px;color:var(--dim)}
128
+ .rv-caption.mod,.rv-modcap{color:var(--k2)}
129
+ .rv-curtain{position:absolute;top:0;bottom:0;width:2px;transform:translateX(-1px);background:var(--k2);pointer-events:none}
130
+ .rv-modes{position:relative;display:inline-flex;flex-direction:column;gap:var(--s1)}
131
+ .rv-onion{position:absolute;top:100%;left:0;right:0;z-index:3;margin-top:var(--s0)}
132
+ .rv-marks{position:absolute;inset:0;user-select:none}
133
+ .rv-marks.draw{cursor:crosshair}
134
+ .rv-marks.pick{cursor:pointer}
135
+ .rv-markimg,.rv-video{display:block;width:100%;height:100%;pointer-events:none}
136
+ .rv-rings{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
137
+ .rv-ring{fill:none;stroke:var(--warn);stroke-width:3}
138
+ .rv-ringshade{fill:none;stroke:var(--solid);stroke-opacity:.35;stroke-width:7}
139
+ .rv-rect{position:absolute;border:2px solid var(--warn);box-shadow:0 0 0 1px var(--hi1);background:color-mix(in srgb,var(--warn) 7%,transparent);cursor:pointer}
140
+ .rv-rect.on{border-width:3px;border-color:var(--blue)}
141
+ .rv-rect.faint{border-style:dashed;opacity:.6;box-shadow:none;background:none}
142
+ .rv-rectn{position:absolute;left:-2px;top:-2px;transform:translateY(-100%);padding:0 var(--s1);border-radius:3px;background:var(--warn);color:var(--bg);font-size:11px;font-weight:800;line-height:16px;white-space:nowrap}
143
+ .rv-rect.on .rv-rectn{background:var(--blue)}
144
+ .rv-drag{position:absolute;border:2px dashed var(--warn)}
145
+ .rv-downloads{display:flex;gap:var(--s2);justify-content:flex-end;margin-top:var(--s1)}
146
+ .rv-dl{font-size:11.5px;text-decoration:none}
147
+ .rv-gallery{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:flex-start}
148
+ .rv-tile{display:flex;flex-direction:column;gap:var(--s1);padding:var(--s1);border-radius:6px;border:2px solid var(--line)}
149
+ .rv-tile[class*=' re-']{border-color:var(--tone);background:color-mix(in srgb,var(--tone) 8%,transparent)}
150
+ .rv-tile.big{width:100%}
151
+ .rv-tileimg{border-radius:4px;cursor:zoom-in;width:100%;background:repeating-conic-gradient(var(--chk1) 0 25%,var(--chk2) 0 50%) 0 0/12px 12px}
152
+ .rv-tile.big .rv-tileimg{cursor:zoom-out}
153
+ .rv-tileimg img{display:block;width:100%;height:100%;object-fit:contain}
154
+ .rv-tilelabel{font-size:11px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
155
+ .rv-clipbar{display:flex;gap:var(--s1);align-items:center;margin-top:var(--s2)}
156
+ .rv-clipbar button{padding:0 var(--s2);line-height:22px}
157
+ .rv-scrub{flex:1;min-width:60px}
158
+ .rv-sound{display:inline-flex;align-items:center;gap:var(--s1)}
159
+ .rv-sound button{padding:0 var(--s1);line-height:22px}
160
+ .rv-sound input{width:72px}
161
+ .rv-phonepair{display:flex;flex-direction:column;gap:var(--s2)}
162
+ /* options: one thin row a view, one below the other; the row playing or last touched has the line, the picked one its tone */
163
+ .rv-opts{display:flex;flex-direction:column;gap:var(--s1)}
164
+ .rv-opt{scroll-margin-top:8px;padding:var(--s1) var(--s2);border-radius:6px;border:1px solid var(--line);border-left:4px solid transparent;background:var(--panel)}
165
+ .rv-opt.active{border-left-color:var(--accent)}
166
+ .rv-opt[class*=' re-']{border-left-color:var(--tone)}
167
+ .rv-opt.picked{--tone:var(--ok);border-color:var(--tone);border-left-color:var(--tone);background:color-mix(in srgb,var(--tone) 9%,var(--panel))}
168
+ .rv-optrow{display:flex;flex-wrap:wrap;gap:var(--s1) var(--s2);align-items:center;min-height:32px}
169
+ .rv-optn{flex:none;width:16px;text-align:right;font:600 12px ui-monospace,Menlo,monospace;color:var(--dim)}
170
+ .rv-optplay{flex:none;width:32px;height:28px;padding:0;font-size:13px}
171
+ .rv-optplay.on{color:var(--bg);background:var(--accent);border-color:var(--accent)}
172
+ .rv-optlabel{flex:0 1 auto;min-width:48px;max-width:30%;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
173
+ .rv-optthumb{flex:none;height:32px;max-width:72px;border-radius:3px;overflow:hidden;cursor:zoom-in;border:1px solid var(--line);background:var(--panel2)}
174
+ .rv-optthumb.img{width:48px}
175
+ .rv-optthumb img{display:block;width:100%;height:100%;object-fit:cover}
176
+ .rv-strip{position:relative;flex:1 1 140px;min-width:120px;height:28px;border-radius:3px;cursor:pointer;touch-action:none;background:var(--panel2);border:1px solid var(--line);overflow:hidden}
177
+ .rv-stripdone{position:absolute;inset:0 auto 0 0;background:var(--wash)}
178
+ /* a waveform is a mask filled with tokens: --dim to play (5.0:1 on --panel2 light, 5.3:1 dark), --accent played
179
+ (4.9:1 both) */
180
+ .rv-stripwave{position:absolute;inset:0;background:var(--dim)}
181
+ .rv-stripwave.done{background:var(--accent)}
182
+ .rv-stripwave.full{background:var(--accent)}
183
+ .rv-wavebox{position:relative;width:100%;border-radius:4px;background:var(--panel2)}
184
+ .rv-striphead{position:absolute;top:0;bottom:0;width:2px;transform:translateX(-1px);background:var(--accent)}
185
+ .rv-opttime{flex:none;font-size:11px;color:var(--dim)}
186
+ .rv-optacts{flex:none;display:inline-flex;gap:var(--s1);align-items:center;margin-left:auto}
187
+ .rv-optacts > button{padding:0 var(--s1);line-height:22px}
188
+ .rv-optacts > button.on{border-color:var(--accent)}
189
+ .rv-stars{display:inline-flex}
190
+ .rv-stars button{border:0;background:none;padding:0;font-size:16px;line-height:22px;color:var(--dim)}
191
+ .rv-stars button.on{color:var(--warn)}
192
+ .rv-pick{font-weight:700;padding:0 var(--s3)!important;color:var(--ok);border-color:color-mix(in srgb,var(--ok) 55%,var(--line))}
193
+ .rv-pick.on{color:var(--bg);background:var(--ok);border-color:var(--ok)}
194
+ .rv-optnote{margin:var(--s0) 0 0 var(--s4);font-size:12.5px;color:var(--ink)}
195
+ .rv-opt .rv-word{margin:var(--s1) 0 0 var(--s4);width:calc(100% - var(--s4))}
196
+ .rv-optbody{margin-top:var(--s2)}
197
+
198
+ /* ── the answer ── */
199
+ .rv-form{display:flex;flex-direction:column;gap:var(--s2);padding:var(--s3)}
200
+ .rv-title{margin:0;font-size:16px;line-height:1.3;color:var(--ink)}
201
+ /* the card's header: the title, one tight line of state, then what the agent says at full ink; --dim holds 5.6:1 or
202
+ more on --bg and --panel in both themes */
203
+ .rv-head{display:flex;flex-direction:column;gap:var(--s1)}
204
+ .rv-meta{display:flex;flex-wrap:wrap;gap:var(--s1) var(--s2);align-items:center;font-size:12px;color:var(--dim);line-height:18px}
205
+ .rv-meta .rv-badge{font-size:10px;line-height:18px;max-width:none}
206
+ .rv-copyid{font-size:11.5px;padding:0 var(--s1);line-height:18px;color:var(--dim)}
207
+ .rv-copyid:hover{color:var(--ink)}
208
+ .rv-what{font-size:13.5px;line-height:1.45;color:var(--ink)}
209
+ .rv-what ul{margin:var(--s0) 0;padding-left:var(--s4)}
210
+ .rv-what p{margin:var(--s0) 0}
211
+ .rv-why{color:var(--dim);font-size:12px}
212
+ .rv-headverdicts{display:flex;flex-wrap:wrap;gap:var(--s1);align-items:center;margin-top:var(--s1)}
213
+ .rv-headverdicts .rv-verdict{padding:0 var(--s2);line-height:24px;font-size:12.5px}
214
+ .rv-keeps{color:var(--ok);font-size:13px}
215
+ .rv-facts{display:flex;flex-direction:column}
216
+ .rv-fact{display:flex;gap:var(--s2);align-items:center;min-height:22px;font-size:12.5px}
217
+ .rv-factl{width:64px;flex:none;color:var(--dim);font-size:12px}
218
+ .rv-factv{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
219
+ .rv-linked{padding:var(--s1) var(--s2);border:1px solid var(--line);border-radius:4px;background:var(--panel)}
220
+ .rv-linkedrow{display:flex;gap:var(--s2);align-items:center;font-size:12px}
221
+ .rv-linkedtitle{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
222
+ .rv-outgoing,.rv-messages{display:flex;flex-direction:column;gap:var(--s1)}
223
+ .rv-outrow{display:flex;gap:var(--s1);align-items:flex-start}
224
+ .rv-outrow textarea{flex:1;min-height:44px}
225
+ .rv-outbadge{flex:none;max-width:170px;margin-top:var(--s0);font-size:11px;padding:0 var(--s2);line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
226
+ color:var(--tone,var(--ink));border-color:color-mix(in srgb,var(--tone,var(--line)) 50%,var(--line))}
227
+ .rv-outbadge.rect{--tone:var(--warn)}
228
+ .rv-outbadge.rect.on{--tone:var(--blue)}
229
+ .rv-x{flex:none;padding:0 var(--s1);border:0}
230
+ .rv-label{font-size:12px;font-weight:600;margin-top:var(--s1)}
231
+ .rv-listenrow{display:flex;gap:var(--s2);align-items:center;font-size:11px}
232
+ .rv-verdicts{display:flex;flex-direction:column;gap:var(--s1)}
233
+ .rv-verdict{text-align:left;padding:var(--s1) var(--s3);font-size:13px;border-color:color-mix(in srgb,var(--tone) 60%,var(--line))}
234
+ .rv-verdict .rv-vglyph{color:var(--tone);font-weight:800}
235
+ .rv-verdict.chosen{background:var(--tone);color:var(--bg);border-color:var(--tone)}
236
+ .rv-verdict.chosen .rv-vglyph{color:var(--bg)}
237
+ .rv-verdict.off{opacity:.5}
238
+ .rv-msg{max-width:88%;align-self:flex-start;padding:var(--s1) var(--s2);border-radius:6px;border:1px solid var(--line);background:var(--panel2)}
239
+ .rv-msg.mine{align-self:flex-end;border-color:var(--hair);background:var(--wash)}
240
+ .rv-msgimg{display:block;max-width:100%;max-height:160px;margin-top:var(--s1);border-radius:4px}
241
+ .rv-replyrow,.rv-formfoot{display:flex;gap:var(--s2);justify-content:space-between}
242
+ .rv-replyrow{justify-content:flex-end}
243
+
244
+ /* ── the phone: the page covers the hub's panes, one pane at a time, the verdicts under the thumb ── */
245
+ .rv-phone,.tr-phone{position:fixed;inset:0;z-index:30;background:var(--bg)}
246
+ .rv-phone{display:flex;flex-direction:column}
247
+ .rv-phonetop{flex:none;display:flex;gap:var(--s2);align-items:center;padding:var(--s1) var(--s2);border-bottom:1px solid var(--line);background:var(--bg-top);font-size:13px}
248
+ .rv-phonetabs{margin:var(--s2);align-self:stretch;display:flex}
249
+ .rv-phonetabs button{flex:1;font-size:13px;padding:var(--s1) var(--s2)}
250
+ .rv-phonepane{flex:1;min-height:0;overflow-y:auto}
251
+ .rv-verdictbar{flex:none;display:flex;gap:var(--s1);padding:var(--s2);border-top:1px solid var(--line);background:var(--panel)}
252
+ .rv-verdictbar .rv-verdict{flex:1;text-align:center;font-size:14px;padding:var(--s2) var(--s1);color:var(--tone)}
253
+ .rv-verdictbar .rv-verdict.chosen{color:var(--bg)}
254
+ .rv-phone .rv-views{gap:var(--s3)}
255
+ /* a row on a phone: ▶, the label and the time on one line, the strip and the acts under them */
256
+ .rv-phone .rv-optrow{flex-wrap:wrap;row-gap:var(--s1)}
257
+ .rv-phone .rv-optlabel{flex:1;max-width:none}
258
+ .rv-phone .rv-strip{order:5;flex:1 1 100%;height:32px}
259
+ .rv-phone .rv-optthumb{order:4}
260
+ .rv-phone .rv-optacts{order:6;flex:1 1 100%;justify-content:space-between}
261
+ .rv-phone .rv-stars button{font-size:22px;padding:0 var(--s1)}
262
+ .rv-phone .rv-optplay{width:40px;height:34px}
263
+ .rv-phone .rv-optnote,.rv-phone .rv-opt .rv-word{margin-left:0;width:100%}
264
+
265
+ /* ── the trailer page ── */
266
+ .tr-cuts{display:flex;flex-direction:column;gap:var(--s2);padding:var(--s3)}
267
+ .tr-trailername{font-weight:800}
268
+ .tr-cutrows{display:flex;flex-direction:column;gap:var(--s0)}
269
+ .tr-cutrow{display:flex;align-items:center;gap:var(--s1);width:100%;padding:var(--s0) var(--s2);border:0;border-radius:4px;text-align:left;font-size:13px}
270
+ .tr-cutrow:hover{background:var(--panel2)}
271
+ .tr-cutrow.on{background:var(--sel);box-shadow:inset 2px 0 0 var(--accent);font-weight:700}
272
+ .tr-cutrow.unrendered{opacity:.45}
273
+ .tr-cutname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
274
+ .tr-edited{color:var(--warn)}
275
+ .tr-cuterr{color:var(--red);font-weight:800}
276
+ .tr-opencount{flex:none;min-width:16px;padding:0 var(--s1);border-radius:8px;background:var(--warn);color:var(--bg);font-size:10px;font-weight:700;text-align:center;line-height:16px}
277
+ .tr-cutage{flex:none;font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap}
278
+ .tr-keys{margin-top:var(--s3);padding:var(--s2);border:1px solid var(--line);border-radius:6px;background:var(--panel);display:flex;flex-direction:column;gap:var(--s0)}
279
+ .tr-keyshead{font-size:11px;font-weight:700;color:var(--dim)}
280
+ .tr-keyrow{display:flex;gap:var(--s2);align-items:baseline;font-size:11px}
281
+ .tr-keyrow kbd{min-width:44px;text-align:center}
282
+ .tr-player{display:flex;flex-direction:column;gap:var(--s2);padding:var(--s3)}
283
+ .tr-banner{display:flex;gap:var(--s2);align-items:center;justify-content:space-between;padding:var(--s2) var(--s3);border-radius:4px;border:1px solid var(--hair);background:var(--wash);font-size:12.5px}
284
+ .tr-banner.warn{border-color:color-mix(in srgb,var(--warn) 50%,var(--line));background:color-mix(in srgb,var(--warn) 10%,var(--panel))}
285
+ .tr-screen{background:var(--solid);border-radius:8px;padding:var(--s2);display:flex;justify-content:center}
286
+ .tr-frame{position:relative}
287
+ .tr-frame video{display:block;width:100%;height:100%;border-radius:4px;background:var(--solid)}
288
+ .tr-clickzone{position:absolute;inset:0;cursor:crosshair}
289
+ .tr-paused{position:absolute;left:var(--s2);top:var(--s2);padding:var(--s0) var(--s2);border-radius:4px;background:var(--scrim);color:var(--tag);font-size:12px;font-variant-numeric:tabular-nums;pointer-events:none}
290
+ .tr-pin{position:absolute;transform:translate(-50%,-100%);pointer-events:none}
291
+ .tr-pinhead{width:22px;height:22px;border-radius:50% 50% 50% 0;transform:rotate(-45deg);background:var(--tone);border:2px solid var(--tag);box-shadow:0 2px 8px var(--shadow);display:flex;align-items:center;justify-content:center}
292
+ .tr-pinhead.on{width:28px;height:28px}
293
+ .tr-pinhead span{transform:rotate(45deg);color:var(--tag);font-weight:800;font-size:11px}
294
+ .tr-pulse{position:absolute;left:calc(50% - 12px);bottom:-6px;width:24px;height:24px;border-radius:12px;border:2px solid var(--warn);animation:trpulse 1.1s ease-out infinite}
295
+ @keyframes trpulse{from{transform:scale(.6);opacity:1}to{transform:scale(1.8);opacity:0}}
296
+ .tr-transport{display:flex;flex-wrap:wrap;gap:var(--s1);align-items:center}
297
+ .tr-tbtn{width:32px;height:32px;padding:0;border-radius:16px;border-color:transparent;font-size:15px}
298
+ .tr-tbtn.big{width:44px;height:44px;border-radius:22px;background:var(--ink);color:var(--bg);font-size:17px}
299
+ .tr-time{margin-left:var(--s1);line-height:1.15;font-variant-numeric:tabular-nums}
300
+ .tr-timebig{font-size:18px;font-weight:800}
301
+ .tr-timebig span{font-size:12px;font-weight:500}
302
+ .tr-file{font-size:11px;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
303
+ .tr-notebtn{color:var(--bg);background:var(--warn);border-color:var(--warn);font-weight:700;padding:var(--s1) var(--s3)}
304
+ .tr-now{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:center;min-height:30px;font-size:12.5px}
305
+ .tr-secbadge{padding:0 var(--s2);border-radius:3px;background:var(--hue);color:var(--bg);font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;line-height:20px}
306
+ .tr-secbadge.small{text-transform:none;letter-spacing:0;line-height:16px;font-size:10.5px}
307
+ .tr-shotnow{font-size:16px}
308
+ .tr-capnow{font-style:italic;padding:0 var(--s2);border-radius:4px;background:color-mix(in srgb,var(--warn) 18%,transparent);white-space:pre-line}
309
+ .tr-timelinewrap{position:relative}
310
+ .tr-timeline{overflow-x:auto;overflow-y:hidden;border-radius:6px;background:var(--bg);border:1px solid var(--line);padding:var(--s1) 0 var(--s2);cursor:col-resize;user-select:none}
311
+ .tr-track{position:relative}
312
+ .tr-tick{position:absolute;border-left:1px solid var(--hi3)}
313
+ .tr-tick span{position:absolute;left:3px;top:1px;font-size:10px;color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap}
314
+ .tr-section{position:absolute;border-radius:4px;background:color-mix(in srgb,var(--hue) 55%,var(--bg));color:var(--ink);font-size:11px;font-weight:700;padding-left:var(--s1);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;opacity:.8}
315
+ .tr-section.on{background:var(--hue);color:var(--bg);opacity:1;box-shadow:0 0 0 2px var(--hi1)}
316
+ .tr-shot{position:absolute;border-radius:3px;background:var(--panel2);border-top:3px solid color-mix(in srgb,var(--hue) 60%,var(--bg));color:var(--ink);font-size:11px;font-weight:600;padding:var(--s0) var(--s1) 0;overflow:hidden;white-space:nowrap;opacity:.85}
317
+ .tr-shot.odd{background:var(--panel)}
318
+ .tr-shot.on{background:color-mix(in srgb,var(--hue) 70%,var(--bg));border-top-color:var(--hue);color:var(--bg);font-weight:800;box-shadow:0 0 0 2px var(--ink);opacity:1;z-index:1}
319
+ .tr-shottime{font-size:9px;opacity:.6;font-variant-numeric:tabular-nums}
320
+ .tr-caption{position:absolute;border-radius:3px;background:color-mix(in srgb,var(--warn) 18%,transparent);color:var(--warn);border:1px solid color-mix(in srgb,var(--warn) 35%,transparent);font-size:10px;font-style:italic;padding:0 var(--s1);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
321
+ .tr-caption.on{background:var(--warn);color:var(--bg)}
322
+ .tr-noteline{position:absolute;border-left:1px dashed var(--tone);opacity:.5;pointer-events:none}
323
+ .tr-noteline.on{border-left-style:solid;opacity:.9}
324
+ .tr-notepin{position:absolute;top:0;width:16px;height:16px;border-radius:50% 50% 50% 0;transform:rotate(-45deg);background:var(--tone);border:1px solid var(--shadow);cursor:pointer;z-index:3}
325
+ .tr-notepin.on{border:2px solid var(--tag)}
326
+ .tr-draftmark{position:absolute;top:0;width:2px;background:var(--warn);z-index:3;pointer-events:none}
327
+ .tr-playhead{position:absolute;top:0;width:2px;background:var(--red);z-index:4;pointer-events:none;box-shadow:0 0 6px var(--red)}
328
+ .tr-playknob{position:absolute;left:-5px;top:-2px;width:12px;height:8px;border-radius:3px;background:var(--red)}
329
+ .tr-hover{position:fixed;width:208px;padding:var(--s2);border-radius:6px;background:var(--panel2);border:1px solid var(--line);box-shadow:0 6px 24px var(--shadow);pointer-events:none;z-index:50;font-size:12px}
330
+ .tr-hoverrow{display:flex;justify-content:space-between;margin-top:var(--s1)}
331
+ .tr-thumb{border-radius:4px;background-color:var(--panel2);background-repeat:no-repeat;flex:none}
332
+ .tr-shotcards{display:flex;gap:var(--s2);overflow-x:auto;padding:var(--s1) var(--s0) var(--s2)}
333
+ .tr-shotcard{position:relative;flex:none;width:148px;padding:var(--s1);border-radius:6px;text-align:left;border:1px solid var(--line);background:var(--panel)}
334
+ .tr-shotcard.on{border:2px solid var(--hue);background:color-mix(in srgb,var(--hue) 12%,var(--panel))}
335
+ .tr-shotname{font-size:12px;font-weight:800;margin-top:var(--s1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
336
+ .tr-shotnotes{position:absolute;top:var(--s2);right:var(--s2);min-width:18px;height:18px;border-radius:9px;background:var(--warn);color:var(--bg);font-size:11px;font-weight:800;text-align:center;line-height:18px;padding:0 var(--s1)}
337
+ .tr-montage{display:flex;flex-direction:column;gap:var(--s1);align-items:flex-start}
338
+ .tr-montagebody{width:100%;font-size:12.5px;padding:var(--s2) var(--s3);border:1px solid var(--line);border-radius:4px;background:var(--panel);overflow-x:auto}
339
+ .tr-notescol{height:100%;display:flex;flex-direction:column;min-height:0}
340
+ .tr-noteshead{display:flex;gap:var(--s2);align-items:center;padding:var(--s2) var(--s3);border-bottom:1px solid var(--line)}
341
+ .tr-openbadge{font-size:10px;font-weight:700;text-transform:uppercase;padding:0 var(--s1);border-radius:3px;line-height:16px;color:var(--warn);border:1px solid color-mix(in srgb,var(--warn) 50%,var(--line))}
342
+ .tr-openbadge.on{background:var(--warn);color:var(--bg)}
343
+ .tr-notesscroll{flex:1;min-height:0;overflow-y:auto;padding:var(--s2);display:flex;flex-direction:column;gap:var(--s2)}
344
+ .tr-elsewhere{font-size:11px;font-weight:700;color:var(--dim);margin-top:var(--s3)}
345
+ .tr-draft{padding:var(--s2) var(--s3);border-radius:6px;border:1px solid var(--warn);box-shadow:0 0 0 3px color-mix(in srgb,var(--warn) 18%,transparent);display:flex;flex-direction:column;gap:var(--s1)}
346
+ .tr-drafthead{display:flex;justify-content:space-between;gap:var(--s2);align-items:center;font-size:12.5px}
347
+ .tr-pinned{color:var(--warn);font-size:11px;font-weight:700}
348
+ .tr-draftbtns{display:flex;justify-content:flex-end;gap:var(--s1)}
349
+ .tr-note{border-radius:6px;border:1px solid var(--line);border-left:4px solid var(--tone);padding:var(--s2);background:var(--panel);cursor:pointer;display:flex;flex-direction:column;gap:var(--s1)}
350
+ .tr-note.ns-done:not(.on),.tr-note.ns-wontfix:not(.on){opacity:.72}
351
+ .tr-note.on{border:2px solid var(--tone);border-left-width:5px;background:var(--panel2)}
352
+ .tr-notetop{display:flex;gap:var(--s2)}
353
+ .tr-notebody{flex:1;min-width:0}
354
+ .tr-notemeta{display:flex;flex-wrap:wrap;gap:var(--s1);align-items:center;font-size:12px}
355
+ .tr-statebadge{font-size:10px;font-weight:700;padding:0 var(--s1);border-radius:3px;background:var(--tone);color:var(--bg);line-height:16px}
356
+ .tr-moved{font-size:11px;color:var(--warn)}
357
+ .tr-notetext{font-size:13px;margin-top:var(--s0);white-space:pre-wrap;overflow-wrap:anywhere}
358
+ .tr-notetext.struck{text-decoration:line-through}
359
+ .tr-edit{display:flex;flex-direction:column;gap:var(--s1)}
360
+ .tr-reply{padding:var(--s1) var(--s2);border-radius:4px;border-left:3px solid var(--tone);background:color-mix(in srgb,var(--tone) 10%,var(--panel));font-size:12px}
361
+ .tr-noteacts{display:flex;flex-wrap:wrap;gap:var(--s1);align-items:center}
362
+ .tr-noteacts button{font-size:12px;padding:0 var(--s2);line-height:22px}
363
+ .tr-done{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 50%,var(--line))}
364
+ .tr-delete{color:var(--red);border-color:transparent}
365
+ .tr-delete.sure{background:var(--red);color:var(--bg)}
366
+ .tr-still{position:relative;flex:none}
367
+ .tr-still img{display:block;width:100%;height:100%;object-fit:cover;border-radius:4px;background:var(--panel2)}
368
+ .tr-stillpin{position:absolute;width:12px;height:12px;transform:translate(-50%,-50%);border-radius:6px;background:var(--tone);border:2px solid var(--tag);box-shadow:0 1px 4px var(--shadow)}
369
+ .tr-phone{overflow-y:auto;display:flex;flex-direction:column}
370
+ .tr-phone > *{flex:none}
371
+ .tr-phone .tr-notescol{height:auto}
372
+ /* the dispatcher's board and a kind's data view (review-board.tsx, kind-view.tsx) */
373
+ .rv-board{padding:var(--s2) var(--s3)}
374
+ .rv-boardrow{padding:var(--s2);border:1px solid var(--line);border-radius:4px;margin-bottom:var(--s1);cursor:pointer;font-size:12.5px}
375
+ .rv-boardrow:hover,.rv-boardrow.on{background:var(--panel)}
376
+ .rv-boardrow .rv-linkedtitle{display:block}
377
+ .rv-kindview{display:flex;flex-direction:column;gap:var(--s2);margin:var(--s2) 0}
378
+ .rv-kindtext{font:12px/1.5 ui-monospace,Menlo,monospace;background:var(--panel);border:1px solid var(--line);border-radius:3px;padding:var(--s2) var(--s3);margin:0;white-space:pre-wrap;overflow-wrap:anywhere}
379
+ .rv-part{border:1px solid var(--line);border-radius:4px;padding:var(--s2) var(--s3);margin-top:var(--s2);background:var(--panel)}
380
+ .rv-parthead{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s2)}
381
+ .rv-particon{width:20px;height:20px;border-radius:5px;flex:none}
382
+ .rv-partbadge{margin-left:auto;font-size:11px;font-weight:700;border:1px solid;border-radius:3px;padding:0 var(--s1)}
383
+ .rv-partbody{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:var(--s3);align-items:start}
384
+ .rv-partbody img,.rv-partbody video{width:100%;max-height:320px;object-fit:contain;border-radius:6px;display:block}
385
+ .rv-parttext{font-size:13px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}
386
+ .rv-anim select{font-size:12px}
387
+ /* a sound view (review-views.tsx SoundBody): its waveform over the player, a row a side on a pair */
388
+ .rv-sound{display:flex;flex-direction:column;gap:var(--s2)}
389
+ .rv-soundrow{display:flex;flex-direction:column;gap:var(--s1);background:var(--panel);border:1px solid var(--line);border-radius:4px;padding:var(--s2)}