/* ==========================================================================
   components.css — floating button, dialogs, pickers, forms, buttons,
   banners, toast.
   ========================================================================== */

/* --- Floating add button -------------------------------------------------- */

.fab {
  position: fixed;
  right: max(var(--page-pad), var(--safe-right));
  bottom: calc(var(--tabbar-h) + var(--safe-bottom) + 14px);
  z-index: 20;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border: 0;
  border-radius: 50%;
  color: var(--on-accent);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-spring),
              opacity var(--dur-fast) var(--ease-out);
}

/* Glass rather than a flat disc: a highlight rolling off the top, a soft
   pool of light at the foot, and a bright rim where the two meet the edge.
   All of it is painted once — no blur filter, nothing that costs a frame. */
.fab {
  background-color: var(--accent);
  background-image:
    linear-gradient(180deg, var(--fab-sheen) 0%, transparent 54%),
    radial-gradient(120% 80% at 50% 118%, var(--fab-glow), transparent 72%);
  box-shadow:
    var(--shadow-fab),
    inset 0 1.5px 0 var(--fab-rim),
    inset 0 -1px 0 var(--fab-shade);
}

.fab:active { transform: scale(0.9); }
.fab__icon { width: 24px; height: 24px; stroke-width: 2.2; }

/* --- Bottom sheets -------------------------------------------------------- */
/* Bottom sheet on phones, centred card from 700px up. */

.sheet {
  width: 100%;
  max-width: 100%;
  max-height: 88dvh;
  margin: 0;
  margin-top: auto;
  padding: 0;
  border: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-sheet);
  overflow: hidden;
}

.sheet::backdrop { background: var(--scrim); }

/* Focus is parked here on open (see js/dialog.js); it is a scroll container,
   not a control, so it should never look like one. */
.sheet:focus,
.sheet:focus-visible,
.alert:focus,
.alert:focus-visible { outline: none; }

/* Catches the light along the top edge where the sheet meets the scrim. */
.sheet,
.alert { box-shadow: var(--shadow-sheet), inset 0 1px 0 var(--gloss-edge); }

.sheet[open] {
  display: flex;
  flex-direction: column;
}

/* Drag affordance. Also the one area where a downward drag always wins over
   scrolling, whatever the body is doing. */
.sheet__grip {
  flex: none;
  display: flex;
  justify-content: center;
  padding: 10px 0 4px;
  cursor: grab;
  touch-action: none;
}

.sheet__handle {
  width: 38px;
  height: 4px;
  border-radius: 999px;
  background: var(--line-strong);
}

.sheet__dismiss {
  flex: none;
  display: flex;
  justify-content: flex-end;
  padding: 0 12px;
}

.sheet__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 2px var(--page-pad) 8px;
}

.sheet__head .sheet__title { flex: 1; margin-bottom: 0; }

/* No close button in this one, so it needs its own breathing room. */
.sheet__head--plain { padding: 4px var(--page-pad) 14px; }

.sheet__body {
  flex: 1 1 auto;
  min-height: 0;
  /* The generous bottom padding keeps the last block — usually the note —
     from sitting flush against the action row. */
  padding: 4px var(--page-pad) 26px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

/* The form ends in its own action row, so it does not want the extra room. */
.form.sheet__body { padding-bottom: 8px; }

.sheet__title {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: 4px;
}

/* One line on the Connection sheet saying which deployment this is. */
.setup__status {
  margin: 2px 0 2px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  background: var(--segment-bg);
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.45;
}

.setup__status.is-stale {
  background: var(--unknown-tint);
  color: var(--unknown);
}

.sheet__lead {
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-dim);
  margin-bottom: 4px;
}

.sheet__actions {
  flex: none;
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  padding: 14px var(--page-pad);
  padding-bottom: calc(14px + var(--safe-bottom));
  border-top: 1px solid var(--line);
  background: var(--surface);
}

/* Inside a scrolling form the action row is part of the flow, not a footer. */
.form .sheet__actions {
  margin: 8px calc(var(--page-pad) * -1) 0;
}

/* Opens to just under the header title, so the entry has room to breathe. */
.sheet--tall {
  height: calc(100dvh - var(--safe-top) - 72px);
  max-height: calc(100dvh - var(--safe-top) - 72px);
}

.sheet--tall .sheet__body {
  display: flex;
  flex-direction: column;
}

/* Short entries settle in the middle; long ones scroll from the top, which a
   justify-content:center would break by clipping the overflow. */
.detail__content {
  width: 100%;
  margin-block: auto;
}

@media (min-width: 700px) {
  .sheet {
    max-width: 520px;
    margin: auto;
    border-radius: var(--radius-lg);
    max-height: 80dvh;
  }
  .sheet__actions { padding-bottom: 14px; }
  /* No swipe gesture with a mouse; the handle would only mislead. */
  .sheet__grip { display: none; }
  .sheet__dismiss { padding-top: 12px; }
  /* A full-height card looks wrong once it is centred in a window. */
  .sheet--tall { height: auto; max-height: 80dvh; }
}

@media (display-mode: browser) and (min-width: 960px) {
  .sheet--tall { max-width: 600px; }
  .sheet--tall .detail__word { font-size: 40px; }
}

/* --- Detail view ---------------------------------------------------------- */

.detail__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.detail__pos {
  display: inline-block;
  padding: 3px 9px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* Puts the word aside, or brings it back. Metrically identical to
   .detail__pos above, so the two sit on one baseline: same padding, same
   border width, same type. */
.detail__archive {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-spring);
}

.detail__archive:active { transform: scale(0.95); }

/* Already archived: filled, and grey rather than red — putting a word aside
   is housekeeping, not a verdict on it. */
.detail__archive[data-archived="1"] {
  border-color: transparent;
  background: var(--archived-tint);
  color: var(--archived);
}

.detail__headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin: 10px 0 22px;
}

.detail__word {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 34px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.03em;
  overflow-wrap: anywhere;
}

/* YouGlish and DuckDuckGo, one above the other. Side by side they crowd the
   word they belong to, and the second would have to lose its label to fit. */
.wordlinks {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

.youglish {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 1px;
  padding: 4px 2px;
  color: var(--text-faint);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-spring);
}

.youglish:active { transform: scale(0.94); }

.youglish__chevron {
  width: 14px;
  height: 14px;
  stroke-width: 2.4;
}

.detail__block + .detail__block { margin-top: 20px; }

/* The section headings on the detail card and on the back of every card.
   This used to share a class with the label control above, and so inherited
   its dashed pill outline — which is where the dotted boxes around
   "Definition" and "Note" came from. */
.detail__heading {
  margin-bottom: 5px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.detail__text {
  font-size: 16px;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* --- Folder menu ----------------------------------------------------------- */
/* Hangs off the bottom edge of the title bar and drops into place, over the
   tabs and the list. Transform and opacity only, so the drop stays on the
   compositor. The negative insets take it back out to the screen edges,
   since it sits inside the header's padding. */

.folder-menu {
  position: absolute;
  top: 100%;
  left: calc(-1 * max(var(--page-pad), var(--safe-left)));
  right: calc(-1 * max(var(--page-pad), var(--safe-right)));
  z-index: 11;
  padding: 4px max(var(--page-pad), var(--safe-left)) 14px max(var(--page-pad), var(--safe-right));
  border-bottom: 1px solid var(--line-strong);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  background: var(--bg);
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.14);
  transform: translateY(-8px);
  opacity: 0;
  transition: transform 200ms var(--ease-out), opacity 160ms var(--ease-out);
}

.folder-menu.is-open {
  transform: translateY(0);
  opacity: 1;
}

.folder-menu__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 2px 2px;
}

.folder-menu__title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.folder-menu__edit {
  padding: 4px 2px;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.folder-menu__list {
  max-height: 46dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.folder-row {
  display: flex;
  align-items: center;
  gap: 6px;
  border-bottom: 1px solid var(--line);
}

.folder-row__choose {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 2px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 16px;
  text-align: left;
  cursor: pointer;
}

.folder-row__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.folder-row__choose[aria-current="true"] .folder-row__name { font-weight: 650; }

.folder-row__count {
  flex: none;
  font-size: 13px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.folder-row__check {
  flex: none;
  width: 17px;
  height: 17px;
  stroke-width: 2.4;
}

.folder-row__tools {
  flex: none;
  display: flex;
  gap: 2px;
}

.folder-row__icon { width: 17px; height: 17px; stroke-width: 1.9; }

.icon-button--danger { color: var(--danger); }

/* Editing is about the folders, not about which one is open. */
.folder-row.is-editing .folder-row__choose { pointer-events: none; }
.folder-row.is-editing .folder-row__count { display: none; }

.folder-menu__new {
  display: flex;
  gap: 8px;
  padding-top: 12px;
}

.folder-menu__new .field__input { flex: 1 1 auto; min-height: 40px; }
.folder-menu__new .button { flex: none; min-height: 40px; }

.folder-menu__error { padding-top: 8px; }

/* --- Quiz home ------------------------------------------------------------- */

/* The add button is not on this tab, so the 96px kept clear for it would do
   nothing here but push the quiz home off centre. */
.app-main.is-quiz-home { padding-bottom: 0; }

/* Centred on the whole screen, the way the card deck is: the two are the
   same gesture — one thing to look at, in the middle. --chrome-diff makes up
   for the header being taller than the tab bar; see js/cards.js. */
.quiz-home {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100%;
  padding: 26px 0 calc(26px + var(--chrome-diff, 0px));
  box-sizing: border-box;
  text-align: center;
}

/* Two empty flex items with auto margins, which centre the content between
   them and collapse to nothing once it is taller than the screen. Plain
   justify-content: center would clip the top in a scrolling box instead. */
.quiz-home::before,
.quiz-home::after {
  content: '';
  flex: none;
}

.quiz-home::before { margin-top: auto; }
.quiz-home::after  { margin-bottom: auto; }

.quiz-home__label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.quiz-home__count {
  font-size: 56px;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.quiz-home__meta {
  display: flex;
  gap: 14px;
  margin-top: 4px;
  font-size: 13px;
  color: var(--text-dim);
}

.quiz-home__unknown { color: var(--unknown); font-weight: 600; }

.quiz-home__start {
  width: 100%;
  max-width: 320px;
  margin-top: 26px;
}

.quiz-home__progress {
  width: 100%;
  max-width: 420px;
  margin-top: 40px;
  text-align: left;
}

.quiz-home__caption {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 7px;
}

.bar {
  display: flex;
  height: 12px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--segment-bg);
}

.bar__part { transition: width var(--dur-mid) var(--ease-out); }
.bar__part--new      { background: var(--stage-new); }
.bar__part--learning { background: var(--stage-learning); }
.bar__part--solid    { background: var(--stage-solid); }

.quiz-home__legend {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 10px;
  font-size: 12px;
}

/* Each figure carries its own colour, and a dot of it, so the bar above can
   be read without counting along it from the left. */
.quiz-home__key {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 600;
  white-space: nowrap;
}

.quiz-home__key::before {
  content: '';
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}

.quiz-home__key--new      { color: var(--stage-new); }
.quiz-home__key--learning { color: var(--stage-learning); }
.quiz-home__key--solid    { color: var(--stage-solid); }

/* An empty figure says nothing; only its dot would show. */
.quiz-home__key:empty { display: none; }

.quiz-home__empty {
  margin-top: 30px;
  font-size: 15px;
  color: var(--text-faint);
}

/* --- Quiz session ---------------------------------------------------------- */
/* Over everything, with its own bar: one card at a time and nothing else. */

.quiz {
  position: fixed;
  inset: 0;
  z-index: 25;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  padding: var(--safe-top) max(var(--page-pad), var(--safe-right))
           calc(var(--quiz-pad-b) + var(--safe-bottom)) max(var(--page-pad), var(--safe-left));
  /* The bar above the card and the block below it are both fixed heights, so
     the stage between them can be padded into the middle of the screen. */
  --quiz-bar-h: 42px;
  --quiz-foot-h: 84px;
  --quiz-pad-b: 16px;
}

.quiz__bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: var(--quiz-bar-h);
  font-size: 13px;
  color: var(--text-faint);
}

.quiz__end {
  padding: 4px 2px;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.quiz__count { font-variant-numeric: tabular-nums; }

.quiz__stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Makes up the difference between the chrome above and below — the buttons
     and the padding under them against the bar — so the card is centred on
     the screen rather than in what is left of it. */
  padding-top: calc(var(--quiz-foot-h) + var(--quiz-pad-b) - var(--quiz-bar-h));
  /* Depth for the turn. Set here rather than inside the card's transform so
     the card itself keeps a plain rotateY the compositor can run. */
  perspective: 1600px;
}

/* One block of a fixed height holding both, so showing the answer does not
   move the card. */
.quiz__foot {
  position: relative;
  flex: none;
  min-height: var(--quiz-foot-h);
}

.quiz__hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  color: var(--text-faint);
  text-align: center;
}

.quiz__actions {
  position: absolute;
  inset: 12px 0 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.grade {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-height: 56px;
  padding: 9px 0;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-spring);
}

.grade:active { transform: scale(0.97); }

.grade--missed { background: var(--unknown-tint); color: var(--unknown); }
.grade--got    { background: var(--known-tint);   color: var(--known); }

.grade__label { font-size: 15px; font-weight: 650; }
.grade__hint  { font-size: 11px; opacity: 0.85; }

/* --- Card deck -------------------------------------------------------------
   The list's other face: the same words one screen at a time, snapped. Its
   own scroller, so the page behind it has to stop scrolling while it is up. */

.app-main.is-cards {
  overflow: hidden;
  padding-bottom: 0;
}

.cards {
  height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
}

.cards::-webkit-scrollbar { display: none; }

/* A screen per card. min-height rather than height, so a card whose back is
   long can grow past the screen and still be read to the end.

   Centred on the screen, not on the strip between the header and the tab bar:
   the header is the taller of the two, so centring in the strip leaves the
   card sitting low. The extra bottom padding is the difference between them
   (--chrome-diff, measured in js/cards.js), which lifts the card by half of
   it — onto the middle of the whole screen, safe areas included. */
.card-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100%;
  padding: 18px 0 calc(18px + var(--chrome-diff, 0px));
  scroll-snap-align: center;
  /* Stops a hard flick from skimming past several words at once. */
  scroll-snap-stop: always;
  perspective: 1600px;
}

/* --- Flash card ------------------------------------------------------------ */
/* Both faces share one grid cell, so the card is exactly as tall as its
   taller face and neither face ever has to scroll on its own — a scrolling
   element inside a 3D turn is where iOS starts showing the wrong side. */

.flashcard {
  position: relative;
  display: grid;
  width: 100%;
  max-width: 520px;
  min-height: min(52vh, 440px);
  min-height: min(52dvh, 440px);
  transition: transform 520ms var(--ease-out);
  cursor: pointer;
  outline: none;
}

/* Promoted only around a turn; see js/quiz.js. */
.flashcard.is-primed { will-change: transform; }

.flashcard:focus-visible .flashcard__face { border-color: var(--accent); }

.flashcard__face {
  grid-area: 1 / 1;
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 26px 22px 14px;
  border: 1px solid var(--line);
  border-radius: 24px;
  background: var(--surface-raise);
  box-shadow: var(--shadow-card), inset 0 1px 0 var(--gloss-edge);
  overflow: hidden;
}

.flashcard__face--front {
  align-items: center;
  text-align: center;
}

/* A card far from the screen is flat: its front only, no 3D at all. */
.flashcard__face--back { visibility: hidden; }

/* --- The turn, for live cards only -----------------------------------------
   Every face with a 3D transform is its own compositing layer, a screen-sized
   bitmap in memory. A deck of 132 cards made 264 of them, which iOS will not
   allocate: a folder that size simply would not open, while a folder of three
   did. So only a card that is on screen or next to it (js/cards.js marks it
   is-live) is set up to turn, and the rest cost nothing but their layout. */

.flashcard.is-live { transform-style: preserve-3d; }

.flashcard.is-live .flashcard__face {
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* An explicit rotation on the front as well: Safari only honours
   backface-visibility on an element that has a 3D transform of its own. */
.flashcard.is-live .flashcard__face--front { transform: rotateY(0deg); }

.flashcard.is-live .flashcard__face--back {
  transform: rotateY(180deg);
  visibility: visible;
}

/* Never on a flat card, which would only show its front mirrored. */
.flashcard.is-live.is-flipped { transform: rotateY(180deg); }

.flashcard__word {
  margin: auto 0;
  padding: 12px 0;
  font-size: 38px;
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.03em;
  overflow-wrap: anywhere;
}

/* On the front the word is centred on the card itself, not on the space left
   above the two links. It leaves the flow to do that — otherwise the links
   push it up by half their height, and a card with them reads as a card whose
   word has slipped. Not clickable, so the links underneath stay reachable
   even where a very long word runs over them.

   The extra 32px at the foot is the optical correction: 26px is the face's
   own padding, and the rest lifts the word 16px above the geometric centre.
   The links below carry weight the empty space above them does not, so dead
   centre reads low — this is where the eye expects it. */
.flashcard__face--front .flashcard__word {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 26px 22px calc(26px + 32px);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

/* --- The picture on a card -------------------------------------------------
   The front carries the photograph; the back carries the same photograph
   blown up and blurred past recognition, which is where its colours come
   from. Nothing reads a pixel: the proxy sends no CORS header, so a canvas
   drawn from it could not be read back even if we wanted to — and blurring
   is the cheaper of the two anyway.

   Only three cards hold a loaded picture at a time; see js/cards.js. */

.flashcard__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The face is already clipped, but Safari will paint a square corner
     through a 3D transform without this. */
  border-radius: inherit;
}

/* A wash under the word, so white type survives a pale photograph. */
.flashcard__scrim {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.34) 0%, rgba(0, 0, 0, 0) 32%),
    linear-gradient(0deg, rgba(0, 0, 0, 0.58) 0%, rgba(0, 0, 0, 0) 46%);
}

/* The picture and its wash sit under everything else. Stated rather than
   left to document order: the word is positioned too, and without this the
   two would be settled by which came first in the markup. */
.flashcard__photo,
.flashcard__scrim { z-index: 0; }

/* Everything the reader is meant to read sits over both.
   The word keeps the absolute centring it has without a picture — giving it
   position: relative here would hand it back to the flow and drop it to the
   top of the card. */
.flashcard.has-picture .flashcard__word {
  z-index: 1;
  color: #ffffff;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.55);
}

.flashcard.has-picture .flashcard__face--front .wordlinks {
  position: relative;
  z-index: 1;
}

.flashcard.has-picture .flashcard__face--front .youglish {
  color: rgba(255, 255, 255, 0.86);
}

/* The back: the same picture, enlarged and blurred. The thumbnail is used
   rather than the full one — enlarging a small image is half the blur for
   free, and costs a fraction of the memory. */
.flashcard__wash {
  position: absolute;
  top: -25%;
  left: -25%;
  width: 150%;
  height: 150%;
  object-fit: cover;
  filter: blur(26px) saturate(1.7);
  /* The blur reaches past the edge; the face clips it back. */
  transform: translateZ(0);
}

/* Enough of the surface back over it that the text keeps its ordinary
   colour. The hues still come through — that is the point — but a card you
   cannot read is not a card. */
.flashcard__veil {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--card-veil);
}

.flashcard.has-picture .flashcard__body,
.flashcard.has-picture .flashcard__face--back .wordlinks {
  position: relative;
  z-index: 1;
}

.flashcard__body {
  flex: 1 1 auto;
  min-height: 0;
  text-align: left;
}

.flashcard__title {
  margin: 10px 0 18px;
  font-size: 26px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
}

.flashcard__empty {
  font-size: 15px;
  color: var(--text-faint);
}

/* On the front they sit at the foot, under the centred word; on the back
   they close the card off below the last block. */
.flashcard .wordlinks {
  align-self: center;
  align-items: center;
  margin-top: 14px;
}

.flashcard__face--front .wordlinks { margin-top: auto; }

.flashcard__face--back .wordlinks {
  align-self: flex-end;
  align-items: flex-end;
}

/* A browser window has the room for larger type; see the note on wide
   screens in layout.css. */
@media (display-mode: browser) and (min-width: 700px) {
  .flashcard { max-width: 640px; }
  .flashcard__word { font-size: 52px; }
  .quiz__bar, .quiz__foot { max-width: 640px; margin-inline: auto; width: 100%; }
}

/* --- Option picker -------------------------------------------------------- */

.options { padding-bottom: 4px; }

.options li + li { border-top: 1px solid var(--line); }

.option {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 54px;
  padding: 13px 6px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  font-size: 17px;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

.option:active { transform: scale(0.99); }

.option__label { flex: 1; }

.option.is-selected .option__label { font-weight: 650; }

.option__check {
  width: 19px;
  height: 19px;
  flex: none;
  stroke-width: 2.4;
}

/* --- Confirmation popup --------------------------------------------------- */

.alert {
  width: calc(100% - 48px);
  max-width: 340px;
  margin: auto;
  padding: 0;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.3);
  overflow: hidden;
}

.alert::backdrop { background: var(--scrim); }

.alert__body {
  padding: 26px var(--page-pad) 18px;
  text-align: center;
}

.alert__title {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin-bottom: 8px;
}

.alert__text {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-dim);
  overflow-wrap: anywhere;
}

.alert__actions {
  display: flex;
  gap: 10px;
  padding: 0 var(--page-pad) 18px;
}

.alert__actions .button { flex: 1; }

/* --- Form ----------------------------------------------------------------- */

.form {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field__label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.field__input {
  width: 100%;
  min-height: 44px;
  padding: 10px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-raise);
  color: var(--text);
  transition: border-color var(--dur-fast) var(--ease-out);
  -webkit-appearance: none;
  appearance: none;
}

.field__input { box-shadow: inset 0 1px 0 var(--gloss-edge); }

.field__input:focus { border-color: var(--accent); outline: none; }
.field__input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.field__input::placeholder { color: var(--text-faint); }

.field__input[aria-invalid="true"] { border-color: var(--danger); }

.field__textarea {
  resize: vertical;
  min-height: 84px;
  line-height: 1.55;
}

/* Opens the shared picker rather than a native select. */
.field__trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
  cursor: pointer;
}

.field__value { flex: 1; }

.field__trigger.is-empty .field__value { color: var(--text-faint); }

.field__chevron {
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--text-dim);
}

.swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 2px 0;
}

/* 34px across with an 8px gap puts eight on a row at 375px wide, so the
   sixteen options land in two tidy rows. */
.swatch {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-spring);
}

.swatch:active { transform: scale(0.9); }

.swatch[aria-checked="true"] { border-color: var(--accent); }

.swatch__dot {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: currentColor;
}

/* The default swatch is the ordinary ink, drawn as an outline so it reads as
   "no colour" rather than as black or white paint. */
.swatch[data-color="default"] .swatch__dot {
  background: transparent;
  border: 2px solid var(--text-dim);
}

.swatch[data-color="red"]         { color: var(--word-red); }
.swatch[data-color="orange"]      { color: var(--word-orange); }
.swatch[data-color="amber"]       { color: var(--word-amber); }
.swatch[data-color="yellow"]      { color: var(--word-yellow); }
.swatch[data-color="lime"]        { color: var(--word-lime); }
.swatch[data-color="green"]       { color: var(--word-green); }
.swatch[data-color="teal"]        { color: var(--word-teal); }
.swatch[data-color="cyan"]        { color: var(--word-cyan); }
.swatch[data-color="blue"]        { color: var(--word-blue); }
.swatch[data-color="indigo"]      { color: var(--word-indigo); }
.swatch[data-color="violet"]      { color: var(--word-violet); }
.swatch[data-color="purple"]      { color: var(--word-purple); }
.swatch[data-color="magenta"]     { color: var(--word-magenta); }
.swatch[data-color="pink"]        { color: var(--word-pink); }
.swatch[data-color="grey"]        { color: var(--word-grey); }

.form__error {
  font-size: 14px;
  color: var(--danger);
}

/* --- Buttons -------------------------------------------------------------- */

.button {
  min-height: 44px;
  padding: 0 20px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-spring),
              opacity var(--dur-fast) var(--ease-out);
}

.button:active { transform: scale(0.96); }
.button:disabled { opacity: 0.45; cursor: default; transform: none; }

.button--ghost {
  border-color: var(--line-strong);
  background: transparent;
  color: var(--text-dim);
}

/* Outlined rather than filled: the palette is greyscale, so red is reserved
   for meaning and is easier to read as an outline in both themes. */
.button--danger {
  border-color: var(--danger);
  background: transparent;
  color: var(--danger);
}

.icon-button {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

.icon-button:active { transform: scale(0.92); }
.icon-button svg { width: 19px; height: 19px; }

/* --- Toast & banners ------------------------------------------------------ */

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--tabbar-h) + var(--safe-bottom) + 84px);
  z-index: 40;
  max-width: min(420px, calc(100vw - 32px));
  padding: 11px 18px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 14px;
  text-align: center;
  box-shadow: var(--shadow-fab);
}

.banner {
  position: fixed;
  left: max(var(--page-pad), var(--safe-left));
  right: max(var(--page-pad), var(--safe-right));
  bottom: calc(var(--tabbar-h) + var(--safe-bottom) + 14px);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 12px 12px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-raise);
  box-shadow: var(--shadow-fab);
}

@media (min-width: 700px) {
  .banner { max-width: 520px; margin-inline: auto; }
}

.banner__text {
  flex: 1;
  font-size: 14px;
  line-height: 1.4;
  color: var(--text-dim);
}

.banner__action {
  flex: none;
  min-height: 36px;
  padding: 0 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-spring);
}

.banner__action:active { transform: scale(0.94); }
