/* ============================================================================
   MEMORIZE MEDICINE — CANVAS OVERRIDE LAYER
   ----------------------------------------------------------------------------
   WHAT THIS FILE IS FOR, AND WHY IT IS SMALL NOW.

   In Pass 1 this file was the whole design system, loaded last so that it could
   outrank 4,159 lines of duplicated inline CSS on equal specificity. Pass 2
   extracted that duplicated block into `assets/app.css` and conformed it, so
   most of what used to be here is now stated once, in the right place, instead
   of being asserted twice.

   What is left is only what must still outrank a page's own <style> block.
   Load order is:

       assets/app.css      tokens, base, shared components
       <style> ... </style>page-bespoke CSS
       assets/canvas.css   THIS FILE — beats page-bespoke on equal specificity

   Anything that belongs to every page belongs in app.css. Put a rule here only
   when a page's inline stylesheet declares the same property on the same
   selector and has to lose. Each block below names the pages it is fighting.

   NO !important. Pass 1 shipped six — four of them on .float-card, fighting CSS
   it could have beaten on specificity, which is the exact trick Pass 1 itself
   had correctly avoided two hundred lines earlier for .btn.btn-x. Those four
   are gone, replaced by a selector that wins honestly. The two survivors in the
   reduced-motion block are gone too; app.css §3 now shortens durations rather
   than zeroing them, which is safer for panels whose visible state is an
   animation's end state.
   ========================================================================== */

/* --- body ---------------------------------------------------------------
   Every page declares `body{background:var(--bg);…;transition:background .3s}`
   inline. The transition has to lose: it smeared the entire page on every
   theme toggle, and it made colour auditing unreliable — the first Pass 2
   contrast run sampled mid-transition and reported 180 failures that were not
   real. The dot grid has to win, because it is the substrate the whole design
   sits on. Pages: all fifteen. */
html body{
  background-color:var(--paper);
  background-image:radial-gradient(circle at 1px 1px,var(--dot) 1.2px,transparent 1.3px);
  background-size:22px 22px;
  color:var(--text);
  font-family:var(--fb);
  font-size:15px; line-height:1.58;
  transition:none;
}

/* --- headings -----------------------------------------------------------
   Several pages restate the heading stack inline at 800 weight. Canvas tops
   out at 600; 800 in Space Grotesk reads as shouting at these sizes.
   Pages: admin, tutor, quiz, index, catalog, course, messages. */
h1,h2,h3,h4,.card-title,.topbar-title,.sect-title,.headline{
  font-family:var(--fd); font-weight:600; letter-spacing:-.011em; line-height:1.12;
}

/* --- atmosphere ---------------------------------------------------------
   index, catalog, course, login and payment each redeclare `.atmos` inline
   with the three blurred gradient orbs. Canvas keeps the dot grid only.
   Pages: index, catalog, course, login, payment, lecture-video. */
.atmos,[data-theme="light"] .atmos{
  background-color:var(--paper);
  background-image:radial-gradient(circle at 1px 1px,var(--dot) 1.2px,transparent 1.3px);
  background-size:22px 22px;
}
.atmos::before,[data-theme="light"] .atmos::before{content:none}
.orb{display:none}

/* --- progress -----------------------------------------------------------
   quiz and flashcards each carry their own `.progress`, `.progress-fill` and a
   `::after` shimmer keyframe. The shimmer has to go: a bar that is still
   moving after it has stopped filling reads as "still loading", which during a
   timed exam is a genuine misreading and not just a decoration.
   Pages: quiz, flashcards. */
.progress,[data-theme="light"] .progress{background:var(--surface-3);border-radius:0;height:6px}
.progress-fill{background:var(--brand);border-radius:0;box-shadow:none}
.progress-fill::after{content:none}

/* --- login split layout -------------------------------------------------
   The two feature cards were position:absolute at top:24%/right:8% and
   bottom:26%/right:16%, and at common laptop widths they landed on top of the
   <h1> — the headline was unreadable underneath "Exam-style blocks". They now
   sit in normal flow beneath the copy.

   Pass 1 did this with four !important declarations. It did not need them:
   login.html's rule is `.float-card` at (0,1,0), so `.float-strip .float-card`
   at (0,2,0) wins on its own. No content is lost; both cards keep their text
   and their icons. Page: login. */
.float-strip{display:flex;flex-wrap:wrap;gap:12px;margin-top:26px}
.float-strip .float-card{
  position:static; inset:auto; animation:none; transform:none;
  flex:1 1 240px; min-width:0;
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-obj); box-shadow:var(--shadow);
  padding:12px 15px; font-size:13.5px; font-weight:600;
}
.float-strip .float-card::before{content:none}
.fc-icon{
  border-radius:var(--r-sm); background-color:var(--surface-3);
  background-image:var(--pat-hatch); box-shadow:none; border:1px solid var(--line);
}
.si-orange.fc-icon{background-image:var(--pat-stip)}
.si-green.fc-icon{background-image:var(--pat-cross)}

/* --- footer -------------------------------------------------------------
   index and catalog restate the footer inline. Page: index, catalog. */
.foot{border-top:1px solid var(--line)}
.foot .fcol b{
  font-family:var(--fm); font-size:10.5px; letter-spacing:.14em;
  text-transform:uppercase; font-weight:500; color:var(--text3);
}
.foot .fcol a{color:var(--text2)}
.foot .fcol a:hover{color:var(--text)}
.foot-bottom{color:var(--text3);border-top:1px solid var(--line-2)}

/* --- the mark swaps with the theme -------------------------------------
   Two <img> stacked in one absolutely-positioned box; exactly one may be seen.

   Doing this with `display` starts a specificity arms race that cannot be won
   cleanly: index declares `.pnav-logo img{display:block}` at (0,1,1), messages
   declares `.threads-top .row a img{display:block}` at (0,3,1), and every page
   is free to add another. Pass 1 ended that race with !important; Pass 2's
   first attempt raised specificity twice and still lost on messages in dark,
   which is how the regression showed up -- BOTH marks rendered and the light
   one, later in the DOM, was the one on screen.

   `opacity` sidesteps it entirely: nothing anywhere in the app sets opacity on
   these images, so a single (0,2,0) rule wins on every page with no
   !important and no arms race. Pages: all fifteen. */
.lock-mark .logo-l{opacity:0;pointer-events:none}
.lock-mark .logo-d{opacity:1}
[data-theme="light"] .lock-mark .logo-l{opacity:1}
[data-theme="light"] .lock-mark .logo-d{opacity:0;pointer-events:none}

/* ===================== 18. EXTRACTED STYLE ATTRIBUTES ==================

   These classes exist for one reason: a CSP that does not carry
   'unsafe-inline' in style-src forbids the style="…" attribute, and this
   app had 758 of them. Each class below is the exact declaration list of
   an attribute that was removed, in its original order, so the cascade it
   participates in is unchanged.

   TWO KINDS.
     u-*   one declaration. Shared atomic utility. Safe to reuse anywhere.
     cs-*  a multi-declaration list lifted verbatim from one or more
           attributes. NOT a design token — a faithful copy. The comment
           on each says which pages used it.

   A style attribute has specificity (1,0,0,0) and a class has (0,0,1,0),
   so anywhere a page rule already set the same property on the same
   element, the attribute used to win and the class does not. Every one of
   these was checked against a before/after computed-style snapshot in both
   themes, blocked AND hydrated. See REDESIGN-PASS3.md §A.

   WHY canvas.css AND WHY EVERY SELECTOR IS DOUBLED.  This block was written
   into app.css first. app.css loads BEFORE each page inline <style>, so every
   class here lost to any page rule touching the same property and the snapshot
   reported 390 changed elements. Load order alone does not fix it — order only
   breaks ties at EQUAL specificity, and the rules winning were things like
   `.card h2` at (0,0,1,1). So the block lives in canvas.css, which is exactly
   what canvas.css is for, and each selector is written twice for (0,0,2,0).
   Class count is compared before element count, so (0,0,2,0) beats (0,0,1,1).
   No !important, which stays banned.

   The honest long-term move is to fold cs-* into real component classes as
   the design settles. They are numbered by frequency so the ones worth
   naming first are at the top.
   ===================================================================== */

/* --- atomic utilities ------------------------------------------------- */
.u-as-center.u-as-center{align-self:center}
.u-bdc-rgba-207-110-18-5.u-bdc-rgba-207-110-18-5{border-color:rgba(207,110,18,.5)}
.u-bdc-rgba-207-110-18-55.u-bdc-rgba-207-110-18-55{border-color:rgba(207,110,18,.55)}
.u-bdc-rgba-231-135-43-4.u-bdc-rgba-231-135-43-4{border-color:rgba(231,135,43,.4)}
.u-bdc-rgba-231-135-43-45.u-bdc-rgba-231-135-43-45{border-color:rgba(231,135,43,.45)}
.u-bg-blue.u-bg-blue{background:var(--blue)}
.u-bg-green.u-bg-green{background:var(--green)}
.u-bg-orange.u-bg-orange{background:var(--orange)}
.u-bg-x317ec2.u-bg-x317ec2{background:#317EC2}
.u-bg-x5aaa46.u-bg-x5aaa46{background:#5AAA46}
.u-bg-xe7872b.u-bg-xe7872b{background:#E7872B}
.u-bg-xe86a62.u-bg-xe86a62{background:#E86A62}
.u-bg-xf2c14e.u-bg-xf2c14e{background:#F2C14E}
.u-c-amber.u-c-amber{color:var(--amber)}
.u-c-green-hi.u-c-green-hi{color:var(--green-hi)}
/* R26 Pass 4 — ITEM D. This was `var(--red,#c0392b)`, a faithful copy of the
   original style attribute. `--red` is defined nowhere in either theme, so the
   fallback fired every time and the colour was #c0392b — a hex neither theme
   owns, unchanged between light and dark, and therefore invisible to
   dark-review only because dark-review reads tokens, not literals. Pass 3 kept
   it byte-faithful on purpose so the conversion stayed provably inert. It is
   folded into --crimson here, and this is the ONE element the Pass 4 snapshot
   is expected to move: student.html's `.small.u-c-red` denial reason.
   #c0392b -> #B93B42 light / #E8757C dark. */
.u-c-red.u-c-red{color:var(--crimson)}
.u-c-text2.u-c-text2{color:var(--text2)}
.u-c-text3.u-c-text3{color:var(--text3)}
.u-cur-pointer.u-cur-pointer{cursor:pointer}
.u-d-none.u-d-none{display:none}
.u-flex-1.u-flex-1{flex:1}
.u-fs-12-5px.u-fs-12-5px{font-size:12.5px}
.u-fs-12px.u-fs-12px{font-size:12px}
.u-fs-13-5px.u-fs-13-5px{font-size:13.5px}
.u-fs-13px.u-fs-13px{font-size:13px}
.u-fs-14px.u-fs-14px{font-size:14px}
.u-fs-15px.u-fs-15px{font-size:15px}
.u-fs-16px.u-fs-16px{font-size:16px}
.u-fs-17px.u-fs-17px{font-size:17px}
.u-fs-30px.u-fs-30px{font-size:30px}
.u-fsh-0.u-fsh-0{flex-shrink:0}
.u-fw-wrap.u-fw-wrap{flex-wrap:wrap}
.u-fwt-600.u-fwt-600{font-weight:600}
.u-gap-6px.u-gap-6px{gap:6px}
.u-gap-7px.u-gap-7px{gap:7px}
.u-gap-8px.u-gap-8px{gap:8px}
.u-gap-9px.u-gap-9px{gap:9px}
.u-gc-1-1.u-gc-1-1{grid-column:1/-1}
.u-gtc-1fr.u-gtc-1fr{grid-template-columns:1fr}
.u-h-40px.u-h-40px{height:40px}
.u-lh-1-9.u-lh-1-9{line-height:1.9}
.u-lh-2.u-lh-2{line-height:2}
.u-m-0.u-m-0{margin:0}
.u-m-0-0-8px.u-m-0-0-8px{margin:0 0 8px}
.u-m-10px-0-0-16px.u-m-10px-0-0-16px{margin:10px 0 0 16px}
.u-m-10px-0-4px.u-m-10px-0-4px{margin:10px 0 4px}
.u-m-12px-0-0.u-m-12px-0-0{margin:12px 0 0}
.u-m-18px-0-8px.u-m-18px-0-8px{margin:18px 0 8px}
.u-m-4px-0.u-m-4px-0{margin:4px 0}
.u-m-6px-0-14px.u-m-6px-0-14px{margin:6px 0 14px}
.u-m-6px-0-16px.u-m-6px-0-16px{margin:-6px 0 16px}
.u-m-8px-0.u-m-8px-0{margin:8px 0}
.u-maxw-130px.u-maxw-130px{max-width:130px}
.u-maxw-180px.u-maxw-180px{max-width:180px}
.u-maxw-200px.u-maxw-200px{max-width:200px}
.u-maxw-220px.u-maxw-220px{max-width:220px}
.u-maxw-280px.u-maxw-280px{max-width:280px}
.u-maxw-300px.u-maxw-300px{max-width:300px}
.u-maxw-460px.u-maxw-460px{max-width:460px}
.u-maxw-520px.u-maxw-520px{max-width:520px}
.u-mb-0.u-mb-0{margin-bottom:0}
.u-mb-10px.u-mb-10px{margin-bottom:10px}
.u-mb-12px.u-mb-12px{margin-bottom:12px}
.u-mb-14px.u-mb-14px{margin-bottom:14px}
.u-mb-4px.u-mb-4px{margin-bottom:4px}
.u-mb-5px.u-mb-5px{margin-bottom:5px}
.u-mb-6px.u-mb-6px{margin-bottom:6px}
.u-mb-7px.u-mb-7px{margin-bottom:7px}
.u-mb-8px.u-mb-8px{margin-bottom:8px}
.u-minw-130px.u-minw-130px{min-width:130px}
.u-mis-8px.u-mis-8px{margin-inline-start:8px}
.u-mis-auto.u-mis-auto{margin-inline-start:auto}
.u-ml-14px.u-ml-14px{margin-left:14px}
.u-ml-8px.u-ml-8px{margin-left:8px}
.u-ml-auto.u-ml-auto{margin-left:auto}
.u-mr-8px.u-mr-8px{margin-right:8px}
.u-mt-0.u-mt-0{margin-top:0}
.u-mt-10px.u-mt-10px{margin-top:10px}
.u-mt-14px.u-mt-14px{margin-top:14px}
.u-mt-26px.u-mt-26px{margin-top:26px}
.u-mt-5px.u-mt-5px{margin-top:5px}
.u-mt-8px.u-mt-8px{margin-top:8px}
.u-mt-9px.u-mt-9px{margin-top:9px}
.u-op-6.u-op-6{opacity:.6}
.u-op-85.u-op-85{opacity:.85}
.u-ovx-auto.u-ovx-auto{overflow-x:auto}
.u-p-10px-0.u-p-10px-0{padding:10px 0}
.u-p-12px-0.u-p-12px-0{padding:12px 0}
.u-p-14px-16px.u-p-14px-16px{padding:14px 16px}
.u-p-18px.u-p-18px{padding:18px}
.u-p-20px-20px-0.u-p-20px-20px-0{padding:20px 20px 0}
.u-p-5px-10px.u-p-5px-10px{padding:5px 10px}
.u-p-8px.u-p-8px{padding:8px}
.u-pl-18px.u-pl-18px{padding-left:18px}
.u-rz-vertical.u-rz-vertical{resize:vertical}
.u-ta-center.u-ta-center{text-align:center}
.u-ta-end.u-ta-end{text-align:end}
.u-ta-right.u-ta-right{text-align:right}
.u-td-none.u-td-none{text-decoration:none}
.u-td-underline.u-td-underline{text-decoration:underline}
.u-trs-stroke-dasharray-5s.u-trs-stroke-dasharray-5s{transition:stroke-dasharray .5s}
.u-trs-stroke-dasharray-5s-stroke-dashoffset-5s.u-trs-stroke-dasharray-5s-stroke-dashoffset-5s{transition:stroke-dasharray .5s,stroke-dashoffset .5s}
.u-trs-stroke-dashoffset-1-1s-cubic-bezier-2-7-3-1.u-trs-stroke-dashoffset-1-1s-cubic-bezier-2-7-3-1{transition:stroke-dashoffset 1.1s cubic-bezier(.2,.7,.3,1)}
.u-w-0pc.u-w-0pc{width:0%}
.u-w-110px.u-w-110px{width:110px}
.u-w-130px.u-w-130px{width:130px}
.u-w-240px.u-w-240px{width:240px}
.u-w-30pc.u-w-30pc{width:30%}
.u-w-78px.u-w-78px{width:78px}
.u-w-90px.u-w-90px{width:90px}
.u-w-auto.u-w-auto{width:auto}
.u-ws-nowrap.u-ws-nowrap{white-space:nowrap}

/* --- lifted declaration lists ----------------------------------------- */
/* 23×  admin.html, student.html, support.html, tutor.html */
.cs-1.cs-1{font-size:21px;font-weight:800}
/* 15×  admin.html, student.html, tutor.html */
.cs-2.cs-2{gap:8px;flex-wrap:wrap}
/* 13×  admin.html, qbank.html, student.html, tutor.html */
.cs-3.cs-3{flex:1;min-width:0}
/* 12×  admin.html, tutor.html */
.cs-4.cs-4{padding:6px 12px;font-size:11.5px}
/* 7×  admin.html, messages.html, tutor.html */
.cs-5.cs-5{font-weight:800;margin-bottom:12px}
/* 7×  admin.html, tutor.html */
.cs-6.cs-6{padding:5px 11px;font-size:11px}
/* 6×  admin.html, tutor.html */
.cs-7.cs-7{color:#e5484d;font-weight:800}
/* 6×  admin.html, tutor.html */
.cs-8.cs-8{gap:10px;margin-top:16px;flex-wrap:wrap}
/* 5×  admin.html, tutor.html */
.cs-9.cs-9{gap:10px;flex-wrap:wrap}
/* 5×  admin.html, catalog.html, student.html, support.html */
.cs-10.cs-10{grid-column:1/-1;text-align:center}
/* 4×  admin.html, qbank.html, student.html, tutor.html */
.cs-11.cs-11{display:flex;align-items:center;gap:10px}
/* 4×  admin.html, tutor.html */
.cs-12.cs-12{font-weight:800;margin-top:8px}
/* 3×  admin.html, flashcards.html, tutor.html */
.cs-13.cs-13{border:1px solid var(--stroke);border-radius:12px;background:var(--glass2);padding:13px 15px}
/* 3×  admin.html, flashcards.html, tutor.html */
.cs-14.cs-14{font-size:9.5px;font-weight:800;letter-spacing:1.4px;text-transform:uppercase}
/* 3×  admin.html, tutor.html */
.cs-15.cs-15{gap:6px;display:none}
/* 3×  admin.html, tutor.html */
.cs-16.cs-16{gap:8px;display:none}
/* 3×  messages.html, student.html */
.cs-17.cs-17{padding:18px;text-align:center}
/* 3×  messages.html */
.cs-18.cs-18{padding:26px 16px;text-align:center}
/* 3×  qbank.html */
.cs-19.cs-19{padding:4px 10px;font-size:11px}
/* 3×  login.html */
.cs-20.cs-20{text-align:center;margin-top:10px}
/* 3×  messages.html */
.cs-21.cs-21{width:100%;padding:11px;background:linear-gradient(120deg,var(--blue),var(--green));color:#fff;border:none}
/* 2×  admin.html, tutor.html */
.cs-22.cs-22{border:1px solid rgba(255,255,255,.14);border-radius:10px;padding:5px}
/* 2×  support.html, tutor.html */
.cs-23.cs-23{display:none;margin-top:10px}
/* 2×  admin.html, course.html */
.cs-24.cs-24{flex:1;font-size:12.5px;font-weight:700}
/* 2×  flashcards.html, tutor.html */
.cs-25.cs-25{flex:1;min-width:0;background:none;border:none;border-bottom:1px solid var(--stroke);color:inherit;font:inherit;padding:2px 4px}
/* 2×  course.html, qbank.html */
.cs-26.cs-26{font-size:12.5px;font-weight:700}
/* 2×  qbank.html, student.html */
.cs-27.cs-27{font-size:12.5px;line-height:1.65;color:var(--text2)}
/* 2×  student.html */
.cs-28.cs-28{font-size:30px;font-weight:800}
/* 2×  student.html */
.cs-29.cs-29{font-size:34px;margin-bottom:8px}
/* 2×  admin.html */
.cs-30.cs-30{font-weight:800;margin-bottom:4px}
/* 2×  qbank.html */
.cs-31.cs-31{gap:12px;flex-wrap:wrap}
/* 2×  admin.html, tutor.html */
.cs-32.cs-32{gap:4px;margin-top:5px;align-items:center}
/* 2×  student.html */
.cs-33.cs-33{gap:6px;margin-bottom:8px}
/* 2×  admin.html, tutor.html */
.cs-34.cs-34{gap:7px;align-items:flex-start}
/* 2×  admin.html */
.cs-35.cs-35{gap:8px;align-items:center;margin-bottom:12px;cursor:pointer}
/* 2×  admin.html */
.cs-36.cs-36{gap:8px;padding:7px 0;border-bottom:1px solid var(--stroke)}
/* 2×  tutor.html */
.cs-37.cs-37{height:100%;width:0%;border-radius:99px;background:linear-gradient(90deg,var(--orange),var(--orange-hi));transition:width .2s}
/* 2×  tutor.html */
.cs-38.cs-38{height:8px;background:var(--stroke);border-radius:99px;overflow:hidden}
/* 2×  admin.html */
.cs-39.cs-39{margin-bottom:6px;color:#e5484d}
/* 2×  qbank.html, quiz.html */
.cs-40.cs-40{margin-left:auto;font-family:var(--fm);font-size:10.5px;color:var(--text3);background:var(--glass2);border:1px solid var(--stroke);border-radius:20px;padding:2px 8px}
/* 2×  admin.html, tutor.html */
.cs-41.cs-41{margin:9px 0 0 18px;line-height:1.7}
/* 2×  admin.html, support.html */
.cs-42.cs-42{max-width:100%;max-height:230px;border-radius:10px;display:block}
/* 2×  admin.html, tutor.html */
.cs-43.cs-43{max-width:150px;max-height:120px;border-radius:8px;display:block}
/* 2×  admin.html, tutor.html */
.cs-44.cs-44{max-width:200px;max-height:150px;border-radius:8px;margin-top:4px}
/* 2×  index.html */
.cs-45.cs-45{min-width:100%;text-align:center}
/* 2×  admin.html, tutor.html */
.cs-46.cs-46{padding:10px 6px;max-width:150px;word-break:break-all}
/* 2×  login.html */
.cs-47.cs-47{text-align:center;margin-top:12px}
/* 2×  student.html */
.cs-48.cs-48{text-align:center;padding:14px 0}
/* 2×  qbank.html */
.cs-49.cs-49{width:100%;min-height:78px;resize:vertical}
/* 2×  lecture-pdf.html */
.cs-50.cs-50{width:100%;min-height:78px;resize:vertical;border-radius:10px;border:1px solid var(--stroke-hi,rgba(255,255,255,.22));background:var(--glass,rgba(255,255,255,.05));color:inherit;font:inherit;font-size:13px;padding:9px 10px}
/* 2×  admin.html */
.cs-51.cs-51{width:15px;height:15px}
/* 2×  admin.html, tutor.html */
.cs-52.cs-52{width:34px;height:34px;font-size:12px}
/* 1×  admin.html */
.cs-53.cs-53{accent-color:var(--orange);width:16px;height:16px}
/* 1×  admin.html */
.cs-54.cs-54{border:1.5px dashed var(--stroke);border-radius:14px;padding:26px;text-align:center;cursor:pointer}
/* 1×  quiz.html */
.cs-55.cs-55{border:1px dashed var(--text3);background:none}
/* 1×  app.js */
.cs-56.cs-56{color:#8d99a8;font-size:13.5px;margin:0 0 18px}
/* 1×  app.js */
.cs-57.cs-57{color:#c8d2df;margin:0 0 14px}
/* 1×  app.js */
.cs-58.cs-58{color:#c8d2df;margin:0 0 18px;padding-inline-start:20px}
/* 1×  admin.html */
.cs-59.cs-59{color:var(--amber);font-weight:700;margin-bottom:6px}
/* 1×  messages.html */
.cs-60.cs-60{display:block;font-size:11.5px;font-weight:800;margin-bottom:5px}
/* 1×  qbank.html */
.cs-61.cs-61{display:block;font-size:11px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;color:var(--text3);margin-bottom:5px}
/* 1×  qbank.html */
.cs-62.cs-62{display:block;font-size:11px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;color:var(--text3);margin:12px 0 5px}
/* 1×  lecture-pdf.html */
.cs-63.cs-63{display:block;font-size:11px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;color:var(--text3,#8d99a8);margin-bottom:5px}
/* 1×  lecture-pdf.html */
.cs-64.cs-64{display:block;font-size:11px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;color:var(--text3,#8d99a8);margin:12px 0 5px}
/* 1×  messages.html */
.cs-65.cs-65{display:flex;align-items:center;gap:8px;font-size:12.5px;margin-bottom:12px}
/* 1×  qbank.html */
.cs-66.cs-66{display:flex;align-items:center;gap:9px}
/* 1×  messages.html */
.cs-67.cs-67{display:flex;align-items:center;justify-content:center;text-decoration:none}
/* 1×  support.html */
.cs-68.cs-68{display:flex;flex-direction:column;gap:10px;max-height:420px;overflow-y:auto;padding:4px 2px 10px}
/* 1×  admin.html */
.cs-69.cs-69{display:flex;flex-direction:column;gap:16px}
/* 1×  messages.html */
.cs-70.cs-70{display:flex;flex:1;min-width:0}
/* 1×  flashcards.html */
.cs-71.cs-71{display:flex;gap:11px;align-items:flex-start}
/* 1×  qbank.html */
.cs-72.cs-72{display:flex;gap:6px;margin-top:8px}
/* 1×  app.js */
.cs-73.cs-73{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap;margin-top:16px}
/* 1×  app.js */
.cs-74.cs-74{display:inline-block;background:#317ec2;color:#fff;text-decoration:none;padding:11px 18px;border-radius:11px;font-weight:700}
/* 1×  lecture-pdf.html */
.cs-75.cs-75{display:none;align-items:center;gap:10px}
/* 1×  qbank.html */
.cs-76.cs-76{display:none;border-color:rgba(231,135,43,.4)}
/* 1×  tutor.html */
.cs-77.cs-77{display:none;margin-top:12px}
/* 1×  student.html */
.cs-78.cs-78{flex-wrap:wrap;gap:10px}
/* 1×  admin.html */
.cs-79.cs-79{flex:1;min-width:140px}
/* 1×  admin.html */
.cs-80.cs-80{flex:1;min-width:150px}
/* 1×  admin.html */
.cs-81.cs-81{flex:1;min-width:160px}
/* 1×  tutor.html */
.cs-82.cs-82{flex:1;min-width:180px}
/* 1×  course.html */
.cs-83.cs-83{flex:1;min-width:200px}
/* 1×  qbank.html */
.cs-84.cs-84{flex:1;min-width:220px}
/* 1×  support.html */
.cs-85.cs-85{flex:1;resize:vertical}
/* 1×  qbank.html */
.cs-86.cs-86{font-family:var(--fm);font-size:24px;font-weight:700}
/* 1×  qbank.html */
.cs-87.cs-87{font-size:11.5px;color:var(--text3);margin-top:9px}
/* 1×  lecture-pdf.html */
.cs-88.cs-88{font-size:11.5px;color:var(--text3,#8d99a8);margin-top:9px}
/* 1×  lecture-pdf.html */
.cs-89.cs-89{font-size:12.5px;line-height:1.65;color:var(--text2,#c8d2df)}
/* 1×  qbank.html */
.cs-90.cs-90{font-size:12px;color:var(--text3);margin-bottom:10px}
/* 1×  lecture-pdf.html */
.cs-91.cs-91{font-size:12px;color:var(--text3,#8d99a8);margin-bottom:10px}
/* 1×  qbank.html */
.cs-92.cs-92{font-size:12px;line-height:1.55;color:var(--text2);border-left:3px solid var(--orange);padding-left:9px;margin-bottom:6px}
/* 1×  qbank.html */
.cs-93.cs-93{font-size:13.5px;font-weight:800}
/* 1×  qbank.html */
.cs-94.cs-94{font-size:13px;font-weight:700}
/* 1×  student.html */
.cs-95.cs-95{font-size:17px;font-weight:800;margin:20px 0 10px}
/* 1×  student.html */
.cs-96.cs-96{font-size:17px;font-weight:800;margin:8px 0 10px}
/* 1×  quiz.html */
.cs-97.cs-97{font-size:18px;font-weight:800;margin-bottom:8px}
/* 1×  qbank.html */
.cs-98.cs-98{font-size:19px;font-weight:800;margin-bottom:4px}
/* 1×  admin.html */
.cs-99.cs-99{font-size:19px;letter-spacing:2px;user-select:all;word-break:break-all}
/* 1×  support.html */
.cs-100.cs-100{font-size:21px;font-weight:800;margin-bottom:14px}
/* 1×  app.js */
.cs-101.cs-101{font-size:21px;font-weight:800;margin:0 0 10px}
/* 1×  flashcards.html */
.cs-102.cs-102{font-size:25px;font-weight:800}
/* 1×  quiz.html */
.cs-103.cs-103{font-size:25px;font-weight:800;margin-top:18px}
/* 1×  student.html */
.cs-104.cs-104{font-size:25px;font-weight:800;margin:3px 0 18px}
/* 1×  quiz.html */
.cs-105.cs-105{font-size:30px;line-height:1;margin-bottom:12px}
/* 1×  app.js */
.cs-106.cs-106{font-size:34px;margin-bottom:10px}
/* 1×  catalog.html */
.cs-107.cs-107{font-size:40px;margin-bottom:12px}
/* 1×  support.html */
.cs-108.cs-108{font-weight:700;margin-bottom:4px}
/* 1×  catalog.html */
.cs-109.cs-109{font-weight:700;margin-inline-end:4px}
/* 1×  admin.html */
.cs-110.cs-110{font-weight:700;margin-top:6px}
/* 1×  app.js */
.cs-111.cs-111{font-weight:800;font-size:15px;margin-bottom:12px}
/* 1×  admin.html */
.cs-112.cs-112{font-weight:800;margin-bottom:10px}
/* 1×  admin.html */
.cs-113.cs-113{font-weight:800;margin-bottom:2px}
/* 1×  messages.html */
.cs-114.cs-114{font-weight:800;margin-bottom:6px}
/* 1×  messages.html */
.cs-115.cs-115{font-weight:800;opacity:.8}
/* 1×  qbank.html */
.cs-116.cs-116{gap:10px;align-items:center;flex-wrap:wrap}
/* 1×  qbank.html */
.cs-117.cs-117{gap:10px;margin-top:20px;flex-wrap:wrap;align-items:center}
/* 1×  lecture-video.html */
.cs-118.cs-118{gap:18px;flex-wrap:wrap;margin-top:14px}
/* 1×  course.html */
.cs-119.cs-119{gap:18px;margin-top:18px}
/* 1×  admin.html */
.cs-120.cs-120{gap:6px;display:none;flex-wrap:wrap}
/* 1×  admin.html */
.cs-121.cs-121{gap:8px;align-items:center;margin-bottom:6px}
/* 1×  tutor.html */
.cs-122.cs-122{gap:8px;cursor:pointer}
/* 1×  tutor.html */
.cs-123.cs-123{gap:8px;cursor:pointer;margin-top:10px}
/* 1×  tutor.html */
.cs-124.cs-124{gap:8px;cursor:pointer;margin-top:8px}
/* 1×  tutor.html */
.cs-125.cs-125{gap:8px;flex-wrap:wrap;display:none}
/* 1×  support.html */
.cs-126.cs-126{gap:8px;margin-top:12px;flex-wrap:wrap}
/* 1×  support.html */
.cs-127.cs-127{gap:8px;margin-top:8px}
/* 1×  admin.html */
.cs-128.cs-128{gap:8px;padding:6px 0}
/* 1×  admin.html */
.cs-129.cs-129{gap:8px;padding:6px 0;border-bottom:1px solid var(--stroke)}
/* 1×  admin.html */
.cs-130.cs-130{gap:9px;align-items:center;padding:9px 0;border-bottom:1px solid var(--stroke);flex-wrap:wrap}
/* 1×  course.html */
.cs-131.cs-131{gap:9px;align-items:center;padding:9px 10px;margin-bottom:7px;border:1px solid var(--stroke);border-radius:12px;cursor:pointer}
/* 1×  admin.html */
.cs-132.cs-132{gap:9px;cursor:pointer;margin-bottom:16px;align-items:center}
/* 1×  admin.html */
.cs-133.cs-133{gap:9px;flex-wrap:wrap}
/* 1×  support.html */
.cs-134.cs-134{gap:9px;margin-top:10px;align-items:flex-end}
/* 1×  login.html */
.cs-135.cs-135{grid-template-columns:1fr;margin-top:8px}
/* 1×  admin.html */
.cs-136.cs-136{height:38%;animation-delay:.05s}
/* 1×  admin.html */
.cs-137.cs-137{height:47%;animation-delay:.1s}
/* 1×  admin.html */
.cs-138.cs-138{height:58%;animation-delay:.15s}
/* 1×  admin.html */
.cs-139.cs-139{height:66%;animation-delay:.3s}
/* 1×  admin.html */
.cs-140.cs-140{height:68%;animation-delay:.25s}
/* 1×  admin.html */
.cs-141.cs-141{height:70%;animation-delay:.2s}
/* 1×  flashcards.html */
.cs-142.cs-142{justify-content:center;gap:10px}
/* 1×  quiz.html */
.cs-143.cs-143{justify-content:center;gap:10px;margin-top:6px}
/* 1×  quiz.html */
.cs-144.cs-144{justify-content:center;gap:9px}
/* 1×  login.html */
.cs-145.cs-145{letter-spacing:.5em;font-family:var(--fm);font-size:20px;text-align:center}
/* 1×  messages.html */
.cs-146.cs-146{margin-inline-start:auto;display:flex;gap:7px}
/* 1×  qbank.html */
.cs-147.cs-147{margin-top:7px;padding:6px 9px;border-radius:8px;font-size:11.5px;background:rgba(231,135,43,.12);border:1px solid rgba(231,135,43,.45);color:var(--orange,#e7872b)}
/* 1×  tutor.html */
.cs-148.cs-148{margin:0 0 6px 16px;width:calc(100% - 16px)}
/* 1×  app.js */
.cs-149.cs-149{margin:0;background:#0b0f16;color:#eef2f7;font:15px/1.65 -apple-system,Segoe UI,Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;padding:24px}
/* 1×  support.html */
.cs-150.cs-150{margin:10px 0 0;white-space:pre-wrap}
/* 1×  support.html */
.cs-151.cs-151{margin:8px 0;white-space:pre-wrap}
/* 1×  admin.html */
.cs-152.cs-152{max-height:230px;overflow:auto}
/* 1×  admin.html */
.cs-153.cs-153{max-width:180px;border-radius:10px;display:block;margin-bottom:8px}
/* 1×  admin.html */
.cs-154.cs-154{max-width:190px;font-size:12px}
/* 1×  messages.html */
.cs-155.cs-155{max-width:220px;border-radius:10px;display:block}
/* 1×  quiz.html */
.cs-156.cs-156{max-width:420px;margin:0 auto 18px}
/* 1×  messages.html */
.cs-157.cs-157{max-width:440px;width:100%;max-height:76vh;overflow:auto;padding:20px;border-radius:16px}
/* 1×  admin.html */
.cs-158.cs-158{max-width:460px;width:100%;max-height:80vh;overflow:auto;padding:22px;border-radius:16px}
/* 1×  admin.html */
.cs-159.cs-159{max-width:480px;width:100%;max-height:80vh;overflow:auto;padding:22px;border-radius:16px}
/* 1×  student.html */
.cs-160.cs-160{max-width:520px;margin:8px auto 0}
/* 1×  tutor.html */
.cs-161.cs-161{max-width:520px;width:100%;max-height:84vh;overflow:auto;padding:22px;border-radius:16px}
/* 1×  flashcards.html */
.cs-162.cs-162{max-width:920px;margin:0 auto}
/* 1×  quiz.html */
.cs-163.cs-163{opacity:1;margin-left:10px}
/* 1×  catalog.html */
.cs-164.cs-164{padding:0;overflow:hidden}
/* 1×  admin.html */
.cs-165.cs-165{padding:11px 0;border-bottom:1px solid var(--stroke);gap:10px;align-items:center;flex-wrap:wrap}
/* 1×  qbank.html */
.cs-166.cs-166{padding:12px 13px;margin-bottom:9px}
/* 1×  admin.html */
.cs-167.cs-167{padding:12px;margin-top:10px;border:1px solid var(--amber)}
/* 1×  support.html */
.cs-168.cs-168{padding:7px 0;border-top:1px solid var(--stroke)}
/* 1×  admin.html */
.cs-169.cs-169{padding:9px 0;border-bottom:1px solid var(--stroke)}
/* 1×  lecture-pdf.html */
.cs-170.cs-170{text-align:center;margin:26px 0 10px}
/* 1×  quiz.html */
.cs-171.cs-171{text-align:center;padding:46px 22px}
/* 1×  lecture-pdf.html */
.cs-172.cs-172{text-align:center;padding:60px 0}
/* 1×  student.html */
.cs-173.cs-173{text-align:center;padding:8px 0}
/* 1×  tutor.html */
.cs-174.cs-174{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* 1×  tutor.html */
.cs-175.cs-175{width:100%;box-sizing:border-box;background:var(--glass2);border:1px solid var(--stroke);border-radius:10px;padding:10px 12px;color:var(--text);font-family:inherit;font-size:13px}
/* 1×  catalog.html */
.cs-176.cs-176{width:100%;height:100%;object-fit:cover}
/* 1×  lecture-pdf.html */
.cs-177.cs-177{width:100%;height:calc(100vh - 160px);border:0;border-radius:12px;background:#fff}
/* 1×  qbank.html */
.cs-178.cs-178{width:100%;margin-bottom:10px}
/* 1×  lecture-pdf.html */
.cs-179.cs-179{width:11px;height:11px}
/* 1×  quiz.html */
.cs-180.cs-180{width:170px;height:170px;margin:0 auto}
/* 1×  app.js */
/* R26 Pass 3: was .cs-181, the signed-in nav avatar built by app.js. It set
   `color:#fff` over `linear-gradient(--blue, --green)`. Both tokens invert
   between themes, the white did not: measured against each gradient stop by
   hand, DARK theme gives 2.50:1 and 2.35:1 at 11px against a 4.5 floor.
   The automated audit could not catch this — a gradient lives in
   background-image and the contrast walker reads backgroundColor — which is
   why it is now reported as UNMEASURABLE and checked by hand instead of being
   silently measured against the page behind the gradient. Four such nodes
   exist; this was the one that failed.
   The flat --brand with --brand-on is a designed pair, contrasts correctly in
   both themes, and loses nothing but a gradient nobody asked for. */
.navav.navav{
  width:24px; height:24px; border-radius:var(--r-btn); flex-shrink:0;
  background:var(--brand); color:var(--brand-on);
  display:inline-flex; align-items:center; justify-content:center;
  font-weight:700; font-size:11px;
}
/* 1×  lecture-pdf.html */
.cs-182.cs-182{width:4px;height:4px}
/* 1×  lecture-pdf.html */
.cs-183.cs-183{width:7px;height:7px}

/* ============ 19. FORMER JS-STRING STYLESHEETS AND PALETTES (Pass 3) ================
   These replace declaration lists that were held in JavaScript variables and
   interpolated into style attributes. CSP blocks the attribute regardless of
   how the string was built, and a stylesheet living in a `var` is one the
   browser cannot cache, the theme cannot reach, and no linter can see.
   Selectors are doubled and this block lives in canvas.css for the same reason
   as section 18: the attributes these replace were (1,0,0,0) and beat every
   page rule; a single class is (0,0,1,0) and beats almost none of them.
   ===================================================================== */

/* messages.html — was `var INPUT_CSS`, interpolated into 9 style attributes
   across the new-ticket, new-announcement and escalate dialogs. */
.mfield.mfield{
  width:100%; box-sizing:border-box;
  background:var(--glass2); border:1px solid var(--stroke);
  border-radius:10px; padding:10px 12px;
  color:var(--text); font-family:var(--fb); font-size:13px;
}
.mfield-v.mfield-v{resize:vertical}

/* R63 — the receipt viewer's photo frame. M.receiptViewer lives in app.js and
   is opened from BOTH admin.html and support.html, so it cannot borrow
   .receipt-shot: that class is declared in support.html only. admin.html has
   used it in markup since R7 with no rule behind it anywhere — its pending
   receipt thumbnails have never had the frame or the fixed height support's
   do, which is a cosmetic bug this release does not touch but does decline to
   inherit. Shared UI takes its styles from the shared stylesheet. */
.rv-shot.rv-shot{min-height:120px;display:flex;align-items:center;justify-content:center;
  border-radius:var(--r-sm);border:1px solid var(--stroke);padding:8px;
  background-color:var(--surface-2);background-image:var(--pat-hatch)}

/* Note-highlight palette — lecture-pdf.html and quiz.html.
   Colour is the MEANING here, not decoration: the student picks the colour and
   it is how they find the note again. That is why these stay hues while
   department markers became hatch patterns — a department is a category the
   system assigns, a highlight colour is a choice the student made and would not
   recognise as a pattern. The four values are the app's existing
   COLORS/QN_COLORS maps, unchanged, so the conversion is inert. */
/* NOTE: .hlsw carries NO declarations. The swatch already gets its size, shape
   and cursor from the page's own `.nf-colors i` / `.swz i` rules; the style
   attribute it replaces set ONLY a background. A first version of this block
   gave .hlsw a width, height, border-radius and border "to be helpful" and
   would have resized every swatch in the app — a design change smuggled into a
   conversion that is supposed to be inert. If you want the swatches restyled,
   do it deliberately and re-run the snapshot. */
/* R63 — EIGHT MORE COLOURS. The owner asked for a palette the size of a real
   note-taking app's, and the four above could not carry a student's colour
   code through a whole course (a common scheme is one hue per system, and
   there are more than four systems).

   The first four keep their exact hex values and their order. That is not
   politeness — `color` is a plain text column with no CHECK constraint, so
   every note already in the database carries one of these four strings, and
   changing what `yellow` renders as would silently restyle work students have
   already done. New names only ever get appended.

   The eight new hues are picked to stay distinguishable from each other AND
   from the original four at the low opacity a highlight now paints at (.24,
   multiply): that rules out a second yellow, a second mid-blue, and anything
   pale enough to vanish over white paper. Names are the colour, not a meaning
   — the meaning is the student's to assign. */
.hl-yellow.hl-yellow{background:#F2C14E}
.hl-green.hl-green{background:#5AAA46}
.hl-blue.hl-blue{background:#317EC2}
.hl-red.hl-red{background:#E86A62}
.hl-orange.hl-orange{background:#E7872B}
.hl-lime.hl-lime{background:#9FC131}
.hl-teal.hl-teal{background:#2AA69A}
.hl-sky.hl-sky{background:#4FB3E8}
.hl-indigo.hl-indigo{background:#5560C6}
.hl-purple.hl-purple{background:#9558C2}
.hl-pink.hl-pink{background:#DE5F97}
.hl-brown.hl-brown{background:#9C7050}

.hlq-yellow.hlq-yellow{border-left-color:#F2C14E}
.hlq-green.hlq-green{border-left-color:#5AAA46}
.hlq-blue.hlq-blue{border-left-color:#317EC2}
.hlq-red.hlq-red{border-left-color:#E86A62}
.hlq-orange.hlq-orange{border-left-color:#E7872B}
.hlq-lime.hlq-lime{border-left-color:#9FC131}
.hlq-teal.hlq-teal{border-left-color:#2AA69A}
.hlq-sky.hlq-sky{border-left-color:#4FB3E8}
.hlq-indigo.hlq-indigo{border-left-color:#5560C6}
.hlq-purple.hlq-purple{border-left-color:#9558C2}
.hlq-pink.hlq-pink{border-left-color:#DE5F97}
.hlq-brown.hlq-brown{border-left-color:#9C7050}

/* --- conditional branches lifted out of style attributes (Pass 3) -------
   Each of these was a ternary inside a style attribute, e.g.
       style="align-self:' + (mine ? 'flex-end' : 'flex-start') + '"
   A conditional STYLE is a conditional CLASS with extra steps: the branch was
   already being decided in JavaScript, so all that changes is which token the
   template emits. Values are copied verbatim from the attributes they replace.
   Doubled selectors for the same specificity reason as section 18. */

/* admin.html — module/section rename rows */
.cf-row.cf-row{gap:6px;margin:0 0 6px 0}
.cf-row-in.cf-row-in{margin-left:16px}

/* index.html — tutor avatar tones. These are the app's TONES array, which is
   gradients; Pass 2 kept nine deliberate gradients and these are among them.
   THERE ARE FOUR. The array had four entries and the first conversion emitted
   `i % 6 + 1`, which would have left two avatars in every six with no
   background. If you add a tone here, change the modulus in index.html. */
.tone-1.tone-1{background:linear-gradient(135deg,var(--blue),#2463A0)}
.tone-2.tone-2{background:linear-gradient(135deg,var(--green),#478A36)}
.tone-3.tone-3{background:linear-gradient(135deg,var(--orange),#C96812)}
.tone-4.tone-4{background:linear-gradient(135deg,var(--blue),var(--green))}

/* qbank.html — note body, and the placeholder state when there is no note */
.qbn-body.qbn-body{font-size:12.5px;line-height:1.6;white-space:pre-wrap}
.qbn-empty.qbn-empty{color:var(--text3);font-style:italic}

/* R45 — the notes card shows the WHOLE question, its correct answer, the
   passage the student selected, and their note. Before, the stem was sliced at
   120 characters and the passage at 200, and the answer was not shown at all,
   so reviewing a note meant going back to the qbank to find the question again
   — the exact work this panel exists to save.
   white-space:pre-wrap on the stem is load-bearing: question text is usually a
   clinical vignette with deliberate line breaks, and collapsing them is half of
   what made the old card unreadable. */
.qbn-q.qbn-q{font-size:13px;line-height:1.6;white-space:pre-wrap;font-weight:500;
  color:var(--text);margin-bottom:8px}
.qbn-ans.qbn-ans{font-size:12px;line-height:1.55;white-space:pre-wrap;margin-bottom:8px;
  padding:5px 9px;border-radius:8px;background:rgba(46,125,50,.12);
  border-left:3px solid var(--green,#2e7d32);color:var(--text2)}
.qbn-lbl.qbn-lbl{font-size:10px;letter-spacing:.6px;text-transform:uppercase;
  color:var(--text3);margin-bottom:3px}

/* R145 — the stem/answer/passage above are reference material the student
   didn't write; closing it off with a hairline stops it from running
   straight into "My note" with the same visual weight. .qbn-q also dropped
   from font-weight 600 to 500 here: bolding an entire clinical vignette
   (sometimes several sentences) reads as shouting and made the card denser
   than the content needed it to be — the R45 fix was about not CUTTING the
   stem, not about it being bold. */
.qbn-ref.qbn-ref{border-bottom:1px solid var(--line-2);margin-bottom:10px;padding-bottom:2px}
/* .qbn-mine is the one thing on this card the student actually wrote — it
   gets the tinted box the other three elements above already had (green
   answer pill, orange-edged passage quote) so it doesn't read as the least
   important line on the card despite being the reason the card exists.
   The empty state (no note taken yet, just a highlight) stays neutral and
   dashed rather than tinted, so an unfilled note doesn't look like a real
   one from a glance down the list. */
.qbn-mine.qbn-mine{border-radius:8px;padding:8px 10px;background:var(--brand-soft)}
.qbn-mine.qbn-mine .qbn-lbl{color:var(--brand-ink)}
.qbn-mine-empty.qbn-mine-empty{background:var(--surface-2);border:1px dashed var(--line)}
.qbn-mine-empty.qbn-mine-empty .qbn-lbl{color:var(--text3)}

/* R148 — "Load more" row under a paginated qbank list (notes, or generated
   quizzes below). Centered and given breathing room so it reads as a
   distinct next-step control, not one more item in the list above it. */
.qbn-loadmore.qbn-loadmore{display:flex;justify-content:center;margin-top:14px}

/* student.html — study-planner rest-day chip */
.spchip.spchip{cursor:pointer}
.spchip-rest.spchip-rest{border-color:var(--orange)}
/* R152 — study-planner Plan-scope chip (same .spchip pill, checked modules
   picked out in the same blue as the "Scoped:" badge next to it, so the two
   read as one idea rather than two different colour systems). */
.spchip-scope.spchip-scope{border-color:var(--brand)}

/* support.html — ticket chat bubbles */
.tkb.tkb{align-self:flex-start;max-width:78%}
.tkb-mine.tkb-mine{align-self:flex-end}
.tkb-who.tkb-who{margin-bottom:2px}
.tkb-who-mine.tkb-who-mine{text-align:end}
.tkb-body.tkb-body{padding:9px 13px;border-radius:13px;font-size:13px;white-space:pre-wrap}
.tkb-body-mine.tkb-body-mine{background:linear-gradient(120deg,rgba(49,126,194,.25),rgba(90,170,70,.12))}

/* tutor.html — recent-conversation rows; the last one has no separator */
.tvrow.tvrow{padding:12px 0;text-decoration:none;color:inherit}
.tvrow-sep.tvrow-sep{border-bottom:1px solid var(--stroke)}

/* --- the global modal (app.js M.modal) ---------------------------------
   Built by app.js with setAttribute('style', …) until Pass 3. Three separate
   problems, only one of which was the CSP:
     - setAttribute('style') is blocked by style-src like any other attribute;
     - the modal had never been conformed to Canvas — 16px radius, a 70px
       shadow, font-weight 800;
     - its var() fallbacks hardcoded the old dark palette, so on any page where
       app.css failed to load it rendered a dark modal on the light theme.
   Radii are the locked Pass 1 values. Doubled selectors, same reason as §18.
   TOKEN NAMES ARE NOT GUESSABLE: the first draft of this block used --scrim,
   --sh-obj, --surface-1 and --accent-ink-as-foreground. Three of those do not
   exist in app.css and the fourth is a hover colour, not a text colour on a
   filled button (that is --accent-on). An undefined var() with no fallback
   makes the whole declaration invalid at computed-value time, so the modal
   would have had NO background and NO shadow — invisible against the page,
   and only on the screens that open a modal. Check the token list in app.css
   section 2 before using a name. */
.m-modal-ov.m-modal-ov{
  position:fixed; inset:0; z-index:9999;
  background:rgba(6,10,18,.62);
  display:flex; align-items:center; justify-content:center; padding:20px;
}
.m-modal-box.m-modal-box{
  width:min(520px,100%); max-height:86vh; overflow:auto;
  border-radius:var(--r-obj);
  background:var(--surface); border:1px solid var(--line);
  box-shadow:var(--shadow);
  padding:18px 18px 16px; color:var(--text); font-family:inherit;
}
.m-modal-btn.m-modal-btn{
  padding:8px 15px; border-radius:var(--r-btn);
  font-weight:600; font-size:12.5px; cursor:pointer; font-family:inherit;
  border:1px solid var(--line); background:transparent; color:var(--text2);
}
.m-modal-btn-primary.m-modal-btn-primary{
  border-color:transparent; background:var(--accent); color:var(--accent-on);
}

/* --- the device-limit screen (app.js) ----------------------------------
   Reached only when register_device rejects a sign-in, which is why it went
   through two full redesign passes untouched: no page visit renders it, so no
   audit sees it. Until Pass 3 it hardcoded the pre-Canvas dark palette
   (#0b0f16 paper, #eef2f7 ink, #317ec2 button) and a system font stack, so it
   ignored the theme entirely — a light-theme user hitting the device limit was
   shown a black page. It also destroyed its own stylesheet link; see the note
   at the call site. Tokens now, so it follows the theme like every other
   screen. Doubled selectors, same reason as §18. */
.dlim-body.dlim-body{
  margin:0; background:var(--paper); color:var(--text);
  font-family:var(--fb); font-size:15px; line-height:1.65;
  display:flex; align-items:center; justify-content:center;
  min-height:100vh; padding:24px;
}
.dlim-icon.dlim-icon{margin-bottom:10px}
.dlim-icon.dlim-icon .ic{width:34px;height:34px}
.dlim-h.dlim-h{
  font-family:var(--fd); font-size:21px; font-weight:600;
  margin:0 0 10px; color:var(--text);
}
.dlim-p.dlim-p{color:var(--text2);margin:0 0 14px}
.dlim-list.dlim-list{color:var(--text2);margin:0 0 18px;padding-inline-start:20px}
.dlim-note.dlim-note{color:var(--text3);font-size:13.5px;margin:0 0 18px}
.dlim-cta.dlim-cta{
  display:inline-block; background:var(--accent); color:var(--accent-on);
  text-decoration:none; padding:11px 18px;
  border-radius:var(--r-btn); font-weight:600;
  border:0; font-family:inherit; font-size:inherit; cursor:pointer;
}
.dlim-cta.dlim-cta:disabled{opacity:.6;cursor:default}

/* R49 — the block screen gained a support CHAT and a way out.
   It used to offer one link to support.html (the staff console) after having
   already signed the student out, so both ends were dead. It now holds a live
   thread and a logout button, which means it needs real controls rather than
   one CTA. Doubled selectors for the same reason as everything else in this
   file: the screen replaces document.body wholesale and must out-specify
   whatever the host page left behind. */
.dlim-actions.dlim-actions{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.dlim-out.dlim-out{
  display:inline-block; background:transparent; color:var(--text2);
  border:1px solid var(--stroke); padding:11px 18px;
  border-radius:var(--r-btn); font-weight:600;
  font-family:inherit; font-size:inherit; cursor:pointer;
}
.dlim-out.dlim-out:hover{color:var(--text);border-color:var(--text3)}
.dlim-out.dlim-out:disabled{opacity:.6;cursor:default}

.dlim-chat.dlim-chat{
  margin-top:18px; border:1px solid var(--stroke);
  border-radius:var(--r-obj); background:var(--surface-2); overflow:hidden;
}
.dlim-chat-h.dlim-chat-h{
  padding:9px 13px; font-weight:600; font-size:13.5px; color:var(--text2);
  border-bottom:1px solid var(--stroke); background:var(--surface-3);
}
/* Capped and scrollable: this screen is often a phone in portrait, and a long
   thread must not push the logout button off the bottom of the viewport. */
.dlim-log.dlim-log{max-height:230px;overflow-y:auto;padding:10px 13px}
.dlim-msg.dlim-msg{margin:0 0 11px}
.dlim-msg.dlim-msg b{
  display:block; font-size:11px; letter-spacing:.5px; text-transform:uppercase;
  color:var(--text3); margin-bottom:2px; font-weight:700;
}
.dlim-msg.dlim-msg p{margin:0;color:var(--text);white-space:pre-wrap;font-size:14px}
.dlim-msg.mine p{color:var(--text2)}
.dlim-compose.dlim-compose{
  display:flex; gap:8px; align-items:flex-end;
  padding:10px 13px; border-top:1px solid var(--stroke);
}
.dlim-ta.dlim-ta{
  flex:1 1 auto; min-width:0; resize:vertical;
  background:var(--surface); color:var(--text);
  border:1px solid var(--stroke); border-radius:var(--r-btn);
  padding:8px 10px; font-family:inherit; font-size:14px; line-height:1.45;
}
.dlim-send.dlim-send{
  flex:0 0 auto; background:var(--accent); color:var(--accent-on);
  border:0; border-radius:var(--r-btn); padding:9px 15px;
  font-family:inherit; font-size:14px; font-weight:600; cursor:pointer;
}
.dlim-send.dlim-send:disabled{opacity:.6;cursor:default}
.dlim-hint.dlim-hint{
  margin:0; padding:0 13px 11px; font-size:12.5px; color:var(--text3);
}
.dlim-hint.dlim-bad{color:var(--danger)}

/* --- the global toast (app.js M.toast) ---------------------------------
   Surfaced by the hydrated dark-review in Pass 3 and by no audit before it: it
   is built with style.cssText — a CSSOM write, correctly outside the CSP style
   attribute census — and it exists for only 3.1 seconds, so no static pass
   could see it. It carried three hardcoded hexes from the pre-Canvas palette,
   identical in both themes, plus system-ui type that bypassed the self-hosted
   stack. Doubled selectors, same reason as §18. */
.m-toast.m-toast{
  position:fixed; bottom:24px; left:50%; transform:translateX(-50%);
  z-index:9999; max-width:92vw; text-align:center;
  padding:12px 20px; border-radius:var(--r-obj);
  font-family:var(--fb); font-weight:600; font-size:14px; line-height:1.3;
  box-shadow:var(--shadow); border:1px solid var(--line);
}
.m-toast-err.m-toast-err {background:var(--crimson-soft); color:var(--crimson); border-color:var(--crimson)}
.m-toast-ok.m-toast-ok  {background:var(--jade-soft);    color:var(--jade);    border-color:var(--jade)}
.m-toast-info.m-toast-info{background:var(--brand-soft); color:var(--brand);   border-color:var(--brand)}

/* --- R27: manual-enrolment panel lists --------------------------------
   Two long lists (search results, current enrollees) inside an overlay that
   is already max-height:80vh and scrolling. Without a bound of their own, a
   course with 400 enrollees turns the whole dialog into one enormous scroll
   and pushes the "Enrol selected" button somewhere nobody will find it. Each
   list gets its own scroller so the controls stay where they were put.

   Doubled selectors for the same reason as §18: these have to win against the
   generic .glass/.flex rules without an inline style, because the Report-Only
   CSP (see _headers) is style-src 'self' — no style attributes. */
.mm-scroll.mm-scroll{
  max-height:230px; overflow-y:auto; overscroll-behavior:contain;
  border:1px solid var(--stroke); border-radius:var(--r-obj);
  padding:2px 10px;
}
/* The rows inside are .cs-36, which draws a bottom rule. On the last one that
   rule sits directly on the container border and reads as a double line. */
.mm-scroll.mm-scroll > :last-child{border-bottom:none}

/* ═══ R68 — THE RECEIPT REVIEW GRID, SHARED AT LAST ═══════════════════════
   Owner: "adjust the margins and size of the boxes of those two pages to make
   sure the pages are neat and organized."

   The cause was not a missing margin value. `.rev-grid` and `.receipt-shot`
   were declared in support.html ONLY, inside its <style> block — but
   admin.html's drawReceipts() BUILDS a .rev-grid at runtime
   (`g.className = 'rev-grid'`) and emits .receipt-shot for every pending
   receipt. With no rule behind either class on that page, admin's receipts tab
   rendered its cards as plain block divs: no two-column grid, no 16px gap,
   cards flush against each other, and an image placeholder with no frame and
   no reserved height — so the panel also jumped as each thumbnail resolved.

   The R63 note ~380 lines above already recorded half of this ("admin.html has
   used it in markup since R7 with no rule behind it anywhere") and declined to
   fix it as out of scope. It is in scope now. Same lesson as that note and as
   M.receiptViewer's .rv-shot: shared UI takes its styles from the shared
   stylesheet, and a class defined in one page's <style> is not shared just
   because two pages write it.

   These rules are now the single definition — the copies in support.html are
   deleted, not duplicated, so the two review screens cannot drift.

   Doubled selectors per §18: they must win against the generic .glass/.flex
   rules without an inline style, because the Report-Only CSP in _headers is
   style-src 'self'. */
.rev-grid.rev-grid{display:grid;grid-template-columns:1fr;gap:16px;align-items:start}
@media(min-width:1020px){.rev-grid.rev-grid{grid-template-columns:1fr 1fr}}

/* height, not min-height: the thumbnail loads asynchronously on both pages, and
   a frame that grows when the image arrives reflows every card below it. */
.receipt-shot.receipt-shot{height:210px;border-radius:var(--r-sm);
  border:1px solid var(--stroke);background-color:var(--surface-2);
  background-image:var(--pat-hatch);display:flex;align-items:center;
  justify-content:center;color:var(--text3);font-size:12px;font-weight:700;
  margin-bottom:13px;overflow:hidden}
[data-theme="light"] .receipt-shot.receipt-shot{background-color:var(--surface-2);background-image:var(--pat-hatch)}
/* admin.html sets the loaded thumbnail's class to cs-42; support.html styles
   its own. Bound both here so a tall receipt photo cannot escape the frame. */
.receipt-shot.receipt-shot img{max-width:100%;max-height:100%;object-fit:contain;display:block}
.receipt-shot.receipt-shot a{display:flex;align-items:center;justify-content:center;width:100%;height:100%}

.failline{display:flex;gap:9px;align-items:flex-start;padding:10px 13px;
  border-radius:var(--r-sm);background:var(--crimson-soft);
  border:1px solid var(--edge-crimson);font-size:12.5px;color:var(--crimson);
  font-weight:700;margin-bottom:13px}
.okline{display:flex;gap:8px;font-size:12px;color:var(--text2);padding:4px 0}
.okline .k{color:var(--green-hi)}
/* 7F: the per-check verdict list is a compact stack, not five full-size alerts. */
.autochk .failline{margin-bottom:5px;padding:6px 11px}
.autochk .okline{padding:3px 0}

/* R68: the pending-receipt cards are built by drawReceipts() and appended
   straight onto #panel-receipts, directly under the four stat cards, with no
   spacing between the two groups. The stat grid's own mb-16 is on the grid,
   not on what follows it. */
#panel-receipts .rev-grid.rev-grid{margin-top:16px}

/* ═══ R137 — AFFILIATE ONBOARDING (admin.html) ═══════════════════════════════
   Three rules, all LAYOUT. They live here rather than in an admin.html <style>
   block for the reason DEPLOY-HANDOFF records against support.html's
   .rev-grid: a page-local rule is not shared just because another page writes
   the class, and these controls are the kind that get copied to a second
   screen later. */

/* An input with a button welded to its right (Generate, Search).
   `.input` is width:100% from app.css. DEPLOY-HANDOFF, v67.0: "width:100%
   INSIDE A SHRINK-TO-FIT FLEX BOX COLLAPSES THE ROW" — the container's
   max-content size goes circular and the button wraps underneath a tall
   narrow input. So the input gets a DEFINITE basis and width:auto, and the
   button is told not to grow. flex-wrap is deliberate: on a narrow phone the
   button SHOULD drop below, and it does so at a sane width rather than by
   accident. */
.af-otp{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.af-otp .input{flex:1 1 220px;min-width:0;width:auto}
.af-otp .btn{flex:0 0 auto}

/* The shared "their affiliation" block sits under whichever of the two panes
   is showing, and needs to read as a continuation of it rather than a third
   unrelated card. */
.af-deal{border-top:1px solid var(--line);padding-top:16px}

/* .input is sized for one line; the terms box is a textarea wearing the same
   class for its border and focus ring. */
.af-terms{min-height:112px;resize:vertical;line-height:1.55}

/* The search results in the promote pane. A plain list of tappable rows —
   `cursor:pointer` matters because these are <div>s, not buttons (a button
   inside the results would submit nothing, and role=button on a div that
   never takes focus is worse than neither). */
.af-hit{display:block;width:100%;text-align:left;padding:9px 12px;margin-top:6px;
  border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface);
  cursor:pointer;font-family:var(--fb);color:var(--text)}
.af-hit:hover{background:var(--surface-2);border-color:var(--brand)}
.af-hit[aria-pressed="true"]{border-color:var(--brand);background:var(--brand-soft)}


/* ══════════════════════════════════════════════════════════════════════════
   R151 — THE AFFILIATE PORTAL, AND THE PAYOUT RECEIPT
   --------------------------------------------------------------------------
   The owner's note on the shipped page was that it "looks very simple with
   just words", and it did: every figure on it rendered at body size because
   affiliate.html painted its stat cards with a class named `stat-num` that
   exists in no stylesheet in this repo. The house component has always been
   .stat-card / .stat-icon / .stat-val / .stat-lbl (app.css §10) — the one
   admin.html's overview and student.html's dashboard both use. Fixing the
   class name is most of the difference; the rules below are the rest, and
   they are LAYOUT plus two pieces of genuinely new furniture (the payout
   timeline and the receipt viewer), not a second visual language.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The link, as the thing the page is actually for ──────────────────────
   An influencer opens this to get their code and leave. It is the hero, so
   it gets the width and the code gets display size — everything else on the
   panel is quieter than this by design. Grid rather than flex so the code
   block and the actions align on one baseline on a laptop and stack in the
   right order on a phone with no media query for the common case. */
.af-hero{display:grid;gap:14px}
.af-code{font-family:var(--fm);font-size:30px;font-weight:500;letter-spacing:.06em;
  line-height:1.1;color:var(--text);word-break:break-all}
@media(max-width:520px){.af-code{font-size:25px}}
.af-chips{display:flex;gap:7px;flex-wrap:wrap}

/* ── The funnel ───────────────────────────────────────────────────────────
   Three counts that are already on the page as separate cards, restated as
   one shape so the DROP between them is visible. A bar is the whole point:
   "412 opens, 30 signups" tells an affiliate nothing until the second number
   is drawn against the first. */
.af-funnel{display:grid;gap:11px;margin-top:4px}
.af-fn-row{display:grid;grid-template-columns:1fr auto;gap:4px 12px;align-items:baseline}
.af-fn-lbl{font-size:12.5px;color:var(--text2)}
.af-fn-val{font-family:var(--fm);font-size:13px;font-variant-numeric:tabular-nums;color:var(--text)}
.af-fn-row .progress{grid-column:1 / -1}
/* The three stages are ranked, so they are shaded rather than hued: same
   instrument, decreasing weight. Colour is never the only cue — the label
   and the number carry the meaning on their own. */
.af-fn-1 .progress-fill{background:var(--brand)}
.af-fn-2 .progress-fill{background:var(--accent)}
.af-fn-3 .progress-fill{background:var(--jade)}

/* ── The arrangement, and any other label/value list ──────────────────────
   Hairline-separated rows instead of a paragraph of facts. The last row
   drops its rule so the block does not end on a line with nothing under it. */
.af-row{display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  padding:10px 0;border-bottom:1px solid var(--line-2)}
.af-row:last-child{border-bottom:0;padding-bottom:0}
.af-row-k{font-size:13px;color:var(--text2)}
.af-row-v{font-family:var(--fm);font-size:13px;font-weight:500;text-align:end;
  color:var(--text);font-variant-numeric:tabular-nums}

/* ── Available balance, as one number rather than one of four ─────────────
   Money the affiliate can actually be sent is not the same KIND of fact as
   "paid to date", and giving them equal cards said it was. */
.af-money-hero{display:flex;flex-wrap:wrap;justify-content:space-between;
  align-items:flex-end;gap:12px;margin-bottom:14px}
.af-money-val{font-family:var(--fm);font-size:38px;font-weight:500;
  letter-spacing:-.035em;line-height:1;font-variant-numeric:tabular-nums}
@media(max-width:520px){.af-money-val{font-size:31px}}
.af-goal{margin-top:12px}
.af-goal-cap{display:flex;justify-content:space-between;gap:12px;
  font-size:12px;color:var(--text3);margin-top:6px}

/* ── A sale, and a payout ─────────────────────────────────────────────────
   Both were single lines of middle-dot-joined text. Rows with a definite
   left column and a right-aligned amount can be SCANNED; a sentence cannot. */
.af-item{display:grid;grid-template-columns:1fr auto;gap:3px 14px;
  padding:12px 0;border-bottom:1px solid var(--line-2)}
.af-item:last-child{border-bottom:0}
.af-item-t{font-size:13.5px;font-weight:600;color:var(--text)}
.af-item-s{grid-column:1;font-size:12px;color:var(--text3)}
.af-item-a{grid-row:1;grid-column:2;font-family:var(--fm);font-size:14px;
  font-weight:500;text-align:end;font-variant-numeric:tabular-nums;white-space:nowrap}
.af-item-x{grid-column:2;grid-row:2;text-align:end}

/* ── The payout timeline ──────────────────────────────────────────────────
   Payouts ARE a sequence — money left on these dates, in this order — which
   is the one condition under which a rail-and-dot treatment carries
   information instead of decorating. The rail is drawn on the container so
   it cannot double up per row, and it stops short of the last dot.
   `inset-inline-start` rather than `left`: this app has an RTL story. */
.af-time{position:relative;padding-inline-start:22px}
.af-time::before{content:"";position:absolute;inset-block:8px 14px;
  inset-inline-start:4px;width:1px;background:var(--line)}
.af-time .af-item{position:relative;border-bottom:0;padding-block:14px}
.af-time .af-item::before{content:"";position:absolute;inset-inline-start:-22px;top:19px;
  width:9px;height:9px;border-radius:50%;background:var(--surface);
  border:2px solid var(--jade)}
/* No proof attached yet is a real state the owner needs to see on their own
   screen, so it gets a hollow, uncoloured marker rather than a silent one. */
.af-time .af-item.af-nodoc::before{border-color:var(--line);background:var(--surface)}

/* ── The receipt viewer ───────────────────────────────────────────────────
   A modal rather than a new tab: on an in-app browser (Instagram, Telegram)
   a signed-URL tab often opens without chrome and strands the person with no
   way back. Fixed inset + its own scroll keeps a tall screenshot usable on a
   phone. */
.af-doc-ov{position:fixed;inset:0;z-index:120;display:flex;align-items:center;
  justify-content:center;padding:18px;background:rgba(0,0,0,.62)}
.af-doc-ov[hidden]{display:none}
.af-doc{background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-obj);box-shadow:var(--shadow);max-width:560px;width:100%;
  max-height:88vh;display:flex;flex-direction:column;overflow:hidden}
.af-doc-top{display:flex;justify-content:space-between;align-items:center;
  gap:12px;padding:13px 16px;border-bottom:1px solid var(--line)}
.af-doc-body{padding:16px;overflow:auto;flex:1 1 auto;text-align:center}
.af-doc-body img{max-width:100%;height:auto;border-radius:var(--r-sm);
  border:1px solid var(--line)}
.af-doc-foot{padding:12px 16px;border-top:1px solid var(--line);
  display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}

/* ── Admin side: the payout dialogue and the due badge ────────────────────
   Recording a payout used to be prompt(), which cannot take a file. */
.af-pay-form{display:grid;gap:10px;margin-top:10px;padding:13px;
  border:1px solid var(--brand);border-radius:var(--r-sm);background:var(--brand-soft)}
.af-pay-form .input{width:100%}
.af-pay-acts{display:flex;gap:8px;flex-wrap:wrap}
.af-due-bar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;
  padding:11px 13px;border:1px solid var(--accent);border-radius:var(--r-sm);
  background:var(--accent-soft);margin-bottom:14px}


/* ══════════════════════════════════════════════════════════════════════════
   R151.1 — THE AFFILIATE PORTAL HAD NO SECTION BOXES AT ALL
   --------------------------------------------------------------------------
   The owner's report was that the page is "crowded", the sections are
   "stacked by each other", and the design "still looks horrible". All three
   are one cause, and it is the SAME class of bug as `stat-num` one round
   earlier: affiliate.html wrapped every section in `<div class="card">`, and
   **`.card` is defined in no stylesheet in this repository.** Measured in
   Chromium, each one computes to

       padding: 0px   margin-bottom: 0px   background: transparent   border: 0

   so nine sections rendered as nine bare divs touching each other with no
   padding, no separation and no boundary. The two that also wore `.glass`
   got a border and a background — and then had their text pressed flat
   against that border, because `.glass` sets no padding either.

   This was NOT introduced by the redesign. v150's affiliate.html has the same
   nine `.card` divs and the same missing rule, which is why the page has
   looked like loose prose since r102. The redesign made the contents better
   and left them in the same non-existent boxes.

   Two lessons, both already written down and both missed:
     - "Before writing var(--something), grep to confirm it is defined" —
       NEXT-CHAT-PROMPT rule 8. The same discipline was never applied to CLASS
       names, and it needs to be: `.card`, `.panel` and `.stat-num` were all
       written confidently and none of them existed. `.panel` at least is
       declared in the page's own <style>; the other two were not.
     - A gate that greps source cannot catch this, and the r151 render gate
       measured the new furniture (.af-item, .af-time, .af-fn-row) without
       ever asking whether the BOX AROUND them had a padding. test-r151-render
       §6 now measures the section boxes themselves.
   ══════════════════════════════════════════════════════════════════════════ */

/* The vertical rhythm of a panel. GRID GAP, NOT MARGINS, for one reason that
   matters here: adjacent margins collapse and a `:last-child` margin leaks
   past the panel, so a margin-based stack silently gives different spacing
   depending on what happens to sit next to it. A gap cannot collapse, cannot
   leak, and needs no :first/:last exceptions. */
.af-stack{display:grid;gap:22px;align-content:start}
@media(max-width:520px){.af-stack{gap:16px}}

/* A section. This is what `.card` was always supposed to be on this page, and
   it is built from the same tokens as .glass rather than a second look —
   whatever the theme does to a card everywhere else, it does here. */
.af-sect{background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-obj);box-shadow:var(--shadow);padding:20px}
@media(max-width:520px){.af-sect{padding:15px}}

/* The hero gets more room and a faint tint, so the thing the page exists for
   reads as the primary object rather than as the first of nine equal boxes. */
.af-sect-hero{padding:24px;background:
  linear-gradient(180deg,var(--surface-3) 0%,var(--surface) 62%)}
@media(max-width:520px){.af-sect-hero{padding:18px}}

/* Section headings. An eyebrow plus a title gives the page a scannable
   skeleton on a phone, where all nine sections otherwise arrive as an
   undifferentiated column. The h3 loses its default margin because the
   spacing is owned by .af-head, in one place. */
.af-head{margin-bottom:15px}
.af-head h3{margin:0 0 2px;font-size:16px;line-height:1.25}
.af-eyebrow{display:block;font-size:11px;font-weight:600;letter-spacing:.09em;
  text-transform:uppercase;color:var(--text3);margin-bottom:5px}
.af-sub{font-size:12.5px;color:var(--text3);margin:0}

/* Spacing INSIDE a section. The funnel used to sit hard against the stat
   grid, and the note hard against the funnel; three blocks with nothing
   between them read as one block. */
.af-funnel{margin-top:18px}
.af-note{margin:14px 0 0;font-size:12px;color:var(--text3);line-height:1.5}
.af-divider{height:1px;background:var(--line-2);margin:18px 0}
