@crievo/omni 0.1.3 → 0.1.5

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 (97) hide show
  1. package/package.json +2 -1
  2. package/reference/learnings.md +12 -0
  3. package/studio/api/app.ts +2 -2
  4. package/studio/api/routes/sound.ts +22 -3
  5. package/studio/api/services/sound.ts +45 -0
  6. package/studio/studio.ts +1 -0
  7. package/studio/web/components/AppShell.tsx +3 -1
  8. package/studio/web/components/CloneVoiceDialog/RecordStep.tsx +3 -0
  9. package/studio/web/components/CloneVoiceDialog/RecorderBar.tsx +37 -20
  10. package/studio/web/components/CloneVoiceDialog/useCloneVoice.ts +39 -12
  11. package/studio/web/components/MissingDmsPanel/BatchSend.tsx +13 -4
  12. package/studio/web/components/Omni/OmniWidget.tsx +14 -5
  13. package/studio/web/components/Omni/PendingActions.tsx +25 -6
  14. package/studio/web/components/OmnichatRulePanel/RuleConfig.tsx +13 -3
  15. package/studio/web/components/OmnichatRulePanel/RuleEnableConfirm.tsx +14 -4
  16. package/studio/web/components/ResultMessage.tsx +12 -3
  17. package/studio/web/components/Sidebar.tsx +63 -24
  18. package/studio/web/components/StageStepper.tsx +105 -34
  19. package/studio/web/components/Toaster.tsx +25 -0
  20. package/studio/web/components/VoiceCarousel.tsx +19 -16
  21. package/studio/web/components/VoiceProfilesSettings.tsx +8 -3
  22. package/studio/web/components/bencho/ActionNode.tsx +456 -0
  23. package/studio/web/components/bencho/AssigneePicker.tsx +246 -0
  24. package/studio/web/components/bencho/BenchoScope.tsx +8 -0
  25. package/studio/web/components/bencho/Checklist.tsx +421 -0
  26. package/studio/web/components/bencho/CreateMenu.tsx +258 -0
  27. package/studio/web/components/bencho/GenerateImage.tsx +290 -0
  28. package/studio/web/components/bencho/ImageAccordion.tsx +273 -0
  29. package/studio/web/components/bencho/LiquidToggle.tsx +289 -0
  30. package/studio/web/components/bencho/MagnifyDock.tsx +200 -0
  31. package/studio/web/components/bencho/NowPlaying.tsx +403 -0
  32. package/studio/web/components/bencho/OneTimeCode.tsx +374 -0
  33. package/studio/web/components/bencho/PullToRefresh.tsx +527 -0
  34. package/studio/web/components/bencho/SlideToConfirm.tsx +385 -0
  35. package/studio/web/components/bencho/SloshSlider.tsx +394 -0
  36. package/studio/web/components/bencho/StackedBars.tsx +342 -0
  37. package/studio/web/components/bencho/ToastStack.tsx +263 -0
  38. package/studio/web/components/bencho/UploadDropzone.tsx +394 -0
  39. package/studio/web/components/bencho/VoiceNote.tsx +705 -0
  40. package/studio/web/components/icons.tsx +27 -0
  41. package/studio/web/components/ui/slider.tsx +34 -1
  42. package/studio/web/components/ui/switch.tsx +18 -5
  43. package/studio/web/index.html +1 -1
  44. package/studio/web/lib/omni-panel.ts +4 -1
  45. package/studio/web/lib/toast.ts +196 -0
  46. package/studio/web/lib/upload.ts +24 -0
  47. package/studio/web/pages/BenchoPage/index.tsx +74 -0
  48. package/studio/web/pages/BenchoPage/registry.ts +174 -0
  49. package/studio/web/pages/NewVideoPage/index.tsx +6 -2
  50. package/studio/web/pages/ReferencesPage/index.tsx +4 -1
  51. package/studio/web/pages/VideoPage/components/ChangedVoices.tsx +14 -17
  52. package/studio/web/pages/VideoPage/components/LineTextEditor.tsx +1 -1
  53. package/studio/web/pages/VideoPage/components/PublishForm.tsx +24 -17
  54. package/studio/web/pages/VideoPage/components/ScheduledInfo.tsx +2 -1
  55. package/studio/web/pages/VideoPage/components/SoundTimeline/AddSound.tsx +12 -14
  56. package/studio/web/pages/VideoPage/components/SoundTimeline/BedRow.tsx +27 -11
  57. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionSaveBar.tsx +22 -18
  58. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineFooter.tsx +3 -4
  59. package/studio/web/pages/VideoPage/components/SoundTimeline/UploadTrack.tsx +59 -0
  60. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundTimeline.ts +1 -0
  61. package/studio/web/pages/VideoPage/components/VoicePauseBanner.tsx +4 -6
  62. package/studio/web/pages/VideoPage/index.tsx +4 -0
  63. package/studio/web/pages/VideosPage/components/VideoRow.tsx +2 -4
  64. package/studio/web/router.tsx +8 -0
  65. package/studio/web/styles/components/bencho/action-node.css +227 -0
  66. package/studio/web/styles/components/bencho/checklist.css +182 -0
  67. package/studio/web/styles/components/bencho/dock.css +188 -0
  68. package/studio/web/styles/components/bencho/gallery.css +109 -0
  69. package/studio/web/styles/components/bencho/generate.css +151 -0
  70. package/studio/web/styles/components/bencho/image-accordion.css +37 -0
  71. package/studio/web/styles/components/bencho/liq-create.css +279 -0
  72. package/studio/web/styles/components/bencho/liq-toggle.css +106 -0
  73. package/studio/web/styles/components/bencho/one-time-code.css +148 -0
  74. package/studio/web/styles/components/bencho/picker.css +204 -0
  75. package/studio/web/styles/components/bencho/pull.css +313 -0
  76. package/studio/web/styles/components/bencho/scope.css +42 -0
  77. package/studio/web/styles/components/bencho/slide-confirm.css +139 -0
  78. package/studio/web/styles/components/bencho/slosh.css +137 -0
  79. package/studio/web/styles/components/bencho/sound.css +260 -0
  80. package/studio/web/styles/components/bencho/stacked-bars.css +334 -0
  81. package/studio/web/styles/components/bencho/toast-stack.css +192 -0
  82. package/studio/web/styles/components/bencho/tokens.css +243 -0
  83. package/studio/web/styles/components/bencho/upload-dropzone.css +352 -0
  84. package/studio/web/styles/components/bencho/voice-note.css +337 -0
  85. package/studio/web/styles/components/clone-voice.css +12 -2
  86. package/studio/web/styles/components/omni.css +6 -1
  87. package/studio/web/styles/components/stepper.css +123 -7
  88. package/studio/web/styles/components/toaster.css +10 -0
  89. package/studio/web/styles/components/ui/slider.css +6 -16
  90. package/studio/web/styles/components/ui/switch.css +2 -41
  91. package/studio/web/styles/components/voice-carousel.css +46 -4
  92. package/studio/web/styles/index.css +23 -0
  93. package/studio/web/styles/pages/video/layout.css +1 -4
  94. package/studio/web/styles/pages/video/sound-timeline.css +8 -0
  95. package/studio/web/styles/theme/identity/shell.css +10 -26
  96. package/tools/omnichat.mjs +19 -6
  97. package/tools/sound.mjs +67 -3
@@ -0,0 +1,227 @@
1
+ /* Action node (components/bencho/ActionNode.tsx), as on bencho.dev (MIT).
2
+ A node card whose controls live in its top-right corner until the pointer asks for them. The component owns the
3
+ geometry (every number that decides where a button sits is arithmetic on an arc, in the .tsx); what is here is what
4
+ the card and the buttons are made of. */
5
+
6
+ /* the Stroke ring follows the card's own corner, which is a knob; the buttons have nothing else on them */
7
+ [data-stroke="on"] .bn-nod-card {
8
+ box-shadow: 0 0 0 1px var(--pane-edge);
9
+ border-radius: var(--nod-r, 20px);
10
+ }
11
+
12
+ [data-stroke="on"] .bn-nod-act {
13
+ box-shadow: 0 0 0 1px var(--pane-edge);
14
+ }
15
+
16
+ /* the box is the card, plus air above and below, never beside: the width stays the card's */
17
+ .bn-nod {
18
+ position: relative;
19
+ /* --nod-w is the Width prop; without it the box is the card's own 272 */
20
+ width: var(--nod-w, max-content);
21
+ font-family: var(--font-ui);
22
+ }
23
+
24
+ /* the card is painted over the fan, which is what makes the buttons come out of the corner rather than out of the
25
+ middle of the card: struck from a point inside it, at rest they sit under it with nothing showing */
26
+ .bn-nod-card {
27
+ position: relative;
28
+ z-index: 1;
29
+ box-sizing: border-box;
30
+ /* a block, left-aligned, no underline: the card is a div, a button or a link depending on who renders it */
31
+ display: block;
32
+ width: var(--nod-w, 272px);
33
+ padding: 20px;
34
+ text-align: left;
35
+ text-decoration: none;
36
+ /* set per render, the Corner knob. The buttons are not cut with it: they are discs */
37
+ border-radius: var(--nod-r, 20px);
38
+ background: var(--fill-slab, var(--card));
39
+ color: var(--fill-on, var(--ink));
40
+ /* it lifts a hair while the fan is out: the card answers at the same moment they do */
41
+ transition: translate 220ms cubic-bezier(0.24, 1.34, 0.38, 1);
42
+ }
43
+
44
+ .bn-nod-card[data-on] {
45
+ translate: 0 -1px;
46
+ }
47
+
48
+ button.bn-nod-card,
49
+ a.bn-nod-card {
50
+ cursor: pointer;
51
+ }
52
+
53
+ /* the card that is picked: a ring in the card's own ink, outside it so the layout does not move */
54
+ .bn-nod-card[data-selected] {
55
+ box-shadow: 0 0 0 2px var(--fill-on, var(--ink));
56
+ }
57
+
58
+ .bn-nod-top {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 8px;
62
+ }
63
+
64
+ /* a caller's own marker replaces the wash box: it brings its own shape */
65
+ .bn-nod-mark[data-bare] {
66
+ width: auto;
67
+ height: auto;
68
+ background: none;
69
+ border-radius: 0;
70
+ flex: none;
71
+ }
72
+
73
+ /* the product's mark, cut with the card (--nod-mark-r is a share of its radius; 15 on a 30px box is the old disc) */
74
+ .bn-nod-mark {
75
+ display: grid;
76
+ place-items: center;
77
+ width: 30px;
78
+ height: 30px;
79
+ border-radius: var(--nod-mark-r, 14px);
80
+ background: rgba(var(--ink-rgb), var(--wash));
81
+ color: var(--fill-on, var(--ink));
82
+ }
83
+
84
+ /* 500, not 600: at 15px nothing has been given away, so 600 is just loud on a card about four quiet buttons */
85
+ .bn-nod-name {
86
+ min-width: 0;
87
+ font-size: 16px;
88
+ font-weight: 500;
89
+ letter-spacing: -0.01em;
90
+ }
91
+
92
+ /* the faces, overlapped: a negative margin and a ring in the card's own colour (not a fixed white, which would draw
93
+ bright arcs on the dark fill). Further from the sentence than the sentence is from the name: who is on the step is
94
+ an aside under the reading. */
95
+ .bn-nod-chain {
96
+ display: flex;
97
+ align-items: center;
98
+ margin-top: 16px;
99
+ }
100
+
101
+ .bn-nod-link {
102
+ display: grid;
103
+ place-items: center;
104
+ width: 24px;
105
+ height: 24px;
106
+ border-radius: 999px;
107
+ overflow: hidden;
108
+ background: color-mix(in srgb, var(--fill-on, #17181a) 12%, transparent);
109
+ box-shadow: 0 0 0 2px var(--fill-slab, var(--card));
110
+ font-size: 11px;
111
+ font-weight: 500;
112
+ color: var(--ink-4);
113
+ letter-spacing: 0em;
114
+ }
115
+
116
+ .bn-nod-link img {
117
+ width: 100%;
118
+ height: 100%;
119
+ object-fit: cover;
120
+ }
121
+
122
+ .bn-nod-link + .bn-nod-link {
123
+ margin-left: -6px;
124
+ }
125
+
126
+ .bn-nod-more {
127
+ margin-left: 8px;
128
+ font-size: 11px;
129
+ font-weight: 500;
130
+ color: var(--ink-4);
131
+ letter-spacing: 0em;
132
+ }
133
+
134
+ .bn-nod-say {
135
+ display: block;
136
+ margin: 13px 0 0;
137
+ font-size: 13px;
138
+ line-height: 1.5;
139
+ color: var(--ink-3);
140
+ letter-spacing: 0em;
141
+ }
142
+
143
+ /* the fan: a zero-sized box pinned to the card's top-right corner. The anchor is a point; giving it a size would
144
+ measure every offset from an edge that has nothing to do with the corner. Under the card (it comes after it in the
145
+ markup, so without z-index every button would show from the first frame). */
146
+ .bn-nod-fan {
147
+ position: absolute;
148
+ z-index: 0;
149
+ width: 0;
150
+ height: 0;
151
+ }
152
+
153
+ /* the pad that makes the fan reachable: invisible, part of .bn-nod so the air between the card and the buttons is
154
+ not a place the pointer falls through. Inert until the fan is out. */
155
+ .bn-nod-reach {
156
+ position: absolute;
157
+ pointer-events: none;
158
+ }
159
+
160
+ .bn-nod-reach[data-on] {
161
+ pointer-events: auto;
162
+ }
163
+
164
+ .bn-nod-act {
165
+ position: absolute;
166
+ /* the anchor is a point and the translate in the component moves the button from it */
167
+ top: 0;
168
+ left: 0;
169
+ display: grid;
170
+ place-items: center;
171
+ padding: 0;
172
+ border: 0;
173
+ border-radius: 999px;
174
+ background: var(--fill-slab, var(--card));
175
+ color: var(--fill-on, var(--ink));
176
+ cursor: pointer;
177
+ -webkit-tap-highlight-color: transparent;
178
+ /* two writers, one scale: the spring owns how far out a button is (--pop, every frame) and the hover owns whether
179
+ it is pointed at (--zoom, twice per hover). Only --zoom is transitioned, so the easing never smooths the
180
+ spring's own frames. */
181
+ scale: calc(var(--pop, 1) * var(--zoom, 1));
182
+ transition: --zoom 160ms cubic-bezier(0.24, 1.34, 0.38, 1);
183
+ }
184
+
185
+ /* the hover is the size and nothing else: one cue, the button grows and stays the colour it was */
186
+ .bn-nod-act:hover {
187
+ --zoom: 1.12;
188
+ }
189
+
190
+ /* the fan flipped: the same geometry about the card's horizontal axis, so the glyphs are flipped back */
191
+ .bn-nod[data-fan="down"] .bn-nod-fan {
192
+ transform: scaleY(-1);
193
+ }
194
+
195
+ .bn-nod[data-fan="down"] .bn-nod-act svg {
196
+ scale: 1 -1;
197
+ }
198
+
199
+ /* dense: for a card among cells (the pipeline stepper). The type and the padding give way, the corner radius is the
200
+ caller's, and the buttons keep their size: they are for a pointer, not for reading. */
201
+ .bn-nod[data-dense] .bn-nod-card {
202
+ padding: 10px 12px;
203
+ }
204
+
205
+ .bn-nod[data-dense] .bn-nod-top {
206
+ gap: 7px;
207
+ }
208
+
209
+ .bn-nod[data-dense] .bn-nod-name {
210
+ font-size: 12.5px;
211
+ line-height: 1.2;
212
+ }
213
+
214
+ .bn-nod[data-dense] .bn-nod-say {
215
+ margin-top: 6px;
216
+ font-size: 11.5px;
217
+ line-height: 1.3;
218
+ }
219
+
220
+ .bn-nod[data-dense] .bn-nod-chain {
221
+ margin-top: 8px;
222
+ }
223
+
224
+ .bn-nod-act:disabled {
225
+ opacity: 0.45;
226
+ cursor: default;
227
+ }
@@ -0,0 +1,182 @@
1
+ /* Checklist (components/bencho/Checklist.tsx), as on bencho.dev.
2
+ Three tasks; tick one and the box fills, the tick draws in and the words strike out. */
3
+ .bn-chk {
4
+ box-sizing: border-box;
5
+ /* it opens, it does not jump: the component states its height rather than letting the rows add up to it, which is
6
+ what gives this something to interpolate. The overflow is clipped so it reads as the card opening: the new task
7
+ takes the empty slot the spare row was in. */
8
+ overflow: hidden;
9
+ transition: height 420ms cubic-bezier(0.22, 1, 0.36, 1);
10
+ /* the rows stay in flow and the fall is a transform on each; this is the stacking context the heap's z-order needs */
11
+ position: relative;
12
+ background: var(--fill-slab, var(--card));
13
+ font-family: var(--font-ui);
14
+ color: var(--fill-on, var(--ink));
15
+ }
16
+
17
+ /* the hairline is the Stroke control's to give: a spread shadow rather than a border, so it costs the card no layout */
18
+ [data-stroke="on"] .bn-chk {
19
+ box-shadow: 0 0 0 1px var(--pane-edge);
20
+ }
21
+
22
+ .bn-chk-row {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 12px;
26
+ width: 100%;
27
+ /* so the heap can be ordered: the row that lands last is the top of the pile, the opposite of DOM order */
28
+ position: relative;
29
+ /* nothing at the sides: the card's own padding is the inset */
30
+ padding: 0;
31
+ border: 0;
32
+ border-radius: 10px;
33
+ background: none;
34
+ color: inherit;
35
+ font: inherit;
36
+ text-align: left;
37
+ cursor: pointer;
38
+ /* the row is the target, not the box: the words beside it are the part anybody points at */
39
+ -webkit-tap-highlight-color: transparent;
40
+ }
41
+
42
+ /* The spare row is the one thing here that is not a task, so it has the same geometry as a row and a third of the ink.
43
+ The type is set once on the row: a button and an input are the same line of text at two moments. */
44
+ .bn-chk-add {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 12px;
48
+ width: 100%;
49
+ font-size: 14px;
50
+ font-weight: 400;
51
+ letter-spacing: -0.01em;
52
+ transition: opacity 220ms ease;
53
+ }
54
+
55
+ /* it keeps its space while the heap is down: removing it would change the card's height in the middle of a fall */
56
+ .bn-chk-add[data-hide] {
57
+ opacity: 0;
58
+ pointer-events: none;
59
+ }
60
+
61
+ /* the box that is not a checkbox: the ring at a fraction of its strength and nothing inside it */
62
+ .bn-chk-ghost {
63
+ flex: none;
64
+ box-shadow: inset 0 0 0 1.5px rgba(var(--fill-on-rgb, var(--ink-rgb)), 0.16);
65
+ }
66
+
67
+ .bn-chk-new {
68
+ padding: 0;
69
+ border: 0;
70
+ background: none;
71
+ color: inherit;
72
+ font: inherit;
73
+ /* `font: inherit` does not carry this: a button and an input take letter-spacing from the UA sheet */
74
+ letter-spacing: inherit;
75
+ text-align: left;
76
+ cursor: pointer;
77
+ opacity: 0.34;
78
+ transition: opacity 160ms ease;
79
+ }
80
+
81
+ .bn-chk-new:hover {
82
+ opacity: 0.62;
83
+ }
84
+
85
+ .bn-chk-field {
86
+ flex: 1;
87
+ min-width: 0;
88
+ padding: 0;
89
+ border: 0;
90
+ background: none;
91
+ color: inherit;
92
+ font: inherit;
93
+ letter-spacing: inherit;
94
+ outline: none;
95
+ }
96
+
97
+ .bn-chk-field::placeholder {
98
+ color: color-mix(in srgb, var(--fill-on, #17181a) 32%, var(--fill-slab, #ffffff));
99
+ }
100
+
101
+ /* On the floor: opaque, and only once it has fallen, or two tasks read through each other at three degrees of rotation.
102
+ On a pseudo-element, inset, because the row's own box is not the object that falls: a row is 40px tall so that the
103
+ whole line is a target, what you see is about twenty. The component sends its own inset down as --slip. Behind the
104
+ words: the row carries a z-index while fallen, which makes it a stacking context, so the negative one lands under
105
+ its own contents and nothing else. */
106
+ .bn-chk-row[data-fell]::before {
107
+ content: "";
108
+ position: absolute;
109
+ left: 0;
110
+ right: 0;
111
+ top: var(--slip, 9px);
112
+ bottom: var(--slip, 9px);
113
+ border-radius: 10px;
114
+ background: var(--fill-slab, var(--card));
115
+ z-index: -1;
116
+ }
117
+
118
+ .bn-chk-row:focus-visible {
119
+ outline: none;
120
+ box-shadow: 0 0 0 2px rgba(var(--fill-on-rgb, var(--ink-rgb)), 0.4);
121
+ }
122
+
123
+ /* The ring never changes; the fill opens inside it. `overflow: hidden` keeps the fill's overshoot inside the ring. */
124
+ .bn-chk-box {
125
+ position: relative;
126
+ flex: none;
127
+ display: grid;
128
+ place-items: center;
129
+ overflow: hidden;
130
+ box-shadow: inset 0 0 0 1.5px rgba(var(--fill-on-rgb, var(--ink-rgb)), 0.28);
131
+ }
132
+
133
+ .bn-chk-fill {
134
+ position: absolute;
135
+ inset: 0;
136
+ background: var(--fill-on, var(--ink));
137
+ /* from the middle out: a corner origin would read as a panel sliding in */
138
+ transform-origin: center;
139
+ }
140
+
141
+ /* Drawn rather than faded: stroke-dashoffset walks the stroke on from its start. It sits on the fill and takes the
142
+ fill's own ground colour, so it can be full strength and invisible until there is something under it. */
143
+ .bn-chk-tick {
144
+ position: relative;
145
+ width: 68%;
146
+ height: 68%;
147
+ overflow: visible;
148
+ fill: none;
149
+ stroke: var(--fill-slab, var(--card));
150
+ stroke-width: 2.6;
151
+ stroke-linecap: round;
152
+ stroke-linejoin: round;
153
+ }
154
+
155
+ .bn-chk-say {
156
+ position: relative;
157
+ /* sized to the words, not to the row: the rule is a scale of this box, so a full-width span would strike out the
158
+ empty half of the line as well */
159
+ display: inline-block;
160
+ font-size: 14px;
161
+ font-weight: 400;
162
+ letter-spacing: -0.01em;
163
+ line-height: 1.2;
164
+ }
165
+
166
+ .bn-chk-word {
167
+ display: inline-block;
168
+ }
169
+
170
+ .bn-chk-rule {
171
+ position: absolute;
172
+ left: 0;
173
+ right: 0;
174
+ /* on the type's own middle rather than the box's: a rule centred on a line box sits low, the box carries the
175
+ descender space and the letters do not */
176
+ top: 46%;
177
+ height: 1.5px;
178
+ border-radius: 2px;
179
+ background: currentColor;
180
+ transform-origin: left center;
181
+ pointer-events: none;
182
+ }
@@ -0,0 +1,188 @@
1
+ /* Magnifying dock (components/bencho/MagnifyDock.tsx), as on bencho.dev.
2
+ Glass navigation: 60% white, 2px frost, refraction, lit inner edge. The glass tokens (--pane, --g-*) come from the
3
+ stage's data-surface="glass". */
4
+ .bn-gdock {
5
+ position: relative;
6
+ background: var(--pane);
7
+ -webkit-backdrop-filter: blur(var(--g-blur, 2px)) saturate(var(--g-sat, 150%)) brightness(var(--g-bright, 100%));
8
+ backdrop-filter: blur(var(--g-blur, 2px)) saturate(var(--g-sat, 150%)) brightness(var(--g-bright, 100%));
9
+ isolation: isolate;
10
+ color: var(--ink);
11
+ }
12
+
13
+ .bn-gdock::before {
14
+ content: "";
15
+ position: absolute;
16
+ inset: 0;
17
+ border-radius: inherit;
18
+ /* transparent unless Stroke says otherwise, see the stroke block below */
19
+ border: 1px solid transparent;
20
+ pointer-events: none;
21
+ z-index: 3;
22
+ }
23
+
24
+ .bn-gdock {
25
+ display: inline-flex;
26
+ align-items: flex-end;
27
+ gap: 12px;
28
+ padding: 12px 16px;
29
+ border-radius: 28px;
30
+ /* sideways is the dock's, up and down stays the page's: a finger passing over this on the way down is scrolling,
31
+ not picking */
32
+ touch-action: pan-y;
33
+ }
34
+
35
+ /* no tile behind the icon: scaling a translucent rectangle over glass is what made this look cheap. The glyph scales
36
+ instead, and SVG scales cleanly at any size. */
37
+ .bn-gdock-item {
38
+ position: relative;
39
+ width: 44px;
40
+ height: 44px;
41
+ border: 0;
42
+ background: none;
43
+ color: var(--ink);
44
+ opacity: 0.42;
45
+ display: grid;
46
+ place-items: center;
47
+ cursor: pointer;
48
+ transition: opacity 220ms ease;
49
+ }
50
+
51
+ /* the magnification lives here, so the dot and the label keep their own size and their own distance from the bar */
52
+ .bn-gdock-glyph {
53
+ display: grid;
54
+ place-items: center;
55
+ transform-origin: center bottom;
56
+ transition: transform 420ms cubic-bezier(0.24, 1.34, 0.38, 1);
57
+ }
58
+
59
+ .bn-gdock-item[data-near="1"] {
60
+ opacity: 0.66;
61
+ }
62
+
63
+ .bn-gdock-item[data-near="0"] {
64
+ opacity: 1;
65
+ }
66
+
67
+ .bn-gdock-item[data-active="true"] {
68
+ opacity: 1;
69
+ }
70
+
71
+ .bn-gdock-item:focus-visible {
72
+ outline: 1.5px solid var(--ink);
73
+ outline-offset: 3px;
74
+ border-radius: 10px;
75
+ }
76
+
77
+ /* the running dot, the only persistent mark in the dock. Inside the item, not slung underneath it: the glyph is 20px
78
+ in a 44px box, so anything hung off the bottom edge lands 17px from the mark it belongs to. */
79
+ .bn-gdock-dot {
80
+ position: absolute;
81
+ left: 50%;
82
+ bottom: 3px;
83
+ width: 3px;
84
+ height: 3px;
85
+ margin-left: -1.5px;
86
+ border-radius: 50%;
87
+ background: var(--ink);
88
+ opacity: 0;
89
+ transform: scale(0.4);
90
+ transition:
91
+ opacity 220ms ease,
92
+ transform 420ms cubic-bezier(0.24, 1.34, 0.38, 1);
93
+ }
94
+
95
+ .bn-gdock-dot[data-on="true"] {
96
+ opacity: 0.55;
97
+ transform: scale(1);
98
+ }
99
+
100
+ /* One tip for the whole dock: a dark pill above the bar that slides to the icon under the pointer and grows or shrinks
101
+ to the word, both on the same 160ms curve, and fades in 120ms. The word is centred in a box whose width animates,
102
+ and the box clips it, so a longer word is uncovered rather than squeezed. */
103
+ .bn-gdock-tip {
104
+ position: absolute;
105
+ left: 0;
106
+ bottom: calc(100% + 8px);
107
+ height: 32px;
108
+ display: inline-flex;
109
+ align-items: center;
110
+ justify-content: center;
111
+ overflow: hidden;
112
+ border-radius: 999px;
113
+ background: var(--fill-on, var(--ink));
114
+ color: var(--fill-slab, var(--on-ink));
115
+ font-family: var(--font-ui);
116
+ font-size: 12px;
117
+ font-weight: 400;
118
+ line-height: 32px;
119
+ white-space: nowrap;
120
+ opacity: 0;
121
+ pointer-events: none;
122
+ z-index: 2;
123
+ transition:
124
+ transform 160ms cubic-bezier(0.22, 1, 0.36, 1),
125
+ width 160ms cubic-bezier(0.22, 1, 0.36, 1),
126
+ opacity 160ms cubic-bezier(0.22, 1, 0.36, 1);
127
+ letter-spacing: 0em;
128
+ }
129
+
130
+ .bn-gdock-tip > span {
131
+ flex: none;
132
+ }
133
+
134
+ .bn-gdock-tip:not([data-ready]) {
135
+ transition: opacity 160ms cubic-bezier(0.22, 1, 0.36, 1);
136
+ }
137
+
138
+ .bn-gdock-tip[data-on="true"] {
139
+ opacity: 1;
140
+ }
141
+
142
+ /* The frost is switched off at the element, not through a token: a var() holding a url() filter cannot be validated at
143
+ parse time, so the browser would drop the property outright, which breaks the two-declaration fallback the glass
144
+ depends on. */
145
+ [data-surface="flat"] .bn-gdock {
146
+ -webkit-backdrop-filter: none;
147
+ backdrop-filter: none;
148
+ }
149
+
150
+ /* Stroke: whether the dock draws a hairline round itself. Off by default; glass draws its own lit rim, which is the
151
+ material rather than a stroke, so glass wins the border wherever the two meet (this sits above the glass rule). */
152
+ [data-stroke="on"] .bn-gdock::before {
153
+ border-color: var(--pane-edge);
154
+ }
155
+
156
+ /* The rim: a gradient ring, bright at the corner facing the light, almost nothing along the sides that run edge-on to
157
+ it, and a second weaker catch at the far corner. A gradient in a border needs the border box filled and the padding
158
+ box punched out of it, which is what the two masks do. Only under glass. */
159
+ [data-surface="glass"] .bn-gdock::before {
160
+ border-color: transparent;
161
+ background: linear-gradient(
162
+ 142deg,
163
+ rgba(255, 255, 255, var(--g-edge-hi, 0)) 0%,
164
+ rgba(255, 255, 255, var(--g-edge-lo, 0)) 26%,
165
+ rgba(255, 255, 255, var(--g-edge-lo, 0)) 62%,
166
+ rgba(255, 255, 255, var(--g-edge-far, 0)) 100%
167
+ )
168
+ border-box;
169
+ -webkit-mask:
170
+ linear-gradient(#000 0 0) padding-box,
171
+ linear-gradient(#000 0 0);
172
+ -webkit-mask-composite: xor;
173
+ mask:
174
+ linear-gradient(#000 0 0) padding-box,
175
+ linear-gradient(#000 0 0);
176
+ mask-composite: exclude;
177
+ }
178
+
179
+ /* Thickness: the lit line along the top is where light enters the edge of a real sheet, the shadow underneath says the
180
+ pane is above the page rather than painted onto it (the one thing a backdrop-filter alone can never say). Both ride on
181
+ --glass and are gone at zero, where the fallbacks take over and the surface is flat. */
182
+ [data-surface="glass"] .bn-gdock {
183
+ box-shadow: var(
184
+ --g-shadow,
185
+ inset 0 1px 0 rgba(255, 255, 255, var(--g-spec, 0)),
186
+ 0 10px 40px rgba(var(--shadow-rgb), var(--g-lift, 0))
187
+ );
188
+ }
@@ -0,0 +1,109 @@
1
+ /* The /bencho gallery page (pages/BenchoPage). The element reset the components need lives in scope.css. */
2
+
3
+ .bencho {
4
+ min-height: 100%;
5
+ padding: 28px 32px 64px;
6
+ background: var(--bg);
7
+ color: var(--ink);
8
+ font-family: var(--font-ui);
9
+ -webkit-font-smoothing: antialiased;
10
+ }
11
+ .bn-top {
12
+ display: flex;
13
+ flex-wrap: wrap;
14
+ align-items: flex-end;
15
+ justify-content: space-between;
16
+ gap: 16px 32px;
17
+ margin-bottom: 24px;
18
+ }
19
+ .bn-top h1 {
20
+ font-size: 26px;
21
+ font-weight: 500;
22
+ letter-spacing: -0.02em;
23
+ }
24
+ .bn-top p {
25
+ margin-top: 4px;
26
+ font-size: 14px;
27
+ color: var(--ink-4);
28
+ }
29
+ .bn-controls {
30
+ display: flex;
31
+ flex-wrap: wrap;
32
+ gap: 8px 16px;
33
+ }
34
+ .bn-choice {
35
+ display: inline-flex;
36
+ align-items: center;
37
+ gap: 2px;
38
+ padding: 3px;
39
+ border-radius: 999px;
40
+ background: var(--surface-2);
41
+ font-size: 13px;
42
+ }
43
+ .bn-choice span {
44
+ padding: 0 8px 0 10px;
45
+ color: var(--ink-4);
46
+ }
47
+ .bn-choice button {
48
+ padding: 5px 12px;
49
+ border-radius: 999px;
50
+ color: var(--ink-3);
51
+ }
52
+ .bn-choice button[aria-pressed="true"] {
53
+ background: var(--bg);
54
+ color: var(--ink);
55
+ box-shadow: 0 1px 2px rgb(var(--shadow-rgb) / 0.12);
56
+ }
57
+
58
+ .bn-grid {
59
+ display: grid;
60
+ grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
61
+ gap: 16px;
62
+ }
63
+ .bn-card {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 6px;
67
+ min-width: 0;
68
+ padding: 14px;
69
+ border-radius: 20px;
70
+ background: var(--surface);
71
+ }
72
+ .bn-card > header {
73
+ display: flex;
74
+ align-items: baseline;
75
+ justify-content: space-between;
76
+ gap: 12px;
77
+ }
78
+ .bn-card h2 {
79
+ font-size: 15px;
80
+ font-weight: 500;
81
+ }
82
+ .bn-cat {
83
+ font-size: 11px;
84
+ letter-spacing: 0.06em;
85
+ text-transform: uppercase;
86
+ color: var(--ink-5);
87
+ }
88
+ .bn-card > p {
89
+ font-size: 13px;
90
+ line-height: 1.45;
91
+ color: var(--ink-4);
92
+ }
93
+ .bn-stage {
94
+ position: relative;
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ min-height: 280px;
99
+ margin-top: 8px;
100
+ padding: 24px;
101
+ overflow: hidden;
102
+ border-radius: 14px;
103
+ background: var(--board);
104
+ color: var(--ink);
105
+ }
106
+ /* bencho.dev's stage is paper under the light fill, so white components (cells, pills, tracks) stay visible */
107
+ .bn-stage[data-fill="light"] {
108
+ background: var(--paper-2);
109
+ }