/* ==========================================================================
   The transcribe room.

   Everything shared comes from elsewhere: `styles.css` for the components --
   the shell, the bar, the panel head, the pills, the tray -- and `themes.css`
   for what this room is made of. Nothing shared is redefined here, and
   `npm run check` refuses a bare class selector that is also a component in
   styles.css, so that is enforced rather than promised.

   What is left is the two things only this room has: the line that says what
   it is doing while it listens, and the transcript itself.
   ========================================================================== */

/* --- the shell, while this room is working --------------------------------

   Wider than the console a bar with one button needs, because the thing being
   looked at here is prose. Not as wide as the photo room, which is showing a
   picture: past about 820px the line length stops helping and starts costing
   you your place. The shared stylesheet already transitions the shell's width,
   so this is part of the same morph rather than a jump after it. */
.stage[data-state="transcribe-work"] .shell {
  width: min(100%, 820px);
}

/* The condensation on the island's edge thickens while the room listens.

   The building's flag for "being worked" is a slider moving, and this room
   is worked for minutes at a time with nothing under the hand: the model
   downloading, then listening. The listening line is showing for exactly
   that long, so its presence is the flag. The halo itself is the room's
   `--react-edge-moving` in `themes.css`; this only says when. It is a
   shadow on a transition rather than an animation, so under reduced motion
   it is still a change of state and nothing pulses. */
.stage[data-state="transcribe-work"] .shell:has(.face.is-active #transcribe-listen:not([hidden])) {
  box-shadow: var(--react-edge-moving);
}

/* --- the language it was said in -------------------------------------------

   A pill that happens to be a list: the building's `<uc-select>` wearing the
   same capsule, hairline and type as the Fast / Careful / Best beside it.
   The `.pill` rule outranks the drop-down's own `:host` look, so it is the
   same height as its neighbours and nothing here restates it. Its caret is
   its own, drawn in `--film-ink-2`.

   The list that opens is the fog's, from `themes.css`: frosted, with the
   island's condensation on its edge, and it resolves out of the white rather
   than dropping from the control -- fog has no direction to arrive from.
   Ninety-nine languages and a Detect; typing the first letters of one finds
   it, open or shut, as a select does. */
/* As wide as the longest name in the list, Norwegian Nynorsk, as the native
   select was: a control that changed width with every choice would push the
   Translate pill along the row each time, and again when detection writes
   "Detected: ..." into it halfway through a recording. 17ch, plus the
   pill's two paddings, the caret and its gap. */
.face[data-face="transcribe-work"] .transcribe-language {
  min-width: calc(17ch + var(--s3) * 2 + 16px + var(--s2));
  max-width: 100%;
}
/* Chosen rather than left to detection reads as chosen, like a pressed pill.
   The caret takes the pressed ink through the one property it reads. */
.face[data-face="transcribe-work"] .transcribe-language[data-chosen] {
  background: var(--knob-face);
  color: var(--knob-ink);
  border-color: transparent;
  --film-ink-2: var(--knob-ink);
}

/* --- the languages it is saved in ------------------------------------------

   The same capsule as the spoken language's list, opening the building's
   multi-pick: one box per language, a hundred of them, typing a first letter
   finds one. It reads back what is chosen ("French, German, Japanese"), so
   it grows with the choice up to a measure and then ends in an ellipsis
   rather than pushing the pill after it off the row. */
.face[data-face="transcribe-work"] .transcribe-in {
  min-width: calc(10ch + var(--s3) * 2 + 16px + var(--s2));
  max-width: min(100%, 40ch);
}

/* How many exports the languages chosen would use. Quiet, like the model's
   download beside Listen; in the failure ink only when there are not that
   many left today -- the one case where it is a warning. */
#transcribe-cost { font-size: var(--t-xs); }
#transcribe-cost[data-over] { color: var(--film-bad); }

/* Each language while it is made: its name, what it is doing, and a bar of
   its own. A table in all but markup -- the names, the words and the bars
   each line up down the list -- so it is a grid with fixed tracks, and the
   words never move the bar as they change. */
.tongues {
  list-style: none;
  margin: var(--s3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(7ch, max-content) minmax(0, 1fr) minmax(64px, 22%);
  column-gap: var(--s3);
  row-gap: var(--s2);
  align-items: center;
}
.tongues__row { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
.tongues__name {
  font-size: var(--t-sm);
  color: var(--film-ink-2);
}
.tongues__what {
  font-size: var(--t-xs);
  color: var(--film-ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.tongues__row[data-state="done"] .tongues__what { color: var(--film-ok); }
.tongues__row[data-state="failed"] .tongues__what { color: var(--film-bad); white-space: normal; }
.tongues__track {
  height: 3px;
  border-radius: var(--radius-control);
  background: var(--film-raise);
  overflow: hidden;
}
/* The listening bar's fill and sweep, the same 160ms and the same honesty:
   a number while there is one, a sweep while there is not. */
.tongues__fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: var(--radius-control);
  background: var(--signal);
  transition: width 160ms linear;
}
.tongues__fill[data-unknown] {
  width: 35%;
  animation: listen-sweep 1.6s var(--ease-soft) infinite;
}
.tongues__row[data-state="done"] .tongues__fill { background: var(--film-ok); }
@media (prefers-reduced-motion: reduce) {
  .tongues__fill { transition: none; }
  .tongues__fill[data-unknown] { animation: none; width: 100%; opacity: 0.35; }
}
@media (max-width: 560px) {
  .tongues { grid-template-columns: minmax(0, 1fr) minmax(56px, 30%); row-gap: var(--s3); }
  .tongues__name { grid-column: 1; grid-row: 1; }
  .tongues__track { grid-column: 2; grid-row: 1; }
  .tongues__what { grid-column: 1 / -1; grid-row: 2; }
}

/* The tray's groups, told apart. Within a group the pills sit a step apart;
   between groups there is three times that, so a group that wraps onto the
   next line starts as a group of its own rather than continuing the one
   above it. Rows keep the building's gap. */
.face[data-face="transcribe-work"] #transcribe-options { column-gap: var(--s5); }
/* A group with nothing showing in it -- Preview, for a recording with no
   picture -- takes no place, not even the gap before it. */
#transcribe-options .tray__set:not(:has(> :not([hidden]))) { display: none; }
/* On a phone a group wraps inside itself, and a pill that falls to the next
   line under the label read as the start of something else. So each label
   heads its group on a line of its own, its pills under it, and the groups
   stand a little further apart than the pills in them. */
@media (max-width: 560px) {
  .face[data-face="transcribe-work"] #transcribe-options { row-gap: var(--s4); }
  #transcribe-options .tray__label { flex-basis: 100%; }
}

/* --- the listening line ---------------------------------------------------

   Downloading a model and transcribing an hour of speech are both long enough
   that silence would read as a hang. Two stages, one bar.

   The bar reports a real fraction while the model is downloading, because the
   total is known. While the model is listening it is not: Whisper reports
   nothing about how far through a recording it is, so rather than invent a
   number the bar stops measuring and simply moves. An invented percentage that
   stalls at 80% is worse than an honest sweep. */

.listen { margin-top: var(--s4); }

.listen__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
}

.listen__what {
  font-size: var(--t-xs);
  color: var(--film-ink-3);
  margin: 0 0 var(--s2);
}

/* The time, quieter than the sentence and never wrapping: it changes every
   second, and a number that reflows the line it sits on is a number that
   draws the eye away from the words every time it ticks. */
.listen__eta {
  font-size: var(--t-xs);
  color: var(--film-ink-3);
  margin: 0 0 var(--s2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.listen__track {
  height: 3px;
  border-radius: var(--radius-control);
  background: var(--film-raise);
  overflow: hidden;
}

.listen__fill {
  height: 100%;
  width: 0;
  border-radius: var(--radius-control);
  background: var(--signal);
  /* Short and linear. A bar catching up to its number on a 420ms ease is a bar
     that is still moving when the next number arrives, and restarting an eased
     transition several times a second reads as hesitation rather than as
     progress. Progress is the one thing on a screen that should not be
     interpreted. */
  transition: width 160ms linear;
}

/* Working, with nothing honest to say about how far. */
.listen__fill[data-unknown] {
  width: 35%;
  animation: listen-sweep 1.6s var(--ease-soft) infinite;
}
@keyframes listen-sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(340%); }
}

/* --- the transcript -------------------------------------------------------

   The thing the room is for, so it gets the reading measure -- and as much
   height as it has lines.

   It used to be a scroller inside the island, which was a column with a
   ceiling the height of the window: the transcript took whatever the head,
   the options, the picture and the buttons left it. On a laptop that was a
   slot of a few lines under the picture, a flick a few pixels either side
   of it moved two different things, and the island itself grew a second
   scroll on a short window. Now the island is as long as what is in it and
   the window scrolls, as it does in the converter: one scroll, and it is the
   page. The line being spoken is kept in sight by moving the page (room.js,
   markLine), and the island's growth when the words arrive is the shell's
   own (shell.js, grow). */
.script {
  margin-top: var(--s4);
}

/* --- the picture, with the words on it --------------------------------------

   Capped in `vh` and not left to its own aspect: a portrait recording from a
   phone handed its natural height would be taller than the window, and the
   words it is there to check would start below the fold. Within the cap it
   fits itself, so portrait and widescreen both read at a glance. */
.screen {
  position: relative;
  margin-top: var(--s4);
  /* Fourteen, not `--radius-control`. This room's controls are capsules, and
     a capsule around a picture puts a 999px curve through the corners of the
     frame -- which looked like a mistake because it is one: a screen is not a
     button. */
  border-radius: 14px;
  overflow: hidden;
  line-height: 0;
}

/* No colour behind it, so whatever the picture's shape does not cover is the
   panel rather than a black bar. A portrait recording from a phone in a box
   made for a landscape one is the common case, and two black columns either
   side of it is the look of a video embedded in the wrong place. */
.screen__video {
  /* Above the held frame (below), so the frame only shows where the video
     itself has nothing to draw. */
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  /* `height` stays auto and the cap does the work.

     It was `height: 100%`, to make the picture follow the box when the column
     squeezed it. A percentage height against a parent whose own height comes
     from its content is undefined, and browsers do not agree about it: Chrome
     treats it as auto and Safari resolves it against the flex line, so the
     same markup gave a sensible picture here and either nothing at all or a
     very tall box there. The room is fixed height either way now. */
  height: auto;
  /* A third of the window, so the picture and the first lines of the
     transcript share the first screen; never under 160 or over 320, the
     size it has always been on a laptop. Fixed for the life of the face:
     Safari rebuilds a video's layer whenever its box changes size, so
     nothing here depends on what else is in the island. */
  max-height: clamp(160px, 36vh, 320px);
  object-fit: contain;
  background: none;
}

/* The paused frame, held by the room under the video: the same box and the
   same fit, so it lines up with the picture pixel for pixel and is only ever
   seen if Safari empties the video under memory pressure (room.js,
   holdFrame). */
.screen__still {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}

/* --- the picture, the whole screen ----------------------------------------

   A dark lens in the picture's corner: black glass with a faint light inside
   its rim, the same frost the island wears turned the other way up, because
   it sits on the picture rather than on the fog and has to read over a white
   sky as well as a night scene. Round, because fog has no corners. It is
   always there over a picture -- nothing in this room appears only under a
   pointer -- and quiet enough at rest not to be part of the film. */
.screen__full {
  position: absolute;
  /* The picture's own corner (room.js, placeLens), not the box's. */
  top: calc(var(--lens-y, 0px) + var(--s2));
  right: calc(var(--lens-x, 0px) + var(--s2));
  z-index: 3;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(10, 13, 16, 0.56);
  color: rgba(242, 246, 248, 0.9);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16),
              inset 0 1px 0 0 rgba(255, 255, 255, 0.22),
              0 0 12px 1px rgba(255, 255, 255, 0.10);
  cursor: pointer;
  line-height: 0;
  transition: background-color var(--dur-state) var(--ease),
              box-shadow var(--dur-state) var(--ease),
              color var(--dur-state) var(--ease);
}
.screen__full svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.screen__full:hover {
  background: rgba(6, 8, 10, 0.72);
  color: #fff;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.28),
              inset 0 1px 0 0 rgba(255, 255, 255, 0.34),
              0 0 18px 3px rgba(255, 255, 255, 0.20);
}
.screen__full:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
/* In, the corners point out; out, they point in. */
.screen__full .screen__full-out { display: none; }
.screen__full[aria-pressed="true"] .screen__full-in { display: none; }
.screen__full[aria-pressed="true"] .screen__full-out { display: inline; }

/* Full screen: the picture as large as the screen allows, on black, the
   subtitle line where a player draws it, and the room's own player along the
   foot. */
.screen:is(:fullscreen, :-webkit-full-screen) {
  margin: 0;
  border-radius: 0;
  width: 100%;
  height: 100%;
  background: #000;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.screen:is(:fullscreen, :-webkit-full-screen) .screen__video {
  flex: 1 1 auto;
  min-height: 0;
  height: 100%;
  max-height: none;
}
.screen:is(:fullscreen, :-webkit-full-screen) .screen__sub {
  bottom: calc(var(--s7, 56px) + var(--s4));
  font-size: clamp(var(--t-md), 3vw, 2.4rem);
}
.screen:is(:fullscreen, :-webkit-full-screen) .screen__full {
  top: calc(var(--lens-y, 0px) + var(--s4));
  right: calc(var(--lens-x, 0px) + var(--s4));
}
/* The play button in full screen is the same dark lens as the corner one,
   so both read over a bright frame as well as a dark one. */
.screen:is(:fullscreen, :-webkit-full-screen) > .player .player__play {
  width: 44px;
  height: 44px;
  border: 0;
  background: rgba(10, 13, 16, 0.56);
  color: rgba(242, 246, 248, 0.92);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16),
              inset 0 1px 0 0 rgba(255, 255, 255, 0.22),
              0 0 12px 1px rgba(255, 255, 255, 0.10);
}
.screen:is(:fullscreen, :-webkit-full-screen) > .player .player__play:hover { background: rgba(6, 8, 10, 0.72); }
.screen:is(:fullscreen, :-webkit-full-screen) > .player .player__play[data-playing] { color: #fff; }
.screen:is(:fullscreen, :-webkit-full-screen) > .player {
  position: absolute;
  /* Above the picture (z-index 1), or the picture takes its presses. */
  z-index: 3;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: var(--s4) var(--s5);
  line-height: normal;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0));
  --film-ink: #F2F6F8;
  --film-ink-3: rgba(242, 246, 248, 0.72);
  --film-rule: rgba(255, 255, 255, 0.28);
  --film-raise: rgba(255, 255, 255, 0.16);
  --film-raise-hi: rgba(255, 255, 255, 0.26);
}
.screen::backdrop { background: #000; }

@media (hover: none) {
  .screen__full { width: 44px; height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  .screen__full { transition: none; }
}

/* Drawn where a player draws them, and looking like what they are.

   Not in this room's type and not in this room's colours: a subtitle is the
   one thing on this screen that is not part of the interface, and dressing it
   in the interface's clothes would make it look right here and wrong
   everywhere the file is going to be played. White, heavy, centred, with a
   shadow hard enough to survive a bright frame underneath -- which is what
   every subtitle renderer in the world does, for the reason that it works. */
.screen__sub {
  position: absolute;
  left: 50%;
  bottom: 6%;
  transform: translateX(-50%);
  z-index: 2;
  max-width: 88%;
  margin: 0;
  padding: 0.15em 0.5em;
  text-align: center;
  text-wrap: balance;
  font-size: clamp(var(--t-sm), 2.4vw, var(--t-md));
  font-weight: 700;
  line-height: 1.25;
  color: #fff;
  background: rgba(0, 0, 0, 0.42);
  border-radius: 4px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 0 10px rgba(0, 0, 0, 0.6);
  pointer-events: none;
}

/* The words coming out of the fog.

   The room's entrance is called `resolve` in `rooms.js` and has been since
   before the room existed; nothing anywhere read it, so what arrived was a
   palette and a round island and no character at all. This is the word made
   good, and it is made good with the building's own keyframe -- the one the
   photo room's picture resolves through -- rather than a second one that
   looked similar until somebody changed one of them.

   On the transcript rather than on the panel, because the transcript is the
   thing worth watching arrive: it is what the room is for, and it is the only
   part of the face that was not already on the screen a moment ago. */
.script[data-arriving] .script__line {
  animation: island-resolve var(--dur-morph, 900ms) var(--ease-soft) both;
  animation-delay: var(--in, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  .script[data-arriving] .script__line { animation: none; }
}

.script__line {
  display: flex;
  gap: var(--s3);
  align-items: baseline;
  margin: 0 0 var(--s2);
  line-height: var(--lh-prose);
}

/* The line being spoken.

   A rule down its left and nothing else. A filled row would redraw the whole
   transcript as it plays, which is movement across the thing you are trying
   to read; a rule appears beside it and leaves the words alone. */
.script__line[data-now] { position: relative; }
.script__line[data-now]::before {
  content: "";
  position: absolute;
  left: calc(var(--s3) * -1);
  top: 0.15em;
  bottom: 0.15em;
  width: 2px;
  border-radius: 1px;
  background: var(--signal);
}
.script__line[data-now] .script__at { color: var(--signal); }

/* The time it was said, and where pressing it takes you. */
.script__at {
  flex: none;
  font-size: var(--t-xs);
  color: var(--film-ink-3);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: color var(--dur-state) var(--ease);
}
.script__at:hover { color: var(--signal); }
.script__at:focus-visible { outline: 2px solid var(--film-ink); outline-offset: 2px; }

.script__words {
  color: var(--film-ink);
  /* A measure you can actually read down. Not the full width of a 1120px
     console, which is about twenty words a line and loses your place. */
  max-width: 62ch;
  /* Editable, and it should not look like a form.

     No box, no background, no border: a transcript is prose and the thing you
     are doing to it is correcting a word, not filling in a field. What it does
     need is somewhere to put the caret on an empty line and a visible answer
     when it has focus, which is a rule under the words rather than a frame
     round them. */
  min-width: 2ch;
  border-radius: 2px;
  outline-offset: 2px;
  transition: box-shadow var(--dur-state) var(--ease), background-color var(--dur-state) var(--ease);
}
/* Both of these mark a line as something you can type into, and both used the
   shade -- a darkening, which says nothing at all now that the room's shade is
   pale. The raised surface is the token that means "lifted off this room's
   panel" whichever way up the room is. */
.script__words:hover { background: var(--film-raise); }
.script__words:focus {
  outline: none;
  background: var(--film-raise-hi);
  box-shadow: inset 0 -1px 0 0 var(--signal);
}
.script__words:focus-visible { outline: 2px solid var(--film-ink); }

/* --- the exact times ------------------------------------------------------

   With Exact times on, each line carries its start and end under its words,
   in the room's own time field (timing.js): a mono capsule of the same
   hairline and raise as every pill here, wide enough for 00:00:00.000 and
   no wider. The tools for the line -- add after, cut at the caret, join to
   the next, take out -- sit at the end of that row on every line, so what
   a line can do is there to be read without pointing at it first (DESIGN.md
   §1.6). Quiet on every line, and full strength on the line being worked
   (the one with the keyboard in it, under the pointer, or being spoken), so
   three hundred lines of buttons read as one row of tools at a time. */
.script[data-exact] .script__line { align-items: flex-start; }
.script__body {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  min-width: 0;
}
.script__times {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
}
.stamp {
  /* 00:00:00.000 is twelve characters; one more for the caret. */
  width: calc(13ch + var(--s3) * 2);
  height: 28px;
  padding: 0 var(--s3);
  font: inherit;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
  color: var(--film-ink-2);
  background: var(--film-raise);
  border: var(--hairline) solid var(--film-rule);
  border-radius: var(--radius-control);
  transition: background-color var(--dur-state) var(--ease),
              border-color var(--dur-state) var(--ease),
              color var(--dur-state) var(--ease);
}
.stamp:hover { background: var(--film-raise-hi); }
.stamp:focus { outline: none; color: var(--film-ink); border-color: var(--signal); background: var(--film-raise-hi); }
.stamp:focus-visible { outline: 2px solid var(--film-ink); outline-offset: 2px; }
/* A time held back at an edge: the field glows the signal for a moment. */
.stamp[data-held] { border-color: var(--signal); box-shadow: 0 0 0 3px rgb(from var(--signal) r g b / 0.18); }
.script__dash { color: var(--film-ink-3); font-size: var(--t-xs); }

/* Running into the next line: allowed, and marked -- the end time takes the
   room's failure ink and a hairline under it, and the tip says why. */
.script__line[data-overlap] .stamp[data-part="end"] {
  color: var(--film-bad);
  border-color: var(--film-bad);
}

.script__tools {
  display: inline-flex;
  gap: var(--s1);
  margin-left: var(--s2);
}
/* Away from the line being worked: no fill, the hairline and the glyph in
   the third ink, still a button to look at and to press. Where there is no
   pointer (phones and tablets), every line's tools stay at full strength --
   there is no hover there to bring them up. */
@media (hover: hover) {
  .script[data-exact] .script__line:not(:focus-within):not([data-now]):not(:hover) .script__tool {
    background: transparent;
    color: var(--film-ink-3);
  }
}
.script__tool {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  padding: 0;
  border: var(--hairline) solid var(--film-rule);
  border-radius: 50%;
  background: var(--film-raise);
  color: var(--film-ink-2);
  cursor: pointer;
  transition: background-color var(--dur-state) var(--ease), color var(--dur-state) var(--ease);
}
.script__tool svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.script__tool:hover { background: var(--film-raise-hi); color: var(--film-ink); }
.script__tool[data-tool="remove"]:hover { color: var(--film-bad); }
.script__tool:focus-visible { outline: 2px solid var(--film-ink); outline-offset: 2px; }

@media (hover: none) {
  .stamp { height: 44px; }
  .script__tool { width: 44px; height: 44px; }
  /* No hover on a phone, so the tools show on every line (the rule that
     keeps them to the line being worked is for a pointer only, above). */
  .script__tools { margin-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .stamp, .script__tool { transition: none; }
}

/* --- the recording, playable ------------------------------------------------

   The browser's own control is a grey slab belonging to another operating
   system. This room already owns a bar that fills, a mono clock and a round
   button, so the player is made of those. */
.player {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s4);
}

.player__play {
  flex: none;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border: var(--hairline) solid var(--film-rule);
  border-radius: 50%;
  background: var(--film-raise);
  color: var(--film-ink);
  cursor: pointer;
  transition: background-color var(--dur-state) var(--ease),
              border-color var(--dur-state) var(--ease);
}
.player__play:hover { background: var(--film-raise-hi); border-color: var(--film-ink-3); }
.player__play:focus-visible { outline: 2px solid var(--film-ink); outline-offset: 2px; }
.player__play[data-playing] { border-color: var(--signal); color: var(--signal); }
.player__icon { width: 15px; height: 15px; fill: currentColor; }
/* Nudged, because a triangle is heavier on its left and sits wrong in a circle
   that is centred on its bounding box. */
.player__play:not([data-playing]) .player__icon { margin-left: 2px; }

.player__track {
  flex: 1 1 auto;
  min-width: 0;
  height: 4px;
  border-radius: 2px;
  /* The same raised surface the listening bar above it is made of, and not a
     translucent shade. A shade is darkness, and darkness only shows where
     there is something behind it to darken: over the moving backdrop the line
     was perfectly clear, and on a phone -- where the backdrop is not played --
     it was black on black and the scrub bar simply was not there. */
  background: var(--film-raise);
  cursor: pointer;
  touch-action: none;
  position: relative;
}
.player__track:focus-visible { outline: 2px solid var(--film-ink); outline-offset: 4px; }
/* The target is taller than the line. Four pixels is right to look at and
   nothing at all to hit, particularly with a thumb. */
.player__track::after {
  content: "";
  position: absolute;
  inset: -12px 0;
}
.player__played {
  height: 100%;
  width: 0%;
  border-radius: inherit;
  background: var(--signal);
}

.player__time {
  flex: none;
  font-size: var(--t-xs);
  color: var(--film-ink-3);
  font-variant-numeric: tabular-nums;
}

/* --- a phone, and a thumb ---------------------------------------------------

   Written with the room rather than added to it afterwards. Two things change
   on a narrow screen and they are not the same thing: how much room there is,
   and what is holding the device.

   Room: the transcript gets more of the height, because on a phone it is very
   nearly the whole page, and the measure that keeps a line readable on a
   1120px console is already narrower than the screen.

   Thumb: every target that was sized for a pointer is sized for a finger.
   Apple and Google both land on about 44 and 48 points respectively, and the
   difference between them is not worth an argument. */
@media (max-width: 760px) {
  .script__line {
    gap: var(--s2);
    margin-bottom: var(--s3);
  }
  .script__line[data-now]::before { left: calc(var(--s2) * -1); }
  .script__words { max-width: none; }
  /* A tall, narrow screen: a smaller share, so the words start on it. */
  .screen__video { max-height: clamp(120px, 28vh, 320px); }
  /* The clock keeps its own line's height so a thumb has something to hit,
     without becoming a button-shaped thing in the middle of prose. */
  .script__at { padding: var(--s1) 0; }
  .player__play { width: 44px; height: 44px; }
  .player__icon { width: 17px; height: 17px; }
  .player__track { height: 6px; }
}

/* A finger cannot hover, so everything that only appears on hover has to be
   there already. These two are the ones that were: a line you can correct
   looked like ordinary prose until a pointer passed over it, which on a phone
   is never. */
@media (hover: none) {
  .script__words {
    /* The raised surface, not the shade.

       `rgb(var(--shade-rgb) / 0.22)` is a darkening, and it stopped being one
       the day this room became a light room: `--shade-rgb` went pale, so the
       wash marking every editable line turned white on white and the one
       affordance a finger has disappeared. `--film-raise` is the token that
       means "a surface lifted off this room's panel", and it flips with the
       room because that is its whole job. */
    background: var(--film-raise);
    padding: 0 var(--s1);
  }
  .script__at { color: var(--film-ink-2); }
  /* Every choice in this room is a pill, and a pill is 30 pixels tall: right
     for a pointer, a near miss for a thumb. DESIGN.md §2.4 asks 44 on touch. */
  .face[data-face="transcribe-work"] .pill:not(.pill--quiet) { min-height: 44px; }
}

@media (prefers-reduced-motion: reduce) {
  .listen__fill { transition: none; animation: none; }
  /* The sweep is named here as well: `.listen__fill[data-unknown]` above
     outranks a bare `.listen__fill`, so the line before this one never
     stopped it and the bar swept under reduced motion all along. */
  .listen__fill[data-unknown] { width: 100%; opacity: 0.4; animation: none; }
  .script__line[data-now]::before { transition: none; }
}
