/* Whyzit, the shipped front end.
 *
 * The screens are the four the owner picked from
 * docs/design/whyzit-feed-options.html: Home C, Playing F, Expanded H and
 * Beat late L. C, F and L come from the Bare player. H comes from Chat
 * forward, so the expanded sheet is the chat thread.
 *
 * Every color, shadow, radius and typeface comes from soft-daylight.css.
 * This file adds shape only. It adds no color literal that is not white,
 * black or one of that file's own tokens.
 *
 * Four rulings from the owner are built in here rather than in the mockups:
 *   1. The Playing screen carries no proposal container.
 *   2. Captions sit straight on the picture, in the films' own treatment.
 *      No plate sits behind them.
 *   3. The beat-late message reads "Writing the next part" and nothing more.
 *   4. No screen shows money.
 *
 * The mockups were measured on a 390 by 844 phone that drew its own status
 * bar and home indicator. A real app has neither, so the numbers below sit
 * on the safe-area insets instead. On a browser with no insets the player's
 * bottom stack lands 34 px lower than the mockup, which is the height of
 * the home indicator the mockup was clearing.
 */

:root {
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);

  /* The stage height in pixels, written by app.js on every resize. The
     caption is sized from it, because the films burn their captions at a
     fixed share of an 832 px frame. */
  --stage-h: 844px;

  /* The shape every film is made at. apps/api/src/session.ts:69 asks fal for
     9:16 and gets 480 by 832 back. The stage is built from these, so a change
     there is a change in one place here. */
  --film-w: 480;
  --film-h: 832;

  /* The caption, matched to the films. scripts/iron-video/round2.py draws
     every caption with drawtext at fontsize 28 on the 480 by 832 frame,
     white, shadowcolor black@0.85 offset 2 across and 3 down, centered,
     with the block's bottom 78 px above the frame edge and a line height
     of 1.29. Those five numbers are read off that file and scaled by the
     stage's own height, so the live caption and the film caption are the
     same size on the same glass. */
  --cap-fs: calc(var(--stage-h) * 28 / 832);
  --cap-shadow-x: calc(var(--stage-h) * 2 / 832);
  --cap-shadow-y: calc(var(--stage-h) * 3 / 832);
}

html, body { overscroll-behavior: none; }

/* soft-daylight.css sizes .phone for a mockup frame. A shipped app fills
   the device instead. */
.phone {
  width: 100%;
  height: 100dvh;
  max-width: 100%;
}

/* DESKTOP. Every screen here is drawn for one portrait column and the films
   are 480 by 832, so a wide window gets that same column in the middle
   rather than a home screen stretched across a monitor. The column is the
   film's shape, so on a desktop the film fills it with nothing left over. */
@media (min-width: 620px) {
  body { background: #E4E7EF; }
  .phone {
    width: min(100vw, calc(100dvh * var(--film-w) / var(--film-h)));
    box-shadow: 0 2px 6px rgba(20, 22, 45, .10), 0 30px 60px -30px rgba(20, 22, 45, .45);
  }
}

.view { position: absolute; inset: 0; display: none; }
html[data-state="home"]     .v-home   { display: flex; flex-direction: column; }
html[data-state="settings"] .v-settings { display: flex; flex-direction: column; }

html[data-state="playing"]  .v-player { display: block; }
html[data-state="expanded"] .v-player { display: block; }
html[data-state="late"]     .v-player { display: block; }
/* The two quiet screens a film's own address shows before the film plays. */
html[data-state="looking"]   .v-film { display: flex; }
html[data-state="preparing"] .v-film { display: flex; }

/* =========================================================
   THE FILM LOOKUP. What /f/{id} shows while it asks the
   server which state the film is in.
   ========================================================= */

.v-film {
  background: var(--base);
  flex-direction: column; align-items: center; justify-content: center; gap: 22px;
  padding: 0 20px;
}
.filmwrap { display: flex; flex-direction: column; align-items: center; gap: 18px; }
.filmline {
  margin: 0; display: flex; align-items: center; gap: 10px;
  font-size: 15px; font-weight: 600; letter-spacing: -.006em; color: var(--slate);
}
.filmline .dot {
  flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent);
  animation: breathe 1.5s ease-in-out infinite;
}

/* =========================================================
   HOME. Panel C.
   ========================================================= */

/* The ground: the Liquid look at blue 15 from docs/design/home-glass-options.html.
   A faint sky at the top that reaches the page color by the foot, a field of
   the deep blue behind the ask box (31 percent down) and a paler shape behind
   the rows (60 percent down), so the glass containers have color to show
   through. Neither view scrolls, their lists do, so the shapes hold still
   under the boxes. The settings screen stands on the same ground, so the two
   read as one product. */
:root {
  --ground:
    radial-gradient(90% 30% at 50% 31%, rgba(21, 94, 239, .054), rgba(21, 94, 239, 0) 70%),
    radial-gradient(80% 20% at 30% 60%, rgba(133, 173, 247, .112), rgba(133, 173, 247, 0) 70%),
    linear-gradient(180deg, #EBEFF9 0%, #EFF1F9 50%, var(--base) 100%);
}
.v-home, .v-settings { background: var(--ground); }
.v-home .content {
  padding: calc(var(--safe-top) + 14px) 20px calc(var(--safe-bottom) + 18px);
}

.topbar {
  flex: 0 0 auto; height: 44px; margin-bottom: 16px;
  display: flex; align-items: center; justify-content: space-between;
}
.wordmark { margin: 0; }
/* The real mark: four rounded squares in a blue ramp, turned ten degrees.
   soft-daylight.css draws a filled blue square under this same class, so every
   part of that placeholder is turned off here. The artwork is 380 by 339, so
   the height sets the size and the width follows it. */
.wordmark .mark {
  width: auto; height: 28px;
  border-radius: 0; background: none; box-shadow: none;
  display: block;
}
/* Sign in, and who is signed in. Both sit in the top bar beside the new chat
   button, and only one is ever shown. Google asks that its mark keeps its own
   colors, so the icon is the one place on this screen that does not take a
   token from the stylesheet. */
.signin {
  height: 44px; padding: 0 14px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--card); box-shadow: var(--sh-card); color: var(--ink);
  font: inherit; font-weight: 600; font-size: 14px;
}
.signin:disabled { opacity: .55; }

/* THE SIGN IN SHEET.
   A dialog of our own, opened from the header button. Email and password are
   the first way in and Google sits under a rule as the second, so no other
   company's logo is the first thing a visitor sees about signing in. Three
   modes on data-mode: in, up and forgot. The name is asked for on sign up
   only, and the password is not asked for on forgot. */
.authsheet {
  width: min(100% - 32px, 393px); max-height: calc(100dvh - 32px);
  margin: auto; padding: 22px 20px calc(var(--safe-bottom) + 20px); border: 0;
  border-radius: var(--r-lg); background: var(--base); color: var(--ink);
  box-shadow: 0 2px 6px rgba(20, 22, 45, .10), 0 30px 60px -30px rgba(20, 22, 45, .45);
}
.authsheet::backdrop { background: rgba(22, 23, 43, .42); }
.authhead { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
/* Its own class, because "mark" is the 28 px accent disc in soft-daylight.css
   and an img under that name renders on a navy square. */
.authmark { display: block; height: 26px; width: auto; }
.authclose {
  width: 44px; height: 44px; margin-right: -10px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center;
  background: none; color: var(--slate);
}
.authtitle {
  margin: 0 0 4px; font-family: var(--display); font-size: 26px; font-weight: 700;
  letter-spacing: -.02em; color: var(--ink);
}
.authnote { margin: 0 0 18px; font-size: 14px; line-height: 1.5; color: var(--slate); }

.authfield { display: block; margin-bottom: 10px; }
.authfield span {
  display: block; margin: 0 2px 6px; font-size: 12px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--faint);
}
.authfield input {
  width: 100%; height: 52px; padding: 0 16px; border: 0; outline: 0;
  border-radius: var(--r-sm); background: var(--card); box-shadow: var(--sh-card);
  font: inherit; font-size: 16px; font-weight: 500; color: var(--ink);
}
.authfield input:focus-visible { box-shadow: var(--sh-card), 0 0 0 2px #155EEF; }
/* The name lives on sign up only, and the password leaves on forgot. A
   display rule beats the hidden attribute, so both are display rules. */
.authsheet:not([data-mode="up"]) .authfield.name { display: none; }
.authsheet[data-mode="forgot"] .authfield.password { display: none; }
.authsheet[data-mode="forgot"] .authforgot { display: none; }

.authgo {
  width: 100%; min-height: 52px; margin-top: 6px; border-radius: var(--r-sm);
  background: #155EEF; color: #FFFFFF; font-size: 16px; font-weight: 700;
  box-shadow: 0 10px 24px -12px rgba(21, 94, 239, .7);
}
.authgo:disabled { opacity: .5; }
.authfail { margin: 10px 2px 0; font-size: 13.5px; font-weight: 600; color: #B4344A; }
.authfail:empty { display: none; }

/* The two lines under the button, stacked. Side by side at phone width they
   each broke mid phrase, "Sign / in" and "Forgot / password?", which reads
   as two mistakes rather than two choices. Each keeps a 44 px press. */
.authlinks { display: flex; flex-direction: column; align-items: flex-start; gap: 0; margin: 10px 2px 0; }
.authlinks button {
  min-height: 44px; padding: 0; background: none; white-space: nowrap;
  font-size: 13.5px; font-weight: 600; color: var(--slate); text-align: left;
}

/* Google, second, under a rule. The G is drawn here and nowhere else in the
   product, so it is a choice a visitor makes rather than the face of the
   sign in. */
.authrule {
  display: flex; align-items: center; gap: 12px; margin: 18px 0 12px;
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--faint);
}
.authrule::before, .authrule::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--line, #E2E7F2); }
.authgoogle {
  width: 100%; min-height: 52px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  background: var(--card); box-shadow: var(--sh-card); color: var(--ink);
  font-size: 15px; font-weight: 600;
}
.authgoogle:disabled { opacity: .55; }
/* Accounts are not merged, and this says so where the choice is made. */
.authaside { margin: 12px 2px 0; font-size: 12.5px; line-height: 1.5; color: var(--faint); text-align: center; }
/* A display rule beats the hidden attribute, so hidden is restated here. The
   sign out control was visible to signed out visitors without this. */
/* A message that something worked, centred low over the picture so it never
   covers the captions or the rail. */
.toast {
  position: absolute; left: 50%; bottom: 96px; transform: translateX(-50%);
  z-index: 6; padding: 10px 16px; border-radius: var(--r-sm);
  background: rgba(22, 23, 43, .88); color: #fff;
  font-size: 14px; font-weight: 600; white-space: nowrap;
  animation: toast-in .16s ease-out;
}
.toast[hidden] { display: none; }
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 6px); } }

.who[hidden], .signin[hidden] { display: none; }
.who { display: inline-flex; align-items: center; gap: 10px; }
/* The top bar spreads its two ends apart, so everything on the right travels
   together in one group. */
.topbar-right { display: inline-flex; align-items: center; gap: 8px; }
.whoname {
  font-size: 14px; font-weight: 600; color: var(--ink);
  max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.signout {
  height: 44px; padding: 0 12px; border-radius: var(--r-sm);
  background: none; color: var(--slate); font: inherit; font-size: 14px;
}

.newchat {
  width: 44px; height: 44px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--card); box-shadow: var(--sh-card); color: var(--accent);
}
.v-home .headline { font-size: 33px; margin-bottom: 18px; max-width: 300px; }

/* A film that never finished, said in one line above the ask box. A display
   rule beats the hidden attribute, so hidden is restated here. */
.gonenote {
  flex: 0 0 auto; margin: -6px 0 14px;
  padding: 12px 15px; border-radius: var(--r-sm);
  background: var(--accent-tint); color: var(--accent-deep);
  font-size: 14px; font-weight: 600; line-height: 1.35; letter-spacing: -.006em;
}
.gonenote[hidden] { display: none; }

/* THE HOLD LOOK, from docs/design/app-home-options.html.
   One raised surface holds the whole act of asking. The field sits on top,
   and the style choice and the Go mark share a row inside it. Everything
   else on the screen lies flat, so this box is the only thing raised and it
   needs no other emphasis.

   soft-daylight.css draws .field as one short row of fixed height, so home
   overrides it into a column. The sheet keeps its own shape for the player
   and the settings screen. */
.v-home .field {
  flex-direction: column; align-items: stretch; gap: 0;
  height: auto; padding: 16px 16px 14px; margin-bottom: 18px;
  border-radius: var(--r-lg);
}
.v-home .field input { height: 26px; align-self: auto; }
.askfoot { display: flex; align-items: center; gap: 14px; margin-top: 16px; }

/* The Go mark stands on its own, with no disc behind it. The mark is wide
   and a disc is round, so a disc wastes the shape. The button keeps a 52 px
   box, which holds the tap target above the 44 px floor.

   Without a mark the button falls back to the accent disc, which is what a
   style that marks.js does not name gets. */
.field .send {
  flex: 0 0 auto; margin-left: auto; width: 52px; height: 52px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--accent); box-shadow: var(--sh-accent);
}
.field .send.hasmark { background: none; box-shadow: none; }
/* Until the styles answer and paintGo draws the button, it has no mark and
   would show as the bare accent disc for the length of that request, which on
   a refresh read as a dark blob. paintGo sets data-style as it draws, so the
   button stays out of sight until then. The follow-up send in the player is
   never painted this way and keeps its disc. */
.v-home .field .send:not([data-style]), .dock .field .send:not([data-style]) { visibility: hidden; }
/* The mark sits at 80 percent of its button. Filling the box, it read too
   large beside the two style words. The button keeps its 52 px, so the tap
   target does not move. */
.field .send .drawnmark { display: block; width: 80%; height: 80%; }
/* The drawn chevron carries an outline on top of each bar, so it covers more
   ground than the smooth one at the same box. Three percent evens them out. */
.field .send.hasmark[data-style="cartoon"] .drawnmark { width: 77.6%; height: 77.6%; }
.field .send:disabled { opacity: .45; }

/* THE STYLE CHOICE, region 1 of table 2 in the specification.
   Inside the box it is two marks with their words, with no card and no
   pressed pill, because the box is already the raised thing. The chosen one
   is ink and bold with its mark at full strength, and the other fades. */
.v-home .switch, .dock .switch {
  flex: 0 0 auto; margin: 0; padding: 0; height: auto; gap: 17px;
  background: none; border-radius: 0; box-shadow: none;
}
.v-home .switch button, .dock .switch button {
  flex: 0 0 auto; height: auto; border-radius: 0;
  padding: 12px 0; margin: -12px 0;
  display: inline-flex; align-items: center; gap: 7px;
  background: none; box-shadow: none;
  font-size: 14px; font-weight: 500; color: var(--slate);
}
.switch button .drawnmark { display: block; width: 20px; height: 20px; opacity: .4; }
.v-home .switch button[aria-pressed="true"], .dock .switch button[aria-pressed="true"] {
  background: none; box-shadow: none; color: var(--ink); font-weight: 700;
}
.switch button[aria-pressed="true"] .drawnmark { opacity: 1; }
/* Below about 28 px the aperture's six blade seams fill the ring, so they
   drop out and the rim carries the shape on its own. */
.switch button .seam { display: none; }

/* The two example questions. They are text under their heading, not cards:
   two blue links beside the Go mark pull the eye away from the Go mark. */
.chips {
  flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center;
  gap: 0 14px; margin: -11px 0 10px; padding: 0 2px;
}
.chip {
  min-width: 0; padding: 0; min-height: 44px; height: auto;
  display: inline-flex; align-items: center;
  background: none; box-shadow: none;
  border-radius: 0; text-align: left;
  font-size: 13.5px; font-weight: 600; letter-spacing: -.006em; color: var(--ink);
  white-space: normal;
}

/* No films yet. The heading above it stays, so the screen says where they
   will land rather than going blank. A display rule beats the hidden
   attribute, so hidden is restated. */
.noearlier {
  flex: 0 0 auto; margin: 0; padding: 0 2px;
  font-size: 13.5px; line-height: 1.5; color: var(--slate);
}
.noearlier[hidden] { display: none; }

.earlier {
  flex: 0 0 auto; margin: 0 0 11px;
  font-size: 11.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint);
}
.earlier[hidden] { display: none; }
/* The favorites list sits above the session list, so the second heading needs
   air over it. An empty list collapses and the rule does nothing. */
.rows:not(:empty) + .earlier { margin-top: 18px; }
/* Earlier chats now keeps its heading when the list is empty, so the heading
   needs its own air whatever sits above it. */
/* #earlierLabel's air now comes from .listhead. */

/* The list heading with its view toggle beside it. The toggle names the
   view a press brings, lit in the accent while the rows are threads; it
   keeps a 44 px tap box. */
.listhead { display: flex; align-items: center; justify-content: space-between; margin-top: 18px; }
.listhead .earlier { margin: 0; }
.listhead #earlierLabel { margin-top: 0; }
.listmode {
  font: inherit; font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--faint); padding: 12px 0 12px 6px; margin: -12px 0; min-height: 44px;
  background: none; border: 0; border-radius: 10px;
}
.listmode[aria-pressed="true"] { color: var(--accent); }
.listhead + .rows { margin-top: 11px; }

/* Both lists scroll as one column. */
.lists {
  flex: 1 1 auto; min-height: 0;
  /* Never sideways: a control at the edge must not turn into a scroll that
     cuts the heading's first letter off. */
  overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  padding-bottom: 12px;
  scrollbar-width: none;
}
.lists::-webkit-scrollbar { display: none; }
.rows { display: flex; flex-direction: column; gap: 10px; }
/* A row is a box holding three things: the text, which is the link or the
   button that opens the film and is stretched over the whole row; the like
   button, which sits above that stretch so a press on it likes and does not
   open; and the square picture. A link cannot hold a button, so the row
   itself is a plain box and the link lives inside it. */
.chat {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 13px; min-height: 74px; padding: 0 15px;
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--sh-card);
  text-align: left;
}
.chat .ct {
  flex: 1 1 auto; min-width: 0; min-height: 44px; align-self: stretch;
  display: flex; flex-direction: column; justify-content: center;
  /* The text is an anchor or a button, so it carries the link's own ink and
     underline, and the button's own face, until these turn them off. */
  color: inherit; text-decoration: none; text-align: left;
  background: none; border: 0; padding: 0; border-radius: 0; box-shadow: none;
  font: inherit;
}
/* The stretch: the link's press area is the whole row, picture included. */
.chat .ct::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
/* The like, left of the picture, lit in the accent when the film is liked.
   It reads and writes the same marks list the player's rail does, so the
   two never disagree. 44 px square, the tap floor; the margin gives back a
   little of what the button takes from the title. */
.chat .rowlike {
  position: relative; z-index: 1; flex: 0 0 auto;
  width: 44px; height: 44px; margin-right: -6px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; padding: 0; border-radius: 50%; box-shadow: none;
  color: var(--faint);
}
.chat .rowlike svg { display: block; width: 22px; height: 22px; }
.chat .rowlike:active { transform: scale(.9); }
.chat .rowlike[aria-pressed="true"] { color: var(--like); }
.chat .rowlike[aria-pressed="true"] svg { fill: var(--like); }
.chat h3 {
  margin: 0 0 3px;
  font-size: 15px; font-weight: 600; letter-spacing: -.01em; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat p {
  margin: 0; font-size: 12.5px; font-weight: 500; color: var(--slate);
  font-variant-numeric: tabular-nums;
}
/* The film's own first frame, at the right end, with the length over its
   foot the way a video thumbnail carries it. The square starts empty and the
   picture arrives when the row comes into view, so the empty state is the
   one to get right: it is what a film still being made shows. */
.chat .sq {
  position: relative; flex: 0 0 auto; width: 54px; height: 54px;
  border-radius: 11px; overflow: hidden; background: var(--panel-2, #E4E9F3);
  display: grid; place-items: center;
}
.chat .sq img { display: block; width: 100%; height: 100%; object-fit: cover; }
.chat .burn {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 9px 5px 3px;
  font-size: 10.5px; font-weight: 700; color: #FFFFFF; text-align: right;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(180deg, rgba(10, 14, 30, 0), rgba(10, 14, 30, .72));
}
/* Three dots while the film has no frame to show yet. The two outer dots are
   shadows of the middle one, so a transform on this box would move them with
   it and leave one dash. The size is set here rather than scaled. */
.chat .dots {
  width: 4px; height: 4px; border-radius: 50%; background: #98A2BA;
  box-shadow: -8px 0 0 #98A2BA, 8px 0 0 #98A2BA;
}


.homefail, .playfail {
  margin: 12px 0 0; font-size: 13.5px; font-weight: 600; color: var(--accent-deep);
}
.homefail:empty, .playfail:empty { display: none; }
.playfail {
  position: absolute; left: 20px; right: 20px; top: calc(var(--safe-top) + 64px);
  z-index: 12; color: #FFFFFF;
  text-shadow: 0 1px 3px rgba(var(--night), .8);
}

/* =========================================================
   PLAYER. Panel F, and panel L for the late state.
   ========================================================= */

/* The stage carries the film's own shape, 480 by 832, so the picture is
   shown whole: never stretched to fill the window, and never enlarged so
   that only part of it shows. Whatever the window's shape, the stage fits
   inside it and the night colour fills the rest.

   Everything drawn on the picture lives inside the stage, so the caption,
   the rail and the bullets all move with it.

   The swipe writes transform on this element, so the centring is margin. */
.stage {
  position: absolute; inset: 0; margin: auto;
  aspect-ratio: var(--film-w) / var(--film-h);
  width: min(100%, calc(100dvh * var(--film-w) / var(--film-h)));
  height: auto; max-height: 100%;
  overflow: hidden;
  background: rgba(var(--night), 1);
  touch-action: none;
  user-select: none; -webkit-user-select: none;
}
/* The night fills the window behind the stage, so a letterboxed film sits on
   black rather than on the home screen's paper. */
.v-player { background: rgba(var(--night), 1); }
.stage.settle { transition: transform .3s cubic-bezier(.22,1.06,.36,1); }

.track {
  position: absolute; left: 0; top: -100%;
  width: 100%; height: 300%;
  will-change: transform;
}
.track.settle { transition: transform .42s cubic-bezier(.22,1.12,.3,1); }

.lesson { position: relative; width: 100%; height: 33.3333%; overflow: hidden; }

.shot { position: absolute; inset: 0; overflow: hidden; background: rgba(var(--night), 1); }

/* A pointer dragged across a picture starts a native media drag, which
   fires pointercancel and kills the swipe. The picture takes no pointer
   events, so the drag stays with the stage. */
.pane, .peek {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  /* The stage already has the film's shape, so this crops nothing today. It
     is contain rather than cover so that a beat that arrives at another
     shape is letterboxed instead of having its edges cut off. */
  object-fit: contain;
  pointer-events: none;
  -webkit-user-drag: none;
  opacity: 0;
  transition: opacity .12s linear;
  transform-origin: 50% 50%;
}
.pane.on, .peek { opacity: 1; }
.peek { transition: none; }

/* The neighbor lessons are dimmed until a drag brings one in. app.js
   raises --near from 0 to 1 while the finger moves. */
.lesson[data-slot="prev"] .shot,
.lesson[data-slot="next"] .shot { filter: brightness(calc(.62 + .38 * var(--near, 0))); }
.lesson[data-slot="prev"] .peek-q,
.lesson[data-slot="next"] .peek-q { opacity: calc(.34 + .66 * var(--near, 0)); }

html[data-state="late"] .lesson[data-slot="cur"] .shot {
  filter: brightness(.72) saturate(.84);
  transition: filter .5s ease;
}

/* The light overlay. Both scrims are built from the caption plate color,
   so the picture stays the brightest thing on screen. */
.scrim-top, .scrim-bot { position: absolute; left: 0; right: 0; pointer-events: none; }
.scrim-top {
  top: 0; height: 138px;
  background: linear-gradient(to bottom, rgba(var(--night), .52), rgba(var(--night), 0));
}
.scrim-bot {
  bottom: 0; height: 330px;
  background: linear-gradient(to top, rgba(var(--night), .74), rgba(var(--night), .26) 46%, rgba(var(--night), 0));
}

/* The recap card's wash. app.js measures the picture under the bullets and
   writes --wash, so the strength is read rather than picked. */
.wash {
  position: absolute; inset: 0; pointer-events: none;
  background: rgba(0, 0, 0, var(--wash, 0));
  opacity: 0;
}
html[data-kind="card"] .wash { opacity: 1; }
html[data-kind="card"] .scrim-bot,
html[data-kind="card"] .scrim-top { opacity: 0; }

/* ---------- leaving the player ---------- */

.back {
  position: absolute; left: 6px; top: calc(var(--safe-top) + 12px); z-index: 9;
  width: 44px; height: 44px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--card);
  opacity: 0; transform: translateX(-6px); pointer-events: none;
  transition: opacity .24s ease, transform .24s ease;
}
html[data-rail="up"] .back,
html[data-state="late"] .back { opacity: 1; transform: none; pointer-events: auto; }
.back:active { opacity: .62; }
.back svg { filter: drop-shadow(0 1px 3px rgba(var(--night), .7)); }

/* ---------- full screen ----------
   Top right, the mirror of the back chevron. It is the one control that stays
   when the others go, wearing its other face, so the thumb that hid them
   brings them back without moving. */
/* Both corner buttons are round, so the hover ring and the focus ring
   they wear are rings and not boxes. */
.full {
  position: absolute; right: 6px; top: calc(var(--safe-top) + 12px); z-index: 9;
  width: 44px; height: 44px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--card); opacity: 1; pointer-events: auto;
}
.full:active { opacity: .62; }
.full svg { filter: drop-shadow(0 1px 3px rgba(var(--night), .7)); }
.full .shrink { display: none; }
html[data-full="yes"] .full .expand { display: none; }
html[data-full="yes"] .full .shrink { display: block; }
/* The rules that hide the other controls under full screen sit after the
   rail's own visibility rule, below, because they tie it on specificity and
   only source order lets them win. */
html[data-full="yes"] .cap { right: 20px; }

/* ---------- the time hairline ----------
   One continuous bar scaled on the X axis, the only thing on the player
   that never fades. It reports elapsed time and takes no touches. */
/* The progress line is a seek bar. The line stays 2 px; the press area is
   the 28 px band around it, so a thumb finds it without covering the film.
   While a finger holds it the line thickens, so the scrub reads as one. */
.hair {
  position: absolute; left: 0; right: 0;
  bottom: calc(var(--safe-bottom) + 1px);
  height: 28px; z-index: 8;
  background: none; cursor: pointer;
  touch-action: none; user-select: none;
}
.hair::before {
  content: ""; position: absolute; left: 0; right: 0; top: 13px; height: 2px;
  background: rgba(255, 255, 255, .22);
  transition: height .12s, top .12s;
}
.hair i {
  position: absolute; left: 0; right: 0; top: 13px; height: 2px;
  background: var(--accent-tint);
  transform-origin: 0 50%;
  transform: scaleX(0);
  transition: height .12s, top .12s;
}
.hair[data-scrub="yes"]::before, .hair[data-scrub="yes"] i { top: 12px; height: 4px; }
.hair:focus-visible { outline: 2px solid var(--hover); outline-offset: -6px; }

/* ---------- the question row, and the one control ---------- */

.qrow {
  position: absolute; left: 20px; bottom: calc(var(--safe-bottom) + 28px);
  width: 276px; min-height: 56px;
  display: flex; align-items: flex-end; gap: 10px;
  padding: 8px 10px 8px 0;
  text-align: left; z-index: 7;
}
.qtext {
  flex: 1 1 auto; margin: 0;
  font-family: var(--display);
  font-size: 17px; font-weight: 500; line-height: 1.22; letter-spacing: -.014em;
  color: var(--card);
  text-shadow: 0 1px 3px rgba(var(--night), .7);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.peek-q {
  position: absolute; left: 20px; bottom: calc(var(--safe-bottom) + 36px);
  width: 276px; z-index: 7;
}
.qrow .chev {
  flex: 0 0 auto; margin-bottom: 2px;
  color: rgba(255, 255, 255, .82);
  transition: transform .2s ease;
}
.qrow:active .chev { transform: translateY(-3px); }
.qrow:active .qtext { opacity: .78; }

/* ---------- captions ----------
   White type straight on the picture. No plate. The size, the line
   height, the shadow and the centering are the films' own, scaled from
   the 480 by 832 frame to this stage. */
.cap {
  position: absolute; left: 20px; right: 20px;
  bottom: calc(var(--safe-bottom) + 108px);
  z-index: 6; margin: 0;
  text-align: center;
  font-family: var(--ui);
  font-size: var(--cap-fs);
  font-weight: 700;
  line-height: 1.29;
  letter-spacing: -.01em;
  color: #FFFFFF;
  text-shadow: var(--cap-shadow-x) var(--cap-shadow-y) 0 rgba(0, 0, 0, .85);
  transition: right .24s ease, opacity .2s ease;
  pointer-events: none;
}
html[data-rail="up"] .cap { right: 92px; }
.cap:empty { opacity: 0; }
html[data-state="late"] .cap,
html[data-kind="card"] .cap { display: none; }

/* ---------- the recap card's bullets ----------
   Four at most, each spoken, each appearing on the character time of its
   own first word and staying. The block is bottom anchored and every
   line starts on the same left margin, as the render lays it out. */
.bullets {
  position: absolute; left: 20px; right: 20px;
  bottom: calc(var(--safe-bottom) + 108px);
  z-index: 6; margin: 0; padding: 0; list-style: none;
  display: none; flex-direction: column;
  gap: calc(var(--stage-h) * 10 / 832);
  color: #FFFFFF;
  font-family: var(--ui);
  font-size: var(--cap-fs);
  font-weight: 700;
  line-height: 1.29;
  letter-spacing: -.01em;
  text-shadow: var(--cap-shadow-x) var(--cap-shadow-y) 0 rgba(0, 0, 0, .7);
  pointer-events: none;
}
html[data-kind="card"] .bullets { display: flex; }
.bullets li {
  padding-left: 1.1em; text-indent: -1.1em;
  opacity: 0; transition: opacity .18s linear;
}
.bullets li.on { opacity: 1; }

/* ---------- the stall plate ----------
   The message the owner wrote, and nothing after it. */
.stall {
  position: absolute; left: 20px; right: 92px;
  bottom: calc(var(--safe-bottom) + 108px);
  z-index: 6;
  display: none; align-items: center; gap: 9px;
  padding: 11px 15px;
  border-radius: var(--r-sm);
  background: rgba(var(--night), .76);
  border-top: 1px solid rgba(255, 255, 255, .16);
  box-shadow: 0 10px 26px -12px rgba(8, 10, 28, .6);
  color: #FFFFFF;
  font-size: 15.5px; font-weight: 600; line-height: 1.34; letter-spacing: -.004em;
  text-shadow: 0 1px 2px rgba(6, 8, 24, .35);
}
html[data-state="late"] .stall,
html[data-waiting="1"] .stall { display: flex; }
.stall .dot {
  flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent-tint);
  animation: breathe 1.5s ease-in-out infinite;
}
@keyframes breathe {
  0%, 100% { opacity: .3; transform: scale(.78); }
  50% { opacity: 1; transform: scale(1); }
}

/* ---------- the rail ---------- */

.rail {
  position: absolute; right: 19px;
  bottom: calc(var(--safe-bottom) + 28px);
  width: 52px; z-index: 9;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  opacity: 0; transform: translateY(10px); pointer-events: none;
  transition: opacity .24s ease, transform .24s ease;
}
html[data-rail="up"] .rail,
html[data-state="late"] .rail { opacity: 1; transform: none; pointer-events: auto; }
/* Full screen: every other control goes, whatever data-rail or the state
   says. These sit after the two rules above on purpose: they tie them on
   specificity, and only source order lets them win. The time hairline stays,
   as it always does. */
html[data-full="yes"] .rail,
html[data-full="yes"] .back { opacity: 0; transform: translateY(10px); pointer-events: none; }

.rbtn {
  width: 52px; height: 52px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--card);
  /* Half the fill the rail first shipped with, on the owner's word, so more
     of the picture shows through each disc. */
  background: rgba(var(--night), .21);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
  transition: transform .16s ease, background .16s ease;
}
.rbtn:active { transform: scale(.9); background: rgba(var(--night), .31); }
/* A quarter of the accent: half of the half the owner first asked for. */
.rbtn.primary { background: color-mix(in srgb, var(--accent) 25%, transparent); box-shadow: var(--sh-accent); }
html[data-liked="yes"] .rbtn.like { color: var(--like); }
html[data-liked="yes"] .rbtn.like svg { fill: var(--like); }
.rbtn.fav[aria-pressed="true"] { color: var(--accent-tint); }
.rbtn.fav[aria-pressed="true"] svg { fill: var(--accent-tint); }

/* ---------- gesture feedback ---------- */

.flash {
  position: absolute; left: 50%; top: 50%; z-index: 10;
  width: 92px; height: 92px; margin: -46px 0 0 -46px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(var(--night), .5);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  color: var(--card);
  opacity: 0; transform: scale(.68); pointer-events: none;
}
.flash.go { animation: flashpop .62s ease-out forwards; }
@keyframes flashpop {
  0%   { opacity: 0; transform: scale(.68); }
  18%  { opacity: 1; transform: scale(1); }
  62%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.18); }
}

.burst {
  position: absolute; z-index: 11; width: 108px; height: 108px;
  left: 50%; top: 50%; margin: -54px 0 0 -54px;
  color: var(--accent-tint);
  opacity: 0; pointer-events: none;
}
.burst.go { animation: burstpop .86s cubic-bezier(.2,1.1,.3,1) forwards; }
@keyframes burstpop {
  0%   { opacity: 0; transform: scale(.3) rotate(-14deg); }
  22%  { opacity: 1; transform: scale(1.26) rotate(6deg); }
  48%  { opacity: 1; transform: scale(1) rotate(0deg); }
  100% { opacity: 0; transform: scale(1.1) translateY(-26px); }
}

/* The seek ripple fills the outer 35 percent that owns the double press,
   so the ripple also teaches the zone. The wash is built from --night,
   because a white wash disappears over the bright core of a picture. */
.seek {
  position: absolute; top: 0; bottom: 0; width: 35%; z-index: 10;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px;
  color: var(--card);
  opacity: 0; pointer-events: none;
}
.seek.l {
  left: 0; --nudge: -7px;
  background: radial-gradient(130% 62% at 0% 50%, rgba(var(--night), .42), rgba(var(--night), 0) 74%);
}
.seek.r {
  right: 0; --nudge: 7px;
  background: radial-gradient(130% 62% at 100% 50%, rgba(var(--night), .42), rgba(var(--night), 0) 74%);
}
.seek span {
  font-size: 13px; font-weight: 600; letter-spacing: .01em;
  text-shadow: 0 1px 3px rgba(var(--night), .7);
}
.seek.go { animation: seekpop .62s ease-out forwards; }
.seek.go svg { animation: chevnudge .62s ease-out; }
@keyframes seekpop {
  0%   { opacity: 0; }
  14%  { opacity: 1; }
  58%  { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes chevnudge {
  0%, 100% { transform: translateX(0); }
  32%      { transform: translateX(var(--nudge)); }
}

/* =========================================================
   EXPANDED. Panel H, Chat forward's sheet.
   The sheet is the chat thread, and Soft daylight returns in full.
   ========================================================= */

.topbar-player {
  position: absolute; left: 18px; right: 18px;
  top: calc(var(--safe-top) + 10px);
  z-index: 3;
  display: none; align-items: center; gap: 12px;
}
html[data-state="expanded"] .topbar-player { display: flex; }
.roundbtn {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: #FFFFFF;
  border: 1px solid rgba(255, 255, 255, .34);
  background: rgba(var(--night), .34);
}
.titlewrap { min-width: 0; }
.chattitle {
  font-family: var(--display);
  font-size: 19px; font-weight: 600; letter-spacing: -.02em; color: #FFFFFF;
  text-shadow: 0 1px 3px rgba(var(--night), .7);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chatsub {
  margin-top: 2px; font-size: 12.5px; font-weight: 500;
  color: rgba(255, 255, 255, .78);
  text-shadow: 0 1px 3px rgba(var(--night), .7);
}

.veil {
  position: absolute; inset: 0; z-index: 2;
  background: rgba(var(--night), .46);
  opacity: 0; visibility: hidden; transition: opacity .18s linear;
}
html[data-state="expanded"] .veil { opacity: 1; visibility: visible; }
html[data-state="expanded"] .rail,
html[data-state="expanded"] .qrow,
html[data-state="expanded"] .cap,
html[data-state="expanded"] .bullets,
html[data-state="expanded"] .hair,
html[data-state="expanded"] .back { opacity: 0; visibility: hidden; }

.sheet {
  position: absolute; left: 0; right: 0; top: 168px; bottom: 0; z-index: 4;
  display: flex; flex-direction: column;
  padding: 0 18px calc(var(--safe-bottom) + 18px);
  background: var(--base);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: var(--sh-stage);
  opacity: 0; visibility: hidden; transform: translateY(18px);
  transition: opacity .2s linear, transform .2s ease-out;
}
html[data-state="expanded"] .sheet { opacity: 1; visibility: visible; transform: none; }

.grab { flex: 0 0 auto; width: 100%; height: 44px; display: flex; align-items: center; justify-content: center; }
.grab i { display: block; width: 42px; height: 5px; border-radius: 3px; background: var(--line); }

.sheethead {
  flex: 0 0 auto; min-height: 52px; margin-bottom: 12px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.sheettitle {
  font-family: var(--display);
  font-size: 20px; font-weight: 600; letter-spacing: -.02em; color: var(--ink);
}
.sheetsub { margin-top: 2px; font-size: 12.5px; font-weight: 500; color: var(--slate); }

.thread {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 12px;
  overflow-y: auto; overscroll-behavior: contain; padding: 2px 0 8px;
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 18px, #000 96%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0, #000 18px, #000 96%, transparent 100%);
}
.thread::-webkit-scrollbar { display: none; }

/* The learner's own words: a glass bubble in the accent, over the ground. */
.ask-b {
  align-self: flex-end; max-width: 80%; margin: 0;
  padding: 11px 15px; border-radius: var(--r-md);
  color: var(--accent-deep);
  font-size: 14.5px; font-weight: 600; line-height: 1.3; letter-spacing: -.006em;
}
.lcard {
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
  width: 100%; min-height: 70px; padding: 11px 13px; text-align: left;
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--sh-card);
}
/* The film playing now wears the accent at a whisper over its glass. */
.lcard.here { color: var(--accent-deep); }
.lbody { flex: 1 1 auto; min-width: 0; }
.ltitle {
  display: block;
  font-family: var(--display);
  font-size: 15px; font-weight: 600; letter-spacing: -.012em; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lmeta {
  margin-top: 4px; display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; font-weight: 500; color: var(--slate);
  font-variant-numeric: tabular-nums;
}
.nowchip {
  flex: 0 0 auto; padding: 0 9px; height: 24px; border-radius: 12px;
  display: inline-flex; align-items: center;
  background: var(--card); color: var(--accent-deep);
  font-size: 11.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
}
.lgo { flex: 0 0 auto; color: var(--faint); display: inline-flex; }

.dock { flex: 0 0 auto; }
/* The follow-up box is the home ask box: a column, the question on the
   first line, the style marks and the Go mark on the second. */
.dock .field {
  flex-direction: column; align-items: stretch; gap: 0;
  height: auto; padding: 16px 16px 14px; margin-bottom: 0;
  border-radius: var(--r-lg);
}
.dock .field input { height: 26px; align-self: auto; }


/* =========================================================
   SETTINGS
   Reached by tapping your own picture. Built only on the
   frozen tokens, and it shows no control that does nothing.
   ========================================================= */
.avatar {
  width: 36px; height: 36px; border-radius: 50%;
  object-fit: cover; display: block; background: var(--faint);
}
.avatar.big { width: 56px; height: 56px; }
.avatar[hidden] { display: none; }

/* Shown when Google sends no picture, or the picture fails to load. */
.initial {
  width: 36px; height: 36px; border-radius: 50%;
  display: none; align-items: center; justify-content: center;
  background: var(--accent); color: var(--card);
  font-weight: 700; font-size: 15px;
}
.initial.big { width: 56px; height: 56px; font-size: 22px; }
.no-face > .initial { display: inline-flex; }

.who {
  width: 44px; height: 44px; padding: 0; border: 0; background: none;
  display: inline-flex; align-items: center; justify-content: center;
}
.who[hidden] { display: none; }

.back-plain {
  width: 44px; height: 44px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center;
  background: none; color: var(--ink);
}
.settings-title { font-size: 17px; font-weight: 700; margin: 0; color: var(--ink); }
.topbar-spacer { width: 44px; }

/* The same gutter home uses. Without it every card on this screen ran into
   both edges, which is what the screen looked like on a real phone. The list
   also has to scroll: it grows with the account, and a long one had no way to
   reach its foot. */
.v-settings .content {
  padding: calc(var(--safe-top) + 14px) 20px calc(var(--safe-bottom) + 18px);
  overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none;
}
.v-settings .content::-webkit-scrollbar { display: none; }

.v-settings .sect {
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--slate); margin: 22px 0 8px;
}
.v-settings .card {
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--sh-card);
  padding: 14px 16px; margin-bottom: 8px;
  display: flex; align-items: center; gap: 14px;
  width: 100%; text-align: left; color: var(--ink);
}
/* The style choice on settings. It reads the way it reads on home, a mark
   with its name and the chosen one in ink, inside the card a settings screen
   groups its choices into. The deep pill it used to carry was drawn in the
   old accent and matched nothing else on the screen. */
.v-settings .switch {
  height: auto; padding: 6px; gap: 4px;
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--sh-card);
}
.v-settings .switch button {
  flex: 1 1 0; height: 52px; border-radius: 12px; padding: 0 12px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: none; box-shadow: none;
  font-size: 15px; font-weight: 500; color: var(--slate);
}
.v-settings .switch button[aria-pressed="true"] {
  background: var(--accent-tint); color: var(--ink); font-weight: 700;
  box-shadow: none;
}

.account-who { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.account-name { font-size: 16px; font-weight: 700; }
.account-mail { font-size: 13px; color: var(--slate); overflow: hidden; text-overflow: ellipsis; }
.row-plain { justify-content: flex-start; }
.row-title { font-size: 15px; font-weight: 600; display: block; }
/* The sub line keeps its height while it is empty, so the plan card is the
   same size before and after GET /api/plan answers and nothing below it
   moves when the numbers land. */
.row-sub { font-size: 13px; color: var(--slate); display: block; min-height: 1.45em; }
.row-link { justify-content: space-between; min-height: 52px; text-decoration: none; }
.row-chev { color: var(--slate); font-size: 22px; line-height: 1; }
.row-link.danger .row-title { color: #B4344A; }
.v-settings .note { font-size: 13px; color: var(--slate); margin: 2px 2px 0; }
/* A paid plan on offer: its name and films a month on the left, its price,
   and an Upgrade. The price is the one place on the whole app that shows
   money, and settings-shot.mjs holds it to this element. */
/* A card's flex display would beat the hidden attribute, which is how a
   Manage billing button showed on the free plan; both are restated. */
.offers[hidden], .v-settings .card[hidden] { display: none; }
.offer .offer-text { flex: 1 1 auto; min-width: 0; }
.offer .price {
  flex: 0 0 auto; font-size: 14px; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.offer .price small { font-size: 12px; font-weight: 500; color: var(--slate); }
/* The same tinted pill as the chosen segment of the picture style switch
   below it, so the one button on this screen that asks for money wears the
   look every other control on the screen wears. */
.offer .upgrade {
  flex: 0 0 auto; height: 40px; padding: 0 15px; border-radius: 12px;
  background: var(--accent-tint); color: var(--ink); box-shadow: none;
  font: inherit; font-size: 14px; font-weight: 700; letter-spacing: -.006em;
}
.offer .upgrade:active { transform: scale(.97); }
.offer .upgrade:disabled { opacity: .55; }
/* The offers end with the one rule a learner must know before paying. */
.offers .carry { font-size: 13px; color: var(--slate); margin: 2px 2px 8px; }
.planfail { margin: 6px 2px 0; font-size: 13px; font-weight: 600; color: var(--accent-deep); }
.planfail:empty { display: none; }
.v-settings .legal {
  margin-top: 26px; font-size: 13px; color: var(--slate);
  display: flex; gap: 8px; justify-content: center;
}
.v-settings .legal a { color: var(--slate); }

/* =========================================================
   THE GLASS. Every raised box on the home and settings screens is the
   Liquid glass from docs/design/home-glass-options.html: a fill of 20 percent white over a
   blurred and saturated backdrop, a highlight along the top edge, a
   reflection band on a diagonal, a blue shade at the bottom edge that reads as
   thickness, and a soft blue drop. Neither box clips its own children, because
   in Chromium an element with a backdrop filter does not honour its own
   overflow clip; the title's ellipsis clip sits on the h3, and the poster's on
   .sq, both plain children. These rules close the file so the glass wins
   over every card rule above it on order alone.
   ========================================================= */
.v-home .field, .chat, .v-settings .card, .v-settings .switch, .dock .field, .lcard, .ask-b {
  position: relative; border: 0;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .55), rgba(255, 255, 255, 0) 42%),
    linear-gradient(112deg, rgba(255, 255, 255, 0) 26%, rgba(255, 255, 255, .5) 44%, rgba(255, 255, 255, 0) 62%),
    rgba(255, 255, 255, .2);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  backdrop-filter: blur(18px) saturate(1.5);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .95),
    inset 0 -1px 0 rgba(255, 255, 255, .35),
    inset 0 -12px 22px -16px rgba(21, 94, 239, .12),
    0 1px 2px rgba(20, 22, 45, .04),
    0 18px 36px -20px rgba(21, 94, 239, .21);
}
/* The rim: a ring 1.5 px wide, bright at the top left where light would
   strike, dimmer at the bottom right, with a second catch light at the end.
   A pseudo element cut to the ring with a mask, so the gradient shows on the
   edge only and the see-through fill stays untouched. Chromium reads the
   -webkit composite, Safari on the phone reads it too. */
.v-home .field::before, .chat::before, .v-settings .card::before, .v-settings .switch::before,
.dock .field::before, .lcard::before, .ask-b::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1.5px; pointer-events: none;
  background: linear-gradient(135deg, rgba(255, 255, 255, 1), rgba(255, 255, 255, .2) 48%, rgba(255, 255, 255, .8));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}

/* =========================================================
   HOVER AND PRESS. The owner's pick from docs/design/hover-options.html:
   Both. Under the mouse the control's ink turns the logo's darkest blue
   and so does its edge; under the finger both turn the square before it.
   A glass row's edge is its rim ring; a pill's or a disc's edge is a ring
   drawn with an inset shadow, which moves no layout. A control filled with
   the accent (the primary rail button, the sheet's Go, a warn Go, the send
   disc a style without a mark falls back to) keeps its white ink and takes
   the two blues as its fill, because blue ink on navy would not read. Hover
   is offered only to a device that has one, so a phone never sticks in a
   hover state after a tap. Keyboard focus wears the hover look. A drawn
   mark keeps its own colors and gets a ring around it instead.
   ========================================================= */
:root { --hover: #155EEF; --press: #4D85F3; }
/* A glyph inside a control is not a target of its own. */
button svg, button .drawnmark, .chat .ct > * { pointer-events: none; }
button, a { transition: color .12s, background-color .12s, box-shadow .12s, opacity .12s, transform .16s; }
button:focus-visible, a:focus-visible { outline: 2px solid var(--hover); outline-offset: 3px; }

@media (hover: hover) {
  .signin:hover { color: var(--hover); box-shadow: inset 0 0 0 1.5px var(--hover), var(--sh-card); }
  .v-home .switch button:hover, .dock .switch button:hover { color: var(--hover); box-shadow: inset 0 0 0 1.5px var(--hover); border-radius: 12px; }
  .v-home .switch button:hover .drawnmark, .dock .switch button:hover .drawnmark { opacity: 1; }
  .lcard:hover::before { background: var(--hover); }
  .lcard:hover .ltitle { color: var(--hover); }
  .field .send.hasmark:hover .drawnmark { outline: 2px solid var(--hover); outline-offset: 3px; border-radius: 50%; }
  .field .send:not(.hasmark):hover { background: var(--hover); }
  .chip:hover { color: var(--hover); box-shadow: inset 0 -1.5px 0 var(--hover); }
  .chat:has(.ct:hover)::before { background: var(--hover); }
  .chat:has(.ct:hover) h3 { color: var(--hover); }
  .chat .rowlike:hover { color: var(--hover); box-shadow: inset 0 0 0 1.5px var(--hover); }
  .chat .rowlike[aria-pressed="true"]:hover, html[data-liked="yes"] .rbtn.like:hover { color: var(--like); }
  .v-settings .switch button:hover { color: var(--hover); box-shadow: inset 0 0 0 1.5px var(--hover); }
  .v-settings .switch button:hover .drawnmark { opacity: 1; }
  .offer .upgrade:hover { color: var(--hover); box-shadow: inset 0 0 0 1.5px var(--hover); }
  .v-settings .card.row-link:hover::before { background: var(--hover); }
  .v-settings .card.row-link:hover .row-title, .v-settings .card.row-link:hover .row-chev { color: var(--hover); }
  .v-settings .card.row-link.danger:hover .row-title { color: #B4344A; }
  .v-settings .legal a:hover { color: var(--hover); box-shadow: inset 0 -1.5px 0 var(--hover); }
  .back:hover, .back-plain:hover, .full:hover, .roundbtn:hover, .rbtn:not(.primary):hover {
    color: var(--hover); box-shadow: inset 0 0 0 1.5px var(--hover);
  }
  .rbtn.primary:hover, .authgo:hover:not(:disabled) { background: var(--hover); }
  .authgoogle:hover:not(:disabled) { color: var(--hover); box-shadow: inset 0 0 0 1.5px var(--hover); }
  .authclose:hover, .authforgot:hover, .authswitch:hover, .gonenote a:hover { color: var(--hover); }
  /* The signed-in picture is a disc of somebody's face, so its ring sits outside it. */
  #who:hover { outline: 2px solid var(--hover); outline-offset: 2px; }
}
.signin:active { color: var(--press); box-shadow: inset 0 0 0 1.5px var(--press), var(--sh-card); }
.v-home .switch button:active, .dock .switch button:active { color: var(--press); box-shadow: inset 0 0 0 1.5px var(--press); border-radius: 12px; }
.lcard:active::before { background: var(--press); }
.lcard:active .ltitle { color: var(--press); }
.field .send.hasmark:active .drawnmark { outline: 2px solid var(--press); outline-offset: 3px; border-radius: 50%; }
.field .send:not(.hasmark):active { background: var(--press); }
.chip:active { color: var(--press); box-shadow: inset 0 -1.5px 0 var(--press); }
.chat:has(.ct:active)::before { background: var(--press); }
.chat:has(.ct:active) h3 { color: var(--press); }
.chat .rowlike:active { color: var(--press); box-shadow: inset 0 0 0 1.5px var(--press); }
.chat .rowlike[aria-pressed="true"]:active, html[data-liked="yes"] .rbtn.like:active { color: var(--like); }
.v-settings .switch button:active { color: var(--press); box-shadow: inset 0 0 0 1.5px var(--press); }
.offer .upgrade:active { color: var(--press); box-shadow: inset 0 0 0 1.5px var(--press); }
.v-settings .card.row-link:active::before { background: var(--press); }
.v-settings .card.row-link:active .row-title, .v-settings .card.row-link:active .row-chev { color: var(--press); }
.v-settings .card.row-link.danger:active .row-title { color: #B4344A; }
.v-settings .legal a:active { color: var(--press); box-shadow: inset 0 -1.5px 0 var(--press); }
.back:active, .back-plain:active, .full:active, .roundbtn:active, .rbtn:not(.primary):active {
  color: var(--press); box-shadow: inset 0 0 0 1.5px var(--press);
}
.rbtn.primary:active, .authgo:active:not(:disabled) { background: var(--press); }
.authgoogle:active:not(:disabled) { color: var(--press); box-shadow: inset 0 0 0 1.5px var(--press); }
.authclose:active, .authforgot:active, .authswitch:active, .gonenote a:active { color: var(--press); }
#who:active { outline: 2px solid var(--press); outline-offset: 2px; }

/* The chat sheet stands on the same ground as home, so its glass has color
   to show. The learner's bubbles and the film that is playing take the
   accent at a whisper in their fill, over the glass. */
.sheet { background: var(--ground); }
.ask-b, .lcard.here {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .55), rgba(255, 255, 255, 0) 42%),
    linear-gradient(112deg, rgba(255, 255, 255, 0) 26%, rgba(255, 255, 255, .5) 44%, rgba(255, 255, 255, 0) 62%),
    rgba(21, 94, 239, .05);
}
