/* gradeone.press — the site layer.
 *
 * This file is deliberately thin. build/css/epub.css is the design system: it
 * carries the palette, the type stack, the table rules and the dark mode, and
 * it is written against WCAG 2.1 AA because the European Accessibility Act
 * requires it of the book. A site that failed where the book passes would be
 * the drift failure docs/BUILD.md keeps recording, so the site loads the book's
 * stylesheet first and adds only what a web page needs and an EPUB does not:
 * a page container, a masthead, a footer, and the affordances that make a
 * 200-row table usable in a browser.
 *
 * The same rules bind here as bind there:
 *
 *   - No fixed font sizes. Readers resize text.
 *   - No colour used alone to carry meaning. Grades are digits, never swatches.
 *   - Real <table> markup, never a div grid, because screen readers need the
 *     header association.
 *   - No web fonts. Nothing is fetched from another host, which is also why
 *     there is no cookie banner to write.
 *
 * WHY THIS FILE USES CUSTOM PROPERTIES AND book.css DOES NOT
 *
 * book.css states its palette as literal hex codes and says why: Adobe RMSDK
 * and Kindle's KFX converter can drop `var()` outright, which would take every
 * table rule in the book with it. That constraint is about e-readers. This file
 * is only ever parsed by a browser, and paying an e-reader's tax here bought
 * nothing and cost something real — every colour was written twice, once in the
 * light rules and once in the dark block, and the two were free to drift. The
 * tokens below are declared once and the dark theme redefines only the tokens.
 * There is no second copy of a decision to forget to update.
 *
 * SIGNAL (#d1502e) is the cover's accent and appears here only where the cover
 * appears. It is not a UI colour; at 4.1:1 on paper white it does not clear AA
 * for body text and must never be used for one.
 */

:root {
  /* Palette. Contrast ratios are against the page colour of the same theme and
   * are the reason each value is what it is; check them before changing one. */
  --page:        #ffffff;
  --ink:         #1a1a1a;
  --accent:      #1d3557;  /* 12.4:1 */
  --link:        #10457a;  /* 8.6:1  */
  --muted:       #595959;  /* 7.0:1  */
  --rule:        #c9c9c9;  /* container edges */
  --rule-soft:   #e3e3e3;  /* row separators, one step quieter */
  --panel:       #f4f5f7;
  --field-bg:    #ffffff;
  --field-edge:  #767676;  /* 3:1 against the page — WCAG 1.4.11 */
  --on-accent:   #ffffff;  /* text on an accent or link fill */

  /* The sans stack, which every navigational element in both artefacts uses.
   * It was written out five times in this file before it was a token. */
  --sans: "Segoe UI", "Open Sans", "Noto Sans", Helvetica, Arial, sans-serif;

  /* Two type steps for chrome, not four. Nav was 0.94em, the filter 0.92em,
   * the footer 0.9em and the tables 0.88em — four values for two jobs, which
   * is the kind of near-miss that reads as untidiness without being nameable. */
  --fs-ui:   0.92em;   /* nav, filter, footer: interface text */
  --fs-data: 0.88em;   /* dense tabular text */
  --fs-lede: 1.25em;

  --radius: 2px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --page:        #16181c;
    --ink:         #e8e8e8;
    --accent:      #9dc4ea;  /* 8.1:1 */
    --link:        #8ab8e8;  /* 7.4:1 */
    --muted:       #a8a8a8;
    --rule:        #4a4f57;
    --rule-soft:   #303439;
    --panel:       #1e2126;
    --field-bg:    #1e2126;
    --field-edge:  #6b7178;
    --on-accent:   #16181c;
  }
}

/* `max-width` should mean the width of the thing, not the width of the thing
 * before its own padding and border are added. Without this, the padded blocks
 * — the panel, the profile definition lists, a card — each overran the measure
 * by their own padding, by a different amount, which is a misalignment nobody
 * can see the cause of. */
*, *::before, *::after { box-sizing: border-box; }

/* ------------------------------------------------------------ paragraphs */

/* epub.css sets `p { margin: 0; text-indent: 1.2em }` — the printed convention,
 * where a new paragraph is signalled by an indent and vertical space is saved.
 * It is right in a book and wrong here.
 *
 * On a screen a reader scans before they read, and run-on indented paragraphs
 * give them no landing points; the eye has to parse the text to find the
 * structure. Space between paragraphs is what a web reader is calibrated for,
 * and it is the convention every reading-mode implementation applies anyway.
 *
 * This is the one place the site deliberately departs from the book's
 * typography, and it applies everywhere — including the free chapters. Two
 * conventions across one site would read as a bug rather than as fidelity.
 */
main p {
  text-indent: 0;
  margin: 0 0 1em;
}

main li > p { margin-bottom: 0.5em; }

/* -------------------------------------------------------------- container
 *
 * ONE LEFT EDGE PER PAGE, AND IT IS THE POINT OF THIS SECTION.
 *
 * The masthead and the footer were both hard-coded to the wide container while
 * `main` chose its own width per page. On the eighteen pages that are not data
 * tables the result, measured at a 1400px viewport, was a masthead and a footer
 * whose content began at x=60 and an article that began at x=364. The rule
 * under the masthead, the rule over the footer and the text between them were
 * three objects on three different grids, and nothing on the page explained
 * why. That is the misalignment you see before you can name it.
 *
 * The shell width is now a property of the page rather than of the element, so
 * the masthead, the article and the footer cannot disagree about it. A data
 * page still gets the room its tables need; it just takes its chrome with it.
 */

body {
  /* epub.css sets `margin: 0 5%`, which is right for a reader that supplies
   * its own frame and wrong for a browser at 1600px. */
  margin: 0;
  padding: 0;

  --shell: 46rem;
  /* The prose measure: about 75 characters. A longer line is measurably
   * harder to track back from, which is why it is capped independently of the
   * shell rather than being whatever the shell happens to be. */
  --measure: 42rem;
}

/* The data tables need the room. Prose does not — see `main > *` below, which
 * is what stops a wide page handing its paragraphs the table's width. */
body.wide { --shell: 80rem; }

.wrap {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 1.25rem;
}

/* The measure applies to what is read, not to what is looked up. Tables, card
 * grids and the filter that drives a table are released to the full shell;
 * everything else — headings, paragraphs, lists, panels — is held to the
 * measure. On a narrow page the shell is the smaller of the two and this
 * changes nothing. On a wide one it is the difference between an intro
 * paragraph set to 672px and the same paragraph set to 1280px, which the
 * comment above the old `.wrap.wide` rule already argued against while the
 * layout went on doing it. */
main > * { max-width: var(--measure); }

main > .scroller,
main > .filter,
main > .cards { max-width: none; }

/* ---------------------------------------------------------------- masthead */

.masthead {
  border-bottom: 1px solid var(--rule);
  margin-bottom: 2rem;
}

.masthead .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.6rem 1.4rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
}

.masthead a { text-decoration: none; }
.masthead a:hover, .masthead a:focus { text-decoration: underline; }

.masthead .imprint {
  font-family: var(--sans);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--accent);
  margin-right: auto;
}

.masthead nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
}

.masthead nav li { margin: 0; }

.masthead nav a {
  font-family: var(--sans);
  font-size: var(--fs-ui);
}

/* aria-current marks the page you are on. It is announced by screen readers,
 * so the underline here is reinforcement rather than the only signal. */
.masthead nav a[aria-current="page"] {
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

/* ------------------------------------------------------------- skip link */

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--page);
  color: var(--link);
  padding: 0.6rem 1rem;
  z-index: 20;
}

.skip:focus { left: 0; }

/* ----------------------------------------------------------------- focus
 *
 * ONE FOCUS TREATMENT, BECAUSE THERE WERE FOUR.
 *
 * A form field outlined 2px, a scroll region outlined 3px, a button that
 * inverted its own colours, and every ordinary link left to whatever the
 * browser drew. A keyboard user learns the shape of a focus ring once and then
 * looks for that shape; four shapes is four things to learn on one site.
 *
 * `:focus-visible` rather than `:focus` so the ring appears for the keyboard
 * and not on every mouse click — which is also what stops the buy button
 * flipping to its inverted state and staying there after it is clicked.
 */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------- footer */

.sitefoot {
  border-top: 1px solid var(--rule);
  margin-top: 4rem;
  padding: 1.5rem 0 3rem;
  font-family: var(--sans);
  font-size: var(--fs-ui);
  color: var(--muted);
}

.sitefoot p { margin: 0 0 0.5em; text-indent: 0; }
.sitefoot p:last-child { margin-bottom: 0; }
.sitefoot a { color: var(--link); }

/* --------------------------------------------------------------- headings */

/* book.css opens a chapter with `h1 { margin: 2.4em 0 1.1em }` — a sink, which
 * is what a printed page does when a chapter starts partway down it. Under a
 * masthead that has already put a rule and 2rem between itself and the content,
 * the same sink measured 73px of empty space above the first line of every page
 * on this site, and the page looked like it had failed to load something.
 *
 * The book's own rhythm below the first heading is kept: h2 still opens a
 * section with 2.2em above it. Only the top of the page changes. */
main h1 {
  margin-top: 0;
  margin-bottom: 0.6em;
}

/* book.css sets h3 to 0.95em, and in the book that is right: an h3 there is a
 * B-head arriving deep inside a section whose own heading has already set the
 * scale, and it is meant to sit quietly under it. On the web the base size is
 * the reader's, not the designer's, so 0.95em is 15.2px against 16px body text
 * — a section heading smaller than the paragraph it introduces, which is the
 * one thing a heading may not be. It keeps the book's treatment in every other
 * respect: the navigation sans, semibold, black rather than accent, and clearly
 * below the 1.2em of an h2. */
main h3 { font-size: 1.05em; }

/* An eyebrow: the small tracked sans label above a title. book.css already
 * sets its own labels this way — the opener label, the setpiece label, the
 * imprint line on the title page — so this is the book's convention arriving
 * on the site rather than a new one invented for it. */
.eyebrow {
  font-family: var(--sans);
  font-size: 0.78em;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.7em;
}

.eyebrow a { color: inherit; }

/* --------------------------------------------------------------- lede */

/* The subtitle, and the one-sentence answer at the top of a data page. Large
 * enough to read as a deck rather than as a first paragraph.
 *
 * The second selector is what makes it consistent. Every generated page emits
 * `p.lede` explicitly; the hand-written ones — About, Corrections, Terms, and
 * each finding — are a markdown blob whose first paragraph is doing exactly the
 * same job ("Found an error? Send it.") and was set as body text. A reader
 * moving between them saw the site change shape for no reason they could name.
 * A chapter page is unaffected: its h1 is not the first child, the eyebrow is.
 */
main p.lede,
main > h1:first-child + p {
  font-size: var(--fs-lede);
  line-height: 1.4;
  color: var(--ink);
  margin: 0 0 1.5em;
  /* THE MEASURE, NOT A NARROWER ONE.
   *
   * This was capped at 34rem, inherited from a version of the file where the
   * lede was only ever the book's subtitle. It is now the opening paragraph of
   * every page, and 34rem against a 42rem body set it about fifty-four
   * characters wide where the prose under it runs to seventy-five — so the
   * first paragraph of the site stopped a third of the way short of the line
   * below it, on every page, and read as a column that had gone wrong rather
   * than as a deck.
   *
   * At 1.25em the same 42rem holds roughly sixty-seven characters, which is
   * inside the comfortable range and still shorter than the body in characters
   * even though both edges now line up. The size does the work of marking it
   * out; it does not need a second signal that costs alignment. */
  max-width: var(--measure);
  /* Display sizes should not hyphenate. book.css turns hyphenation on for the
   * whole document, which is right for justified-width body text and wrong
   * here: the About page was breaking "before" across two lines at 20px, in
   * the first sentence a reader sees. */
  -webkit-hyphens: none;
  hyphens: none;
}

/* --------------------------------------------------------- call to action */

.buy {
  display: inline-block;
  font-family: var(--sans);
  font-weight: 700;
  background: var(--accent);
  color: var(--on-accent);
  text-decoration: none;
  padding: 0.7em 1.4em;
  border: 2px solid var(--accent);
  border-radius: var(--radius);
}

.buy:hover {
  background: var(--page);
  color: var(--accent);
  text-decoration: underline;
}

.buy.secondary {
  background: transparent;
  color: var(--link);
  border-color: var(--link);
}

.buy.secondary:hover {
  background: var(--link);
  color: var(--on-accent);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
  margin: 1.5rem 0;
}

/* ---------------------------------------------------------------- panels */

.panel {
  background: var(--panel);
  border-left: 3px solid var(--accent);
  padding: 1rem 1.25rem;
  margin: 1.5rem 0;
}

.panel > :first-child { margin-top: 0; }
.panel > :last-child { margin-bottom: 0; }

/* -------------------------------------------------------------- data table */

/* A 200-row table must scroll inside its own box. The page body never scrolls
 * sideways — that breaks reading position on a phone and is a WCAG 1.4.10
 * reflow failure.
 *
 * THE HEIGHT CAP IS WHAT MAKES THE STICKY HEADER BELOW REAL.
 *
 * `position: sticky` sticks within its scroll container, and a scroll container
 * with no height constraint has no scroll range to stick in. This box declared
 * a sticky header from its first version and never had one: measured, the
 * /sources/ table was 74,235px tall inside a 74,235px box, so the column
 * headings scrolled off the top of a two-hundred-row table and did not come
 * back. /roles/ was 16,412px and /gaps/ 11,873px, the same way. Capping the box
 * gives the header something to stick to, and it is also what finally makes the
 * `tabindex="0"` on this element worth having — before, there was nothing to
 * scroll to.
 */
.scroller {
  overflow: auto;
  max-height: min(78vh, 44rem);
  margin: 1.5rem 0;
  border: 1px solid var(--rule);
  /* Tells the browser this box owns the gesture, so a phone does not chain the
   * scroll on to the page when the table reaches its end. */
  overscroll-behavior: contain;
}

/* The two summary tables — the grade distribution and the download list — are
 * three columns of prose read alongside the paragraphs around them, not data to
 * be scanned. `table { width: 100% }` was stretching them to the full 1280px of
 * a wide page, so five rows of "Grade / Meaning / Sources" spanned the viewport
 * while the sentence introducing them stopped at half that. */
.scroller.compact { max-width: var(--measure); }

/* A reference table with eight columns of prose cannot be made to fit a phone,
 * and squeezing it produces six-line cells that are harder to read than a
 * horizontal scroll. So the wide tables declare a floor and scroll below it,
 * inside .scroller, which is why the page body still never scrolls sideways. */
table.data.roomy { min-width: 76rem; }
table.data.roomy td, table.data.roomy th { min-width: 6rem; }

/* Long prose columns get the room; short factual ones do not need it. */
table.data col.w-wide  { width: 18%; }
table.data col.w-mid   { width: 12%; }
table.data col.w-tight { width: 6%; }

/* A table fills its box and drops its own frame. book.css gives a table a
 * 1.2em margin and rules of its own, which inside a bordered box read as a gap
 * and a double line. */
.scroller table { margin: 0; border: 0; width: 100%; }

/* THE STICKY HEADER KEYS OFF THE BOX, NOT OFF `table.data`.
 *
 * `table.data` is the class a template puts on a table it generates. The tables
 * written in markdown — the BLS occupation table on a finding, the instrument
 * panel in Chapter 8 — do not carry it and never will, and they are in the same
 * scrolling box for the same reason. Pinning a header is a property of being in
 * a scroll box, so that is what it keys off. */
.scroller thead th {
  position: sticky;
  top: 0;
  /* Above the cells it is now scrolling over, which is the other half of what
   * a sticky header needs and was never required while it did not stick. */
  z-index: 2;
  background: var(--page);
  /* book.css puts a 2px accent rule ABOVE a `th`, for a table that sits in
   * running text and has no frame of its own. This one has a frame — the box's
   * border — and the two together drew a double line across the top of every
   * data table on the site.
   *
   * The rule below the header becomes an inset shadow rather than a border,
   * because a border on a sticky element is painted at its original position by
   * some engines and disappears the moment the element actually sticks. */
  border-top: 0;
  border-bottom: 0;
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* The same double line at the other end: book.css closes a table with a 2px
 * accent rule under its last row, at a specificity that beat the site's own row
 * rule, and inside a bordered box that rule sat one pixel above the border. */
.scroller tbody tr:last-child th,
.scroller tbody tr:last-child td { border-bottom: 0; }

.scroller tbody tr:hover { background: var(--panel); }

table.data {
  font-family: var(--sans);
  font-size: var(--fs-data);
  border-collapse: collapse;
}

table.data th, table.data td {
  padding: 0.5em 0.7em;
  vertical-align: top;
  border-bottom: 1px solid var(--rule-soft);
}

table.data thead th {
  text-align: left;
  white-space: nowrap;
}

/* Grades are digits and stay digits. The weight change is secondary emphasis,
 * never the carrier of the meaning. */
td.grade { text-align: center; font-weight: 700; white-space: nowrap; }
td.num   { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
td.date  { white-space: nowrap; font-variant-numeric: tabular-nums; }

.muted { color: var(--muted); }

/* ------------------------------------------------------------------ filter */

.filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  margin: 1.5rem 0 0.5rem;
  font-family: var(--sans);
  font-size: var(--fs-ui);
}

.filter label { font-weight: 700; }

.filter input[type="search"], .filter select {
  font: inherit;
  padding: 0.4em 0.6em;
  border: 1px solid var(--field-edge);
  border-radius: var(--radius);
  background: var(--field-bg);
  color: var(--ink);
  min-width: 12rem;
}

/* Announced via aria-live, so a filter that returns nothing says so out loud
 * rather than presenting an empty table and no explanation. */
.count { color: var(--muted); }

/* ------------------------------------------------------------------- cards */

.cards {
  list-style: none;
  margin: 1.5rem 0;
  padding: 0;
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.cards li {
  margin: 0;
  border: 1px solid var(--rule);
  padding: 1rem 1.1rem;
}

/* The card title was an `<h3>`, and book.css sets h3 to 0.95em because in a
 * book an h3 is a C-head arriving deep inside a section that has already
 * established its own size. On a card it is the first and largest thing, and it
 * was rendering at 15.2px against 16px body text — a title smaller than the
 * sentence under it. It is an `<h2>` now, which is also the level the document
 * outline wants: these cards sit directly under the page's h1, and h1 → h3 → h2
 * was the heading order on both index pages. */
.cards h2 {
  font-family: var(--sans);
  font-size: 1.05em;
  margin: 0 0 0.4em;
}

.cards .eyebrow { margin-bottom: 0.35em; }
.cards p { margin-bottom: 0.5em; }
.cards p:last-child { margin-bottom: 0; }

/* -------------------------------------------------------------- definition */

.terms dt { margin-top: 1.1em; }

/* ------------------------------------------------------------------- print */

@media print {
  .masthead nav, .filter, .actions, .skip { display: none; }

  /* The height cap and the sticky header are screen affordances. On paper a
   * capped box prints one screenful of a two-hundred-row table and silently
   * drops the rest. */
  .scroller {
    overflow: visible;
    max-height: none;
    border: 0;
  }
  table.data thead th { position: static; }

  /* book.css sets `h1 { page-break-before: always }` so a chapter starts on a
   * fresh leaf. On a web page whose h1 is the first thing in the article, that
   * rule broke the page after the masthead and printed a first sheet carrying
   * nothing but the navigation. */
  main h1 { page-break-before: auto; break-before: auto; }

  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; }
}
