@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,148 @@
|
|
|
1
|
+
/* One-time code (components/bencho/OneTimeCode.tsx), as on bencho.dev.
|
|
2
|
+
Everything that moves is written inline; what is here is the material. */
|
|
3
|
+
|
|
4
|
+
/* The goo's <defs> hold nothing you can see, and an <svg> with no dimensions is a 300x150 box in the flow: taking it
|
|
5
|
+
out of flow costs nothing. */
|
|
6
|
+
.bn-liq-defs {
|
|
7
|
+
position: absolute;
|
|
8
|
+
width: 0;
|
|
9
|
+
height: 0;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* positioned at the root so a grab halo does not paint over the field */
|
|
14
|
+
.bn-otp {
|
|
15
|
+
position: relative;
|
|
16
|
+
font-family: var(--font-ui);
|
|
17
|
+
display: grid;
|
|
18
|
+
place-items: center;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.bn-otp-row {
|
|
22
|
+
position: relative;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.bn-otp-goo {
|
|
26
|
+
position: absolute;
|
|
27
|
+
inset: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* one colour under the filter, or the threshold smears two */
|
|
31
|
+
.bn-otp-cell {
|
|
32
|
+
position: absolute;
|
|
33
|
+
left: 0;
|
|
34
|
+
top: 0;
|
|
35
|
+
height: 100%;
|
|
36
|
+
background: var(--fill-slab, var(--card));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.bn-otp-slot {
|
|
40
|
+
position: absolute;
|
|
41
|
+
left: 0;
|
|
42
|
+
top: 0;
|
|
43
|
+
height: 100%;
|
|
44
|
+
display: grid;
|
|
45
|
+
place-items: center;
|
|
46
|
+
pointer-events: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.bn-otp-digit {
|
|
50
|
+
font-size: 20px;
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
line-height: 1;
|
|
53
|
+
letter-spacing: -0.02em;
|
|
54
|
+
font-variant-numeric: tabular-nums;
|
|
55
|
+
color: var(--fill-on, var(--ink));
|
|
56
|
+
animation: bn-otp-in 300ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
57
|
+
transition: color 160ms ease;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.bn-otp[data-phase="no"] .bn-otp-digit {
|
|
61
|
+
color: #e5484d;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.bn-otp-caret {
|
|
65
|
+
position: absolute;
|
|
66
|
+
left: 0;
|
|
67
|
+
top: 50%;
|
|
68
|
+
width: 2px;
|
|
69
|
+
height: 18px;
|
|
70
|
+
margin-top: -9px;
|
|
71
|
+
border-radius: 2px;
|
|
72
|
+
background: var(--fill-on, var(--ink));
|
|
73
|
+
opacity: 0;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
transition: opacity 160ms ease;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.bn-otp-caret[data-on="true"] {
|
|
79
|
+
opacity: 1;
|
|
80
|
+
animation: bn-otp-breathe 1.1s ease-in-out infinite alternate;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.bn-otp-ok {
|
|
84
|
+
position: absolute;
|
|
85
|
+
inset: 0;
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
gap: 8px;
|
|
90
|
+
font-size: 14px;
|
|
91
|
+
font-weight: 400;
|
|
92
|
+
color: var(--fill-on, var(--ink));
|
|
93
|
+
pointer-events: none;
|
|
94
|
+
letter-spacing: -0.01em;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* the field: over the whole row, there and invisible. 16px so iOS does not zoom the page when it takes focus. */
|
|
98
|
+
.bn-otp-field {
|
|
99
|
+
position: absolute;
|
|
100
|
+
inset: 0;
|
|
101
|
+
width: 100%;
|
|
102
|
+
height: 100%;
|
|
103
|
+
opacity: 0;
|
|
104
|
+
border: 0;
|
|
105
|
+
padding: 0;
|
|
106
|
+
font-size: 16px;
|
|
107
|
+
caret-color: transparent;
|
|
108
|
+
cursor: text;
|
|
109
|
+
letter-spacing: -0.01em;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.bn-otp[data-phase="ok"] .bn-otp-field {
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@media (prefers-reduced-motion: reduce) {
|
|
117
|
+
.bn-otp-digit {
|
|
118
|
+
animation: none;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media (prefers-reduced-motion: reduce) {
|
|
123
|
+
.bn-otp-caret[data-on="true"] {
|
|
124
|
+
animation: none;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes bn-otp-in {
|
|
129
|
+
from {
|
|
130
|
+
transform: translateY(45%) scale(0.86);
|
|
131
|
+
opacity: 0;
|
|
132
|
+
filter: blur(3px);
|
|
133
|
+
}
|
|
134
|
+
to {
|
|
135
|
+
transform: none;
|
|
136
|
+
opacity: 1;
|
|
137
|
+
filter: none;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@keyframes bn-otp-breathe {
|
|
142
|
+
from {
|
|
143
|
+
opacity: 1;
|
|
144
|
+
}
|
|
145
|
+
to {
|
|
146
|
+
opacity: 0.25;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/* Assignees picker (components/bencho/AssigneePicker.tsx), as on bencho.dev.
|
|
2
|
+
A pill that fills with faces, and the list it fills from. The rail's width is an inline number rather than
|
|
3
|
+
anything measured. */
|
|
4
|
+
.bn-pik {
|
|
5
|
+
position: relative;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
font-family: var(--font-ui);
|
|
8
|
+
color: var(--fill-on, var(--ink));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.bn-pik-pill {
|
|
12
|
+
position: relative;
|
|
13
|
+
z-index: 2;
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 12px;
|
|
17
|
+
height: 44px;
|
|
18
|
+
/* tighter on the left: a face is a filled circle and a word is not, so 16 of air beside a word is 8 beside a
|
|
19
|
+
picture */
|
|
20
|
+
padding: 0 12px 0 8px;
|
|
21
|
+
border: 0;
|
|
22
|
+
background: var(--fill-slab, var(--card));
|
|
23
|
+
/* no shadow and no hairline: the ring comes back only when Stroke asks for it */
|
|
24
|
+
color: inherit;
|
|
25
|
+
font: inherit;
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
letter-spacing: -0.01em;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[data-stroke="on"] .bn-pik-pill {
|
|
33
|
+
box-shadow: 0 0 0 1px var(--pane-edge);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.bn-pik-pill:focus-visible {
|
|
37
|
+
outline: none;
|
|
38
|
+
box-shadow: 0 0 0 2px rgba(var(--fill-on-rgb, var(--ink-rgb)), 0.45);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* the rail the faces sit in: its width is the whole animation. The faces are absolute inside it, so none moves when
|
|
42
|
+
another arrives. */
|
|
43
|
+
.bn-pik-rail {
|
|
44
|
+
position: relative;
|
|
45
|
+
flex: none;
|
|
46
|
+
height: 28px;
|
|
47
|
+
transition: width 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.bn-pik-face {
|
|
51
|
+
position: absolute;
|
|
52
|
+
/* both corners pinned: the face is placed by its transform, in the row and in the quad alike */
|
|
53
|
+
top: 0;
|
|
54
|
+
left: 0;
|
|
55
|
+
width: 28px;
|
|
56
|
+
height: 28px;
|
|
57
|
+
border-radius: 999px;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
/* the ring is the CARD's colour, not white: it is the gap between two overlapping faces, so it has to be whatever
|
|
60
|
+
is behind them or the stack grows a halo in dark mode */
|
|
61
|
+
box-shadow: 0 0 0 2px var(--fill-slab, var(--card));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.bn-pik-face canvas,
|
|
65
|
+
.bn-pik-av canvas {
|
|
66
|
+
width: 100%;
|
|
67
|
+
height: 100%;
|
|
68
|
+
object-fit: cover;
|
|
69
|
+
display: block;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* with no faces the rail collapses to nothing but the pill's gap still sits in front of the word; pull it back so
|
|
73
|
+
the text is inset 12 from the edge, matching the pill's own right padding */
|
|
74
|
+
.bn-pik-say {
|
|
75
|
+
margin-left: -6px;
|
|
76
|
+
color: var(--fill-on, var(--ink));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* the chevron is the hover state: the glyph goes to a stronger ink, nothing new on screen */
|
|
80
|
+
.bn-pik-chev {
|
|
81
|
+
flex: none;
|
|
82
|
+
margin-left: -2px;
|
|
83
|
+
color: color-mix(in srgb, var(--fill-on, #17181a) 32%, var(--fill-slab, #ffffff));
|
|
84
|
+
transition:
|
|
85
|
+
rotate 220ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
86
|
+
color 160ms ease;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.bn-pik-pill:hover .bn-pik-chev {
|
|
90
|
+
color: color-mix(in srgb, var(--fill-on, #17181a) 72%, var(--fill-slab, #ffffff));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.bn-pik-pill[aria-expanded="true"] .bn-pik-chev {
|
|
94
|
+
rotate: 180deg;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* the list: absolute, so opening it moves nothing */
|
|
98
|
+
.bn-pik-card {
|
|
99
|
+
position: absolute;
|
|
100
|
+
top: 54px;
|
|
101
|
+
left: 0;
|
|
102
|
+
right: 0;
|
|
103
|
+
z-index: 1;
|
|
104
|
+
box-sizing: border-box;
|
|
105
|
+
padding: 8px;
|
|
106
|
+
background: var(--fill-slab, var(--card));
|
|
107
|
+
/* it unfolds from the pill's corner: top left, not the middle */
|
|
108
|
+
transform-origin: 0 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[data-stroke="on"] .bn-pik-card {
|
|
112
|
+
box-shadow: 0 0 0 1px var(--pane-edge);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.bn-pik-row {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 12px;
|
|
119
|
+
width: 100%;
|
|
120
|
+
height: 48px;
|
|
121
|
+
padding: 0 8px;
|
|
122
|
+
border: 0;
|
|
123
|
+
/* the card's corner less its 6px padding, set in the component so it tracks the Corner knob */
|
|
124
|
+
border-radius: var(--pik-row-r, 14px);
|
|
125
|
+
background: transparent;
|
|
126
|
+
color: inherit;
|
|
127
|
+
font: inherit;
|
|
128
|
+
text-align: left;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
transition: background 160ms ease;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.bn-pik-row:hover {
|
|
134
|
+
background: color-mix(in srgb, var(--fill-on, #17181a) 6%, transparent);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.bn-pik-row:focus-visible {
|
|
138
|
+
outline: none;
|
|
139
|
+
background: color-mix(in srgb, var(--fill-on, #17181a) 6%, transparent);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.bn-pik-av {
|
|
143
|
+
flex: none;
|
|
144
|
+
width: 32px;
|
|
145
|
+
height: 32px;
|
|
146
|
+
border-radius: 999px;
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
display: block;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.bn-pik-who {
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: 2px;
|
|
155
|
+
min-width: 0;
|
|
156
|
+
flex: 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.bn-pik-name {
|
|
160
|
+
font-size: 13px;
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
overflow: hidden;
|
|
163
|
+
text-overflow: ellipsis;
|
|
164
|
+
letter-spacing: 0em;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.bn-pik-role {
|
|
168
|
+
font-size: 11px;
|
|
169
|
+
color: color-mix(in srgb, var(--fill-on, #17181a) 52%, var(--fill-slab, #ffffff));
|
|
170
|
+
letter-spacing: 0em;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* the mark is drawn at rest as an empty ring and filled when on, so the row always shows that it can be chosen */
|
|
174
|
+
.bn-pik-mark {
|
|
175
|
+
flex: none;
|
|
176
|
+
display: grid;
|
|
177
|
+
place-items: center;
|
|
178
|
+
width: 18px;
|
|
179
|
+
height: 18px;
|
|
180
|
+
/* a rounded square, not a disc: one radius for both states, 0.3 of the side */
|
|
181
|
+
border-radius: 6px;
|
|
182
|
+
box-shadow: inset 0 0 0 1.5px rgba(var(--fill-on-rgb, var(--ink-rgb)), 0.22);
|
|
183
|
+
transition:
|
|
184
|
+
background 160ms ease,
|
|
185
|
+
box-shadow 160ms ease;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.bn-pik-row[data-on] .bn-pik-mark {
|
|
189
|
+
background: var(--fill-on, var(--ink));
|
|
190
|
+
box-shadow: inset 0 0 0 1.5px var(--fill-on, var(--ink));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.bn-pik-tick {
|
|
194
|
+
display: grid;
|
|
195
|
+
place-items: center;
|
|
196
|
+
color: var(--fill-slab, var(--card));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@media (prefers-reduced-motion: reduce) {
|
|
200
|
+
.bn-pik-rail,
|
|
201
|
+
.bn-pik-chev {
|
|
202
|
+
transition-duration: 160ms;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/* Pull to refresh (components/bencho/PullToRefresh.tsx), as on bencho.dev.
|
|
2
|
+
A portfolio card: pull it down and it stretches, let go past the line to refresh. */
|
|
3
|
+
|
|
4
|
+
/* Stroke is a question about the block's own edge: a 1px spread shadow is the same mark as a border without
|
|
5
|
+
needing a pseudo-element, and it composites without touching layout. */
|
|
6
|
+
[data-stroke="on"] .bn-bal {
|
|
7
|
+
box-shadow: 0 0 0 1px var(--pane-edge);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* Height is not declared: the content sizes the card, so there is nothing to keep in agreement. */
|
|
11
|
+
.bn-bal {
|
|
12
|
+
position: relative;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
width: 300px;
|
|
15
|
+
border-radius: var(--bal-r, 28px);
|
|
16
|
+
background: var(--fill-slab, color-mix(in srgb, var(--fill-on, #17181a) 6%, transparent));
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
font-family: var(--font-ui);
|
|
19
|
+
/* The card grows with the pull. The sheet inside slides down by --at and the card's own height follows it, so
|
|
20
|
+
the top edge stays put and the bottom travels with the finger. Padding rather than a scale: a scaled box drags
|
|
21
|
+
its corner radius out of shape. --grow carries the rest padding as well as the growth, so the rebound can
|
|
22
|
+
spend it: the spring goes past zero on the way home and the card compresses a few pixels below its resting
|
|
23
|
+
height. That is the bounce.
|
|
24
|
+
|
|
25
|
+
The negative margin cancels it in layout. A card whose real height changed during a pull would make whatever
|
|
26
|
+
measures it re-pack and rescale every frame, and the re-render mid-gesture made the pull stop responding.
|
|
27
|
+
Growing and un-growing by the same number leaves the margin box exactly where it was: the card paints taller
|
|
28
|
+
and measures the same. */
|
|
29
|
+
padding-bottom: var(--grow, 8px);
|
|
30
|
+
margin-bottom: calc(-1 * var(--grow, 8px));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* The indicator, under the sheet and revealed as the sheet is pulled down off it. --dx/--dy are worked out in the
|
|
34
|
+
component from each dot's angle and the spread. The ring turns as a whole while it works (the animation is on
|
|
35
|
+
this box, not on the dots). */
|
|
36
|
+
.bn-bal-goo {
|
|
37
|
+
position: absolute;
|
|
38
|
+
left: 50%;
|
|
39
|
+
/* the ring has to clear the sheet's top edge at the moment the pull commits */
|
|
40
|
+
top: 30px;
|
|
41
|
+
width: 60px;
|
|
42
|
+
height: 60px;
|
|
43
|
+
margin: -30px 0 0 -30px;
|
|
44
|
+
z-index: 1;
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
/* the dots share one cell and are displaced out of it */
|
|
47
|
+
display: grid;
|
|
48
|
+
place-items: center;
|
|
49
|
+
opacity: var(--p, 0);
|
|
50
|
+
scale: calc(0.82 + var(--p, 0) * 0.18);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.bn-bal[data-phase="work"] .bn-bal-goo {
|
|
54
|
+
opacity: 1;
|
|
55
|
+
scale: 1;
|
|
56
|
+
animation: bn-bal-spin var(--rpm, 900ms) linear infinite;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.bn-bal-drop {
|
|
60
|
+
grid-area: 1 / 1;
|
|
61
|
+
width: 4px;
|
|
62
|
+
height: 4px;
|
|
63
|
+
border-radius: 999px;
|
|
64
|
+
/* grey, not ink: a loading mark is the quietest thing that can be on a screen and still do its job. A wash of the
|
|
65
|
+
fill's own ink, so it stays a step off whatever the component is made of in either direction. */
|
|
66
|
+
background: color-mix(in srgb, var(--fill-on, #17181a) 32%, transparent);
|
|
67
|
+
transform: translate(calc(var(--dx, 0) * 1px), calc(var(--dy, 0) * 1px));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.bn-bal-scroll {
|
|
71
|
+
position: relative;
|
|
72
|
+
z-index: 2;
|
|
73
|
+
transform: translateY(var(--at));
|
|
74
|
+
/* pan-x reserves the vertical drag for the element and leaves horizontal to the browser, the right way round for
|
|
75
|
+
something you pull down. .bn-bal-plot is the opposite case (its scrub is horizontal) and keeps pan-y. */
|
|
76
|
+
touch-action: pan-x;
|
|
77
|
+
/* a pull must not select the figures under the finger (the site's stage supplied this) */
|
|
78
|
+
-webkit-user-select: none;
|
|
79
|
+
user-select: none;
|
|
80
|
+
/* opaque, or the dots underneath show through the sheet they are supposed to be hidden behind */
|
|
81
|
+
background: var(--fill-slab, var(--card));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.bn-bal-scroll:focus-visible {
|
|
85
|
+
outline: none;
|
|
86
|
+
box-shadow: inset 0 0 0 2px rgba(var(--fill-on-rgb, var(--ink-rgb)), 0.45);
|
|
87
|
+
border-radius: 28px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Everything is on the fill's own ink, so a dark Fill inverts the whole sheet. The top gets more padding than the
|
|
91
|
+
sides; the foot gets less, because the card under it already carries 8px of its own for the squash. */
|
|
92
|
+
.bn-bal-sheet {
|
|
93
|
+
padding: 24px 12px 4px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.bn-bal-sum {
|
|
97
|
+
margin: 0;
|
|
98
|
+
font-size: 30px;
|
|
99
|
+
line-height: 1;
|
|
100
|
+
font-weight: 500;
|
|
101
|
+
letter-spacing: -0.03em;
|
|
102
|
+
color: var(--fill-on, var(--ink));
|
|
103
|
+
font-variant-numeric: tabular-nums;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* the mark is part of the number: same size, weight and colour */
|
|
107
|
+
.bn-bal-cur {
|
|
108
|
+
font-size: inherit;
|
|
109
|
+
font-weight: inherit;
|
|
110
|
+
margin-right: 2px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.bn-bal-dec {
|
|
114
|
+
font-size: 20px;
|
|
115
|
+
opacity: 0.34;
|
|
116
|
+
font-weight: 500;
|
|
117
|
+
letter-spacing: -0.02em;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.bn-bal-move {
|
|
121
|
+
/* tabular above all: this line is rewritten on every step of a scrub */
|
|
122
|
+
font-variant-numeric: tabular-nums;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: baseline;
|
|
125
|
+
gap: 8px;
|
|
126
|
+
margin: 9px 0 0;
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
font-weight: 400;
|
|
129
|
+
color: color-mix(in srgb, var(--fill-on, #17181a) 52%, var(--fill-slab, #ffffff));
|
|
130
|
+
letter-spacing: 0em;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.bn-bal-move[data-up="true"] {
|
|
134
|
+
color: var(--ok);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.bn-bal-move[data-up="false"] {
|
|
138
|
+
color: var(--ember);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* the word, or the time: always the quiet half of the line */
|
|
142
|
+
.bn-bal-when {
|
|
143
|
+
color: color-mix(in srgb, var(--fill-on, #17181a) 32%, var(--fill-slab, #ffffff));
|
|
144
|
+
font-variant-numeric: tabular-nums;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* The line. Green because the reading it draws is: `color` is set on the wrapper so the stroke, the guide and the
|
|
148
|
+
dot all agree without any of them naming a value. */
|
|
149
|
+
.bn-bal-plot {
|
|
150
|
+
position: relative;
|
|
151
|
+
display: block;
|
|
152
|
+
margin: 22px 0 6px;
|
|
153
|
+
/* The grab area is the plot, and it is worth more than the line is tall. It is a variable because the dot and the
|
|
154
|
+
guide place themselves against the chart, and the plot is 12px taller than the chart: written once, they
|
|
155
|
+
cannot drift. */
|
|
156
|
+
--pad-y: 6px;
|
|
157
|
+
padding: var(--pad-y) 0;
|
|
158
|
+
/* brighter than the number on purpose: --ok is a text green, and drawn as a 1.7px stroke it is muddy, since a
|
|
159
|
+
thin line of a dark colour always looks darker than a block of it */
|
|
160
|
+
--line: #1f9d55;
|
|
161
|
+
color: var(--line);
|
|
162
|
+
cursor: crosshair;
|
|
163
|
+
touch-action: pan-y;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
[data-theme="dark"] .bn-bal-plot {
|
|
167
|
+
--line: #1f9d55;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* and the same argument the other way for a fall */
|
|
171
|
+
.bn-bal-plot[data-up="false"] {
|
|
172
|
+
--line: #e5484d;
|
|
173
|
+
color: var(--line);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
[data-theme="dark"] .bn-bal-plot[data-up="false"] {
|
|
177
|
+
--line: #ff7a4d;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.bn-bal-line {
|
|
181
|
+
display: block;
|
|
182
|
+
width: 100%;
|
|
183
|
+
height: 92px;
|
|
184
|
+
overflow: visible;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.bn-bal-curve {
|
|
188
|
+
fill: none;
|
|
189
|
+
stroke: currentColor;
|
|
190
|
+
stroke-width: 2.1;
|
|
191
|
+
stroke-linecap: round;
|
|
192
|
+
stroke-linejoin: round;
|
|
193
|
+
/* without this the stroke stretches with the viewBox: a rope along one axis and a hairline along the other */
|
|
194
|
+
vector-effect: non-scaling-stroke;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* The guide only exists while you are on the plot. */
|
|
198
|
+
.bn-bal-guide {
|
|
199
|
+
position: absolute;
|
|
200
|
+
top: var(--pad-y, 6px);
|
|
201
|
+
bottom: var(--pad-y, 6px);
|
|
202
|
+
width: 1px;
|
|
203
|
+
margin-left: -0.5px;
|
|
204
|
+
background: currentColor;
|
|
205
|
+
opacity: 0;
|
|
206
|
+
pointer-events: none;
|
|
207
|
+
transition: opacity 160ms ease;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.bn-bal-plot[data-scrub] .bn-bal-guide {
|
|
211
|
+
opacity: 0.28;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* The mark on the reading, outside the stretched svg, placed from the same points the path was built from.
|
|
215
|
+
It does not transition its position: a dot that eases toward the pointer is wrong for 140ms at every step, and the
|
|
216
|
+
whole claim of a scrub is that the number and the place agree. Only the size changes on a timer.
|
|
217
|
+
`--tip` is where the reading is down the chart, 0 at the top and 1 at the bottom, resolved against the chart's own
|
|
218
|
+
height: a plain `top: %` resolves against the plot, which would land the dot 6px high at the top of the range, 6px
|
|
219
|
+
low at the bottom and exactly right in the middle. */
|
|
220
|
+
.bn-bal-tip {
|
|
221
|
+
position: absolute;
|
|
222
|
+
top: calc(var(--pad-y, 6px) + (100% - 2 * var(--pad-y, 6px)) * var(--tip, 0));
|
|
223
|
+
width: 9px;
|
|
224
|
+
height: 9px;
|
|
225
|
+
margin: -4.5px 0 0 -4.5px;
|
|
226
|
+
border-radius: 999px;
|
|
227
|
+
background: var(--fill-slab, var(--bg));
|
|
228
|
+
box-shadow: 0 0 0 2px currentColor;
|
|
229
|
+
pointer-events: none;
|
|
230
|
+
transition: scale 160ms cubic-bezier(0.24, 1.34, 0.38, 1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.bn-bal-plot[data-scrub] .bn-bal-tip {
|
|
234
|
+
scale: 1.18;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* The window: three small pills. Light on purpose: the chart above is the subject, and what sits under it should be
|
|
238
|
+
a control rather than a second reading competing with the first. */
|
|
239
|
+
.bn-bal-win {
|
|
240
|
+
position: relative;
|
|
241
|
+
display: flex;
|
|
242
|
+
gap: 4px;
|
|
243
|
+
margin: 16px 0 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* One tab wide, and one tab plus a gap per step across. The percentage in translateX is of the pill's own width,
|
|
247
|
+
which is the tab width, so the sum is exact at any card scale and nothing has to be measured. */
|
|
248
|
+
.bn-bal-win-pill {
|
|
249
|
+
position: absolute;
|
|
250
|
+
left: 0;
|
|
251
|
+
top: 0;
|
|
252
|
+
bottom: 0;
|
|
253
|
+
width: calc((100% - (var(--win-n, 3) - 1) * 4px) / var(--win-n, 3));
|
|
254
|
+
border-radius: var(--bal-tab-r, 999px);
|
|
255
|
+
background: color-mix(in srgb, var(--fill-on, #17181a) 6%, transparent);
|
|
256
|
+
transform: translateX(calc(var(--win, 0) * (100% + 4px)));
|
|
257
|
+
/* a bounce you feel rather than watch: the overshoot is what separates a thing being moved from a thing being
|
|
258
|
+
switched, but kept in the low single figures */
|
|
259
|
+
transition: transform 420ms cubic-bezier(0.24, 1.34, 0.38, 1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.bn-bal-win-btn {
|
|
263
|
+
position: relative;
|
|
264
|
+
z-index: 1;
|
|
265
|
+
flex: 1;
|
|
266
|
+
height: 32px;
|
|
267
|
+
border: 0;
|
|
268
|
+
border-radius: var(--bal-tab-r, 999px);
|
|
269
|
+
background: none;
|
|
270
|
+
color: color-mix(in srgb, var(--fill-on, #17181a) 52%, var(--fill-slab, #ffffff));
|
|
271
|
+
font-family: var(--font-ui);
|
|
272
|
+
font-size: 12px;
|
|
273
|
+
font-weight: 500;
|
|
274
|
+
letter-spacing: 0em;
|
|
275
|
+
cursor: pointer;
|
|
276
|
+
/* the label lands before the pill does, so the word you pressed is the one that reads first */
|
|
277
|
+
transition:
|
|
278
|
+
color 220ms ease,
|
|
279
|
+
transform 220ms cubic-bezier(0.24, 1.34, 0.38, 1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.bn-bal-win-btn:hover {
|
|
283
|
+
color: var(--fill-on, var(--ink));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.bn-bal-win-btn:active {
|
|
287
|
+
transform: scale(0.96);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* the pad is the pill's now; this only carries the ink */
|
|
291
|
+
.bn-bal-win-btn[data-on] {
|
|
292
|
+
color: var(--fill-on, var(--ink));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@media (prefers-reduced-motion: reduce) {
|
|
296
|
+
.bn-bal[data-phase="work"] .bn-bal-goo {
|
|
297
|
+
animation-duration: 3s;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@media (prefers-reduced-motion: reduce) {
|
|
302
|
+
.bn-bal-tip,
|
|
303
|
+
.bn-bal-guide,
|
|
304
|
+
.bn-bal-win-pill {
|
|
305
|
+
transition-duration: 160ms;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@keyframes bn-bal-spin {
|
|
310
|
+
to {
|
|
311
|
+
rotate: 360deg;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* What bencho.dev's Tailwind preflight gave the ported components, over the studio's own element rules. Applies inside
|
|
2
|
+
the gallery page (.bencho) and inside any real screen's wrapper (.bn-scope), never outside. */
|
|
3
|
+
|
|
4
|
+
.bencho *,
|
|
5
|
+
.bencho *::before,
|
|
6
|
+
.bencho *::after,
|
|
7
|
+
.bn-scope *,
|
|
8
|
+
.bn-scope *::before,
|
|
9
|
+
.bn-scope *::after {
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
}
|
|
12
|
+
/* the wrapper adds no box: layout around it is untouched, the custom properties still inherit through it */
|
|
13
|
+
.bn-scope {
|
|
14
|
+
display: contents;
|
|
15
|
+
font-family: var(--font-ui);
|
|
16
|
+
color: var(--ink);
|
|
17
|
+
-webkit-font-smoothing: antialiased;
|
|
18
|
+
}
|
|
19
|
+
:where(.bencho, .bn-scope) :where(button, input, textarea, select) {
|
|
20
|
+
font: inherit;
|
|
21
|
+
color: inherit;
|
|
22
|
+
margin: 0;
|
|
23
|
+
padding: 0;
|
|
24
|
+
background: none;
|
|
25
|
+
border: 0;
|
|
26
|
+
border-radius: 0;
|
|
27
|
+
outline-offset: 2px;
|
|
28
|
+
letter-spacing: inherit;
|
|
29
|
+
}
|
|
30
|
+
:where(.bencho, .bn-scope) :where(button) {
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
:where(.bencho, .bn-scope) :where(h1, h2, h3, h4, p) {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-size: inherit;
|
|
36
|
+
font-weight: inherit;
|
|
37
|
+
letter-spacing: inherit;
|
|
38
|
+
text-transform: none;
|
|
39
|
+
}
|
|
40
|
+
:where(.bencho, .bn-scope) :where(svg) {
|
|
41
|
+
display: block;
|
|
42
|
+
}
|