/* The island — the web port of the app's dictation pill (DictationPillView).
   Geometry, colours and motion are the app's own; every number is traced in
   docs/research/2026-09-11-site-redesign/work/ISLAND-SPEC.md. This file only
   says how those numbers are drawn; island.js decides which state is on. */

.bi {
  --s: 1;
  /* Tokens come from the page theme when it defines them (the site's :root);
     the fallbacks are Paper Studio, so the island stands on its own too. */
  --bi-stage: var(--stage, #121417);
  --bi-stage2: var(--stage2, #1A1E22);
  --bi-ink: var(--paper-ink, #F3ECE0);
  --bi-ink2: var(--paper-ink2, #B9B4AB);
  --bi-ink3: var(--paper-ink3, #7C7972);
  --bi-tint: var(--accent-bright, #37E3D2);
  --bi-hands: rgb(86.7 216.75 140.25);   /* Theme.handsFree = (0.34, 0.85, 0.55) */
  --bi-warn: #FEBC2E;
  --bi-rec: #FF5A4D;
  --bi-border: rgb(255 255 255 / .08);    /* borderOnStage */
  --bi-hairline: rgb(255 255 255 / .12);  /* hairlineOnStage */
  /* .spring(response: 0.34, dampingFraction: 0.82), sampled — see ISLAND-SPEC §8 */
  --bi-spring: linear(0, 0.0229, 0.0808, 0.1602, 0.2511, 0.3459, 0.4394, 0.5279, 0.6092,
    0.6819, 0.7458, 0.8006, 0.847, 0.8855, 0.9169, 0.9422, 0.962, 0.9773, 0.9888, 0.9972,
    1.0031, 1.0071, 1.0095, 1.0107, 1.0111, 1.0109, 1.0103, 1.0094, 1.0084, 1.0074, 1.0063,
    1.0053, 1.0044, 1.0036, 1.0029, 1.0023, 1.0017, 1.0013, 1.001, 1.0007, 1);
  --bi-spring-t: 493ms;
  /* Shadows: contact (black .45, r6, y3), ambient (black .28, r18, y10), live glow (r12)
     — IslandPill's three .shadow()s (OverlayIsland.swift), drawn as CSS box-shadows fitted to the app's pixels. */
  --bi-sh1-y: 3.5px; --bi-sh1-b: 16px; --bi-sh1-a: .58;
  --bi-sh2-y: 14px; --bi-sh2-b: 35.5px; --bi-sh2-a: .34;
  --bi-sh3-b: 29px; --bi-sh3-k: 1.31;
  position: relative;
  display: block;
  font-family: "Hanken Grotesk", Helvetica, sans-serif;
  font-kerning: normal;
  font-synthesis: none;
  line-height: normal;
  color: var(--bi-ink);
  pointer-events: none;
}

/* One slab per branch of the app's `if showsTranscript { expanded } else { compact }`:
   switching branch cross-fades two slabs, as SwiftUI does; within a branch the width
   rides the spring. */
.bi-slab {
  position: absolute;
  top: 0;
  left: 0;
  width: calc(var(--w) * 1px);
  height: calc(var(--h) * 1px);
  border-radius: calc(12px * var(--s));
  transition: width var(--bi-spring-t) var(--bi-spring),
              opacity var(--bi-spring-t) var(--bi-spring),
              box-shadow var(--bi-spring-t) var(--bi-spring);
  /* contact · ambient · live glow — IslandPill (OverlayIsland.swift); SwiftUI's shadow
     radius reads as half a CSS blur */
  box-shadow:
    0 calc(var(--bi-sh1-y) * var(--s)) calc(var(--bi-sh1-b) * var(--s)) rgb(0 0 0 / var(--bi-sh1-a)),
    0 calc(var(--bi-sh2-y) * var(--s)) calc(var(--bi-sh2-b) * var(--s)) rgb(0 0 0 / var(--bi-sh2-a)),
    0 0 calc(var(--bi-sh3-b) * var(--s))
      color-mix(in srgb, var(--bi-glow-c, transparent) calc(var(--bi-glow-p, 0) * var(--bi-sh3-k) * 1%), transparent);
}
.bi-slab.is-anchored-right { left: auto; right: 0; }
.bi-slab.is-anchored-bottom { top: auto; bottom: 0; }
.bi-slab.is-centered { left: 50%; translate: -50% 0; }

/* Body, machined edge and state ring: three layers cut by the same continuous-corner
   masks (left cap · straight middle · right cap), so the corner is Apple's curve in
   every browser and the width can still animate. The middle overlaps each cap by a
   pixel of straight edge, so a width caught between pixels mid-spring shows no seam. */
.bi-fill, .bi-edge, .bi-ring {
  position: absolute;
  inset: 0;
  border-radius: inherit;
}
.bi-fill { background: linear-gradient(var(--bi-stage2), var(--bi-stage)); }
.bi-edge {
  background: linear-gradient(var(--bi-hairline) 0%, var(--bi-border) 45%, rgb(255 255 255 / .04) 100%);
}
.bi-ring {
  background: var(--bi-ring, transparent);
  transition: background-color var(--bi-spring-t) var(--bi-spring);
}
@supports (mask-image: none) or (-webkit-mask-image: none) {
  .bi-fill, .bi-edge, .bi-ring { border-radius: 0; }
  .bi-fill {
    -webkit-mask: var(--bi-cap-l) left top / var(--bi-cap) 100% no-repeat,
                  var(--bi-cap-r) right top / var(--bi-cap) 100% no-repeat,
                  linear-gradient(#000, #000) center / calc(100% - 2 * var(--bi-cap) + 2px) 100% no-repeat;
    mask: var(--bi-cap-l) left top / var(--bi-cap) 100% no-repeat,
          var(--bi-cap-r) right top / var(--bi-cap) 100% no-repeat,
          linear-gradient(#000, #000) center / calc(100% - 2 * var(--bi-cap) + 2px) 100% no-repeat;
  }
  .bi-edge, .bi-ring {
    -webkit-mask: var(--bi-rim-l) left top / var(--bi-cap) 100% no-repeat,
                  var(--bi-rim-r) right top / var(--bi-cap) 100% no-repeat,
                  linear-gradient(#000 0 1px, transparent 1px calc(100% - 1px), #000 calc(100% - 1px))
                    center / calc(100% - 2 * var(--bi-cap) + 2px) 100% no-repeat;
    mask: var(--bi-rim-l) left top / var(--bi-cap) 100% no-repeat,
          var(--bi-rim-r) right top / var(--bi-cap) 100% no-repeat,
          linear-gradient(#000 0 1px, transparent 1px calc(100% - 1px), #000 calc(100% - 1px))
            center / calc(100% - 2 * var(--bi-cap) + 2px) 100% no-repeat;
  }
}

/* SwiftUI casts each .shadow from every layer of the slab, not from the slab as one
   picture — so while a take is live the meter, the port and the words light the body
   around them too (measured: +8 green next to the meter). Only the glow is worth
   carrying; the black shadows of the content vanish on the dark body. */
.bi-slab.is-glowing .bi-row, .bi-slab.is-glowing .bi-text {
  filter: drop-shadow(0 0 calc(var(--bi-in-b, 13px) * var(--s))
    color-mix(in srgb, var(--bi-glow-c) calc(var(--bi-glow-p) * var(--bi-in-k, 1) * 1%), transparent));
}

/* Content rides inside the slab and is clipped to it (the app's .clipShape). */
.bi-clip {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
}
.bi-row {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 100%;                             /* compact: the row IS the slab */
}
.bi-slab[data-branch="expanded"] .bi-row { height: calc(44px * var(--s)); }

/* The fixed port every phase shows its glyph through. */
.bi-ap {
  position: absolute;
  left: calc(13px * var(--s));
  top: calc(50% - 12px * var(--s));
  width: calc(24px * var(--s));
  height: calc(24px * var(--s));
}
.bi-ap > svg.bi-port { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.bi-port path { fill: var(--bi-stage); }
.bi-port path + path { fill: none; stroke: var(--bi-border); stroke-width: 1px; }
.bi-glyph {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  transition: opacity var(--bi-spring-t) var(--bi-spring);
}
.bi-glyph > svg { overflow: visible; display: block; }
.bi-key {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-weight: 400;
  font-size: calc(11px * var(--s));
  line-height: normal;
  color: var(--bi-ink);
}

/* Record dot: 7pt, and a 4pt ring that pulses out (RecDot, RecorderPill.swift). */
.bi-dot { position: relative; width: calc(7px * var(--s)); height: calc(7px * var(--s)); }
.bi-dot::before, .bi-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
}
.bi-dot::before { background: var(--bi-rec); }
.bi-dot::after {
  inset: calc(-2px * var(--s));
  border: calc(4px * var(--s)) solid rgb(255 90 77 / .55);
  animation: bi-pulse 1.4s cubic-bezier(0, 0, .58, 1) infinite;
}
@keyframes bi-pulse {
  from { transform: scale(1); border-color: rgb(255 90 77 / .55); }
  to { transform: scale(2); border-color: rgb(255 90 77 / 0); }
}

/* The action's mark pulses while the model makes the action (.symbolEffect(.pulse)). */
.bi-sparkles.is-pulsing { animation: bi-sparkle 1.1s ease-in-out infinite; }
@keyframes bi-sparkle { 50% { opacity: .35; } }
.bi-spin, .bi-globe { animation: bi-turn linear infinite; }
.bi-spin { animation-duration: .7s; }
.bi-globe { animation-duration: 2.4s; }
@keyframes bi-turn { to { transform: rotate(1turn); } }

/* Where the browser's baseline lands against the app's, fitted on the reference
   renders: Chrome snaps text to whole CSS pixels unless the line has its own layer,
   so the words get one and move by a device pixel. */
.bi { --bi-text-dy: -0.5px; }
.bi:not(.bi--home) { --bi-cap-dy: -0.5px; }
.bi-line.is-two { --bi-text-dy: 0px; }       /* a two-line readout sits a device pixel lower */
.bi.bi--home { --bi-text-dx: -0.5px; }       /* the app snaps the 19.6pt inset down to the pixel */

/* The slot after the aperture: the meter while a take runs, the caption otherwise. */
.bi-center {
  position: absolute;
  top: 0;
  height: 100%;
  left: calc(46px * var(--s));
}
.bi-caption {
  position: absolute;
  left: 0;
  top: 50%;
  will-change: transform;
  white-space: pre;
  font-size: calc(11.5px * var(--s));
  font-weight: 600;
  color: var(--bi-ink2);
  translate: 0 calc(-50% + var(--bi-cap-dy, 0px));
  transition: transform var(--bi-spring-t) var(--bi-spring), opacity var(--bi-spring-t) var(--bi-spring);
}
.bi-caption.is-reserved { text-align: center; }
.bi-caption.is-error { font-weight: 500; color: var(--bi-ink); }
.bi-caption.is-wrapping { white-space: normal; }

.bi-meter {
  position: absolute;
  left: 0;
  top: calc(50% - 10px * var(--s));
  width: calc(85.5px * var(--s));
  height: calc(20px * var(--s));
  transition: opacity var(--bi-spring-t) var(--bi-spring);
}

.bi-timer {
  position: absolute;
  top: 50%;
  right: calc(13px * var(--s));
  width: calc(38px * var(--s));
  text-align: right;
  translate: 0 -50%;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: calc(12px * var(--s));
  font-variant-numeric: tabular-nums;
  color: var(--bi-ink2);
  transition: opacity var(--bi-spring-t) var(--bi-spring);
}

/* The readout under the status row. */
.bi-rule {
  position: absolute;
  top: calc(44px * var(--s));
  left: calc(13px * var(--s));
  right: calc(13px * var(--s));
  height: 1px;
  background: var(--bi-border);
}
.bi-text {
  position: absolute;
  top: calc(44px * var(--s) + 1px + 10px * var(--s));
  left: calc(14px * var(--s));
  right: calc(14px * var(--s));
  height: calc(38px * var(--s));
  display: flex;
  align-items: center;
  overflow: hidden;
}
.bi-line {
  /* The app sets its live text at whole points (13 → 13, 18.2 → 18: measured on the
     Home-scale reference), unlike its captions, which keep 16.1. */
  font-size: var(--bi-text-size, 13px);
  translate: var(--bi-text-dx, 0px) var(--bi-text-dy, 0px);
  will-change: transform;
  font-weight: 400;
  white-space: pre-wrap;
  overflow-wrap: normal;
  color: var(--bi-ink);
}
.bi-line .t { color: var(--bi-ink3); }
/* `▌` has no glyph in Hanken; Core Text draws it in Hiragino Sans W3 — a full-width
   character (1 em of advance, which decides where lines break and where a cut line
   ends) inked as a block 0.52 em wide and 1.02 em tall, 0.12 em below the baseline. */
.bi-caret {
  display: inline-block;
  vertical-align: -0.12em;
  width: 1em;
  height: 1.02em;
  background: linear-gradient(90deg, var(--bi-tint) 0 0.52em, transparent 0.52em);
}
/* 0.5 s on, 0.5 s off, phased to the clock like the app's TimelineView (island.js sets the delay) */
.bi-caret.is-blinking { animation: bi-blink 1s step-end infinite; }
@keyframes bi-blink { 0% { opacity: 1; } 50% { opacity: 0; } }

/* The pointing tail toward an anchored field (OverlayView.tail, WaveformView.swift). */
.bi-tail {
  position: absolute;
  width: calc(8px * var(--s));
  height: calc(14px * var(--s));
  overflow: visible;
  transition: left var(--bi-spring-t) var(--bi-spring), top var(--bi-spring-t) var(--bi-spring);
}
.bi-tail path { fill: var(--bi-stage); }
.bi-tail.is-top path { fill: var(--bi-stage2); }

/* Entrance / exit of the whole island — the panel's 0.22 s fade and 20pt slide. */
.bi.is-entering { animation: bi-in .22s cubic-bezier(0, 0, .58, 1) both; }
.bi.is-leaving { animation: bi-out .22s cubic-bezier(.42, 0, 1, 1) both; }
@keyframes bi-in { from { opacity: 0; translate: 0 calc(20px * var(--s)); } }
@keyframes bi-out { to { opacity: 0; translate: 0 calc(20px * var(--s)); } }

@media (prefers-reduced-motion: reduce) {
  .bi-slab, .bi-glyph, .bi-caption, .bi-meter, .bi-timer, .bi-ring, .bi-tail { transition: none; }
  .bi-dot::after, .bi-sparkles.is-pulsing { animation: none; }
  .bi.is-entering, .bi.is-leaving { animation-duration: 1ms; }
}
.bi.is-still *, .bi.is-frozen * { transition: none !important; }
/* ImageRenderer draws the pulse's end state (ring gone); a frozen frame matches it. */
.bi.is-frozen .bi-dot::after { opacity: 0; }
.bi.is-still .bi-dot::after, .bi.is-frozen .bi-dot::after,
.bi.is-frozen .bi-spin, .bi.is-frozen .bi-globe, .bi.is-still .bi-sparkles, .bi.is-frozen .bi-sparkles { animation: none; }
.bi.is-hidden { visibility: hidden; }
