/* ══ 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); }

.tts-langbar { display: flex; margin: 16px 0 12px; }
.tts-seg { flex: 0 0 auto; min-width: 264px; }
/* Seven languages (Kannada and Malayalam joined on 2026-10-23) do not fit in
 * equal columns with the beta mark beside each name. So the strip fills its
 * row, each column is its word's width plus an equal share of what is left,
 * every beta mark sits under its language, and a card too narrow for all
 * seven scrolls the strip sideways instead of squeezing a name. The selection
 * clip is measured from these columns (ttsSegSelect).
 * (.tts-studio raises these over models.css's wide rules, which load later.) */
.model-view #content .tts-studio .model-field:has(> .tts-langbar) { flex: 1 1 100%; min-width: 0; }
.model-view #content .tts-studio .tts-langbar { overflow-x: auto; overscroll-behavior-x: contain;
                                                scrollbar-width: none; }
.model-view #content .tts-studio .tts-langbar::-webkit-scrollbar { display: none; }
.model-view #content .tts-studio .tts-seg { display: block; flex: 1 0 auto; min-width: max-content; }
.model-view #content .tts-studio .tts-seg .seg-layer { grid-auto-columns: auto; }
.model-view #content .tts-studio .tts-seg .seg-btn { min-width: 0; padding: 4px 10px; flex-direction: column;
                                                     gap: 2px; min-height: 40px; font-size: 12.5px; }
.model-view #content .tts-studio .tts-seg .seg-beta { display: inline-block; padding: 1px 4px; font-size: 8.5px; }
/* The three-tab strip. `.howrow.ttsrow`, not `.ttsrow`: 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; }

/* ── the stats strip ──────────────────────────────────────────────────────
 * Always rendered, always the same height. Values crossfade in place. Four
 * figures in one row at every width: a fifth would wrap on a phone, and a row
 * that wraps on some renders and not others is layout shift. */
.tts-stats { transition: opacity 200ms ease, filter 200ms ease; }
.tts-stats[data-filling] { opacity: 0.55; filter: blur(2px); }
.model-view #content #ttsstats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.model-view #content #ttsstats i { white-space: nowrap; }
/* The server/network split is the tooltip; the dotted rule says there is one. */
#ttsfirst[title] i { text-decoration: underline dotted var(--c-ink-4);
                     text-underline-offset: 3px; cursor: help; }

/* ── the bench: playground left, Settings in the rail ────────────────────
 * The shared .model-bench goes two-column at 900px of page; the TTS rail sits
 * beside the playground from 720px, which is what a 1440 screen with the
 * test panel open leaves (about 760px). Scoped to this panel by id, so the
 * Language model and Speech to text pages keep the shared rule. Narrower than
 * 720px they stack, the rail after the playground. */
@container (min-width: 720px) {
  #ttspanel-playground.model-bench { display: grid; grid-template-columns: minmax(0, 1fr) 280px;
                                     align-items: start; }
  #ttspanel-playground > .model-side { position: sticky; top: 16px; }
}
@container (min-width: 900px) {
  #ttspanel-playground.model-bench { grid-template-columns: minmax(0, 1fr) 300px; }
}
/* The playground card measures itself, so the rules below follow ITS width
 * (narrow beside the rail, wide when stacked), not the page's. */
.tts-studio .model-main { container: ttsmain / inline-size; }

/* The Voice field: the select, and the quiet way to a voice of one's own. */
.tts-voicerow { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 12px; }
.tts-studio .tts-cventry { margin: 0; font-size: 12.5px; white-space: nowrap; }

/* ── settings: the rail card ──────────────────────────────────────────────
 * The card head, three sliders stacked, then the three selects full width. */
.model-view #content .tts-settings .tts-sethead { display: flex; align-items: center;
  justify-content: space-between; min-height: 22px; }
.model-view #content .tts-settings .tts-sethead h3 { margin: 0; }
.model-view #content .tts-reset { font-size: 12.5px; font-weight: 600; color: var(--c-ink-2); }
.tts-sliders { display: grid; grid-template-columns: minmax(0, 1fr); row-gap: 12px; margin-top: 14px; }
.tts-selects { display: grid; grid-template-columns: minmax(0, 1fr); row-gap: 12px;
               margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--ui-sep); }
.model-view #content .tts-selects .model-field { min-width: 0; }
.model-view #content .tts-selects select { width: 100%; min-width: 0; }
.model-view #content .tts-selects select:disabled { opacity: 1; color: var(--c-ink-3);
  background-color: var(--c-sunk); background-image: none; box-shadow: none; cursor: default; }

.tts-sliderhead { display: flex; align-items: center; justify-content: space-between;
                  gap: 8px; min-height: 22px; user-select: none; -webkit-user-select: none; }
.model-view #content .tts-sliderhead label { font: 600 12px/1.2 var(--sans); color: var(--c-ink-3); }
/* The value pill: tabular, so 0.95× and 1.00× are the same width. Off its
 * default it takes the brand tint, the same green as the slider's fill. No
 * transition: it changes with the thumb, and arrow keys move the thumb. */
.tts-sliderhead output { min-width: 48px; padding: 2px 8px; border-radius: 999px;
  background: var(--ui-fill-2); text-align: center;
  font: 500 12px/1.4 var(--sans); font-variant-numeric: tabular-nums; color: var(--c-ink-3); }
.tts-slider[data-changed] output { background: var(--accent-bg); color: var(--accent); }

/* The slider. A native range input (keyboard, screen readers and touch for
 * free) in the brand colour: a 4px --c-line track filled in --accent from the
 * minimum to the thumb, a white thumb with an --accent ring, and a small tick
 * at the default. The thumb travels from 8px to (100% - 8px), so a position is
 * `8px + (100% - 16px) * fraction`. --fill is set on the input itself by
 * tts.js; --tick is fixed per slider in the template. */
.tts-range { position: relative; display: block; margin-top: 4px; }
.tts-range::before { content: ""; position: absolute; top: 50%; width: 2px; height: 12px;
  left: calc(8px + (100% - 16px) * var(--tick, 0)); margin: -6px 0 0 -1px;
  border-radius: 1px; background: var(--c-ink-4); pointer-events: none; }
.tts-range input[type=range] { position: relative; display: block; width: 100%; height: 24px;
  margin: 0; padding: 0; border: 0; background: transparent; cursor: pointer;
  -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; }
.tts-range input[type=range]:focus { outline: none; }
.tts-range input[type=range]::-webkit-slider-runnable-track { height: 4px; border-radius: 999px;
  background-color: var(--c-line);
  background-image: linear-gradient(to right,
      var(--accent) calc(8px + (100% - 16px) * var(--fill, 0)),
      transparent calc(8px + (100% - 16px) * var(--fill, 0))),
    linear-gradient(to right,
      transparent calc(8px + (100% - 16px) * var(--tick, 0) - 1px),
      var(--c-ink-4) calc(8px + (100% - 16px) * var(--tick, 0) - 1px) calc(8px + (100% - 16px) * var(--tick, 0) + 1px),
      transparent calc(8px + (100% - 16px) * var(--tick, 0) + 1px)); }
.tts-range input[type=range]::-moz-range-track { height: 4px; border-radius: 999px;
  background-color: var(--c-line); }
.tts-range input[type=range]::-moz-range-progress { height: 4px; border-radius: 999px;
  background-color: var(--accent); }
/* Press feedback is pointer-only by construction: :active never fires for an
 * arrow key, so a run of keyboard steps moves the thumb with no animation. */
.tts-range input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none;
  box-sizing: border-box; width: 16px; height: 16px; margin-top: -6px; border-radius: 50%;
  border: 2px solid var(--accent); background: var(--c-surface);
  box-shadow: 0 1px 2px rgba(27, 26, 22, 0.14), 0 2px 6px rgba(27, 26, 22, 0.08);
  transition: transform 120ms var(--ease-out), box-shadow 150ms ease; }
.tts-range input[type=range]::-moz-range-thumb { box-sizing: border-box; width: 16px; height: 16px;
  border-radius: 50%; border: 2px solid var(--accent); background: var(--c-surface);
  box-shadow: 0 1px 2px rgba(27, 26, 22, 0.14), 0 2px 6px rgba(27, 26, 22, 0.08);
  transition: transform 120ms var(--ease-out), box-shadow 150ms ease; }
.tts-range input[type=range]:active::-webkit-slider-thumb { transform: scale(1.12);
  box-shadow: 0 1px 3px rgba(27, 26, 22, 0.18), 0 4px 10px rgba(27, 26, 22, 0.1); }
.tts-range input[type=range]:active::-moz-range-thumb { transform: scale(1.12);
  box-shadow: 0 1px 3px rgba(27, 26, 22, 0.18), 0 4px 10px rgba(27, 26, 22, 0.1); }
/* Keyboard focus: a white gap and a second --accent ring round the thumb,
 * which is the thing the arrow keys move. */
.tts-range input[type=range]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 2px var(--c-surface), 0 0 0 4px var(--accent); }
.tts-range input[type=range]:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 2px var(--c-surface), 0 0 0 4px var(--accent); }

/* The keyboard shortcut beside Generate. A phone has no ⌘, so it goes there. */
.tts-kbd { display: inline-flex; align-items: center; height: 22px; padding: 0 7px;
  border-radius: 6px; background: var(--ui-fill-2); box-shadow: inset 0 -1px 0 var(--ui-sep);
  font: 500 11.5px/1 var(--sans); letter-spacing: 0.02em; color: var(--c-ink-3); white-space: nowrap; }
@media (hover: none), (pointer: coarse) { .tts-kbd { display: none; } }

/* A narrow playground card: beside the rail on a laptop, or on a phone. */
@container ttsmain (max-width: 560px) {
  /* The download button left the waveform a thumb's width. It takes its own
   * line under the player instead; it is always rendered, so the row count
   * never changes when a clip arrives. */
  .tts-player { grid-template-columns: auto minmax(0, 1fr) auto; }
  .tts-studio #ttsdl { grid-column: 1 / -1; justify-self: end; }
  /* Tighter columns, so the seven still fit without scrolling where they can. */
  .model-view #content .tts-studio .tts-seg .seg-btn { padding: 4px 6px; }
}

/* 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); }
/* ttsCode()'s colours, for the Playground's request code. */
.tts-code-string { color: #b6d4a4; }
.tts-code-keyword { color: #bdd0e5; }
.tts-code-comment { color: #aaa99d; }
/* 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) {
  .ttsrow .seg-btn { font-size: 11.5px; }
  .tts-seg { min-width: 0; width: 100%; }
  .tts-studio .dev-cardhead { flex-wrap: wrap; gap: 5px; }
}
/* 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 .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 .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 {
    transition-property: opacity, filter, background-color, color !important;
    transition-duration: 150ms !important;
    animation: none !important;
  }
  .tts-studio *:active { transform: none !important; }
  .tts-range input[type=range]:active::-webkit-slider-thumb { transform: none; }
  .tts-range input[type=range]:active::-moz-range-thumb { transform: none; }
  #ttswaveplayed { transition: none; }
}
