/* stephenkelman.co.uk — replica stylesheet
   Type scale + palette are the measured values from docs/design-reference/.

   SPACING SOURCE (2026-08-05): the live site's own compiled theme stylesheet is
   cached at docs/design-reference/theme.css. Every metric it declares is copied
   verbatim below — including its two real breakpoints, **932px and 540px**
   (the earlier 1000/768/520 were guesses). Anything Adobe applied at RUNTIME
   via JS — project/about module rhythm, page-header offsets, per-page
   backgrounds — is NOT in that file and stays at its live-measured value. */

:root {
  --font-sans: "neue-haas-unica", "Helvetica Neue", Arial, sans-serif;
  --font-serif: "le-monde-livre-std", Georgia, "Times New Roman", serif;

  --c-text: #282828;      /* primary */
  --c-secondary: #636569; /* h1, body, project titles */
  --c-muted: #939393;     /* nav */
  --c-faint: #b9b9b9;     /* footer */
  --c-accent: #ef4023;    /* CTA outline/text */
  --c-bg: #ffffff;

  /* body line-height — the unit for "a line of space" (matches the blank-line
     gaps authored inside the text modules themselves) */
  --line: 24px;
  /* air above AND below the project-page title; halves at the 932 breakpoint */
  --title-gap: 48px;

  --maxw-page: 1400px;
  --maxw-project: 1200px;
  --maxw-narrow: 700px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* No letter-spacing anywhere (live uses none) */
body, h1, h2, h3, p, a, nav, .btn, .masthead-title, .project-title { letter-spacing: normal; }
body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 24px;
  color: var(--c-secondary);
  background: var(--c-bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }

.visually-hidden {
  position: absolute !important; 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: -9999px; top: 0; background: #fff;
  padding: 8px 16px; z-index: 100;
}
.skip-link:focus { left: 8px; top: 8px; }

/* ---------- Header ---------- */
.site-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  /* live: height 60, padding 0 3% (the old 38px was 3% frozen at a 1280 window) */
  height: 60px;
  padding: 0 3%;
  position: sticky;
  top: 0;
  z-index: 50;
  background: #fff;   /* header stays white even on dark pages (live) */
}
.primary-nav { justify-self: start; }
.nav-menu {
  /* live: nav items carry padding-right 20px (last-child 0) */
  display: flex; gap: 20px; list-style: none; margin: 0; padding: 0;
}
.nav-menu a {
  font-size: 15px; line-height: 24px; color: var(--c-muted);
}
.nav-menu a:hover, .nav-menu a[aria-current="page"] { color: var(--c-text); }

.logo { justify-self: center; }
.logo img { width: 52px; height: 44px; }

.social {
  justify-self: end;
  /* Live: 25px icons on a 35px pitch → 10px gap (measured 1280). */
  display: flex; gap: 10px; list-style: none; margin: 0; padding: 0;
  align-items: center;
}
.social a { color: var(--c-muted); display: inline-flex; }
.social a:hover { color: var(--c-text); }
.social svg { width: 25px; height: 25px; fill: currentColor; }

.nav-toggle { display: none; }

/* ---------- Dark pages ----------
   Live keeps the WHITE header + grey nav on dark pages (only the content area
   below goes black). So the header is unchanged; just the project title on the
   black content lifts to #bfbfbf. */
.theme-dark .project-title { color: #bfbfbf; }

/* ---------- Masthead (home) ---------- */
/* live .masthead: padding 95px 0% 72px. Our header is in flow (sticky) and
   takes the first 60px, so the top padding is 95-60=35. .masthead-contents caps
   the whole stack at 650px — applied here per child. */
.masthead {
  text-align: center;
  margin: 0 auto;
  padding: 35px 0 72px;
}
.masthead-title {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: 31px;
  line-height: 42px;
  color: var(--c-secondary);
  margin: 0 auto;
  max-width: 650px;
}
.masthead-sub {
  font-size: 15px; line-height: 24px; color: var(--c-secondary);
  margin: 25px auto 5px;   /* live .masthead p: margin 25px top / 5px bottom */
  max-width: 650px;
}
.masthead-buttons {
  /* live: .masthead-buttons padding-top 27px; buttons 17px apart. PADDING, not
     margin — a top margin would collapse with the subtitle's 5px bottom margin
     and swallow it, putting the buttons 5px high. */
  display: flex; justify-content: center; gap: 17px; margin: 0 auto;
  padding-top: 27px; max-width: 650px; flex-wrap: wrap;
}
.btn {
  /* live .masthead-button-1/2: height 40, padding 0 15px, 1px border.
     Live sets line-height:40 to centre the label, but that centres the EM box —
     and Unica's ascent (16px at 15px type) is far taller than its cap height
     (10.7px), so the letterforms land ~1.7px low. Centring with flex and
     trimming 2px off the bottom puts the cap block on the optical centre
     instead. Height and padding are unchanged, so the pill matches live. */
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; height: 40px; line-height: 1;
  color: var(--c-accent);
  border: 1px solid var(--c-accent);
  border-radius: 20px;
  padding: 0 15px 2px;
  transition: background .15s ease, color .15s ease;
}
.btn:hover { background: var(--c-accent); color: #fff; }

/* ---------- Project grid (home) ---------- */
/* Live structure (measured 2026-08-04 at 1280/768/375): the cell is 400×315 and
   the cover sits inside it with `margin: 10px 5px 0`. That margin — not a grid
   gap — is what separates the thumbnails, so the grid itself stays gap-0 and
   unpadded, and the 5px sides inset the outer edges of the grid too. */
.project-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;                 /* gutters come from the cover margins, as on live */
  max-width: 1200px;      /* 3 × 400px, centred (live) */
  margin: 0 auto;
  padding: 0;             /* live .project-covers: padding 0 at every width —
                             the footer's 60px top padding is the only gap */
  --cover-inset-y: 10px;  /* live cover margin-top */
  --cover-inset-x: 5px;   /* live cover margin left/right */
}
.grid-item { position: relative; display: block; background: transparent; }
/* The cover's own aspect ratio drives the cell height at every width (live's
   in-flow <img> mechanism): cell = cover height + top margin. 409/320 is the
   covers' intrinsic ratio; yields live's measured cells at 1280/768/375
   (400x315 / 384x301 / 375x300). */
.grid-cover {
  display: block; overflow: hidden;
  margin: var(--cover-inset-y) var(--cover-inset-x) 0;
  aspect-ratio: 409 / 320;
}
.grid-cover picture { display: block; width: 100%; height: 100%; }
.grid-item img { width: 100%; height: 100%; object-fit: cover; }
/* Rollover (matches live): a near-white panel fades in over the cover, inset
   0.65em from the cover on ALL FOUR sides — bottom included — with the project
   title centred in #565656 and padded 15% each side. */
.grid-title {
  position: absolute;
  top: calc(var(--cover-inset-y) + .65em); bottom: .65em;
  left: calc(var(--cover-inset-x) + .65em); right: calc(var(--cover-inset-x) + .65em);
  display: flex; align-items: center; justify-content: center;
  /* Live pads the title 15% of the PANEL width; a bare 15% here would resolve
     against the cell instead, so subtract the panel's own insets first. */
  padding: 0 calc(.15 * (100% - 2 * (var(--cover-inset-x) + .65em)));
  text-align: center;
  font-size: 15px; line-height: 24px; color: #565656;
  background: rgba(255, 255, 255, .97);
  opacity: 0; transition: opacity .15s ease;
}
.grid-item:hover .grid-title, .grid-item:focus-visible .grid-title { opacity: 1; }

/* ---------- Project page ---------- */
/* Live (measured at 900/1440): images are FULL-BLEED — no side padding below
   the 1200px cap; each image module carries 14px bottom padding; text modules
   are a 500px box with 45px internal side padding (410px measure); buttons
   pad 14px 45px. Module spacing is per-module, not a flex gap. */
/* The project title sits in equal air: --title-gap above (as the page's own top
   padding, under the 60px header) and the same below (Stephen, 2026-08-05 —
   was 48/20 desktop and 24/14 mobile; equalised to the LARGER of the pair). */
.project { padding: var(--title-gap) 0 64px; }
.project-title {
  text-align: center;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 15px;
  line-height: 24px;
  color: var(--c-secondary);
  margin: 0 0 var(--title-gap);
}
.project-modules {
  max-width: var(--maxw-project);
  margin: 0 auto;
  display: flex; flex-direction: column; gap: 0;
}
.project-image { margin: 0; padding-bottom: 20px; }
.project-image img { width: 100%; cursor: zoom-in; }

/* Text + button modules (interleaved with images).
   Block is centred (max-width + auto margins); text itself reads left — Adobe's
   align-center centres the block, not the copy. */
/* Every module carries its own TRAILING space — that's what replaced the flex
   gap. Text modules were missing theirs, so a text block had 20px above it
   (the previous image's) and 0 below, and the next image butted straight up. */
/* One blank line of air above AND below the copy (Stephen, 2026-08-05).
   The space above already carries the preceding module's 20px trailing rhythm,
   so the text only adds its own line on top; below it must supply both. Net
   result is symmetric: 20+24 = 44px each side (14+24 = 38px under 932). */
.project-text { font-size: 15px; line-height: 24px; margin: 0 auto; padding: var(--line) 45px calc(20px + var(--line)); width: 100%; max-width: 500px; text-align: left; }
.project-text a, .about-text a, .contact-intro a { color: inherit; text-decoration: underline; }
.project-text.align-right { text-align: right; }
.project-text p { margin: 0; }
.project-button { text-align: center; margin: 0; padding: 20px 0; }
.project-embed { margin: 0 auto; padding-bottom: 20px; width: 100%; }
.project-embed .embed-ratio { position: relative; height: 0; }
.project-embed iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }

/* ---------- Narrow pages (about / contact) ---------- */
.page-narrow { max-width: 548px; margin: 0 auto; padding: 24px 24px 80px; }
.about-image { margin: 0 0 32px; }
/* Match live rhythm: headings sit TIGHT above their body (big gap above to
   separate sections, none below); paragraphs/links are one blank line (24px)
   apart; consecutive lines within a para (award/education lists) stay tight. */
.about-text p, .about-text h2 { font-size: 15px; line-height: 24px; color: var(--c-secondary); }
/* Unica Medium (500) now in the kit — matches live's subheading weight. */
/* Measured live rhythm (2026-08-04): heading sits 28px below the previous
   block (mt 28 collapses over the para's mb 24); the big "—" section
   separators get 52px above AND below. */
.about-text h2 { font-weight: 500; margin: 28px 0 0; }
/* Services headings carry 2 authored blank lines on live (52px); the CV-tail
   headings carry 1 (28px, the default above). Flag comes from about.json. */
.about-text h2.gap-wide { margin-top: 52px; }
.about-text .about-para { margin: 0 0 24px; }
.about-text h2 + .about-para { margin-top: 0; }   /* body tight under its heading */
.about-text .about-link { margin: 0 0 24px; }
.about-text .about-divider { color: var(--c-muted); margin: 52px 0; }
.contact-intro p { margin: 0 0 20px; }
.contact-heading { font-size: 15px; font-weight: 500; color: var(--c-secondary); margin: 0 0 20px; }

/* ---------- Contact form ---------- */
.contact-form { display: flex; flex-direction: column; gap: 12px; margin-top: 28px; }
.contact-form input, .contact-form textarea {
  font-family: var(--font-sans); font-size: 15px; color: var(--c-text);
  padding: 14px 16px; border: 1px solid #dadada; border-radius: 3px; width: 100%;
}
.contact-form textarea { resize: vertical; }
.contact-form button {
  align-self: flex-start;
  font-family: var(--font-sans); font-size: 15px; color: var(--c-secondary);
  background: none; border: 1px solid #dadada; border-radius: 3px;   /* match inputs */
  padding: 12px 28px; cursor: pointer;
}
.contact-form button:hover { border-color: var(--c-text); color: var(--c-text); }

/* ---------- 404 ---------- */
.error-page { text-align: center; max-width: 560px; margin: 0 auto; padding: 80px 24px 100px; }
.error-title { font-family: var(--font-serif); font-weight: 300; font-size: 31px; line-height: 42px; color: var(--c-secondary); margin: 0 0 16px; }
.error-page .btn { margin-top: 24px; }

/* ---------- Footer ---------- */
/* live .site-footer: padding 60px 0 (28px at <=932, 25px at <=540) */
.site-footer { text-align: center; padding: 60px 0; }
.site-footer p { font-size: 12px; line-height: 20px; color: var(--c-faint); margin: 0; }

/* ---------- Lightbox ---------- */
.lightbox {
  position: fixed; inset: 0; background: rgba(255,255,255,.97);
  display: none; align-items: center; justify-content: center; z-index: 200;
  cursor: zoom-out;
}
.lightbox.open { display: flex; }
.lightbox img { max-width: 92vw; max-height: 92vh; width: auto; height: auto; }
.lightbox-close {
  position: absolute; top: 20px; right: 24px; font-size: 28px;
  background: none; border: 0; color: var(--c-text); cursor: pointer; line-height: 1;
}

/* ---------- Responsive ----------
   Live has exactly TWO breakpoints, both from theme.css: 932px and 540px. */

@media (max-width: 932px) {
  /* Masthead ladder (live): padding 79px 5% 32px, h1 18/24, p 14/20 with
     15px above and 5px below, buttons 15px under that at 38px tall.
     79px is measured from the page top; our header takes the first 60. */
  .masthead { padding: 19px 5% 32px; }
  .masthead-title { font-size: 18px; line-height: 24px; }
  .masthead-sub { font-size: 14px; line-height: 20px; margin-top: 15px; }
  .masthead-buttons { padding-top: 15px; }
  .btn { font-size: 14px; height: 38px; }   /* line-height stays 1: see .btn */

  /* Project pages drop to live's mobile rhythm: 24px under the header, 14px
     modules, 14/20 title. Measured on live at 800/375 — Adobe applies module
     spacing via JS, so theme.css can't confirm it; the 932 boundary is
     inherited from every rule that IS in the file. */
  /* --title-gap drives both the space above the title and the space below it */
  :root { --title-gap: 24px; }
  .project-title { font-size: 14px; line-height: 20px; }
  .project-image, .project-embed { padding-bottom: 14px; }
  .project-text { padding-bottom: calc(14px + var(--line)); }
  .project-button { padding: 14px 0; }

  /* live .site-footer at <=932: 28px top/bottom, 5% sides */
  .site-footer { padding: 28px 5%; }

  /* Live mobile header: 60px tall, 5% padding, logo CENTRED at 48x40 (live
     caps .logo-image img to max-height 40), hamburger RIGHT, socials hidden. */
  .site-header { grid-template-columns: 1fr auto 1fr; padding: 0 5%; height: 60px; align-items: center; }
  .primary-nav { grid-column: 3; grid-row: 1; justify-self: end; }
  .logo { grid-column: 2; grid-row: 1; }
  .logo img { width: 48px; height: 40px; }
  .social { display: none; }

  /* Live glyphs (measured): burger = three 24x2 bars, 4px gaps, GREY #939393
     (var(--c-muted)); close = two 2x20 strokes at ±45°, same grey, 20x20 box. */
  .nav-toggle {
    display: inline-flex; flex-direction: column; justify-content: center; gap: 4px;
    width: 44px; height: 44px; align-items: center;
    background: none; border: 0; cursor: pointer; padding: 0;
    position: relative; z-index: 210;   /* stays above the open menu overlay */
  }
  .nav-toggle span { display: block; height: 2px; background: var(--c-muted); width: 24px; transition: transform .2s ease, opacity .2s ease, width .2s ease; }
  /* Open state: the bars become live's close button, which is its own fixed
     element at top 40px / right 5% in a 20x20 box (top 30px under 540). */
  .primary-nav.open .nav-toggle {
    position: fixed; top: 40px; right: 5%; width: 20px; height: 20px; gap: 0;
  }
  .primary-nav.open .nav-toggle span { position: absolute; }
  .primary-nav.open .nav-toggle span:nth-child(1) { width: 20px; transform: rotate(45deg); }
  .primary-nav.open .nav-toggle span:nth-child(2) { opacity: 0; }
  .primary-nav.open .nav-toggle span:nth-child(3) { width: 20px; transform: rotate(-45deg); }

  /* Live's open menu is a FULL-SCREEN white overlay: .responsive-nav pads
     50px 5% 0 and centres the link block, which itself carries an 80px bottom
     padding to clear the social bar. Links are 22/32 with 30px under EACH
     (including the last) -> a 62px pitch. */
  .nav-menu {
    display: none; position: fixed; inset: 0;
    flex-direction: column; align-items: center; justify-content: center;
    gap: 0; padding: 50px 5% 80px; background: #fff; z-index: 200;
  }
  .nav-menu li { padding-bottom: 30px; }
  .primary-nav.open .nav-menu { display: flex; }
  /* live's overlay shows no current-page highlight — every link is the same grey */
  .nav-menu a, .nav-menu a[aria-current="page"] { font-size: 22px; line-height: 32px; color: var(--c-muted); }
  .nav-menu a:hover { color: var(--c-text); }

  /* ...with the social icons centred along the bottom, above a hairline. Live:
     min-height 80, padding 5%, 1px rgba(0,0,0,.08) top border, and icons on a
     62px pitch (25px glyph + 20px li padding + the svg's own letterboxing). */
  .primary-nav.open ~ .social {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 205;
    /* the <ul> is a grid item of the header, and its justify-self:end beats the
       left/right:0 stretch — pin the width so the hairline spans edge to edge */
    width: 100%; justify-self: stretch;
    justify-content: center; gap: 37px; padding: 5%; min-height: 80px;
    background: #fff; border-top: 1px solid rgba(0, 0, 0, .08); margin: 0;
  }

  /* Live is full-bleed here (grid padding 0) with a shallower cover margin. */
  .project-grid { grid-template-columns: repeat(2, 1fr); --cover-inset-y: 8px; }
  .grid-title { font-size: 14px; line-height: 20px; }
}

@media (max-width: 540px) {
  /* live at <=540: masthead padding-bottom 28, h1 16/22, p margin-top 14,
     buttons 14px under it, stacked with a 17px spacer, 35px tall. */
  .masthead { padding-bottom: 28px; }
  .masthead-title { font-size: 16px; line-height: 22px; }
  .masthead-sub { margin-top: 14px; }
  .masthead-buttons { padding-top: 14px; flex-direction: column; align-items: center; gap: 17px; }
  .btn { height: 35px; }
  .primary-nav.open .nav-toggle { top: 30px; }
  .site-footer { padding: 25px 5%; }
  /* Single column on live drops the side margins entirely. */
  .project-grid { grid-template-columns: 1fr; --cover-inset-y: 7px; --cover-inset-x: 0px; }
}

/* asset rev: turnstile-invisible-gap
   NB the ?v= cache-buster is an md5 of this file (+ site.js + image-manifest),
   and /assets/* ships `immutable, max-age=31536000` via _headers. So a request
   made in the propagation window right after `wrangler pages deploy` — new HTML
   with the new hash, old asset bytes still at the edge — pins STALE content
   under the NEW url for a year. Wait ~60s after deploying before fetching, or
   the verification request itself poisons the cache. Bump this comment to mint
   a fresh hash if that happens. */

/* Contact form result message (post-submit redirect) */
.contact-result { color: var(--c-secondary); margin: 0 0 24px; }

/* Anti-spam honeypot — off-screen rather than display:none, which the cruder
   bots check for. Never focusable, never announced. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
/* Turnstile widget sits in the form's flex column; the iframe carries its own
   box, so it only needs the column's rhythm. */
.contact-form .cf-turnstile { margin-top: 4px; }

/* In Invisible mode Turnstile renders NO iframe — the container is left at
   zero height but still occupies a flex item, so it ate a gap either side and
   put 28px above Send where every other field gap is 12px. Take it out of
   flow when it has nothing to show. Deliberately position:absolute rather
   than display:none: the element stays rendered and scriptable (Turnstile
   still populates the hidden token input from it) and keeps its static
   position, it just stops consuming a gap. Managed/Non-interactive modes DO
   render an iframe, so they keep their slot and this rule never applies —
   flipping the widget mode back needs no CSS change. Browsers without :has()
   simply keep the old 28px. */
.contact-form .cf-turnstile:not(:has(iframe)) { position: absolute; margin-top: 0; }
