/* ============================================================================
   WRITING PRACTICE  (URL: /writing/practice)  — rendered by 27-writing-practice.js
   Theme-aware: every colour comes from the shared tokens in core/00-base.css so
   dark and light both work without a second palette.
   ============================================================================ */

/* ---- takeover: hide the rest of the site, keep the top nav, drop the footer --
   #home is a DIV, not a section, so `section:not(#x)` misses it — it needs its
   own selector or the homepage keeps rendering underneath. */
body.wpractice-mode > section:not(#writing-practice-page),
body.wpractice-mode > #home{ display:none !important; }
body.wpractice-mode{ position:relative; }
body.wpractice-mode > header{ position:relative; z-index:50; }
body.wpractice-mode > #writing-practice-page{ position:relative; z-index:1; }
/* The library and the leave-the-mock dialog are position:fixed children of the
   page, so their z-index is measured INSIDE this stacking context — at z-index:1
   the site header (z-index:50) paints straight over the top of a modal. Lift the
   whole page above the header for as long as one is open. */
body.wpractice-mode.wp-sheet-open > #writing-practice-page,
body.wpractice-mode.wp-modal-open > #writing-practice-page{ z-index:100; }
body.wpractice-mode > footer{ display:none !important; }
body.wp-sheet-open{ overflow:hidden; }

#writing-practice-page{ min-height: calc(100vh - 70px); }

.wp{ position:relative; padding: 10px 20px 90px; }
/* No page-local aurora: the site already paints a fixed `div.aurora` behind every
   route, and stacking a second blurred layer on top only muddied the cards.
   What glow there is lives *inside* the console card, where it can't wash out
   anything it isn't part of. */
.wp-wrap{ max-width:1240px; margin:0 auto; position:relative; z-index:1; }

/* ------------------------------ masthead ----------------------------------
   Two lines: who this is and how you are doing. Nothing here is a control
   except the one link that leads somewhere else. */
.wp-top{ padding:18px 0 20px; }
.wp-back{
  display:inline-block; font-size:13px; font-weight:600; color:var(--muted);
  text-decoration:none; margin-bottom:18px;
}
.wq-head{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:20px 28px; flex-wrap:wrap;
}
.wp-top h1{
  font-family:'Bricolage Grotesque','Native Emoji',sans-serif; font-weight:800;
  font-size:clamp(27px, 3vw, 38px); line-height:1.04; letter-spacing:-1.2px;
  text-align:left; margin:0;
}
.wp-grad{
  font-style:normal;
  background:linear-gradient(100deg,var(--lime) 8%,var(--cyan) 52%,var(--violet));
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
.wq-head-r{ display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
.wp-top p.wp-meta{
  display:flex; align-items:baseline; flex-wrap:wrap; gap:0 9px;
  margin:0; max-width:none; font-size:12.5px; color:var(--muted);
}
.wp-meta > span{ white-space:nowrap; }
.wp-meta > span + span::before{ content:'·'; margin-right:9px; opacity:.42; }
.wp-meta b{ color:var(--text); font-weight:700; font-variant-numeric:tabular-nums; }
/* Was a full-width dashed card of its own. It is one route out of this page,
   which is what a link is for. */
.wq-full{
  cursor:pointer; border:0; background:none; color:inherit; font:inherit;
  display:inline-flex; align-items:center; gap:7px; padding:0;
  font-size:12.5px; font-weight:700; color:var(--lime); white-space:nowrap;
}
.wq-full u{ text-decoration:none; font-weight:800; }

/* ---------------------------- resume banner -------------------------------- */
.wp-resume{
  cursor:pointer; width:100%; text-align:left; margin-bottom:14px;
  display:flex; align-items:center; gap:14px; padding:11px 15px; border-radius:12px;
  background:var(--card-bg-soft); border:1px solid color-mix(in srgb,#ffab60 32%,transparent);
  color:inherit; font:inherit;
}
.wp-resume-tx{ flex:1; min-width:0; display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; }
.wp-resume-tx em{
  font-style:normal; font-size:10.5px; font-weight:800; letter-spacing:.1em;
  text-transform:uppercase; color:#e79a4d; white-space:nowrap;
}
.wp-resume-tx b{ font-size:13.5px; font-weight:700; letter-spacing:-.2px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.wp-resume-tx > span{ font-size:12px; color:var(--muted); white-space:nowrap; }
.wp-resume-go{ flex:none; font-size:12.5px; font-weight:700; color:var(--lime); white-space:nowrap; }

/* ============================== THE TOOLBAR ================================
   Task, type and search on one line, stuck to the top. Filtering a list you
   are halfway down is pointless if the filters are a screen behind you. */
/* It floats over a field of colour, so it is glass rather than a panel: a top
   highlight along the rim, a shadow under it, and enough blur that whichever
   orb is behind it reads as a tint rather than a shape. */
.wq-bar{
  position:sticky; top:8px; z-index:20;
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  padding:8px; margin-bottom:18px; border-radius:15px;
  background:
    linear-gradient(180deg, color-mix(in srgb,var(--text) 5%,transparent), transparent 55%),
    color-mix(in srgb,var(--bg-soft) 82%,transparent);
  border:1px solid var(--card-border);
  box-shadow:0 1px 0 color-mix(in srgb,var(--text) 8%,transparent) inset,
             0 14px 30px -22px rgba(8,12,26,.7);
  backdrop-filter:blur(18px) saturate(155%);
  -webkit-backdrop-filter:blur(18px) saturate(155%);
}
/* A segmented control, so it moves like one: the pill slides between the two
   rather than blinking off one and on under the other. Same trick as .wq-seg —
   a single ::before parked under whichever half is on. */
.wq-tasks{
  position:relative; display:flex; gap:2px; padding:3px; border-radius:11px; flex:none;
  background:var(--chip-bg); border:1px solid var(--hairline);
  --gap:2px; --pad:3px; --x:0;
}
.wq-tasks::before{
  content:''; position:absolute; z-index:0; top:var(--pad); bottom:var(--pad); left:var(--pad);
  width:calc((100% - 2 * var(--pad) - var(--gap)) / 2); border-radius:8px;
  background:var(--bg-soft); box-shadow:0 1px 3px rgba(0,0,0,.22);
  transform:translateX(calc(var(--x) * (100% + var(--gap))));
}
:root[data-theme="light"] .wq-tasks::before{ background:#fff; box-shadow:0 1px 3px rgba(15,23,42,.14); }
.wq-tasks:has(> .wq-task:nth-child(2).is-on){ --x:1; }
.wq-task{
  position:relative; z-index:1;
  cursor:pointer; display:inline-flex; align-items:center; gap:7px; flex:1 1 0;
  justify-content:center;
  padding:7px 13px; border-radius:8px; border:0; background:transparent;
  color:var(--muted); font:inherit; font-size:12.5px; font-weight:700; white-space:nowrap;
}
.wq-task i{ font-style:normal; font-size:10.5px; font-weight:800; opacity:.6;
            font-variant-numeric:tabular-nums; }
.wq-task.is-on{ color:var(--text); }
/* No :has() — the pill can't track, so each half carries its own. */
@supports not selector(:has(*)){
  .wq-tasks::before{ display:none; }
  .wq-task.is-on{ background:var(--bg-soft); box-shadow:0 1px 3px rgba(0,0,0,.22); }
  :root[data-theme="light"] .wq-task.is-on{ background:#fff; box-shadow:0 1px 3px rgba(15,23,42,.13); }
}

/* One scrolling line, never a wrapped block: eight chips wrapping to three rows
   is what pushed the first topic off the screen in the old layout. */
.wq-types{
  flex:1 1 260px; min-width:0; display:flex; gap:5px; align-items:center;
  overflow-x:auto; overscroll-behavior-x:contain; scrollbar-width:none;
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 22px),transparent);
          mask-image:linear-gradient(90deg,#000 calc(100% - 22px),transparent);
}
.wq-types::-webkit-scrollbar{ display:none; }
.wq-type{
  cursor:pointer; flex:none; display:inline-flex; align-items:center; gap:6px;
  padding:7px 12px; border-radius:999px; border:1px solid transparent;
  background:transparent; color:var(--muted); font:inherit;
  font-size:12.5px; font-weight:650; white-space:nowrap;
}
.wq-type i{ font-style:normal; font-size:10.5px; font-weight:800; opacity:.55;
            font-variant-numeric:tabular-nums; }
.wq-type:hover{ color:var(--text); background:var(--chip-bg); }
.wq-type.is-on{
  color:var(--text); font-weight:750;
  background:color-mix(in srgb,var(--lime) 16%,transparent);
  border-color:color-mix(in srgb,var(--lime) 42%,transparent);
}
.wq-search{
  flex:none; width:190px; display:flex; align-items:center; gap:8px;
  padding:0 10px; border-radius:10px;
  background:var(--input-bg); border:1px solid var(--hairline);
}
.wq-search svg{ width:14px; height:14px; flex:none; fill:none; stroke:var(--muted);
                stroke-width:1.7; stroke-linecap:round; }
.wq-search input{
  flex:1; min-width:0; padding:9px 0; font-size:13px;
  background:transparent; border:0; color:var(--text);
}
.wq-search input:focus{ outline:none; }
.wq-search input::-webkit-search-cancel-button{ display:none; }
.wq-search-x{
  cursor:pointer; width:20px; height:20px; border-radius:6px; flex:none;
  background:var(--chip-bg); border:1px solid var(--hairline); color:var(--muted);
  font-size:9px; opacity:0; pointer-events:none;
}
.wq-search-x.is-on{ opacity:1; pointer-events:auto; }

.wq-count{
  display:block; margin-bottom:12px; font-size:12px; color:var(--muted);
}
.wq-count b{ color:var(--text); font-weight:800; font-variant-numeric:tabular-nums; }

/* =============================== THE INDEX ================================
   A hundred and fifty near-identical cards need three things to stop being a
   wall: a light source, a reading hierarchy, and colour that means something.

     · Light. Every card is lit from above — a top-down gradient, a hairline
       that is brighter along the top edge, and a shadow underneath. That is
       what makes a surface read as a surface instead of a flat rectangle.
     · Hierarchy. Title, then question, then metadata — three distinct sizes,
       weights and opacities, not one mush at 13px.
     · Colour. Each question type owns a hue (see --acc below). You learn
       "pie charts are violet" in seconds and stop reading labels entirely.
   ========================================================================== */

/* The grid draws the spotlight, not each card: one moving layer instead of a
   hundred and fifty fading in and out on every mousemove. */
.wq-list{
  position:relative; display:grid; gap:18px;
  grid-template-columns:repeat(auto-fill,minmax(290px,1fr));
  grid-auto-flow:row dense; align-items:stretch;
}
.wq-list::before{
  content:''; position:absolute; inset:-10px; pointer-events:none; z-index:0; opacity:0;
  background:radial-gradient(420px circle at var(--mx,50%) var(--my,50%),
             color-mix(in srgb,var(--lime) 16%,transparent), transparent 70%);
}
.wq-list.is-lit::before{ opacity:1; }

/* ---- the type palette ----------------------------------------------------
   Two stops per type, not one. `--acc` is the hue as text and hairlines use it;
   `--acc2` is the far end of the same hue family, and the pair is what the Task
   2 cover is painted with.

   A pair rather than one colour darkened, because darkening is what killed the
   first version of these covers: `color-mix(#ffb454, black)` in sRGB drops the
   chroma with the lightness, so amber landed on mud and lime landed on khaki —
   six covers that all looked like the same dead olive. Naming the far end by
   hand keeps every one of them saturated end to end. */
.wq-card{ --acc:var(--cyan); --acc2:var(--acc); }
.wq-card[data-cat="bar"]     { --acc:#5ac8fa; --acc2:#2f6fd0; }
.wq-card[data-cat="line"]    { --acc:#c8f45f; --acc2:#3f9a55; }
.wq-card[data-cat="pie"]     { --acc:#b98cff; --acc2:#6236d6; }
.wq-card[data-cat="table"]   { --acc:#ffb454; --acc2:#e0592e; }
.wq-card[data-cat="map"]     { --acc:#4fd6a8; --acc2:#1c86b8; }
.wq-card[data-cat="process"] { --acc:#ff8fa8; --acc2:#c62f6b; }
.wq-card[data-cat="mixed"]   { --acc:#7fd3ff; --acc2:#3b5bd9; }
.wq-card[data-cat="opinion"] { --acc:#c8f45f; --acc2:#3f9a55; }
.wq-card[data-cat="views"]   { --acc:#5ac8fa; --acc2:#2f6fd0; }
.wq-card[data-cat="adv"]     { --acc:#b98cff; --acc2:#6236d6; }
.wq-card[data-cat="cause"]   { --acc:#ffb454; --acc2:#e0592e; }
.wq-card[data-cat="posneg"]  { --acc:#4fd6a8; --acc2:#1c86b8; }
.wq-card[data-cat="twopart"] { --acc:#ff8fa8; --acc2:#c62f6b; }

/* ---- the card ------------------------------------------------------------ */
.wq-card{
  position:relative; z-index:1; display:flex; flex-direction:column;
  border-radius:18px; overflow:hidden;
  /* Lit from above: the surface is a shade lighter at the top than the bottom. */
  background:
    linear-gradient(180deg, color-mix(in srgb,var(--acc) 5%,transparent), transparent 42%),
    linear-gradient(180deg, rgba(255,255,255,.055), rgba(255,255,255,.012) 55%, transparent),
    var(--card-bg-soft);
  border:1px solid var(--card-border);
  /* Three layers, and the third is the point: a wide, very soft shadow in the
     card's OWN hue rather than in black. A card lit by the colour block at the
     top of it should spill a little of that colour onto the page underneath —
     that is what makes a grid of these read as lit objects instead of stickers.
     It is a coloured box-shadow and not a blurred pseudo-element on purpose:
     150 `filter:blur()` layers is a scroll that stutters. */
  box-shadow:0 1px 0 rgba(255,255,255,.05) inset,
             0 10px 26px -20px rgba(0,0,0,.9),
             0 18px 42px -30px color-mix(in srgb,var(--acc) 70%,transparent);
}
/* The accent rule. It is the card's own hue, drawn across the top edge, and it
   is the fastest thing on screen to read. */
.wq-card::before{
  content:''; position:absolute; top:0; left:0; right:0; height:2px; z-index:3;
  background:linear-gradient(90deg, var(--acc), color-mix(in srgb,var(--acc) 30%,transparent));
  transform:scaleX(.22); transform-origin:0 50%; opacity:.65;
}
/* On a Task 2 card the cover already IS the accent, and it starts 10px below
   this. A second stub of the same hue floating above it reads as a stray line
   rather than a rule, so it only draws on the way in — hover and open still
   sweep it the full width (35). */
.wq-card.is-task2::before{ opacity:0; }
.wq-card-main{
  cursor:pointer; width:100%; text-align:left; background:none; border:0;
  display:flex; flex-direction:column; padding:0; color:inherit; font:inherit;
  flex:1 1 auto; min-height:0;
}

/* ---- the figure, mounted like a print ------------------------------------
   A chart bleeding to the card edge reads as a pasted screenshot. Inset it,
   give it a warm paper tone rather than pure white, and shade the inside edge:
   now it is a figure ON the card rather than a hole THROUGH it. */
.wq-media{
  position:relative; display:block; margin:10px 10px 0; border-radius:12px;
  aspect-ratio:16/10; overflow:hidden;
  background:linear-gradient(165deg,#fbfaf7,#eceef2);
  box-shadow:inset 0 0 0 1px rgba(15,23,42,.08), inset 0 -14px 24px -18px rgba(15,23,42,.30);
}
.wq-media img{
  width:100%; height:100%; object-fit:contain; object-position:center; padding:6px;
}
/* ---- the cover a Task 2 prompt draws for itself --------------------------
   A Task 2 question has no picture. It used to be handed the same 16:10 plate
   as one that does, filled with a single 34px emoji floating in the middle of a
   violet-to-cyan wash that was the same on every card — so a hundred and fifty
   prompts arrived as a hundred and fifty identical pastel holes, each spending
   190px of the card on nothing.

   It gets a cover that is actually about the prompt instead: the question type's
   own hue as a solid colour block, a motif that belongs to that type and no
   other, a specular edge so the block reads as a lit surface, and the type name
   set on it. Shorter, too — the space the emoji was floating in goes back to the
   title and the question, which is what a student is here to read.

   None of it is an image: no request, no lazy-load, nothing to fail offline, and
   it is exact at any card width. */
.wq-card.is-task2 .wq-media{
  aspect-ratio:auto; height:96px;
  /* The pair, plus a light source at the top-left corner and a scrim along the
     foot so the type name has something to sit on whichever end of the gradient
     lands under it. Both themes get the same block: the point of a saturated
     cover is that a light page gets colour rather than another pale rectangle. */
  background:
    radial-gradient(125% 130% at 6% -25%, rgba(255,255,255,.34), transparent 58%),
    linear-gradient(0deg, rgba(6,10,22,.34), transparent 52%),
    linear-gradient(140deg, var(--acc) 0%, var(--acc2) 100%);
  /* and the block itself glows onto the card below it. The card clips the
     outer shadow at its own edge, which is exactly right — the light stays
     inside the object it belongs to. */
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.14),
             0 14px 28px -18px color-mix(in srgb,var(--acc2) 85%,transparent);
}
/* The motif layer — one per question type, and all of them faint. These are
   watermarks: a motif you notice as a pattern is a motif competing with the
   twelve titles around it. */
.wq-card.is-task2 .wq-media::before{
  content:''; position:absolute; inset:0; opacity:.72;
}
/* Agree / disagree: the arcs of a scale, swinging from off the bottom-left. */
.wq-card.is-task2[data-cat="opinion"] .wq-media::before{
  background:repeating-radial-gradient(circle at 18% 160%,
    transparent 0 17px, rgba(255,255,255,.15) 17px 18px, transparent 18px 36px);
}
/* Both views: two circles of thought, overlapping. */
.wq-card.is-task2[data-cat="views"] .wq-media::before{
  background:
    radial-gradient(circle at 34% 52%, transparent 0 34px, rgba(255,255,255,.17) 34px 35px, transparent 35px),
    radial-gradient(circle at 58% 52%, transparent 0 34px, rgba(255,255,255,.17) 34px 35px, transparent 35px);
}
/* For and against: one set of rules leaning each way, meeting in the middle. */
.wq-card.is-task2[data-cat="adv"] .wq-media::before{
  background:
    repeating-linear-gradient(120deg, rgba(255,255,255,.14) 0 1px, transparent 1px 15px) left  / 50% 100% no-repeat,
    repeating-linear-gradient(240deg, rgba(255,255,255,.14) 0 1px, transparent 1px 15px) right / 50% 100% no-repeat;
}
/* Cause and solution: everything radiating out of one point. */
.wq-card.is-task2[data-cat="cause"] .wq-media::before{
  background:repeating-conic-gradient(from 194deg at 10% 145%,
    rgba(255,255,255,.13) 0 1.3deg, transparent 1.3deg 13deg);
}
/* Positive or negative: a hard split, and a rising rule on each side of it. */
.wq-card.is-task2[data-cat="posneg"] .wq-media::before{
  background:
    linear-gradient(106deg, rgba(255,255,255,.12) 0 44%, transparent 44.2%),
    repeating-linear-gradient(106deg, transparent 0 15px, rgba(255,255,255,.10) 15px 16px);
}
/* Two questions in one: a field of pairs. */
.wq-card.is-task2[data-cat="twopart"] .wq-media::before{
  background:radial-gradient(rgba(255,255,255,.26) 1.2px, transparent 1.3px);
  background-size:15px 15px;
}
/* the specular edge, parked off to the left until the card is hovered. Its
   resting look belongs here and not in 35: that file is decoration the page is
   supposed to survive losing, so nothing in it may own how something looks
   standing still. */
.wq-card.is-task2 .wq-media::after{
  content:''; position:absolute; inset:0; transform:translateX(-45%); opacity:.55;
  background:linear-gradient(104deg, transparent 36%, rgba(255,255,255,.26) 50%, transparent 62%);
}
/* The type, set on its own colour. It is the one fact every Task 2 prompt has
   (the theme is missing on a third of them), so the cover can always carry it —
   which is why it is not repeated in the meta row below. */
.wq-media-tag{
  position:absolute; left:12px; bottom:11px; right:12px; z-index:2;
  font-size:11px; font-weight:800; letter-spacing:.13em; text-transform:uppercase;
  color:rgba(255,255,255,.94); text-shadow:0 1px 5px rgba(6,10,22,.5);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* An editorial numeral, on a frosted chip. Sitting bare on the plate it landed
   on top of whichever chart happened to carry its title in the top-left — the
   chip keeps it legible over any figure without hiding the artwork. */
.wq-media-n{
  position:absolute; top:7px; left:7px; z-index:2;
  min-width:24px; padding:3px 7px; border-radius:7px; text-align:center;
  font-family:'Bricolage Grotesque','Native Emoji',sans-serif;
  font-size:12.5px; font-weight:800; letter-spacing:-.3px; line-height:1.15;
  color:rgba(15,23,42,.62); font-variant-numeric:tabular-nums;
  background:rgba(255,255,255,.82);
  box-shadow:0 0 0 1px rgba(15,23,42,.07), 0 2px 6px -2px rgba(15,23,42,.28);
  backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
}
/* On a cover the numeral has to hold against the brightest end of the accent
   gradient, which is the corner it sits in. */
.wq-card.is-task2 .wq-media-n{
  color:#fff; background:rgba(9,12,22,.55);
  box-shadow:0 0 0 1px rgba(255,255,255,.2);
}

/* ---- the reading hierarchy ----------------------------------------------- */
.wq-card-body{ display:flex; flex-direction:column; flex:1 1 auto; padding:13px 15px 0; min-width:0; }
.wq-card-top{ display:flex; align-items:center; gap:7px; flex-wrap:wrap; margin-bottom:9px; }
.wq-badge{
  font-size:9.5px; font-weight:800; letter-spacing:.11em; text-transform:uppercase;
  padding:3px 8px; border-radius:5px; white-space:nowrap;
  color:var(--acc); background:color-mix(in srgb,var(--acc) 13%,transparent);
  border:1px solid color-mix(in srgb,var(--acc) 30%,transparent);
}
/* The type is the thing you scan for, so it is set in its own colour and given
   a dot rather than another boxed pill — two pills side by side is noise. */
/* The palette is tuned for a dark surface; on white those same hues are too
   pale to read as text. Mix them toward the ink instead of showing them raw. */
:root[data-theme="light"] .wq-badge{
  color:color-mix(in srgb,var(--acc) 55%,#0b1020);
  background:color-mix(in srgb,var(--acc) 18%,transparent);
  border-color:color-mix(in srgb,var(--acc) 45%,transparent);
}
:root[data-theme="light"] .wq-chip::before{
  background:color-mix(in srgb,var(--acc) 72%,#0b1020);
}
/* A card on white needs two shadows, not one: a tight contact shadow that says
   the card is touching the page, and a wide soft one that says it is above it.
   A single -18px blur gave it neither — which is most of why the light theme
   read as flat paper cut-outs. */
:root[data-theme="light"] .wq-card{
  background:
    linear-gradient(180deg, color-mix(in srgb,var(--acc) 7%,transparent), transparent 44%),
    linear-gradient(180deg, #fff, color-mix(in srgb,#fff 88%, var(--bg)));
  border-color:rgba(15,23,42,.09);
  box-shadow:0 1px 0 rgba(255,255,255,.9) inset,
             0 1px 2px rgba(15,23,42,.06),
             0 12px 26px -16px rgba(30,40,80,.24),
             0 20px 44px -30px color-mix(in srgb,var(--acc) 62%,transparent);
}
:root[data-theme="light"] .wq-card:hover{
  box-shadow:0 1px 0 rgba(255,255,255,.9) inset,
             0 2px 4px rgba(15,23,42,.05),
             0 30px 52px -26px rgba(30,40,80,.32),
             0 0 0 1px color-mix(in srgb,var(--acc) 34%,transparent),
             0 34px 66px -32px color-mix(in srgb,var(--acc) 80%,transparent);
}

.wq-chip{
  display:inline-flex; align-items:center; gap:6px;
  font-size:11.5px; font-weight:650; color:var(--muted); white-space:nowrap;
}
.wq-chip::before{
  content:''; width:5px; height:5px; border-radius:50%; background:var(--acc); flex:none;
}
/* The subject a Task 2 prompt is about — Education, Environment, Health. It was
   in the data all along and shown nowhere: the card spent it on picking an emoji
   and then threw it away. A third of the bank has none, so it is a tag that
   simply isn't there rather than a column that is sometimes empty. */
.wq-theme{
  font-size:10.5px; font-weight:700; letter-spacing:.01em; white-space:nowrap;
  padding:2.5px 8px; border-radius:6px; color:var(--muted);
  background:var(--chip-bg); border:1px solid var(--hairline);
}
.wq-done{
  margin-left:auto; font-size:9.5px; font-weight:800; letter-spacing:.06em;
  text-transform:uppercase; color:var(--lime); white-space:nowrap;
}
/* Set to be read, not to be counted. `text-wrap:balance` keeps a two-line title
   off a one-word second line; the tracking is loosened from -.32 because the
   old value was compensating for a weight the font could not render (see the
   font link in index.html — 750 did not exist, so this was faux-bold, and
   faux-bold is wider, which is what the negative tracking was fighting). */
.wq-card-title{
  display:block; font-size:16px; font-weight:700; letter-spacing:-.2px; line-height:1.34;
  color:var(--text); text-wrap:balance;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
/* The question, at a size a person can actually sit and read. It was 12.5px at
   82% opacity — which on white came out around 3.2:1 against the page, under
   the 4.5:1 floor for body text. Opacity is the wrong tool for "quieter": it
   fades the ink toward the paper. --muted is already the quiet colour. */
.wq-card-prev{
  display:block; margin-top:8px; font-size:13.5px; line-height:1.68; color:var(--muted);
  text-wrap:pretty;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
/* A Task 2 card spends 96px on its cover where a Task 1 card spends ~190px on a
   chart. That difference goes to the question rather than to white space. */
.wq-card.is-task2 .wq-card-prev{ -webkit-line-clamp:4; }
.wq-card-title mark, .wq-card-prev mark{
  background:color-mix(in srgb,var(--lime) 32%,transparent); color:var(--text);
  border-radius:3px; padding:0 2px;
}

/* ---- the way in ---------------------------------------------------------- */
.wq-cta{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  margin-top:auto; padding:13px 15px 14px;
  font-size:13px; font-weight:600; letter-spacing:-.1px;
  color:color-mix(in srgb, var(--text) 62%, var(--muted));
  border-top:1px solid var(--hairline);
}
.wq-cta u{ text-decoration:none; font-size:14px; font-weight:700; }
.wq-card.is-done{ opacity:.78; }
.wq-card.is-done:hover{ opacity:1; }

/* ---- the panel that opens inside the row --------------------------------
   grid-template-rows 0fr → 1fr is the one way to animate to a height nobody
   has measured; the inner element carries the overflow so the content is
   clipped rather than squashed on the way. */
.wq-setup{ display:grid; grid-template-rows:0fr; }
.wq-setup-in{ overflow:hidden; min-height:0; }
.wq-card.is-open .wq-setup{ grid-template-rows:1fr; }

/* An opened card takes the whole row. Without this the panel below is laid out
   inside one ~300px column of a four-column grid — and since its own grid asks
   for `1fr + 320px`, the question column resolves to a couple of pixels and the
   label renders one letter per line straight down the card. The panel is the one
   thing on this page that genuinely needs the width, which is why the renderer
   has always said an open row spans the grid; the rule to do it was missing. */
.wq-card.is-open{ grid-column:1 / -1; }
/* …and once it is four columns wide the cover has to stop growing with it. A
   16:10 plate becomes a 700px-tall chart; a Task 2 colour block becomes a
   1300px slab. Both get capped, and the cap is a width as well as a height —
   object-fit:contain would letterbox a 400px chart in the middle of a 1300px
   sheet of white, which is a lot of nothing to look at. */
.wq-card.is-open .wq-media{ max-width:min(560px, 100%); }
.wq-card.is-open.is-task1 .wq-media{ aspect-ratio:auto; height:min(230px, 34vh); }
.wq-card.is-open.is-task2 .wq-media{ height:66px; }
.wq-card.is-open .wq-card-title{ -webkit-line-clamp:2; }

/* Once there is room for it, an open card stops being a column and becomes the
   filmstrip the phone breakpoint has always undone: cover on the left, what it
   is beside it, the way in on the end. Capping the cover and leaving the stack
   alone would put a 500px chart at the left of a 1300px card and 800px of empty
   white to its right. */
@media (min-width: 721px){
  .wq-card.is-open .wq-card-main{ flex-direction:row; align-items:stretch; }
  .wq-card.is-open .wq-media{
    flex:none; width:min(44%, 500px); max-width:none; margin:13px 0 13px 13px;
  }
  .wq-card.is-open.is-task1 .wq-media{ aspect-ratio:16/10; height:auto; max-height:300px; }
  /* height:auto + a stretched row makes the colour block full-height: a Task 2
     cover that is 66px tall next to three lines of type reads as a leftover. */
  .wq-card.is-open.is-task2 .wq-media{ height:auto; }
  .wq-card.is-open .wq-card-body{ flex:1 1 auto; justify-content:center; padding:18px 20px; }
  /* The one card you opened out of a hundred and fifty gets to be a headline
     rather than the same 15px it was in the grid — which is also what makes the
     space beside the cover read as composition instead of a gap. */
  .wq-card.is-open .wq-card-title{ font-size:clamp(19px, 1.7vw, 25px); letter-spacing:-.65px; line-height:1.2; }
  .wq-card.is-open .wq-card-top{ margin-bottom:12px; }
  .wq-card.is-open .wq-cta{
    flex:none; align-self:center; margin-top:0; border-top:0; padding:14px 20px;
  }
}
/* The panel below sets the question out in full, under its own label. Leaving
   the two-line preview above it means reading the same sentence twice — but the
   body's only bottom spacing was that paragraph, so it has to be given back. */
.wq-card.is-open .wq-card-prev{ display:none; }
.wq-card.is-open .wq-card-body{ padding-bottom:14px; }

.wq-setup-grid{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,320px);
  gap:20px; align-items:start;
  margin:0 15px 16px; padding:16px 18px; border-radius:14px;
  background:var(--card-bg); border:1px solid var(--card-border);
}
/* Belt and braces against the same failure ever coming back: the two columns
   only make sense once the card is wide, and a card's width comes from the
   grid, not from the window — so ask the card. Only the open one is made a
   container: it is the only card that has a panel, and containment on all 150
   would be a cost paid 149 times for nothing. */
@supports (container-type: inline-size){
  .wq-card.is-open{ container-type:inline-size; }
  @container (max-width: 620px){
    .wq-setup-grid{ grid-template-columns:1fr; gap:16px; }
  }
}
.wq-setup-fig{
  position:relative; border-radius:11px; overflow:hidden; background:#fff;
  border:1px solid var(--hairline);
}
.wq-setup-fig img{ display:block; width:100%; height:auto; }
.wq-zoom{
  position:absolute; right:7px; bottom:7px; width:26px; height:26px; border-radius:8px;
  cursor:zoom-in; display:grid; place-items:center; font-size:12px; font-weight:800;
  color:#0f172a; background:rgba(255,255,255,.88); border:1px solid rgba(15,23,42,.14);
}
.wq-lbl{
  display:block; font-size:10.5px; font-weight:800; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted); margin-bottom:8px;
}
/* The open card is the full width of the grid, so this column can be 900px —
   and a question set across 900px is a question nobody's eye can track back to
   the start of. It gets a measure. */
.wq-setup-q p{ font-size:14px; line-height:1.7; color:var(--text); max-width:66ch; }
.wq-setup-ctl{ display:grid; gap:14px; align-content:start; }
.wq-field{ min-width:0; }
.wq-note{ font-size:12px; color:var(--muted); line-height:1.55; }
.wq-field small{ display:block; margin-top:8px; font-size:11.5px; color:var(--muted); }

.wq-seg{
  position:relative; display:grid; grid-template-columns:1fr 1fr; gap:2px;
  padding:3px; border-radius:11px; background:var(--chip-bg); border:1px solid var(--hairline);
  --gap:2px; --pad:3px; --x:0;
}
.wq-seg::before{
  content:''; position:absolute; z-index:0; top:var(--pad); bottom:var(--pad); left:var(--pad);
  width:calc((100% - 2 * var(--pad) - var(--gap)) / 2); border-radius:8px;
  background:var(--bg-soft); box-shadow:0 1px 3px rgba(0,0,0,.2);
  transform:translateX(calc(var(--x) * (100% + var(--gap))));
}
:root[data-theme="light"] .wq-seg::before{ background:#fff; box-shadow:0 1px 3px rgba(15,23,42,.13); }
.wq-seg:has(> .wq-seg-btn:nth-child(2).is-on){ --x:1; }
.wq-seg-btn{
  position:relative; z-index:1; cursor:pointer; padding:8px 6px; border-radius:8px;
  border:0; background:transparent; font-size:12.5px; font-weight:650; color:var(--muted);
}
.wq-seg-btn.is-on{ color:var(--text); font-weight:750; }
@supports not selector(:has(*)){
  .wq-seg::before{ display:none; }
  .wq-seg-btn.is-on{ background:var(--bg-soft); box-shadow:0 1px 2px rgba(0,0,0,.16); }
}

.wq-step{
  display:inline-flex; align-items:center; gap:2px; padding:3px; border-radius:11px;
  background:var(--chip-bg); border:1px solid var(--hairline);
}
.wq-step-btn{
  cursor:pointer; width:32px; height:30px; border-radius:8px; border:0; background:transparent;
  color:var(--muted); font-size:16px; font-weight:700; line-height:1;
}
.wq-step-val{ display:inline-flex; align-items:baseline; gap:4px; padding:0 4px; }
.wq-step-val input{
  width:34px; background:transparent; border:0; color:var(--text);
  font-size:14px; font-weight:750; text-align:right; padding:0;
  font-variant-numeric:tabular-nums;
}
.wq-step-val input:focus{ outline:none; }
.wq-step-val input::-webkit-outer-spin-button,
.wq-step-val input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
.wq-step-val input[type=number]{ -moz-appearance:textfield; appearance:textfield; }
.wq-step-val em{ font-style:normal; font-size:11.5px; color:var(--muted); }

.wq-start{
  position:relative; overflow:hidden; cursor:pointer; text-align:left;
  display:grid; grid-template-columns:1fr auto; align-items:center;
  padding:13px 18px; border-radius:14px; border:0;
  background:linear-gradient(100deg,var(--lime),color-mix(in srgb,var(--lime) 62%,var(--cyan)));
  color:var(--on-lime);
}
.wq-start b{ grid-column:1; font-size:15px; font-weight:800; letter-spacing:-.2px; }
.wq-start em{ grid-column:1; font-style:normal; font-size:11.5px; font-weight:650; opacity:.75; margin-top:2px; }
.wq-start u{ grid-column:2; grid-row:1 / span 2; text-decoration:none; font-size:19px; font-weight:800; }
.wq-start-sweep{
  position:absolute; inset:0; pointer-events:none; transform:translateX(-110%);
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.45), transparent);
}

/* ---- list tail ------------------------------------------------------------
   Not a button any more. "Show 12 more · 114 left" was a click standing between
   a student and the next twelve topics, ten times over to reach the end of the
   Task 2 pool. This is a sentinel: an observer (watchTail in 27) sees it come
   within 900px of the viewport and pulls the next page in before it can be
   scrolled to, so the list simply never ends.

   It still draws something. A list that grows out of a blank gap reads as a
   glitch — three dots say "more is coming" where nothing says "you hit the
   bottom". They are almost never on screen, because the page they announce
   lands a screen and a half before they could be reached.

   grid-column:1/-1 because both lists this sits in are grids; without it the
   dots take one 290px column and the next page opens a hole beside them. */
.wq-tail{
  grid-column:1 / -1;
  display:flex; justify-content:center; align-items:center; gap:7px;
  padding:26px 0 12px; min-height:42px;
}
.wq-tail i{ width:7px; height:7px; border-radius:50%; background:var(--muted); opacity:.3; }
.wq-mini{
  cursor:pointer; display:inline-flex; align-items:center; gap:8px;
  padding:10px 18px; border-radius:999px; font-size:12.5px; font-weight:700;
  color:var(--text); background:var(--chip-bg); border:1px solid var(--hairline);
}
.wq-mini i{ font-style:normal; font-size:11px; color:var(--muted); }
.wq-empty{ padding:44px 20px; text-align:center; }
.wq-empty b{ display:block; font-size:15px; font-weight:750; }
.wq-empty p{ margin:7px 0 16px; font-size:13px; color:var(--muted); }

/* ========================== THE BACKGROUND FIELD ===========================
   The site paints one generic `div.aurora` behind every route — three blurred
   blobs on a shared 22s loop. Behind an index of a hundred and fifty coloured
   cards that generic wash is exactly what made the page read as pastel mush, so
   this route hides it (the same move /profile makes) and paints its own.

   Three orbs on deliberately unequal periods, so the field never visibly lands
   back where it started; a mesh that fades out well above the cards, so it is
   texture behind the masthead rather than graph paper under the grid; and a
   horizon glow along the bottom to stop a half-empty last row from ending in
   flat nothing.

   Two rules this layer must not break. It moves on transform ONLY — no filter,
   no blur, no background-position — because a blurred 900px box repainting at
   60fps behind a scrolling grid is the one thing that would make this page
   stutter; the softness is in the radial gradients themselves and costs nothing.
   And it is a fixed `overflow:hidden` box, so an orb hanging off the right edge
   can never invent a horizontal scrollbar — which is the classic way a purely
   decorative layer breaks a real one. */
body.wpractice-mode > .aurora{ display:none !important; }

.wq-aurora{
  position:fixed; inset:0; pointer-events:none; z-index:0; overflow:hidden;
  opacity:.9;
}
:root[data-theme="light"] .wq-aurora{ opacity:.62; }

.wq-aurora b{
  position:absolute; display:block; border-radius:50%; aspect-ratio:1;
  will-change:transform; backface-visibility:hidden;
}
/* The three are placed to cover the viewport between them, not to decorate one
   corner of it: the layer is fixed, so wherever you are in 150 cards there is
   colour behind them rather than the flat page grey the old wash left below the
   first screen. */
.wq-aurora b:nth-child(1){
  width:clamp(520px,64vw,1020px); top:-26%; left:-16%;
  background:radial-gradient(closest-side,
             color-mix(in srgb,var(--violet) 46%,transparent), transparent 74%);
}
.wq-aurora b:nth-child(2){
  width:clamp(440px,50vw,820px); top:-4%; right:-18%;
  background:radial-gradient(closest-side,
             color-mix(in srgb,var(--lime) 34%,transparent), transparent 72%);
}
.wq-aurora b:nth-child(3){
  width:clamp(560px,70vw,1160px); top:34%; left:12%;
  background:radial-gradient(closest-side,
             color-mix(in srgb,var(--cyan) 26%,transparent), transparent 76%);
}
/* The mesh. It does not fade to nothing — it drops to a whisper and stays there,
   so the page has a surface all the way down instead of a designed masthead
   sitting on top of a blank one. */
.wq-aurora::before{
  content:''; position:absolute; inset:0;
  background-image:
    linear-gradient(90deg, color-mix(in srgb,var(--text) 6%,transparent) 1px, transparent 1px),
    linear-gradient(     color-mix(in srgb,var(--text) 6%,transparent) 1px, transparent 1px);
  background-size:62px 62px;
  -webkit-mask-image:radial-gradient(130% 78% at 50% -6%,
      #000 6%, rgba(0,0,0,.34) 54%, rgba(0,0,0,.14) 100%);
          mask-image:radial-gradient(130% 78% at 50% -6%,
      #000 6%, rgba(0,0,0,.34) 54%, rgba(0,0,0,.14) 100%);
}
/* the horizon, and a vignette to keep the corners off pure page grey */
.wq-aurora::after{
  content:''; position:absolute; inset:0;
  background:
    radial-gradient(72% 34% at 50% 122%, color-mix(in srgb,var(--lime) 20%,transparent), transparent 72%),
    radial-gradient(115% 85% at 50% 46%, transparent 52%, color-mix(in srgb,var(--violet) 11%,transparent));
}

/* ---- one essay, on its own -----------------------------------------------
   /writing/practice/essay/:id — what the reader opens in its own window. A
   reading measure, not the full page width: this is a document. */
.wq-solo{
  max-width:760px; margin:0 auto; padding:8px 0 60px;
}
.wq-solo-head{ padding-bottom:20px; margin-bottom:22px; border-bottom:1px solid var(--hairline); }
.wq-solo-head .wp-eyebrow{ margin-bottom:10px; }
.wq-solo-head h1{
  font-family:'Bricolage Grotesque','Native Emoji',sans-serif; font-weight:800;
  font-size:clamp(22px,3vw,32px); line-height:1.18; letter-spacing:-.9px; text-align:left;
}
.wq-solo-head .wp-essay-meta{ margin-top:14px; }
.wq-solo-loading{ padding:70px 0; text-align:center; color:var(--muted); font-size:14px; }
.wq-solo-miss{ color:var(--muted); font-size:14px; line-height:1.7; margin-bottom:22px; }
/* The window has no site chrome to go back to, so the actions sit left rather
   than centred like they do on the review screen. */
.wq-solo .wp-review-acts{ justify-content:flex-start; margin-top:26px; }

/* ---- your essays, below the index ----------------------------------------
   The section heading has to state its own size and alignment: 02-homepage.css
   carries a bare `h2{}` rule — unscoped, so it applies on every route — that
   would otherwise render this as a 46px centred homepage headline. */
.wq-mine{ margin-top:54px; padding-top:26px; border-top:1px solid var(--hairline); }
.wq-mine .wp-block-head{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:16px; flex-wrap:wrap; margin-bottom:18px;
}
.wq-mine .wp-block-head h2{
  font-family:'Bricolage Grotesque','Native Emoji',sans-serif; font-weight:800;
  font-size:22px; line-height:1.2; letter-spacing:-.5px; text-align:left;
}
.wq-mine .wp-block-head p{ margin-top:5px; font-size:13px; color:var(--muted); }

/* The one <select> left on this page. Native controls are the only thing here
   that would still look like the operating system rather than the site. */
.wp-select{ position:relative; display:inline-flex; align-items:center; gap:8px; }
.wp-select > span{
  font-size:10.5px; font-weight:800; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted);
}
.wp-select select{
  appearance:none; -webkit-appearance:none; cursor:pointer;
  padding:8px 30px 8px 13px; border-radius:10px;
  font-size:12.5px; font-weight:650; color:var(--text);
  background:var(--chip-bg); border:1px solid var(--hairline);
}
.wp-select select:focus{ outline:none; }
.wp-select::after{
  content:''; position:absolute; right:12px; top:50%; width:6px; height:6px;
  margin-top:-5px; pointer-events:none;
  border-right:1.7px solid var(--muted); border-bottom:1.7px solid var(--muted);
  transform:rotate(45deg);
}
.wp-select:focus-within select{
  border-color:color-mix(in srgb,var(--lime) 50%,transparent);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--lime) 12%,transparent);
}

/* The kicker above a saved essay in the reader sheet. */
.wp-eyebrow{
  display:inline-flex; align-items:center; gap:8px; font-size:11px; font-weight:800;
  letter-spacing:.13em; text-transform:uppercase; color:var(--muted);
}

/* --------------------------------- tags ----------------------------------- */
.wp-tag{
  display:inline-flex; align-items:center; gap:5px;
  font-size:11px; font-weight:800; padding:3px 9px; border-radius:999px;
  background:var(--chip-bg); border:1px solid var(--hairline); color:var(--muted);
  white-space:nowrap;
}
.wp-tag-cat{ color:var(--text); background:color-mix(in srgb,var(--violet) 14%,transparent);
             border-color:color-mix(in srgb,var(--violet) 30%,transparent); }
.wp-tag-theme{ color:var(--text); background:color-mix(in srgb,var(--cyan) 13%,transparent);
             border-color:color-mix(in srgb,var(--cyan) 30%,transparent); }
.wp-tag-mock{ color:var(--text); background:color-mix(in srgb,var(--lime) 18%,transparent);
             border-color:color-mix(in srgb,var(--lime) 40%,transparent); }
.wp-tag-casual{ color:var(--text); background:color-mix(in srgb,var(--cyan) 16%,transparent);
             border-color:color-mix(in srgb,var(--cyan) 36%,transparent); }
.wp-tag-draft{ color:var(--text); background:color-mix(in srgb,#ffab60 18%,transparent);
             border-color:color-mix(in srgb,#ffab60 38%,transparent); }
.wp-tag-done{ color:var(--text); background:color-mix(in srgb,var(--lime) 20%,transparent);
             border-color:color-mix(in srgb,var(--lime) 45%,transparent); }

/* -------------------------------- buttons --------------------------------- */
.wp-btn{
  cursor:pointer; border-radius:11px; font-weight:700; font-size:14px;
  padding:12px 20px; border:1px solid var(--hairline); background:var(--chip-bg);
  color:var(--text); text-decoration:none; display:inline-flex; align-items:center;
  justify-content:center; gap:8px;
  transition:background .18s, border-color .18s, filter .18s;
}
.wp-btn:hover{ background:var(--chip-bg-hover); border-color:var(--card-border-hi); }
.wp-btn:active{ transform:scale(.98); }
.wp-btn-primary{
  background:var(--lime); color:var(--on-lime); border-color:transparent; font-weight:750;
}
.wp-btn-primary:hover{ background:var(--lime); filter:brightness(1.07); }
.wp-btn-ghost{ background:var(--chip-bg); }
.wp-btn-sm{ padding:8px 14px; font-size:12.5px; border-radius:9px; }
.wp-mini{
  cursor:pointer; font-size:12px; font-weight:650; padding:6px 11px; border-radius:8px;
  background:transparent; border:1px solid var(--hairline); color:var(--muted);
  white-space:nowrap; transition:.16s;
}
.wp-mini:hover{ background:var(--chip-bg-hover); color:var(--text); border-color:var(--card-border-hi); }
.wp-mini-hi{ color:var(--text); border-color:var(--card-border-hi); }
.wp-mini-hi:hover{ background:color-mix(in srgb,var(--lime) 16%,transparent);
                   border-color:color-mix(in srgb,var(--lime) 45%,transparent); }
.wp-mini-danger:hover{
  color:#ff6b7a; border-color:color-mix(in srgb,#ff6b7a 40%,transparent);
  background:color-mix(in srgb,#ff6b7a 10%,transparent);
}
.wp-icon-btn{
  cursor:pointer; width:34px; height:34px; border-radius:9px; display:grid; place-items:center;
  background:var(--chip-bg); border:1px solid var(--hairline); color:var(--muted);
  font-size:14px; transition:.18s;
}
.wp-icon-btn:hover{ background:var(--chip-bg-hover); color:var(--text); }

/* ---------------------------- saved essays -------------------------------- */
.wp-mine-head{ display:flex; align-items:flex-end; justify-content:space-between; gap:16px; flex-wrap:wrap; }

.wp-essays{ display:grid; grid-template-columns:repeat(auto-fill,minmax(330px,1fr)); gap:10px; margin-top:18px; }
.wp-essay{
  position:relative; cursor:pointer; text-align:left;
  display:grid; grid-template-columns:auto minmax(0,1fr); gap:14px;
  padding:15px 16px; border-radius:14px; background:var(--card-bg-soft);
  border:1px solid var(--card-border);
  transition:border-color .2s, background .2s;
}
.wp-essay:hover{ border-color:var(--card-border-hi); background:var(--chip-bg-hover); }
.wp-essay:focus-visible{ outline:2px solid var(--lime); outline-offset:2px; }
.wp-essay.is-draft{ border-style:dashed; }
.wp-essay-ring{ position:relative; width:46px; height:46px; flex:none; }
.wp-essay-ring svg{ width:100%; height:100%; transform:rotate(-90deg); }
.wp-essay-ring circle{ fill:none; stroke-width:3; }
.wp-essay-ring circle:first-child{ stroke:color-mix(in srgb,var(--muted) 18%,transparent); }
.wp-ring-f{ stroke:#ffab60; stroke-linecap:round; transition:stroke-dashoffset .7s cubic-bezier(.2,.9,.25,1); }
.wp-essay-ring.is-hit .wp-ring-f{ stroke:var(--lime); }
.wp-essay-ring b{
  position:absolute; inset:0; display:grid; place-items:center;
  font-size:12.5px; font-weight:750; font-variant-numeric:tabular-nums; letter-spacing:-.4px;
}
.wp-essay-tx{ min-width:0; }
/* Task/mode/draft used to be three coloured pills; they are one muted line now. */
.wp-essay-top{
  display:flex; gap:10px; align-items:baseline; flex-wrap:wrap;
  font-size:11px; color:var(--muted);
}
.wp-essay-date{ margin-left:auto; }
.wp-essay h3{
  font-size:14px; font-weight:700; margin-top:7px; letter-spacing:-.2px; line-height:1.35;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.wp-essay-prompt{
  font-size:12px; color:var(--muted); line-height:1.55; margin-top:5px;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.wp-essay-meta{ display:flex; gap:12px; flex-wrap:wrap; font-size:11.5px; color:var(--muted); margin-top:10px; }
.wp-essay-meta .is-hit{ color:var(--lime); font-weight:700; }
.wp-essay-meta .is-under{ color:#ffab60; font-weight:700; }
.wp-essay-meta-lg{ font-size:13px; gap:16px; margin-bottom:18px; }
/* Not dimmed at rest — a greyed-out row of buttons reads as "disabled", which is
   the wrong message on the one card the student came here to reopen. */
.wp-essay-acts{ grid-column:2; display:flex; gap:7px; margin-top:12px; flex-wrap:wrap; }

.wp-empty{
  margin-top:16px; padding:44px 24px; border-radius:14px; text-align:center;
  background:var(--card-bg-soft); border:1px solid var(--card-border);
}
.wp-empty-ic{ font-size:26px; display:block; margin-bottom:12px; opacity:.6; }
.wp-empty b{ font-size:15px; font-weight:700; }
.wp-empty p{ margin-top:8px; color:var(--muted); font-size:13px; max-width:44ch; margin-inline:auto; line-height:1.6; }
.wp-empty .wp-btn{ margin-top:16px; }

.wp-offline{
  margin-top:20px; padding:14px 16px; border-radius:12px; display:flex; gap:12px;
  align-items:center; flex-wrap:wrap;
  background:color-mix(in srgb,#ffab60 10%,transparent);
  border:1px solid color-mix(in srgb,#ffab60 32%,transparent);
}
.wp-offline span{ color:var(--muted); font-size:13px; }
.wp-skel{ display:grid; gap:14px; padding:40px 0; }
.wp-skel span{
  height:90px; border-radius:14px; background:var(--glass);
  animation:wpPulse 1.4s ease-in-out infinite;
}
.wp-skel span:nth-child(2){ animation-delay:.15s; height:170px; }
.wp-skel span:nth-child(3){ animation-delay:.3s; height:120px; }
@keyframes wpPulse{ 0%,100%{ opacity:.45 } 50%{ opacity:.9 } }

/* ---- the essay reader (still a side sheet — it is one document) ---- */
.wp-sheet{ position:fixed; inset:0; z-index:1200; }
.wp-sheet-scrim{
  position:absolute; inset:0; background:var(--overlay); backdrop-filter:blur(4px);
  opacity:0; transition:opacity .22s;
}
.wp-sheet.is-open .wp-sheet-scrim{ opacity:1; }
.wp-sheet-panel{
  position:absolute; right:0; top:0; bottom:0; width:min(760px,100%);
  background:var(--bg-soft); border-left:1px solid var(--glass-line);
  display:flex; flex-direction:column;
  transform:translateX(24px); opacity:0; transition:transform .26s cubic-bezier(.2,.8,.2,1), opacity .26s;
  box-shadow:var(--shadow-lg);
}
.wp-sheet.is-open .wp-sheet-panel{ transform:none; opacity:1; }
.wp-sheet-head{
  display:flex; align-items:flex-start; justify-content:space-between; gap:14px;
  padding:20px 22px 14px; border-bottom:1px solid var(--hairline);
}
.wp-sheet-head h2{ font-size:20px; font-weight:800; letter-spacing:-.4px; text-align:left; }
.wp-sheet-body{ flex:1; overflow-y:auto; padding:20px 22px 30px; }

.wp-read-prompt, .wp-read-essay{ margin-bottom:22px; }
.wp-read-prompt > b, .wp-read-essay > b{
  display:block; font-size:11.5px; font-weight:800; letter-spacing:.12em;
  text-transform:uppercase; color:var(--muted); margin-bottom:8px;
}
.wp-read-prompt p{ font-size:14.5px; line-height:1.7; }
/* object-fit:contain with a cap, not a bare width:100% — a small table blown up
   to the full reading measure was mostly white space, and a cropped chart is a
   different question from the one that was set. */
.wp-read-img{
  display:block; width:100%; max-height:520px; object-fit:contain; object-position:center;
  margin-top:14px; padding:10px; border-radius:14px;
  background:#fff; border:1px solid var(--hairline);
}

/* ============================================================================
   THE ARENA — exam chrome
   Laid out like the computer-delivered test the students already sit: status
   bar (session id + time left), task rubric card, a draggable split between
   the question and the answer sheet, live word count in the corner of the
   paper, part rail along the bottom. Motion is doing real work here — the
   drain strip, the ring and the rails all say "time is passing" without
   asking anyone to read a number.
   ============================================================================ */
/* Two classes, not one: the phone breakpoint sets `.wp{ padding:8px 14px 70px }`
   and lives later in this file, so a single-class rule here loses to it and the
   arena renders inset — 8px of page scroll on the one screen that must not
   scroll at all. */
.wp.wp-arena-root{ padding:0; }

/* While the arena is open the site nav is hidden, so the exam owns the whole
   screen exactly as it does on test day. The arena's own bar carries the exit. */
body.wp-arena-open > header{ display:none !important; }
body.wp-arena-open{ overflow:hidden; }

.wpx{
  --wpx-bar: 60px;
  --wpx-line: var(--hairline);
  --wpx-paper: var(--bg-soft);
  --wpx-accent: var(--lime);
  display:flex; flex-direction:column; height:100vh; height:100dvh;
  background:var(--bg);
  position:relative;
}
:root[data-theme="light"] .wpx{ --wpx-paper:#fff; }
.wpx.is-warn { --wpx-accent:#f59e0b; }
.wpx.is-final{ --wpx-accent:#ef4444; }

/* entrance: the chrome assembles top-down, the panes arrive from their sides */
@keyframes wpxDown { from{ opacity:0; transform:translateY(-14px) } to{ opacity:1; transform:none } }
@keyframes wpxUp   { from{ opacity:0; transform:translateY(16px) }  to{ opacity:1; transform:none } }
@keyframes wpxInL  { from{ opacity:0; transform:translateX(-18px) } to{ opacity:1; transform:none } }
@keyframes wpxInR  { from{ opacity:0; transform:translateX(18px) }  to{ opacity:1; transform:none } }
@keyframes wpxFade { from{ opacity:0 } to{ opacity:1 } }
@keyframes wpxPop  { 0%{ transform:scale(.72); opacity:0 } 60%{ transform:scale(1.08) } 100%{ transform:none; opacity:1 } }

.wpx-bar   { animation: wpxDown .42s cubic-bezier(.2,.8,.2,1) both; }
.wpx-prompt{ animation: wpxInL  .5s  cubic-bezier(.2,.8,.2,1) .10s both; }
.wpx-editor{ animation: wpxInR  .5s  cubic-bezier(.2,.8,.2,1) .14s both; }
.wpx-foot  { animation: wpxUp   .45s cubic-bezier(.2,.8,.2,1) .18s both; }
.wpx-divider{ animation: wpxFade .5s ease .28s both; }

/* ---------------------------- drain strip --------------------------------- */
.wpx-strip{
  position:absolute; top:0; left:0; right:0; height:3px; z-index:5;
  background:color-mix(in srgb, var(--muted) 16%, transparent);
}
.wpx-strip i{
  display:block; height:100%; width:100%;
  background:linear-gradient(90deg, var(--wpx-accent), color-mix(in srgb,var(--wpx-accent) 55%, var(--cyan)));
  transition:width 1s linear, background .6s ease;
  box-shadow:0 0 14px -2px var(--wpx-accent);
}
.wpx.is-final .wpx-strip i{ animation:wpxStripBeat 1s ease-in-out infinite; }
@keyframes wpxStripBeat{ 0%,100%{ opacity:1 } 50%{ opacity:.45 } }

/* ------------------------------ status bar -------------------------------- */
/* The bar carries the clock, how long is left, and four controls. The Bandly
   monogram and the "🎯 Mock exam" pill that used to sit here were decoration on
   a screen where every pixel is time. */
.wpx-bar{
  flex:none; height:var(--wpx-bar); display:flex; align-items:center; gap:12px;
  padding:0 16px; border-bottom:1px solid var(--wpx-line);
  background:var(--wpx-paper);
}
.wpx-id-tx{ flex:1; min-width:0; }
.wpx-id-tx > span{
  display:block;
  font-size:13px; font-weight:500; color:var(--muted); letter-spacing:-.1px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* the number is the only part of this line anyone reads */
.wpx-id-tx b{ font-weight:700; color:var(--text); }
.wpx-bar-r{ display:flex; align-items:center; gap:6px; }

/* ---- the chronometer -----------------------------------------------------
   A dial, a sweeping head and an odometer. The rule the old build broke: the
   only thing allowed to MOVE once a second is the second hand. Digits that did
   not change must not move at all — a 2px nudge on the whole time string every
   second, an arm's length from the sentence you are writing, is a metronome
   for the eye. So the seconds ride the ring, and the one digit that turns over
   rolls inside its own clipped slot while the minutes hold dead still.

   The ring shows the MINUTE, not the session: the session already runs across
   the full width of the screen as the drain strip above, and an arc measuring
   forty minutes moves a third of a degree per tick — indistinguishable from
   broken. Mock drains it, the stopwatch fills it, from the same `sec % 60`. */
.wpx-clock{
  --wpx-r:  21px;                     /* must track the SVG circle radius */
  --wpx-c1: var(--cyan);
  --wpx-c2: var(--violet);
  position:relative; width:54px; height:54px; flex:none;
  display:grid; place-items:center; isolation:isolate;
  transition:opacity .35s ease, filter .35s ease;
}
.wpx-clock.is-count{ --wpx-c1:var(--wpx-accent); --wpx-c2:var(--cyan); }
.wpx-clock.is-warn { --wpx-c1:#fbbf24; --wpx-c2:#f59e0b; }
.wpx-clock.is-final{ --wpx-c1:#fb7185; --wpx-c2:#ef4444; }

/* the lit face — a gauge glowing from inside, not a hoop on a flat bar */
.wpx-clock::before{
  content:''; position:absolute; inset:5px; border-radius:50%; z-index:0;
  background:
    radial-gradient(circle at 50% 34%, color-mix(in srgb,var(--wpx-c1) 17%,transparent), transparent 74%),
    color-mix(in srgb, var(--text) 4%, transparent);
  transition:background .5s ease;
}
:root[data-theme="light"] .wpx-clock::before{
  background:
    radial-gradient(circle at 50% 34%, color-mix(in srgb,var(--wpx-c1) 13%,transparent), transparent 74%),
    color-mix(in srgb, var(--text) 3%, transparent);
}

.wpx-dial{
  position:absolute; inset:0; width:100%; height:100%; z-index:1;
  transform:rotate(-90deg);           /* arcs start at twelve o'clock */
  overflow:visible;
}
.wpx-g1{ stop-color:var(--wpx-c1); }
.wpx-g2{ stop-color:var(--wpx-c2); }
/* five-second marks; the sweeping head crosses one every five seconds */
.wpx-bezel line{
  stroke:color-mix(in srgb, var(--muted) 42%, transparent);
  stroke-width:1.4; stroke-linecap:round;
}
.wpx-ring-bg, .wpx-ring{ fill:none; stroke-width:3.2; }
.wpx-ring-bg{ stroke:color-mix(in srgb, var(--muted) 17%, transparent); }
.wpx-ring{
  stroke:url(#wpxGrad); stroke-linecap:round;
  filter:drop-shadow(0 0 4px color-mix(in srgb, var(--wpx-c2) 55%, transparent));
  transition:stroke-dashoffset 1s linear;
}

/* the head of the arc, riding it: an HTML dot rather than an SVG node so the
   rotation is a plain compositor transform with no transform-box guesswork */
.wpx-head{
  position:absolute; inset:0; z-index:2; pointer-events:none;
  transition:transform 1s linear;
}
.wpx-head > i{
  position:absolute; left:50%; top:50%; width:6.5px; height:6.5px;
  margin:-3.25px 0 0 -3.25px; border-radius:50%;
  transform:translateY(calc(var(--wpx-r) * -1));
  background:var(--wpx-c2);
  box-shadow:0 0 0 2px var(--wpx-paper), 0 0 9px 1px color-mix(in srgb,var(--wpx-c2) 85%,transparent);
}

/* the odometer: one clipped slot per character, only the changed ones roll */
.wpx-odo{
  position:relative; z-index:3; display:flex; align-items:center;
  font-size:14px; font-weight:800; font-variant-numeric:tabular-nums;
  letter-spacing:-.3px; line-height:1;
}
/* h:mm:ss only appears after an hour of casual stopwatch, and it is two
   characters wider than the dial — step the type down rather than let it run
   under the ring. */
.wpx-odo[data-len="7"]{ font-size:11.5px; letter-spacing:-.5px; }
.wpx-odo[data-len="8"]{ font-size:10.5px; letter-spacing:-.5px; }
.wpx-d{ position:relative; display:block; width:1ch; height:1.3em; overflow:hidden; font-style:normal; }
.wpx-sep{
  display:block; width:.4ch; text-align:center; font-style:normal;
  transform:translateY(-.04em); animation:wpxColon 2.6s ease-in-out infinite;
}
@keyframes wpxColon{ 0%,100%{ opacity:1 } 50%{ opacity:.42 } }
.wpx-dg{
  position:absolute; inset:0; display:grid; place-items:center;
  font-style:normal; line-height:1;
  transition:transform .42s cubic-bezier(.2,.9,.25,1), opacity .42s ease;
}
/* counting up, digits arrive from below; counting down, from above */
.wpx-odo[data-dir="up"]   .wpx-dg.is-in { transform:translateY(108%);  opacity:0; }
.wpx-odo[data-dir="up"]   .wpx-dg.is-out{ transform:translateY(-108%); opacity:0; }
.wpx-odo[data-dir="down"] .wpx-dg.is-in { transform:translateY(-108%); opacity:0; }
.wpx-odo[data-dir="down"] .wpx-dg.is-out{ transform:translateY(108%);  opacity:0; }

.wpx-clock.is-warn  .wpx-odo{ color:#f59e0b; }
.wpx-clock.is-final .wpx-odo{ color:#ef4444; }
/* paused: the instrument goes cold rather than half-erasing itself */
.wpx-clock.is-paused{ filter:saturate(.15); opacity:.62; }
.wpx-clock.is-paused .wpx-sep{ animation:none; }

/* the last minute is the one moment worth breaking the calm for — and it
   breaks it around the dial, never on the digits */
.wpx-clock::after{
  content:''; position:absolute; inset:-2px; border-radius:50%; z-index:0;
  opacity:0; pointer-events:none;
}
.wpx.is-final .wpx-clock::after{ animation:wpxPing 1.7s cubic-bezier(.2,.7,.3,1) infinite; }
@keyframes wpxPing{
  0%  { box-shadow:0 0 0 0 rgba(239,68,68,.42); opacity:1 }
  100%{ box-shadow:0 0 0 13px rgba(239,68,68,0); opacity:0 }
}

.wpx-ico{
  width:36px; height:36px; flex:none; border-radius:9px; cursor:pointer;
  display:grid; place-items:center; color:var(--muted);
  background:transparent; border:1px solid transparent;
  transition:background .18s, border-color .18s, color .18s;
}
.wpx-ico svg{ width:18px; height:18px; }
.wpx-ico:hover{ background:var(--chip-bg-hover); border-color:var(--hairline); color:var(--text); }
.wpx-ico:active{ transform:scale(.94); }
.wpx-ico.is-on{
  color:var(--text); background:var(--chip-bg); border-color:var(--card-border-hi);
}
.wpx-ico.is-playing{ color:var(--lime); }
.wpx-ico-quit:hover{ color:#ef4444; border-color:color-mix(in srgb,#ef4444 40%,transparent);
                     background:color-mix(in srgb,#ef4444 10%,transparent); }
.wpx.is-paused .wpx-ico[data-wp="pause"]{ box-shadow:0 0 0 3px color-mix(in srgb,var(--lime) 22%,transparent); }

/* --------------------------------- split ----------------------------------
   The arena is exactly one screen. Nothing in it scrolls except the answer
   sheet — a student hunting for the bottom of their own chart is losing exam
   time to the furniture. */
.wpx-split{
  --split:46%;
  flex:1; min-height:0; display:grid;
  grid-template-columns:var(--split) 0px 1fr;
  transition:grid-template-columns .42s cubic-bezier(.2,.9,.25,1);
}
.wpx-split.is-dragging{ transition:none; user-select:none; }
.wpx-split.is-snapping{ transition:grid-template-columns .42s cubic-bezier(.2,1.5,.35,1); }
.wpx-split.is-collapsed{ grid-template-columns:0% 0px 1fr; }
.wpx-split.is-collapsed .wpx-prompt-in{ opacity:0; transform:translateX(-10px); }
.wpx-split.is-collapsed .wpx-divider{ opacity:0; pointer-events:none; }
body.wp-resizing{ cursor:col-resize; }

.wpx-pane{ min-width:0; min-height:0; display:flex; flex-direction:column; position:relative; z-index:1; }
.wpx-prompt{ overflow:hidden; padding:16px 20px 18px 22px; }
/* The column, not the pane, is what has to fit: rubric and question take their
   natural height and the figure gets everything that is left. */
.wpx-prompt-in{
  flex:1; min-height:0; display:flex; flex-direction:column;
  transition:opacity .3s ease, transform .3s ease;
}
/* The task card used to be its own bordered panel above the split, spending
   ~80px of the one screen on two facts. */
.wpx-rubric{
  flex:none; display:flex; align-items:baseline; gap:9px; margin-bottom:10px;
  font-size:11.5px; color:var(--muted);
}
.wpx-rubric b{
  font-size:11px; font-weight:800; letter-spacing:.1em; text-transform:uppercase;
  color:var(--text);
}
.wpx-question{ flex:none; font-size:15.5px; line-height:1.62; font-weight:500; }
/* min-height:0 lets the figure shrink below the image's intrinsic height, which
   is the whole trick — without it the flex item refuses to give ground and the
   pane overflows exactly as it did before. */
/* centred, because a wide chart in a tall pane can never fill it — slack split
   top and bottom reads as composition, slack all at the bottom reads as a bug. */
.wpx-figure{
  flex:1; min-height:0; margin-top:14px;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:7px;
}
/* max-width/max-height with auto sizing, NOT width:100% + object-fit:contain.
   Both scale the picture the same way, but object-fit letterboxes *inside* the
   element box — so the white sheet stayed the full width of the pane with a
   small chart floating in the middle of it. Letting the box size itself means
   the white is exactly the picture.
   The 25px is the caption line plus the gap: max-height resolves against the
   figure, which also has to fit the caption. */
.wpx-figure img{
  min-height:0; max-width:100%; max-height:calc(100% - 25px); width:auto; height:auto;
  border-radius:10px; background:#fff; cursor:zoom-in; display:block;
  border:1px solid var(--wpx-line); padding:8px;
  transition:border-color .2s;
}
.wpx-figure img:hover{ border-color:var(--wpx-accent); }
.wpx-figure figcaption{ flex:none; font-size:11px; color:var(--muted); }
/* Task 2 has no visual, so the prompt is all there is — let it scroll if a very
   long one ever needs to, rather than clipping the question. */
.wpx-prompt-in:not(:has(.wpx-figure)){ overflow-y:auto; }
.wpx-note{
  flex:none; margin-top:14px; font-size:13px; line-height:1.65; color:var(--muted);
}

/* the divider lives in a zero-width column and paints outside it, so dragging
   never reflows the panes it sits between */
.wpx-divider{
  position:relative; width:0; display:flex; align-items:center; justify-content:center;
  transition:opacity .3s ease;
  /* the grip paints outside a zero-width column, so it has to outrank both
     panes — the editor comes later in the DOM and would cover half of it */
  z-index:4;
}
.wpx-divider::before{
  content:''; position:absolute; top:0; bottom:0; left:-1px; width:1px;
  background:var(--wpx-line); transition:background .2s;
}
.wpx-divider:hover::before,
.wpx-split.is-dragging .wpx-divider::before{ background:var(--wpx-accent); }
.wpx-grip{
  /* flex:none matters — the divider column is 0px wide, so a shrinkable grip
     collapses to a 2px sliver */
  position:relative; z-index:2; flex:none; width:34px; height:34px; border-radius:10px; cursor:col-resize;
  display:grid; place-items:center; color:var(--muted);
  background:var(--wpx-paper); border:1px solid var(--wpx-line);
  transition:transform .22s cubic-bezier(.2,1.5,.4,1), color .2s, border-color .2s, box-shadow .2s;
}
.wpx-grip svg{ width:17px; height:17px; }
.wpx-grip:hover{
  transform:scale(1.14); color:var(--text);
  border-color:var(--wpx-accent); box-shadow:0 6px 18px -8px var(--wpx-accent);
}
.wpx-split.is-dragging .wpx-grip{ transform:scale(1.2); color:var(--text); border-color:var(--wpx-accent); }

/* --------------------------------- paper ---------------------------------- */
.wpx-editor{ padding:16px 18px 14px 20px; gap:10px; }
.wpx-paper{
  position:relative; flex:1; min-height:0; display:flex; overflow:hidden;
  border-radius:12px; background:var(--wpx-paper); border:1px solid var(--wpx-line);
  transition:border-color .25s ease, box-shadow .25s ease;
}
.wpx-paper:focus-within{
  border-color:color-mix(in srgb,var(--wpx-accent) 55%,transparent);
  box-shadow:0 0 0 4px color-mix(in srgb,var(--wpx-accent) 12%,transparent);
}
.wpx-paper textarea{
  flex:1; width:100%; resize:none; border:0; background:transparent; color:var(--text);
  padding:24px 26px 52px; font-size:16.5px; line-height:1.85; font-family:inherit;
}
.wpx-paper textarea:focus{ outline:none; }
.wpx-paper textarea::placeholder{ color:var(--muted); opacity:.55; }
/* the fill is the word-count progress, drawn along the foot of the sheet */
.wpx-paper-fill{
  position:absolute; left:0; bottom:0; height:3px; width:0;
  background:var(--violet); transition:width .35s cubic-bezier(.2,.8,.2,1), background .3s;
}
.wpx-paper-fill.is-near{ background:var(--cyan); }
.wpx-paper-fill.is-hit { background:var(--lime); }
.wpx-count{
  position:absolute; right:16px; bottom:12px; display:flex; align-items:baseline; gap:6px;
  font-size:13px; color:var(--muted); pointer-events:none;
  background:linear-gradient(90deg, transparent, var(--wpx-paper) 22%);
  padding-left:26px;
}
.wpx-count b{ font-size:15px; font-weight:800; color:var(--text); font-variant-numeric:tabular-nums; }
.wpx-count i{ font-style:normal; font-size:12px; }
.wpx-count.is-hit b, .wpx-count.is-hit i{ color:var(--lime); }
.wpx-count b.is-bump{ animation:wpxBump .28s cubic-bezier(.2,1.6,.4,1); }
@keyframes wpxBump{ 0%{ transform:translateY(3px) scale(.9); opacity:.5 } 100%{ transform:none; opacity:1 } }
.wpx-count-ok{ font-weight:800; }

/* crossing the word target — one flash, then it stays quiet */
.wpx-paper.is-celebrate{ animation:wpxCelebrate 1.1s cubic-bezier(.2,.8,.2,1); }
@keyframes wpxCelebrate{
  0%   { box-shadow:0 0 0 0 color-mix(in srgb,var(--lime) 55%,transparent); border-color:var(--lime); }
  55%  { box-shadow:0 0 0 12px color-mix(in srgb,var(--lime) 0%,transparent); border-color:var(--lime); }
  100% { box-shadow:0 0 0 0 transparent; }
}

/* live prose stats — advisory, so they sit quietly under the sheet.
   The ‹ › part arrows that shared this row are gone: the Part 1 / Part 2
   buttons in the footer already switched task, so they were two more buttons
   for a job two buttons were already doing. */
.wpx-stats{
  flex:none; min-width:0; display:flex; gap:16px; flex-wrap:wrap; align-items:baseline;
  min-height:17px;
}
.wpx-stat{ display:flex; align-items:baseline; gap:5px; font-size:11px; color:var(--muted); }
.wpx-stat b{
  font-size:12.5px; font-weight:750; color:var(--text);
  font-variant-numeric:tabular-nums; letter-spacing:-.2px;
}
.wpx-stat.is-warn b{ color:#f59e0b; }
.wpx-stat.is-warn::after{ content:'!'; font-weight:800; color:#f59e0b; margin-left:-2px; }

/* -------------------------------- part rail ------------------------------- */
.wpx-foot{
  flex:none; display:flex; align-items:center; gap:10px; padding:9px 16px;
  border-top:1px solid var(--wpx-line); background:var(--wpx-paper);
}
.wpx-parts{ flex:1; display:flex; gap:8px; }
.wpx-part{
  flex:0 1 220px; position:relative; cursor:pointer; text-align:left; overflow:hidden;
  padding:8px 14px; border-radius:9px;
  background:transparent; border:1px solid var(--wpx-line);
  transition:background .2s, border-color .2s;
}
.wpx-part:hover{ background:var(--chip-bg-hover); }
.wpx-part.is-on{
  background:var(--chip-bg);
  border-color:color-mix(in srgb,var(--wpx-accent) 45%,transparent);
}
.wpx-part b{ display:block; font-size:12.5px; font-weight:700; }
.wpx-part em{ display:block; font-style:normal; font-size:11px; color:var(--muted); margin-top:1px; }
.wpx-part-rail{
  position:absolute; left:0; right:0; bottom:0; height:2px;
  background:color-mix(in srgb, var(--muted) 14%, transparent);
}
.wpx-part-rail i{
  display:block; height:100%; width:0; background:var(--wpx-accent);
  transition:width .4s cubic-bezier(.2,.8,.2,1);
}

/* A labelled button, not a 96px green slab with a tick in it — "Finish" is the
   one irreversible thing on this screen and it should say so. */
.wpx-submit{
  position:relative; flex:none; height:38px; padding:0 18px; border-radius:9px; cursor:pointer;
  display:inline-flex; align-items:center; gap:8px;
  font-size:13px; font-weight:700; color:var(--on-lime);
  background:var(--lime); border:0;
  transition:filter .18s, transform .18s;
}
.wpx-submit svg{ width:17px; height:17px; }
.wpx-submit:hover{ filter:brightness(1.06); }
.wpx-submit:active{ transform:scale(.97); }
.wpx-submit.is-sending{ transform:scale(.96); }
.wpx-submit.is-sending .wpx-tick{
  stroke-dasharray:26; stroke-dashoffset:26; animation:wpxDraw .42s ease forwards;
}
@keyframes wpxDraw{ to{ stroke-dashoffset:0 } }

/* --------------------------- part change transition ----------------------- */
.wpx.is-leaving-fwd  .wpx-split{ opacity:0; transform:translateX(-22px); transition:.15s ease; }
.wpx.is-leaving-back .wpx-split{ opacity:0; transform:translateX(22px);  transition:.15s ease; }
.wpx.is-entering-fwd  .wpx-split{ animation:wpxInR .4s cubic-bezier(.2,.8,.2,1) both; }
.wpx.is-entering-back .wpx-split{ animation:wpxInL .4s cubic-bezier(.2,.8,.2,1) both; }
.wpx.is-entering-fwd  .wpx-prompt, .wpx.is-entering-fwd  .wpx-editor,
.wpx.is-entering-back .wpx-prompt, .wpx.is-entering-back .wpx-editor{ animation:none; }

/* ------------------------------- zoom overlay ----------------------------- */
.wp-zoom{
  position:fixed; inset:0; z-index:1400; display:grid; place-items:center; padding:30px;
  background:var(--overlay); backdrop-filter:blur(6px); cursor:zoom-out;
  animation:wpFade .18s ease;
}
.wp-zoom img{
  max-width:100%; max-height:100%; border-radius:14px; background:#fff;
  animation:wpxPop .34s cubic-bezier(.2,1.2,.35,1) both;
}
.wp-zoom .wp-icon-btn{ position:absolute; top:20px; right:20px; }
@keyframes wpFade{ from{ opacity:0 } to{ opacity:1 } }

/* -------------------------------- responsive ------------------------------ */
@media (max-width: 980px){
  .wpx{ --wpx-bar:54px; }
  .wpx-bar{ padding:0 10px; gap:8px; }
  .wpx-id-tx > span{ font-size:12px; }
  .wpx-bar-r{ gap:2px; }
  /* the SVG scales itself; --wpx-r is the one measure that has to be told */
  .wpx-clock{ width:46px; height:46px; --wpx-r:17.9px; }
  .wpx-clock::before{ inset:4px; }
  .wpx-odo{ font-size:12px; }
  .wpx-odo[data-len="7"]{ font-size:9.5px; }
  .wpx-odo[data-len="8"]{ font-size:8.5px; }
  .wpx-head > i{ width:5.5px; height:5.5px; margin:-2.75px 0 0 -2.75px; }
  .wpx-ico{ width:34px; height:34px; }
  .wpx-ico svg{ width:17px; height:17px; }
  .wpx-split{ grid-template-columns:1fr !important; grid-template-rows:auto minmax(0,1fr); }
  /* A percentage row, not `auto` + a max-height: the figure below fits itself
     into the space with flex, and flex can only do that against a parent whose
     height is definite. With `auto` the row sizes to the image's natural height
     and the pane overflows — which is exactly the scrolling this pass removed. */
  /* 52%, not 40%: below about half the pane the chart scales down to a couple
     of centimetres and stops being readable, which defeats showing it at all.
     The answer sheet keeps the rest and scrolls, as a text field may. */
  .wpx-split:has(.wpx-figure){ grid-template-rows:minmax(0,52%) minmax(0,1fr); }
  .wpx-prompt{ padding:12px 14px 14px; border-bottom:1px solid var(--wpx-line); }
  .wpx-prompt-in:not(:has(.wpx-figure)){ max-height:30vh; }
  .wpx-question{ font-size:14.5px; line-height:1.55; }
  .wpx-split.is-collapsed{ grid-template-rows:0 minmax(0,1fr) !important; }
  .wpx-split.is-collapsed .wpx-prompt{ padding:0; border:0; }
  .wpx-divider{ display:none; }
  .wpx-editor{ padding:12px 14px; gap:8px; }
  /* the count moves out of the corner and onto its own line — overlaid on a
     short phone-height sheet it sat on top of the student's last sentence */
  .wpx-paper{ flex-direction:column; }
  .wpx-paper textarea{ padding:14px 15px 6px; font-size:16px; }
  .wpx-count{
    position:static; padding:7px 14px 9px; justify-content:flex-end;
    background:none; border-top:1px solid var(--wpx-line);
  }
  .wpx-stats{ gap:12px; }
  .wpx-stat em{ display:none; }
  .wpx-stat::after{ content:attr(data-short); }
  .wpx-stat.is-warn::after{ content:attr(data-short) ' !'; margin-left:0; }
  .wpx-foot{ padding:8px 10px; gap:8px; }
  .wpx-part{ flex:1; padding:7px 11px; }
}
/* Below this the bar is all controls: the clock and four buttons. "59 minutes
   remaining" only fits as "59 minute…", and the clock two centimetres to its
   left already says 58:07 — so drop the words, not the time. */
@media (max-width: 560px){
  .wpx-id-tx{ display:none; }
  .wpx-rubric{ margin-bottom:7px; }
  .wpx-submit span{ display:none; }
  .wpx-submit{ padding:0 14px; }
}
@media (max-width: 420px){
  .wpx-part b{ font-size:12px; }
  .wpx-part em{ display:none; }
}

@media (prefers-reduced-motion: reduce){
  .wpx-bar, .wpx-prompt, .wpx-editor, .wpx-foot, .wpx-divider,
  .wpx.is-entering-fwd .wpx-split, .wpx.is-entering-back .wpx-split,
  .wp-zoom img{ animation:none !important; }
  .wpx-split, .wpx-paper-fill, .wpx-part-rail i, .wpx-strip i,
  .wpx-ring, .wpx-head, .wpx-dg, .wpx-ico, .wpx-part, .wpx-submit{ transition:none !important; }
  .wpx-sep, .wpx-clock::after, .wpx-count b, .wpx-paper{ animation:none !important; }
}

/* ============================== THE REVIEW =================================
   A report, not an echo. The old screen reprinted the essay inside two
   collapsed accordions and offered four ☐ glyphs of advice, so a one-word
   answer produced a 438px card of empty gradient. Now the essay reads down the
   left and everything measurable about it is listed down the right, which is
   the only arrangement where a short answer looks *worse* rather than emptier. */
.wp-review{ padding-top:18px; }

.wp-done{
  display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  padding:16px 20px; margin-bottom:20px; border-radius:20px;
  background:var(--card-bg-soft); border:1px solid var(--card-border);
}
.wp-done-ic{ font-size:30px; flex:none; line-height:1; }
.wp-done-tx{ flex:1 1 300px; min-width:0; }
.wp-done h1{
  font-family:'Bricolage Grotesque','Native Emoji',sans-serif;
  font-weight:800; font-size:clamp(19px,2.2vw,25px); letter-spacing:-.6px; text-align:left;
}
.wp-done p{ margin-top:5px; color:var(--muted); font-size:13px; line-height:1.55; }
.wp-done-stats{ display:flex; gap:8px; flex-wrap:wrap; }
/* Self-contained on purpose: these used to reuse the masthead's `.wp-stat`, and
   lost every style the moment that section was redrawn without stat tiles. */
.wp-done-stat{
  display:flex; align-items:center; gap:9px; padding:8px 13px 8px 10px; border-radius:14px;
  background:var(--card-bg); border:1px solid var(--card-border);
}
.wp-done-stat-ic{
  font-size:14px; width:28px; height:28px; flex:none; display:grid; place-items:center;
  border-radius:9px; background:var(--glass); border:1px solid var(--hairline);
}
.wp-done-stat-tx{ display:flex; flex-direction:column; line-height:1.18; }
.wp-done-stat-tx b{ font-size:15px; font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:-.3px; }
.wp-done-stat-tx em{ font-style:normal; color:var(--muted); font-size:11px; margin-top:1px; }
/* The results screen is the last place these pills survive — the masthead used
   to carry three of them and now carries a line of text instead. */
.wp-stat{
  display:flex; align-items:center; gap:9px; padding:8px 13px 8px 10px; border-radius:11px;
  background:var(--card-bg); border:1px solid var(--card-border);
}
.wp-stat-ic{
  font-size:13px; width:26px; height:26px; flex:none; display:grid; place-items:center;
  border-radius:8px; background:var(--glass); border:1px solid var(--hairline);
}
.wp-stat-tx{ display:flex; flex-direction:column; line-height:1.2; }
.wp-stat b{ font-weight:750; font-size:15px; color:var(--text); font-variant-numeric:tabular-nums; letter-spacing:-.3px; }
.wp-stat em{ font-style:normal; color:var(--muted); font-size:11px; margin-top:1px; }

/* ---- one card per task ---- */
.wp-rv{
  border-radius:22px; overflow:hidden; margin-bottom:18px;
  background:var(--card-bg-soft); border:1px solid var(--card-border);
  box-shadow:var(--shadow-lg);
  animation:wpRise .5s cubic-bezier(.2,.8,.2,1) both; animation-delay:calc(var(--i) * 90ms);
}
.wp-rv-head{
  display:flex; align-items:center; justify-content:space-between; gap:18px; flex-wrap:wrap;
  padding:16px 20px; border-bottom:1px solid var(--hairline);
  background:linear-gradient(180deg, color-mix(in srgb,var(--violet) 7%,transparent), transparent);
}
.wp-rv-id{ min-width:0; flex:1 1 320px; }
.wp-rv-tags{ display:flex; gap:6px; flex-wrap:wrap; }
/* text-align is set here on purpose: 02-homepage.css carries a bare `h2{}` rule
   with text-align:center that applies on every page, and this heading sits in a
   shrink-to-fit box — short titles were being centred inside it. */
.wp-rv-id h2{
  margin-top:7px; font-size:18px; font-weight:800; letter-spacing:-.35px;
  line-height:1.25; text-align:left;
}

.wp-rv-score{ display:flex; align-items:center; gap:12px; flex:none; }
.wp-rv-ring{ position:relative; width:60px; height:60px; flex:none; }
.wp-rv-ring svg{ width:100%; height:100%; transform:rotate(-90deg); }
.wp-rv-ring circle{ fill:none; stroke-width:5; stroke:color-mix(in srgb,var(--muted) 20%,transparent); }
.wp-rv-ring-f{ stroke-linecap:round; transition:stroke-dashoffset 1s cubic-bezier(.2,.9,.25,1); }
.wp-rv-ring b{
  position:absolute; inset:0; display:grid; place-items:center;
  font-size:17px; font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:-.4px;
}
.wp-rv-score span{ display:flex; flex-direction:column; line-height:1.3; }
.wp-rv-score span b{ font-size:13px; font-weight:750; }
.wp-rv-score span em{ font-style:normal; font-size:11.5px; color:var(--muted); margin-top:2px; }
.wp-rv-score.is-hit  .wp-rv-ring-f{ stroke:var(--lime); }
.wp-rv-score.is-hit  .wp-rv-ring b{ color:var(--lime); }
.wp-rv-score.is-under .wp-rv-ring-f{ stroke:#ffab60; }
.wp-rv-score.is-under .wp-rv-ring b{ color:#ffab60; }

/* Findings first, full width — they are the reason the screen exists. Putting
   them in a sidebar meant a one-line answer left a 600px void beside a report
   taller than the essay it was reporting on. */
.wp-rv-findings{ padding:16px 20px 4px; }
.wp-rv-find-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(310px,1fr)); gap:9px; }

.wp-rv-body{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,320px);
  align-items:start; gap:0;
}
.wp-rv-paper{ padding:16px 20px 22px; min-width:0; }
.wp-rv-body > .wp-checks{
  margin:16px 20px 22px; align-self:start;
}
.wp-rv-label{
  display:block; font-size:10.5px; font-weight:800; letter-spacing:.13em;
  text-transform:uppercase; color:var(--muted); margin-bottom:9px;
}
.wp-rv-q{
  padding:12px 14px; border-radius:14px; margin-bottom:16px;
  background:var(--glass); border:1px solid var(--hairline);
}
.wp-rv-q p{ font-size:13px; line-height:1.6; color:var(--muted); }
.wp-essay-text{ font-size:15px; line-height:1.85; }
.wp-essay-text p + p{ margin-top:14px; }
.wp-rv-empty{ font-size:13.5px; color:var(--muted); font-style:italic; }

/* ---- findings ---- */
.wp-find{
  display:flex; gap:10px; align-items:flex-start;
  padding:10px 12px; border-radius:13px;
  background:var(--glass); border:1px solid var(--hairline);
}
.wp-find i{
  flex:none; width:19px; height:19px; border-radius:50%; display:grid; place-items:center;
  font-style:normal; font-size:11px; font-weight:900; margin-top:1px;
}
.wp-find span{ min-width:0; }
.wp-find b{ display:block; font-size:13px; font-weight:750; letter-spacing:-.15px; }
.wp-find em{ display:block; font-style:normal; font-size:12px; color:var(--muted); line-height:1.55; margin-top:3px; }
.wp-find.is-bad{
  background:color-mix(in srgb,#ff6b6b 9%,transparent);
  border-color:color-mix(in srgb,#ff6b6b 30%,transparent);
}
.wp-find.is-bad i{ background:#ff6b6b; color:#fff; }
.wp-find.is-warn{
  background:color-mix(in srgb,#ffab60 10%,transparent);
  border-color:color-mix(in srgb,#ffab60 30%,transparent);
}
.wp-find.is-warn i{ background:#ffab60; color:#3a2408; }
.wp-find.is-good{
  background:color-mix(in srgb,var(--lime) 9%,transparent);
  border-color:color-mix(in srgb,var(--lime) 28%,transparent);
}
.wp-find.is-good i{ background:var(--lime); color:var(--on-lime); }
.wp-rv-note{ font-size:11px; color:var(--muted); line-height:1.5; opacity:.85; margin-top:9px; }

/* ---- the numbers ---- */
/* A thin full-width band between the findings and the essay. Six across reads as
   an instrument panel; three across in a sidebar read as leftovers. */
.wp-rv-metrics{
  display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:1px;
  margin:12px 20px 0; border-radius:14px; overflow:hidden; background:var(--hairline);
  border:1px solid var(--hairline);
}
.wp-metric{
  padding:10px 8px; text-align:center; background:var(--card-bg);
  display:flex; flex-direction:column; gap:2px;
}
.wp-metric b{ font-size:15px; font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:-.3px; }
.wp-metric em{ font-style:normal; font-size:10px; color:var(--muted); line-height:1.3; }

/* ---- the self-check list (now actually tickable) ---- */
.wp-checks{
  padding:13px 15px; border-radius:15px;
  background:color-mix(in srgb,var(--lime) 7%,transparent);
  border:1px solid color-mix(in srgb,var(--lime) 24%,transparent);
  transition:background .3s, border-color .3s;
}
.wp-checks.is-all{
  background:color-mix(in srgb,var(--lime) 15%,transparent);
  border-color:color-mix(in srgb,var(--lime) 48%,transparent);
}
.wp-checks > b{ display:flex; align-items:center; justify-content:space-between; gap:10px; font-size:12.5px; margin-bottom:9px; }
.wp-checks-n{
  font-size:11px; font-weight:800; font-variant-numeric:tabular-nums;
  padding:2px 8px; border-radius:999px; background:var(--glass); border:1px solid var(--glass-line);
}
.wp-checks ul{ list-style:none; display:grid; gap:5px; }
.wp-checks label{
  display:flex; gap:9px; align-items:flex-start; cursor:pointer;
  font-size:12.5px; color:var(--muted); line-height:1.5;
  padding:4px 6px; border-radius:9px; transition:background .16s, color .16s;
}
.wp-checks label:hover{ background:var(--glass); color:var(--text); }
.wp-checks input{
  flex:none; width:15px; height:15px; margin-top:1px; cursor:pointer;
  accent-color:var(--lime);
}
.wp-checks li.is-on label{ color:var(--text); }
.wp-checks li.is-on span{ text-decoration:line-through; text-decoration-color:color-mix(in srgb,var(--muted) 55%,transparent); }

.wp-review-acts{ display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-top:22px; }

/* ------------------------------ responsive -------------------------------- */
/* Below this the two columns stop fitting side by side. The setup card goes
   back on top of the library and stops sticking — a sticky panel taller than
   half the window covers the thing it is meant to sit beside. */
@media (max-width: 900px){
  .wq-decks{ grid-template-columns:1fr; gap:10px; }
  .wq-deck{ padding:16px 18px 14px; }
  .wq-deck-n{ font-size:30px; }
  /* Three columns of setup beside a 66px thumbnail is three columns of nothing.
     The panel stacks: figure, question, then the controls. */
  .wq-setup-grid{ grid-template-columns:1fr; gap:16px; }
  .wq-setup-fig{ max-width:320px; }
}
@media (max-width: 720px){
  .wp{ padding:8px 14px 70px; }
  .wp-top{ padding-bottom:22px; }
  /* One card per row. An opened card is already a column here — the filmstrip
     is gated at 721px and up — so it needs no undoing; the rules that used to
     do it (`width:100%` on a box with a 10px side margin) overflowed the card
     by exactly that margin and the cover bled under the right-hand border. */
  .wq-list{ grid-template-columns:1fr; }
  .wq-setup-grid{ margin-left:8px; margin-right:8px; padding:14px 15px; }
  .wq-bar{ gap:10px; }
  .wq-count{ flex:1 1 100%; }
  .wp-essays{ grid-template-columns:1fr; }
  .wp-mine-head{ align-items:flex-start; }
  .wp-sheet-panel{ width:100%; }
  .wp-done{ padding:14px 15px; }
  .wp-rv-head, .wp-rv-paper, .wp-rv-findings{ padding-left:15px; padding-right:15px; }
  .wp-rv-metrics{ grid-template-columns:repeat(2,minmax(0,1fr)); margin-left:15px; margin-right:15px; }
  .wp-rv-body > .wp-checks{ margin-left:15px; margin-right:15px; }
}
@media (max-width: 980px){
  .wp-rv-body{ grid-template-columns:1fr; }
  .wp-rv-metrics{ grid-template-columns:repeat(3,minmax(0,1fr)); }
}

/* ---- leave-the-mock confirmation (in-page: a native confirm() would freeze
        the countdown along with the rest of the tab) ---- */
.wp-ask{ position:fixed; inset:0; z-index:1500; display:grid; place-items:center; padding:24px; }
.wp-ask-scrim{ position:absolute; inset:0; background:var(--overlay); backdrop-filter:blur(3px); }
.wp-ask-box{
  position:relative; max-width:420px; padding:26px 26px 22px; border-radius:22px;
  background:var(--bg-soft); border:1px solid var(--glass-line); box-shadow:var(--shadow-lg);
  transform:scale(.95); opacity:0; transition:transform .2s cubic-bezier(.2,1.3,.4,1), opacity .2s;
}
.wp-ask.is-open .wp-ask-box{ transform:none; opacity:1; }
.wp-ask-box b{ font-size:18px; font-weight:800; letter-spacing:-.3px; }
.wp-ask-box p{ margin-top:9px; color:var(--muted); font-size:13.5px; line-height:1.65; }
.wp-ask-acts{ display:flex; gap:10px; margin-top:20px; justify-content:flex-end; flex-wrap:wrap; }
@media (prefers-reduced-motion: reduce){ .wp-ask-box{ transition:none; opacity:1; transform:none; } }
