@crievo/omni 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/onboarding.md +69 -161
- package/omnivideo.config.example.json +1 -1
- package/package.json +2 -1
- package/reference/learnings.md +12 -0
- package/reference/omnichat.md +5 -4
- package/reference/publish.md +9 -9
- package/reference/studio.md +1 -1
- package/studio/api/app.ts +2 -2
- package/studio/api/routes/sound.ts +22 -3
- package/studio/api/services/onboarding.ts +242 -21
- package/studio/api/services/settings.ts +4 -2
- package/studio/api/services/sound.ts +45 -0
- package/studio/shared/types/api.ts +26 -1
- package/studio/studio.ts +1 -0
- package/studio/web/components/AppShell.tsx +3 -1
- package/studio/web/components/CloneVoiceDialog/RecordStep.tsx +3 -0
- package/studio/web/components/CloneVoiceDialog/RecorderBar.tsx +37 -20
- package/studio/web/components/CloneVoiceDialog/useCloneVoice.ts +39 -12
- package/studio/web/components/MissingDmsPanel/BatchSend.tsx +13 -4
- package/studio/web/components/Omni/OmniWidget.tsx +14 -5
- package/studio/web/components/Omni/PendingActions.tsx +25 -6
- package/studio/web/components/OmnichatRulePanel/RuleConfig.tsx +13 -3
- package/studio/web/components/OmnichatRulePanel/RuleEnableConfirm.tsx +14 -4
- package/studio/web/components/ResultMessage.tsx +12 -3
- package/studio/web/components/Sidebar.tsx +63 -24
- package/studio/web/components/StageStepper.tsx +105 -34
- package/studio/web/components/Toaster.tsx +25 -0
- package/studio/web/components/VoiceCarousel.tsx +19 -16
- package/studio/web/components/VoiceProfilesSettings.tsx +8 -3
- package/studio/web/components/bencho/ActionNode.tsx +456 -0
- package/studio/web/components/bencho/AssigneePicker.tsx +246 -0
- package/studio/web/components/bencho/BenchoScope.tsx +8 -0
- package/studio/web/components/bencho/Checklist.tsx +421 -0
- package/studio/web/components/bencho/CreateMenu.tsx +258 -0
- package/studio/web/components/bencho/GenerateImage.tsx +290 -0
- package/studio/web/components/bencho/ImageAccordion.tsx +273 -0
- package/studio/web/components/bencho/LiquidToggle.tsx +289 -0
- package/studio/web/components/bencho/MagnifyDock.tsx +200 -0
- package/studio/web/components/bencho/NowPlaying.tsx +403 -0
- package/studio/web/components/bencho/OneTimeCode.tsx +374 -0
- package/studio/web/components/bencho/PullToRefresh.tsx +527 -0
- package/studio/web/components/bencho/SlideToConfirm.tsx +385 -0
- package/studio/web/components/bencho/SloshSlider.tsx +394 -0
- package/studio/web/components/bencho/StackedBars.tsx +342 -0
- package/studio/web/components/bencho/ToastStack.tsx +263 -0
- package/studio/web/components/bencho/UploadDropzone.tsx +394 -0
- package/studio/web/components/bencho/VoiceNote.tsx +705 -0
- package/studio/web/components/icons.tsx +27 -0
- package/studio/web/components/ui/slider.tsx +34 -1
- package/studio/web/components/ui/switch.tsx +18 -5
- package/studio/web/hooks/useOnboarding.ts +24 -6
- package/studio/web/index.html +1 -1
- package/studio/web/lib/alerts.ts +10 -0
- package/studio/web/lib/omni-panel.ts +4 -1
- package/studio/web/lib/toast.ts +196 -0
- package/studio/web/lib/upload.ts +24 -0
- package/studio/web/pages/BenchoPage/index.tsx +74 -0
- package/studio/web/pages/BenchoPage/registry.ts +174 -0
- package/studio/web/pages/GettingStartedPage/components/ComputerCard.tsx +102 -0
- package/studio/web/pages/GettingStartedPage/index.tsx +84 -58
- package/studio/web/pages/NewVideoPage/index.tsx +6 -2
- package/studio/web/pages/ReferencesPage/index.tsx +4 -1
- package/studio/web/pages/SettingsPage/components/AgentAccounts.tsx +30 -3
- package/studio/web/pages/VideoPage/components/ChangedVoices.tsx +14 -17
- package/studio/web/pages/VideoPage/components/LineTextEditor.tsx +1 -1
- package/studio/web/pages/VideoPage/components/PublishForm.tsx +24 -17
- package/studio/web/pages/VideoPage/components/ScheduledInfo.tsx +2 -1
- package/studio/web/pages/VideoPage/components/SoundTimeline/AddSound.tsx +12 -14
- package/studio/web/pages/VideoPage/components/SoundTimeline/BedRow.tsx +27 -11
- package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionSaveBar.tsx +22 -18
- package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineFooter.tsx +3 -4
- package/studio/web/pages/VideoPage/components/SoundTimeline/UploadTrack.tsx +59 -0
- package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundTimeline.ts +1 -0
- package/studio/web/pages/VideoPage/components/VoicePauseBanner.tsx +4 -6
- package/studio/web/pages/VideoPage/index.tsx +4 -0
- package/studio/web/pages/VideosPage/components/VideoRow.tsx +2 -4
- package/studio/web/router.tsx +8 -0
- package/studio/web/styles/components/bencho/action-node.css +227 -0
- package/studio/web/styles/components/bencho/checklist.css +182 -0
- package/studio/web/styles/components/bencho/dock.css +188 -0
- package/studio/web/styles/components/bencho/gallery.css +109 -0
- package/studio/web/styles/components/bencho/generate.css +151 -0
- package/studio/web/styles/components/bencho/image-accordion.css +37 -0
- package/studio/web/styles/components/bencho/liq-create.css +279 -0
- package/studio/web/styles/components/bencho/liq-toggle.css +106 -0
- package/studio/web/styles/components/bencho/one-time-code.css +148 -0
- package/studio/web/styles/components/bencho/picker.css +204 -0
- package/studio/web/styles/components/bencho/pull.css +313 -0
- package/studio/web/styles/components/bencho/scope.css +42 -0
- package/studio/web/styles/components/bencho/slide-confirm.css +139 -0
- package/studio/web/styles/components/bencho/slosh.css +137 -0
- package/studio/web/styles/components/bencho/sound.css +260 -0
- package/studio/web/styles/components/bencho/stacked-bars.css +334 -0
- package/studio/web/styles/components/bencho/toast-stack.css +192 -0
- package/studio/web/styles/components/bencho/tokens.css +243 -0
- package/studio/web/styles/components/bencho/upload-dropzone.css +352 -0
- package/studio/web/styles/components/bencho/voice-note.css +337 -0
- package/studio/web/styles/components/clone-voice.css +12 -2
- package/studio/web/styles/components/omni.css +6 -1
- package/studio/web/styles/components/stepper.css +123 -7
- package/studio/web/styles/components/toaster.css +10 -0
- package/studio/web/styles/components/ui/slider.css +6 -16
- package/studio/web/styles/components/ui/switch.css +2 -41
- package/studio/web/styles/components/voice-carousel.css +46 -4
- package/studio/web/styles/index.css +23 -0
- package/studio/web/styles/pages/getting-started.css +46 -0
- package/studio/web/styles/pages/settings/shell.css +4 -0
- package/studio/web/styles/pages/video/layout.css +1 -4
- package/studio/web/styles/pages/video/sound-timeline.css +8 -0
- package/studio/web/styles/theme/identity/shell.css +10 -26
- package/tools/lib/agents.mjs +24 -13
- package/tools/lib/claude-settings.mjs +32 -3
- package/tools/lib/deps.mjs +24 -0
- package/tools/lib/platforms/instagram.mjs +1 -1
- package/tools/lib/up.mjs +5 -2
- package/tools/omni.mjs +7 -5
- package/tools/omnichat.mjs +5 -7
- package/tools/package.mjs +5 -3
- package/tools/publish.mjs +40 -137
- package/tools/reference.mjs +1 -1
- package/tools/setup.mjs +16 -15
- package/tools/sound.mjs +67 -3
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/* Generate (components/bencho/GenerateImage.tsx), as on bencho.dev.
|
|
2
|
+
A picture card and a pill in its middle. The card is a picture edge to edge, so it answers neither fill nor stroke;
|
|
3
|
+
the pill answers both. */
|
|
4
|
+
.bn-gen {
|
|
5
|
+
font-family: var(--font-ui);
|
|
6
|
+
/* positioned, and load-bearing on a canvas: a board's grab halo is a positioned ::before, and a positioned layer
|
|
7
|
+
paints over every non-positioned sibling after it, so the pill sat under the halo and never heard a click */
|
|
8
|
+
position: relative;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 12px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.bn-gen-card {
|
|
16
|
+
position: relative;
|
|
17
|
+
width: 270px;
|
|
18
|
+
/* 330, down from the picture's own 360 (3:4). The print is `cover`, so the frame trims a sliver of paper top and
|
|
19
|
+
bottom rather than squashing the flower */
|
|
20
|
+
height: 330px;
|
|
21
|
+
border-radius: 20px;
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
/* white, the blank page. The print's own paper is a warm grey, and the run carries the card from one to the other:
|
|
24
|
+
the haze fades up over the white, so the page greys as the picture arrives rather than starting grey */
|
|
25
|
+
background: var(--fill-slab, #ffffff);
|
|
26
|
+
/* the corner clips a canvas and an image, and Safari leaks both past a radius without a layer of its own */
|
|
27
|
+
isolation: isolate;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.bn-gen-print {
|
|
31
|
+
position: absolute;
|
|
32
|
+
inset: 0;
|
|
33
|
+
width: 100%;
|
|
34
|
+
height: 100%;
|
|
35
|
+
user-select: none;
|
|
36
|
+
-webkit-user-drag: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* the filter's defs, out of the flow: an svg with no size is 300x150 of layout otherwise */
|
|
40
|
+
.bn-gen-defs {
|
|
41
|
+
position: absolute;
|
|
42
|
+
width: 0;
|
|
43
|
+
height: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
[data-stroke="on"] .bn-gen-card {
|
|
47
|
+
box-shadow: 0 0 0 1px var(--pane-edge);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* the button, in the middle of the frame: light grey on the white page. It answers the pointer by size only, a little
|
|
51
|
+
on hover, more on the press, and leaves by shrinking and fading when the run starts. Every colour is the fill
|
|
52
|
+
control's, so dark gives a dark frame, a lifted pill and light ink */
|
|
53
|
+
.bn-gen-go {
|
|
54
|
+
position: absolute;
|
|
55
|
+
left: 50%;
|
|
56
|
+
top: 50%;
|
|
57
|
+
z-index: 2;
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 8px;
|
|
61
|
+
height: 42px;
|
|
62
|
+
padding: 0 20px;
|
|
63
|
+
border: 0;
|
|
64
|
+
border-radius: 20px;
|
|
65
|
+
background: color-mix(in srgb, var(--fill-on, #17181a) 6%, var(--fill-slab, #ffffff));
|
|
66
|
+
font: inherit;
|
|
67
|
+
font-size: 14px;
|
|
68
|
+
font-weight: 500;
|
|
69
|
+
color: var(--fill-on, #17181a);
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
translate: -50% -50%;
|
|
72
|
+
transition:
|
|
73
|
+
opacity 220ms ease,
|
|
74
|
+
scale 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
75
|
+
letter-spacing: -0.01em;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.bn-gen-go:hover:not(:disabled) {
|
|
79
|
+
scale: 0.97;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.bn-gen-go:active:not(:disabled) {
|
|
83
|
+
scale: 0.96;
|
|
84
|
+
transition-duration: 160ms;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.bn-gen-go:focus-visible {
|
|
88
|
+
outline: none;
|
|
89
|
+
box-shadow:
|
|
90
|
+
0 0 0 2px var(--fill-slab, #ffffff),
|
|
91
|
+
0 0 0 4px rgba(var(--fill-on-rgb, 23, 24, 26), 0.45);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* gone while a picture is being made */
|
|
95
|
+
.bn-gen[data-loading="true"] .bn-gen-go {
|
|
96
|
+
opacity: 0;
|
|
97
|
+
scale: 0.9;
|
|
98
|
+
pointer-events: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* made: out of the way until the pointer is on the picture, and white over the print: the grey that sits quietly on a
|
|
102
|
+
blank page reads as a smudge on a grey halftone */
|
|
103
|
+
.bn-gen[data-made="true"]:not([data-loading="true"]) .bn-gen-go {
|
|
104
|
+
opacity: 0;
|
|
105
|
+
background: var(--fill-slab, #ffffff);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.bn-gen[data-made="true"]:not([data-loading="true"]) .bn-gen-card:hover .bn-gen-go,
|
|
109
|
+
.bn-gen[data-made="true"]:not([data-loading="true"]) .bn-gen-go:focus-visible {
|
|
110
|
+
opacity: 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* the count: a small pill in the bottom-left corner, only while a run is going (and for the beat it holds at 100) */
|
|
114
|
+
.bn-gen-pct {
|
|
115
|
+
position: absolute;
|
|
116
|
+
left: 10px;
|
|
117
|
+
bottom: 10px;
|
|
118
|
+
z-index: 2;
|
|
119
|
+
display: grid;
|
|
120
|
+
place-items: center;
|
|
121
|
+
height: 22px;
|
|
122
|
+
padding: 0 8px;
|
|
123
|
+
border-radius: 999px;
|
|
124
|
+
background: color-mix(in srgb, var(--fill-slab, #ffffff) 80%, transparent);
|
|
125
|
+
-webkit-backdrop-filter: blur(8px);
|
|
126
|
+
backdrop-filter: blur(8px);
|
|
127
|
+
font-size: 11px;
|
|
128
|
+
font-weight: 500;
|
|
129
|
+
line-height: 1;
|
|
130
|
+
color: var(--fill-on, #17181a);
|
|
131
|
+
font-variant-numeric: tabular-nums;
|
|
132
|
+
opacity: 0;
|
|
133
|
+
translate: 0 4px;
|
|
134
|
+
transition:
|
|
135
|
+
opacity 220ms ease,
|
|
136
|
+
translate 220ms ease;
|
|
137
|
+
pointer-events: none;
|
|
138
|
+
letter-spacing: 0em;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.bn-gen[data-loading="true"] .bn-gen-pct {
|
|
142
|
+
opacity: 1;
|
|
143
|
+
translate: 0 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@media (prefers-reduced-motion: reduce) {
|
|
147
|
+
.bn-gen-go,
|
|
148
|
+
.bn-gen-pct {
|
|
149
|
+
transition-duration: 160ms;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* Image accordion (components/bencho/ImageAccordion.tsx), as on bencho.dev.
|
|
2
|
+
A row of pictures cropped to slivers, one of which opens under the pointer. The arithmetic of who pays for that is
|
|
3
|
+
in the component (five grow numbers on five springs); here is the box they negotiate inside and the crop that keeps
|
|
4
|
+
a picture a picture while it happens. */
|
|
5
|
+
.bn-acc {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: row;
|
|
8
|
+
align-items: stretch;
|
|
9
|
+
/* the strip's radius is set in the component and is the same number every picture takes (RMAX). The clip stays
|
|
10
|
+
because flex widths land on fractions and a cell can overhang its container by a sub-pixel. */
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
-webkit-user-select: none;
|
|
13
|
+
user-select: none;
|
|
14
|
+
/* a finger dragged across the strip reads as a hover, not as an attempt to scroll the page sideways */
|
|
15
|
+
touch-action: pan-y;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.bn-acc-cell {
|
|
19
|
+
position: relative;
|
|
20
|
+
/* the grow number is the whole story: `flex-basis: 0` so nothing is reserved before the negotiation starts, and
|
|
21
|
+
`min-width: 0` because a flex item will not shrink below its content by default and the content is an image */
|
|
22
|
+
flex-basis: 0;
|
|
23
|
+
flex-shrink: 1;
|
|
24
|
+
min-width: 0;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
/* the radius is set per cell from the gap, so no notch of page shows at the joins */
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.bn-acc-cell img {
|
|
30
|
+
display: block;
|
|
31
|
+
width: 100%;
|
|
32
|
+
height: 100%;
|
|
33
|
+
/* `cover`, and the height fixes the scale: widening a cell admits more of the picture on each side and moves
|
|
34
|
+
nothing that was already showing, so it reads as a window opening rather than a picture being pulled about */
|
|
35
|
+
object-fit: cover;
|
|
36
|
+
-webkit-user-drag: none;
|
|
37
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/* Create menu (components/bencho/CreateMenu.tsx), as on bencho.dev.
|
|
2
|
+
The menu has no ::before to hang a ring on: its shape is a blob on its own layer, so the ring is a spread shadow,
|
|
3
|
+
which is the same mark by another means. */
|
|
4
|
+
[data-stroke="on"] .bn-crt-body {
|
|
5
|
+
box-shadow: 0 0 0 1px var(--pane-edge);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* The stage, the panel and the rows are the numbers this was drawn at: the open state is sized by the four rows it
|
|
9
|
+
holds, and the stage by the panel plus the room the shape needs to travel. */
|
|
10
|
+
.bn-crt-stage {
|
|
11
|
+
position: relative;
|
|
12
|
+
width: 244px;
|
|
13
|
+
height: var(--stage-h, 244px);
|
|
14
|
+
/* the two states, as two sets of numbers: one attribute picks between them and everything else follows */
|
|
15
|
+
/* The pill only. Its width is set inline from the measured label, so this is the fallback for the frame before
|
|
16
|
+
that measurement lands. */
|
|
17
|
+
--pill-w: 120px;
|
|
18
|
+
--pill-h: 38px;
|
|
19
|
+
--panel-w: 212px;
|
|
20
|
+
--panel-h: 166px; /* four rows and the padding; no header. The component writes the real one from its item count. */
|
|
21
|
+
--w: var(--pill-w);
|
|
22
|
+
--h: var(--pill-h);
|
|
23
|
+
--r: var(--crt-shut-r, 999px);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.bn-crt-stage[data-open="true"] {
|
|
27
|
+
/* above whatever sits after it in a real column while the menu hangs over it */
|
|
28
|
+
z-index: 30;
|
|
29
|
+
--w: var(--panel-w);
|
|
30
|
+
--h: var(--panel-h);
|
|
31
|
+
/* 28 outside against a 10px inset leaves 18 for the rows to nest in, and a 34px row rounds fully at 17, so the two
|
|
32
|
+
curves are concentric rather than merely both round. The Corner knob moves all three together. */
|
|
33
|
+
--r: var(--crt-r, 28px);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* No metaball filter on this one, and that is the point: one shape, so a blur can only damage it (the corners came
|
|
37
|
+
back bulged and uneven instead of at the radius they were given). The liquid here is entirely in the timing. */
|
|
38
|
+
.bn-crt-blobs {
|
|
39
|
+
position: absolute;
|
|
40
|
+
inset: 0;
|
|
41
|
+
pointer-events: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* One shape, dead centre in both states, centred by auto margins and NO TRANSFORM: Motion writes width and height
|
|
45
|
+
inline every frame, and a CSS transform on a Motion-managed element can lose the centring (CSS translate and scale
|
|
46
|
+
resolve before transform). inset: 0 plus margin: auto centres against the containing block at whatever size the box
|
|
47
|
+
is right now. */
|
|
48
|
+
.bn-crt-body {
|
|
49
|
+
position: absolute;
|
|
50
|
+
inset: 0;
|
|
51
|
+
margin: auto;
|
|
52
|
+
width: var(--w);
|
|
53
|
+
height: var(--h);
|
|
54
|
+
border-radius: var(--r);
|
|
55
|
+
background: var(--slab);
|
|
56
|
+
/* no shadow: Stroke is the way to give it an edge */
|
|
57
|
+
/* width, height and the corner are Motion's now; `scale` stays here because the press beat is a discrete state,
|
|
58
|
+
not a value being interpolated from JS */
|
|
59
|
+
transition: scale 420ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* The first beat: pressed down. A plain uniform shrink, in fast, because a press should be felt at once. 160 against
|
|
63
|
+
the 60ms handoff: the spread starts while this is still running, so there is never a frame where nothing moves. */
|
|
64
|
+
.bn-crt-stage[data-sink="true"] .bn-crt-body,
|
|
65
|
+
.bn-crt-stage[data-sink="true"] .bn-crt-pill {
|
|
66
|
+
scale: 0.94;
|
|
67
|
+
transition: scale 160ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* and closing is the same single move, run down */
|
|
71
|
+
.bn-crt-stage:not([data-open="true"]) .bn-crt-body {
|
|
72
|
+
transition: scale 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* A panel, not a slab of ink. --slab is redefined here rather than at the root because exactly two things read it.
|
|
76
|
+
On the stage, not the body: the shape and the type are two sibling layers, so tokens on the body would reach the
|
|
77
|
+
fill and none of the words. --board and --ink both flip with the theme, so one definition covers both. */
|
|
78
|
+
.bn-crt-stage {
|
|
79
|
+
--slab: var(--fill-slab, var(--board));
|
|
80
|
+
--on-slab: var(--fill-on, var(--ink));
|
|
81
|
+
--on-slab-rgb: var(--fill-on-rgb, var(--ink-rgb));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.bn-crt-ink {
|
|
85
|
+
position: absolute;
|
|
86
|
+
inset: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* the pill. It does not move aside, it is simply not there once the shape has spread. The same auto-margin centring
|
|
90
|
+
as .bn-crt-body: three boxes on one stage centred by different mechanisms can round a sub-pixel apart. */
|
|
91
|
+
.bn-crt-pill {
|
|
92
|
+
position: absolute;
|
|
93
|
+
inset: 0;
|
|
94
|
+
margin: auto;
|
|
95
|
+
width: var(--pill-w);
|
|
96
|
+
height: var(--pill-h);
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
gap: 8px;
|
|
101
|
+
border: 0;
|
|
102
|
+
background: none;
|
|
103
|
+
color: var(--on-slab);
|
|
104
|
+
font-family: var(--font-ui);
|
|
105
|
+
font-size: 13px;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
transition:
|
|
108
|
+
width 420ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
109
|
+
opacity 160ms ease,
|
|
110
|
+
scale 420ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
111
|
+
letter-spacing: 0em;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.bn-crt-stage[data-open="true"] .bn-crt-pill {
|
|
115
|
+
opacity: 0;
|
|
116
|
+
pointer-events: none;
|
|
117
|
+
transition: opacity 160ms ease;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* held at full strength through the press: fading it during the squash would hide the beat the squash is for */
|
|
121
|
+
.bn-crt-stage[data-sink="true"] .bn-crt-pill {
|
|
122
|
+
opacity: 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.bn-crt-glyph {
|
|
126
|
+
display: grid;
|
|
127
|
+
place-items: center;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* the pill takes the width of whatever it is saying, so the label can change without it jumping */
|
|
131
|
+
.bn-crt-say {
|
|
132
|
+
display: block;
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
white-space: nowrap;
|
|
135
|
+
text-align: left;
|
|
136
|
+
transition: width 420ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* inline-block so it has an offsetWidth to be measured by */
|
|
140
|
+
.bn-crt-say > span {
|
|
141
|
+
display: inline-block;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Hung from the top right corner instead of centred: for a pill at the edge of a column. The stage is just the pill
|
|
145
|
+
(in flow, so the width transition follows a label change), and the shape and the window are pinned to its top right
|
|
146
|
+
corner and grow left and down from it, past the stage's box, which nothing clips. Motion still owns the body's width,
|
|
147
|
+
height and corner; only where they hang from changes. */
|
|
148
|
+
.bn-crt-stage[data-anchor="top-end"] {
|
|
149
|
+
width: var(--pill-w);
|
|
150
|
+
height: var(--pill-h);
|
|
151
|
+
transition: width 420ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.bn-crt-stage[data-anchor="top-end"] .bn-crt-body,
|
|
155
|
+
.bn-crt-stage[data-anchor="top-end"] .bn-crt-panel {
|
|
156
|
+
inset: 0 0 auto auto;
|
|
157
|
+
margin: 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* The same surface as the column it hangs in (a white pill on a white sidebar, a dark one on a dark one), so the shape
|
|
161
|
+
gets the edge the gallery's stage gives it for free: a hairline in the ink colour, and under the open panel a soft
|
|
162
|
+
drop shadow so it reads as lifted. Only on the anchored form, which is the one that lives inside a screen. */
|
|
163
|
+
.bn-crt-stage[data-anchor="top-end"] .bn-crt-body {
|
|
164
|
+
box-shadow:
|
|
165
|
+
0 0 0 1px rgba(var(--ink-rgb), 0.1),
|
|
166
|
+
0 6px 20px rgba(var(--shadow-rgb), 0.08);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* the window, in the same place */
|
|
170
|
+
.bn-crt-panel {
|
|
171
|
+
position: absolute;
|
|
172
|
+
inset: 0;
|
|
173
|
+
margin: auto;
|
|
174
|
+
width: var(--panel-w);
|
|
175
|
+
height: var(--panel-h);
|
|
176
|
+
padding: 12px 12px;
|
|
177
|
+
color: var(--on-slab);
|
|
178
|
+
pointer-events: none;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.bn-crt-stage[data-open="true"] .bn-crt-panel {
|
|
182
|
+
pointer-events: auto;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.bn-crt-menu {
|
|
186
|
+
margin: 0;
|
|
187
|
+
padding: 0;
|
|
188
|
+
list-style: none;
|
|
189
|
+
display: grid;
|
|
190
|
+
align-content: start;
|
|
191
|
+
gap: 2px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.bn-crt-item {
|
|
195
|
+
opacity: 0;
|
|
196
|
+
transform: translateY(6px);
|
|
197
|
+
/* the same curve as the shell, and no overshoot either: a row that slides past its resting line and comes back is
|
|
198
|
+
the same wobble at a smaller size */
|
|
199
|
+
transition:
|
|
200
|
+
opacity 160ms ease,
|
|
201
|
+
transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.bn-crt-stage[data-open="true"] .bn-crt-item {
|
|
205
|
+
opacity: 1;
|
|
206
|
+
transform: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.bn-crt-item button {
|
|
210
|
+
position: relative;
|
|
211
|
+
width: 100%;
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
gap: 12px;
|
|
215
|
+
height: 34px;
|
|
216
|
+
padding: 0 16px;
|
|
217
|
+
/* fully round, so a row nests inside the card's own corners instead of squaring off against them */
|
|
218
|
+
border: 0;
|
|
219
|
+
border-radius: var(--crt-row-r, 999px);
|
|
220
|
+
background: none;
|
|
221
|
+
/* 0.78, not 0.72: dark type at 0.72 on white lands at 4.6:1, and this is the menu's only body text */
|
|
222
|
+
color: rgba(var(--on-slab-rgb), 0.78);
|
|
223
|
+
font-family: var(--font-ui);
|
|
224
|
+
font-size: 13px;
|
|
225
|
+
text-align: left;
|
|
226
|
+
cursor: pointer;
|
|
227
|
+
transition: color 220ms ease;
|
|
228
|
+
letter-spacing: 0em;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.bn-crt-item button:hover {
|
|
232
|
+
color: var(--on-slab);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* the hover wash, one per row: each fades its own in and out; nothing moves between them */
|
|
236
|
+
.bn-crt-item button::before {
|
|
237
|
+
content: "";
|
|
238
|
+
position: absolute;
|
|
239
|
+
inset: 0;
|
|
240
|
+
z-index: -1;
|
|
241
|
+
border-radius: inherit;
|
|
242
|
+
background: rgba(var(--on-slab-rgb), 0.08);
|
|
243
|
+
opacity: 0;
|
|
244
|
+
transition: opacity 220ms ease;
|
|
245
|
+
pointer-events: none;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@media (hover: hover) {
|
|
249
|
+
.bn-crt-item button:hover::before {
|
|
250
|
+
opacity: 1;
|
|
251
|
+
transition-duration: 160ms;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.bn-crt-item button:focus-visible::before {
|
|
256
|
+
opacity: 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* the wash sits behind the label, inside its own row */
|
|
260
|
+
.bn-crt-item button {
|
|
261
|
+
isolation: isolate;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@media (prefers-reduced-motion: reduce) {
|
|
265
|
+
.bn-crt-body,
|
|
266
|
+
.bn-crt-pill,
|
|
267
|
+
.bn-crt-item,
|
|
268
|
+
.bn-crt-say,
|
|
269
|
+
.bn-crt-stage[data-anchor="top-end"] {
|
|
270
|
+
transition-duration: 160ms;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@media (prefers-reduced-motion: reduce) {
|
|
275
|
+
.bn-crt-stage[data-sink="true"] .bn-crt-body,
|
|
276
|
+
.bn-crt-stage[data-sink="true"] .bn-crt-pill {
|
|
277
|
+
scale: 1;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* Liquid toggle (components/bencho/LiquidToggle.tsx), as on bencho.dev.
|
|
2
|
+
One droplet that stretches along its travel inside a pill track. The switch is black and white and has no edge of
|
|
3
|
+
its own: Stroke (data-stroke) is the only thing that draws one. */
|
|
4
|
+
|
|
5
|
+
/* A 1px spread shadow is the hairline, and it composites without touching layout. The original's grouped rule also
|
|
6
|
+
rounded every element in it to --nod-r; that was meant for the node card alone, and on this one it would square the
|
|
7
|
+
pill, so only the ring is kept. */
|
|
8
|
+
[data-stroke="on"] .bn-liq-sw {
|
|
9
|
+
box-shadow: 0 0 0 1px var(--pane-edge);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.bn-liq-well {
|
|
13
|
+
width: 240px;
|
|
14
|
+
height: 130px;
|
|
15
|
+
display: grid;
|
|
16
|
+
place-items: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* The layout box. The switch inside is always drawn at 92x46 and scaled down from the top-left corner, so this box is
|
|
20
|
+
given the scaled size (inline, from the `scale` prop) and the neighbours lay out against what is seen, not against
|
|
21
|
+
the unscaled 92x46 a transform leaves behind. */
|
|
22
|
+
.bn-liq-box {
|
|
23
|
+
position: relative;
|
|
24
|
+
display: inline-block;
|
|
25
|
+
flex: none;
|
|
26
|
+
border-radius: 999px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* The ring sits on the box, not the button: an outline on the scaled button would shrink with it to under a pixel. */
|
|
30
|
+
.bn-liq-box:has(> .bn-liq-sw:focus-visible) {
|
|
31
|
+
outline: 2px solid var(--pick);
|
|
32
|
+
outline-offset: 2px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.bn-liq-box[data-disabled] {
|
|
36
|
+
opacity: 0.5;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.bn-liq-box[data-disabled] .bn-liq-sw {
|
|
40
|
+
cursor: not-allowed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.bn-liq-sw {
|
|
44
|
+
position: absolute;
|
|
45
|
+
left: 0;
|
|
46
|
+
top: 0;
|
|
47
|
+
transform-origin: 0 0;
|
|
48
|
+
width: 92px;
|
|
49
|
+
height: 46px;
|
|
50
|
+
padding: 0;
|
|
51
|
+
border: 0;
|
|
52
|
+
/* always a pill: that is the shape the gesture is named after, and squaring it turns a thing you slide into a
|
|
53
|
+
thing you click */
|
|
54
|
+
border-radius: 999px;
|
|
55
|
+
/* OFF is the slab, not a grey: the two states are the two ends of the fill's own ramp (white track on a light fill,
|
|
56
|
+
near-black on a dark one), and the dot is a grey mixed toward it. ON inverts: ink track, slab dot. The track
|
|
57
|
+
carries the state and the dot is along for the ride. */
|
|
58
|
+
background: var(--fill-slab, var(--card));
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
/* it is a rail you can push along, not only a button */
|
|
61
|
+
touch-action: none;
|
|
62
|
+
transition: background 420ms ease;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* the ring is drawn on the box (above), so the button's own would be a second, clipped one */
|
|
66
|
+
.bn-liq-sw:focus-visible {
|
|
67
|
+
outline: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.bn-liq-sw[data-on="true"] {
|
|
71
|
+
background: var(--fill-on, var(--ink));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.bn-liq-sw-blobs {
|
|
75
|
+
position: absolute;
|
|
76
|
+
inset: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* The droplet is the only body. `left: 0` is not optional: it is an inline element, so with `left: auto` the static
|
|
80
|
+
position is wherever the line box put it, and the button it sits in centres its text, which pushed the thumb to the
|
|
81
|
+
middle of the track. The size comes from LiquidToggle.tsx, which also needs it for the travel: one number, so the
|
|
82
|
+
circle and the distance it crosses cannot disagree. Opaque on purpose, so the grey is a color-mix and not an alpha. */
|
|
83
|
+
.bn-liq-thumb {
|
|
84
|
+
position: absolute;
|
|
85
|
+
left: 0;
|
|
86
|
+
top: 50%;
|
|
87
|
+
width: var(--liq-thumb, 36px);
|
|
88
|
+
height: var(--liq-thumb, 36px);
|
|
89
|
+
margin-top: calc(var(--liq-thumb, 36px) / -2);
|
|
90
|
+
border-radius: 999px;
|
|
91
|
+
background: color-mix(in srgb, var(--fill-on, var(--ink)) 19%, var(--fill-slab, var(--card)));
|
|
92
|
+
/* transform is Motion's: a CSS transition on the same property re-interpolates every frame the spring writes, and
|
|
93
|
+
what you see is the easing, not the spring. The colour is the only thing here that eases. */
|
|
94
|
+
transition: background 420ms ease;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ON inverts the pair: ink track, slab droplet */
|
|
98
|
+
.bn-liq-sw[data-on="true"] .bn-liq-thumb {
|
|
99
|
+
background: var(--fill-slab, var(--board));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@media (prefers-reduced-motion: reduce) {
|
|
103
|
+
.bn-liq-thumb {
|
|
104
|
+
transition-duration: 160ms;
|
|
105
|
+
}
|
|
106
|
+
}
|