@kolkrabbi/kol-hardware 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-hardware",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Panel controls for instruments",
6
6
  "license": "MIT",
@@ -20,9 +20,9 @@
20
20
  "react-dom": "^18.3.0 || ^19.0.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@kolkrabbi/kol-icons": "^0.41.0",
24
- "@kolkrabbi/kol-component": "^0.287.0",
25
- "@kolkrabbi/kol-theme": "^0.183.0"
23
+ "@kolkrabbi/kol-component": "^0.301.0",
24
+ "@kolkrabbi/kol-icons": "^0.45.0",
25
+ "@kolkrabbi/kol-theme": "^0.189.0"
26
26
  },
27
27
  "files": [
28
28
  "src",
@@ -4,22 +4,30 @@
4
4
  * to its patch panel. The body turns; everything around it holds still, so the back is the same
5
5
  * shaped object as the front.
6
6
  *
7
+ * THE FACES SWAP BY BACKFACE, NOT BY A CLOCK (FlipCardFaceSwap, kol-monitor 2026-10-24 — user: *"the card flipps,
8
+ * but the image snaps in once its landed. not believable."*). It swapped `visibility` at a fixed duration / 2, but
9
+ * `ease` crosses edge-on at ~a third of the turn, so from there to the swap neither face showed — a blank card for
10
+ * ~110ms of a 450 — and the back popped in near the end. Both faces hide their backface and the back is pre-rotated
11
+ * 180° in the preserve-3d turner, so the back shows exactly as the card passes edge-on, whatever the easing. The
12
+ * face that is away is `inert` (and aria-hidden), which is what the visibility had been for: out of tab order.
13
+ *
7
14
  * @param {boolean} flipped
8
15
  * @param {ReactNode} front · back
9
16
  * @param {number|string} width the card's width (the back is laid over the front at it)
10
17
  * @param {number} duration ms (default 450, mirror's)
11
18
  */
12
19
  export default function FlipCard({ flipped = false, front, back, width, duration = 450, className = '' }) {
13
- const half = `visibility 0s ${duration / 2}ms`
20
+ const hidden = { backfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden' }
14
21
  return (
15
22
  <div className={className} style={{ perspective: 1400, position: 'relative', width }}>
16
23
  <div style={{ transition: `transform ${duration}ms ease`, transformStyle: 'preserve-3d', transform: flipped ? 'rotateY(180deg)' : 'none' }}>
17
- <div style={{ backfaceVisibility: 'hidden', transition: half, visibility: flipped ? 'hidden' : 'visible' }}>
24
+ <div aria-hidden={flipped || undefined} inert={flipped || undefined} style={hidden}>
18
25
  {front}
19
26
  </div>
20
27
  <div
21
- aria-hidden={!flipped}
22
- style={{ position: 'absolute', inset: 0, transform: 'rotateY(180deg)', backfaceVisibility: 'hidden', overflowY: 'auto', scrollbarWidth: 'none', transition: half, visibility: flipped ? 'visible' : 'hidden' }}
28
+ aria-hidden={!flipped || undefined}
29
+ inert={!flipped || undefined}
30
+ style={{ position: 'absolute', inset: 0, transform: 'rotateY(180deg)', overflowY: 'auto', scrollbarWidth: 'none', ...hidden }}
23
31
  >
24
32
  {back}
25
33
  </div>
@@ -73,7 +73,7 @@ function Tabs({ sections, tabs, saved, onPick, onAppend }) {
73
73
  <TabsRow tabs={visible} value={current?.id} onChange={setTab} />
74
74
  <div className="flex min-h-0 flex-col gap-4 overflow-y-auto">
75
75
  {current?.id === 'saved' && !saved?.length
76
- ? <div className="kol-helper-10 text-fg-48">Nothing saved yet.</div>
76
+ ? <div className="kol-mono-10 text-fg-48">Nothing saved yet.</div>
77
77
  : list.map((s) => <Section key={s.title} section={s} onPick={onPick} onAppend={onAppend} showTitle={list.length > 1} />)}
78
78
  </div>
79
79
  </div>
@@ -93,7 +93,7 @@ function Panel({ sections, tabs, saved, onPick, onAppend }) {
93
93
  </div>
94
94
  <div className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4" style={{ scrollbarWidth: 'none' }}>
95
95
  {current?.id === 'saved' && !saved?.length
96
- ? <div className="kol-helper-10 text-fg-48">Nothing saved yet.</div>
96
+ ? <div className="kol-mono-10 text-fg-48">Nothing saved yet.</div>
97
97
  : list.map((s) => <Section key={s.title} section={s} onPick={onPick} onAppend={onAppend} showTitle={list.length > 1} />)}
98
98
  </div>
99
99
  </div>
@@ -189,7 +189,8 @@ function EnvelopeHandles({ adsr, sec, onDragStart, onDrag, onDragEnd }) {
189
189
  *
190
190
  * TIME is the scope's clock: `bpm` sets its speed (60 BPM = one unit a second, so `t` reads as
191
191
  * seconds at the default and as beats at any other). Play / pause stops it where it is. `now`
192
- * (`() => t`) hands the clock to the host — a desk transport — and the scope reads it instead.
192
+ * (`() => t`) hands the clock to the host — a desk transport — and the scope reads it instead;
193
+ * Play/Pause, BPM and Trigger are then not drawn, since the host's transport does their jobs.
193
194
  *
194
195
  * ADSR: four handles on the scope — A (attack time), D (decay time), S (at the end of the hold:
195
196
  * sideways = how long the gate holds, up/down = the sustain level), R (release time). The scope
@@ -225,14 +226,18 @@ export default function EnvelopeGenerator({ generator, reference = 'panel', clas
225
226
  const passSec = mode === 'equation' ? sec : envelopeLength(adsr)
226
227
  const panel = reference === true || reference === 'panel'
227
228
 
229
+ /* a host clock (`now`) owns play, speed and restart — the controls for them would do nothing, so
230
+ * they are not drawn (EnvelopeGeneratorHostClockControls, kol-mirror 2026-10-22). Cycle still
231
+ * acts: it decides whether the host's time wraps over the pass or holds at its end. */
232
+ const ownClock = !g.now
228
233
  const transport = (
229
234
  <div className="flex h-6 shrink-0 items-center gap-3">
230
- <Button tone="ghost" size="sm" iconOnly={g.playing ? 'pause' : 'play'} aria-label={g.playing ? 'Pause' : 'Play'} onClick={() => g.setPlaying(!g.playing)} />
231
- <Field label="BPM" value={g.bpm} chars={3} onCommit={(v) => g.setBpm(Math.max(1, Math.min(999, num(v, g.bpm))))} />
235
+ {ownClock && <Button tone="ghost" size="sm" iconOnly={g.playing ? 'pause' : 'play'} aria-label={g.playing ? 'Pause' : 'Play'} onClick={() => g.setPlaying(!g.playing)} />}
236
+ {ownClock && <Field label="BPM" value={g.bpm} chars={3} onCommit={(v) => g.setBpm(Math.max(1, Math.min(999, num(v, g.bpm))))} />}
232
237
  {mode === 'adsr' && (
233
238
  <>
234
239
  <Toggle value={g.cycle} onChange={g.setCycle} label="cycle" />
235
- <Button tone="ghost" size="sm" onClick={g.fire}>Trigger</Button>
240
+ {ownClock && <Button tone="ghost" size="sm" onClick={g.fire}>Trigger</Button>}
236
241
  </>
237
242
  )}
238
243
  <span className="ml-auto text-fg-48">{mode === 'adsr' ? `${passSec.toFixed(2)} s` : ''}</span>