/* ══ MOTION RULES FOR THIS FILE ═══════════════════════════════════════════
 * Every transition below obeys these. They are written down because the next
 * person to add a control here will otherwise reach for `transition: all`,
 * and one of these rules exists precisely to stop that.
 *
 *  1. NEVER `transition: all`. Name the properties. `all` animates things you
 *     did not think about — including, one day, a layout property.
 *  2. Animate only `transform`, `opacity`, `clip-path`, `filter`,
 *     `background-color`, `color`, `box-shadow`. NEVER height, width, margin
 *     or padding: those are layout, they cost a reflow per frame, and they
 *     are the same thing as the layout shift this card was rebuilt to remove.
 *  3. Two curves and no others: --ease-out (cubic-bezier(.23,1,.32,1)) for
 *     entrances and press feedback, --ease-in-out (cubic-bezier(.77,0,.175,1))
 *     for something moving across the screen. Never ease-in — it withholds
 *     movement at the exact moment the eye is on it.
 *  4. Durations: 120ms press feedback, 150ms selection, 180ms segment slide,
 *     200ms crossfades. Nothing over 250ms.
 *  5. KEYBOARD ACTIONS DO NOT ANIMATE AT ALL. Arrowing through voices or tabs,
 *     ⌘⏎ to generate, space to play — these are pressed in runs, and an
 *     animation on a repeated action is a delay you have to sit through. The
 *     JS sets `data-instant` for the frame and the rules below honour it.
 *  6. Hover styles live inside `@media (hover: hover) and (pointer: fine)`.
 *     A touch tap triggers :hover and leaves it stuck on.
 *  7. Every pressable gets `:active { transform: scale(0.97) }`.
 *  8. Under `prefers-reduced-motion` the opacity crossfades stay — they carry
 *     meaning — and every transform and clip-path move goes.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* Improvements within the original console layout. */
.tts-studio [hidden] { display: none !important; }

/* ── the segmented control ───────────────────────────────────────────────
 * One component, two users: the Playground/Logs/API strip and the language
 * picker. The labels are rendered twice — `.seg-on` is a clone laid exactly
 * over `.seg-layer`, styled as if every segment were selected, and clipped to
 * the one that is. Moving that clip is a single composited shape; the colour
 * change falls out of it. Three separate colour transitions can never agree
 * with each other well enough to look like one movement.
 *
 * Both layers carry the SAME font weight on purpose. A selected label that
 * gets bolder is a label that gets wider, and the clip would then cut through
 * a letter on the layer that did not grow. */
.seg { position: relative; border: 1px solid var(--c-line);
       border-radius: var(--r-sm); background: var(--c-sunk);
       overflow: hidden; }
.seg-layer { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
.seg-btn { display: flex; align-items: center; justify-content: center;
           gap: 6px; min-height: 34px; padding: 7px 10px; border: 0;
           border-radius: 0; background: none; cursor: pointer;
           font: 600 12px/1.2 var(--sans); letter-spacing: 0.01em;
           color: var(--c-ink-3); white-space: nowrap;
           transition: transform 120ms var(--ease-out),
                       color 150ms ease; }
.seg-btn:active { transform: scale(0.97); }
/* The no-overlay fallback, and the reason it is colour and not weight: see
 * the note above about the clip cutting through a letter. */
.seg-btn.on { color: var(--c-ink); }
.seg-on { position: absolute; inset: 0; pointer-events: none;
          background: var(--c-surface);
          clip-path: inset(0 100% 0 0);
          transition: clip-path 180ms var(--ease-in-out); }
.seg-on .seg-btn { color: var(--c-ink); }
/* Rule 5: the frame in which a keyboard press moved the selection. */
.seg[data-instant] .seg-on { transition: none; }
.seg-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
@media (hover: hover) and (pointer: fine) {
  .seg-btn:not(.on):hover { color: var(--c-ink-2); }
}
/* beta, on the control rather than in a footnote. --c-open is the token this
 * build already uses for "unresolved", which is exactly what beta means. */
.seg-beta { font: 600 8.5px/1 var(--mono); letter-spacing: 0.08em;
            text-transform: uppercase; padding: 2px 4px; border-radius: 2px;
            color: var(--c-open-ink); background: var(--c-open-bg);
            border: 1px solid var(--c-open-line); }
/* Beta on the Languages list, in the same shape as the live chips so the row
 * reads as one set. HOLLOW where a live language is solid — the mark carries
 * the state, not the colour, which is the rule the rest of this build follows
 * and the reason it still reads in greyscale. */
.tts-lang.beta { background: var(--c-surface); border-color: var(--c-line-strong); }
.tts-lang.beta b { color: var(--c-ink); }
.tts-lang.beta b::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 1px;
  margin-right: 6px; box-shadow: inset 0 0 0 1.5px var(--c-open-ink);
}

.tts-langbar { display: flex; margin: 16px 0 12px; }
.tts-seg { flex: 0 0 auto; min-width: 264px; }
/* The three-tab strip. `.howrow.ttsrow`, not `.ttsrow`, for the reason .devrow
 * and .qsrow both document: the base .howrow is a TWO-column switch and this
 * one has three, so the override has to win on specificity rather than on file
 * order.
 *
 * It SCROLLS rather than wraps on a narrow screen. A strip that wraps to two
 * rows stops reading as one control — and these are three short words, so on
 * any real phone they fit anyway; the overflow is the promise that a fourth
 * tab would not break the layout. */
.ttsrow { margin: 14px 0 20px; }
.ttsrow .seg-layer { grid-auto-columns: minmax(104px, 1fr); }
.ttsrow .seg-btn { min-height: 40px; font-size: 12.5px; }
.tts-panel[hidden] { display: none !important; }
.tts-inputlabel { display: flex; align-items: center; justify-content: space-between; margin-top: 18px; }
.tts-inputlabel span { font: 11px/1.5 var(--mono); color: var(--c-ink-3); }
.tts-studio .tts-text { min-height: 132px; margin-top: 8px; font-size: 16px; line-height: 1.75; }
.tts-studio .tts-bar { gap: 8px; }
.tts-studio #ttsgo { min-height: 34px; }
.tts-studio :is(button, a, textarea, summary):focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.tts-output { border-top: 1px solid var(--c-line); margin-top: 16px; padding-top: 12px; }
.tts-outputhead { display: flex; justify-content: space-between; gap: 12px; font: 11px/1.5 var(--mono); color: var(--c-ink-3); }
.tts-output[data-state="error"] #ttsstatus { color: var(--c-alarm); }
.tts-output[data-state="streaming"] #ttsstatus { color: var(--accent); }
/* ── one player ───────────────────────────────────────────────────────────
 * A fixed-height row that exists before there is anything to play. Its height
 * is the same empty or full, which is the whole point: generating used to
 * insert this and push the Voices card down the page mid-read. */
.tts-player { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
              align-items: center; gap: 10px; margin: 8px 0 2px; }
.tts-playbtn { position: relative; flex: none; width: 34px; height: 34px;
               border-radius: 50%; padding: 0; display: flex;
               align-items: center; justify-content: center;
               border: 1px solid var(--c-line-strong); background: var(--c-surface);
               color: var(--c-ink); cursor: pointer;
               transition: transform 120ms var(--ease-out),
                           border-color 150ms ease, opacity 150ms ease; }
.tts-playbtn:active { transform: scale(0.97); }
.tts-playbtn:disabled { opacity: 0.45; cursor: not-allowed; }
.tts-playbtn svg { position: absolute; width: 13px; height: 13px;
                   /* Two icons in one place, crossfaded. The blur is what
                    * stops the eye reading it as two objects swapping — see
                    * the note on `.tts-golabel`. */
                   transition: opacity 120ms var(--ease-out),
                               filter 120ms var(--ease-out); }
.tts-playbtn .ico-pause { opacity: 0; filter: blur(2px); }
.tts-playbtn[data-playing="1"] .ico-play { opacity: 0; filter: blur(2px); }
.tts-playbtn[data-playing="1"] .ico-pause { opacity: 1; filter: blur(0); }

.tts-seek { position: relative; height: 56px; background: var(--c-paper);
            border-radius: var(--r-sm); overflow: hidden; cursor: pointer;
            touch-action: none; }
.tts-seek[aria-disabled="true"] { cursor: default; }
.tts-seek:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tts-seek canvas { position: absolute; inset: 0; width: 100%; height: 56px; }
/* The played layer is the SAME bars in the brand tone, revealed by a clip.
 * Re-colouring bar by bar on every timeupdate repaints the whole strip sixty
 * times a second to change two of them, and snaps the playhead to a bar edge. */
#ttswaveplayed { clip-path: inset(0 100% 0 0); }
/* The empty state is a flat line drawn into the canvas plus this label, which
 * carries the track's own background so it masks the line behind it — one
 * object, rather than text sitting on a rule. */
#ttsempty { position: absolute; left: 50%; top: 50%;
            transform: translate(-50%, -50%); padding: 0 8px; z-index: 1;
            background: var(--c-paper); pointer-events: none;
            white-space: nowrap; font-size: 11px; color: var(--c-ink-3); }
.tts-time { font: 11px/1.4 var(--mono); color: var(--c-ink-3);
            font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The engine, not a control. The browser's own bar was a second play button,
 * a second scrubber and a second clock for the same four seconds. */
#ttsaudio { display: none; }

/* ── voices ───────────────────────────────────────────────────────────────
 * The row is the radio. Four areas: who it is, what it is called, a preview
 * that opts out of the row's press, and a check that is always there and only
 * sometimes visible. */
.tts-studio .sc-item.tts-voice {
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas: 'avatar title play check' 'avatar blurb play check';
  gap: 2px 12px; cursor: pointer;
  transition: background-color 150ms ease, transform 120ms var(--ease-out); }
.tts-studio .sc-item.tts-voice:active { transform: scale(0.97); }
.tts-voicelist[data-instant] .sc-item.tts-voice { transition: none; }
.tts-studio .sc-item.selected { background: var(--accent-bg); }
.tts-studio .sc-item.tts-voice:focus-visible { outline: 2px solid var(--accent);
                                               outline-offset: -2px; }
.tts-studio .sc-item .sc-title { font-weight: 500; }
.tts-voice .sc-play { grid-area: play; }
.tts-avatar { grid-area: avatar; width: 36px; height: 36px; border-radius: 50%;
              display: flex; align-items: center; justify-content: center;
              font-size: 14px; line-height: 1; color: var(--c-ink-2);
              border: 1px solid var(--c-line);
              /* One colour at four dilutions, not four hues: --accent mixed
               * into --c-sunk by --tint-mix, which tts.js sets per row. The
               * plain fill is the fallback where color-mix is unsupported. */
              background: var(--c-sunk);
              background: color-mix(in srgb, var(--accent) var(--tint-mix, 8%), var(--c-sunk));
              transition: box-shadow 150ms var(--ease-out); }
.sc-item.selected .tts-avatar { box-shadow: 0 0 0 2px var(--accent); }
.tts-voice-check { grid-area: check; width: 16px; height: 16px;
                   display: flex; align-items: center; justify-content: center;
                   color: var(--accent); opacity: 0; transform: scale(0.9);
                   transition: opacity 150ms var(--ease-out),
                               transform 150ms var(--ease-out); }
.tts-voice-check svg { width: 15px; height: 15px; }
.sc-item.selected .tts-voice-check { opacity: 1; transform: scale(1); }

/* ── the stats strip ──────────────────────────────────────────────────────
 * Always rendered, always the same height. Values crossfade in place. */
.tts-stats { transition: opacity 200ms ease, filter 200ms ease; }
.tts-stats[data-filling] { opacity: 0.55; filter: blur(2px); }
.tts-statsnote { margin: 6px 0 0; }
.tts-statsnote summary { font-size: 11.5px; color: var(--c-ink-3);
                         cursor: pointer; }
.tts-statsnote[open] summary { margin-bottom: 6px; }
.tts-statsnote .dev-help { margin: 0; }

/* The Generate label crossfades rather than snapping, and the button reserves
 * the width of the longest word it can say so nothing beside it moves. Blur is
 * what makes a two-word crossfade read as one word changing instead of two
 * words overlapping. */
.tts-studio #ttsgo { min-width: 150px; }
.tts-golabel { display: inline-block;
               transition: opacity 120ms var(--ease-out),
                           filter 120ms var(--ease-out); }
.tts-golabel[data-swapping] { opacity: 0; filter: blur(2px); }
.tts-setupdetails { margin: 16px 0; border: 1px solid var(--c-line); border-radius: var(--r-sm); padding: 12px; }
.tts-setupdetails summary { font-size: 12px; font-weight: 500; cursor: pointer; color: var(--c-ink-2); }
.tts-setupdetails[open] summary { margin-bottom: 12px; }
.tts-setupdetails .dev-cmd { margin-top: 0; }
.tts-keyrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; font-size: 11px; color: var(--c-ink-3); }
.tts-keyrow .dev-copy { position: static; font-size: 11px; background: var(--c-surface); color: var(--accent); border-color: var(--accent-line); }
.tts-stepheading { font-size: 12px; font-weight: 500; margin: 20px 0 10px; }
.tts-integration .dev-cmd pre { padding-top: 40px; font-size: 12px; line-height: 1.7; }
.tts-code-string { color: #b6d4a4; }
.tts-code-keyword { color: #bdd0e5; }
.tts-code-comment { color: #aaa99d; }
.tts-compatnote { border-left: 2px solid var(--c-open); padding-left: 10px; }
.tts-wavoption { font-size: 11.5px; color: var(--c-ink-2); }
.tts-wavoption summary { cursor: pointer; }
.tts-integrationfooter { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 16px; }
.tts-integrationfooter .dev-docs { margin: 0; }
/* The request log. It borrows .stm-table from the billing statement rather
 * than growing a second table style: both are ledgers of the same money, and
 * two table looks on one product is one too many. What is added here is only
 * what a request log has and a statement does not — a monospaced id, and a
 * status word that has to read as a state without relying on colour alone. */
.tts-reqtable td { font-size: 12px; }
.tts-reqtime { color: var(--c-ink-3); }
.tts-reqid { font: 11px/1.5 var(--mono); color: var(--c-ink-3); }
.tts-reqstate { font-size: 11px; font-weight: 500; text-transform: capitalize; }
/* `completed` is deliberately unmarked: it is what every row should say, and
 * a page of ticks trains the eye past the one row that is not one. */
.tts-reqstate.is-failed, .tts-reqstate.is-rejected { color: var(--c-alarm-ink); }
.tts-reqstate.is-failed::before, .tts-reqstate.is-rejected::before,
.tts-reqstate.is-truncated::before { content: "\25CF\00a0"; font-size: 8px; }
.tts-reqstate.is-truncated { color: var(--c-open-ink); }
/* Where a row came from. Muted and small: it is a footnote on the status, not
 * a second status — and only a row that has one carries it, so it reads as
 * "this one is different" rather than as a column. */
.tts-reqtag { margin-left: 6px; padding: 1px 5px; font-size: 9.5px;
              font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase;
              color: var(--c-ink-3); background: var(--c-paper);
              border: 1px solid var(--c-line); border-radius: 3px;
              white-space: nowrap; }
.tts-reqfoot { display: flex; justify-content: center; margin-top: 12px; }
.tts-reqfoot button { font-size: 11px; }
#ttsreqempty p { margin: 0 0 8px; }
#ttsreqempty .dev-docs { margin: 0; }
#ttsreqcard .dev-cardhead { align-items: center; }

@media (max-width: 600px) {
  .tts-keyrow, .tts-integrationfooter { flex-wrap: wrap; }
  .tts-studio .tts-bar .tts-hint { flex-basis: 100%; }
  .ttsrow .seg-btn { font-size: 11.5px; }
  .tts-seg { min-width: 0; width: 100%; }
  .tts-studio .dev-cardhead { flex-wrap: wrap; gap: 5px; }
  .tts-integration .dev-cmd pre { font-size: 11px; }
}
/* Rule 7: every pressable admits it was pressed. 120ms, --ease-out, transform
 * only — nothing here is allowed to move its neighbours. */
.tts-studio .chip,
.tts-studio #ttsgo,
.tts-studio #ttsdl,
.tts-studio .sc-play,
.tts-studio .dev-copy,
.tts-studio .ghost { transition: transform 120ms var(--ease-out),
                                 background-color 150ms ease,
                                 border-color 150ms ease, color 150ms ease; }
.tts-studio .chip:active,
.tts-studio #ttsgo:active,
.tts-studio #ttsdl:active,
.tts-studio .sc-play:active,
.tts-studio .dev-copy:active,
.tts-studio .ghost:active { transform: scale(0.97); }

/* Rule 8: reduced motion keeps the crossfades, which carry meaning — a value
 * that changed, an icon that swapped — and drops everything that MOVES. The
 * clip-path playhead is exempt: it is not decoration, it is where the audio
 * is, and freezing it would make the player lie. */
@media (prefers-reduced-motion: reduce) {
  .tts-studio *, .tts-studio *::before,
  .seg-btn, .seg-on, .tts-avatar, .tts-voice-check {
    transition-property: opacity, filter, background-color, color !important;
    transition-duration: 150ms !important;
    animation: none !important;
  }
  .tts-studio *:active { transform: none !important; }
  #ttswaveplayed { transition: none; }
}
