/* ══════════════════════════════════════════════════════════════════════════
   Dean Reception — widget presentation.

   SCOPING CONTRACT. Every selector in this file sits under `.rc-app` (the two
   exceptions are documented where they appear: the @font-face blocks, which
   are at-rules and not selectors, and #rc-hp, an id that exists only inside
   this widget). The staff SPA loads this same stylesheet so the Studio
   Preview bench can mount the identical markup — one rule that escaped `.rc-app`
   would repaint the whole back office. Enforced by:
       grep -nE "^\s*(html|body|\*|:root)\s*[,{]" public/reception.css   # no output

   TOKEN CONTRACT. Every custom property is `--rc-*`. The staff SPA owns
   `--surface` / `--ink` and INVERTS them in dark mode; a bare `--surface` here
   would corrupt the host app the moment the bench mounted the widget.

   ACCENT. The per-school accent is written by reception.js onto <html>
   (`document.documentElement.style.setProperty('--rc-accent', …)`). This file
   therefore READS `--rc-accent` with a neutral default fallback and never DECLARES it —
   a `--rc-accent: #6d28d9` on `.rc-app` would shadow the inherited runtime
   value and silently kill per-school theming. Everything downstream derives
   from `--rc-brand` so one setting repaints the widget.
   (reception.js also sets `--rc-accent-deep` to the *same* colour as the
   accent; deriving the deep tone here instead keeps the stage gradient real
   whatever a school picks.)

   MOTION. One easing curve. Only `transform`, `translate` and `opacity` are
   animated — never width/height/top/left — so every transition stays on the
   compositor. `prefers-reduced-motion: reduce` at the foot of the file kills
   all of it, scoped to `.rc-app`.

   FONTS are self-hosted (compliance D4): no visitor IP may reach Google.
   ══════════════════════════════════════════════════════════════════════════ */

/* At-rules, not selectors — they cannot leak styling into a host document.
   unicode-ranges match public/form-theme.css exactly so the widget and the
   enquiry form load the same two subsets (one cache entry, not two). latin-ext
   is what carries Polish, Czech, Hungarian, Turkish, Romanian and Lithuanian
   diacritics — the widget ships on all of those domains. */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/app/fonts/plus-jakarta-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/app/fonts/plus-jakarta-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── Tokens ──────────────────────────────────────────────────────────────
   The complete LIGHT palette is defined here. The dark block below redefines
   tokens only — no colour anywhere in this file has its sole definition
   inside a media query. */
.rc-app {
  /* Brand — read, never declared. See the ACCENT note in the header. */
  --rc-brand: var(--rc-accent, #6d28d9);
  --rc-brand-deep: color-mix(in srgb, var(--rc-brand) 52%, #04070e);
  --rc-brand-lift: color-mix(in srgb, var(--rc-brand) 64%, #ffffff);
  --rc-brand-tint: color-mix(in srgb, var(--rc-brand) 9%, transparent);
  --rc-brand-ring: color-mix(in srgb, var(--rc-brand) 26%, transparent);
  --rc-on-brand: #ffffff;
  --rc-bubble-visitor: var(--rc-brand);

  /* Neutrals */
  --rc-surface: #ffffff;
  --rc-surface-2: #f5f7fb;
  --rc-surface-3: #eaeef6;
  --rc-ink: #171c2e;
  --rc-ink-2: #5b6480;
  --rc-ink-3: #7d86a0;
  --rc-line: #e4e8f1;
  --rc-line-strong: #cfd6e4;

  /* Warning / failure surface (offline banner, send failures) */
  --rc-warn: #8a4708;
  --rc-warn-bg: #fdf4e7;
  --rc-warn-line: #f2ddbd;

  /* Chrome on the stage — translucent white over the lit gradient. */
  --rc-glass: rgba(255, 255, 255, 0.11);
  --rc-glass-2: rgba(255, 255, 255, 0.2);
  --rc-glass-line: rgba(255, 255, 255, 0.22);

  /* Tinted diffusion, not grey drop shadow. */
  --rc-shadow-1: 0 1px 2px color-mix(in srgb, var(--rc-brand) 12%, transparent);
  --rc-shadow-2: 0 14px 34px -16px color-mix(in srgb, var(--rc-brand) 46%, transparent);
  --rc-fig-shadow: 0 16px 24px rgba(4, 8, 20, 0.42);

  --rc-focus: var(--rc-brand);

  --rc-radius: 16px;
  --rc-radius-lg: 20px;
  --rc-radius-sm: 10px;
  --rc-pill: 999px;
  --rc-pad: 14px;

  --rc-ease: cubic-bezier(.16, 1, .3, 1);
  --rc-t-fast: 130ms;
  --rc-t-mid: 220ms;
  --rc-t-slow: 300ms;

  /* Set by reception.js from window.visualViewport so the composer clears the
     on-screen keyboard. 0 everywhere else. */
  --rc-kb: 0px;

  /* The widget owns its whole frame. `position:absolute` on the initial
     containing block sidesteps the UA's default body margin without this file
     ever writing a `body` rule — see the SCOPING CONTRACT. Inside `.rc-device`
     (the studio bench) it re-anchors to the frame instead; see that rule. */
  position: absolute;
  inset: 0 0 auto 0;
  height: 100dvh;
  overflow: clip;

  /* Size container, so the width breakpoint below keys off the WIDGET's own
     inline size instead of the browser viewport. Identical result on the
     public page — the widget is alone inside its iframe, so its inline size
     IS that iframe's viewport — and it is the only thing that makes the
     studio bench's device toggle honest: a 380 px frame on a 1440 px staff
     screen has to render the same bubble widths as the real 380 px desktop
     embed panel, whose iframe viewport genuinely is 380 px. Keyed on width,
     it did not, and staff reviewed a layout no visitor ever sees.
     Safe here: nothing in this file is position:fixed (the fixed launcher,
     nudge and frame live in reception-widget.css, on the HOST page, outside
     the iframe), so the layout containment this brings captures nothing, and
     `.rc-app` already establishes both a containing block (position:absolute)
     and a formatting context (display:flex). */
  container-type: inline-size;
  container-name: rc;

  display: flex;
  flex-direction: column;
  min-height: 0;
  padding-bottom: var(--rc-kb, 0px);

  font-family: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.45;
  color: var(--rc-ink);
  background: var(--rc-surface);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

@media (prefers-color-scheme: dark) {
  .rc-app {
    --rc-brand-deep: color-mix(in srgb, var(--rc-brand) 40%, #01030a);
    --rc-brand-lift: color-mix(in srgb, var(--rc-brand) 72%, #ffffff);
    --rc-brand-tint: color-mix(in srgb, var(--rc-brand) 24%, transparent);
    --rc-brand-ring: color-mix(in srgb, var(--rc-brand) 44%, transparent);
    /* Lifted just enough that white text keeps its contrast on the bubble AND
       the bubble keeps its contrast against the recessed list behind it. */
    --rc-bubble-visitor: color-mix(in srgb, var(--rc-brand) 84%, #ffffff);

    --rc-surface: #1a1e35;
    --rc-surface-2: #11142a;
    --rc-surface-3: #262c48;
    --rc-ink: #edeff8;
    --rc-ink-2: #a6adc8;
    --rc-ink-3: #838ba6;
    --rc-line: #2c3255;
    --rc-line-strong: #3b4269;

    --rc-warn: #f2c48c;
    --rc-warn-bg: #33240f;
    --rc-warn-line: #4d3819;

    --rc-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5);
    --rc-shadow-2: 0 16px 38px -16px rgba(0, 0, 0, 0.72);
    --rc-fig-shadow: 0 16px 26px rgba(0, 0, 0, 0.6);

    --rc-focus: color-mix(in srgb, var(--rc-brand) 34%, #ffffff);
  }
}

/* ── Scoped reset ────────────────────────────────────────────────────────
   `:where()` contributes zero specificity, so every rule below beats it
   without an escalation war. */
.rc-app,
.rc-app *,
.rc-app *::before,
.rc-app *::after { box-sizing: border-box; }

.rc-app :where(p, ul, ol, li, h1, h2, h3, h4, figure, blockquote) { margin: 0; padding: 0; }
.rc-app :where(button, input, textarea, select) { font: inherit; color: inherit; }
.rc-app :where(button) { background: none; border: 0; }

/* The `hidden` attribute must beat every `display:` this file sets, or
   #rc-starters / #rc-email-form / #rc-speak / #rc-mic / #rc-voice-note render
   the moment they are given a layout mode. */
.rc-app [hidden] { display: none !important; }

/* ══ STAGE ════════════════════════════════════════════════════════════════
   A lit space with Dean standing in it. The box height changes in a single
   step (`height` is never transitioned — see MOTION in the header); the
   collapse is CARRIED by the figure's transform, which is what the eye
   tracks. That is also why the message list reflows once rather than on
   every frame of a 300 ms height animation.

   Figure geometry lives in tokens because the emblem light (.rc-core) is a
   SIBLING of the image, not a child, and has to be positioned from the same
   numbers. --rc-fh / --rc-ftop / --rc-fright describe the figure's EFFECTIVE
   (post-transform) box; the image itself is pinned to the stage's top-right
   corner and moved there with transform+scale.

   --rc-core-x / --rc-core-y are the emblem's centre as a fraction of the
   artwork, measured off the files themselves (canvas pixel scan, not guessed):
       dean-hover  572×1200  0.4517 / 0.2230
       dean-hero   570×1200  0.4878 / 0.4048
       dean-guard  569×1200  0.5386 / 0.2517
   The poses put the chest emblem in genuinely different places, so the
   defaults below are dean-hover's and the sibling selectors further down
   correct them per pose. */
.rc-stage {
  --rc-stage-h: 176px;
  --rc-fig-base: 330px;
  --rc-fig-scale: 1;
  --rc-fh: 330px;
  --rc-ftop: 4px;
  --rc-fright: -8px;
  --rc-fig-ar: .4767;
  --rc-core-x: .4517;
  --rc-core-y: .2230;
  --rc-core-size: 54px;
  --rc-focus: rgba(255, 255, 255, 0.92);

  position: relative;
  flex: none;
  /* A FLOOR, not a fixed height. Two of the chip's strings are full sentences
     and one of them arrives while the bar is collapsed; a hard 74px clipped
     the escape-hatch button clean in half. The bar grows for those states and
     is 176/74 for every ordinary one. Height is never TRANSITIONED — see
     MOTION in the header. */
  min-height: var(--rc-stage-h);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 10px;
  /* Right padding reserves the figure's visible silhouette, measured from the
     artwork rather than guessed at. */
  padding: 14px 118px 15px var(--rc-pad);
  color: #fff;
  background:
    radial-gradient(56% 74% at 74% 106%, rgba(255, 255, 255, 0.2), transparent 66%),
    radial-gradient(132% 104% at 86% -14%, var(--rc-brand-lift) 0%, var(--rc-brand) 44%, var(--rc-brand-deep) 100%);
  /* Hairline highlight along the bottom edge, plus a short tinted diffusion
     cast DOWN onto the transcript — the stage is `position: relative` and the
     list is not, so it paints over it. That soft edge is what stops a
     mid-sentence line looking severed where the list scrolls under the bar. */
  box-shadow:
    inset 0 -1px 0 rgba(255, 255, 255, 0.1),
    0 10px 22px -14px color-mix(in srgb, var(--rc-brand) 70%, transparent);
}

.rc-stage.compact {
  --rc-stage-h: 74px;
  --rc-fig-scale: .4545;
  --rc-fh: 150px;
  --rc-ftop: 2px;
  --rc-fright: 10px;
  --rc-core-size: 24px;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  padding: 8px 62px 8px var(--rc-pad);
}

/* ── The figure ──────────────────────────────────────────────────────────
   `translate` (the standalone property) carries the idle float; `transform`
   carries the state changes. Keeping them on separate properties is what
   lets a 6 s float run underneath a 340 ms collapse without either clobbering
   the other. */
.rc-fig {
  position: absolute;
  top: 0;
  right: 0;
  height: var(--rc-fig-base);
  width: auto;
  transform-origin: 100% 0;
  transform: translate3d(calc(-1 * var(--rc-fright)), var(--rc-ftop), 0) scale(var(--rc-fig-scale));
  transition: transform 340ms var(--rc-ease), opacity var(--rc-t-slow) var(--rc-ease);
  animation: rc-float 6s ease-in-out infinite alternate;
  filter: drop-shadow(var(--rc-fig-shadow));
  will-change: transform, translate;
  pointer-events: none;
  user-select: none;
}

/* Dean steps out of the way once a person has taken the conversation over. */
.rc-stage.stepped-aside .rc-fig {
  transform: translate3d(calc(-1 * var(--rc-fright) + 34px), var(--rc-ftop), 0) scale(var(--rc-fig-scale));
  opacity: .38;
}

/* ── The emblem ──────────────────────────────────────────────────────────
   A soft radial light sitting exactly over the chest logo already present in
   the artwork — the only light in the whole design. Never a neon outer glow
   on chrome. */
.rc-core {
  --rc-fw: calc(var(--rc-fh) * var(--rc-fig-ar));
  position: absolute;
  width: var(--rc-core-size);
  height: var(--rc-core-size);
  top: calc(var(--rc-ftop) + var(--rc-fh) * var(--rc-core-y) - var(--rc-core-size) / 2);
  right: calc(var(--rc-fright) + var(--rc-fw) * (1 - var(--rc-core-x)) - var(--rc-core-size) / 2);
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(196, 220, 255, .92) 0%,
    rgba(120, 168, 255, .55) 38%,
    rgba(86, 132, 240, .22) 58%,
    transparent 72%);
  opacity: 0;
  transform: scale(.7);
  transition: opacity 150ms ease-out, transform 150ms var(--rc-ease);
  pointer-events: none;
}

/* Per-pose correction. `.rc-core` follows `.rc-fig` in the markup, so a
   general sibling combinator can read the pose straight off the live `src`
   that reception-stage.js writes. */
.rc-fig[src*="dean-hero"] ~ .rc-core { --rc-core-x: .4878; --rc-core-y: .4048; }
.rc-fig[src*="dean-guard"] ~ .rc-core { --rc-core-x: .5386; --rc-core-y: .2517; }

/* A low resting glow for the whole utterance… */
.rc-stage.speaking .rc-core { opacity: .34; transform: scale(.94); }
/* …lifted once word boundaries are actually arriving (`.pulsing` is added on
   the first one and dropped at end-of-utterance, so it distinguishes "audio
   opened" from "words are landing")… */
.rc-stage.pulsing .rc-core { opacity: .46; transform: scale(.99); }
/* …and a brief flash on each individual word. Every combination is listed so
   the flash always out-specifies the two resting rules above, and still lands
   if `.hit` ever arrives without them. */
.rc-core.hit,
.rc-stage.speaking .rc-core.hit,
.rc-stage.pulsing .rc-core.hit { opacity: 1; transform: scale(1.2); }

/* ── Identity block ──────────────────────────────────────────────────────── */
.rc-id {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
}
/* In the collapsed bar the identity column gives way FOUR TIMES faster than
   the tools: a status chip wraps gracefully, a 27-character button does not.
   Neither side gets `min-width: 0` — flexbox's automatic min-content floor is
   exactly what stops either of them being split mid-word. */
.rc-stage.compact .rc-id {
  flex: 1 4 auto;
  flex-direction: row;
  align-items: center;
  gap: 8px;
}

.rc-name {
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.15;
}
.rc-stage.compact .rc-name { font-size: 14px; flex: 0 0 auto; }

.rc-sub {
  font-size: 12px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.72);
}
/* No room for it in the collapsed bar. The Article 50 disclosure it echoes is
   carried permanently by .rc-foot, which never collapses. */
.rc-stage.compact .rc-sub { display: none; }

/* ── Status chip ─────────────────────────────────────────────────────────
   Conversation state lives here, not in a banner. Some strings are full
   sentences ("You're talking with a member of the team."), so the label
   wraps rather than truncating. */
.rc-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 1 auto;
  max-width: 100%;
  margin-top: 1px;
  padding: 5px 11px;
  border-radius: var(--rc-pill);
  border: 1px solid var(--rc-glass-line);
  background: var(--rc-glass);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.3;
  color: #fff;
  transition: background var(--rc-t-mid) var(--rc-ease);
}
.rc-stage.speaking .rc-chip { background: var(--rc-glass-2); }
.rc-stage.compact .rc-chip { font-size: 11px; padding: 4px 8px; gap: 6px; margin-top: 0; }
/* `break-word`, NOT `anywhere`: the collapsed bar squeezes this flex item hard
   and `anywhere` splits mid-word — "Speakin / g". The chip keeps flexbox's
   automatic min-content floor (no `min-width: 0` above) so it can only wrap
   BETWEEN words. */
.rc-chip-label { overflow-wrap: break-word; }

/* ── Five-bar meter ──────────────────────────────────────────────────────
   An accessibility affordance, not filigree: for a muted or deaf visitor this
   is the only visible proof that audio is playing. Present ONLY while audio
   is genuinely playing (reception-stage.js adds `.speaking` off a real
   SpeechSynthesis start event), completely still otherwise.

   Under reduced motion the bars are frozen at their per-bar peak, which is a
   readable static waveform rather than a flat block — and the chip still says
   "Speaking", so nothing is carried by movement alone. */
.rc-eq {
  display: none;
  align-items: flex-end;
  gap: 3px;
  height: 14px;
  flex: none;
}
.rc-stage.speaking .rc-eq { display: inline-flex; }
.rc-eq b {
  display: block;
  width: 3px;
  height: 14px;
  border-radius: 1.5px;
  background: currentColor;
  transform: scaleY(var(--rc-eq-peak, 1));
  transform-origin: 50% 100%;
}
.rc-stage.speaking .rc-eq b { animation: rc-eq 880ms var(--rc-ease) infinite alternate; }
.rc-eq b:nth-child(1) { --rc-eq-peak: .45; animation-delay: 0ms; }
.rc-eq b:nth-child(2) { --rc-eq-peak: .8; animation-delay: 130ms; }
.rc-eq b:nth-child(3) { --rc-eq-peak: 1; animation-delay: 260ms; }
.rc-eq b:nth-child(4) { --rc-eq-peak: .7; animation-delay: 90ms; }
.rc-eq b:nth-child(5) { --rc-eq-peak: .5; animation-delay: 320ms; }

/* ── Stage tools ─────────────────────────────────────────────────────────── */
.rc-stage-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.rc-stage.compact .rc-stage-tools { flex: 0 1 auto; }

.rc-stage .rc-icon {
  color: #fff;
  background: var(--rc-glass);
  border: 1px solid var(--rc-glass-line);
}
.rc-stage .rc-icon:hover { background: var(--rc-glass-2); }
.rc-stage .rc-icon.on,
.rc-stage .rc-icon[aria-pressed="true"] {
  background: #fff;
  border-color: #fff;
  color: var(--rc-brand-deep);
}

/* ── "Talk to a human" ───────────────────────────────────────────────────
   The label is 15 characters in English and 27 in German ("Mit einem
   Menschen sprechen"), and it flips to the visitor's language the moment the
   pending legal sign-off lands. So: no fixed width, no `nowrap`, and it is
   allowed to wrap and to shrink inside whatever the stage has left. Verified
   at 380 px and 320 px, expanded and collapsed. */
.rc-human {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
  padding: 7px 13px;
  border: 1px solid rgba(255, 255, 255, 0.36);
  border-radius: var(--rc-pill);
  background: var(--rc-glass);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  text-align: center;
  cursor: pointer;
  overflow-wrap: break-word;
  hyphens: auto;
  transition: background var(--rc-t-fast) var(--rc-ease),
              border-color var(--rc-t-fast) var(--rc-ease),
              transform var(--rc-t-fast) var(--rc-ease);
}
.rc-human:hover { background: var(--rc-glass-2); border-color: rgba(255, 255, 255, 0.6); }
.rc-human:active { transform: scale(.97); }
.rc-stage.compact .rc-human { font-size: 11px; padding: 5px 10px; }

/* ══ TRANSIENT BANNER ═════════════════════════════════════════════════════
   Offline and retry notices only. Conversation state belongs to the stage
   chip, so this is deliberately NOT styled as a persistent state bar. */
.rc-status {
  flex: none;
  padding: 9px var(--rc-pad);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--rc-warn);
  background: var(--rc-warn-bg);
  border-bottom: 1px solid var(--rc-warn-line);
}

/* ══ MESSAGES ═════════════════════════════════════════════════════════════ */
.rc-messages {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-behavior: smooth;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  padding: 16px var(--rc-pad) 10px;
  background: var(--rc-surface-2);
  scrollbar-width: thin;
  scrollbar-color: var(--rc-line-strong) transparent;
}
/* Chat convention: a short transcript sits ON the composer rather than
   floating in a half-empty panel. A zero-height flex item with `margin-top:
   auto` eats the free space and collapses to nothing once the list overflows,
   which `justify-content: flex-end` would not — that makes the top of an
   overflowing list unreachable. The negative bottom margin cancels the gap
   this pseudo-item would otherwise introduce. */
.rc-messages::before { content: ''; margin-top: auto; margin-bottom: -10px; }
.rc-messages::-webkit-scrollbar { width: 8px; }
.rc-messages::-webkit-scrollbar-thumb {
  background: var(--rc-line-strong);
  border-radius: var(--rc-pill);
  border: 2px solid transparent;
  background-clip: content-box;
}

.rc-msg {
  position: relative;
  /* Never let the scroll container squash a bubble. The list is a column flex
     container, so `height` is the MAIN size here and the default shrink
     factor flattened anything without intrinsic content — the thinking
     skeleton collapsed from 56 px to 22 px. */
  flex: none;
  max-width: 86%;
  padding: 10px 13px;
  border-radius: var(--rc-radius);
  font-size: 14.5px;
  line-height: 1.5;
  overflow-wrap: anywhere;
  animation: rc-in var(--rc-t-slow) var(--rc-ease) both;
}
.rc-msg.assistant,
.rc-msg.staff {
  align-self: flex-start;
  border-bottom-left-radius: 6px;
  background: var(--rc-surface);
  border: 1px solid var(--rc-line);
  box-shadow: var(--rc-shadow-1);
}
/* A person has taken over: tinted, with an accent edge. No hardcoded "Team"
   label — that string would be English on 21 language domains. The stage
   already relabels itself on handoff, and `data-label` is here so reception.js
   can add a translated one later without another stylesheet change. */
.rc-msg.staff {
  background: color-mix(in srgb, var(--rc-brand) 7%, var(--rc-surface));
  border-color: var(--rc-brand-ring);
}
.rc-msg.staff[data-label]::before {
  content: attr(data-label);
  display: block;
  margin-bottom: 3px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--rc-brand);
}
.rc-msg.visitor {
  align-self: flex-end;
  border-bottom-right-radius: 6px;
  background: var(--rc-bubble-visitor);
  color: var(--rc-on-brand);
  box-shadow: var(--rc-shadow-1);
}
.rc-msg.system {
  align-self: center;
  max-width: 92%;
  padding: 2px 8px;
  background: none;
  border: 0;
  box-shadow: none;
  font-size: 12.5px;
  text-align: center;
  color: var(--rc-ink-2);
}
.rc-msg.pending { opacity: .55; }

/* The renderer emits p / ul / li / strong / em / a. Style all of them or bold
   and bullets land unstyled. */
.rc-msg p { margin-top: 8px; }
.rc-msg p:first-child { margin-top: 0; }
.rc-msg ul { margin: 8px 0 0; padding-left: 18px; list-style: disc; }
.rc-msg ul:first-child { margin-top: 0; }
.rc-msg li { margin-top: 3px; }
.rc-msg li::marker { color: var(--rc-ink-3); }
.rc-msg.visitor li::marker { color: rgba(255, 255, 255, .7); }
.rc-msg strong { font-weight: 700; }
/* The self-hosted subset has no true italic, so this is a SYNTHESISED oblique.
   The shear leans the last glyph into the following space and visually closes
   it ("rooftop poolon site"); the trailing padding buys it back. */
.rc-msg em { font-style: italic; padding-inline-end: .08em; }
.rc-msg a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  overflow-wrap: anywhere;
}
.rc-msg a:hover { text-decoration-thickness: 2px; }

/* ── Per-message tools ───────────────────────────────────────────────────
   Read-aloud + copy, appended inside the bubble by reception.js. They keep
   their space at all times (no hover-driven reflow) and fade in on pointer
   devices. WCAG 2.2 SC 2.5.8 asks for 24×24; 36×36 is comfortable. */
.rc-msg-tools {
  display: flex;
  align-items: center;
  gap: 2px;
  /* Pulled tight against the bubble's bottom edge: a 36 px control row costs
     ~26 px of bubble height rather than ~45 px, so a one-line answer doesn't
     read as a card with a hole in it. */
  margin: -1px -7px -8px;
  opacity: 0;
  transition: opacity var(--rc-t-fast) var(--rc-ease);
}
.rc-msg:hover .rc-msg-tools,
.rc-msg:focus-within .rc-msg-tools { opacity: 1; }
/* A touch user cannot hover. */
@media (hover: none) {
  .rc-msg-tools { opacity: 1; }
}
.rc-msg:has(> .rc-msg-tools) { padding-bottom: 4px; }
.rc-msg-tools .rc-icon { color: var(--rc-ink-2); }
.rc-msg-tools .rc-icon:hover { color: var(--rc-ink); background: var(--rc-surface-3); }
/* Thumbs up/down (Task 17). Scoped to `.rc-thumb` specifically, not to every
   `.rc-msg-tools .rc-icon[aria-pressed]` — speak/copy never carry
   aria-pressed at all today, but scoping tightly means that stays true by
   construction rather than by coincidence if one of them ever does. Distinct
   from `.rc-stage .rc-icon[aria-pressed="true"]` (speak/sound, near-white
   glass chrome on the photographic stage) on purpose: this row sits on a
   plain bubble, where a filled-white pill would look like an error state,
   not a selection. */
.rc-msg-tools .rc-icon.rc-thumb[aria-pressed="true"] {
  color: var(--rc-brand);
  background: var(--rc-brand-tint);
}
.rc-msg-tools .rc-icon.rc-thumb[aria-pressed="true"]:hover { color: var(--rc-brand); }

/* ── Thinking skeleton ───────────────────────────────────────────────────
   Two grey bars under a sheen that moves on `transform` only. */
/* `.rc-msg.rc-skel`, not `.rc-skel`: reception.js gives this element
   `rc-msg assistant rc-skel`, and `.rc-msg.assistant` above would otherwise
   out-specify a bare `.rc-skel` and repaint the bars away. */
.rc-msg.rc-skel {
  width: 158px;
  height: 56px;
  overflow: hidden;
  /* Two bars of unequal length. Positions are padding-box relative. */
  background-image:
    linear-gradient(var(--rc-surface-3) 0 0),
    linear-gradient(var(--rc-surface-3) 0 0);
  background-repeat: no-repeat;
  background-size: 120px 10px, 78px 10px;
  background-position: 1px 5px, 1px 22px;
  background-color: var(--rc-surface);
}
.rc-msg.rc-skel::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 18%, color-mix(in srgb, var(--rc-brand) 14%, transparent) 50%, transparent 82%);
  transform: translate3d(-100%, 0, 0);
  animation: rc-sheen 1.35s var(--rc-ease) infinite;
}

/* ── Inline send failure ─────────────────────────────────────────────────── */
.rc-msg.rc-fail {
  align-self: flex-start;
  max-width: 86%;
  padding: 9px 12px;
  text-align: left;
  color: var(--rc-warn);
  background: var(--rc-warn-bg);
  border: 1px solid var(--rc-warn-line);
  border-radius: var(--rc-radius);
  font-size: 13px;
  font-weight: 500;
}
.rc-retry {
  display: inline-block;
  margin-left: 2px;
  padding: 3px 10px;
  border: 1px solid currentColor;
  border-radius: var(--rc-pill);
  color: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: background var(--rc-t-fast) var(--rc-ease), transform var(--rc-t-fast) var(--rc-ease);
}
.rc-retry:hover { background: color-mix(in srgb, currentColor 14%, transparent); }
.rc-retry:active { transform: scale(.97); }

/* ══ STARTERS ═════════════════════════════════════════════════════════════
   Opening suggestions. Sits on the message list's surface so the two read as
   one continuous panel. */
.rc-starters {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 2px var(--rc-pad) 12px;
  background: var(--rc-surface-2);
}
.rc-starter {
  padding: 8px 14px;
  border: 1px solid var(--rc-line);
  border-radius: var(--rc-pill);
  background: var(--rc-surface);
  color: var(--rc-ink);
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  box-shadow: var(--rc-shadow-1);
  transition: transform var(--rc-t-fast) var(--rc-ease),
              border-color var(--rc-t-fast) var(--rc-ease),
              background var(--rc-t-fast) var(--rc-ease);
}
.rc-starter:hover {
  border-color: var(--rc-brand-ring);
  background: color-mix(in srgb, var(--rc-brand) 6%, var(--rc-surface));
  transform: translate3d(0, -1px, 0);
}
.rc-starter:active { transform: scale(.97); }

/* ══ EMAIL CAPTURE ════════════════════════════════════════════════════════ */
.rc-email {
  flex: none;
  padding: 12px var(--rc-pad);
  border-top: 1px solid var(--rc-line);
  background: var(--rc-surface);
  font-size: 13px;
}
.rc-email label { display: block; font-weight: 600; color: var(--rc-ink); }
.rc-email-row { display: flex; gap: 8px; margin-top: 8px; }
.rc-email-row input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--rc-line-strong);
  border-radius: var(--rc-radius-sm);
  background: var(--rc-surface-2);
  color: var(--rc-ink);
  font-size: 16px; /* < 16px makes iOS Safari zoom the whole widget on focus. */
}
.rc-email-row button {
  flex: none;
  padding: 10px 16px;
  border-radius: var(--rc-radius-sm);
  background: var(--rc-brand);
  color: var(--rc-on-brand);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: transform var(--rc-t-fast) var(--rc-ease), filter var(--rc-t-fast) var(--rc-ease);
}
.rc-email-row button:hover { filter: brightness(1.1); }
.rc-email-row button:active { transform: scale(.97); }

/* ══ COMPOSER ═════════════════════════════════════════════════════════════ */
.rc-input {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--rc-line);
  background: var(--rc-surface);
}
/* `.rc-text` is the studio bench's hook: its composer is the same markup with
   cs-prefixed ids, so an id-only selector would leave the bench with an
   unstyled system text box in the one place staff are asked to judge the
   widget's looks. reception.html keeps the id; both selectors, one rule. */
.rc-input #rc-text,
.rc-input .rc-text {
  flex: 1 1 auto;
  min-width: 0;
  padding: 11px 15px;
  border: 1px solid var(--rc-line-strong);
  border-radius: var(--rc-pill);
  background: var(--rc-surface-2);
  color: var(--rc-ink);
  font-size: 16px; /* iOS zoom guard — see .rc-email-row input. */
  transition: border-color var(--rc-t-fast) var(--rc-ease), box-shadow var(--rc-t-fast) var(--rc-ease);
}
.rc-input #rc-text::placeholder,
.rc-input .rc-text::placeholder { color: var(--rc-ink-3); }
.rc-input #rc-text:focus,
.rc-input .rc-text:focus {
  outline: none;
  border-color: var(--rc-brand);
  box-shadow: 0 0 0 3px var(--rc-brand-ring);
}

/* ── Icon buttons ────────────────────────────────────────────────────────
   One shape everywhere: stage tools, composer, per-message tools. */
.rc-icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  color: var(--rc-ink-2);
  cursor: pointer;
  transition: background var(--rc-t-fast) var(--rc-ease),
              color var(--rc-t-fast) var(--rc-ease),
              transform var(--rc-t-fast) var(--rc-ease),
              opacity var(--rc-t-fast) var(--rc-ease);
}
.rc-icon:hover { background: var(--rc-surface-3); color: var(--rc-ink); }
.rc-icon:active { transform: scale(.97); }
.rc-icon:disabled { opacity: .42; cursor: not-allowed; }
.rc-icon:disabled:hover { background: none; }

/* One visible ring everywhere. No border-radius here — the outline already
   follows each control's own radius, and forcing one would square off the
   circular icon buttons. */
.rc-app :focus-visible {
  outline: 2px solid var(--rc-focus);
  outline-offset: 2px;
}

/* Send — the one filled control. */
.rc-input .rc-send {
  width: 40px;
  height: 40px;
  background: var(--rc-brand);
  color: var(--rc-on-brand);
  box-shadow: var(--rc-shadow-2);
}
.rc-input .rc-send:hover { background: var(--rc-brand); filter: brightness(1.12); }
.rc-input .rc-send:disabled { box-shadow: none; }
.rc-input .rc-send:disabled:hover { background: var(--rc-brand); filter: none; }

/* Mic — on-device speech only (D4 2026-07-06). It must never look like Send,
   or the listening state is invisible. */
.rc-input .rc-mic {
  width: 38px;
  height: 38px;
  border: 1px solid var(--rc-line-strong);
  background: var(--rc-surface);
}
.rc-input .rc-mic.installing { border-color: var(--rc-brand); }
/* Recording has to be unmistakable and must not rely on motion alone —
   hence the fill and ring, not just the pulse. */
.rc-input .rc-mic.listening {
  background: #c62828;
  border-color: #8e1f1f;
  color: #fff;
  box-shadow: 0 0 0 3px rgba(198, 40, 40, .26);
  animation: rc-listen 1.2s ease-in-out infinite;
}
.rc-input .rc-mic.listening:hover { background: #c62828; color: #fff; }

/* ══ FOOT MATTER ══════════════════════════════════════════════════════════ */
.rc-voice-note {
  flex: none;
  padding: 0 var(--rc-pad) 6px;
  font-size: 11.5px;
  color: var(--rc-ink-2);
  background: var(--rc-surface);
}
/* Article 50 disclosure. Small print, but it must stay legible — ink-2 clears
   AA on both surfaces; ink-3 does not. */
.rc-foot {
  flex: none;
  padding: 2px var(--rc-pad) 10px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--rc-ink-2);
  background: var(--rc-surface);
}

/* Honeypot: off-screen, never display:none (bots check computed display).
   The class hook is for the studio bench, whose copy of the composer carries
   cs-prefixed ids — without it the bench would render a visible stray text
   input where the visitor sees nothing. Scoped under `.rc-app`, unlike the
   id, so it stays inside the SCOPING CONTRACT. */
#rc-hp,
.rc-app .rc-hp { position: absolute; left: -9999px; top: -9999px; width: 1px; height: 1px; opacity: 0; }

/* ══ DEVICE FRAME ═════════════════════════════════════════════════════════
   The staff studio renders the widget at true size inside this. `.rc-app` is
   absolutely positioned (see its rule), so the frame has to be its containing
   block or the widget would size itself off the viewport instead. */
.rc-device {
  position: relative;
  border: 1px solid var(--rc-line, #e4e8f1);
  border-radius: var(--rc-radius-lg, 20px);
  overflow: hidden;
  background: var(--rc-surface, #fff);
  box-shadow: 0 18px 38px -18px rgba(20, 26, 50, .28);
  /* The ONE width/height transition in this file, and a deliberate exception
     to the MOTION rule in the header: this is staff-only bench chrome, not the
     widget, and it fires on a discrete Desktop/Mobile toggle whose whole point
     is that the frame changes ASPECT RATIO — which a transform cannot express.
     Nothing inside the widget animates a layout property. */
  transition: width var(--rc-t-slow, 300ms) cubic-bezier(.16, 1, .3, 1),
              height var(--rc-t-slow, 300ms) cubic-bezier(.16, 1, .3, 1);
}
.rc-device > .rc-app {
  position: absolute;
  inset: 0;
  height: 100%;
}

/* ══ MOTION ═══════════════════════════════════════════════════════════════ */
@keyframes rc-float {
  from { translate: 0 0; }
  to { translate: 0 -7px; }
}
@keyframes rc-in {
  from { opacity: 0; transform: translate3d(0, 9px, 0) scale(.985); }
  to { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
}
@keyframes rc-eq {
  from { transform: scaleY(.24); }
  to { transform: scaleY(var(--rc-eq-peak, 1)); }
}
@keyframes rc-sheen {
  from { transform: translate3d(-100%, 0, 0); }
  to { transform: translate3d(160%, 0, 0); }
}
@keyframes rc-listen {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.09); }
}

/* ══ MOBILE ═══════════════════════════════════════════════════════════════
   Below 480 px the widget is the whole sheet. 100dvh, never vh — vh does not
   shrink for iOS Safari's collapsing toolbar, so the composer ends up under
   it. */
@media (max-width: 480px) {
  .rc-app {
    height: 100dvh;
    padding-top: env(safe-area-inset-top, 0px);
    padding-right: env(safe-area-inset-right, 0px);
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--rc-kb, 0px));
    padding-left: env(safe-area-inset-left, 0px);
  }
}

/* Bubble width is the one mobile rule that is about the WIDGET's width rather
   than the device's, so it keys off the `rc` container (see `.rc-app`) — the
   380 px desktop embed panel and the full-sheet phone layout both get it, and
   so does the studio bench at either device size. The block above stays a
   viewport media query on purpose: `height: 100dvh` and the safe-area insets
   are properties of the real screen, and `.rc-device > .rc-app` overrides the
   height for the bench anyway. */
@container rc (max-width: 480px) {
  .rc-msg { max-width: 90%; }
}

/* Narrow frames: the stage gives the figure less room so the German label and
   the full-sentence chip strings still have somewhere to go. */
@media (max-width: 360px) {
  .rc-app { --rc-pad: 12px; }
  .rc-stage { padding-right: 96px; --rc-fig-scale: .82; --rc-fh: 271px; }
  .rc-stage.compact { padding-right: 56px; --rc-fig-scale: .394; --rc-fh: 130px; }
  .rc-stage.compact .rc-name { display: none; }
  .rc-name { font-size: 17px; }
}

/* ══ REDUCED MOTION ═══════════════════════════════════════════════════════
   Everything stops — the idle float and the emblem pulse included. The status
   chip still reads "Speaking" (reception-stage.js writes it regardless), so
   no information was ever carried by movement alone. Scoped to `.rc-app`:
   this file must not reach into the host page's motion settings. */
@media (prefers-reduced-motion: reduce) {
  .rc-app,
  .rc-app *,
  .rc-app *::before,
  .rc-app *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
