/* ==========================================================================
   CYGM base layer - reset, typography, layout primitives

   Depends on tokens.css. Reads semantic tokens only. There is no colour value
   in this file, and there must never be one.

   The type and motion treatments here are the original cygm.me ones, restored
   from the <style> block in ../../index.html after an achromatic pass was
   rejected.
   ========================================================================== */

@layer reset {

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

  * { margin: 0; }

  html {
    -webkit-text-size-adjust: 100%;
    /* Anchor jumps must clear the sticky header, or every deep link lands with
       its heading hidden underneath the nav. */
    scroll-padding-top: calc(var(--nav-height) + var(--step-4));
  }

  @media (prefers-reduced-motion: no-preference) {
    html { scroll-behavior: smooth; }
  }

  body {
    min-height: 100svh;
    font-family: var(--font-body);
    font-size: var(--size-base);
    line-height: var(--leading-body);
    color: var(--color-text);
    background: var(--color-page);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  img, picture, svg, video, canvas {
    display: block;
    max-width: 100%;
  }

  /* Height alongside width is what actually prevents layout shift, so every
     img in this codebase carries both attributes and this keeps the pair
     honest when CSS resizes one of them. */
  img { height: auto; }

  input, button, textarea, select { font: inherit; color: inherit; }

  button { background: none; border: 0; cursor: pointer; }

  /* A keyboard user gets a ring. A mouse user does not. :focus-visible is what
     separates the two, and it is why removing outlines wholesale is wrong. */
  :focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
  }
  :focus:not(:focus-visible) { outline: none; }

  /* Markers are deliberately NOT stripped globally. Doing that silently removes
     the numbers from every set of build steps on the site, and a numbered
     sequence that has lost its numbers reads as a run-on paragraph. The few
     lists that are navigation rather than content opt out with .list-plain. */
  ul, ol { padding-left: 1.4em; }

  table { border-collapse: collapse; width: 100%; }

  ::selection {
    background: var(--color-accent);
    color: var(--color-page);
  }
}

@layer base {

  :root { --nav-height: 3.25rem; }
  @media (min-width: 60rem) { :root { --nav-height: 4rem; } }

  /* ------------------------------------------------------------------------
     Headings. Hierarchy comes from size and weight only, never from colour.
     text-wrap: balance removes the one-word orphan line, which is the cheapest
     typographic upgrade available and the one readers notice without knowing
     why.
     ------------------------------------------------------------------------ */
  h1, h2, h3, h4 {
    font-family: var(--font-display);
    font-weight: 700;
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--color-text);
    text-wrap: balance;
    /* A display face broken mid-word reads as a bug at headline size, whatever
       the browser's hyphenation default happens to be. */
    hyphens: manual;
  }

  h1 {
    font-size: var(--size-3xl);
    font-weight: 800;
    line-height: 1.05;
    letter-spacing: -0.03em;
  }
  h2 { font-size: var(--size-2xl); letter-spacing: -0.02em; }
  h3 { font-size: var(--size-lg); font-weight: 600; line-height: var(--leading-snug); }
  h4 { font-size: var(--size-base); font-weight: 600; line-height: var(--leading-snug); }

  p { text-wrap: pretty; }

  p + p { margin-top: var(--step-4); }

  /* A sub-heading in running copy needs air above it, or it reads as the
     last line of the paragraph before. */
  :is(p, ul, ol, figure) + h3 { margin-top: var(--step-6); }

  strong { color: var(--color-text); font-weight: 600; }

  a {
    color: var(--color-accent-2);
    text-decoration: none;
    /* An underline that clears the descenders reads as typography rather than
       as a default browser link. */
    text-underline-offset: 0.22em;
    text-decoration-thickness: 1px;
    transition: color var(--dur) var(--ease);
  }

  a:hover { color: var(--color-text); text-decoration: underline; }

  /* Prose links inside body copy stay underlined at rest, because in a
     paragraph an underline is the only reliable signal that a word is a link. */
  .prose a { text-decoration: underline; }

  code, kbd, samp, .mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
  }

  code {
    font-size: 0.9em;
    padding: 0.15em 0.4em;
    background: var(--color-surface-alt);
    border: var(--hairline) solid var(--color-border);
    border-radius: var(--radius-tight);
    color: var(--color-text);
  }

  /* ------------------------------------------------------------------------
     The eyebrow. A numbered, uppercase, monospace micro-label above each
     section. It is the single strongest instrument cue on the page and it costs
     one element.
     ------------------------------------------------------------------------ */
  .eyebrow {
    font-family: var(--font-mono);
    font-size: var(--size-2xs);
    font-weight: 500;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--color-accent);
    display: flex;
    align-items: center;
    gap: var(--step-3);
    margin-bottom: var(--step-4);
  }

  /* The rule that trails an eyebrow across the column is a hairline, not a
     border on a box. Structure you can see, with nothing boxed in. */
  .eyebrow::after {
    content: '';
    flex: 1;
    height: var(--hairline);
    background: var(--color-border-strong);
  }

  .eyebrow-index { color: var(--color-accent-dim); }

  /* Numbers, units, versions and part references all speak in mono. This is the
     class that carries that voice everywhere it is not already implied. */
  .figure {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
  }

  /* ------------------------------------------------------------------------
     Content lists. The marker takes the monospace voice the rest of the site
     uses for numbers, so a step sequence reads as an instrument's procedure
     rather than as a word-processor list.
     ------------------------------------------------------------------------ */
  li { margin-bottom: var(--step-3); max-width: var(--measure); }

  li:last-child { margin-bottom: 0; }

  li::marker {
    font-family: var(--font-mono);
    font-size: 0.85em;
    color: var(--color-text-faint);
  }

  ol { list-style: decimal; }
  ul { list-style: disc; }

  /* Nested lists tighten rather than repeat the outer spacing. */
  li > ul, li > ol { margin-top: var(--step-3); }

  /* Navigation and chrome lists are structure, not content. */
  .list-plain,
  .nav-list,
  .footer ul,
  .crumbs ol {
    padding-left: 0;
    list-style: none;
  }

  .list-plain > li,
  .nav-list > li,
  .footer li,
  .crumbs li { max-width: none; }

  .lede {
    font-size: var(--size-lg);
    line-height: 1.45;
    color: var(--color-text-muted);
    max-width: var(--measure);
  }

  figcaption {
    margin-top: var(--step-3);
    font-size: var(--size-xs);
  }

  /* --------------------------------------------------------------------------
     Compact density, proportionally.

     A fixed 3rem section pad is tighter than the normal tier's own floor on a
     phone, so compact used to make a narrow screen denser than it makes a wide
     one. Growing the step with the viewport keeps compact reading as one tier
     rather than as two different decisions.
     -------------------------------------------------------------------------- */
  [data-density='compact'] { --space-section: calc(var(--step-7) + 2vw); }

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

  .skip-link {
    position: absolute;
    left: var(--step-4);
    top: var(--step-4);
    z-index: 100;
    padding: var(--step-3) var(--step-4);
    background: var(--color-surface-high);
    color: var(--color-text);
    border: var(--hairline) solid var(--color-border-strong);
    border-radius: var(--radius-button);
    transform: translateY(-200%);
  }
  .skip-link:focus { transform: none; }

  /* ------------------------------------------------------------------------
     Scroll reveal and entrance motion.

     .reveal is inert on its own: an IntersectionObserver adds .visible as the
     element enters the viewport. Without that script nothing on a .reveal
     element would ever become visible, so the reduced-motion block below also
     serves as the no-JS floor.
     ------------------------------------------------------------------------ */
  /* Gated on .js, which an inline script in the head adds before anything
     paints. Without that gate a reader whose script did not run, or ran and
     failed, would be served a page whose content is permanently at opacity 0.
     Hiding content on the assumption that script will arrive to unhide it is
     how a site becomes blank for the people least able to work around it. */
  .js .reveal {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity var(--dur-reveal) var(--ease-out),
                transform var(--dur-reveal) var(--ease-out);
  }
  .reveal.visible { opacity: 1; transform: translateY(0); }

  .reveal-delay-1 { transition-delay: 0.1s; }
  .reveal-delay-2 { transition-delay: 0.2s; }
  .reveal-delay-3 { transition-delay: 0.3s; }
  .reveal-delay-4 { transition-delay: 0.4s; }

  @keyframes fade-in-up {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  @keyframes pulse-dot {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.5; }
  }

  @media (prefers-reduced-motion: reduce) {
    .reveal, .reveal.visible {
      opacity: 1;
      transform: none;
      transition: none;
    }
    *, *::before, *::after { animation: none !important; }
  }
}

@layer layout {

  /* ------------------------------------------------------------------------
     One container, one gutter, everywhere. The gutter respects the notch on a
     landscape phone, which is the one place a fixed padding visibly fails.
     ------------------------------------------------------------------------ */
  .container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: max(var(--space-gutter), env(safe-area-inset-left));
  }

  .container-narrow { max-width: var(--container-narrow); }

  .section { padding-block: var(--space-section); }

  /* A hairline between sections instead of an alternating background. Bands of
     alternating colour are a template signature; a rule is a drawn line. */
  .section + .section { border-top: var(--hairline) solid var(--color-border); }

  /* ------------------------------------------------------------------------
     The grid. Twelve columns on desktop, six on tablet, four on phone. Every
     section snaps to it, and sections break it asymmetrically on purpose:
     centring everything is the fastest way to look generated.
     ------------------------------------------------------------------------ */
  .grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--step-5);
  }

  @media (min-width: 40rem) {
    .grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  }

  @media (min-width: 60rem) {
    .grid { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--step-6); }
  }

  .stack > * + * { margin-top: var(--flow, var(--step-4)); }

  .cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--step-3);
  }

  /* ------------------------------------------------------------------------
     Auto-fitting card rails. The card component sizes itself with a container
     query, so the rail only has to decide how many fit. That is why there are
     almost no width media queries in the component layer.
     ------------------------------------------------------------------------ */
  .rail {
    display: grid;
    gap: var(--step-4);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--rail-min, 17rem)), 1fr));
  }

  /* Subgrid is what makes a row of cards with unequal copy still line their
     footers up. It is the clearest single difference between a designed card
     grid and an assembled one. */
  @supports (grid-template-rows: subgrid) {
    .rail-aligned { grid-auto-rows: auto; }
    .rail-aligned > * {
      display: grid;
      grid-row: span 3;
      grid-template-rows: subgrid;
    }
  }

  /* Between these two widths auto-fit was choosing three columns of a rail of
     four or five cards, which leaves one card alone on a second row. Two is the
     only count that divides tidily at this width. */
  @media (min-width: 40rem) and (max-width: 59.999rem) {
    .rail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  .prose { max-width: var(--measure); }
  .prose-narrow { max-width: var(--measure-narrow); }

  /* ------------------------------------------------------------------------
     Section rhythm.

     These five rules replace roughly two hundred inline margin-top declarations
     that all carried the same handful of values. They are scoped to .section so
     a hero, whose internal spacing is its own composition, is untouched.

     A block that carries an image or a grid gets the larger step; a block of
     copy gets the smaller one; a second paragraph block after a first gets
     smaller again, because those two read as one passage.
     ------------------------------------------------------------------------ */
  .section :is(.rail, .shots, .grid, .photos, .table-wrap, .flash-panel) {
    margin-top: var(--step-7);
  }

  .section :is(.prose, .callout, .qa, .cluster) { margin-top: var(--step-6); }

  .section .prose + .prose { margin-top: var(--step-5); }

  .section .lede { margin-top: var(--step-4); }
  .section .lede:first-child { margin-top: 0; }

  /* One measure for the two things a reader's eye tracks across a section: the
     heading and the sentence under it. Only these two are capped, so a rail of
     cards below them still runs the full container width.

     The indirection is load-bearing. --measure is 68ch, and ch resolves against
     each element's OWN font, so applying it directly gives the heading a cap
     computed in a 48px display face and the sentence one computed in a 22px
     body face: two different edges, which is the opposite of one measure.
     Registering the shared value as a length makes it compute once, on the
     container, and inherit as a fixed number to both. */
  @property --measure-shared {
    syntax: '<length>';
    inherits: true;
    initial-value: 0px;
  }

  .section > .container { --measure-shared: var(--measure); }

  .section > .container > :is(h2, .lede) { max-width: var(--measure-shared); }
}

@layer utilities {
  .visually-hidden:not(:focus):not(:active) {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .no-wrap { white-space: nowrap; }
  .text-center { text-align: center; }

  /* Wide content must scroll inside its own box. The page body never scrolls
     sideways, which is the difference between a table that is awkward on a
     phone and a layout that is broken on one. */
  .scroll-x {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
  }
}
