/* about.css — the public landing page at /about.
   ---------------------------------------------------------------------------
   Deliberately its own file, for the same reason admin.css is: style.css
   belongs to the student app, which is the thing that must never break. This
   page loads style.css first and layers on top, so every token there
   (--ink-*, --crimson, --font-display, the type scale) is reused as-is.

   One thing IS new here and is defined below rather than in style.css: the
   mono @font-face rules. Not used by the app; doesn't belong in the app's
   stylesheet.

   The page is two stacked panels (Task 2, then Task 1), each one viewport
   tall, in ordinary document flow — no scroll-jacking, no pinning, no snap.
   The only script is about.js, whose sole job is toggling .is-scrolled on the
   navbar; everything else here is static. Motion is limited to hover, gated
   on @media (hover: hover).

   No @media (prefers-reduced-motion) anywhere in this file — CLAUDE.md rule
   4: every device in this project reports reduce-motion on, so such a block
   would permanently disable whatever it guarded. */


/* ---------- fonts ----------
   Self-hosted, same pattern and same font-display as style.css:17-59 —
   font-src is 'self' in the CSP, so a Google Fonts <link> would be blocked.

   Caveat, the script face this page used for the annotation captions and the
   manifesto letter, was retired 2026-08-28 — both now take the body face via
   --font-hand. That also removes the reason they had to stay English: Caveat
   shipped no Vietnamese subset, so a tone mark fell back to a system face
   mid-word (the trap style.css:9-12 records for "Archivo Black"). */

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/static/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/static/fonts/jetbrains-mono-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
                 U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323,
                 U+0329, U+1EA0-1EF9, U+20AB;
}

:root {
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Was Caveat, a script face. Retired 2026-08-28: everything that used it now
     renders in the body face. That also lifts the "keep this English" rule the
     annotations carried — Caveat shipped no Vietnamese subset, so a single tone
     mark used to drop mid-word to a system face. */
  --font-hand: var(--font-body);

  /* The page ground. An alias now, not a value: --lp-ground in style.css owns
     the colour so the landing and anything else adopting this direction cannot
     drift apart. (It briefly pointed at a warm notebook paper mid-session;
     that direction was dropped, and this comment said so for longer than it
     was true.)

     Still an alias rather than letting callers use --lp-ground directly,
     because the NAVBAR is painted with it too: the bar is meant to be
     invisible at rest and separate itself only by the scroll shadow. One name
     for "whatever the page ground is" keeps the two from diverging. */
  --lp-bg: var(--lp-ground);
}


/* ---------- page shell ---------- */

.about-body {
  background: var(--lp-bg);
  color: var(--lp-ink);
}

.about-body ::selection { background: rgba(227,24,55,.15); color: var(--lp-ink); }


/* No grid texture on this page. It carried one until 2026-08-26 (the same
   64px hairline pattern hub-dash.css still uses behind #hubView); the
   background is deliberately flat now, so the console replica is the only
   thing competing for attention. hub-dash.css keeps its own copy — this was
   never a shared rule. */


/* ---------- solo landing ----------
   ⚠️ ORPHANED 2026-09-03. NO PAGE LOADS THIS BLOCK ANY MORE.
   /about was rebuilt in the neo-brutalist direction and now loads exactly one
   stylesheet, static/landing.css — not this file. Nothing else ever wore
   .about-body--solo, so every rule from here to the end of the section is
   dead: .lp-solo, .lp-solo-mark, .lp-badge, .lp-solo-name, .lp-solo-sub and
   the @media block that goes with them.

   Kept rather than deleted, the same way .lp-cta below was kept: the serif
   direction is one design call old and this repo reverses those, so throwing
   the work away would only mean typing it again. Delete the block once the
   serif direction is settled either way — it is logged in STATUS.md so the
   next cleanup has somewhere to start.

   NOTE for whoever revives it: .lp-badge carried the dated "Học miễn phí hết
   đến 5/10" promise. That promise now lives in the announcement bar in
   about.html. Do not resurrect a second copy of it.

   Original note, still true of the mechanics:
   Only /about wore .about-body--solo. Everything below is scoped behind it
   so /tutorial — which shares this stylesheet and owns the two full-height
   .lp-stage panels — is untouched.

   Column flex + `flex: 1` rather than min-height: calc(100dvh - 58px - …):
   the navbar's 58px is fixed, but .about-foot's padding is a clamp() that
   changes with the viewport, so any hand-subtracted total would be wrong at
   most sizes and leave a few stray pixels of scroll under a page whose whole
   point is that it does not scroll. */

.about-body--solo {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.about-body--solo .lp-solo {
  flex: 1;
  display: grid;
  place-items: center;
  /* align-content, not just place-items: with two rows the implicit tracks
     stretch to fill the height and `gap` then measures the space between two
     already-spread rows, which threw the button most of a screen below the
     wordmark. Packing the rows makes the gap literal. */
  align-content: center;
  gap: var(--space-5);
  padding: var(--space-5);
  position: relative;
  isolation: isolate;
  overflow: hidden;   /* clips the oversized glow below */
}

/* The glow. One soft ellipse of cool light sitting behind the headline — the
   whole of the hero's "atmosphere", and the reason the composition reads as
   airy without an illustration behind it.

   An ellipse wider than it is tall, anchored ABOVE centre: it has to sit
   behind the type, not behind the button, or the page looks bottom-lit.
   Kept under .17 alpha — past that it stops being light and starts being a
   coloured shape, which is the difference between this and a gradient blob.

   Replaces two layers from the notebook direction: a ruled-paper repeating
   gradient and a rotated, tiled security watermark. Both are gone. */
.about-body--solo .lp-solo::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 34%;
  width: min(1180px, 130%);
  aspect-ratio: 2 / 1;
  transform: translate(-50%, -50%);
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    ellipse at center,
    var(--lp-glow) 0%,
    rgba(120, 145, 210, .07) 42%,
    transparent 70%);
}

/* Badge, headline and subtitle as one grid child, so .lp-solo's gap measures
   the space between that block and the button rather than splitting the three
   apart. Their own internal rhythm is set here. */
.lp-solo-mark {
  display: grid;
  justify-items: center;
  gap: var(--space-4);
  width: 100%;
  /* No max-width here. It carried `21ch` briefly and that was a real bug: `ch`
     resolves against THIS element's font (Alexandria 16px ~ 238px), while the
     headline inside is Playfair at 70px whose unbreakable "IELTS Writing" is
     433px on its own. The h1 overflowed its parent — and because overflow only
     goes one way, the whole hero rendered visibly off-centre. Each child caps
     its own measure below, where `ch` means what it looks like it means. */
}

/* The dated promise, as a small outlined pill above the headline. Quiet on
   purpose — it is a detail that rewards a second look, not a shout. */
.lp-badge {
  margin: 0;
  font-family: var(--font-body);
  font-weight: var(--weight-medium);
  font-size: var(--text-xs);
  color: var(--lp-dim);
  background: var(--lp-surface);
  border: var(--border-hair) solid var(--lp-line);
  border-radius: var(--radius-pill);
  padding: 7px 16px;
  white-space: nowrap;
}

/* The headline. Serif, with the italic carrying the emphasis — that pairing IS
   the direction, not decoration on top of it.

   Not --font-display: that token still points at Cal Sans and is read in ~46
   places across the app, so repointing it would change weights site-wide. See
   the @font-face note in style.css.

   Negative tracking because Playfair sets loose at display size. line-height
   1.18 rather than --leading-tight (1.15): Vietnamese stacks diacritics above
   AND below (ệ, ự), and at 1.15 the marks on one line touch the caps on the
   next. Measured against the real string, not inherited. */
.lp-solo-name {
  margin: 0;
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: clamp(2.4rem, 6.6vw, 4.4rem);
  line-height: 1.18;
  letter-spacing: -0.015em;
  color: var(--lp-ink);
  text-align: center;
  text-wrap: balance;
  /* 16ch of Playfair at display size, not of the parent's body font — this is
     what sets the three-line break. */
  max-width: 16ch;
  margin-inline: auto;
}

/* A real italic file, not a synthesised slant. If this ever looks mechanically
   sheared rather than drawn, the italic woff2 failed to load — check
   document.fonts.check('italic 1em "Playfair Display"'). */
.lp-solo-name em {
  font-style: italic;
  font-weight: 500;
}

/* The line that says what the product actually does — the thing the landing
   page never said until now. Sans, a step down in colour, one line. */
.lp-solo-sub {
  margin: 0;
  font-family: var(--font-body);
  font-weight: var(--weight-body);
  font-size: clamp(0.95rem, 2.1vw, 1.12rem);
  line-height: var(--leading-body);
  color: var(--lp-dim);
  text-align: center;
  text-wrap: balance;
  max-width: 46ch;
  margin-inline: auto;
}

/* Undo the @media (max-width: 860px) rule above, for this page only. Hiding
   "How it works" on a phone was free when the landing page still had the two
   panels under the fold — the link was a shortcut, not the only way in. Now
   the page IS a nameplate, so that rule would leave a phone visitor with a
   logo, a login button, and no route to the product tour at all. /tutorial
   keeps the rule: its own nav links are in-page anchors you can reach by
   scrolling.

   Fitting logo + link + CTA across a 375px phone takes every one of the
   rules below, and the bar breaks in two different ways without them: let
   the label wrap and it becomes three lines and pushes the bar past the 58px
   the layout assumes; leave the default widths and the row overflows by
   ~27px and the login button runs off the right edge. Sizes are measured,
   not guessed — checked against nav.scrollWidth vs nav.clientWidth at 375px,
   the narrowest phone worth targeting. */
@media (max-width: 860px) {
  .about-body--solo .about-nav-links { display: flex; }
  .about-body--solo .about-nav-link { white-space: nowrap; padding: 9px 4px; font-size: var(--text-xs); }
  /* The logo is text, not an <svg width: 80px>. 22px was tuned for the script
     face that briefly lived here; Playfair sets wider at the same size and left
     the three nav items nearly touching at 375px. padding-top went with the
     script too — that was compensating for its high baseline, and a serif does
     not need it. */
  .about-body--solo .about-nav-logo { font-size: 19.5px; padding-top: 0; }
  .about-body--solo .about-nav { gap: 8px; }
  .about-body--solo .about-nav-cta { padding: 8px 12px; font-size: var(--text-xs); }
}


/* ---------- shared type ---------- */

/* Still a small tracked label — that IS the job of an eyebrow — but in the
   body face rather than mono, and one step down in tracking. Mono + uppercase
   + 0.11em was applied in 24 places across the site and read as shouting. */
.about-eyebrow {
  margin: 0;
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  font-size: var(--text-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--lp-dimmer);
}

.about-headline {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: clamp(2.1rem, 5.2vw, 4rem);
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--ink-950);
}


/* ---------- navbar ----------
   sticky, not fixed: it sits first in the document so it behaves identically
   on load and on scroll, but participates in layout — which is measurably
   better behaved when mobile Safari animates its address bar in and out.
   That failure class has bitten this codebase before (thesocratic.md §10).

   Painted the same colour as the page body, so at rest the bar is invisible
   and the content simply starts under a band of empty ground. What separates
   it from the page is the drop shadow, which appears only once scrolled —
   about.js toggles .is-scrolled. Any at-rest shadow or tint would defeat the
   whole effect, which is why there is none here.

   No backdrop-filter any more: the bar is fully opaque, so there is nothing
   behind it left to blur.

   Note this bar is IN FLOW (58px of it). It used to cancel itself with
   margin-bottom:-58px so a translucent bar could float over artwork — but an
   opaque body-coloured bar has nothing to float over, and with two stacked
   full-height panels the negative margin only made the first panel's height
   arithmetic harder to reason about. .lp-stage's height rules assume this. */

.about-nav {
  position: sticky;
  top: 0;
  z-index: 80;
  /* Height stays deterministic: 9 + 40 + 9 = 58 (padding + the logo svg's
     own height). .lp-stage subtracts this exact number. */
  height: 58px;
  margin: 0;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 9px clamp(16px, 4vw, 40px);
  background: var(--lp-bg);
  border: 0;
  border-radius: 0;
  box-shadow: none;
  transition: box-shadow 0.18s ease;
}

/* Set by about.js past ~4px of scroll. Two layers: a tight hairline that
   reads as an edge, and a wide soft one that reads as lift. */
.about-nav.is-scrolled {
  box-shadow: 0 1px 3px rgba(10,10,10,0.08), 0 6px 18px rgba(10,10,10,0.06);
}


/* The wordmark is live text in the script face now, not a <use> of a drawn
   <symbol>. The old rule needed pointer-events:none because hit-testing landed
   on the symbol's shadow content and the logo link was inert; text has no such
   problem, so the link works again. */
/* Live text in the shared brand face — the same lockup the landing page and
   the app header use. It was Playfair briefly, and before that a drawn SVG
   with cyan/pink ghost layers; the padding-top below is gone with the script
   face whose high baseline it was compensating for. */
.about-nav-logo {
  flex: none;
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: 23px;
  line-height: 1.15;
  color: var(--lp-ink);
  text-decoration: none;
}

.about-nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}

/* Body face, sentence case, no tracking. It used to be mono + uppercase +
   0.11em tracking — one of 24 places doing that, and a large part of why the
   chrome read as shouty. */
.about-nav-link {
  font-family: var(--font-body);
  font-weight: var(--weight-body);
  font-size: var(--text-sm);
  color: var(--lp-dim);
  text-decoration: none;
  padding: 9px 12px;
  border-radius: var(--radius-pill);
  transition: color 0.16s, background 0.16s;
}
.about-nav-link:hover { color: var(--lp-ink); background: rgba(22,24,29,0.05); }

/* Outlined, square, ink — not a crimson pill with a gloss highlight.
   The old rule was crimson fill + 999px + `inset 0 1px 0 rgba(255,255,255,.25)`,
   which is a web-2.0 glossy button; crimson is now reserved for the accent
   marks and for error semantics inside the lesson. */
.about-nav-cta {
  flex: none;
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  font-size: var(--text-sm);
  color: var(--lp-ink);
  background: transparent;
  text-decoration: none;
  padding: 8px 17px;
  border: var(--border-hair) solid var(--lp-line);
  border-radius: var(--radius-pill);
  transition: background 0.16s, color 0.16s;
}
.about-nav-cta:hover { background: var(--lp-ink); color: var(--lp-ground); }

@media (max-width: 860px) {
  .about-nav-links { display: none; }
  .about-nav { gap: 12px; }
  .about-nav-cta { margin-left: auto; }
}


/* =======================================================================
   PANELS — the annotated console, once per task type.
   -----------------------------------------------------------------------
   Two stacked .lp-stage panels, each one viewport tall: Task 2 first, then
   Task 1. Each is a head block (headline · one-line sub · CTA) above a
   browser-framed replica of the real /writing workspace on the LEFT, with
   three numbered callouts stacked in a rail on the RIGHT.

   Everything inside the frame is style.css's REAL production markup — .card,
   .essay-*, .sidebar-*, .tag, .quote-block, .summary-card, .report-*,
   .step-dot. This file only sets layout, frame chrome, and scale overrides.
   It defines no colour or type rule for any of those components, which is
   what makes the replica structurally unable to drift from the product.
   (Same doctrine and the same scaling approach the deleted tutorial.css
   used — "only sizes are touched, never the structure or the colours.")

   No motion beyond hover, all of it behind @media (hover: hover). No
   @media (prefers-reduced-motion) anywhere: every device in this project
   reports reduce-motion on, so such a block would permanently disable what
   it guards (CLAUDE.md rule 4).
   ======================================================================= */

.lp-stage {
  /* The navbar is in flow and 58px tall, so the FIRST panel only gets what is
     left of the opening viewport.
     vh first as a fallback, dvh second to win where it's understood — same
     fix as style.css's body rule. 100vh on mobile Safari doesn't account for
     the collapsible address bar, so as it hides/shows during a scroll on this
     exact page (two full-viewport panels, the ones most likely to be
     scrolled slowly and looked at) the browser re-lays-out against a height
     that just changed — a documented stutter source, and the one page here
     built entirely out of vh-sized sections. */
  min-height: calc(100vh - 58px);
  min-height: calc(100dvh - 58px);
  padding: clamp(12px, 2vh, 28px) clamp(14px, 2.4vw, 38px) clamp(10px, 1.4vh, 22px);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* flex-start, not center. Centring split a tall screen's slack evenly, which
     parked ~90px of nothing above the headline while the CTA stayed welded to
     the console 9px below it — the head block read as pushed down AND cramped
     at the same time. Anchoring to the top lifts the headline, and the console
     grid's own vh-scaled top margin (below) spends the freed height on the one
     gap that actually needed it: CTA -> console. */
  justify-content: flex-start;
}

/* Every panel after the first is a full viewport, and unlike the first one it
   scrolls UNDER the sticky bar — so it needs its own clearance, which the
   first panel gets for free by simply sitting below the bar in flow. */
.lp-stage + .lp-stage {
  min-height: 100vh;
  min-height: 100dvh;
  /* Floor is 64, not 58: it must clear the bar, and a few px of air keeps the
     headline from looking welded to it. */
  padding-top: clamp(64px, 8vh, 84px);
}


/* ---------- head block ---------- */

/* No max-width: the subheadline is meant to sit on one line, so the head
   block has to be free to be as wide as that line needs.

   margin-bottom is the FLOOR for the CTA -> console gap. The console's auto
   margins handle the generous case; this stops the button touching the frame
   when there is no slack to hand out. */
.lp-head { text-align: center; margin-bottom: clamp(16px, 2.2vh, 28px); }

.lp-headline {
  color: var(--crimson);
  /* Much smaller than the headline this page carried when it was the only
     thing on screen: a CTA and a full console now have to fit underneath it
     inside one viewport. */
  font-size: clamp(1.8rem, 3.7vw, 2.65rem);
  margin-top: 0;
}

.lp-sub {
  margin: 9px auto 0;
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--slate-600);
}

/* One line, as asked — but only once the viewport can actually hold it. The
   string is ~110 characters; below this width nowrap would push the page into
   horizontal scroll, so narrow screens keep wrapping. Breakpoint set from the
   measured line width, not guessed. */
@media (min-width: 1000px) {
  .lp-sub { white-space: nowrap; }
}

/* Press-down button, from the deleted tutorial.css's .tut-cta-btn.
   Currently UNUSED: the "Vào học" CTA was taken off both panels on
   2026-08-27 (the navbar's login button is the only way in now). Kept
   because the panels are still being iterated on and this is the button
   they would get back.

   ⚠️ THE NAME .lp-cta IS NOW LOAD-BEARING SOMEWHERE ELSE, AND STYLED
   DIFFERENTLY THERE. static/landing.css defines its own .lp-cta (red slab,
   hard offset shadow) for the rebuilt /about, and track.js binds the funnel's
   `cta_click` event to that class name on every page.

   The two definitions cannot collide TODAY only because the two stylesheets
   are never loaded by the same page: /about loads landing.css alone, and
   /tutorial loads this file alone. That is a property of the current route
   wiring, not something either file enforces. If you ever add about.css to
   the landing, or landing.css to /tutorial, whichever loads second silently
   wins and one of the two pages gets a button in the wrong language.

   Rename this one if it is ever revived. The landing's copy is the one that
   cannot be renamed — track.js is watching it. */
/* Square, solid ink, no shadow. Marc's call: do not round this button.

   What it was: crimson fill + 2.5px ink border + 999px pill + a hard
   `4px 4px 0` offset shadow, which sank 2px on :active like a toy button.
   That single element carried four of the five signals that made the site read
   as childish, all at once. */
.lp-cta {
  display: inline-block;
  margin-top: var(--space-3);
  padding: 14px 32px;
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  font-size: var(--text-base);
  color: #fff;
  text-decoration: none;
  /* Dark solid pill. All three reference pages use a dark fill rather than a
     brand-coloured one — the colour sits in the page's atmosphere, and the
     button stays the quietest possible shape that still reads as the primary
     action. Marc reversed the earlier "don't round it" call for this. */
  background: var(--lp-ink);
  border: var(--border-hair) solid var(--lp-ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-card);
}

/* ---------- the two-column stage ---------- */

.lp-console-grid {
  width: 100%;
  max-width: 1320px;
  /* auto top AND bottom: a flex item's auto margins soak up the free space, so
     whatever height the panel has left over after the head block gets split
     evenly above and below the console instead of all piling up at the bottom.
     The guaranteed minimum gap lives on .lp-head's margin-bottom, because auto
     margins collapse to 0 when there is no slack — on a short screen that
     would weld the CTA to the console again. */
  margin: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 18px;
  /* stretch, not start: the rail's three callouts are taller than the console
     was, so the console used to stop short and the rail hung below it — the
     two columns read as mismatched. Stretching makes the console grow to the
     rail's height, which both squares the bottoms off and gives the replica
     the extra size it was being denied. The flex chain below is what carries
     that height down into the frame. */
  align-items: stretch;
}

/* Console left, all three callouts in a rail on the right. The console is
   first in source order, so the stacked layout already reads console-then-
   callouts and needs no ordering hack. */
@media (min-width: 900px) {
  .lp-console-grid {
    grid-template-columns: minmax(0, 1fr) minmax(240px, 300px);
    gap: 26px;
  }
}

/* The rail's three callouts spread over the console's full height rather than
   bunching at the top, so each one sits roughly beside the part of the
   interface it is talking about. */
.lp-rail { display: flex; flex-direction: column; gap: 18px; }

@media (min-width: 900px) {
  .lp-rail { justify-content: space-between; min-height: 100%; gap: 14px; }
}


/* ---------- callouts ----------
   The label and description stay in the accessibility tree; the replica
   surfaces they wrap are aria-hidden in the markup, because they restate in
   fake numbers what the label already says in words. */

.lp-callout { position: relative; margin: 0; }

.lp-callout-label {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-lg);
  line-height: 1.25;
  color: var(--ink-950);
}

.lp-num {
  flex: none;
  width: 26px; height: 26px;
  border-radius: 999px;
  background: var(--crimson);
  color: #fff;
  font-size: 16px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.lp-callout-desc {
  display: block;
  margin: 8px 0 0;
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--slate-500);
}

/* No connector lines between the rail and the console. There were dashed
   rules with arrowheads here; they read as visual noise against the numbered
   badges, which already carry the ordering on their own. */


/* ---------- the browser frame ----------
   Ported from the deleted tutorial.css's .g-browser, which was this
   project's established "app window" chrome. The 7px offset is the ladder's
   card-level weight (login-card sat at 7, and so did .g-browser). */

/* The chain that carries the stretched column height down into the replica:
   console -> frame -> body -> columns -> the two cards' scrollable regions.
   Break any link and the frame stops short again, leaving the rail hanging. */
.lp-console { display: flex; }
.lp-browser { flex: 1; display: flex; flex-direction: column; }
.lp-browser-body { flex: 1; display: flex; flex-direction: column; }
.lp-cols { flex: 1; }
.lp-essay { display: flex; flex-direction: column; }
.lp-essay .essay-body { flex: 1; }

.lp-browser {
  background: var(--offwhite);
  /* Required, not decorative: style.css components inherit the body colour,
     which on this page is near-white — without this the real .essay-body
     renders white on white. First recorded in the deleted tutorial.css
     after it actually happened there. */
  color: var(--slate-800);
  border: var(--border-hair) solid var(--line-crisp);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.lp-browser-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 8px 13px;
  background: var(--ink-950);
}
.lp-dot { width: 9px; height: 9px; border-radius: 999px; background: var(--ink-600); }
.lp-url {
  margin-left: 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--slate-500);
  letter-spacing: 0.04em;
}

.lp-browser-body { padding: 14px 16px 16px; }

.lp-stepper {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 14.5px;
  margin-bottom: 13px;
}

.lp-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }

@media (min-width: 700px) {
  .lp-cols { grid-template-columns: minmax(0, 1.42fr) minmax(0, 1fr); align-items: stretch; }
}


/* ---------- replica scale overrides ----------
   Sizes only. No colours, no fonts, no structure. Scaled up from the round-2
   values now that the console owns the wide side of the grid. */

.lp-essay { margin-bottom: 0; overflow: hidden; }
.lp-essay .essay-head { padding: 14px 17px 10px; }
.lp-essay .essay-body { padding: 15px 17px; font-size: 17px; line-height: 1.95; }
.lp-essay .essay-body p { margin: 0 0 14px; }
.lp-essay .essay-body p:last-child { margin-bottom: 0; }
.lp-essay .essay-legend { padding: 10px 17px; font-size: 14px; gap: 15px; }
.lp-essay .essay-legend span { display: inline-flex; align-items: center; gap: 6px; }

.lp-sidebar { margin-bottom: 0; display: flex; flex-direction: column; }
.lp-sidebar .sidebar-head { padding: 11px 16px; }
/* Ink, not #fff. This tracks style.css's .sidebar-head, which was a solid navy
   bar and is white now — the replica inherits that rule, so a hardcoded white
   here rendered the round label INVISIBLE on both panels of this page. Caught
   by comparing computed colour against computed background, not by eye: white
   on white leaves no artefact to notice. */
.lp-sidebar-title {
  margin: 0;
  font-family: var(--font-display);
  color: var(--ink-950);
  font-weight: var(--weight-medium);
  font-size: 16px;
  letter-spacing: -0.01em;
}
.lp-sidebar .sidebar-body { padding: 16px; flex: 1; }
.lp-sidebar .tag { font-size: 12.5px; padding: 4px 8px; }
.lp-sidebar .quote-block { font-size: 15.5px; margin-top: 11px; padding-left: 11px; }
.lp-sidebar .question-text { font-size: 15.5px; margin-top: 12px; line-height: 1.6; }
.lp-sidebar .scope-note { font-size: 14px; }
.lp-sidebar .fix-toolbar { margin-top: 10px; }
.lp-sidebar .fix-toolbar span { font-size: 14px; }

/* Task 1 only: the chart banner, full width above the two columns. Real
   .chart-banner markup; writing-mood.css already turns its <summary> into the
   mono uppercase label. Height-capped so the chart cannot crowd out the
   workspace underneath it, exactly as style.css caps the real one. */
.lp-chart { margin-bottom: 11px; }
.lp-chart .chart-banner-head { padding: 7px 14px; }
.lp-chart .chart-banner-body { padding: 9px 14px 10px; display: flex; gap: 14px; align-items: center; }
.lp-chart .chart-banner-topic { margin: 0; font-size: 13px; line-height: 1.55; flex: 1; min-width: 0; }
.lp-chart svg { display: block; flex: none; width: 152px; height: auto; border-radius: 6px; border: 1px solid var(--slate-200); background: #fff; }

@media (max-width: 700px) {
  .lp-chart .chart-banner-body { flex-direction: column; align-items: stretch; }
  .lp-chart svg { width: 100%; }
}

/* A picture of a form, not a form: the markup already carries readonly and
   tabindex="-1", and these two make it visually inert to the mouse too. */
.lp-sidebar .correction-input {
  margin-top: 11px; font-size: 15.5px; padding: 9px 12px;
  min-height: 44px; background: #fff; pointer-events: none; resize: none;
}
.lp-sidebar .btn-submit { font-size: 15.5px; padding: 8px 17px; pointer-events: none; }

/* Beat ① — the band card. writing-mood.css supplies its ink border and
   crimson offset shadow (that stylesheet is loaded by about.html for exactly
   this reason); only the internal scale is touched here. */
.lp-mini { margin-top: 11px; }

.lp-band { min-width: 0; padding: 13px 15px; gap: 12px; display: flex; }
/* Capped, and the state pill is allowed to wrap inside it. Uncapped, the
   "Estimated · 2/4 criteria" pill sets the left column's width on one line and
   starves the right column, which stacks all four chips into a single vertical
   run and squashes the gauge. */
.lp-band-left { position: relative; flex: none; max-width: 108px; }
.lp-band-right { flex: 1; min-width: 0; }
.lp-band .band-eyebrow { font-size: 11px; }
.lp-band .band-number { font-size: 30px; }
.lp-band .band-max { font-size: 12.5px; }
.lp-band .band-state {
  font-size: 10.5px; margin-top: 4px;
  white-space: normal; line-height: 1.35;
}
.lp-band .chips-row { gap: 5px; flex-wrap: wrap; }
.lp-band .chip { font-size: 11.5px; padding: 3px 6px; }
.lp-band .gauge-track { margin-top: 9px; height: 7px; }
.lp-band .gauge-marker { width: 14px; height: 14px; top: -4px; }
.lp-band .gauge-scale { font-size: 10.5px; }

/* Beat ③ — the report. Real .report-* structure from app.js's renderReport(). */
.lp-report {
  background: #fff;
  border: var(--border-hair) solid var(--line-crisp);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 12px 15px;
}
.lp-report .report-overall { padding: 5px 0 10px; }
.lp-report .report-overall-label { font-size: 11px; }
.lp-report .report-overall-scores { font-size: 26.5px; gap: 7px; }
.lp-report .report-overall-scores .was { font-size: 18.5px; }
.lp-report .report-overall-scores .arrow { font-size: 15px; }
.lp-report .report-ceiling-line { font-size: 12.5px; margin: 5px 0 0; }
.lp-report .report-row { padding: 7px 0; border-bottom: 0; }
.lp-report .report-crit { font-size: 12.5px; }
.lp-report .report-scores { font-size: 12.5px; }
.lp-report .report-delta { font-size: 11px; }


/* ---------- highlighter ----------
   A span, not the app's .highlight-btn. Same geometry and the same category
   colours, but .highlight-btn is a <button>, and a flagged phrase in a column
   this narrow wraps across lines — an absolutely-positioned band on a
   multi-line inline box collapses to a sliver at the first fragment.
   background-image + box-decoration-break:clone gives every line fragment its
   own band, which is what the real one does. Lifted from the deleted
   tutorial.css, minus the animated sweep (nothing moves on this page). */
.lp-hl {
  display: inline;
  background-image: linear-gradient(var(--hl-c, transparent), var(--hl-c, transparent));
  background-repeat: no-repeat;
  box-shadow: inset 0 -1px 0 var(--hl-u, transparent);
  border-radius: 2px;
  padding: 0.02em 0.15em;
  margin: 0 -0.02em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.lp-hl[data-hl="logic"]    { --hl-c: var(--amber-100); --hl-u: var(--amber-300); }
.lp-hl[data-hl="language"] { --hl-c: var(--blue-100);  --hl-u: var(--blue-300); }
.lp-hl[data-hl="spelling"] { --hl-c: #fdeceb;          --hl-u: var(--crimson); }


/* ---------- hover ----------
   Gated on (hover: hover) so a touch device never sticks in a hover state —
   the same gate hub-dash.css:38-45 uses. Never a reduced-motion query. */

@media (hover: hover) {
  .lp-browser { transition: transform 0.14s ease, box-shadow 0.14s ease; }
  .lp-browser:hover {
    transform: translateY(-2px);
    border-color: var(--line-ink);
    box-shadow: var(--shadow-lift);
  }
  /* Motion gated on hover capability, never on prefers-reduced-motion — every
     machine in this project reports that on, so such a query would delete the
     effect for everyone (CLAUDE.md rule 4). */
  .lp-cta { transition: background 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease; }
  .lp-cta:hover { background: #000; border-color: #000; }
  .lp-mini > * { transition: transform 0.14s ease; }
  .lp-callout:hover .lp-mini > * { transform: translateY(-1px); }
}


/* ---------- footer ----------
   Cut down to the two links Google OAuth's consent-screen crawler needs to
   find on the public homepage (see the comment on the markup itself) —
   nothing else survives the makeover. */

/* Light footer on the same paper, separated by a hairline. It used to be a
   solid #0A0A0A slab holding a 455px-tall watermark at 5% opacity — a heavy
   block of ink under a page whose whole point is quiet. The notebook language
   separates with a rule. */
.about-foot {
  position: relative;
  background: var(--lp-bg);
  border-top: var(--border-hair) solid var(--lp-line);
  padding: var(--space-7) var(--space-5) var(--space-6);
}

/* .about-foot-watermark is gone: the footer no longer contains a giant
   low-opacity copy of the drawn wordmark, because there is no drawn wordmark
   any more (see tutorial.html). */

.about-foot-inner {
  position: relative;
  max-width: 1180px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 56px;
}

.about-foot-col { display: flex; flex-direction: column; gap: 10px; }
.about-foot-col .about-eyebrow { margin-bottom: 6px; }
.about-foot-col a {
  font-size: var(--text-sm);
  color: var(--lp-dim);
  text-decoration: none;
}
.about-foot-col a:hover { color: var(--lp-ink); }

.about-foot-copyright {
  position: relative;
  max-width: 1180px;
  margin: clamp(56px, 8vw, 90px) auto 0;
  padding-top: 22px;
  border-top: var(--border-hair) solid var(--lp-line);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--lp-dimmer);
}
