@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.
Files changed (122) hide show
  1. package/docs/onboarding.md +69 -161
  2. package/omnivideo.config.example.json +1 -1
  3. package/package.json +2 -1
  4. package/reference/learnings.md +12 -0
  5. package/reference/omnichat.md +5 -4
  6. package/reference/publish.md +9 -9
  7. package/reference/studio.md +1 -1
  8. package/studio/api/app.ts +2 -2
  9. package/studio/api/routes/sound.ts +22 -3
  10. package/studio/api/services/onboarding.ts +242 -21
  11. package/studio/api/services/settings.ts +4 -2
  12. package/studio/api/services/sound.ts +45 -0
  13. package/studio/shared/types/api.ts +26 -1
  14. package/studio/studio.ts +1 -0
  15. package/studio/web/components/AppShell.tsx +3 -1
  16. package/studio/web/components/CloneVoiceDialog/RecordStep.tsx +3 -0
  17. package/studio/web/components/CloneVoiceDialog/RecorderBar.tsx +37 -20
  18. package/studio/web/components/CloneVoiceDialog/useCloneVoice.ts +39 -12
  19. package/studio/web/components/MissingDmsPanel/BatchSend.tsx +13 -4
  20. package/studio/web/components/Omni/OmniWidget.tsx +14 -5
  21. package/studio/web/components/Omni/PendingActions.tsx +25 -6
  22. package/studio/web/components/OmnichatRulePanel/RuleConfig.tsx +13 -3
  23. package/studio/web/components/OmnichatRulePanel/RuleEnableConfirm.tsx +14 -4
  24. package/studio/web/components/ResultMessage.tsx +12 -3
  25. package/studio/web/components/Sidebar.tsx +63 -24
  26. package/studio/web/components/StageStepper.tsx +105 -34
  27. package/studio/web/components/Toaster.tsx +25 -0
  28. package/studio/web/components/VoiceCarousel.tsx +19 -16
  29. package/studio/web/components/VoiceProfilesSettings.tsx +8 -3
  30. package/studio/web/components/bencho/ActionNode.tsx +456 -0
  31. package/studio/web/components/bencho/AssigneePicker.tsx +246 -0
  32. package/studio/web/components/bencho/BenchoScope.tsx +8 -0
  33. package/studio/web/components/bencho/Checklist.tsx +421 -0
  34. package/studio/web/components/bencho/CreateMenu.tsx +258 -0
  35. package/studio/web/components/bencho/GenerateImage.tsx +290 -0
  36. package/studio/web/components/bencho/ImageAccordion.tsx +273 -0
  37. package/studio/web/components/bencho/LiquidToggle.tsx +289 -0
  38. package/studio/web/components/bencho/MagnifyDock.tsx +200 -0
  39. package/studio/web/components/bencho/NowPlaying.tsx +403 -0
  40. package/studio/web/components/bencho/OneTimeCode.tsx +374 -0
  41. package/studio/web/components/bencho/PullToRefresh.tsx +527 -0
  42. package/studio/web/components/bencho/SlideToConfirm.tsx +385 -0
  43. package/studio/web/components/bencho/SloshSlider.tsx +394 -0
  44. package/studio/web/components/bencho/StackedBars.tsx +342 -0
  45. package/studio/web/components/bencho/ToastStack.tsx +263 -0
  46. package/studio/web/components/bencho/UploadDropzone.tsx +394 -0
  47. package/studio/web/components/bencho/VoiceNote.tsx +705 -0
  48. package/studio/web/components/icons.tsx +27 -0
  49. package/studio/web/components/ui/slider.tsx +34 -1
  50. package/studio/web/components/ui/switch.tsx +18 -5
  51. package/studio/web/hooks/useOnboarding.ts +24 -6
  52. package/studio/web/index.html +1 -1
  53. package/studio/web/lib/alerts.ts +10 -0
  54. package/studio/web/lib/omni-panel.ts +4 -1
  55. package/studio/web/lib/toast.ts +196 -0
  56. package/studio/web/lib/upload.ts +24 -0
  57. package/studio/web/pages/BenchoPage/index.tsx +74 -0
  58. package/studio/web/pages/BenchoPage/registry.ts +174 -0
  59. package/studio/web/pages/GettingStartedPage/components/ComputerCard.tsx +102 -0
  60. package/studio/web/pages/GettingStartedPage/index.tsx +84 -58
  61. package/studio/web/pages/NewVideoPage/index.tsx +6 -2
  62. package/studio/web/pages/ReferencesPage/index.tsx +4 -1
  63. package/studio/web/pages/SettingsPage/components/AgentAccounts.tsx +30 -3
  64. package/studio/web/pages/VideoPage/components/ChangedVoices.tsx +14 -17
  65. package/studio/web/pages/VideoPage/components/LineTextEditor.tsx +1 -1
  66. package/studio/web/pages/VideoPage/components/PublishForm.tsx +24 -17
  67. package/studio/web/pages/VideoPage/components/ScheduledInfo.tsx +2 -1
  68. package/studio/web/pages/VideoPage/components/SoundTimeline/AddSound.tsx +12 -14
  69. package/studio/web/pages/VideoPage/components/SoundTimeline/BedRow.tsx +27 -11
  70. package/studio/web/pages/VideoPage/components/SoundTimeline/CaptionSaveBar.tsx +22 -18
  71. package/studio/web/pages/VideoPage/components/SoundTimeline/TimelineFooter.tsx +3 -4
  72. package/studio/web/pages/VideoPage/components/SoundTimeline/UploadTrack.tsx +59 -0
  73. package/studio/web/pages/VideoPage/components/SoundTimeline/useSoundTimeline.ts +1 -0
  74. package/studio/web/pages/VideoPage/components/VoicePauseBanner.tsx +4 -6
  75. package/studio/web/pages/VideoPage/index.tsx +4 -0
  76. package/studio/web/pages/VideosPage/components/VideoRow.tsx +2 -4
  77. package/studio/web/router.tsx +8 -0
  78. package/studio/web/styles/components/bencho/action-node.css +227 -0
  79. package/studio/web/styles/components/bencho/checklist.css +182 -0
  80. package/studio/web/styles/components/bencho/dock.css +188 -0
  81. package/studio/web/styles/components/bencho/gallery.css +109 -0
  82. package/studio/web/styles/components/bencho/generate.css +151 -0
  83. package/studio/web/styles/components/bencho/image-accordion.css +37 -0
  84. package/studio/web/styles/components/bencho/liq-create.css +279 -0
  85. package/studio/web/styles/components/bencho/liq-toggle.css +106 -0
  86. package/studio/web/styles/components/bencho/one-time-code.css +148 -0
  87. package/studio/web/styles/components/bencho/picker.css +204 -0
  88. package/studio/web/styles/components/bencho/pull.css +313 -0
  89. package/studio/web/styles/components/bencho/scope.css +42 -0
  90. package/studio/web/styles/components/bencho/slide-confirm.css +139 -0
  91. package/studio/web/styles/components/bencho/slosh.css +137 -0
  92. package/studio/web/styles/components/bencho/sound.css +260 -0
  93. package/studio/web/styles/components/bencho/stacked-bars.css +334 -0
  94. package/studio/web/styles/components/bencho/toast-stack.css +192 -0
  95. package/studio/web/styles/components/bencho/tokens.css +243 -0
  96. package/studio/web/styles/components/bencho/upload-dropzone.css +352 -0
  97. package/studio/web/styles/components/bencho/voice-note.css +337 -0
  98. package/studio/web/styles/components/clone-voice.css +12 -2
  99. package/studio/web/styles/components/omni.css +6 -1
  100. package/studio/web/styles/components/stepper.css +123 -7
  101. package/studio/web/styles/components/toaster.css +10 -0
  102. package/studio/web/styles/components/ui/slider.css +6 -16
  103. package/studio/web/styles/components/ui/switch.css +2 -41
  104. package/studio/web/styles/components/voice-carousel.css +46 -4
  105. package/studio/web/styles/index.css +23 -0
  106. package/studio/web/styles/pages/getting-started.css +46 -0
  107. package/studio/web/styles/pages/settings/shell.css +4 -0
  108. package/studio/web/styles/pages/video/layout.css +1 -4
  109. package/studio/web/styles/pages/video/sound-timeline.css +8 -0
  110. package/studio/web/styles/theme/identity/shell.css +10 -26
  111. package/tools/lib/agents.mjs +24 -13
  112. package/tools/lib/claude-settings.mjs +32 -3
  113. package/tools/lib/deps.mjs +24 -0
  114. package/tools/lib/platforms/instagram.mjs +1 -1
  115. package/tools/lib/up.mjs +5 -2
  116. package/tools/omni.mjs +7 -5
  117. package/tools/omnichat.mjs +5 -7
  118. package/tools/package.mjs +5 -3
  119. package/tools/publish.mjs +40 -137
  120. package/tools/reference.mjs +1 -1
  121. package/tools/setup.mjs +16 -15
  122. package/tools/sound.mjs +67 -3
@@ -0,0 +1,139 @@
1
+ /* Slide to confirm (components/bencho/SlideToConfirm.tsx), as on bencho.dev.
2
+ A handle crossing a track; on commit the handle is the confirmation, unfurling leftward to fill what it crossed. */
3
+ .bn-sld {
4
+ box-sizing: border-box;
5
+ /* a fluid track is width:100% and must not shrink out of a flex row */
6
+ max-width: 100%;
7
+ font-family: var(--font-ui);
8
+ color: var(--fill-on, var(--ink));
9
+ }
10
+
11
+ /* in a row of buttons the slide takes what is left, wrapping to its own line when the row is narrow */
12
+ .bn-sld-grow {
13
+ flex: 1 1 240px;
14
+ min-width: 200px;
15
+ }
16
+
17
+ .bn-sld-track {
18
+ position: relative;
19
+ width: 100%;
20
+ height: 100%;
21
+ box-sizing: border-box;
22
+ /* the track is the slab, the handle is the ink: white track with a near-black handle in light, the reverse in dark.
23
+ On commit the handle fills the track, so the confirmed state is a solid ink pill in both */
24
+ background: var(--fill-slab, var(--card));
25
+ /* the drag is the component; the browser may not take the gesture for panning */
26
+ touch-action: none;
27
+ -webkit-user-select: none;
28
+ user-select: none;
29
+ }
30
+
31
+ [data-stroke="on"] .bn-sld-track {
32
+ box-shadow: inset 0 0 0 1px var(--pane-edge);
33
+ }
34
+
35
+ /* the ground already covered: the same ink as the handle, so the pair reads as one capsule with a rounded cap at each
36
+ end (it runs from the left pad to the handle's right edge). Above the label so the advancing cap wipes the words
37
+ instead of leaving them muddy on their own colour; the handle needs a z-index of its own to stay over it */
38
+ .bn-sld-wash {
39
+ position: absolute;
40
+ left: 4px;
41
+ top: 4px;
42
+ bottom: 4px;
43
+ background: var(--fill-on, var(--ink));
44
+ z-index: 1;
45
+ pointer-events: none;
46
+ }
47
+
48
+ /* centred in the pill, not in the room beside the handle: the track is the object */
49
+ .bn-sld-say {
50
+ position: absolute;
51
+ inset: 0;
52
+ display: grid;
53
+ place-items: center;
54
+ font-size: 14px;
55
+ font-weight: 500;
56
+ letter-spacing: -0.01em;
57
+ color: color-mix(in srgb, var(--fill-on, #17181a) 52%, var(--fill-slab, #ffffff));
58
+ pointer-events: none;
59
+ white-space: nowrap;
60
+ }
61
+
62
+ .bn-sld-grip {
63
+ position: absolute;
64
+ left: 4px;
65
+ top: 4px;
66
+ height: 48px;
67
+ padding: 0;
68
+ border: 0;
69
+ background: var(--fill-on, var(--ink));
70
+ color: var(--fill-slab, var(--card));
71
+ cursor: grab;
72
+ /* it takes the pointer so it can swell on hover; a press still reaches the track by bubbling */
73
+ pointer-events: auto;
74
+ /* the wall it lands against, so the squash compresses toward the end of the track */
75
+ transform-origin: 0% 50%;
76
+ /* it is placed by a motion value, so nothing here may transition `transform` */
77
+ transition: none;
78
+ z-index: 2;
79
+ }
80
+
81
+ /* the owner is not ready for this action (nothing to send yet, another request running): the slide is there but cannot
82
+ be taken */
83
+ .bn-sld[data-disabled] {
84
+ opacity: 0.5;
85
+ }
86
+
87
+ .bn-sld[data-disabled] .bn-sld-grip {
88
+ cursor: not-allowed;
89
+ }
90
+
91
+ /* the keyboard's way in: the handle is ink on a slab, so a ring of the same ink one pad out sits inside the track */
92
+ .bn-sld-grip:focus-visible {
93
+ outline: 2px solid var(--fill-on, var(--ink));
94
+ outline-offset: 2px;
95
+ }
96
+
97
+ .bn-sld-track[data-held] .bn-sld-grip {
98
+ cursor: grabbing;
99
+ }
100
+
101
+ /* two contents, both absolute: neither has to make room for the other and the handle's width is free to be whatever
102
+ the morph says. In one grid cell an overflowing item resolved `center` to `start` and shoved the arrow 21px off */
103
+ .bn-sld-arrow,
104
+ .bn-sld-done {
105
+ position: absolute;
106
+ inset: 0;
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ gap: 8px;
111
+ font-size: 14px;
112
+ font-weight: 500;
113
+ letter-spacing: -0.01em;
114
+ white-space: nowrap;
115
+ pointer-events: none;
116
+ }
117
+
118
+ /* working: the action the slide started is still running */
119
+ .bn-sld-spin {
120
+ width: 14px;
121
+ height: 14px;
122
+ box-sizing: border-box;
123
+ border-radius: 50%;
124
+ border: 2px solid color-mix(in srgb, var(--fill-slab, #fff) 35%, transparent);
125
+ border-top-color: var(--fill-slab, #fff);
126
+ animation: bn-sld-spin 0.8s linear infinite;
127
+ }
128
+
129
+ @keyframes bn-sld-spin {
130
+ to {
131
+ transform: rotate(360deg);
132
+ }
133
+ }
134
+
135
+ @media (prefers-reduced-motion: reduce) {
136
+ .bn-sld-done {
137
+ transition-duration: 160ms;
138
+ }
139
+ }
@@ -0,0 +1,137 @@
1
+ /* Slosh slider (components/bencho/SloshSlider.tsx), as on bencho.dev. Ported from bencho.dev (MIT).
2
+ No transitions anywhere in here: every value on this block is written by the loop, and a transition on top of a
3
+ spring is two opinions about the same number. */
4
+
5
+ /* the block's frame (the original's shared .ply base merged in), no card: just the track */
6
+ .bn-slo {
7
+ position: relative;
8
+ box-sizing: border-box;
9
+ width: 320px;
10
+ font-family: var(--font-ui);
11
+ color: var(--ink);
12
+ }
13
+
14
+ /* In a real screen the track takes the width its row gives it (the demo's 320px is only the demo's). The studio's own
15
+ rules for a slider, which are more specific, still win over this on width. */
16
+ .bn-slo--fluid {
17
+ width: 100%;
18
+ }
19
+
20
+ /* the hairline, hung on a ::before like every other stroke on the bench: transparent until Stroke says otherwise.
21
+ Slosh is not glass, so the hairline is the only edge it can have. */
22
+ .bn-slo-track::before {
23
+ content: "";
24
+ position: absolute;
25
+ inset: 0;
26
+ border-radius: inherit;
27
+ border: 1px solid transparent;
28
+ pointer-events: none;
29
+ }
30
+
31
+ [data-stroke="on"] .bn-slo-track::before {
32
+ border-color: var(--pane-edge);
33
+ }
34
+
35
+ /* In a real screen the card behind it is the same white as the liquid, which would leave a bare slider with no left
36
+ edge at all. The hairline is on there, in ink rather than glass. */
37
+ .bn-scope .bn-slo-track::before {
38
+ z-index: 1;
39
+ border-color: rgba(var(--ink-rgb), 0.1);
40
+ }
41
+
42
+ /* The well is grey, the liquid is white: the thing that moves is the bright, raised surface and the back is a recess.
43
+ No shadow on either; the well is darker than the card it is cut into, which is the whole of how a recess reads.
44
+ Opaque, and mixed from the fill's own pair rather than a wash, so it looks the same whatever it stands on. */
45
+ .bn-slo-track {
46
+ position: relative;
47
+ width: 100%;
48
+ /* 40px is the demo; the studio's slider sets --slo-h (or the height of its track slot) and takes the height from there */
49
+ height: var(--slo-h, 40px);
50
+ border-radius: var(--slo-r, 14px);
51
+ background: color-mix(in srgb, var(--fill-on, var(--ink)) 11%, var(--fill-slab, var(--board)));
52
+ /* the fill leans past its own width at speed, and the track is what stops the meniscus running off the end */
53
+ overflow: hidden;
54
+ cursor: ew-resize;
55
+ touch-action: none;
56
+ }
57
+
58
+ .bn-slo-track:focus-visible {
59
+ outline: none;
60
+ box-shadow:
61
+ 0 0 0 2px var(--fill-slab, var(--bg)),
62
+ 0 0 0 4px rgba(var(--fill-on-rgb, var(--ink-rgb)), 0.4);
63
+ }
64
+
65
+ /* The liquid. The leading edge is slanted by a clip rather than by a skew: a skew leans the whole body and the bottom
66
+ edge lifts off the floor of the track, which is a bar tipping over rather than a surface with momentum in it. */
67
+ .bn-slo-fill {
68
+ position: absolute;
69
+ left: 0;
70
+ top: 0;
71
+ bottom: 0;
72
+ width: 62%;
73
+ background: var(--fill-slab, var(--board));
74
+ clip-path: polygon(0 0, calc(100% + var(--lean, 0px)) 0, calc(100% - var(--lean, 0px)) 100%, 0 100%);
75
+ }
76
+
77
+ /* The same two roles, the ramp the other way up: in a dark fill the slab is already the floor, so the well is the
78
+ slab and the slider is the lift off it. The slider is a raised grey rather than a literal white, since a near-white
79
+ body that size on a near-black page is the brightest object on the screen. */
80
+ [data-fill="dark"] .bn-slo-track {
81
+ background: var(--fill-slab);
82
+ }
83
+
84
+ [data-fill="dark"] .bn-slo-fill {
85
+ background: color-mix(in srgb, var(--fill-on) 15%, var(--fill-slab));
86
+ }
87
+
88
+ /* Inside a real screen the fill follows the system theme (see .bn-scope in tokens.css), not a data-fill on a stage. */
89
+ @media (prefers-color-scheme: dark) {
90
+ .bn-scope .bn-slo-track {
91
+ background: var(--fill-slab);
92
+ }
93
+
94
+ .bn-scope .bn-slo-fill {
95
+ background: color-mix(in srgb, var(--fill-on) 15%, var(--fill-slab));
96
+ }
97
+ }
98
+
99
+ .bn-slo[data-disabled] .bn-slo-track {
100
+ cursor: not-allowed;
101
+ }
102
+
103
+ /* A reference value (0 dB, 100 %): a dashed rule through the well and the liquid alike, so it reads on either. */
104
+ .bn-slo-mark {
105
+ position: absolute;
106
+ top: 0;
107
+ height: 100%;
108
+ border-left: 2px dashed rgba(var(--fill-on-rgb, var(--ink-rgb)), 0.4);
109
+ pointer-events: none;
110
+ }
111
+
112
+ /* The words ride on top of the liquid and never get in the way of the finger. */
113
+ .bn-slo-label {
114
+ position: absolute;
115
+ left: 0.75rem;
116
+ top: 50%;
117
+ translate: 0 -50%;
118
+ pointer-events: none;
119
+ white-space: nowrap;
120
+ font-size: 0.75rem;
121
+ font-variant-numeric: tabular-nums;
122
+ color: var(--fill-on, var(--ink));
123
+ }
124
+
125
+ /* The handle: rigid and under your finger. Thin on purpose, it is a reading of where you are, and the fill is the
126
+ thing with the mass. */
127
+ .bn-slo-knob {
128
+ position: absolute;
129
+ top: 6px;
130
+ bottom: 6px;
131
+ left: 62%;
132
+ width: 3px;
133
+ margin-left: -1.5px;
134
+ border-radius: 999px;
135
+ background: var(--fill-on, var(--ink));
136
+ pointer-events: none;
137
+ }
@@ -0,0 +1,260 @@
1
+ /* Now playing (components/bencho/NowPlaying.tsx), as on bencho.dev.
2
+ A now-playing bar that opens into a player.
3
+
4
+ There is almost nothing here, and that is deliberate: every position, size and radius is written by the tween in
5
+ NowPlaying.tsx and arrives as an inline style. A transition on any of these rules would be a second opinion about
6
+ where a thing is, fighting the first one every frame. What is left is the material: the surfaces, the type and the
7
+ two hover states. Ported from bencho.dev (MIT). */
8
+
9
+ @media (prefers-reduced-motion: reduce) {
10
+ .bn-snd-op,
11
+ .bn-snd-art,
12
+ .bn-snd-like {
13
+ transition-duration: 160ms;
14
+ }
15
+ }
16
+
17
+ @media (prefers-reduced-motion: reduce) {
18
+ .bn-snd-like[data-on] svg {
19
+ animation: none;
20
+ }
21
+ }
22
+
23
+ .bn-snd {
24
+ position: relative;
25
+ display: grid;
26
+ place-items: center;
27
+ font-family: var(--font-ui);
28
+ }
29
+
30
+ /* The object: the quietest surface that still reads as one, the pane the rest of the bench uses. */
31
+ .bn-snd-box {
32
+ position: relative;
33
+ background: var(--pane);
34
+ -webkit-backdrop-filter: blur(2px) saturate(150%);
35
+ backdrop-filter: blur(2px) saturate(150%);
36
+ overflow: hidden;
37
+ }
38
+
39
+ /* No border of its own: the hairline is the Stroke control's to give, as a spread shadow, since the box has
40
+ overflow: hidden and a border would be one more thing the morph has to carry through every frame. */
41
+ [data-stroke="on"] .bn-snd-box {
42
+ box-shadow: 0 0 0 1px var(--pane-edge);
43
+ }
44
+
45
+ /* The cover. `cover`, not `contain`: the square is the fixed thing and the picture is cropped to it. The gradient
46
+ stays underneath as the ground, for the frame before the art has decoded. */
47
+ .bn-snd-art {
48
+ position: absolute;
49
+ background-color: var(--surface-2);
50
+ background-image: linear-gradient(148deg, var(--surface-3), var(--surface-2));
51
+ background-size: cover;
52
+ background-position: center;
53
+ background-repeat: no-repeat;
54
+ }
55
+
56
+ /* The words: one block that travels beside the cover. Centred by the box, not by a number: it is given the sleeve's
57
+ own height and centres inside it, so the two are level at every size the morph passes through. */
58
+ .bn-snd-say {
59
+ position: absolute;
60
+ display: flex;
61
+ flex-direction: column;
62
+ justify-content: center;
63
+ gap: 2px;
64
+ min-width: 0;
65
+ pointer-events: none;
66
+ }
67
+
68
+ .bn-snd-title {
69
+ color: var(--ink);
70
+ font-weight: 500;
71
+ letter-spacing: -0.01em;
72
+ line-height: 1.25;
73
+ white-space: nowrap;
74
+ overflow: hidden;
75
+ text-overflow: ellipsis;
76
+ }
77
+
78
+ .bn-snd-by {
79
+ color: var(--ink-4);
80
+ line-height: 1.25;
81
+ white-space: nowrap;
82
+ }
83
+
84
+ /* The track: a rail and a run, and the times at either end under it. Nothing here is interactive. */
85
+ .bn-snd-bar {
86
+ position: absolute;
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: 8px;
90
+ pointer-events: none;
91
+ }
92
+
93
+ .bn-snd-rail {
94
+ display: block;
95
+ height: 3px;
96
+ border-radius: 999px;
97
+ background: color-mix(in srgb, var(--fill-on, #17181a) 12%, transparent);
98
+ overflow: hidden;
99
+ }
100
+
101
+ .bn-snd-run {
102
+ display: block;
103
+ height: 100%;
104
+ border-radius: 999px;
105
+ background: var(--ink);
106
+ }
107
+
108
+ /* The interface face with tabular figures, so the digits stop moving as the count ticks. line-height 1 makes the row
109
+ the height of the digits and not of the font's own leading. */
110
+ .bn-snd-clock {
111
+ display: flex;
112
+ justify-content: space-between;
113
+ font-family: var(--font-ui);
114
+ font-variant-numeric: tabular-nums;
115
+ font-size: 11px;
116
+ line-height: 1;
117
+ letter-spacing: 0em;
118
+ color: var(--ink-4);
119
+ }
120
+
121
+ /* The transport, placed by its centre: the row's own width changes between the two states, so an edge would move even
122
+ when the middle of it did not. */
123
+ .bn-snd-ops {
124
+ position: absolute;
125
+ display: flex;
126
+ align-items: center;
127
+ translate: -50% -50%;
128
+ }
129
+
130
+ .bn-snd-op {
131
+ flex: none;
132
+ display: grid;
133
+ place-items: center;
134
+ padding: 0;
135
+ border: 0;
136
+ border-radius: 999px;
137
+ background: transparent;
138
+ color: var(--ink-3);
139
+ cursor: pointer;
140
+ overflow: hidden;
141
+ transition:
142
+ color 160ms ease,
143
+ background 160ms ease,
144
+ scale 160ms cubic-bezier(0.22, 1, 0.36, 1);
145
+ }
146
+
147
+ .bn-snd-op:hover {
148
+ color: var(--ink);
149
+ background: color-mix(in srgb, var(--fill-on, #17181a) 6%, transparent);
150
+ }
151
+
152
+ /* The press yields before it acts. `scale` as its own property, not inside a transform: the row is placed with
153
+ `translate` and the play button's size is written by the tween. */
154
+ .bn-snd-op:active {
155
+ scale: 0.96;
156
+ }
157
+
158
+ /* the one you press most, and the only one that is filled */
159
+ .bn-snd-op[data-lead] {
160
+ background: var(--ink);
161
+ color: var(--on-ink);
162
+ }
163
+
164
+ .bn-snd-op[data-lead]:hover {
165
+ background: var(--ink);
166
+ opacity: 0.88;
167
+ }
168
+
169
+ .bn-snd-op:focus-visible {
170
+ outline: none;
171
+ box-shadow:
172
+ 0 0 0 2px var(--card),
173
+ 0 0 0 4px rgba(var(--ink-rgb), 0.35);
174
+ }
175
+
176
+ /* The heart: built off .bn-snd-op rather than sharing it, since it is not in the transport row and it is the only
177
+ control that stays on after you let go. Ink when on, not red: the shape already carries the meaning. */
178
+ .bn-snd-like {
179
+ position: absolute;
180
+ display: grid;
181
+ place-items: center;
182
+ padding: 0;
183
+ border: 0;
184
+ border-radius: 999px;
185
+ background: transparent;
186
+ color: var(--ink-3);
187
+ cursor: pointer;
188
+ transition:
189
+ color 160ms ease,
190
+ background 160ms ease,
191
+ scale 160ms cubic-bezier(0.22, 1, 0.36, 1);
192
+ }
193
+
194
+ .bn-snd-like:hover {
195
+ color: var(--ink);
196
+ background: color-mix(in srgb, var(--fill-on, #17181a) 6%, transparent);
197
+ }
198
+
199
+ .bn-snd-like:active {
200
+ scale: 0.96;
201
+ }
202
+
203
+ .bn-snd-like[data-on] {
204
+ color: var(--ink);
205
+ }
206
+
207
+ .bn-snd-like[data-on]:hover {
208
+ color: var(--ink);
209
+ background: color-mix(in srgb, var(--fill-on, #17181a) 6%, transparent);
210
+ }
211
+
212
+ /* The beat, on the way on only: unliking does not deserve a flourish. Overshoot is the whole effect. */
213
+ .bn-snd-like[data-on] svg {
214
+ animation: bn-snd-beat 340ms cubic-bezier(0.3, 1.4, 0.5, 1);
215
+ }
216
+
217
+ .bn-snd-like:focus-visible {
218
+ outline: none;
219
+ box-shadow:
220
+ 0 0 0 2px var(--card),
221
+ 0 0 0 4px rgba(var(--ink-rgb), 0.35);
222
+ }
223
+
224
+ /* the hit area, and nothing else */
225
+ .bn-snd-tap {
226
+ position: absolute;
227
+ padding: 0;
228
+ border: 0;
229
+ background: none;
230
+ cursor: pointer;
231
+ }
232
+
233
+ /* the cover gives too: it is the thing you press to change the shape, so it answers the way the transport does.
234
+ Scoped to the art, since the hit area is invisible and scaling nothing looks like nothing. */
235
+ .bn-snd-box:has(.bn-snd-tap:active) .bn-snd-art {
236
+ scale: 0.96;
237
+ }
238
+
239
+ .bn-snd-art {
240
+ transition: scale 160ms cubic-bezier(0.22, 1, 0.36, 1);
241
+ }
242
+
243
+ .bn-snd-tap:focus-visible {
244
+ outline: none;
245
+ box-shadow:
246
+ 0 0 0 2px var(--card),
247
+ 0 0 0 4px rgba(var(--ink-rgb), 0.35);
248
+ }
249
+
250
+ @keyframes bn-snd-beat {
251
+ 0% {
252
+ scale: 1;
253
+ }
254
+ 38% {
255
+ scale: 1.34;
256
+ }
257
+ 100% {
258
+ scale: 1;
259
+ }
260
+ }