/* SELF-HOSTED FONTS (issue #13). The page used to link Google Fonts, which put a third party in the
   request path of a surface that holds a live microphone. Latin and Latin Extended only; Hebrew text
   falls back to the system stack, which is what it did before. Licences beside the files. */
@font-face{font-family:'Bricolage Grotesque';font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/bricolage-grotesque-latin-ext-600.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}
@font-face{font-family:'Bricolage Grotesque';font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/bricolage-grotesque-latin-600.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:'Bricolage Grotesque';font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/bricolage-grotesque-latin-ext-600.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}
@font-face{font-family:'Bricolage Grotesque';font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/bricolage-grotesque-latin-600.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:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/ibm-plex-sans-latin-ext-400.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}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/ibm-plex-sans-latin-400.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:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/ibm-plex-sans-latin-ext-400.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}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/ibm-plex-sans-latin-400.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:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/ibm-plex-sans-latin-ext-400.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}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/ibm-plex-sans-latin-400.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}

/* ── The call screen ──────────────────────────────────────────────────────
   A call app, laid out the way a learner already knows one: a tile per person
   and a control bar.

   WHAT IS OURS RATHER THAN BORROWED: colour means IDENTITY. There is no video
   for most of a call, so each teammate keeps one hue for the whole session and
   the speaking ring is drawn in it — colour is doing the job a face would.

   AND WHEN THERE IS A FACE, IT GOES IN THAT PERSON'S TILE. One face at a time
   is a cost rule (`wearFace`), and a tile grid states it without a word: one
   tile is playing video, the rest are showing their initial.

   Tile size is COMPUTED, not chosen — see `layoutTiles`. A fixed track cannot
   know the height it has left, so a fifth person used to start a row that fell
   off the bottom of the stage. */
/* THE PALETTE IS THE STUDIO'S, so a learner who arrives from an audition page does not cross a visible
   seam into a different product. The values come from UniTrail-Studio's
   `webapp/frontend/app/styles/unitrail-tokens.css`: brand blue 2E5BFF, cool slate neutrals, and its
   ink surfaces for dark. Only the token VALUES moved — every rule below still reads the same names.

   THE TYPEFACES DID NOT MOVE. The Studio pairs IBM Plex with itself; this page keeps Bricolage
   Grotesque for display, because the faces are self-hosted here (issue #13) and dropping one would
   orphan two woff2 files for a change nobody asked for. */
:root{
  --shell:#F7F9FC; --stage:#EEF2F8; --tile:#fff; --panel:#fff; --bar:#fff;
  --rule:#E1E7F0; --rule-2:#CBD4E1;
  --ink:#131C31; --ink-2:#4A5871; --ink-3:#66748C;
  --accent:#2E5BFF; --go:#12A150; --danger:#E0402E; --danger-ink:#fff;
  /* The scrim that keeps a name legible over a portrait. There is no `--glass` any more: the chrome
     was translucent so an ambient field could show through it, and that field is gone. */
  --scrim:linear-gradient(to top,rgba(7,11,22,.88) 0%,rgba(7,11,22,.55) 34%,transparent 72%);
  /* One hue per teammate, in the order the server lists them. Four steps of the Studio's own ramp
     that stay apart at a glance: brand blue, cyan, violet, amber. */
  --p1:#1E44E0; --p2:#0891B2; --p3:#6D46E8; --p4:#C4790A;
  /* THE CALL SCREEN'S OWN SURFACES, the same in both themes. A call is dark the way every call app is
     dark: video and a speaking ring read against ink, not against a white page. These are the
     Studio's ink steps, and they do not move when the learner flips the theme. */
  --ink-900:#0B1120; --ink-950:#070B16; --ink-tile:#141F38; --ink-tile-2:#0E1729;
  --on-ink:#F7F9FC; --on-ink-2:#CBD4E1; --on-ink-3:#98A5BC; --ink-line:rgba(255,255,255,.10);
  /* The Studio's electric cyan, used on the ink surfaces only: the joining ring and the speaking edge. */
  --cyan:#22D3EE;
  --display:'Bricolage Grotesque',ui-sans-serif,system-ui,sans-serif;
  --body:'IBM Plex Sans',ui-sans-serif,system-ui,sans-serif;
  --e-out:cubic-bezier(.23,1,.32,1);
  --e-back:cubic-bezier(.34,1.56,.64,1);
  --bar-h:80px;
}
:root[data-theme="dark"]{
  --shell:#0B1120; --stage:#070B16; --tile:#131C31; --panel:#131C31; --bar:#131C31;
  --rule:#22304E; --rule-2:#33405A;
  --ink:#F7F9FC; --ink-2:#CBD4E1; --ink-3:#98A5BC;
  /* The brand lifts a step on a dark ground: 2E5BFF against ink reads as a hole rather than a colour. */
  --accent:#6180FF; --go:#3FBF74; --danger:#EE6154; --danger-ink:#fff;
  --p1:#6180FF; --p2:#22D3EE; --p3:#9B8CF5; --p4:#F0A93A;
}
*{box-sizing:border-box;margin:0;padding:0}
/* ── SCROLLBARS, IN THE PAGE'S OWN COLOURS ──────────────────────────────────
   A default bar is a light grey slab on the ink panel and a dark one on the light room, which is the
   one piece of chrome that kept announcing it was not part of this product. Firefox takes the two
   `scrollbar-*` properties; WebKit takes the pseudo-elements. Both read the same tokens, so a surface
   that flips theme flips its bar with it, and the surfaces that are always ink get the ink pair. */
*{scrollbar-width:thin;scrollbar-color:var(--rule-2) transparent}
*::-webkit-scrollbar{width:10px;height:10px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:var(--rule-2);border-radius:999px;
  border:3px solid transparent;background-clip:content-box}
*::-webkit-scrollbar-thumb:hover{background:var(--ink-3);background-clip:content-box}
*::-webkit-scrollbar-corner{background:transparent}
/* The call screen and everything on it is ink in both themes, so its bars are too. */
.side,.script,.after,.after-script,.main{scrollbar-color:rgba(255,255,255,.22) transparent}
.side ::-webkit-scrollbar-thumb,.script::-webkit-scrollbar-thumb,
.after ::-webkit-scrollbar-thumb,.after-script::-webkit-scrollbar-thumb{
  background:rgba(255,255,255,.22);background-clip:content-box}
.side ::-webkit-scrollbar-thumb:hover,.script::-webkit-scrollbar-thumb:hover,
.after ::-webkit-scrollbar-thumb:hover,.after-script::-webkit-scrollbar-thumb:hover{
  background:rgba(255,255,255,.34);background-clip:content-box}
/* MUST outrank the class rules below: `.lobby{display:flex}` and `.main{display:grid}`
   are author rules and would otherwise beat the browser's own `[hidden]{display:none}`,
   leaving the lobby rendered underneath the call. */
[hidden]{display:none!important}
html,body{height:100%}
body{background:var(--shell);color:var(--ink);font-family:var(--body);font-size:16px;line-height:1.5;
  -webkit-font-smoothing:antialiased;overflow:hidden}
button{font-family:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ── THE FIELD BEHIND EVERYTHING ────────────────────────────────────────────
   Four layers, each doing a separate job, and none of them an asset — the page is still one file
   served from one route.

   `wash` is the base hue field, and it is the layer that means the screen is never flat black.
   The three `blob`s drift on 23 / 29 / 37 second loops: non-multiple durations, so the composite
   never repeats. `grain` is an inline feTurbulence at 5.5%, which is what hides the banding that
   big soft gradients always show on an 8-bit panel.

   FIXED AND BEHIND, not a body background-image: nothing on this page scrolls, and a fixed layer
   keeps drifting behind a countdown that is itself full-screen. */
/* FLAT, BECAUSE THE STUDIO IS FLAT. This used to be four layers: a colour wash, three blurred blobs
   drifting on 23/29/37 second loops, and a grain overlay. It was the page's own identity, and against
   the Studio it read as a different product — an author crossing from an audition page into a moving
   colour field notices the seam immediately. The layers are gone rather than re-tinted: the Studio's
   surfaces are a plain slate ground with white cards on it, and tinting a drifting blob in brand blue
   would still be a drifting blob.

   THE MARKUP STAYS. `.bg` is one element the page has always painted the ground with; its children are
   now inert and are removed in `call.html`. */
.bg{position:fixed;inset:0;z-index:-1;background:var(--shell)}

.app{height:100dvh;display:grid;grid-template-rows:56px 1fr var(--bar-h)}

/* ── top bar ────────────────────────────────────────────── */
/* SOLID, NOT GLASS. There is nothing behind the chrome to see through any more, and a blurred
   translucent bar over a flat ground is just a muddier flat ground. The Studio's chrome is a white
   surface on a hairline slate border; so is this. */
.head{display:flex;align-items:center;gap:14px;padding:0 16px;min-width:0;
  background:var(--bar);border-bottom:1px solid var(--rule)}
.brand{font:700 14px var(--display);letter-spacing:-.01em;white-space:nowrap}
.brand i{color:var(--accent);font-style:normal}
.sep{width:1px;height:20px;background:var(--rule);flex:none}
.callname{font:500 14px var(--body);color:var(--ink-2);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;min-width:0}
.callname b{color:var(--ink);font-weight:600}
.head-r{margin-left:auto;display:flex;align-items:center;gap:9px;flex:none}
.pill{display:inline-flex;align-items:center;gap:7px;background:transparent;border:1px solid var(--rule-2);
  color:var(--ink-2);border-radius:999px;padding:6px 13px;font:500 13px var(--body);cursor:pointer;
  transition:border-color .15s ease,color .15s ease}
.pill:hover{border-color:var(--accent);color:var(--accent)}
/* ON A CALL THE CHROME GOES DARK WITH THE STAGE. `showCall` puts the class on the body, because this
   bar is the one element both screens share and they want opposite treatments: the room follows the
   learner's theme, the call is ink in both. */
body.on-call .head{background:var(--ink-900);border-bottom-color:var(--ink-line)}
body.on-call .brand{color:var(--on-ink)}
body.on-call .sep{background:var(--ink-line)}
body.on-call .callname{color:var(--on-ink-2)}
body.on-call .callname b{color:var(--on-ink)}
body.on-call .pill{border-color:var(--ink-line);color:var(--on-ink-2)}
body.on-call .pill:hover{border-color:var(--on-ink-3);color:#fff}

/* ── lobby ──────────────────────────────────────────────── */
.lobby{grid-row:2/4;overflow-y:auto;display:flex;flex-direction:column;align-items:center;
  justify-content:center;padding:32px 24px;gap:24px}
.lobby h1{font:700 clamp(26px,4vw,36px) var(--display);letter-spacing:-.03em;text-wrap:balance;text-align:center}
.lobby p{color:var(--ink-2);max-width:52ch;text-align:center;margin-top:-14px}
/* ── THE TEAMMATE CARD ──────────────────────────────────────────────────────
   A FACE, not a coloured initial with a button under it. The portrait fills the card and the name
   sits on a scrim over the bottom of it, the way every contact card does — a caption bar under the
   image would eat a fifth of the picture for a line of 13px text.

   THE WHOLE CARD IS THE BUTTON. The round control bottom-right is the affordance, what a learner
   aims at; the click target is the card, so a near miss still starts the call. It is one `<button>`
   with everything inside it, which is also why the icon is `aria-hidden` and the label lives on the
   card — a screen reader gets one control saying "Video call Daniel, senior engineer".

   NO PORTRAIT IS A DESIGNED STATE, NOT A BROKEN IMAGE. `/v1/portraits/{avatar_id}` 404s on a
   deployment with no face vendor, on a vendor with no still, and on a fetch that simply failed. The
   initial on this teammate's identity hue is drawn UNDERNEATH the image in every case, so the
   fallback is what was already on screen rather than something that arrives after a blank.

   THE CARD IS CAPPED, NOT STRETCHED. A meeting has one teammate, and `auto-fit` drops the empty
   tracks, so an uncapped `1fr` stretched that one card to the grid's full 920px and, at 4:5, pushed
   the lobby past the fold. 296px is the width the cards had when three shared a row; it centres. */
.picks{display:grid;grid-template-columns:repeat(auto-fit,minmax(196px,296px));justify-content:center;gap:16px;width:100%;max-width:920px}
.pick{position:relative;aspect-ratio:4/5;border-radius:20px;overflow:hidden;border:0;padding:0;
  cursor:pointer;background:var(--hue);text-align:start;color:inherit;
  box-shadow:inset 0 0 0 1px var(--rule),0 4px 10px rgba(19,28,49,.07),0 12px 28px -18px rgba(19,28,49,.18);
  transition:transform .3s var(--e-out),box-shadow .3s var(--e-out);
  animation:card-in .5s var(--e-out) both}
@keyframes card-in{from{opacity:0;transform:translateY(16px) scale(.97)}to{opacity:1;transform:none}}
.pick:hover:not(:disabled){transform:translateY(-4px);
  box-shadow:inset 0 0 0 1px var(--rule-2),0 12px 28px rgba(19,28,49,.10),0 4px 10px rgba(19,28,49,.05)}
.pick:active:not(:disabled){transform:translateY(-2px) scale(.99)}
.pick .shot{position:absolute;inset:0;display:grid;place-items:center;overflow:hidden}
/* The initial is the ground; the portrait covers it when one arrives. */
.pick .init{font:700 clamp(48px,7vw,76px) var(--display);color:rgba(255,255,255,.92);letter-spacing:-.04em}
.pick .shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center 18%;
  display:block;transition:transform .55s var(--e-out),opacity .35s ease;will-change:transform}
.pick:hover:not(:disabled) .shot img{transform:scale(1.055)}
.pick .veil{position:absolute;inset:0;background:var(--scrim)}
.pick .who{position:absolute;left:16px;right:70px;bottom:16px;color:#fff;z-index:2}
.pick .who .nm{font:700 20px var(--display);letter-spacing:-.02em;display:block}
.pick .who .rl{font-size:12.5px;color:rgba(255,255,255,.75);display:block;margin-top:1px}
/* Shaped the way a phone shapes one, and it stays that shape: it lifts and its shadow brightens on
   hover, and that is the whole animation. Green for a voice call and the accent for video, so the
   button carries the mode the chooser above is set to — one less thing to check before pressing it. */
.pick .call{position:absolute;right:14px;bottom:14px;z-index:3;width:52px;height:52px;border-radius:50%;
  background:var(--go);color:#fff;display:grid;place-items:center;
  box-shadow:0 8px 22px -8px color-mix(in oklab,var(--go) 75%,transparent);
  transition:transform .22s var(--e-back),box-shadow .22s var(--e-out),filter .18s ease}
.pick.video .call{background:var(--accent);box-shadow:0 8px 22px -8px color-mix(in oklab,var(--accent) 75%,transparent)}
.pick .call svg{width:23px;height:23px}
.pick:hover:not(:disabled) .call{transform:scale(1.09);filter:brightness(1.08);
  box-shadow:0 12px 28px -8px color-mix(in oklab,var(--go) 85%,transparent)}
.pick.video:hover:not(:disabled) .call{box-shadow:0 12px 28px -8px color-mix(in oklab,var(--accent) 85%,transparent)}
.pick:disabled{cursor:default;filter:grayscale(.75) brightness(.68)}
.pick:disabled .call{background:var(--ink-3);box-shadow:none}
.foot{color:var(--ink-3);font-size:13px;max-width:56ch;text-align:center}

/* ── THE ROOM BEFORE THE CALL ───────────────────────────────────────────────
   Two columns: what the other side will see on the left, what the learner still has to decide on the
   right. The preview is the loud half on purpose — a self-view is the one thing here nobody has to be
   taught to read.

   THE LOBBY CENTRES ITS CHILD, so the room is one flex item and does its own layout inside. */
.room{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);gap:28px;align-items:center;
  width:100%;max-width:980px}
@media (max-width:820px){.room{grid-template-columns:1fr;gap:20px;max-width:520px}}

.pv{position:relative;border-radius:18px;overflow:hidden;background:var(--stage);aspect-ratio:4/3;
  display:grid;place-items:center;box-shadow:inset 0 0 0 1px var(--rule),0 4px 10px rgba(19,28,49,.07)}
/* Mirrored, like every other self-view, and `object-fit:cover` so a 16:9 camera fills a 4:3 frame. */
.pv video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:#000;
  opacity:0;transition:opacity .3s var(--e-out)}
.pv.live video{opacity:1}
.pv-off{position:relative;display:flex;flex-direction:column;align-items:center;gap:10px;color:var(--ink-3)}
.pv.live .pv-off{display:none}
.pv-ini{width:84px;height:84px;border-radius:50%;background:var(--rule);color:var(--ink-2);
  display:grid;place-items:center;font:700 26px var(--display);letter-spacing:-.02em}
.pv-said{font-size:13.5px}
.pv-bar{position:absolute;left:12px;right:12px;bottom:12px;z-index:2;display:flex;align-items:center;gap:10px}
.round{width:42px;height:42px;border-radius:50%;border:0;flex:none;cursor:pointer;display:grid;place-items:center;
  background:rgba(7,11,22,.58);color:#fff;backdrop-filter:blur(8px);
  transition:background .16s ease,transform .16s ease}
.round:hover{transform:translateY(-1px)}
.round svg{width:19px;height:19px}
.round[aria-pressed="false"]{background:var(--danger);color:var(--danger-ink)}
/* The meter is the answer to "can it hear me", so it is the widest thing in the row. */
.lvl{flex:1;height:6px;border-radius:999px;background:rgba(255,255,255,.26);overflow:hidden;
  box-shadow:inset 0 0 0 1px rgba(7,11,22,.18)}
.lvl i{display:block;height:100%;width:0;border-radius:999px;background:var(--go);transition:width .12s linear}

/* A STACK OF CARDS, LEFT-ALIGNED, which is what the mockup is: an eyebrow, who you are calling, why,
   your devices, and the button. Nothing is centred — a centred column of cards reads as a landing
   page, and this is a form the learner works down. */
.ready{display:flex;flex-direction:column;align-items:stretch;gap:12px;min-width:0;width:100%}
.eyebrow{font:600 11px var(--body);letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3)}
.ready .picks{display:block;max-width:none;width:100%}

/* THE TEAMMATE CARD, INSIDE THE ROOM ONLY. Everywhere else `.pick` is the tall 4:5 portrait the
   lobby has always used, and the countdown still draws its backdrop from the same portrait. Here it
   is a row: a square of their identity hue, their name, their role. Same element, same JavaScript. */
.room .pick{aspect-ratio:auto;display:flex;align-items:center;gap:13px;padding:12px 14px;
  border-radius:12px;background:var(--tile);box-shadow:inset 0 0 0 1px var(--rule);
  animation:none;transform:none;cursor:pointer}
.room .pick:hover:not(:disabled){transform:none;box-shadow:inset 0 0 0 1px var(--rule-2)}
.room .pick .shot{position:relative;width:46px;height:46px;flex:none;border-radius:10px;
  background:var(--hue);overflow:hidden}
.room .pick .init{font:700 20px var(--display);color:#fff;letter-spacing:-.02em}
.room .pick .veil,.room .pick .call{display:none}
.room .pick .who{position:static;left:auto;right:auto;bottom:auto;color:inherit;min-width:0}
.room .pick .who .nm{font:600 15px var(--body);letter-spacing:0;color:var(--ink)}
.room .pick .who .rl{font-size:13px;color:var(--ink-3);margin-top:0}

.brief{background:var(--tile);box-shadow:inset 0 0 0 1px var(--rule);border-radius:12px;
  padding:13px 15px;color:var(--ink-2);font-size:14px;line-height:1.5;margin:0;text-align:start}
.brief:empty{display:none}

.devices{display:flex;flex-direction:column;gap:10px;width:100%}
.dev{display:flex;flex-direction:column;gap:6px}
.dev-l{font:600 11px var(--body);letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3)}
/* The field carries its own status, the way the mockup reads: what the device is on the left, what it
   is doing on the right, inside one bordered row. */
.dev-f{display:flex;align-items:center;gap:10px;background:var(--tile);border-radius:10px;
  box-shadow:inset 0 0 0 1px var(--rule);padding:0 13px 0 3px}
.dev-f select{flex:1;min-width:0;font:inherit;font-size:14px;color:var(--ink);background:transparent;
  border:0;padding:11px 10px;cursor:pointer;text-overflow:ellipsis;appearance:none}
.dev-f select:focus{outline:none}
.dev-s{font-style:normal;font-size:12.5px;color:var(--ink-3);flex:none;white-space:nowrap}

.acts{display:flex;justify-content:flex-end;align-items:center;gap:14px;margin-top:2px}
.join{font:600 15px var(--body);background:var(--accent);color:#fff;border:0;border-radius:999px;
  padding:13px 28px;cursor:pointer;box-shadow:0 6px 20px rgba(46,91,255,.28);
  transition:filter .16s ease,transform .16s ease}
.join:hover{filter:brightness(1.06);transform:translateY(-1px)}
.join:active{transform:none}
.join:disabled{background:var(--rule-2);box-shadow:none;cursor:default}
.ready .foot{text-align:start;font-size:12.5px;max-width:none}


/* ── gauges (the bench sidebar, folded in) ──────────────────
   Numbers a learner does not need and the person judging a vendor cannot work without.
   Hidden until a call opens, so the lobby stays a lobby. */
.gauges{border-bottom:1px solid var(--ink-line);padding:12px 16px}
.gauges dl{display:grid;grid-template-columns:1fr auto;gap:5px 12px;font-size:12.5px;margin:0}
.gauges dt{color:var(--on-ink-3)}
.gauges dd{margin:0;font-variant-numeric:tabular-nums;font-weight:600;color:var(--on-ink-2)}
.gauges dd.warn{color:var(--danger)}

/* ── stage ──────────────────────────────────────────────────────────────────
   THE CALL SCREEN IS DARK IN BOTH THEMES, which is what the mockup draws and what every call app
   does: video and a speaking ring read against ink, not against a white page. The room around it
   still follows the learner's theme — this is the one surface that does not. */
.main{display:grid;grid-template-columns:minmax(0,1fr) min(33%,420px);min-height:0;
  background:var(--ink-950,#070B16)}
.main .stage{background:transparent}
.main.solo{grid-template-columns:1fr}
@media (max-width:900px){.main{grid-template-columns:1fr}.side{display:none}}
/* `min-height:0` is load-bearing: a grid item defaults to min-height:auto, so the
   stage would grow to whatever its tiles need, push past the row it was given, and
   `layoutTiles` would then measure a stage taller than the window. */
.stage{position:relative;min-width:0;min-height:0;padding:14px;display:flex;align-items:center;
  justify-content:center;--caps-reserve:0px;padding-bottom:calc(14px + var(--caps-reserve))}
.stage.hascaps{--caps-reserve:92px}
.grid{display:grid;gap:12px;width:100%;height:100%;min-height:0;overflow:hidden;
  grid-template-columns:repeat(var(--cols,2),var(--tile-w,1fr));
  justify-content:center;align-content:center}

.tile{position:relative;border-radius:16px;overflow:hidden;display:grid;
  place-items:center;width:var(--tile-w,100%);aspect-ratio:16/10;min-width:0;min-height:0;
  background:var(--ink-tile);box-shadow:inset 0 0 0 1px var(--ink-line);
  transition:box-shadow .24s var(--e-out);animation:tile-in .3s var(--e-out) both}
@keyframes tile-in{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:none}}
.tile.talking{box-shadow:inset 0 0 0 2px var(--hue),0 20px 44px -26px color-mix(in oklab,var(--hue) 55%,transparent)}
.tile .avwrap{position:relative;display:grid;place-items:center}
.tile canvas{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:calc(var(--av) * 2.6);height:calc(var(--av) * 2.6);pointer-events:none}
.tile .av{position:relative;width:var(--av);height:var(--av);border-radius:50%;background:var(--hue);
  display:grid;place-items:center;color:#fff;font:700 calc(var(--av) * .36) var(--display);
  letter-spacing:-.02em;transition:transform .3s var(--e-back);will-change:transform}
.tile.talking .av{transform:scale(1.07)}
.tile.thinking .av{animation:ponder 1.5s ease-in-out infinite}
@keyframes ponder{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}
/* The face fills the tile it belongs to and covers the avatar behind it.

   HIDDEN WITH `opacity`, NOT `display`. A `display:none` video is not guaranteed to decode, and the
   first painted frame is exactly the signal `showFace` waits for before swapping the avatar out —
   hiding it the other way would mean waiting for an event that never comes. It also means the face
   fades in over the avatar rather than replacing it in one frame. */
.tile video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:#000;
  opacity:0;transition:opacity .28s var(--e-out)}
/* A self-view that does not mirror reads as somebody else's camera. */
.tile video.mirror{transform:scaleX(-1)}
.tile.faced video{opacity:1}
.tile.faced .avwrap{opacity:0;transition:opacity .28s var(--e-out)}
.tile .label{position:absolute;left:10px;bottom:10px;display:flex;align-items:center;gap:7px;
  background:rgba(7,11,22,.62);backdrop-filter:blur(6px);color:var(--on-ink);
  border-radius:999px;padding:5px 11px;font:500 13px var(--body);max-width:calc(100% - 20px);z-index:2}
.tile.faced .label{background:rgba(0,0,0,.55);color:#fff}
.tile .label .nm{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tile .label .mic{width:14px;height:14px;flex:none;color:var(--on-ink-3)}
.tile.faced .label .mic{color:#fff}
.tile.talking .label .mic{color:var(--hue)}
.tile .badge{position:absolute;right:10px;top:10px;font:500 11.5px var(--body);letter-spacing:.06em;
  text-transform:uppercase;color:var(--hue);background:color-mix(in oklab,var(--hue) 16%,transparent);
  border-radius:6px;padding:3px 8px;opacity:0;transition:opacity .2s ease;z-index:2}
.tile.thinking .badge,.tile.checking .badge{opacity:1}
/* Your own tile is the one that is not a teammate, so it sits a step back from them. */
/* YOUR OWN TILE IS A CORNER, NOT A CELL. The mockup puts the teammate on the whole stage and the
   learner in a picture-in-picture over it, which is what every call app does once there is exactly
   one other person: an equal-sized self-view spends half the screen on the one face nobody is
   talking to. `layoutTiles` skips it when counting, or the teammate would be sized for two. */
.tile.you{position:absolute;right:16px;bottom:16px;z-index:4;
  width:clamp(128px,20%,196px);aspect-ratio:16/10;background:var(--ink-tile-2)}
.stage.hascaps .tile.you{bottom:calc(16px + var(--caps-reserve))}
@media (max-width:640px){.tile.you{width:112px;right:12px;bottom:12px}}
/* Not `--ink-3`: that is a dark slate in the light theme, and this avatar sits on the inked stage
   in both. The call screen's own neutrals are the only ones safe here. */
/* Not `--ink-3`: that is a dark slate in the light theme, and this avatar sits on the inked stage in
   both. The call screen's own neutrals are the only ones safe here. */
.tile.you .av{background:#33405A;color:var(--on-ink)}

/* captions over the stage: two lines, so they never cover the people */
.caps{position:absolute;left:50%;bottom:16px;transform:translateX(-50%);width:min(720px,calc(100% - 36px));
  display:flex;flex-direction:column;gap:6px;pointer-events:none;z-index:5}
.caps .cap{background:rgba(0,0,0,.78);color:#fff;border-radius:10px;padding:9px 14px;font-size:16px;
  line-height:1.45;animation:cap-in .26s var(--e-out) both}
@keyframes cap-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.caps .cap b{color:var(--capHue,#fff);font-weight:600;margin-right:8px}
.caps .cap[dir="rtl"]{direction:rtl}
.caps.off{display:none}

/* ── side panel: the record ─────────────────────────────── */
.side{background:var(--ink-900);border-left:1px solid var(--ink-line);
  display:flex;flex-direction:column;min-height:0;min-width:0}
.side-h{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--ink-line)}
.side-h h2{font:600 14px var(--body);letter-spacing:-.01em;color:var(--on-ink)}
.side-h .x{margin-left:auto;background:transparent;border:0;color:var(--on-ink-3);cursor:pointer;
  border-radius:7px;padding:4px;display:grid;place-items:center}
.side-h .x:hover{background:rgba(255,255,255,.1);color:#fff}
.side-h .x svg{width:17px;height:17px}
.script{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:14px;min-height:0}
.turn{display:grid;grid-template-columns:3px 1fr;gap:11px;animation:cap-in .24s var(--e-out) both}
.turn i{background:var(--hue);border-radius:2px}
.turn.mine i{background:var(--on-ink-3)}
.turn .who{font:600 12px var(--body);letter-spacing:.05em;text-transform:uppercase;color:var(--hue);margin-bottom:2px}
.turn.mine .who{color:var(--on-ink-3)}
.turn .said{font-size:14.5px;color:var(--on-ink-2);line-height:1.5}
.turn[dir="rtl"]{direction:rtl}
.blank{margin:auto;text-align:center;color:var(--on-ink-3);font-size:14px;padding:0 10px}

/* ── control bar ────────────────────────────────────────── */
.bar{display:flex;align-items:center;justify-content:center;gap:10px;padding:0 16px;position:relative;
  background:var(--ink-900);border-top:1px solid var(--ink-line)}
.bar .left{position:absolute;left:16px;font:500 13.5px var(--body);color:var(--on-ink-3);
  font-variant-numeric:tabular-nums;display:flex;align-items:center;gap:8px}
.bar .left .rec{width:8px;height:8px;border-radius:50%;background:var(--danger);animation:rec 1.8s ease-in-out infinite}
@keyframes rec{0%,100%{opacity:1}50%{opacity:.25}}
.bar .right{position:absolute;right:16px;display:flex;align-items:center;gap:8px}
@media (max-width:820px){.bar .left,.bar .right{display:none}}
.ctl{width:48px;height:48px;border-radius:50%;border:1px solid var(--ink-line);
  background:rgba(255,255,255,.08);color:#fff;cursor:pointer;display:grid;place-items:center;
  transition:background .16s ease,border-color .16s ease,transform .16s ease}
.ctl:hover{background:rgba(255,255,255,.16);transform:translateY(-1px)}
.ctl svg{width:21px;height:21px}
.ctl[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:#fff}
.ctl.muted{background:var(--danger);border-color:var(--danger);color:var(--danger-ink)}
.ctl.end{width:62px;border-radius:24px;background:var(--danger);border-color:var(--danger);color:var(--danger-ink)}
.ctl.end:hover{background:var(--danger);filter:brightness(1.08)}
.ctl-wrap{position:relative;display:grid;place-items:center}
/* The tooltip sits over the inked bar in both themes, so it is inverted against the STAGE rather than
   against the page: `--ink`/`--shell` would be dark-on-dark whenever the learner is in light mode. */
.ctl-wrap .tipl{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);
  background:var(--on-ink);color:var(--ink-950);font:500 12px var(--body);padding:4px 9px;border-radius:6px;
  white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .15s ease}
.ctl-wrap:hover .tipl{opacity:1}
.unmute{font:600 13.5px var(--body);background:var(--accent);color:#fff;border:0;border-radius:999px;
  padding:9px 16px;cursor:pointer;white-space:nowrap}

/* ── THE PRE-CALL COUNTDOWN ─────────────────────────────────────────────────
   Three seconds of cover over a join that has nowhere natural to show progress. Not decoration: the
   two things it hides are the microphone permission prompt and a face vendor taking two to four
   seconds to render a first frame, and both of those used to happen against an empty stage saying
   "Connecting…".

   IT NAMES WHAT IT IS DOING. Three lines tick off underneath in the order `join` performs them, so
   a countdown that overruns reads as work still happening rather than as a hung page. */
.count{position:fixed;inset:0;z-index:60;display:grid;place-items:center;
  opacity:0;visibility:hidden;transition:opacity .42s ease,visibility .42s}
.count.on{opacity:1;visibility:visible}
/* INK, NOT A BLURRED PORTRAIT. The joining screen is the same surface as the call it opens onto, so
   the two read as one screen rather than a colour field handing over to a dark stage. `openCountdown`
   still writes the portrait into this element; it simply sits behind an opaque ground now, which
   keeps that code honest without making the screen about it. */
.count .backdrop{position:absolute;inset:0;overflow:hidden;background:var(--ink-950)}
.count .backdrop img{display:none}
.count .inner{position:relative;display:flex;flex-direction:column;align-items:center;gap:22px;
  animation:cap-in .45s var(--e-out) both}
.count .to{font:500 13px var(--body);letter-spacing:.2em;text-transform:uppercase;color:rgba(255,255,255,.6)}
.count .to b{color:#fff;font-weight:600}
.cnum{position:relative;width:190px;height:190px;display:grid;place-items:center}
.cnum svg.ring{position:absolute;inset:0;transform:rotate(-90deg)}
.cnum .trk{stroke:var(--ink-line)}
/* Cyan, the way the mockup draws it: the one accent on a screen that is otherwise ink and white, so
   the eye goes to the thing that is actually progressing. */
.cnum .bar{stroke:var(--cyan);stroke-linecap:round;transition:stroke-dashoffset 1s linear}
/* TWO ELEMENTS PER NUMBER, NEVER ONE. Replaying a keyframe on the same node means the old digit is
   gone the instant the new one starts — nothing to hand over from, which reads as a jump. The
   outgoing digit swells and blurs away while the incoming one arrives out of a blur beneath it, and
   the timings overlap so the two motions read as one. */
.cnum b{position:absolute;inset:0;display:grid;place-items:center;
  font:800 88px/1 var(--display);color:#fff;font-variant-numeric:tabular-nums;
  animation:num-in .5s cubic-bezier(.22,1,.36,1) both}
.cnum b.leave{animation:num-out .58s cubic-bezier(.45,0,.9,.6) both}
@keyframes num-in{from{opacity:0;transform:scale(.55);filter:blur(12px)}to{opacity:1;transform:scale(1);filter:blur(0)}}
@keyframes num-out{from{opacity:1;transform:scale(1);filter:blur(0)}to{opacity:0;transform:scale(1.5);filter:blur(16px)}}
/* WHEN THE JOIN OUTRUNS THE COUNT. The digits are spent and the line is not open: the ring turns on
   its own rather than the number sitting at nothing, which is the difference between "still
   working" and "stuck". The spin goes on the whole <svg> — a CSS transform on an SVG child rotates
   about its own origin, not the ring's centre. */
.cnum.over .bar{transition:none}
.cnum.over svg.ring{animation:cspin 1.1s linear infinite}
@keyframes cspin{from{transform:rotate(-90deg)}to{transform:rotate(270deg)}}
.cnum .dot{display:none;width:12px;height:12px;border-radius:50%;background:#fff;animation:cbob 1.1s ease-in-out infinite}
.cnum.over .dot{display:block}
@keyframes cbob{0%,100%{opacity:.35;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}
.steps{display:flex;flex-direction:column;gap:9px;min-width:270px}
.step{display:flex;align-items:center;gap:11px;font:500 14px var(--body);color:var(--on-ink-3);
  transition:color .3s ease}
.step.now{color:var(--on-ink)}
.step.done{color:var(--on-ink-2)}
.step .tick{width:18px;height:18px;border-radius:50%;flex:none;display:grid;place-items:center;
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.3);transition:box-shadow .3s ease,background .3s ease}
.step.done .tick{background:var(--go);box-shadow:none}
.step.done .tick::after{content:"";width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;
  transform:rotate(-45deg) translate(1px,-1px)}
.step.now .tick{box-shadow:inset 0 0 0 2px #fff;animation:cpulse 1.3s ease-in-out infinite}
@keyframes cpulse{0%,100%{transform:scale(1)}50%{transform:scale(1.22)}}
.count .bail{background:transparent;border:0;color:rgba(255,255,255,.55);font:500 13.5px var(--body);
  cursor:pointer;padding:8px 14px;border-radius:999px;transition:color .16s ease,background .16s ease}
.count .bail:hover{color:#fff;background:rgba(255,255,255,.1)}

/* ── AFTER THE CALL ─────────────────────────────────────────────────────────
   On the stage's own ink, because it replaces the call rather than the room: hanging up should not
   flash the light room behind a summary of what just happened. */
/* UNDER THE NOTICE, NOT OVER IT. `.msg` is z-index 40 and carries the sentence saying why a call
   could not start; a summary card on top of it told the learner the call had ended instead. */
.after{position:fixed;inset:0;z-index:35;display:grid;place-items:center;padding:24px;
  background:var(--ink-950);animation:cap-in .3s var(--e-out) both}
.after[hidden]{display:none!important}
.after-card{width:min(520px,100%);display:flex;flex-direction:column;gap:16px;
  background:var(--ink-900);border-radius:18px;padding:24px;
  box-shadow:inset 0 0 0 1px var(--ink-line),0 24px 56px rgba(0,0,0,.5)}
.after-card .eyebrow{color:var(--on-ink-3)}
.after-card h2{font:700 22px var(--display);letter-spacing:-.02em;color:var(--on-ink)}
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.stat{display:flex;flex-direction:column;gap:3px;background:var(--ink-tile);border-radius:10px;padding:12px}
.stat b{font:600 18px var(--body);color:var(--on-ink);font-variant-numeric:tabular-nums}
.stat span{font:600 11px var(--body);letter-spacing:.09em;text-transform:uppercase;color:var(--on-ink-3)}
/* The turns keep the styles they had in the panel — they are the same nodes, moved. */
.after-script{background:var(--ink-tile);border-radius:10px;padding:14px;max-height:190px;overflow-y:auto;
  display:flex;flex-direction:column;gap:12px}
.after-script:empty{display:none}
.after-script .turn .said{color:var(--on-ink-2)}

/* ── notice ─────────────────────────────────────────────── */
.msg{position:fixed;left:50%;top:70px;transform:translateX(-50%);z-index:40;display:none;
  background:var(--panel);border:1px solid var(--rule-2);border-left:3px solid var(--accent);
  border-radius:12px;padding:16px 20px;max-width:min(520px,calc(100vw - 32px));
  box-shadow:0 20px 44px -22px rgba(0,0,0,.5)}
/* ITS OWN KEYFRAMES, and the reason is the bug this fixes. `cap-in` was written for the
   captions, which flexbox centres, so it ends at `transform:none` — and `both` makes that
   final value STICK. Borrowed here it wiped the `translateX(-50%)` that does the centring, so
   the box settled with its left edge at 50% and read as pinned to the right. Any animation on
   a transform-centred element has to carry the centring through every frame. */
@keyframes msg-in{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
.msg.on{display:block;animation:msg-in .24s var(--e-out) both}
.msg.bad{border-left-color:var(--danger)}
.msg h2{font:700 16px var(--display);letter-spacing:-.01em;margin-bottom:4px}
.msg.bad h2{color:var(--danger)}
.msg p{color:var(--ink-2);font-size:14.5px}
.msg code{background:var(--stage);padding:2px 6px;border-radius:4px;font-size:13px;word-break:break-all}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important}
}
