/* FolioAI web app styles.
 *
 * Lifted out of an inline <style> block in base.html. It lived there while the
 * app was four pages and one look; it moved here because the block was being
 * re-sent and re-parsed on every page load, and because progress.html had
 * already grown a second <style> block of its own, which is how one stylesheet
 * quietly becomes several.
 *
 * The palette is deliberately the booklet's palette. --navy is the same
 * #1F3A5F the PDF sets its headings and Class Work band in, so the site a
 * parent signs up on and the page they print look like one product. Orange is
 * an accent only, never an action colour, which is the rule the PDF cover
 * already follows.
 */

:root {
  --navy:#1F3A5F; --navy-d:#16273f; --navy-l:#2d5183;

  /* Accent, not action. The booklet cover uses orange in its illustration and
   * never for a heading or a section band, and the site follows that: navy is
   * what you click, orange is what makes it feel like a person made it.
   * --orange-d is the darker cut for anything carrying text or a thin stroke,
   * where #f0a028 on white is too pale to read. */
  --orange:#f0a028; --orange-d:#c97e0a;

  /* Two page surfaces, on purpose. --bg is the cooler grey the working pages
   * sit on. --paper is the warm cream of the booklet cover, used only where
   * someone is being introduced to the product rather than trying to get
   * something done: the landing page, sign-up, empty states. */
  --bg:#f4f6fa; --paper:#faf6ef;

  --card:#fff; --ink:#1c2434; --muted:#667085;
  --border:#e4e8f0; --shadow:0 1px 2px rgba(16,30,54,.04), 0 4px 16px rgba(16,30,54,.06);
  --radius:14px; --radius-lg:20px;

  /* ---- typefaces ----
   * Headings are set in a serif, the body in the UI sans. The site used one
   * system sans stack for everything, which is the default every unstyled web
   * app resolves to: on Windows that is Segoe UI, so Folio looked like the
   * operating system rather than like a product. A serif headline is also the
   * honest signal here, because what is being sold is a printed booklet a child
   * writes on, not a dashboard.
   *
   * Every face named is already on Windows, macOS or Linux, so nothing is
   * fetched at page load. A webfont would be a network request on the critical
   * path, and a font that arrives late moves the text after the parent has
   * started reading it. */
  --display:Georgia,"Iowan Old Style",Charter,"Bitstream Charter","Liberation Serif",serif;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;

  /* ---- type scale ----
   * Seven steps, each a clear jump from the last. The old sizes ran 11.5, 12.5,
   * 13, 13.5, 14, 14.5, 15, 16.5: eight values inside five points, so no two
   * levels looked deliberately different and nothing established a hierarchy.
   * Sizes that are nearly the same read as a mistake rather than as a rank. */
  --t-hero:46px; --t-figure:38px; --t-h1:32px; --t-h2:23px;
  --t-lead:17px; --t-body:15px; --t-small:13.5px; --t-micro:12px;

  /* ---- spacing scale ----
   * A 4px rhythm. Margins and padding were previously picked one at a time
   * (26, 22, 18, 15, 11, 9, 6, 5, 3), which is what makes a layout feel
   * approximately aligned rather than aligned. */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:64px;

  /* Status colours. These were hardcoded three times over in the pills and a
   * fourth time inline on the delete button, while --green sat declared and
   * unused. Same values, named once. */
  --green:#1B8A3A; --green-tint:#e7f6ec; --green-ink:#136b2d;
  --amber-tint:#fff3d9; --amber-ink:#8a5a00;
  --danger:#a02020; --danger-d:#8a1b1b; --danger-tint:#fdeaea;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
[hidden]{display:none!important}
body{
  font-family:var(--sans);
  font-size:var(--t-body);
  background:var(--bg);color:var(--ink);margin:0;line-height:1.55;
  -webkit-font-smoothing:antialiased;
}

/* ---- header ---- */
header{
  background:var(--navy);color:#fff;padding:0 var(--s4);
  position:sticky;top:0;z-index:20;box-shadow:0 1px 3px rgba(16,30,54,.12);
}
.headerInner{
  max-width:880px;margin:0 auto;display:flex;align-items:center;
  justify-content:space-between;gap:var(--s3);min-height:64px;flex-wrap:wrap;
}
header a{color:#fff;text-decoration:none}
.brand{
  font-family:var(--display);font-weight:700;letter-spacing:.6px;
  font-size:var(--t-h2);display:flex;align-items:baseline;gap:var(--s2);
}
.brandName em{font-style:normal;color:#93aac6}
/* The mark is now the supplied artwork rather than tokens, so the light-surface
 * variant is a different file (the app-icon tile) rather than a recolour. It
 * only needs its corners rounded to match the tile it was drawn on. */
.brandMark.onLight{border-radius:22%;display:block}
.brand .brandTag{
  font-family:var(--sans);font-weight:500;font-size:var(--t-micro);
  letter-spacing:.3px;color:#aab8cc;
}
.brandMark{flex:none;display:block;align-self:center}
.nav{display:flex;align-items:center;gap:var(--s1);flex-wrap:wrap}
.nav a{
  font-size:var(--t-small);padding:var(--s2) var(--s2);border-radius:6px;white-space:nowrap;
  border-bottom:2px solid transparent;
  transition:background .15s ease, border-color .15s ease;
}
.nav a:hover{background:rgba(255,255,255,.12)}
/* An orange rule rather than a lozenge. The lozenge read as a generic admin
 * panel; the rule is the one place the brand accent appears on every page. */
.nav a.active{font-weight:600;border-bottom-color:var(--orange)}
.mobileLabel{display:none}
button:focus-visible,.btn:focus-visible,.nav a:focus-visible,.brand:focus-visible,
summary:focus-visible,a.link:focus-visible{
  outline:3px solid rgba(240,160,40,.65);outline-offset:3px;
}
.skipLink{
  position:fixed;top:8px;left:8px;z-index:100;background:#fff;color:var(--navy);
  padding:var(--s2) var(--s3);border-radius:7px;font-weight:700;transform:translateY(-160%);
}
.skipLink:focus{transform:translateY(0);outline:3px solid var(--orange)}

/* ---- layout ---- */
.wrap{max-width:880px;margin:var(--s5) auto var(--s8);padding:0 var(--s4)}
.card{
  background:var(--card);border:1px solid var(--border);border-radius:var(--radius);
  padding:var(--s5);box-shadow:var(--shadow);margin-bottom:var(--s4);
}
.card.narrow{max-width:440px;margin-left:auto;margin-right:auto}
/* Warm surface, for cards that introduce the product rather than run it. */
.card.warm{background:var(--paper);border-radius:var(--radius-lg);border-color:#ece3d4}
.card.danger{background:var(--danger-tint);border-color:#f3cfcf}

/* Headings carry the serif and a normal weight. The old h1 was 800 with
 * negative tracking, which is the standard way to make a system sans look
 * deliberate; a serif does not need the help, and at 800 it turns muddy. */
h1{
  font-family:var(--display);font-weight:700;
  margin:0 0 var(--s2);font-size:var(--t-h1);letter-spacing:-.2px;line-height:1.15;
}
h2{
  font-family:var(--display);font-weight:700;
  margin:0 0 var(--s3);font-size:var(--t-h2);letter-spacing:-.1px;line-height:1.25;
}
/* A measure. Without one an intro line runs the full 848px column, which is
 * about 100 characters and is genuinely hard to track back to the next line. */
.sub{
  color:var(--muted);margin:0 0 var(--s5);font-size:var(--t-lead);
  line-height:1.55;max-width:58ch;
}

/* ---- footer ---- */
footer{
  max-width:880px;margin:0 auto;padding:var(--s5) var(--s4) var(--s6);
  color:var(--muted);font-size:var(--t-small);display:flex;gap:var(--s3) var(--s5);
  align-items:center;justify-content:space-between;flex-wrap:wrap;
}
footer a{color:var(--muted);font-weight:600}
.footerLinks{display:flex;gap:var(--s3)}
.navForm{margin:0}
.navForm button{
  width:auto;margin:0;padding:var(--s2) var(--s2);background:transparent;border-radius:6px;
  border-bottom:2px solid transparent;font-weight:400;font-size:var(--t-small);
}
.navForm button:hover{background:rgba(255,255,255,.12)}

/* ---- forms ---- */
label{display:block;font-size:var(--t-small);font-weight:600;margin:var(--s4) 0 var(--s1);color:#3b4658}
input,select,textarea{
  width:100%;padding:var(--s3) var(--s3);border:1px solid #d6dce8;border-radius:9px;
  font:inherit;font-size:var(--t-body);background:#fff;color:var(--ink);
  transition:border-color .15s ease, box-shadow .15s ease;
}
textarea{resize:vertical;min-height:88px;line-height:1.5}
select{
  appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23667085' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 13px center;padding-right:var(--s6);
}
input:focus,select:focus,textarea:focus{
  outline:none;border-color:var(--navy-l);box-shadow:0 0 0 3px rgba(45,81,131,.13);
}
.row{display:grid;grid-template-columns:1fr 1fr;gap:var(--s3)}
@media (max-width:560px){ .row{grid-template-columns:1fr} }

button,.btn{
  background:var(--navy);color:#fff;border:0;padding:var(--s3) var(--s4);border-radius:9px;
  font:inherit;font-weight:600;font-size:var(--t-body);cursor:pointer;width:100%;
  text-align:center;display:inline-block;text-decoration:none;margin-top:var(--s4);
  transition:background .15s ease, transform .06s ease;
}
button:hover,.btn:hover{background:var(--navy-d)}
button:disabled{background:#aab4c3;cursor:not-allowed;transform:none}
button:active,.btn:active{transform:translateY(1px)}
.btn.secondary{
  background:#fff;color:var(--navy);border:1px solid #d6dce8;
}
.btn.secondary:hover{background:#f7f9fc}
.btn.small{width:auto;padding:var(--s2) var(--s3);font-size:var(--t-small);margin:0}
/* Replaces a hardcoded inline background on the delete button. */
.btn.danger{background:var(--danger)}
.btn.danger:hover{background:var(--danger-d)}

.checkbox{display:flex;align-items:flex-start;gap:var(--s2);margin-top:var(--s4)}
.checkbox input{width:auto;margin-top:var(--s1);flex:none}
.checkbox label{margin:0;font-weight:500;font-size:var(--t-small);color:var(--ink)}

/* ---- create flow ---- */
.formSection{border:0;border-top:1px solid var(--border);padding:var(--s5) 0 0;margin:var(--s5) 0 0}
.formSection legend{
  padding:0 var(--s3) 0 0;font-size:var(--t-lead);font-weight:700;color:var(--ink);
  display:flex;align-items:center;gap:var(--s2);
}
.stepBadge{
  width:25px;height:25px;border-radius:50%;display:inline-grid;place-items:center;
  background:var(--navy);color:#fff;font-size:var(--t-micro);font-weight:800;
}
.programGrid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s3);margin-top:var(--s4)}
.programOption{position:relative;min-width:0}
.programOption input{
  position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;
}
.programOption label{
  position:relative;height:100%;min-height:105px;margin:0;padding:var(--s4) var(--s6) var(--s4) var(--s4);
  display:flex;align-items:flex-start;gap:var(--s3);border:1px solid #d6dce8;
  border-radius:12px;background:#fff;color:var(--ink);cursor:pointer;
  transition:border-color .15s ease,box-shadow .15s ease,background .15s ease,transform .08s ease;
}
.programOption label:hover{border-color:#aebbd0;background:#fbfcfe;transform:translateY(-1px)}
.programOption input:focus-visible + label{
  outline:3px solid rgba(45,81,131,.18);outline-offset:2px;border-color:var(--navy-l);
}
.programOption input:checked + label{
  border-color:var(--navy);background:#f5f8fc;box-shadow:0 0 0 1px var(--navy);
}
.programIcon{flex:none;margin-top:var(--s1)}
.programOption strong{display:block;font-size:var(--t-body);line-height:1.35;margin-bottom:var(--s1)}
/* small sits inside a label, and label is font-weight:600, so this blurb
  * inherited bold while the identical blurb in .termChoice (which sets 400)
  * did not. Two of the same control, two different weights, side by side. */
.programOption small{display:block;color:var(--muted);font-size:var(--t-micro);font-weight:400;line-height:1.4}
.choiceMark{
  position:absolute;top:15px;right:14px;width:18px;height:18px;border:1.5px solid #b9c2d1;
  border-radius:50%;background:#fff;
}
.programOption input:checked + label .choiceMark{border:5px solid var(--navy)}
.fieldHelp{margin:var(--s1) 0 0;color:var(--muted);font-size:var(--t-micro)}

.termChoice{position:relative;margin-top:var(--s5)}
.termChoice input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.termChoice label{
  margin:0;padding:var(--s4);display:flex;align-items:flex-start;gap:var(--s3);
  border:1px solid #d6dce8;border-radius:12px;background:#f7f9fc;cursor:pointer;
  transition:border-color .15s ease,box-shadow .15s ease,background .15s ease;
}
.termChoice label:hover{border-color:#aebbd0}
.termChoice input:focus-visible + label{
  outline:3px solid rgba(45,81,131,.18);outline-offset:2px;border-color:var(--navy-l);
}
.termChoice input:checked + label{
  border-color:var(--navy);background:#f2f6fb;box-shadow:0 0 0 1px var(--navy);
}
.termChoice strong{display:block;font-size:var(--t-small);line-height:1.35;margin-bottom:var(--s1)}
.termChoice small{display:block;color:var(--muted);font-size:var(--t-micro);font-weight:400;line-height:1.45}
.termCheck{
  width:20px;height:20px;border:1.5px solid #aeb8c8;border-radius:5px;background:#fff;
  flex:none;position:relative;margin-top:var(--s1);
}
.termChoice input:checked + label .termCheck{background:var(--navy);border-color:var(--navy)}
.termChoice input:checked + label .termCheck::after{
  content:"";position:absolute;left:5px;top:2px;width:6px;height:10px;
  border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);
}

/* The one claim on the landing page that no competitor can copy used to be set
 * in .hint: grey, 13.5px, in a pale box with a warning bar down its left edge.
 * That styling means "caution, small print" everywhere else on the web, so the
 * strongest thing Folio can say was the quietest thing on the page. */
.claim{
  border-top:1px solid var(--border);margin-top:var(--s6);padding-top:var(--s5);
  font-family:var(--display);font-size:var(--t-h2);line-height:1.35;
  color:var(--ink);
}

/* ---- misc ---- */
.flash{
  background:#fff8e6;color:#7c5b00;border:1px solid #fde68a;
  padding:var(--s3) var(--s3);border-radius:10px;margin-bottom:var(--s4);font-size:var(--t-small);
}
.muted{color:var(--muted);font-size:var(--t-small)}
a.link{color:var(--navy);font-weight:600}
/* Legal boilerplate directly under the primary CTA was the heaviest text in
 * the card, because .link is 600 and the browser underlines it. */
.legalNote{font-size:var(--t-small);color:var(--muted);margin:var(--s3) 0 0}
.legalNote a{color:var(--muted);font-weight:400}
.hint{
  background:#f7f9fc;border:1px solid var(--border);
  border-left:3px solid var(--orange);border-radius:0 10px 10px 0;
  padding:var(--s3) var(--s3);font-size:var(--t-small);color:var(--muted);margin-top:var(--s4);
}


/* ---- field sizes ----
 * A control as wide as the card tells the user nothing about what belongs in
 * it, and an 848px box for a first name is the clearest sign a form was
 * generated rather than designed. Width is a label. */
.fieldName{max-width:18rem}
.fieldWide{max-width:32rem}
.formSection .row{max-width:32rem}
/* When the subject select is hidden for a program that has no subject choice,
 * the grid used to leave a half-width hole where it had been. */
.row:has(> [hidden]){grid-template-columns:minmax(0,18rem)}

/* The rule ran only to the right of the legend and stopped at the card edge,
 * which looked like a rendering fault rather than a divider. Sections are
 * separated by space. */
.formSection{border-top:0;padding-top:0;margin-top:var(--s7)}

/* The banner put the number and the link at opposite ends of an 848px bar. */
.creditBanner{justify-content:flex-start;gap:var(--s5)}
.creditBanner.centered{justify-content:center}

/* ---- history list ---- */
.jobs{list-style:none;margin:0;padding:0}
.jobItem{
  display:flex;align-items:center;gap:var(--s3);padding:var(--s4) var(--s1);
  border-bottom:1px solid var(--border);
}
.jobItem:last-child{border-bottom:0}
.jobMain{flex:1;min-width:0}
.jobTitle{font-weight:600;font-size:var(--t-body);margin:0 0 var(--s1);word-break:break-word}
.jobMeta{color:var(--muted);font-size:var(--t-micro);display:flex;align-items:center;flex-wrap:wrap;gap:var(--s1)}
.pill{
  display:inline-block;border-radius:999px;padding:var(--s1) var(--s2);
  font-size:var(--t-micro);font-weight:700;letter-spacing:.2px;
}
.pill.done{background:var(--green-tint);color:var(--green-ink)}
.pill.running{background:var(--amber-tint);color:var(--amber-ink)}
.pill.error{background:var(--danger-tint);color:var(--danger)}
.empty{text-align:center;padding:var(--s6) var(--s2);color:var(--muted)}
.empty p{margin:0 0 var(--s4)}
/* An empty library is a first impression too, so it gets the warm surface and
 * the motifs rather than being a blank apology. */
.emptyWarm{
  position:relative;overflow:hidden;background:var(--paper);
  border:1px solid #ece3d4;border-radius:var(--radius-lg);
  padding:var(--s7) var(--s4);
}
/* No hover highlight: the row is not a link and never was, so lighting it up
 * promised a click that does nothing. The actions in the row are the controls. */
.pill.gone{background:#eef1f6;color:var(--muted)}
.jobMeta .pill{margin-right:var(--s2)}

/* ---- motifs ----
 * Drawn in templates/_motifs.html, coloured from here. They inherit
 * currentColor, so the same macro is navy in a header and pale grey behind an
 * empty state without a second copy of the file.
 */
.motif{display:block}
.ink-navy{color:var(--navy)}
.ink-orange{color:var(--orange)}
.ink-pale{color:#c8d4e6}

/* A row of motifs above a heading, for pages that would otherwise open cold. */
.motifRow{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s3)}

/* Corner scatter behind a panel. Deliberately only in the corners and behind
 * nothing readable: the booklet cover frames its title the same way, leaving
 * the middle of the page clear. */
.scatter{position:absolute;inset:0;pointer-events:none;overflow:hidden}
.scatter .s{position:absolute;opacity:.5}
.scatter .s1{top:18px;left:20px;transform:rotate(-8deg)}
.scatter .s2{top:34px;right:74px;transform:rotate(12deg)}
.scatter .s3{top:96px;right:22px}
.scatter .s4{bottom:74px;left:26px;transform:rotate(6deg)}
.scatter .s5{bottom:10px;right:30px;transform:rotate(-10deg)}
.scatter .s6{top:112px;left:78px}
.scatter .s7{bottom:26px;left:104px;transform:rotate(10deg)}
.scatter .s8{top:64px;left:150px}
@media (max-width:700px){
  /* On a phone the panel is narrow enough that the scatter crowds the words. */
  .scatter .s6,.scatter .s7,.scatter .s8,.scatter .s2{display:none}
  .scatter .s{opacity:.38}
}

/* ---- paulio ----
 * MASCOT_GUIDE.md sets two size bands and says which CSS pixel range each
 * renders in on desktop: standard poses 160-280px, desk scenes 260-420px.
 * The files themselves ship at 2x whatever width is set here (see
 * scripts/build_paulio_assets.py), so this is the one place that number is
 * chosen; the asset stays sharp on a retina screen either way.
 *
 * No transform is ever applied to a Paulio image. The guide is explicit that
 * an asset must never be CSS-mirrored: the pencil has to stay behind his
 * anatomical left ear, and mirroring would put it on the wrong side. Facing
 * the other way means loading the file drawn facing that way, not flipping
 * this one.
 */
.paulioStd{display:block;width:220px;max-width:100%;height:auto;flex:none}
/* Above the hero headline rather than beside it: heroInner is a single
 * centred 560px column, and a side-by-side layout would squeeze the
 * headline into a column too narrow for --t-hero to wrap cleanly. */
.paulioIntro{width:150px;margin:0 auto var(--s2);display:block;height:auto}
@media (max-width:700px){ .paulioIntro{width:120px} }
.paulioDesk{display:block;width:320px;max-width:100%;height:auto;flex:none;margin:0 auto var(--s2)}
@media (max-width:700px){
  .paulioStd{width:170px}
  .paulioDesk{width:240px}
}

/* A speech bubble under whichever Paulio image precedes it, so a line reads
 * as him talking rather than as a caption printed near him. One tail
 * direction only: everywhere he appears now pairs an image with a bubble
 * directly below it (see .paulioWith for the two-column placements), so
 * there was never a need for a second, sideways-pointing variant. */
.paulioBubble{
  /* Self-centering rather than inline-block relying on a centred parent:
   * the hero and pricing intro are text-align:center, but .card is not, and
   * this is used inside both. */
  display:block;width:fit-content;max-width:230px;margin:var(--s2) auto 0;
  position:relative;
  background:#fff;border:1px solid var(--border);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:var(--s2) var(--s3);
  font-size:var(--t-small);color:var(--ink);line-height:1.4;text-align:left;
}
.paulioBubble::before,.paulioBubble::after{
  content:"";position:absolute;left:50%;transform:translateX(-50%);
  border-left:8px solid transparent;border-right:8px solid transparent;
}
.paulioBubble::before{top:-9px;border-bottom:9px solid var(--border)}
.paulioBubble::after{top:-8px;border-bottom:8px solid #fff}

/* Pairs one Paulio image with the bubble under it as a single unit, for the
 * two-column layouts where he sits beside a block of unrelated page text
 * rather than alone in a centred column. */
.paulioWith{display:flex;flex-direction:column;align-items:center;flex:none}

/* Flush with the top edge of whatever it sits on, per the guide. The file is
 * drawn upside down already, so this only positions it, never rotates it.
 * Right-aligned and clear of the heading row on purpose: a first version sat
 * left-aligned at the same spot the card's own heading starts, landing his
 * face directly over the words "Choose your practice". */
.paulioHanging{
  display:block;width:110px;max-width:30%;height:auto;
  position:absolute;top:0;right:var(--s5);transform:translateY(-20%);
  z-index:2;
}
@media (max-width:700px){
  /* Narrower still than the desktop rule: the subtitle line wraps close to
   * the card's full width on a phone, so there is less clear space on the
   * right for him to hang into without sitting over the text. */
  .paulioHanging{width:64px;right:0;transform:translateY(-14%)}
}

/* He is absolutely positioned, so he takes no space in the flow and nothing
 * stops a heading or subtitle running underneath him. Shrinking him further
 * was not the fix: on a phone the subtitle wrapped wider than he is narrow
 * and his paw landed on the word "student". Reserve the column he hangs in
 * instead, so the text wraps before it reaches him. The gap is his width
 * plus the clear space MASCOT_GUIDE.md asks for around the mark. */
.paulioHangCard > h2,
.paulioHangCard > .sub{padding-right:150px}
@media (max-width:700px){
  .paulioHangCard > h2,
  .paulioHangCard > .sub{padding-right:78px}
}

/* A pose beside a block of text, looking in at it. Stacks on a phone rather
 * than shrinking the text column to fit a fixed-width companion beside it. */
.paulioRow{display:flex;align-items:center;gap:var(--s6);flex-wrap:wrap}
.paulioRow>*{flex:1 1 260px;min-width:0}
.paulioRow .paulioWith{flex:0 0 auto;margin-inline:auto}
@media (max-width:700px){
  .paulioRow{flex-direction:column-reverse}
  /* flex-basis:260px targets a WIDTH in the row layout above. In a column
   * flex container the main axis is vertical, so the same basis was read as
   * a minimum HEIGHT instead, and a two-line paragraph sat inside a 260px
   * tall box with the rest left blank. auto sizes it to its content again. */
  .paulioRow>*{flex-basis:auto}
}

/* ---- landing ----
 * A band, not a card. Every section of the site used to be the same rounded
 * white rectangle at the same width, so at squint distance the page was four
 * identical outlines: the silhouette of every starter template. A band runs
 * the full width of the window and carries its own surface colour, which gives
 * the page a rhythm (cream, grey, cream) without inventing any new decoration.
 *
 * The bleed is done with negative inline margins and NO declared width. Saying
 * width:100vw here would include the scrollbar and push the whole document
 * sideways; leaving width auto lets the box fill exactly what the negative
 * margins opened up, which is the window and nothing more. */
.bleed{
  margin-inline:calc(50% - 50vw);
  padding-inline:calc(50vw - 50% + var(--s4));
}
.hero{
  position:relative;overflow:hidden;background:var(--paper);
  border:0;border-radius:0;box-shadow:none;
  padding-block:var(--s8);margin-bottom:var(--s7);
}
.heroInner{position:relative;z-index:1;max-width:560px;margin:0 auto;text-align:center}
.eyebrow{
  display:inline-block;background:#fff;border:1px solid #ece3d4;
  color:var(--orange-d);font-size:var(--t-micro);font-weight:700;letter-spacing:.3px;
  padding:var(--s1) var(--s3);border-radius:999px;margin-bottom:var(--s4);
}
.hero h1{font-size:var(--t-hero);line-height:1.08;margin-bottom:var(--s4)}
.hero .sub{font-size:var(--t-lead);color:#4a5568;margin-bottom:var(--s5);line-height:1.6}
.heroCta{display:flex;gap:var(--s3);justify-content:center;flex-wrap:wrap}
.heroCta .btn{margin-top:0;width:auto;padding:var(--s3) var(--s5);font-size:var(--t-body)}
.reassure{margin:var(--s4) 0 0;font-size:var(--t-small);color:var(--muted)}
@media (max-width:700px){
  :root{--t-hero:36px}
  .hero{padding:var(--s6) var(--s5)}
}

/* The sample sits on the page background between two cream bands, so the eye
 * gets grey, cream, grey rather than four white rectangles in a row. */
.sampleBand{margin-bottom:var(--s7)}
.sampleBand .sampleText{padding-block:var(--s2)}

/* The closing band mirrors the hero, which frames the page rather than ending
 * it on yet another card. */
.closer{
  position:relative;background:var(--paper);padding-block:var(--s7);
  text-align:center;margin-bottom:calc(-1 * var(--s8));
}
.closerInner{max-width:44ch;margin-inline:auto}
.closer .muted{margin-bottom:var(--s5)}
.btn.wide{width:auto;padding:var(--s3) var(--s6);margin-top:0}

/* The four product lines, straight from programs.py. */
.lines{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s3);margin-top:var(--s1)}
@media (max-width:700px){ .lines{grid-template-columns:1fr} }
.line{
  display:flex;gap:var(--s3);align-items:flex-start;
  border:1px solid var(--border);border-radius:12px;padding:var(--s4) var(--s4);
  background:#fff;
}
.lineIcon{flex:none;margin-top:var(--s1)}
.lineName{font-weight:700;font-size:var(--t-body);margin:0 0 var(--s1)}
.lineBlurb{color:var(--muted);font-size:var(--t-small);margin:0;line-height:1.45}

.ticks{list-style:none;margin:var(--s4) 0 0;padding:0}
.ticks{margin-top:var(--s5)}
.ticks li{
  display:flex;gap:var(--s3);align-items:flex-start;
  margin-bottom:var(--s5);font-size:var(--t-body);line-height:1.45;
}
.ticks .motif{flex:none;color:var(--green);margin-top:var(--s1)}

/* The sample booklet page, so a visitor sees the actual product. */
.sample{display:flex;gap:var(--s7);align-items:flex-start;flex-wrap:wrap}
.sampleText{flex:1 1 300px;min-width:0}
.sampleLink{
  /* min-width:0 matters: a flex item defaults to min-width:auto, which for an
   * image is its intrinsic width. At 620px intrinsic that refused to shrink
   * and shoved itself onto its own row under the text. */
  flex:0 1 320px;width:320px;min-width:0;color:var(--navy);text-align:center;
  text-decoration:none;font-size:var(--t-small);font-weight:600;
}
.creditBanner{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  padding:var(--s3) var(--s3);border:1px solid #cfd9e7;border-radius:10px;
  background:#f5f8fc;color:var(--navy);font-size:var(--t-small);margin:var(--s4) 0 var(--s1);
}
.creditBanner.centered{max-width:380px;margin:var(--s4) auto 0;justify-content:center}
.creditBanner strong{font-size:var(--t-lead)}
/* Was text-align:center, which put three different alignments inside 60px of
 * one card: left form, centred legal line, left "already have an account". */
.authAside{margin:var(--s3) 0 0}
.jobItem form{margin:0;flex:none}
.sampleShot{
  /* Shown as the top of the page rather than the whole of it. The shot is
   * 620x877, so at full height it stood about 200px taller than the text
   * beside it and left a column of empty card under the tick list. Cropping to
   * the head of the page keeps the two columns level, and the full page is one
   * click away on the link directly beneath. */
  width:100%;min-width:0;height:360px;object-fit:cover;object-position:top;
  margin-bottom:var(--s2);
  border:1px solid var(--border);border-radius:10px;
  box-shadow:0 6px 22px rgba(16,30,54,.13);display:block;
}
.sampleLink:hover .sampleShot{box-shadow:0 8px 26px rgba(16,30,54,.18)}
.sampleLink:focus-visible{outline:3px solid rgba(45,81,131,.2);outline-offset:4px;border-radius:10px}
@media (max-width:700px){
  .sampleLink{flex-basis:auto;width:100%;max-width:360px;margin:0 auto}
}

/* ---- stat tiles (account) ---- */
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s3)}
@media (max-width:560px){ .stats{grid-template-columns:1fr} }
.stat{background:#f7f9fc;border:1px solid var(--border);border-radius:12px;padding:var(--s4)}
.statNum{font-family:var(--display);font-size:var(--t-h1);font-weight:700;letter-spacing:-.2px;line-height:1.1;color:var(--navy)}
.statLabel{font-size:var(--t-small);font-weight:650;margin:var(--s1) 0 var(--s1)}
.statNote{color:var(--muted);font-size:var(--t-micro);line-height:1.45}

/* Account deletion is deliberately available but visually quiet until the
 * account owner chooses to open it. */
.dangerZone{
  background:#fff;border:1px solid var(--border);border-radius:var(--radius);
  box-shadow:var(--shadow);margin-bottom:var(--s4);overflow:hidden;
}
.dangerZone summary{
  list-style:none;padding:var(--s4) var(--s5);display:flex;justify-content:space-between;
  align-items:center;gap:var(--s4);cursor:pointer;color:var(--danger);
}
.dangerZone summary::-webkit-details-marker{display:none}
.dangerZone summary:hover{background:#fffafa}
.dangerZone summary strong{display:block;font-size:var(--t-small)}
.dangerZone summary small{display:block;color:var(--muted);font-size:var(--t-micro);font-weight:400;margin-top:var(--s1)}
.disclosure{
  width:9px;height:9px;border:solid var(--muted);border-width:0 2px 2px 0;
  transform:rotate(45deg);transition:transform .15s ease;flex:none;margin-right:var(--s1);
}
.dangerZone[open]{border-color:#f3cfcf;background:var(--danger-tint)}
.dangerZone[open] .disclosure{transform:rotate(225deg);margin-top:var(--s1)}
.dangerBody{padding:0 var(--s5) var(--s5);border-top:1px solid #f3cfcf}


/* ---- header call to action ----
 * "Sign up" was the third of three identical 14px links. It is the only thing
 * a logged-out visitor is being asked to do, so it gets the one filled shape
 * in the bar. */
.navCta{
  background:#fff;color:var(--navy)!important;font-weight:700;
  padding:var(--s2) var(--s3)!important;border-radius:8px;margin-left:var(--s2);
}
.navCta:hover{background:#eef2f8}

/* ---- auth cards ----
 * One alignment per card, and a footer line that does not outweigh the button
 * above it. */
.authFoot{margin-top:var(--s4);font-size:var(--t-small)}
.labelNote{font-weight:400;color:var(--muted);font-size:var(--t-small)}

/* An auth card used to sit at the top of an otherwise empty 1280x1440 page with
 * the footer floating directly under it. Centring it in the space that is
 * actually there costs nothing and stops the page looking unfinished. */
main.wrap:has(> .card.narrow){
  min-height:calc(100vh - 220px);
  display:flex;flex-direction:column;justify-content:center;
}

/* ---- pricing tiers ----
 * The two tiers are a row of independent cards, so a one-line description on
 * the right and a two-line description on the left threw every tick row below
 * them out of alignment. A shared minimum height on the description puts the
 * feature lists back on the same baseline. */
.priceCard > .muted{min-height:2.9em}

/* ---- pricing, support, and owner pages ---- */
.pricingIntro{text-align:center;max-width:620px;margin:var(--s2) auto var(--s5)}
.pricingIntro .motifRow{justify-content:center}
.pricingGrid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s4);margin-bottom:var(--s4)}
.priceCard{
  position:relative;background:#fff;border:1px solid var(--border);
  border-radius:var(--radius);padding:var(--s5);box-shadow:var(--shadow);
  display:flex;flex-direction:column;
}
.priceCard.featured{border:2px solid var(--navy);padding:var(--s5)}
/* Was a navy pill half-overlapping the card's navy border, inset from a corner
 * it had no relationship to: it read as a sticker applied afterwards. As a band
 * across the top of the tier it is part of the card it labels. */
.priceBadge{
  display:block;margin:calc(-1 * var(--s5)) calc(-1 * var(--s5)) var(--s5);
  background:var(--navy);color:#fff;text-align:center;
  border-radius:calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
  padding:var(--s2);font-size:var(--t-micro);font-weight:700;letter-spacing:.4px;
}
/* The band occupies real height, so without a matching spacer the unbadged
 * tier starts higher and every row across the two cards is thrown out of line:
 * exactly the misalignment the shared description height was added to fix. */
.priceCard:not(.featured)::before{
  content:"";display:block;
  margin:calc(-1 * var(--s5)) calc(-1 * var(--s5)) var(--s5);
  padding:var(--s2);font-size:var(--t-micro);line-height:1.2;
}
.price{font-family:var(--display);color:var(--navy);font-size:var(--t-figure);font-weight:700;margin:var(--s1) 0 var(--s2);letter-spacing:-.5px}
.price span{font-size:var(--t-lead);vertical-align:top;line-height:2.1;margin-right:var(--s1)}
.priceFeatures{list-style:none;padding:0;margin:var(--s4) 0 var(--s1);flex:1}
.priceFeatures li{display:flex;align-items:flex-start;gap:var(--s2);margin:var(--s2) 0;font-size:var(--t-small)}
.priceFeatures .motif{color:var(--green);flex:none;margin-top:var(--s1)}
.priceCard form{margin:0}
/* Headings may be centred; paragraphs may not. Four ragged-both-edges
  * paragraphs was the clearest "nobody styled this section" signal on the site. */
.pricingFinePrint{text-align:center}
.pricingFinePrint p{text-align:left;max-width:62ch}
.pricingFinePrint p{max-width:660px;margin:var(--s2) auto;color:var(--muted);font-size:var(--t-small)}
.centerCard{text-align:center}
.centerCard .motifRow{justify-content:center}
.centerCard .btn{width:auto;margin-right:var(--s1)}
.balanceCallout{
  padding:var(--s4);background:#f5f8fc;border:1px solid var(--border);border-radius:10px;
  margin:var(--s4) 0;color:var(--navy);
}
.balanceCallout strong{font-size:var(--t-h2);margin-right:var(--s1)}
.policyPage{font-size:var(--t-body);line-height:1.7;max-width:64ch}
.policyPage > p,.policyPage > h2{max-width:720px}
.policyPage h2{margin-top:var(--s5)}
.supportGrid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s3);margin-top:var(--s5)}
.supportGrid section{border:1px solid var(--border);border-radius:12px;padding:var(--s4);background:#f9fafc}
.supportGrid section h2{margin:0 0 var(--s2);font-size:var(--t-lead)}
.supportGrid section p{margin:0;color:var(--muted);font-size:var(--t-small)}
.businessDetails{margin-top:var(--s5)!important;padding-top:var(--s4);border-top:1px solid var(--border);color:var(--muted)}
.adminCreditForm{display:grid;grid-template-columns:2fr .6fr 2fr auto;gap:var(--s3);align-items:end}
.adminCreditForm label{margin-top:0}
.adminCreditForm button{margin:0;white-space:nowrap}
.tableScroll{overflow-x:auto;margin-top:var(--s4)}
.adminTable{width:100%;border-collapse:collapse;font-size:var(--t-small)}
.adminTable th,.adminTable td{padding:var(--s2);text-align:left;border-bottom:1px solid var(--border);vertical-align:middle}
.adminTable th{color:var(--muted);font-size:var(--t-micro);text-transform:uppercase;letter-spacing:.35px}
.adminTable form{margin:0}

/* The qualifier beside a stat, previously an inline style at an off-scale
 * 17px and weight 650, which no face here actually has. */
.statOf{font-size:var(--t-small);font-weight:600;color:var(--muted)}

/* Referenced by account.html and defined nowhere, so purchase history rendered
 * at full library row height. A receipt is not a booklet. */
.compactJobs .jobItem{padding:var(--s3) 0}
.compactJobs .jobTitle{font-size:var(--t-small)}

/* An orphan in the last row of a two-column grid left a 50% white gap and a
 * ragged bottom edge. Auto-fit lets five topics fill the row they are in. */
.supportGrid{grid-template-columns:repeat(auto-fit,minmax(15rem,1fr))}

@media (max-width:620px){
  /* The scale itself steps down, so every heading follows without a single
   * per-component override. The overrides that used to live here (.hero h1,
   * h1) were a second, competing type scale that had to be kept in sync by
   * hand and was already out of sync. */
  :root{--t-hero:32px; --t-figure:30px; --t-h1:26px; --t-h2:20px; --t-lead:16px}
  header{padding:0 var(--s3)}
  .signedIn .headerInner{padding-top:var(--s2);row-gap:var(--s1)}
  .signedIn .brandTag{display:none}
  .signedIn .nav{
    order:2;width:100%;justify-content:space-between;
    border-top:1px solid rgba(255,255,255,.14);padding:var(--s1) 0 var(--s1);
  }
  .signedIn .nav a{flex:1;text-align:center;padding:var(--s1) var(--s1);font-size:var(--t-small)}
  .signedIn .navForm{flex:1}
  .signedIn .navForm button{width:100%;padding:var(--s1) var(--s1);font-size:var(--t-small)}
  .signedIn .desktopLabel{display:none}
  .signedIn .mobileLabel{display:inline}
  .wrap{margin-top:var(--s4);padding:0 var(--s3)}
  .card{padding:var(--s5) var(--s5)}
  .jobItem{flex-wrap:wrap}
  .jobMain{flex:1 1 100%}
  .jobItem .rate,.jobItem .btn.small,.jobItem form{margin-left:auto}
  .programGrid{grid-template-columns:1fr}
  .programOption label{min-height:0}
  .heroCta .btn{width:100%}
  .pricingGrid,.supportGrid{grid-template-columns:1fr}
  .adminCreditForm{grid-template-columns:1fr}
  .centerCard .btn{width:100%;margin-right:0}
}

/* ---- progress page ----
 * The bar is deliberately indeterminate. The pipeline reports no stages, so a
 * percentage would be a number we invented; a moving bar says "still working"
 * without claiming to know how far along it is.
 */
/* The plane and its trail above the heading, so the wait has something to look
 * at that is not a bar. */
.flight{display:flex;align-items:center;gap:0;margin-bottom:var(--s4)}
.flight .motif:last-child{transform:translate(-8px,-7px)}
.ink-green{color:var(--green)}
.doneMark{margin-bottom:var(--s1)}

.bar{height:7px;border-radius:99px;background:#e8edf5;overflow:hidden}
.bar span{
  display:block;height:100%;width:40%;border-radius:99px;
  background:linear-gradient(90deg,var(--navy-l),var(--navy));
  animation:slide 1.6s ease-in-out infinite;
}
/* Travels between the two edges rather than off them. The old cycle started at
 * margin-left:-38%, so for part of every loop the track was empty and the wait
 * looked stalled at zero. */
@keyframes slide{
  0%{transform:translateX(0)} 50%{transform:translateX(150%)} 100%{transform:translateX(0)}
}


/* ---- a real failure, not a notice ----
 * The pipeline error was rendered in .flash, the amber used for "check your
 * email". A generation that spent a credit and produced nothing should not
 * look like an FYI. */
.alert{
  background:var(--danger-tint);border:1px solid #f3cfcf;border-left:3px solid var(--danger);
  color:#7a1919;padding:var(--s3) var(--s4);border-radius:0 10px 10px 0;
  margin-bottom:var(--s4);font-size:var(--t-small);
}
/* Buttons are full width by default, which stacked two equal slabs on the
 * screens that have two actions. In a row the primary keeps the weight. */
.btnRow{display:flex;gap:var(--s3);flex-wrap:wrap;margin-top:var(--s4)}
.btnRow .btn{width:auto;margin-top:0;flex:1 1 160px}

/* ---- booklet feedback ----
 * Orange is the one place on the site it carries meaning rather than
 * decoration: a filled star. Everything unfilled stays grey so the count is
 * readable at a glance rather than needing to be counted.
 */
.srOnly{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
.stars{border:0;padding:0;margin:var(--s1) 0 var(--s1)}
.stars legend{
  font-size:var(--t-small);font-weight:600;color:#3b4658;padding:0;margin-bottom:var(--s1);
}
/* Reversed so the markup can run 5 to 1 while the stars read 1 to 5. */
.starRow{display:flex;flex-direction:row-reverse;justify-content:flex-end;gap:var(--s1)}
.starRow input{
  position:absolute;opacity:0;width:1px;height:1px;
}
/* Were #d8dee9, which on white is fainter than the field borders around them:
 * the primary control on the page looked switched off. An outline star reads
 * as empty and available rather than as disabled. */
.starRow label{
  margin:0;cursor:pointer;font-size:var(--t-h1);line-height:1;color:#94a2b8;
  transition:color .1s ease, transform .06s ease;
}
.starRow label:active{transform:translateY(1px)}
/* The checked star and every star before it, which in this reversed markup
 * means every later sibling. */
.starRow input:checked ~ label,
.starRow label:hover,
.starRow label:hover ~ label{color:var(--orange)}
.starRow input:focus-visible + label{
  outline:2px solid var(--navy-l);outline-offset:3px;border-radius:4px;
}

/* The library's per-row entry point. Quiet until it has something to report. */
.rate{
  flex:none;text-decoration:none;font-size:var(--t-small);font-weight:600;
  color:var(--muted);padding:var(--s1) var(--s2);border-radius:8px;white-space:nowrap;
}
.rate:hover{background:#f0f3f8;color:var(--navy)}
.rate.rated{color:var(--orange-d);font-size:var(--t-body);letter-spacing:1px}
.rateNudge{margin-top:var(--s4)}

/* Support console. Long comments must wrap rather than stretch the table. */
.feedbackComment{max-width:420px;white-space:normal;line-height:1.45}
.ratingCell{color:var(--orange-d);letter-spacing:1px;white-space:nowrap}
