/*
 * The renderer contract every avatar stage shares -- and nothing else.
 *
 * Loaded by the student session (css/student.css), the studio (css/studio.css)
 * and the instructor's avatar tester (js/avatar-tester.js) for the layers
 * js/avatar-liveavatar.js and js/chroma.js create inside `#stage`:
 * `.stage__canvas` first, then `.stage__backdrop` (the filter keyer's picture)
 * and `.stage__video` (the WebRTC frame); the opaque `.stage__cover` is
 * inline-styled by the renderer. Also here: transcript-replay.js's
 * "Hear this again" button and the interlinear gloss it shares with student.js,
 * and the student root's own box. Every page class the 2026 design replaced
 * (bar, columns, connecting screen, composer, mic, captions, suggestions,
 * switch) was removed on 2026-10-24; the pages draw those themselves.
 */

.session {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

/* -- the renderer contract ------------------------------------------------ */

.stage__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/*
 * The backdrop, for the filter keyer.
 *
 * The GPU compositor draws the backdrop into the canvas itself. The CSS-filter
 * keyer cannot -- a filter transforms one element and knows nothing about what
 * is behind it -- so the picture is a plain layer underneath the video, and the
 * keyed-out wall simply reveals it.
 */
.stage__backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/*
 * WebRTC video (LiveAvatar) is layered over the canvas rather than copied into
 * it: drawing a video frame to a canvas every tick costs real CPU for nothing.
 *
 * Sized to the stage's *height*, not to its box, and centred. This is the
 * filter keyer's half of the rule the GPU keyer applies in the shader
 * (`fitHeightScale` in js/chroma.js) -- the reasoning is written out there, and
 * the short version is that `object-fit: cover` on a stage wider than 16:9
 * crops the frame vertically, and the top of a head-and-shoulders frame is the
 * top of the head. The stage's aspect ratio is free: it is a grid cell in a
 * window the student can drag to any shape, and short-and-wide is one drag
 * away. So the frame's full height always maps to the stage's full height, at
 * every window shape, with the headroom it was filmed with.
 *
 * `height` is definite and `width` is auto, so the used width is the height
 * times the video's own aspect ratio -- never a stretch. Wider than the stage
 * it overflows into `.stage`'s `overflow: hidden`, which crops the wall either
 * side of a centred person. Narrower, it leaves a gap, and the gap is filled by
 * `.stage__backdrop` behind it. (With no backdrop and no key there is nothing
 * behind it but the stage, so the gap is stage-coloured -- which is what any
 * video player does, and still better than a decapitated tutor.)
 */
.stage__video {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  height: 100%;
  width: auto;
  /* Stated rather than assumed: this is the one element in the app that means
     to be wider than its container, and any `max-width: 100%` reaching it --
     from a reset, or from the host page when the session is embedded -- would
     clamp the width while the height stayed 100% and squash the picture. */
  max-width: none;
  display: block;
  background: var(--color-field-navy);
}


/* -- transcript-replay's button and the gloss (student.js, transcript-replay.js) -- */

/*
 * "Hear this again", on the avatar's own lines.
 *
 * Bottom-left of the bubble and inside it, so there is never a question about
 * which line it plays. Its own row rather than tucked beside the last word: a
 * reply ends wherever it ends, and a button floated into that ragged edge lands
 * somewhere different in every bubble.
 *
 * Quiet by default -- it is offered on every avatar line, and eight of them
 * down a column must not read as eight things to press. Colour arrives on
 * hover, on focus, and while it is playing.
 */
.bubble__replay {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  /* Negative on three sides: the icon is small inside a hit target big enough
     to press, and without pulling the box back the glyph sits indented from the
     text above it and pads the bubble out below it. */
  margin: 1px 0 -5px;
  margin-inline-start: -5px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--color-ink-3);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

.bubble__replay:hover {
  color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 13%, transparent);
}

.bubble__replay:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  color: var(--color-primary);
}

/* Waiting on the server to synthesise the line. It is a round trip, not a
   cached clip, so on a slow turn this is a second or two of nothing. */
.bubble__replay[data-state='loading'] {
  color: var(--color-primary);
  animation: replay-pulse 1s ease-in-out infinite;
}

.bubble__replay[data-state='playing'] {
  color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 16%, transparent);
}

@keyframes replay-pulse {
  50% {
    opacity: 0.35;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bubble__replay,
  .bubble__replay[data-state='loading'] {
    transition: none;
    animation: none;
  }
}

/*
 * A line the avatar said in another language, written out for reading.
 *
 * Styled as a subtitle rather than as speech, because that is what it is: the
 * avatar never says this. Set on its own line, indented behind a rule and in
 * the muted colour the transcript uses for things that are not dialogue, so it
 * reads as an aside to the sentence above it and is skipped by a student who
 * did not need it.
 */
.bubble__subtitle {
  display: block;
  margin: 5px 0 3px;
  /* Logical, not left: the subtitle is often the right-to-left half of the
     pair, and a rule pinned to the left edge there sits at the end of the line
     instead of the start of it. */
  padding-inline-start: 9px;
  border-inline-start: 2px solid color-mix(in srgb, var(--color-primary) 40%, transparent);
  color: var(--color-ink-2);
  font-size: 13px;
  line-height: 1.45;
  font-style: italic;
}

/* Nothing above it to be an aside to, so it does not need the gap. */
.bubble__subtitle:first-child {
  margin-top: 0;
}


/*
 * "Hide" beside the chevron that puts the transcript column away: the word is
 * drawn by the desktop pages' own rules; the phone's sheet keeps the bare icon.
 */
.sheet__close-label {
  display: none;
}
