/* Shared foundation for katjanorstad.no.
 *
 * Linked immediately BEFORE each page's own <style> block, so a page rule of
 * equal specificity still wins. Only rules proven identical (or with an
 * identical shared half) across all 12 pages live here.
 *
 * Deliberately NOT here, because measurement showed they differ for real:
 *   .wrap            4 layout widths: 860 / 780 / 640 / 520px
 *   footer padding   4 values
 *   body font-size   set on 10 of 12; index and snake omit it on purpose
 * See docs/superpowers/specs/2026-09-07-design-refinement-design.md
 */

/* Every size on this site is already expressed in rem, so scaling the root
   scales the whole type system at once -- tokens, clamp() headings and all --
   without editing a single page rule.

   The bounds are in rem, not px, on purpose. Per CSS, rem on the root element
   resolves against font-size's initial value, i.e. the reader's own browser
   default. A px clamp here would silently override someone who had set their
   default to 20px, which is the exact group that cannot afford to lose it.

   Measured against a 16px default: 16.04px at 375px wide (mobile is unchanged)
   rising to 18px at 1280px and holding. Old-style faces have a small x-height,
   so 18px of one reads nearer 15px of a normal face; the site was genuinely
   undersized on a desktop.

   That still holds after the switch from Cormorant Garamond to EB Garamond.
   Measured from the OS/2 tables: x-height 0.400 em against Cormorant's 0.386,
   3.6% larger, which at 18px is a quarter of a pixel. EB Garamond reads
   sturdier because its strokes carry more ink, not because it is bigger, so
   the compensation here is not double-counting. */
html { font-size: clamp(1rem, .944rem + .25vw, 1.125rem); }

:root {
  --bg: #f5f2ed;
  --ink: #1a1814;
  --muted: #6e6963;
  --accent: #9a2a2a;
  --line: #d8d3cc;

  /* Two greys for lines, because a line drawn under a paragraph and a line
     that IS a button are not the same object.

     --line stays where it is. At 1.33:1 against the page it is barely there,
     which is exactly right for a rule between two rows of text: the text is
     what you read, the rule only says "these are separate". WCAG agrees --
     purely decorative parts are exempt from 1.4.11.

     --line-strong is for controls whose border is the only thing that makes
     them a control. The play buttons, the learned dots, the snake D-pad and
     the voice picker are all background:transparent, so there is no fill to
     see and no label inside; take the hairline away and there is nothing on
     the page. Those need 3:1 under 1.4.11, and 1.33:1 was not close.
     Measured against --bg: #d8d3cc is 1.33:1, this is 3.03:1. It is the same
     hue walked down in lightness until it cleared the bar, so the controls
     get more definite without the palette turning cold. */
  --line-strong: #8f8b87;

  /* Three roles, three faces. The split is the one both of the sites this
     design borrows from use: a serif you read, a sans for the furniture
     around it, and a mono reserved for things that are not words.
     collusion.wiki names its three --serif / --sans / --mono; the New York
     Times sets its body in Imperial and every kicker, byline and label in
     Franklin.
     ------------------------------------------------------------------------
     WHICH ROLE GETS WHICH is not a matter of taste, and there is a test on it
     in tests/typography.test.js:

       --font-serif   running text, headings, anything that is a sentence
       --font-ui      names: labels, navigation, buttons, tags, captions
       --font-mono    figures and notation: clock readouts, scores, counts,
                      IPA. Never a word, and never uppercase.

     The site used to have no such rule. DM Mono set 65 of its 91 font-family
     declarations -- every eyebrow, every footer, every button -- which is why
     the furniture read as terminal output rather than as an editorial page.
     Neither reference site uses a monospace that way. */
  --font-serif: 'EB Garamond', Georgia, 'Times New Roman', serif;

  /* A system stack, and deliberately not a fourth webfont.
     Libre Franklin was the alternative and it measured 28.6KB per weight for
     text that is never larger than 12px and always tracked-out uppercase --
     the exact size at which SF, Segoe UI and Roboto are indistinguishable
     from a bought face. It would also have arrived after first paint, so the
     furniture would have swapped under the reader while the page settled.
     collusion.wiki reaches the same conclusion: its --sans is a system stack
     headed by Avenir Next, and only its serif is self-hosted. */
  --font-ui: 'Avenir Next', Avenir, -apple-system, system-ui, 'Segoe UI',
             Roboto, 'Helvetica Neue', Arial, sans-serif;

  --font-mono: 'DM Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  /* One scale replacing 47 ad-hoc font-size values. Headings keep their
     clamp() rules and are not tokenised.

     --fs-4xs was added after the first tokenising pass left 39 literals
     behind. Counting them showed the omission: 25 of the 39 sat at 9.28 or
     9.6px -- 0.32px apart, the same size in practice -- while the scale
     bottomed out at 10.4px. The site has two micro tiers for its uppercase
     mono labels, and the scale had only ever named the upper one. */
  --fs-4xs: .6rem;
  --fs-3xs: .65rem;
  --fs-2xs: .7rem;
  --fs-xs: .8rem;
  --fs-sm: .875rem;
  --fs-base: 1rem;
  --fs-md: 1.1rem;
  --fs-lg: 1.2rem;
  --fs-xl: 1.3rem;
  --fs-2xl: 1.5rem;
  --fs-3xl: 1.6rem;
  --fs-body: 1.125rem;

  /* A length, not a multiplier. A unitless line-height is re-multiplied by each
     descendant's own font-size, so every block invents its own rhythm; a length
     inherits as one computed value and puts running text on a shared baseline.

     Being in rem it still tracks the fluid root above, so the ratio to body
     text stays at 1.689 from 375px to 1920px instead of drifting.

     Only safe on elements at or below body size. Anything larger -- a display
     word, a timer readout -- must set its own line-height or its glyphs will
     outgrow the line box. */
  --leading-body: 1.9rem;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* The universal core only. font-size and line-height stay on the pages that
   set them, because index.html and snake.html deliberately do not.

   font-weight is 400 where it said 300 for Cormorant. EB Garamond's lightest
   cut IS 400, so a 300 here would not have lightened anything -- the browser
   would have used 400 anyway and the tier above it would have collapsed into
   the body text. The three tiers the site had (300 body / 400 title / 500
   strong) are now two: 400 running text, 500 anything picked out of it. */
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-serif);
  font-weight: 400;
  min-height: 100vh;
}

/* Optical sizing, done by hand, because EB Garamond has no optical axis and
   no cut below 400 -- its wght axis is literally min 400, and Google answers
   a request for 300 with the 400 file and no warning. So when large text
   reads too heavy, weight is not available as the fix.
   ------------------------------------------------------------------------
   What IS available: macOS renders text with a smoothing pass that fattens
   the strokes, and turning it off thins them without touching size, colour
   or contrast. Measured on the homepage greeting by rendering it and weighing
   the ink pixel by pixel (see the note in the commit):

     as it was, EB Garamond 400        ink 100%   stroke darkness 100%
     grayscale smoothing                   -20%                   100%
     Cormorant 300, the old face           -27%                   100%

   That last row is the target: this is a correction of a regression the font
   swap introduced, not a new preference. Fading the colour would also have
   dropped the ink 11%, but it drops stroke darkness with it -- that is not
   lighter type, that is greyer type, and the old face was full black too.

   Scoped to headings on purpose. The same rule on body copy would thin 18px
   running text by the same 20%, which is the exact complaint that got
   Cormorant replaced in the first place. Thin at display size, full weight
   at reading size, which is what an optical axis would have done for us.

   Does nothing off macOS -- and does not need to. Windows and Linux never
   applied the fattening pass, so the heaviness this corrects is not there. */
h1 {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.back {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--font-ui);
  font-size: var(--fs-3xs);
  /* Explicit, so the length --leading-body cannot inherit down and wrap this
     10px label in a 30px line box. Same reason on .eyebrow below. */
  line-height: 1.4;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  padding: 1.8rem 0 0;
  transition: color .2s;
}
.back:hover { color: var(--accent); }

.eyebrow {
  font-family: var(--font-ui);
  font-size: var(--fs-2xs);
  line-height: 1.4;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 1rem;
}

/* text-align is unanimous across all 11 pages with a footer; padding is not. */
footer { text-align: center; }

/* Site-wide navigation.
 *
 * The site was a strict tree: every page offered "back" to its parent and
 * nothing else, so reaching Coffee notes from the Pomodoro timer meant two
 * hops through the homepage, and a language page was three from anywhere
 * that was not a language. This is the flat version, in the same sans /
 * uppercase / tracked vocabulary as .back and .eyebrow so it reads as
 * furniture rather than as content competing with the page.
 *
 * .back stays. It is the one-key escape and it names WHERE back goes,
 * which a flat list of six cannot. */
.sitenav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  /* No row gap: the links carry their own vertical padding for the tap
     target, and a gap on top of that opens a visible hole between rows. */
  gap: 0 1rem;
  margin-bottom: .6rem;
  font-family: var(--font-ui);
  font-size: var(--fs-4xs);
  line-height: 1.4;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.sitenav a,
.sitenav span {
  /* WCAG 2.5.8 wants 24px. The text alone is a ~13px line box, so the
     padding is what makes these tappable -- same reasoning as the learned
     dot, and the reason this is padding rather than a row gap. */
  display: inline-block;
  padding: .55rem .15rem;
  color: var(--muted);
  text-decoration: none;
  transition: color .2s;
}
.sitenav a:hover { color: var(--accent); }
/* The page you are on is listed but not linked: it says where you are
   without offering a click that goes nowhere. */
.sitenav [aria-current="page"] { color: var(--ink); }

@keyframes rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Four of these pages are the tool, not a description of the tool: the word
   lists, the timer, the board and the coffee figures are all built in the
   browser. With scripting off they do not degrade, they just stop -- a
   heading and then nothing. There is no build step here and no server, so
   the content cannot be pre-rendered into the HTML; the honest fix is to say
   so rather than leave someone looking at a blank page wondering whether it
   is still loading. Not on cv/resources/languages/index: those are written
   out in HTML and read the same either way. */
.noscript-note {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--ink);
  background: #efe9df;
  border-left: 3px solid var(--accent);
  padding: .9rem 1.1rem;
  margin: 1.5rem 0;
  border-radius: 2px;
}

/* Durations collapse to .01ms rather than `animation: none`. Elements that
   start at opacity:0 are revealed BY the animation; disabling it outright
   would leave them invisible for exactly the users who asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
