/* ===========================================================================
   FRANÇAIS — styles, moved out of app.css on 15 October (the rebuild).
   ===========================================================================
   Loaded straight after app.css (see room-css in index.html), so the cascade is
   what it was: this block sat together near the end of app.css and nothing
   later in app.css touches these classes. The screen code that draws them is
   app/rooms/french.js.
   =========================================================================== */
/* ===========================================================================
   FRANÇAIS — THE PAGE FOR THE PHRASEBOOK THAT ALREADY EXISTED
   ===========================================================================
   13 October. The language half of Routines has been running since
   24 September — SM-2, forty-three French phrases, a review queue and
   matchSignals() reading his own calendar — and it has been living inside a
   tab on another page the whole time. This is the room it should have had.

   THE WHOLE DESIGN IS ONE IDEA: this is not a screen with French on it, it is
   a page out of a French exercise book. Everything below follows from that,
   and the brief it is answering was one sentence long: it cannot be a plain
   box with words in it.

   SEYÈS RULING is the ruling in every French schoolbook and has been since
   1892. Horizontal majors 8mm apart with three fainter lines between them,
   verticals at 8mm, and a red margin down the left. No American child has
   ever written on it, which is exactly why it reads as French instantly
   without a flag, a beret or a tower.

   THE GRID IS LOAD-BEARING, NOT WALLPAPER. 8mm is 32px here and every line
   height, margin and padding below is a multiple of 8px, so the text SITS ON
   the ruling the way handwriting does. Three pixels out and the whole thing
   reads as a background image with text floating over it.

   TWO THINGS WERE WRONG IN THE FIRST DRAFTS AND BOTH ARE WORTH RECORDING,
   because they are the two ways this kind of page fails:

     1. THE RULING RAN FULL BLEED to all four edges of the window, and that
        one decision made it a SPREADSHEET. A grid with no edge is a table.
        It needed to be a sheet — a max width, a hairline border and a soft
        drop — and the same ruling then read as paper on a desk.
     2. THE MINORS AND THE VERTICALS WERE AT THE SAME WEIGHT AS THE MAJORS,
        which puts a full-strength line every 8px on the page and the eye
        reads a ledger. Real Seyès minors are much fainter. They are at 45%
        and the verticals at 55% here, and that is the difference.

   LIGHT AND DARK KEY OFF data-mode, NOT off prefers-color-scheme and not off
   a list of theme names — setTheme() writes data-mode on the root for exactly
   this and says so. The default below is the night palette, because all but
   two of Lucid's themes are dark; the light block overrides it.
   =========================================================================== */

#view-french {
  /* ---- THE GRID. One number everything else derives from. --------------- */
  --fr-u: 8px;            /* the interline, 2mm */
  --fr-seyes: 32px;       /* the major ruling, 8mm */
  --fr-margin-x: 72px;    /* where the red margin falls */

  /* ---- THE NIGHT PALETTE ------------------------------------------------
     The conceit is the same notebook under a lamp: the paper goes to the
     blue-grey of a Paris night, the ruling nearly vanishes, the ink turns to
     lamplight. The margin line stays red because it is PRINTED, and print
     does not change colour in the dark. */
  --fr-paper:       #191d24;
  --fr-paper-edge:  #11141a;
  --fr-rule:        #2b3340;
  --fr-rule-strong: #384253;
  --fr-margin-line: #7e4a4f;
  --fr-ink:         #ece3d2;
  --fr-ink-soft:    #a79c8c;
  --fr-ink-faint:   #5d6470;
  --fr-zinc:        #8d9aa8;
  --fr-marron:      #b5848b;
  --fr-vert:        #7f9f88;
  --fr-ruling-op:   .85;
}

/* ---- THE CREAM PALETTE, on the two light themes ------------------------
   Measured off real things rather than picked off a wheel:
     paper   Clairefontaine ivory, the pads actually sold in France
     rule    the violet-blue of Seyès ruling, which is NOT sky blue
     margin  the rose-red of the printed vertical margin
     ink     iron gall, which is brown-black and never pure black
     zinc    Paris rooftop zinc, the grey-blue of the whole skyline
     marron  chestnut blossom, the pink that lines the avenues in April
     vert    the green of the chairs in the Jardin du Luxembourg */
html[data-mode="light"] #view-french {
  --fr-paper:       #f7f2e6;
  --fr-paper-edge:  #ddd5c2;
  --fr-rule:        #cfd8ea;
  --fr-rule-strong: #b3c0dc;
  --fr-margin-line: #d49494;
  --fr-ink:         #2a2520;
  --fr-ink-soft:    #6a6054;
  --fr-ink-faint:   #a79c8c;
  --fr-zinc:        #7a8794;
  --fr-marron:      #d9a7ae;
  --fr-vert:        #4a6b55;
  --fr-ruling-op:   .5;
}

/* ===========================================================================
   THE SHEET
   =========================================================================== */
.fr-sheet {
  position: relative;
  margin: 0 auto;
  max-width: 1120px;
  background-color: var(--fr-paper);
  border: 1px solid rgba(128,128,128,.16);
  box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 12px 40px -12px rgba(0,0,0,.18);
  overflow: hidden;
}

/* THE RULING, on its own layer so its weight is one number per mode.
   All three gradients are pinned to 0 0 so the verticals and the horizontals
   share one origin. Three gradients each starting from their own corner is
   the classic bug here, and it makes the lines cross at a different place in
   every single cell. */
.fr-sheet::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: var(--fr-ruling-op);
  background-position: 0 0;
  background-image:
    repeating-linear-gradient(to right,
      color-mix(in srgb, var(--fr-rule) 55%, transparent) 0 .5px,
      transparent .5px var(--fr-seyes)),
    repeating-linear-gradient(to bottom,
      var(--fr-rule-strong) 0 1px, transparent 1px var(--fr-seyes)),
    repeating-linear-gradient(to bottom,
      color-mix(in srgb, var(--fr-rule) 45%, transparent) 0 .5px,
      transparent .5px var(--fr-u));
}

/* THE RED MARGIN. Printed, so it sits above the ruling and below the text.
   Two hairlines 5px apart, which is how the cahiers actually print it. */
.fr-sheet::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: var(--fr-margin-x);
  width: 5px;
  z-index: 1;
  border-left: 1px solid var(--fr-margin-line);
  border-right: 1px solid var(--fr-margin-line);
  opacity: .8;
  pointer-events: none;
}

/* A PARIS BALCONY RAIL, bled off the right edge.
   This was a marronnier leaf first — the right tree, since the chestnuts are
   what flower on the avenues in April. Rendered, a palmate leaf with five
   narrow serrated leaflets is a cannabis leaf to every American eye alive,
   and no amount of being botanically correct survives that. The second
   attempt was wrought-iron scrolls, which at a watermark's weight came out as
   two light bulbs. At this opacity only geometry survives, so it is geometry:
   overlapping rings on a rail, the oldest pattern in ironwork and legible at
   any size. */
.fr-rail {
  position: absolute;
  right: -84px; bottom: 44px;
  width: 340px; height: 283px;
  opacity: .16;
  color: var(--fr-zinc);
  z-index: 1;
  pointer-events: none;
}
html[data-mode="light"] #view-french .fr-rail { opacity: .1; }
@media (max-width: 860px) { .fr-rail { display: none; } }

/* The column. Padding in grid units, so the first baseline lands on a line. */
.fr-col {
  position: relative;
  z-index: 2;
  max-width: 880px;
  padding: calc(var(--fr-u) * 5) 24px calc(var(--fr-u) * 10)
           calc(var(--fr-margin-x) + 24px);
}
@media (max-width: 720px) {
  #view-french { --fr-margin-x: 40px; }
  .fr-col {
    padding-left: calc(var(--fr-margin-x) + 16px);
    padding-right: 16px;
  }
}

/* ===========================================================================
   THE HEAD
   =========================================================================== */
.fr-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin: 0 0 calc(var(--fr-u) * 4);
}
.fr-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 40px;
  line-height: var(--fr-seyes);
  letter-spacing: .01em;
  margin: 0;
  color: var(--fr-ink);
}
/* The date in French, lower case, the way a French date is written. */
.fr-date {
  font-family: var(--font-display);
  font-style: normal; font-weight: 500;
  font-size: 19px;
  line-height: var(--fr-seyes);
  color: var(--fr-ink-soft);
  margin: 0;
}

/* ===========================================================================
   THE DREAM LINE — where the feature gets its point
   ===========================================================================
   This is not a settings field and it must not look like one. It is the
   sentence at the top of a page in a notebook: a question in French, and the
   answer written on the line under it.

   IT IS ALSO THE ONE CONTROL ON THE PAGE THAT CHANGES WHAT THE DECK DOES.
   suggestLanguage() and matchSignals() in _phrasebook.js both read
   want_to_go, so a place typed here is what turns a deck of phrases about
   nothing into a deck about the trip. */
.fr-dream { margin: 0 0 calc(var(--fr-u) * 5); }
.fr-dream-q {
  font-family: var(--font-display);
  font-style: normal; font-weight: 500;
  font-size: 22px;
  line-height: var(--fr-seyes);
  color: var(--fr-zinc);
  margin: 0;
}
.fr-dream-a {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  line-height: var(--fr-seyes);
}
.fr-dream-place {
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 500;
  color: var(--fr-ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid transparent;
  padding: 0;
  line-height: var(--fr-seyes);
  /* As wide as what is written in it, the way a word on a line is. The size
     attribute keeps it from being a 20-character slot where field-sizing is
     not supported. */
  field-sizing: content;
  min-width: 5ch;
}
.fr-dream-place::placeholder { color: var(--fr-ink-faint); font-style: italic; }
.fr-dream-place:focus { outline: none; border-bottom-color: var(--fr-marron); }
.fr-dream-with {
  font-family: var(--font-body);
  font-size: 16px;
  color: var(--fr-ink-soft);
}
.fr-dream-with b { font-weight: 500; color: var(--fr-ink); }

/* PHRASES · LISONS — two words under the title, the lit one in ink. */
.fr-mode { margin: calc(var(--fr-u) * -3) 0 calc(var(--fr-u) * 3);
  font-family: var(--font-display); font-size: 20px; line-height: var(--fr-seyes);
  color: var(--fr-ink-faint); }
.fr-mode button { appearance: none; background: none; border: 0; padding: 0; font: inherit;
  color: var(--fr-ink-soft); cursor: pointer; font-style: italic; }
.fr-mode button.on { color: var(--fr-ink); font-style: normal; border-bottom: 1px solid var(--fr-marron); }
.fr-mode button:focus-visible { outline: 2px solid var(--fr-marron); outline-offset: 4px; }
/* LISONS — the passage is set exactly like a phrase, one line per verse. */
.fr-read { margin: 0 0 calc(var(--fr-u) * 4); }
.fr-read-t { font-family: var(--font-body); font-size: 16px; line-height: var(--fr-seyes);
  color: var(--fr-zinc); margin: 0; }
.fr-read-fr { font-size: clamp(30px, 4.6vw, 44px); text-wrap: pretty; }
.fr-read-nav { display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
  margin: calc(var(--fr-u) * 3) 0; font-family: var(--font-display); font-size: 20px;
  line-height: var(--fr-seyes); color: var(--fr-ink); }
.fr-read-n, .fr-read-end { color: var(--fr-ink-soft); font-style: italic; }
.fr-read-nav > * { white-space: nowrap; }

/* PARLONS (C4, 14 October) — a scene is written on the same paper as the deck.
   Their line is set like a phrase; what has already been said is the small
   print above it, their side in the soft ink and his in ink, so the page reads
   as a conversation going down the sheet rather than as a card. */
.fr-ex { margin: 0 0 calc(var(--fr-u) * 4); }
.fr-ex-q { font-family: var(--font-display); font-style: normal; font-weight: 500; font-size: 22px;
  line-height: var(--fr-seyes); color: var(--fr-zinc); margin: 0; }
.fr-ex-list { list-style: none; margin: 0; padding: 0; }
.fr-ex-list li { margin: 0 0 calc(var(--fr-u) * 2); font-family: var(--font-display);
  font-size: 22px; line-height: var(--fr-seyes); color: var(--fr-ink); }
.fr-ex-meta, .fr-ex-who, .fr-ex-n, .fr-ex-cue { font-family: var(--font-body); font-size: 16px;
  line-height: var(--fr-seyes); color: var(--fr-ink-soft); }
.fr-ex-meta { display: block; max-width: 56ch; }
.fr-ex-who { margin: 0 0 calc(var(--fr-u) * 2); }
.fr-ex-who b, .fr-ex-cue b { color: var(--fr-ink); font-weight: 500; }
.fr-ex-n { margin: 0; font-style: italic; }
.fr-ex-cue { margin: calc(var(--fr-u) * 2) 0 0; }
.fr-ex-said { list-style: none; margin: 0 0 calc(var(--fr-u) * 2); padding: 0;
  font-family: var(--font-body); font-size: 16px; line-height: var(--fr-seyes); }
.fr-ex-said li { display: flex; flex-direction: column; margin: 0 0 8px; }
.fr-ex-them { color: var(--fr-ink-soft); font-style: italic; }
.fr-ex-me { color: var(--fr-ink); padding-left: 1.5em; }
.fr-ex-me i { color: var(--fr-ink-soft); }
.fr-ex-miss { margin: calc(var(--fr-u) * 2) 0; padding-left: 12px; border-left: 2px solid var(--fr-marron); }
.fr-ex-want { font-size: clamp(24px, 3.4vw, 32px); }
/* The words that did not land, marked on the line itself — the deck's own
   wavy pen stroke, so a miss looks the same in both places. */
.fr-ex-want span.miss { text-decoration: underline wavy var(--fr-margin-line);
  text-decoration-thickness: 2px; text-underline-offset: 5px; }
.fr-ex-type { display: flex; align-items: baseline; gap: 14px; margin-top: calc(var(--fr-u) * 2); }
.fr-ex-type .fr-mine-in { flex: 1; min-height: 0; resize: none; }

/* YOUR OWN PHRASES — written on the same paper, no card round it. */
.fr-mine { margin: calc(var(--fr-u) * 4) 0; }
.fr-mine-q {
  font-family: var(--font-display);
  font-style: normal; font-weight: 500; font-size: 22px; line-height: var(--fr-seyes);
  color: var(--fr-zinc); margin: 0;
}
.fr-mine-in {
  display: block; width: 100%; box-sizing: border-box; resize: vertical;
  font-family: var(--font-body); font-size: 18px;
  line-height: var(--fr-seyes); color: var(--fr-ink);
  background: transparent; border: 0; border-bottom: 1px solid var(--fr-ink-faint);
  padding: 0; min-height: calc(var(--fr-seyes) * 2);
}
.fr-mine-in::placeholder { color: var(--fr-ink-faint); font-style: italic; }
.fr-mine-in:focus { outline: none; border-bottom-color: var(--fr-marron); }
.fr-mine-row { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-top: 8px; }
.fr-mine-go {
  font-family: var(--font-display); font-size: 20px; font-weight: 500;
  color: var(--fr-marron); background: transparent; border: 0; padding: 0; cursor: pointer;
  border-bottom: 1px solid currentColor;
}
.fr-mine-go:disabled { opacity: .45; cursor: default; }
.fr-mine-note { font-family: var(--font-body); font-size: 15px; color: var(--fr-ink-soft); }

/* MON OBJECTIF (C6, 14 October) — the goal is written on the same line as the
   dream, in the same hand; where he stands against it is the small print under
   it. The scenes are a short list, the ones that hold in ink and the ones that
   do not yet in the softer ink — never red, because "not yet" is not a fault. */
.fr-goal { margin: calc(var(--fr-u) * -2) 0 calc(var(--fr-u) * 4); }
.fr-goal-in {
  display: block; width: 100%; box-sizing: border-box;
  font-family: var(--font-display); font-size: 24px; font-weight: 500;
  line-height: var(--fr-seyes); color: var(--fr-ink);
  background: transparent; border: 0; border-bottom: 1px solid transparent; padding: 0;
}
.fr-goal-in::placeholder { color: var(--fr-ink-faint); font-style: italic; font-weight: 400; }
.fr-goal-in:focus { outline: none; border-bottom-color: var(--fr-marron); }
.fr-goal-pos, .fr-goal-note {
  font-family: var(--font-body); font-size: 16px; line-height: var(--fr-seyes);
  color: var(--fr-ink-soft); margin: 0; max-width: 52ch;
}
.fr-goal-scenes { list-style: none; margin: 0; padding: 0;
  font-family: var(--font-body); font-size: 15px; line-height: var(--fr-seyes);
  color: var(--fr-ink-soft); }
.fr-goal-scenes b { font-weight: 500; }
.fr-goal-scenes .can b { color: var(--fr-ink); }
.fr-goal-scenes .not b { color: var(--fr-ink-soft); font-style: italic; }

/* WHAT LUCID CHANGED — one line per rule that fired, with the marron rule down
   the side that the page uses for things Lucid did rather than things he did. */
.fr-coach { margin: 0 0 calc(var(--fr-u) * 4); padding-left: 12px; border-left: 2px solid var(--fr-marron); }
.fr-coach-line { font-family: var(--font-body); font-size: 16px; line-height: var(--fr-seyes);
  color: var(--fr-ink); margin: 0; max-width: 52ch; }
.fr-coach-off { color: var(--fr-ink-soft); font-style: italic; }

/* THE PIECES — the phrase again, cut where it breathes, each piece pressable. */
.fr-pieces { margin: var(--fr-u) 0 0; font-family: var(--font-display);
  font-size: 22px; line-height: var(--fr-seyes); color: var(--fr-ink-faint); }
.fr-piece { appearance: none; background: none; border: 0; padding: 0; font: inherit;
  color: var(--fr-ink); cursor: pointer; border-bottom: 1px dotted var(--fr-marron); }
.fr-piece:focus-visible { outline: 2px solid var(--fr-marron); outline-offset: 4px; }

/* ===========================================================================
   THE PHRASE
   ===========================================================================
   The English is small and quiet and sits ABOVE, because it is the prompt and
   not the content. The French is the largest thing on the page by a long way,
   with no border, no fill, no radius and no shadow — it is written on the
   paper. The moment a card goes round it, it becomes a tile with words in it.
   =========================================================================== */
.fr-phrase { margin: 0 0 calc(var(--fr-u) * 4); }

.fr-en {
  font-family: var(--font-body);
  font-size: 17px;
  line-height: var(--fr-seyes);
  color: var(--fr-ink-soft);
  margin: 0;
}

/* line-height is locked to TWICE the ruling so that however large the type
   gets, the baseline still lands on a line. It was 2.5x first, which left a
   hole between the two lines of a phrase that wrapped — and a phrase that
   wraps is the common case here, since half the deck is a sentence. */
.fr-fr {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(36px, 6vw, 58px);
  line-height: calc(var(--fr-seyes) * 2);
  letter-spacing: -.012em;
  text-wrap: balance;
  color: var(--fr-ink);
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  width: 100%;
  display: block;
  text-align: left;
  position: relative;
  cursor: pointer;
  transition: color .18s ease;
}
.fr-fr:hover, .fr-fr:active, .fr-fr.speaking { color: var(--fr-vert); }
.fr-fr:focus-visible { outline: 2px solid var(--fr-marron); outline-offset: 6px; }

/* The speaking state: an underline that fills left to right for as long as
   the voice is actually talking. Not a spinner — a spinner on a one-second
   action is a lie about how long it takes. */
.fr-underline {
  position: absolute;
  left: 0; right: 0;
  bottom: calc(var(--fr-seyes) * .55);
  height: 2px;
  background: var(--fr-vert);
  transform: scaleX(0);
  transform-origin: left;
  opacity: 0;
}
.fr-fr.speaking .fr-underline {
  opacity: .5;
  animation: fr-say var(--fr-say-ms, 1400ms) linear forwards;
}
@keyframes fr-say { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
  .fr-fr.speaking .fr-underline { animation: none; opacity: .5; transform: scaleX(1); }
}

/* Pronunciation. Letterspaced small caps is how a dictionary sets it, and a
   respelling is still language, so it stays in the serif. */
.fr-say {
  font-family: var(--font-body);
  font-variant: small-caps;
  letter-spacing: .14em;
  font-size: 16px;
  line-height: var(--fr-seyes);
  color: var(--fr-zinc);
  margin: 0;
}
.fr-say .fr-stress { color: var(--fr-ink); font-weight: 500; }

/* ===========================================================================
   WHY THIS ONE — the sentence that justifies the feature existing
   ===========================================================================
   pickToday() returns the reason as well as the card so the screen can say
   which of its three branches happened. This is the line that makes it Lucid
   rather than a flashcard app, so it is never hidden and never shortened. */
.fr-why {
  margin: calc(var(--fr-u) * 3) 0 0;
  padding-top: var(--fr-u);
  border-top: 1px solid var(--fr-rule-strong);
  max-width: 46ch;
}
.fr-why p {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: var(--fr-seyes);
  color: var(--fr-ink-soft);
  margin: 0;
}
.fr-why .fr-quote { font-style: italic; color: var(--fr-ink); }

/* ===========================================================================
   THE FOUR WORDS
   ===========================================================================
   Not four buttons in a pill row. Four French words on one ruled line, which
   is prettier and is also the better teaching choice: reading `encore` and
   `facile` is itself four words of French, forty times a week. The English
   sits under each in a whisper.

   SM-2 TAKES FOUR GRADES AND THESE ARE THEM, in the engine's own order and
   under the engine's own words. GRADES in _phrasebook.js maps them, and the
   client sends the WORD, never a number — a raw 0-5 on the wire is a scale
   the browser can get wrong and nothing can validate. */
.fr-grades {
  display: flex;
  gap: clamp(18px, 5vw, 52px);
  align-items: flex-start;
  flex-wrap: wrap;
  margin: calc(var(--fr-u) * 5) 0 0;
}
.fr-grade {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  padding: 0 0 2px;
  cursor: pointer;
  text-align: left;
  color: var(--fr-ink);
  transition: border-color .16s ease, color .16s ease;
}
.fr-grade-word {
  display: block;
  font-family: var(--font-display);
  font-size: 27px;
  font-weight: 400;
  line-height: var(--fr-seyes);
}
.fr-grade-en {
  display: block;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: var(--fr-u);
  letter-spacing: .02em;
  color: var(--fr-ink-faint);
}
.fr-grade:hover { border-bottom-color: currentColor; }
.fr-grade:focus-visible { outline: 2px solid var(--fr-marron); outline-offset: 4px; }
.fr-grade[data-g="again"]:hover { color: var(--fr-margin-line); }
.fr-grade[data-g="easy"]:hover  { color: var(--fr-vert); }
.fr-grade[disabled] { opacity: .45; cursor: default; }

/* ===========================================================================
   THE JOURNEY, WHICH IS NOT A PROGRESS BAR
   ===========================================================================
   Marks ruled in fives, the way anybody counts by hand. Inked for met,
   hollow for not.

   ONE OF FORTY-THREE LOOKS LIKE A BEGINNING. The same fact in a progress bar
   reads "2%", which is a number that makes a person close the tab — and it
   would read the same on the day he finishes his first week.

   THE MARKS ARE DRAWN AS IF BY HAND, which is the entire reason this is a
   tally and not a bar chart, and it takes three things: a lean, a different
   lean on each of the five, and a round cap so a stroke has a beginning and
   an end rather than a cut edge. Upright and evenly spaced, it is a barcode —
   which is exactly what the first version looked like. */
.fr-journey {
  margin: calc(var(--fr-u) * 7) 0 0;
  padding-top: calc(var(--fr-u) * 2);
  border-top: 1px solid var(--fr-rule-strong);
}
.fr-journey-line {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: var(--fr-seyes);
  color: var(--fr-ink-soft);
  margin: 0 0 var(--fr-u);
}
.fr-journey-line b { color: var(--fr-ink); font-weight: 500; }
.fr-tally {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 30px;
  align-items: flex-end;
  padding-top: 6px;
}
.fr-tally-group { display: flex; gap: 9px; align-items: flex-end; position: relative; }
.fr-mark {
  display: block;
  width: 3px;
  height: 30px;
  border-radius: 2px;
  background: currentColor;
  color: var(--fr-ink-faint);
  /* .38 made the single inked mark look like a stray text cursor beside a row
     of ghosts. The unmet marks have to be clearly THERE: they are the
     forty-two he has not met, which is a fact about the deck, not an absence. */
  opacity: .5;
  transform: rotate(var(--fr-lean, 4deg));
}
.fr-tally-group .fr-mark:nth-child(1) { --fr-lean: 5deg; }
.fr-tally-group .fr-mark:nth-child(2) { --fr-lean: 2deg; }
.fr-tally-group .fr-mark:nth-child(3) { --fr-lean: 6deg; }
.fr-tally-group .fr-mark:nth-child(4) { --fr-lean: 3deg; }
.fr-tally-group .fr-mark:nth-child(5) { --fr-lean: 5deg; }
.fr-mark.met { color: var(--fr-ink); opacity: 1; height: 33px; width: 3.5px; }

/* The fifth of a struck five lies across the other four, as it does on paper,
   and only when the WHOLE five is met — a part-struck five is not a thing
   anybody has ever written. */
.fr-tally-group.struck::after {
  content: '';
  position: absolute;
  left: -5px; right: -5px; top: 48%;
  height: 3px;
  border-radius: 2px;
  background: var(--fr-ink);
  transform: rotate(-17deg);
}
/* The fifth upright is not drawn at all in a struck five, because on paper it
   IS the stroke across. Drawing both gives six marks for five phrases, which
   is house rule 211 on a tally chart. */
.fr-tally-group.struck .fr-mark:nth-child(5) { visibility: hidden; }

/* ===========================================================================
   TOGETHER — gated, and the gate is stated plainly
   =========================================================================== */
.fr-together {
  margin: calc(var(--fr-u) * 5) 0 0;
  max-width: 54ch;
}
.fr-together p {
  font-family: var(--font-display);
  font-style: normal; font-weight: 500;
  font-size: 19px;
  line-height: var(--fr-seyes);
  color: var(--fr-zinc);
  margin: 0;
}
.fr-together button {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--fr-marron);
  padding: 0;
  font: inherit;
  font-style: normal;
  color: var(--fr-ink);
  cursor: pointer;
}

/* LEARN TOGETHER (C5, 15 October) — written on the same paper as Vos phrases
   à vous: no card round it, the heading in zinc, the shared phrase in ink, the
   streaks as short lines of small print. "Not yet today" is the softer ink and
   never a warning colour: not yet is not a fault. Leave is the quietest thing
   in the panel on purpose. */
.fr-lt { margin: 0; }                 /* .fr-together round it gives the air and the measure */
.fr-together > p:not([hidden]) + #frLtHost .fr-lt { margin-top: calc(var(--fr-u) * 3); }
.fr-lt .fr-lt-q {
  font-family: var(--font-display);
  font-style: normal; font-weight: 500; font-size: 22px; line-height: var(--fr-seyes);
  color: var(--fr-zinc); margin: 0;
}
.fr-lt .fr-lt-line {
  font-family: var(--font-body); font-weight: 400; font-size: 17px; line-height: var(--fr-seyes);
  color: var(--fr-ink-soft); margin: 0;
}
.fr-lt .fr-lt-en {
  font-family: var(--font-body); font-weight: 400; font-size: 17px; line-height: var(--fr-seyes);
  color: var(--fr-ink-soft); margin: var(--fr-u) 0 0;
}
.fr-lt .fr-lt-fr {
  appearance: none; background: none; border: 0; padding: 0; margin: 0;
  display: block; text-align: left; cursor: pointer;
  font-family: var(--font-display); font-weight: 500; font-size: 28px;
  line-height: var(--fr-seyes); color: var(--fr-ink);
}
.fr-lt .fr-lt-fr:focus-visible { outline: 2px solid var(--fr-marron); outline-offset: 4px; }
/* .fr-together p would otherwise restyle the borrowed pronunciation line */
.fr-lt .fr-say { font-family: var(--font-body); font-weight: 400; font-size: 16px; color: var(--fr-zinc); }
.fr-lt .fr-lt-who { list-style: none; margin: var(--fr-u) 0 0; padding: 0; }
.fr-lt .fr-lt-row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 14px;
  font-family: var(--font-body); font-size: 16px; line-height: var(--fr-seyes);
}
.fr-lt .fr-lt-name { color: var(--fr-ink); font-weight: 500; overflow-wrap: anywhere; }
.fr-lt .fr-lt-n { color: var(--fr-ink-soft); }
.fr-lt .fr-lt-today { color: var(--fr-ink-faint); margin-left: auto; }
.fr-lt .fr-lt-today.is-done { color: var(--fr-vert); }
.fr-lt .fr-lt-go {
  font-family: var(--font-display); font-size: 20px; font-weight: 500;
  color: var(--fr-marron); background: transparent; border: 0; padding: 0; cursor: pointer;
  border-bottom: 1px solid currentColor;
}
.fr-lt .fr-lt-leave {
  appearance: none; background: none; border: 0; padding: 0; margin-top: var(--fr-u);
  font-family: var(--font-body); font-size: 15px; line-height: var(--fr-seyes);
  color: var(--fr-ink-faint); cursor: pointer; text-decoration: underline;
  text-underline-offset: 3px;
}
.fr-lt .fr-lt-go:disabled, .fr-lt .fr-lt-leave:disabled { opacity: .45; cursor: default; }
.fr-lt .fr-lt-go:focus-visible, .fr-lt .fr-lt-leave:focus-visible { outline: 2px solid var(--fr-marron); outline-offset: 4px; }
.fr-lt .fr-lt-note { font-family: var(--font-body); font-weight: 400; font-size: 15px; line-height: var(--fr-seyes); color: var(--fr-ink-soft); margin: 0; }

.fr-foot {
  margin: calc(var(--fr-u) * 6) 0 0;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: var(--fr-seyes);
  color: var(--fr-ink-faint);
}
.fr-foot em { font-style: italic; }

/* THE FIRST PAINT, before the read comes back. Four ghosts at the size the
   real lines will be, so the shape of the page exists before the data does —
   the same rule lifeFirstRunHtml() follows. Not a spinner over an empty
   sheet. */
.fr-ghost {
  background: currentColor;
  color: var(--fr-ink-faint);
  opacity: .14;
  border-radius: 2px;
  display: block;
}

/* ===========================================================================
   SAYING IT BACK
   ===========================================================================
   The page read the phrase at him and that was half a conversation. This is
   the other half, and it is drawn in the hand the rest of the page is already
   written in: a French word on a ruled line with the English whisper under
   it, exactly like the four grades. No pill, no card, no icon, no fill.

   EVERY VERTICAL MEASURE HERE IS A MULTIPLE OF 8px, like everything else on
   this sheet, so the transcript lands on the ruling as it is typed. The one
   number worth naming is the 48px line on .fr-heard — one and a half majors —
   which is the smallest line that holds 38px type without the descenders
   cutting the rule under it.

   THE TRANSCRIPT HAS A MINIMUM HEIGHT AND IT IS NOT DECORATION. Without it
   the whole lower half of the page — the reason line, the four words, the
   tally — jumps up 48px the moment he stops speaking. A page that moves its
   grades out from under the pointer is a page that marks the wrong card.
   =========================================================================== */
.fr-listen { margin: calc(var(--fr-u) * 3) 0 0; }

.fr-hear {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  padding: 0 0 2px;
  margin: 0;
  cursor: pointer;
  text-align: left;
  color: var(--fr-ink);
  transition: border-color .16s ease, color .16s ease;
}
.fr-hear-word {
  display: block;
  font-family: var(--font-display);
  font-size: 27px;
  font-weight: 400;
  line-height: var(--fr-seyes);
}
.fr-hear-en {
  display: block;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: var(--fr-u);
  letter-spacing: .02em;
  color: var(--fr-ink-faint);
}
.fr-hear:hover { border-bottom-color: currentColor; }
.fr-hear:focus-visible { outline: 2px solid var(--fr-marron); outline-offset: 4px; }
.fr-hear.live { color: var(--fr-vert); border-bottom-color: var(--fr-vert); }

/* The only moving thing on the page besides the underline the voice draws.
   An inked dot that breathes, which is what "listening" looks like when the
   vocabulary is pen and paper rather than a waveform. */
.fr-hear-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: 10px;
  vertical-align: .1em;
  border-radius: 50%;
  background: currentColor;
  opacity: 0;
  transition: opacity .16s ease;
}
.fr-hear.live .fr-hear-dot {
  opacity: 1;
  animation: fr-breathe 1.6s ease-in-out infinite;
}
@keyframes fr-breathe { 0%, 100% { opacity: .22; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .fr-hear.live .fr-hear-dot { animation: none; opacity: 1; }
}

/* HIS OWN WORDS, APPEARING IN FRENCH. The same Cormorant as the phrase and
   lighter, so it reads as the answer written under the question rather than a
   second phrase competing with the first. */
.fr-heard {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(26px, 3.6vw, 38px);
  line-height: calc(var(--fr-seyes) * 1.5);
  letter-spacing: -.008em;
  text-wrap: balance;
  color: var(--fr-ink-faint);
  margin: var(--fr-u) 0 0;
  min-height: calc(var(--fr-seyes) * 1.5);
}
.fr-heard.settled { color: var(--fr-ink-soft); }

/* THE WORDS OF THE CARD, MARKED. A wavy rule under the ones that did not
   land, in the red the margin is printed in, because that is what a French
   teacher puts under the word you missed. The words themselves are never
   recoloured away: he has to be able to read them to say them again. */
.fr-back {
  display: flex;
  flex-wrap: wrap;
  gap: 0 10px;
  margin: 0 0 var(--fr-u);
}
.fr-back span {
  font-family: var(--font-display);
  font-size: 24px;
  line-height: var(--fr-seyes);
  color: var(--fr-ink);
}
.fr-back span.miss {
  text-decoration: underline wavy var(--fr-margin-line);
  /* 1px survived on the cream and vanished on the night paper, where the
     printed red is muted. 2px is a pen stroke in both modes. */
  text-decoration-thickness: 2px;
  text-underline-offset: 5px;
}

.fr-verdict {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: var(--fr-seyes);
  color: var(--fr-ink-soft);
  margin: 0;
}
.fr-verdict b { color: var(--fr-ink); font-weight: 500; }

/* The suggestion, and it is a press. Nothing here writes a grade on his
   behalf — see the note on frHearVerdictHtml in app.js. Drawn like the
   invitation in .fr-together, which is the other button on this page that
   offers rather than acts. */
.fr-suggest {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--fr-marron);
  padding: 0;
  font: inherit;
  color: var(--fr-ink);
  cursor: pointer;
}
.fr-suggest:focus-visible { outline: 2px solid var(--fr-marron); outline-offset: 4px; }

/* One line, and it is the one thing to do. */
.fr-hear-help {
  font-family: var(--font-body);
  font-size: 14px;
  line-height: var(--fr-seyes);
  color: var(--fr-ink-faint);
  margin: 0;
}
