/* ============================================================================
   Srivus Scientifica - srivus.com
   Graphite and paper. Monochrome, quiet, academic.

   The palette comes from the app's own brand mark: graphite #22252A, paper
   #F5F4F1, a mid neutral for the bracket strokes. Screenshots supply all the
   color on the page, so the page itself never competes with them.

   Type is Atkinson Hyperlegible, the same Braille Institute family the app
   bundles for students with low vision or dyslexia. The files are served
   UNMODIFIED and are never subset: the license permits redistribution of the
   unaltered files only.
   ============================================================================ */

/* ---------- 1. Fonts ------------------------------------------------------ */

@font-face {
  font-family: "Atkinson Hyperlegible";
  src: url("../fonts/atkinson-regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  src: url("../fonts/atkinson-bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Atkinson Hyperlegible Mono";
  src: url("../fonts/atkinson-mono-regular.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Atkinson Hyperlegible Mono";
  src: url("../fonts/atkinson-mono-bold.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ---------- 2. Tokens ----------------------------------------------------- */
/* Light is defined on bare :root so the page always has a complete palette,
   even before any media query or toggle is consulted. */

:root {
  --paper:        #f5f4f1;
  --graphite:     #22252a;

  --bg:           #f5f4f1;
  --bg-alt:       #ecebe6;
  --surface:      #fffffe;
  --surface-2:    #f1efeb;
  --border:       #dcd8d1;
  --border-soft:  #e6e3dd;
  /* --border-control is SOLVED, not chosen by eye, and it is a separate token
     from --border on purpose. --border draws card and table edges, which are
     decoration: nothing is lost if you cannot quite see one. A form control's
     edge is the only thing that says where an EMPTY text field is, so WCAG
     1.4.11 asks it for 3:1, and --border measured 1.29:1 against the field fill
     and 1.42:1 against the card behind it. A control border sits BETWEEN those
     two surfaces, so it has to clear 3:1 on both, not on whichever is kinder.
     This is the lightest value that does: 3.24:1 on the fill, 3.56:1 on the
     card. Re-solve it against both if either surface token moves. */
  --border-control: #8d877c;

  --text:         #22252a;
  --text-muted:   #565b63;
  /* --text-faint is SOLVED, not chosen by eye. It was #8a8e95, which measured
     2.76:1 against the light surfaces -- well under the 4.5:1 body-text bar,
     and it is used for real prose (the snippet notes, the captions, the
     colophon), not just decoration. This value is the lightest gray that still
     clears 4.5:1 against every surface it sits on. If you change a surface
     token, re-solve this one against it. */
  --text-faint:   #666a71;

  /* Also solved rather than eyeballed. The accent is not decoration: it colors
     the eyebrows, the "In both editions" chips and the Out[] markers, all of
     which are text somebody reads. At #8a6a12 the worst case measured 3.81:1,
     partly because the chips sit on --accent-soft, which tints the backdrop
     toward the very color being measured. This value clears 4.5:1 against every
     surface AND against its own soft tint. */
  --accent:       #7c5c04;
  --accent-hover: #614801;
  --accent-soft:  rgba(124, 92, 4, 0.09);
  --on-accent:    #fffdf7;

  --shadow-sm: 0 1px 2px rgba(34, 37, 42, 0.06), 0 1px 1px rgba(34, 37, 42, 0.04);
  --shadow-md: 0 4px 14px rgba(34, 37, 42, 0.08), 0 1px 3px rgba(34, 37, 42, 0.05);
  --shadow-lg: 0 18px 48px rgba(34, 37, 42, 0.14), 0 4px 12px rgba(34, 37, 42, 0.07);

  --font-sans: "Atkinson Hyperlegible", ui-sans-serif, system-ui, -apple-system,
               "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono: "Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", "Cascadia Mono",
               Consolas, "Liberation Mono", monospace;

  --measure: 34rem;
  --container: 72rem;
  --container-wide: 84rem;

  --radius-sm: 6px;
  --radius:    10px;
  --radius-lg: 16px;

  --step: clamp(3.5rem, 8vw, 7rem);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:           #16181b;
    --bg-alt:       #1b1e22;
    --surface:      #22252a;
    --surface-2:    #2a2e34;
    --border:       #343941;
    --border-soft:  #2b3037;
    /* Solved the same way as the light one: 3:1 against BOTH surfaces a control
       border sits between. 4.12:1 on the field fill, 3.56:1 on the card.
       #686e78 was the first candidate and reached only 2.99 on the card. */
    --border-control: #747a84;

    --text:         #f2f1ee;
    --text-muted:   #aab0b8;
    --text-faint:   #8f959d;   /* solved for 4.5:1 on every dark surface; was #7f858d at 3.67:1 */

    --accent:       #d9b45b;
    --accent-hover: #e6c476;
    --accent-soft:  rgba(217, 180, 91, 0.13);
    --on-accent:    #1b1d21;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.45);
    --shadow-lg: 0 18px 48px rgba(0, 0, 0, 0.55);
  }
}

:root[data-theme="dark"] {
  --bg:           #16181b;
  --bg-alt:       #1b1e22;
  --surface:      #22252a;
  --surface-2:    #2a2e34;
  --border:       #343941;
  --border-soft:  #2b3037;
  --border-control: #747a84;   /* see the note in the media-query block above */

  --text:         #f2f1ee;
  --text-muted:   #aab0b8;
  --text-faint:   #8f959d;   /* solved for 4.5:1 on every dark surface; was #7f858d at 3.67:1 */

  --accent:       #d9b45b;
  --accent-hover: #e6c476;
  --accent-soft:  rgba(217, 180, 91, 0.13);
  --on-accent:    #1b1d21;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 18px 48px rgba(0, 0, 0, 0.55);
}

/* ---------- 3. Base ------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, svg, video { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  line-height: 1.18;
  letter-spacing: -0.018em;
  margin: 0 0 0.5em;
  font-weight: 700;
  text-wrap: balance;
}

h1 { font-size: clamp(2.25rem, 1.5rem + 3.4vw, 3.9rem); letter-spacing: -0.03em; }
h2 { font-size: clamp(1.7rem, 1.3rem + 1.7vw, 2.6rem); }
h3 { font-size: clamp(1.2rem, 1.08rem + 0.5vw, 1.45rem); letter-spacing: -0.01em; }
h4 { font-size: 1.05rem; }

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

/* The `hidden` attribute is styled `display: none` by the BROWSER's own sheet,
   and an author `display` of any specificity beats a UA one. So every element
   here that is hidden in markup and also carries a class setting `display`
   was never hidden at all: `.shot--pending` sets `display: grid`, and
   `#buy-now` is a `.btn` at `display: inline-flex`. The screenshot one shipped
   visibly - the striped "drop calc.png here" note rendered UNDER each real
   screenshot the day the screenshots landed, because until then the
   placeholder was SUPPOSED to be showing and nothing looked wrong. `!important`
   because the point is to outrank whatever a class says. */
[hidden] { display: none !important; }

a { color: inherit; text-decoration-color: var(--text-faint); text-underline-offset: 0.18em; }
a:hover { text-decoration-color: currentColor; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

::selection { background: var(--accent-soft); color: var(--text); }

code, kbd, pre { font-family: var(--font-mono); }

hr {
  border: 0;
  border-top: 1px solid var(--border-soft);
  margin: var(--step) 0;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; left: 1rem; top: -4rem; z-index: 200;
  background: var(--surface); color: var(--text);
  padding: 0.7rem 1.1rem; border-radius: var(--radius-sm);
  border: 1px solid var(--border); box-shadow: var(--shadow-md);
  text-decoration: none; font-weight: 700;
  transition: top 0.15s ease;
}
.skip-link:focus { top: 1rem; }

/* ---------- 4. Layout ----------------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(1.15rem, 4vw, 2.5rem);
}
.container--wide { max-width: var(--container-wide); }
.container--narrow { max-width: 46rem; }

.section { padding-block: var(--step); }
.section--tight { padding-block: calc(var(--step) * 0.6); }
.section--alt { background: var(--bg-alt); }
.section--edge { border-top: 1px solid var(--border-soft); }

.measure { max-width: var(--measure); }

.section-head { max-width: 40rem; margin-bottom: 2.75rem; }
/* The margin reset is :last-child ONLY. It used to apply to every paragraph
   here, which was harmless while a section head was eyebrow + h2 + one
   paragraph. Several heads now carry two or three, and a blanket reset ran
   them together into one block with no gap - and it silently flattened the
   .eyebrow's own 0.85rem too, since `.section-head p` outranks `.eyebrow`. */
.section-head p { color: var(--text-muted); font-size: 1.075rem; }
.section-head p:last-child { margin-bottom: 0; }
.section-head .eyebrow { margin-bottom: 0.85rem; }

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 0.85rem;
}

.lede {
  font-size: clamp(1.1rem, 1.02rem + 0.4vw, 1.3rem);
  line-height: 1.6;
  color: var(--text-muted);
}

/* ---------- 5. Buttons ---------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 0.82rem 1.5rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  font: inherit;
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease,
              transform 0.15s ease, box-shadow 0.15s ease, color 0.15s ease;
}
.btn:active { transform: translateY(1px); }

.btn--primary {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
  box-shadow: var(--shadow-sm);
}
.btn--primary:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

.btn--ghost { background: transparent; color: var(--text); border-color: var(--border); }
.btn--ghost:hover { border-color: var(--text); background: var(--surface); }

.btn--accent { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.btn--accent:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

.btn--sm { padding: 0.55rem 1rem; font-size: 0.92rem; }
.btn--block { width: 100%; }

.btn-row { display: flex; flex-wrap: wrap; gap: 0.85rem; align-items: center; }

.btn-note { font-size: 0.9rem; color: var(--text-faint); margin: 0.9rem 0 0; }

/* ---------- 6. Site header ------------------------------------------------ */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.5) blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}
.site-header[data-scrolled="true"] { border-bottom-color: var(--border-soft); }

.site-header__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 4.25rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  font-weight: 700;
  letter-spacing: -0.015em;
  margin-right: auto;
  flex-shrink: 0;
}
.brand__mark { width: 1.85rem; height: 1.85rem; }
.brand__name { font-size: 1.06rem; white-space: nowrap; }
.brand__name span { color: var(--text-faint); font-weight: 400; }

.site-nav { display: flex; align-items: center; gap: 0.35rem; }

/* :not(.btn) is load-bearing, not tidiness. Without it this rule (specificity
   0,1,1) beat .btn--primary (0,1,0) and repainted the header's Download button
   in --text-muted while it kept its --text background: measured at 1.93:1,
   which is gray on near-white. The most important button on the page was the
   least readable thing on it, and it looked deliberate enough to scroll past. */
.site-nav a:not(.btn) {
  padding: 0.45rem 0.7rem;
  border-radius: var(--radius-sm);
  text-decoration: none;
  font-size: 0.95rem;
  color: var(--text-muted);
  white-space: nowrap;
}
.site-nav a:not(.btn):hover { color: var(--text); background: var(--surface-2); }
.site-nav a:not(.btn)[aria-current="page"] { color: var(--text); font-weight: 700; }

/* THE SEVENTH LINK (Chemistry, 2026-08-16) MADE THE ROW WRAP, leaving "Schools"
   alone on a second line between roughly 990 and 1090px. Nothing was clipped and
   nothing overlapped - the header simply grew a second line - so this is tidiness
   rather than a defect, but it is the sort of tidiness a reader notices.

   IT TOOK THREE LEVERS TOGETHER, and each one alone was measured and found
   insufficient - which is the note worth leaving, because each looked like the
   whole answer at the time:
     - Raising the hide-breakpoint ALONE did not work. The wrap was still
       happening at 1040px, well above 64rem.
     - Shortening "The language" to "Language" saved 28px and left a 13px
       shortfall.
     - Trimming the links' side padding closed that 13px, and the row STILL
       wrapped at 996px.
   What ships is all three: hide below 64rem, shorter label, trimmed padding
   between 64rem and 72rem. Above 72rem the roomier padding returns and the row
   fits on its own.

   THE ARITHMETIC WAS WRONG BY ABOUT 40px at every step - it predicted a fit at
   1006px that did not happen at 1040px - because the header's padding is not
   what reading the CSS suggests, and because the browser renders a word-space
   between inline anchors that appears nowhere in the markup. Measure the wrap at
   several widths; do not compute it, and do not trust one width. */
@media (max-width: 64rem) {
  .site-nav__links { display: none; }
}

/* 0.4rem, not 0.5rem. At 0.5 the row came to 578px against 577px available and
   wrapped on THREE pixels - the links themselves fit, and what tipped it was the
   word-space the browser renders between inline anchors, which is invisible in
   the markup and easy to forget is there at all. */
@media (min-width: 64.01rem) and (max-width: 72rem) {
  .site-nav a:not(.btn) { padding-inline: 0.4rem; }
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.4rem; height: 2.4rem;
  padding: 0;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  flex-shrink: 0;
}
.theme-toggle:hover { color: var(--text); border-color: var(--text-faint); }
.theme-toggle svg { width: 1.15rem; height: 1.15rem; }
.theme-toggle .icon-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
}

/* ---------- 7. Hero ------------------------------------------------------- */

.hero { padding-block: clamp(3rem, 7vw, 6rem) 0; text-align: center; }

.hero__mark {
  width: clamp(4rem, 9vw, 5.5rem);
  height: auto;
  margin: 0 auto 1.75rem;
}

.hero h1 { margin-bottom: 0.6em; }
.hero h1 em { font-style: normal; color: var(--accent); }

/* The manifesto under the h1. It was written as <h3><p>...</p></h3>, which is
   invalid - a browser closes the h3 at the <p>, so the paragraph fell out of
   the heading and took none of its styling. It is a paragraph, and it is
   constrained here rather than left to run the full container width. */
.hero__manifesto {
  max-width: 46rem;
  margin: 0 auto 1.6rem;
  text-wrap: pretty;
}

.hero__lede {
  max-width: 40rem;
  margin: 0 auto 1.4rem;
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.35rem);
  line-height: 1.55;
  color: var(--text-muted);
}

.hero__promise {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.4rem 0.95rem;
  margin-bottom: 2rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--text-muted);
}
.hero__promise::before {
  content: "";
  width: 0.5rem; height: 0.5rem;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}

.hero .btn-row { justify-content: center; }

.hero__shot {
  margin-top: clamp(3rem, 6vw, 4.5rem);
  max-width: var(--container-wide);
  margin-inline: auto;
  padding-inline: clamp(1.15rem, 4vw, 2.5rem);
}

/* ---------- 8. Screenshot frame ------------------------------------------- */

.shot {
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--surface-2);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.shot img { width: 100%; display: block; }

.shot--flat { box-shadow: var(--shadow-md); border-radius: var(--radius); }

/* Every screenshot is a LINK to its own full-size file. Measured: the shots are
   1440x900 on disk and render in these columns at roughly 600-650px, so a
   visitor is looking at about 45% of the pixels that exist.

   display:block matters - it keeps the frame's geometry exactly what it was
   before the wrapper existed, so `.shot img { width: 100% }` still fills it and
   nothing shifts. With scripting off, or if site.js fails to load, this is an
   ordinary link and the browser opens the PNG. That is the feature working with
   no JavaScript at all; the overlay in section 22 is only an upgrade on top. */
.shot__zoom {
  display: block;
  position: relative;
  cursor: zoom-in;
  text-decoration: none;
}

/* The affordance. It stays invisible until the pointer or the keyboard arrives,
   so 27 screenshots do not each carry a permanent chip in the corner.

   The plus is drawn from two gradients rather than a glyph or a data-URI SVG,
   because both of those pin a color: a data URI cannot read a CSS variable, and
   this badge has to be legible in all three theme states. Two bars in
   var(--text) on var(--surface) follow the theme for free. */
.shot__zoom::after {
  content: "";
  position: absolute;
  right: 0.7rem;
  bottom: 0.7rem;
  width: 2.15rem;
  height: 2.15rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background:
    linear-gradient(var(--text), var(--text)) center / 0.85rem 2px no-repeat,
    linear-gradient(var(--text), var(--text)) center / 2px 0.85rem no-repeat,
    var(--surface);
  box-shadow: var(--shadow-md);
  opacity: 0;
  transform: translateY(0.3rem);
  transition: opacity 0.18s ease, transform 0.18s ease;
  pointer-events: none;
}
.shot__zoom:hover::after,
.shot__zoom:focus-visible::after { opacity: 1; transform: none; }

/* A shot whose file is missing has its ANCHOR hidden by initShots(), not just
   its image - an empty link with no accessible name is a keyboard stop that
   announces nothing. This is belt and braces for the same thing. */
.shot__zoom[hidden] { display: none; }

/* Two screenshots side by side, for the places where one picture cannot make
   the point on its own - the Statistics tab and the Statistical Distributions
   tab are two different tabs, and the section is about both. Stacks below the
   feature-row breakpoint so neither shot is ever squeezed to a stripe. */
.shot-pair { display: grid; gap: clamp(1.25rem, 3vw, 2rem); }
@media (min-width: 56rem) {
  .shot-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}

.shot__caption {
  padding: 0.7rem 1rem;
  border-top: 1px solid var(--border-soft);
  background: var(--surface);
  font-size: 0.86rem;
  color: var(--text-faint);
  text-align: center;
}

/* A placeholder that reads as deliberate while real screenshots are pending. */
.shot--pending {
  display: grid;
  place-items: center;
  min-height: clamp(13rem, 34vw, 26rem);
  background:
    repeating-linear-gradient(
      45deg,
      var(--surface) 0 12px,
      var(--surface-2) 12px 24px
    );
  text-align: center;
  padding: 2rem;
}
/* --text-muted, not --text-faint: the faint slot measured 3.67:1 against the
   striped placeholder, under the 4.5:1 body-text bar. Placeholder text is still
   text somebody has to read. */
.shot--pending .shot__label {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  max-width: 24rem;
}
.shot--pending .shot__label strong { display: block; color: var(--text); margin-bottom: 0.4rem; }

/* ---------- 9. Feature rows ----------------------------------------------- */

.feature {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  padding-block: clamp(2.5rem, 6vw, 4.5rem);
}
.feature + .feature { border-top: 1px solid var(--border-soft); }

@media (min-width: 56rem) {
  .feature { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }
  .feature--reverse .feature__media { order: -1; }
}

.feature__body h3 { margin-bottom: 0.55em; }
.feature__body > p { color: var(--text-muted); }

.feature__tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  padding: 0.25rem 0.55rem;
  border-radius: 4px;
  margin-bottom: 0.9rem;
}
.feature__tag--free { background: var(--accent-soft); color: var(--accent); }
.feature__tag--advanced { background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border); }

/* ---------- 10. Code samples ---------------------------------------------- */
/* Mirrors the app's own In[ ] / Out[ ] transcript, so what a student sees on
   the site is what they will see on screen. */

.snippet {
  margin: 1.35rem 0 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: 0.895rem;
  line-height: 1.55;
}
.snippet__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.7rem;
  padding: 0.62rem 0.95rem;
  overflow-x: auto;
}
.snippet__row + .snippet__row { border-top: 1px solid var(--border-soft); }
.snippet__row--out { background: var(--surface-2); }
.snippet__tag { color: var(--text-faint); user-select: none; white-space: nowrap; }
.snippet__row--out .snippet__tag { color: var(--accent); }
.snippet__code { white-space: pre; color: var(--text); }
.snippet__note {
  padding: 0.55rem 0.95rem;
  border-top: 1px solid var(--border-soft);
  font-family: var(--font-sans);
  font-size: 0.85rem;
  color: var(--text-faint);
}

/* ---------- 11. Cards and grids ------------------------------------------- */

.grid { display: grid; gap: 1.15rem; }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 15.5rem), 1fr)); }

.card {
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
/* h2 and h3 look identical inside a card ON PURPOSE. The heading LEVEL is an
   outline fact for anyone navigating by headings, and the SIZE is a visual one;
   tying them together is what pushes an author into writing h3 because h2 looks
   too big, which is how buy.html came to run h1 -> h3 -> h2. Pick the level the
   outline needs and let the card decide the size. */
.card h2,
.card h3 { font-size: 1.08rem; margin-bottom: 0.4em; letter-spacing: -0.01em; }
.card p { color: var(--text-muted); font-size: 0.95rem; margin-bottom: 0; }
.card__icon {
  width: 2rem; height: 2rem;
  margin-bottom: 0.9rem;
  color: var(--accent);
}

/* ---------- 12. Quote ----------------------------------------------------- */

.quote {
  max-width: 44rem;
  margin: 0 auto;
  padding-left: clamp(1rem, 3vw, 1.75rem);
  border-left: 3px solid var(--accent);
}
.quote p {
  font-size: clamp(1.1rem, 1rem + 0.6vw, 1.35rem);
  line-height: 1.6;
}
.quote cite {
  display: block;
  margin-top: 1.1rem;
  font-style: normal;
  font-size: 0.92rem;
  color: var(--text-faint);
}

/* ---------- 13. Theme gallery --------------------------------------------- */

.gallery__frame { position: relative; }

.gallery__swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  justify-content: center;
  margin-top: 1.5rem;
}

.swatch {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.85rem 0.45rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  font: inherit;
  font-size: 0.88rem;
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}
.swatch:hover { color: var(--text); border-color: var(--text-faint); }
.swatch[aria-pressed="true"] {
  border-color: var(--text);
  color: var(--text);
  font-weight: 700;
  background: var(--surface-2);
}
.swatch__chip {
  width: 1.05rem; height: 1.05rem;
  border-radius: 50%;
  border: 1px solid var(--border);
  flex-shrink: 0;
}

/* ---------- 14. Editions table -------------------------------------------- */

.editions { display: grid; gap: 1.25rem; }
@media (min-width: 52rem) { .editions { grid-template-columns: 1fr 1fr; } }

.edition {
  display: flex;
  flex-direction: column;
  padding: clamp(1.5rem, 3vw, 2.15rem);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.edition--featured { border-color: var(--accent); box-shadow: var(--shadow-md); }

.edition__name { font-size: 1.3rem; margin-bottom: 0.25em; }
.edition__price {
  font-size: 1.9rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0.5rem 0 0.15rem;
}
.edition__price small { font-size: 0.95rem; font-weight: 400; color: var(--text-faint); }
.edition__who {
  font-size: 0.94rem;
  color: var(--text-muted);
  padding-bottom: 1.1rem;
  margin-bottom: 1.1rem;
  border-bottom: 1px solid var(--border-soft);
}
.edition__list { list-style: none; margin: 0 0 1.6rem; padding: 0; display: grid; gap: 0.55rem; }

/* The check mark is positioned, NOT a grid column.
   This used to be `display: grid` with a 1.15rem icon track and a text track,
   which looks equivalent and is not. A grid container turns every loose run of
   text into its own anonymous grid item, so a bullet written as
   `<strong>Notebooks</strong> - a script editor` is TWO items: the <strong>
   took the text column and the description was pushed into an implicit row in
   the 18px icon column, where it wrapped one word per line. Only the one
   bullet with no <strong> in it looked right, which is what made it easy to
   miss. Absolute positioning is immune to however the content is marked up. */
.edition__list li {
  position: relative;
  padding-left: 1.75rem;
  font-size: 0.95rem;
  color: var(--text-muted);
}
.edition__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.3em;
  width: 1.15rem; height: 1.15rem;
  background: var(--accent);
  -webkit-mask: var(--check) center / contain no-repeat;
  mask: var(--check) center / contain no-repeat;
}
.edition__cta { margin-top: auto; }

:root {
  --check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.6 13.4 4.2 10l-1.2 1.2 4.6 4.6 9.4-9.4-1.2-1.2z'/%3E%3C/svg%3E");
}

/* ---------- 15. FAQ ------------------------------------------------------- */

.faq { max-width: 46rem; }
.faq details {
  border-bottom: 1px solid var(--border-soft);
  padding: 0.35rem 0;
}
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.05rem 0;
  cursor: pointer;
  font-weight: 700;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
/* A flex item will not shrink below its own min-content width unless it is
   told it may, so an element child of a summary - the one on download.html
   that wraps a <code> - held the row 3px wider than the container and gave the
   WHOLE PAGE a sideways scroll on a 375px phone. Measured: 383px of scroll
   width against a 375px viewport, back to 375 with this line. */
.faq summary > * { min-width: 0; }
.faq summary::after {
  content: "";
  flex-shrink: 0;
  width: 0.7rem; height: 0.7rem;
  border-right: 2px solid var(--text-faint);
  border-bottom: 2px solid var(--text-faint);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.2s ease;
}
.faq details[open] summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.faq details > div { padding-bottom: 1.15rem; color: var(--text-muted); }

/* ---------- 16. Schools band ---------------------------------------------- */

.band {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: clamp(1.75rem, 4vw, 3rem);
}
.band__grid { display: grid; gap: 2rem; align-items: start; }
@media (min-width: 56rem) { .band__grid { grid-template-columns: 1.2fr 1fr; } }

/* ---------- 17. Download page --------------------------------------------- */

.platform-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.75rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--border-soft);
}
.platform-tab {
  padding: 0.6rem 1.05rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: transparent;
  font: inherit;
  font-size: 0.94rem;
  color: var(--text-muted);
  cursor: pointer;
}
.platform-tab:hover { color: var(--text); border-color: var(--text-faint); }
.platform-tab[aria-selected="true"] {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg);
  font-weight: 700;
}

.steps { counter-reset: step; list-style: none; margin: 0; padding: 0; display: grid; gap: 1.5rem; }
.steps > li {
  counter-increment: step;
  display: grid;
  grid-template-columns: 2.1rem minmax(0, 1fr);
  gap: 1rem;
}
.steps > li::before {
  content: counter(step);
  display: grid;
  place-items: center;
  width: 2.1rem; height: 2.1rem;
  border-radius: 50%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text-muted);
}
.steps h3 { font-size: 1.05rem; margin-bottom: 0.3em; }
.steps p { color: var(--text-muted); font-size: 0.96rem; }

.cmd {
  display: block;
  margin: 0.8rem 0 0;
  padding: 0.85rem 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 0.87rem;
  overflow-x: auto;
  white-space: pre;
}

.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); }
table.data { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
table.data th, table.data td {
  text-align: left;
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--border-soft);
  white-space: nowrap;
}
table.data th { background: var(--surface-2); font-size: 0.82rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
table.data tr:last-child td { border-bottom: 0; }
table.data td.wrap { white-space: normal; }
table.data code { font-size: 0.85rem; word-break: break-all; }

.callout {
  display: grid;
  grid-template-columns: 1.4rem minmax(0, 1fr);
  gap: 0.85rem;
  padding: 1.1rem 1.25rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--accent-soft);
  font-size: 0.95rem;
}
.callout__icon { width: 1.4rem; height: 1.4rem; color: var(--accent); }
.callout p { margin-bottom: 0.5em; }
.callout p:last-child { margin-bottom: 0; }

/* ---------- 18. Footer ---------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--border-soft);
  padding-block: var(--step) 2.5rem;
  background: var(--bg-alt);
}
.site-footer__grid { display: grid; gap: 2.5rem; }
@media (min-width: 46rem) { .site-footer__grid { grid-template-columns: 1.4fr 1fr 1fr; } }

/* h3, not h4. The footer headings followed the page's h2 sections, so the
   outline read h2 -> h4 and a screen-reader user walking by heading level
   would hear a level that has no parent. */
.site-footer h3 {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 1rem;
}
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
.site-footer a { color: var(--text-muted); text-decoration: none; font-size: 0.95rem; }
.site-footer a:hover { color: var(--text); text-decoration: underline; }

.etymology {
  font-size: 0.95rem;
  color: var(--text-muted);
  max-width: 30rem;
}
.etymology em { color: var(--text); font-style: normal; font-weight: 700; }

.colophon {
  margin-top: 2.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--border-soft);
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  justify-content: space-between;
  font-size: 0.87rem;
  color: var(--text-faint);
}

/* ---------- 19. Motion ---------------------------------------------------- */

.reveal { opacity: 0; transform: translateY(14px); }
.reveal[data-shown="true"] {
  opacity: 1;
  transform: none;
  transition: opacity 0.55s ease, transform 0.55s ease;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ---------- 20. Quote builder --------------------------------------------- */
/* The form on quote.html. Named qb- rather than quote- because section 12's
   .quote is the pull quote on the home page, and one of those names had to
   give way. Form controls get explicit colors: a bare <input> inherits the
   browser's own light-mode chrome and turns into black text on a white box in
   the middle of the dark theme. */

.qb { max-width: 44rem; }

.qb fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.4rem 1.5rem 1.6rem;
  margin: 0 0 1.5rem;
  background: var(--surface);
}
.qb legend {
  font-weight: 700;
  padding: 0 0.5rem;
  font-size: 1.02rem;
}

.qb__row { display: grid; gap: 1.1rem; }
@media (min-width: 40rem) {
  .qb__row--two { grid-template-columns: 1fr 1fr; }
}

.qb__field { display: flex; flex-direction: column; gap: 0.35rem; }

.qb__field label { font-weight: 700; font-size: 0.94rem; }
.qb__req { color: var(--accent); font-weight: 700; }

.qb__field input,
.qb__field select {
  font-family: var(--font-sans);
  font-size: 1rem;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.7rem;
  width: 100%;
  min-height: 2.75rem;   /* a comfortable target, and the same height either side */
}
.qb__field select { cursor: pointer; }
.qb__field input:hover,
.qb__field select:hover { border-color: var(--text-faint); }

.qb__help { font-size: 0.87rem; color: var(--text-faint); margin: 0; }

/* Invalid state is never color alone: the border thickens and a worded message
   appears under the field, tied to it with aria-describedby. */
.qb__field input[aria-invalid="true"],
.qb__field select[aria-invalid="true"] {
  border-color: var(--accent);
  border-width: 2px;
}
.qb__error { font-size: 0.87rem; color: var(--accent); font-weight: 700; margin: 0; }
.qb__error:empty { display: none; }

.qb__actions { display: flex; flex-wrap: wrap; gap: 0.85rem; align-items: center; }

/* The whole-form message, used for "fill in the school name" and for the
   configuration notice. Bordered and worded, never a bare red tint. */
.qb__notice {
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding: 1rem 1.15rem;
  margin: 0 0 1.5rem;
}
.qb__notice p { font-size: 0.95rem; margin-bottom: 0.6em; }
.qb__notice p:last-child { margin-bottom: 0; }
.qb__notice code { font-size: 0.85rem; }

/* ---------- 21. The generated quote document ------------------------------ */
/* Deliberately plain. This is the only thing on the site that gets printed,
   photocopied and stapled to a requisition, so it does not use the page's
   graphite-and-paper styling beyond the type. */

.qdoc {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: clamp(1.5rem, 4vw, 2.75rem);
  margin-top: 2rem;
}

.qdoc__head {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  justify-content: space-between;
  align-items: flex-start;
  padding-bottom: 1.25rem;
  border-bottom: 2px solid var(--text);
  margin-bottom: 1.5rem;
}
.qdoc__title { font-size: 1.5rem; margin: 0 0 0.25rem; }
.qdoc__meta { font-family: var(--font-mono); font-size: 0.84rem; color: var(--text-muted); }
.qdoc__meta div { white-space: nowrap; }

.qdoc__parties { display: grid; gap: 1.75rem; margin-bottom: 1.75rem; }
@media (min-width: 40rem) { .qdoc__parties { grid-template-columns: 1fr 1fr; } }
.qdoc__party h3 {
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.5em;
}
.qdoc__party p { font-size: 0.95rem; margin: 0; }

.qdoc table { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; }
.qdoc th, .qdoc td {
  text-align: left;
  padding: 0.75rem 0.6rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.95rem;
  vertical-align: top;
}
.qdoc thead th {
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  border-bottom: 1px solid var(--text-muted);
}
.qdoc .num { text-align: right; white-space: nowrap; font-family: var(--font-mono); }
.qdoc tr.qdoc__total td {
  border-bottom: 0;
  border-top: 2px solid var(--text);
  font-weight: 700;
  font-size: 1.1rem;
  padding-top: 0.9rem;
}

.qdoc__terms { border-top: 1px solid var(--border); padding-top: 1.25rem; }
.qdoc__terms h3 { font-size: 0.95rem; margin-bottom: 0.6em; }
.qdoc__terms ul { margin: 0; padding-left: 1.2rem; }
.qdoc__terms li { font-size: 0.9rem; color: var(--text-muted); margin-bottom: 0.4em; }

/* An unset seller field must be impossible to overlook in a printed document. */
.qdoc__unset {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  background: var(--accent-soft);
  border: 1px dashed var(--accent);
  color: var(--accent);
  padding: 0.05em 0.35em;
  border-radius: 3px;
}

/* ---------- 22. The enlarged screenshot ----------------------------------- */

/* Built by site.js, never authored into a page, so nothing is duplicated across
   the seven pages and index.html gains only the 27 link wrappers.

   z-index 300 clears both of the things that already stack: the skip link at
   200 and the sticky header at 100. */
.lightbox {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 300;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 0.85rem;
  padding: clamp(0.75rem, 3vw, 2.5rem);

  /* The scrim is dark in EVERY theme, deliberately. A screenshot judges best
     against a neutral dark surround, and the picture is the subject here, not
     the page behind it. That is why the few colors in this section are literal
     rather than tokens - they sit on this scrim, not on a themed surface. */
  background: rgba(12, 13, 15, 0.88);

  /* THERE IS NO FADE HERE, and that is the considered choice rather than a
     thing left undone.

     Both ways of writing one start the overlay at opacity 0 and rely on the
     frame clock to carry it to 1 - a transition needs a second frame to move
     on, and a keyframe animation needs frames to advance through. When those
     frames do not come, neither degrades to "appears without a fade". Both
     leave a FULLY TRANSPARENT overlay covering the viewport, swallowing clicks
     and holding the focus trap: an invisible modal.

     Measured 2026-09-07, not reasoned about. With the overlay open, a running
     lightbox-in animation sat at currentTime 0 for two seconds while the image
     underneath it had loaded and laid out correctly at 973x608. A stalled frame
     clock is not a normal browsing condition, but this is a program for school
     machines, and the cost of the effect failing badly is out of all proportion
     to the effect. So the overlay is simply visible when it is shown. */
}

/* `display: grid` above beats the hidden attribute's UA rule, so it is restated
   here. Without this line the overlay is visible from the moment it is built. */
.lightbox[hidden] { display: none; }

/* Never scaled past its natural size: there is no width here, only a ceiling.
   On a 1920 screen a 1440-wide shot is shown at 1440 - every pixel in the file
   and not one interpolated one. */
.lightbox__img {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - 7rem);
  width: auto;
  height: auto;
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}

.lightbox__caption {
  max-width: 60rem;
  margin: 0;
  text-align: center;
  font-size: 0.88rem;
  line-height: 1.5;
  color: #e6e3dd;
}

.lightbox__close {
  position: absolute;
  top: clamp(0.5rem, 2vw, 1.4rem);
  right: clamp(0.5rem, 2vw, 1.4rem);
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.38);
  background: rgba(20, 21, 24, 0.72);
  color: #f4f2ee;
  font-family: var(--font-mono);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}
.lightbox__close:hover { background: rgba(72, 75, 82, 0.92); }

/* The page's focus ring is var(--accent), which in the LIGHT theme is #7c5c04 -
   a dark gold that all but disappears on this scrim. Inside the overlay the
   ring uses the dark theme's accent, which is what the scrim actually is. */
.lightbox :focus-visible { outline-color: #d9b45b; }

/* Hold the page still underneath. Both elements, because body already carries
   overflow-x: hidden and setting only one of them lets the page scroll on. */
.has-lightbox, .has-lightbox body { overflow: hidden; }

/* ---------- 23. Print ------------------------------------------------------ */
/* Save as PDF is how a teacher will actually turn this into an attachment, so
   printing has to yield the quote ALONE: no navigation, no form, no footer,
   and black on white whatever theme the screen was in. */

@media print {
  .site-header, .site-footer, .qb, .qb__notice, .skip-link,
  .qdoc__actions, .noprint, .lightbox { display: none !important; }

  /* The badge is a screen affordance and prints as a stray circle otherwise. */
  .shot__zoom::after { display: none !important; }

  html, body { background: #ffffff !important; color: #000000 !important; }
  body { font-size: 11pt; line-height: 1.45; }
  .section { padding-block: 0 !important; }

  .qdoc {
    border: 0;
    border-radius: 0;
    padding: 0;
    margin: 0;
    background: #ffffff !important;
    color: #000000 !important;
  }
  .qdoc__head { border-bottom-color: #000000; }
  .qdoc__meta, .qdoc__party h3, .qdoc thead th, .qdoc__terms li { color: #333333 !important; }
  .qdoc th, .qdoc td { border-bottom-color: #999999; }
  .qdoc tr.qdoc__total td { border-top-color: #000000; }
  .qdoc__unset { background: #ffffff !important; border-color: #000000; color: #000000 !important; }

  /* Keep the money and the terms on one page with what they refer to. */
  .qdoc table, .qdoc__terms, .qdoc__parties { break-inside: avoid; }

  @page { margin: 18mm 16mm; }
}
