/* Shared site chrome — blue/cream/Rubik direction from the Figma home page,
   now applied to every page (not just Home). This is the single source of
   truth for the header/nav, footer, and colour variables. cv.css @imports
   this file for the chrome and adds only the resume-specific rules on top;
   every other page (index/about/work/blog) loads this file directly and
   adds its own content rules below the import line in this same file, so
   there's one place to change the header/nav/footer instead of four.

   Colours pulled directly from the Home page in Figma (2026-08 pass):
     blue  #2667fe (background — bulb.js repoints this between the dark-mode
           and light-mode background colour on click; see js/bulb.js. The
           name is a holdover from when dark mode was blue — light mode is
           now a very light grey, not blue)
     cream #fff3b7 (text/links/headings — the site's one text colour.
           bulb.js also repoints this, between cream for dark mode and
           --ink for light mode, so text stays legible against whichever
           background is active. --muted/--border below are derived from
           --cream via color-mix() so they follow it automatically)
     yellow #f7da50 (bulb — sparing highlight only, unchanged from earlier pass)
     ink   #232323 (light-mode text colour, via bulb.js; also used directly
           for print — see the print block at the bottom) */

:root {
  --blue: #2667fe;
  --cream: #fff3b7;
  --yellow: #f7da50;
  --pink: #cb2787; /* the bulb lottie's own pink — same one cv.css already
    uses for its light-mode accent rule */
  --ink: #232323;
  --muted: color-mix(in srgb, var(--cream) 65%, transparent); /* de-emphasised
    text — derived from --cream rather than a fixed colour, so it tracks
    whichever text colour bulb.js has set (cream in dark mode, ink in light
    mode) instead of going stale/washed-out when the theme switches */
  --paper: #ffffff;
  --border: color-mix(in srgb, var(--cream) 25%, transparent); /* borders/
    dividers — same reasoning as --muted: derived from --cream so it stays
    visible against whichever background colour is active */
  --max-width: 44rem;
  --font-body: "Rubik", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

html { color-scheme: light; }

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--cream);
  background: var(--blue);
  line-height: 1.6;
}

/* Body text and links are both cream now, so links need their own visual
   marker (underline) rather than relying on a colour difference — colour
   alone stopped working once every page's text became one colour. */
a { color: var(--cream); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--cream) 50%, transparent); }
a:hover { text-decoration-color: currentColor; }

/* Top nav — solid blue bar, cream links, centred. Spacing (72px gaps) and
   the home-lottie icon match the Figma "home-light" frame's "top-nav"
   node exactly (js/home-nav.js + js/home-data.js render the lottie). */

/* Cross-document view transitions: smooths the visual cut between full
   page loads on this plain multi-page site (no fetch/router involved —
   still genuine full navigations, just bridged with a native browser
   crossfade instead of a hard cut). Naming .site-header/.bulb-toggle the
   same on every page tells the browser to treat them as one continuous
   element across the navigation rather than an old one vanishing and a
   new one popping in. Chromium-only for now; unsupported browsers just
   ignore this block and navigate exactly as before — and file://'s
   same-origin rules for this feature haven't been verified on this site
   specifically, so treat it as a bonus layer on top of the real fixes
   (self-hosted lottie.min.js, the <head> preload hint, and no longer
   blocking navigation on the click clip), not the fix itself. */
@view-transition {
  navigation: auto;
}

.site-header {
  view-transition-name: site-header;
  background: var(--blue);
  padding: 1.25rem 1.5rem;
  position: relative; /* creates a stacking context for the z-index below
    (position:relative alone doesn't without it) */
  z-index: 15; /* keeps the header's own contents above ordinary page
    content (some of which creates its own implicit stacking context via
    transform, e.g. hover-animated cards), while staying below
    .bulb-toggle (z-index: 20), which is meant to sit on top of the
    header. */
}
.site-header-inner {
  max-width: 64rem;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1rem;
}
/* nav is a 3-column grid — 1fr / auto / 1fr — so Home (.nav-primary)
   sits in the centre column, genuinely centred on the nav's own full
   width rather than just centred relative to whatever's next to it.
   width: 100% is what gives the two 1fr spacer columns something to
   actually distribute — without it, nav would only be as wide as its own
   content needs and the "spacers" would collapse to nothing.

   The CV/Contact dropdown (.nav-menu) that used to sit in the header
   alongside Home is gone — the CV-led homepage carries its own Download
   CV / Email Me buttons now, so a second copy in every page's nav was
   redundant. Animation/Illustration were already hidden from here before
   that (their pages still exist, still linked from the CV homepage's
   illustration/animation teaser) so Home is the only thing left in the
   header on any page. Kept as a grid rather than simplified to a plain
   centred flex row on the offchance something else joins the header
   later — either way, it still centres .nav-primary correctly today. */
.site-header nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  width: 100%;
}
.nav-primary {
  grid-column: 2;
  justify-self: center;
}
.site-header nav ul {
  display: flex;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-header nav a {
  display: inline-block;
  color: var(--cream);
  font-weight: 600;
  text-decoration: none;
  opacity: 0.85;
  /* Springy "pop" on hover (overshoot easing) and a quick squash while
     held down — every nav link gets both, home's icon included. Home's
     icon also plays its own squash-and-back Lottie clip on click
     (js/home-nav.js) as a bonus flourish, but doesn't wait for it to
     finish before following the link — this CSS squash is what gives
     every link (home included) its instant, un-delayed press feedback. */
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.site-header nav a:hover { opacity: 1; text-decoration: none; transform: scale(1.08); }
.site-header nav a[aria-current="page"] { opacity: 1; border-bottom: 2px solid var(--cream); padding-bottom: 2px; }
.site-header nav a:active {
  transform: scale(0.92);
  transition: transform 0.1s ease;
}
/* Home button — the "head" node in Figma's top-nav (60.883 x 54.176px).
   Its own link is meant to skip the aria-current underline treatment
   above, since being "current" doesn't apply to a single icon the same
   way — but .site-header nav a[aria-current="page"] and this rule are
   equally specific, so without explicitly cancelling border-bottom/
   padding-bottom here too, the underline was still showing up under the
   icon on the Home page itself. */
.site-header nav .nav-home a {
  display: block;
  opacity: 1;
  border-bottom: 0;
  padding-bottom: 0;
}
.nav-home-lottie {
  display: block;
  width: 152px; /* was 183px (3x) — pulled back slightly, ~2.5x the
    original 61px */
  height: 135px; /* was 162px (3x) — ~2.5x the original 54px */
}
.nav-home-lottie svg { display: block; }

.site-footer {
  color: var(--muted);
  font-weight: 700; /* same bold body font as .illustration-hero-intro/
    -meta and Home's .hero-copy */
  font-size: 0.85rem;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.5rem;
  text-align: center;
}

/* Clickable bulb (js/bulb.js) — present on every page. Anchored to the
   top-left corner of the PAGE (not the viewport) so it reads as hanging
   down from the top edge of the header and scrolls away together with it,
   the same as the home icon and CV/Email Me nav links do — it used to be
   position: fixed, which pinned it to the viewport and kept it floating
   on screen by itself once you'd scrolled past the header, decoupled from
   everything else up there. position: absolute with no positioned
   ancestor in between is positioned against the page itself (the initial
   containing block), which is exactly what makes it scroll normally
   instead of staying fixed — same top/left coordinates, so it sits in the
   same spot on first paint, it just isn't pinned to the viewport as you
   scroll. Sized down a lot since the source 1080x1080 frame has a lot of
   empty space around the actual bulb/cord artwork. z-index keeps it above
   the header bar it overlaps. */
.bulb-toggle {
  position: absolute;
  top: 0;
  left: 2.5rem; /* was 1.5rem — pulled further in */
  width: 140px;
  height: 140px;
  z-index: 20;
  cursor: pointer;
  view-transition-name: bulb-toggle;
  /* Same springy hover "pop" as the header's home lottie/link (.site-header
     nav a below) — identical transform, identical overshoot easing, and
     the same quick squash on click. It was using the default centre
     transform-origin (50% 50%) at first, which looked flat rather than
     springy: this element sits pinned at top: 0, so scaling from its
     centre pushes roughly half the growth above y: 0 — off the top edge
     of the viewport — where it's simply not visible, cutting the pop off
     mid-motion. Anchoring the origin to the top instead (matching "hangs
     down from the top edge" above) means all of the growth happens
     downward into visible space, so the full bounce actually shows.
     will-change hints the browser to give this its own compositing layer,
     so the hover transition stays smooth independent of the bulb's own
     continuously-animating idle Lottie loop repainting underneath it. */
  transform-origin: 50% 0;
  will-change: transform;
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.bulb-toggle:hover { transform: scale(1.08); }
.bulb-toggle:active { transform: scale(0.92); transition: transform 0.1s ease; }
.bulb-toggle svg { display: block; }

@media (max-width: 640px) {
  .bulb-toggle {
    left: 1.75rem; /* was 1rem — pulled further in, same direction as desktop */
    width: 100px;
    height: 100px;
  }
}

/* Light/dark switches (bulb.js repoints --blue between the theme colours on
   click, and on load from localStorage/system preference) are now instant,
   no fade — was a 0.4s background-color transition on body.bg-transitions-
   ready here. The class itself stays (still gates the bulb's own lottie
   turn-on/turn-off animation in bulb.js, unrelated to this), just nothing
   in CSS keys off it for a colour transition any more. */

/* ---- Generic content pages (about / work / blog) ---- */

main {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 3rem 1.5rem 4rem;
}

.page-header { margin-bottom: 2rem; }
.page-title {
  margin: 0 0 0.5rem;
  font-size: 1.75rem;
  font-weight: 700;
  color: var(--cream);
}
.page-intro { color: var(--muted); margin: 0; }

main > p { margin: 0 0 1rem; }

.post-meta {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0 0 1.5rem;
}
.post-meta .p-category { text-transform: capitalize; }

.e-content > p { margin: 0 0 1rem; }
.e-content > p:last-child { margin-bottom: 0; }

.post-footer {
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.9rem;
}

/* Work / blog index listings */
.post-list, .work-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.post-list li, .work-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem 1rem;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--border);
}
.post-list li:first-child, .work-list li:first-child { padding-top: 0; }
.post-list a, .work-list a { font-weight: 600; color: var(--cream); }
.post-list a:hover, .work-list a:hover { opacity: 0.75; }
.post-list time, .work-role {
  color: var(--muted);
  font-size: 0.9rem;
  white-space: nowrap;
}

/* Individual work item */
.work-hero {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 6px;
  margin: 0 0 1.5rem;
  border: 1px solid var(--border);
}
.work-video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 6px;
  margin: 0 0 1.5rem;
  background: #000;
}
.video-pending {
  color: var(--muted);
  font-style: italic;
  margin: -0.75rem 0 1.5rem;
}

/* Landing page hero + link cards (index.html) */
.hero { text-align: center; margin-bottom: 2.5rem; }
.hero h1 {
  margin: 0 0 0.35rem;
  font-size: 2rem;
  font-weight: 700;
  color: var(--cream);
}
.hero-title { margin: 0 0 1rem; color: var(--muted); font-weight: 500; }
.hero-intro { max-width: 36rem; margin: 0 auto; }

.link-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1rem;
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
}
.link-cards a {
  display: block;
  padding: 1.1rem 1rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--cream);
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}
.link-cards a:hover {
  border-color: var(--cream);
  opacity: 0.85;
}
.link-cards .link-card-glyph {
  display: block;
  font-size: 1.4rem;
  margin-bottom: 0.4rem;
}

/* Illustration detail page (illustration/<collection>.html) — the page's
   own page-header (h1 + intro line, styled generically above) covers the
   title/copy for the whole connected series. Two non-hero pieces sit side
   by side per row; a hero image (add class illustration-piece--hero to its
   <li>, when a collection has one) spans the full row on its own, always
   first. Reuses the same 44rem reading width (doubled by illustration.css)
   as every other content page rather than going full-bleed, so it lines up
   with the page-header above it. */
.illustration-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.illustration-piece--hero {
  grid-column: 1 / -1;
}
.illustration-piece {
  position: relative;
  cursor: pointer; /* clickable — js/illustration-lightbox.js opens it centred
    over a dimmed backdrop, trialled on pinnacle-valley.html first */
  will-change: transform; /* js/illustration-magnetic.js drives this
    element's transform continuously via requestAnimationFrame while
    hovered, same as .illustration-card-link above — see that file's own
    comment for why there's no CSS transition here to race against it */
}
.illustration-piece img:not(.illustration-badge) {
  display: block;
  width: 100%;
  height: auto;
  /* A flat, fixed radius - same 24px on every card regardless of its size
     (hero or regular piece, any viewport). These images used to ship with
     their corners already cut into the artwork's own alpha channel; that
     baked-in masking has been flattened out of every illustration piece and
     hero image site-wide, so this rule is the only thing rounding any of
     them now. */
  border-radius: 24px;
}
/* Photoshop badge — the SVG already carries its own dark rounded-square
   background, so it just needs positioning: top-left corner, inset from
   the edge rather than flush against it. :not() above stops the generic
   width:100% image rule from stretching this to fill the container (it
   otherwise wins on specificity since it's also scoped under
   .illustration-piece), which is what made it render oversized. */
.illustration-badge {
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
  width: 34px;
  height: 34px;
  display: block;
}

@media (max-width: 640px) {
  .illustration-list { grid-template-columns: 1fr; }
}

/* Previous/next project links at the bottom of each detail page
   (work/illustration/<collection>.html) — cycles through the same order
   the cards appear in on the Illustration index, wrapping around at each
   end (the last project's "next" is the first, and vice versa). A plain
   hairline top border and generous top spacing echo .site-footer just
   above it, rather than looking like another card. */
.project-nav {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 2rem;
  margin-top: 6rem;
  padding-top: 3rem;
}
.project-nav a {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  color: var(--cream);
  text-decoration: none;
  /* Same springy hover pop as the top nav links, so these read as part of
     the same interactive language rather than a one-off. */
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.project-nav a:hover { transform: scale(1.03); }
.project-nav a:active {
  transform: scale(0.97);
  transition: transform 0.1s ease;
}
.project-nav-next {
  text-align: right;
  align-items: flex-end;
}
.project-nav-label {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.project-nav-title {
  font-size: 1.15rem;
  font-weight: 700;
}

@media (max-width: 640px) {
  .project-nav { gap: 1rem; }
  .project-nav-title { font-size: 1rem; }
}

/* Illustration page header — a two-column row: title on the left,
   intro on the right, both starting flush with the top of the row.
   Sits above the piece grid or card grid; shared by Animation, the
   Illustration index, and every detail page
   (work/illustration/<collection>.html), so all of them use identical
   header styling. Title/intro copy is edited directly in each page's
   HTML — .illustration-hero-intro is deliberately plain (regular weight,
   sentence case, same line-height as body text elsewhere on the site),
   not a styled label, so whatever's typed there reads as normal body
   copy.

   Back to each box's original, unconstrained width (title capped at
   40rem same as before either of the last two changes; intro at 100%
   again) — the earlier three-quarters-width, centred version is gone.
   The left/right positioning here isn't a text-align trick: the title
   sits at the row's start because it's a plain flex item (default
   flex: 0 1 auto — it doesn't grow), and the intro is flex: 1 1 20rem,
   so it grows to fill whatever's left in the row, its own right edge
   landing flush with the page's right edge without ever centring or
   right-aligning the paragraph text itself inside it — that stays
   left-aligned, ragged-right, same as any other body copy on the site.
   align-items: flex-start is what keeps the two top-aligned with each
   other rather than one drifting down to vertically centre against the
   other's height. flex-wrap: wrap is the narrow-viewport fallback: once
   there's no longer room for both side by side, intro drops to its own
   line below the title instead of squeezing unreadably thin. */
.illustration-hero {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 2rem;
  margin-bottom: 6rem; /* doubled from 3rem — felt cramped against the
    piece/card grid below it */
}
/* Detail pages wrap the title and the Role/Copyright line together in
   .illustration-hero-left, so meta sits directly under the title — in the
   same left column — rather than spanning the full width below both the
   title and the (often taller, wrapping) intro on the right. Animation
   and the Illustration index have no meta line, so their .illustration-
   hero-title renders unwrapped as a direct flex child instead — its own
   max-width below still bounds it the same way either way. */
.illustration-hero-left {
  max-width: 40rem;
}
.illustration-hero-title {
  margin: 0;
  max-width: 40rem;
  font-size: clamp(2rem, 5vw, 2.73rem); /* max nudged to match
    slimjimstudios.com/work/pat's own H2 (measured ~43.65px/2.73rem at a
    1280px viewport) almost exactly — it was already close at 2.75rem, this
    just tightens it */
  line-height: 1.2; /* their H2's own line-height/font-size ratio measures
    ~1.206 — 1.2 is the same, cleanly */
  font-weight: 700;
  color: var(--cream);
  text-align: left;
}
.illustration-hero-intro {
  flex: 1 1 20rem;
  margin: 0 0 0 auto; /* the auto left margin is what actually pushes the
    box itself to sit flush against the row's right edge — any leftover
    row space once the box has grown to its 50% cap collapses into this
    margin rather than justify-content, so the box is right-aligned while
    the text inside it (text-align: left, below) stays ragged-right same
    as always */
  max-width: 50%;
  font-weight: 700; /* matches slimjimstudios.com/work/pat's own body
    paragraph, which is bold rather than the regular weight used
    everywhere else on this site */
  font-size: 17.5px; /* matches their body paragraph (measured ~17.5px at
    a 1280px viewport) — superseding the earlier match to Home's
    .hero-copy (20px/20px) */
  line-height: 1.7; /* their paragraph's own line-height/font-size ratio
    measures ~1.7 almost exactly, so this is their real authored value,
    not just a close approximation */
  color: var(--cream); /* matches .illustration-hero-title's own colour
    exactly (rather than the softer --muted derived from it), so this text
    is never a separate shade from the heading above it in either theme -
    bulb.js repoints --cream itself between dark-mode cream and light-mode
    ink, so this still reads as near-black in light mode without needing
    a fixed colour of its own. Shared by Animation, the Illustration
    index, and every detail page (see the comment above .illustration-
    hero), so the change already applies everywhere it's used. */
  text-align: left;
}
/* Sits inside .illustration-hero-left, directly under the title — a
   plain block under a block, not a flex item, so it just needs its own
   top margin and an explicit left alignment (the row above it can put
   the title flush left while this stays independently anchored left
   too, regardless of whatever alignment .illustration-hero-intro next
   to it is doing). */
.illustration-hero-meta {
  margin: 1rem 0 0;
  text-align: left;
  color: var(--muted);
  font-weight: 700; /* matches .illustration-hero-intro's bold body font */
  font-size: 17.5px;
  line-height: 1.7;
}
.illustration-hero-meta strong {
  color: var(--cream);
  font-weight: 700;
}
/* Copyright stacks directly under Role now, rather than sitting beside it
   on a bullet-separated line — same block-per-span treatment at every
   width, so the old sub-640px-only override (and its bullet, no longer
   shown anywhere) is gone too. */
.illustration-hero-meta span {
  display: block;
}

/* "Created By a Human, Not By AI" badge (notbyai.fyi) — lives in
   .site-footer (illustration.html only), wrapped in a link to the project
   itself. Base rule has no margin of its own; .no-ai-badge--footer below
   sets the placement actually used. width: auto keeps the SVG's native
   ~3.14:1 aspect ratio. Only one of the two is ever visible at a time (see
   the light/dark rule right after), so there's never a layout jump between
   them, just a swap. */
.no-ai-badge {
  display: block;
  width: auto;
}
:root[data-theme="dark"] .no-ai-badge--light,
:root[data-theme="light"] .no-ai-badge--dark {
  display: none;
}
/* Sized down from the 2.75rem this was first tried at directly under the
   page title — that read too heavy stacked between the heading and the
   intro paragraph, competing with both, so it moved down here instead.
   notbyai.fyi's own stated minimum is 42px/1x; 1.75rem (28px) is under
   that at the default 16px root, but this is a footer credit rather than
   the primary usage the minimum is aimed at (a work's main attribution),
   and it's still clearly legible at footer scale. */
.no-ai-badge--footer {
  height: 1.75rem;
  margin: 0 auto 0.75rem;
}
.no-ai-badge-link {
  display: block;
  width: fit-content;
  margin: 0 auto;
  /* Same hover/active treatment as a.fig-contact-item and .fig-cta —
     opacity dim plus the same spring-ish scale, so this reads as part of
     the same interactive-element language as everything else on the site
     rather than a plain unstyled image link. */
  transition: opacity 0.2s ease, transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.no-ai-badge-link:hover { opacity: 0.85; transform: scale(1.05); }
.no-ai-badge-link:active { opacity: 1; transform: scale(0.95); transition-duration: 0.1s; }
/* .site-footer's own text (below) stays centred as plain inline content;
   stacking it as a column here just gives the badge its own centred row
   above the two <p> lines, rather than trying to force an <img> into the
   middle of running text. illustration.html is the only page with this
   markup, so this only takes effect there despite being a shared class. */
.site-footer:has(.no-ai-badge-link) {
  display: flex;
  flex-direction: column;
  align-items: center;
}

@media (max-width: 640px) {
  .illustration-hero-title { font-size: 1.75rem; }
  .illustration-hero-intro {
    font-size: 14px; /* same 80% mobile scale-down home.css's own 640px
      breakpoint used to give .hero-copy (20->16), just applied to the new
      17.5px base instead */
    /* Desktop's flex:1 1 20rem + max-width:50% + margin-left:auto is what
       sits this beside .illustration-hero-left and flush against the
       row's right edge on a wide screen. Once the row wraps here (title
       no longer fits next to it), those same rules kept it capped at half
       width and pushed right on its own line too — squeezed into the
       right half of the screen with the left half sitting empty, instead
       of reading as body copy under the title. flex-basis/max-width:100%
       plus a plain top margin (replacing the auto side margin, which had
       nothing left to center/push against at full width) makes it a
       normal full-width block stacked directly under the header. */
    flex-basis: 100%;
    max-width: 100%;
    margin: 1.5rem 0 0;
  }
}

/* Email me button icon: a small envelope wiggle, now and again rather than
   constantly — mirrors the wave-emoji animation's character (home-draft.css:
   a few small rotations with a touch of squash/stretch for weight) but on
   a mostly-still cycle, since a static SVG wiggling nonstop reads as a
   glitch rather than a gesture. .fig-cta--primary is only ever the Email me
   button (index.html and illustration.html, both the in-page and floating
   CTAs), so this is scoped here in site.css once rather than duplicated
   per page. transform-origin stays centred (an envelope has no "wrist" to
   pivot from, unlike the waving hand). */
.fig-cta--primary svg {
  display: inline-block;
  transform-origin: 50% 50%;
  animation: email-wiggle 7s ease-in-out infinite;
}
@keyframes email-wiggle {
  0%, 84%, 100% { transform: rotate(0deg) scale(1, 1); }
  86% { transform: rotate(-12deg) scale(0.97, 1.03); }
  89% { transform: rotate(10deg) scale(1.03, 0.97); }
  92% { transform: rotate(-7deg) scale(0.98, 1.02); }
  95% { transform: rotate(5deg) scale(1.02, 0.98); }
  98% { transform: rotate(0deg) scale(1, 1); }
}
@media (prefers-reduced-motion: reduce) {
  .fig-cta--primary svg { animation: none; }
}

/* Printed pages (mainly the CV) should stay legible on paper regardless of
   the on-screen blue/cream theme — override the custom properties themselves
   rather than patching every individual rule, so print always renders dark
   text on white no matter what --blue/--cream currently point at. */
@media print {
  :root {
    /* !important is load-bearing here: index.html's inline flash-prevention
       script (and js/bulb.js) set --blue/--cream directly on <html>'s own
       style attribute, and an inline style always beats a plain stylesheet
       rule for the same property — that's exactly why dark mode's --cream
       (#fff3b7, a pale/dark yellow) was still winning and showing up as
       yellow-tinted text in printed/saved PDFs despite this whole block.
       An !important stylesheet rule is the one thing that outranks an
       inline style, so these force it back to plain dark-on-white. */
    --blue: #1a1a1a !important;
    --cream: #1a1a1a !important;
    --muted: #555 !important;
    --border: #ddd !important;
    --paper: #ffffff !important;
    /* --accent-rule (cv.css) swaps pink/yellow by data-theme, which this
       block doesn't otherwise touch — printing from a dark-mode session
       was saving the PDF with the yellow accent instead. A print-out is
       effectively always "light mode" (dark text on white paper), so this
       forces the same pink (#cb2787) light mode itself uses, regardless of
       which theme was active on screen when printed. cv.css's own print
       block already forced this for cv.html's .resume-section > h2 etc.
       individually; doing it at the variable's source here covers every
       consumer (index.html's .fig-section-heading included) in one place. */
    --accent-rule: #cb2787 !important;
  }
  /* Everything on the CV is sized in rem, so scaling the root font-size
     changes the whole page proportionally, text, gaps and all, rather
     than just shrinking or stretching individual rules. This used to be
     10.84px, tuned down over several rounds to fit a much longer version
     of the CV into 2 printed pages. After trimming client detail for
     confidentiality and consolidating Apadmi into one flat bullet list,
     the same real content ran about a third of page 2, so this was tuned
     back up to 15.5px instead (with section gaps in home-cv-draft.css's
     print block tightened at the same time, so the larger text didn't
     balloon the rem-based gaps into visible "chunks" of dead space).
     Content was then added back in: an intro line above both the Apadmi
     and Arctic Shores bullet lists, plus several new/expanded bullets
     (SaaS onboarding, bespoke rebrands, etc.), which pushed 15.5px over
     to 3 pages, then tuned down to 14.2px to restore 2 pages. The
     downloaded/printed PDF still wasn't landing right at 14.2px, so
     dropped by 2 more to 12.2px for a firmer safety margin against
     Safari rendering the text/wrapping slightly differently from this
     sandbox's Chromium-based testing, then nudged back up by 1 to
     13.2px since 12.2px read too small and left page 2 mostly empty.
     13.2px still held at 2 pages in this sandbox's Chromium testing
     (cutoff here was 14.6px), but the actual downloaded PDF on the live
     site (Safari, real device) still came out at 3 pages at 13.2px, so
     the Chromium-vs-Safari gap is bigger than the earlier safety margin
     assumed. Dropped by half a point to 12.7px on that real-world
     result rather than sandbox testing alone. */
  html { font-size: 12.2px; }
  .site-header, .site-footer, .no-print, .bulb-toggle { display: none; }
  main { padding: 0; max-width: none; }
  /* 1.2 rather than the on-screen 1.6 — resume convention runs close to
     single spacing (~1.0-1.15); 1.2 stays a touch more breathable than
     that while being clearly tighter than reading-comfort spacing. */
  body { background: #fff; color: #1a1a1a; line-height: 1.2; }
  a { color: inherit; text-decoration: none; }
  /* Rubik (the site's webfont, loaded from Google Fonts) forms standard
     ligatures like "ff"/"fi" — fine on screen, but some browsers' PDF
     export doesn't map the ligature glyph back to its plain letters in the
     PDF's text layer, so copying "handoff" or "Effects" out of a saved PDF
     can come back as "hando!"/"E!ects" with the ligature silently eaten.
     ATS/recruiter systems read that same text layer, so this is worth
     avoiding outright rather than hoping it renders fine: switching
     ligatures off for print means each letter stays its own character in
     the exported PDF, whichever browser generated it. */
  * { font-variant-ligatures: none; }
}

/* More generous top/side page margins than the browser default; bottom
   stays tighter since that's where the CV already has spare room. */
@page { margin: 2.5cm 2.5cm 1.5cm; }
