/* Site-wide tokens, base, nav, and the motion system. Section styles live in css/<section>.css. */

/* ---------- Fonts ----------
   Peace Sans (headings) and Bricolage Grotesque are SIL OFL fonts, self-hosted.
   Garet (her body font) needs a paid web license from Type Forward, so Plus Jakarta Sans
   stands in. To use Garet once it's licensed, point these four @font-face rules at the Garet
   files and keep the family name in --font-body in step. */
@font-face { font-family: "Peace Sans"; src: url("../assets/fonts/peace-sans.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "Plus Jakarta Sans"; src: url("../assets/fonts/plus-jakarta-sans-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Plus Jakarta Sans"; src: url("../assets/fonts/plus-jakarta-sans-400-italic.woff2") format("woff2"); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: "Plus Jakarta Sans"; src: url("../assets/fonts/plus-jakarta-sans-700.woff2") format("woff2"); font-weight: 700; font-display: swap; }
@font-face { font-family: "Plus Jakarta Sans"; src: url("../assets/fonts/plus-jakarta-sans-800.woff2") format("woff2"); font-weight: 800; font-display: swap; }
@font-face { font-family: "Bricolage Grotesque"; src: url("../assets/fonts/bricolage-grotesque-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }

:root {
  /* Her palette, from the Canva export */
  --plum: #761b44;      /* nav bar */
  --wine: #6c122c;      /* Why Marketing */
  --crimson: #80031b;   /* work experience panels */
  --oxblood: #780a1d;
  --brick: #842b2c;     /* card text */
  --pink: #ffa9d1;
  --turquoise: #5ce1e6; /* RESUME HERE highlight */
  --cream: #f4f3e7;     /* testimonial notes */
  --clip-gray: #adb0b2;
  --forest: #134836;
  --pine: #0f3d2e;      /* skills */
  --olive: #5b5d34;     /* strategy */
  --white: #fff;
  --black: #000;

  --font-display: "Peace Sans", "Arial Black", Impact, sans-serif;
  --font-body: "Plus Jakarta Sans", system-ui, sans-serif;
  --font-bricolage: "Bricolage Grotesque", system-ui, sans-serif;

  /* One point of her 1024pt-wide Canva page at the current width (capped at 1440px),
     and one point of a 390-wide phone layout. Both resolve against <main>'s width. */
  --u: calc(min(100cqi, 1440px) / 1024);
  --m: calc(100cqi / 390);
  --nav-h: clamp(44px, 4.1vw, 56px);

  /* Motion: strong curves for UI, springs for objects that should feel alive */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  /* duration 0.5s, bounce 0.2: settles in 690ms, 1.5% overshoot */
  --spring: linear(0, 0.0147, 0.0533, 0.1089, 0.1755, 0.2486, 0.3245, 0.4003, 0.474, 0.544, 0.6093, 0.6692, 0.7235, 0.7719, 0.8146, 0.8518, 0.8838, 0.9111, 0.9341, 0.9532, 0.9688, 0.9814, 0.9913, 0.999, 1.0049, 1.0091, 1.012, 1.0138, 1.0148, 1.0152, 1.015, 1.0144, 1.0136, 1.0126, 1.0115, 1.0104, 1.0092, 1.0081, 1.007, 1.006, 1.0051, 1.0043, 1.0035, 1.0029, 1.0023, 1.0018, 1.0014, 1.001, 1);
  /* duration 0.55s, bounce 0.3: settles in 860ms, 4.6% overshoot */
  --spring-playful: linear(0, 0.0192, 0.0695, 0.1412, 0.2265, 0.3187, 0.4128, 0.5048, 0.592, 0.6723, 0.7445, 0.808, 0.8626, 0.9086, 0.9465, 0.9768, 1.0004, 1.0181, 1.0307, 1.039, 1.0438, 1.0458, 1.0457, 1.0439, 1.0409, 1.0372, 1.033, 1.0287, 1.0244, 1.0203, 1.0165, 1.013, 1.0099, 1.0073, 1.005, 1.0031, 1.0016, 1.0004, 0.9995, 0.9988, 0.9983, 0.9981, 0.9979, 0.9979, 0.9979, 0.9981, 0.9982, 0.9984, 1);
  /* duration 0.9s, bounce 0.55: a pendulum (notes swinging on a clip), settles in 2.2s */
  --spring-swing: linear(0, 0.0462, 0.1649, 0.3274, 0.5086, 0.6878, 0.8497, 0.9843, 1.0866, 1.1558, 1.1939, 1.2053, 1.1959, 1.1715, 1.1381, 1.1009, 1.0641, 1.0309, 1.0032, 0.9822, 0.968, 0.9602, 0.9578, 0.9598, 0.9648, 0.9716, 0.9793, 0.9868, 0.9937, 0.9993, 1.0037, 1.0066, 1.0082, 1.0087, 1.0083, 1.0072, 1.0058, 1.0043, 1.0027, 1.0013, 1.0001, 0.9993, 0.9987, 0.9983, 0.9982, 0.9983, 0.9985, 0.9988, 1);
}

/* ---------- Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-padding-top: calc(var(--nav-h) + env(safe-area-inset-top, 0px)); text-size-adjust: 100%; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
body {
  margin: 0;
  overflow-x: clip;
  background: var(--black);
  color: var(--white);
  font-family: var(--font-body);
  line-height: 1.2;
  font-synthesis: none; /* Peace Sans has one weight: never fake a bold */
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; }
h1, h2, h3, h4 { font-weight: inherit; }
ul, ol { padding: 0; list-style: none; }
img { display: block; max-width: 100%; }
a { color: inherit; }
main { container-type: inline-size; }
main > section { position: relative; overflow: clip; }

/* Her page is 1024pt wide; .frame holds a section's content at that width, centered past 1440px.
   Backgrounds stay full bleed on the section itself. */
.frame { position: relative; width: min(100%, calc(1024 * var(--u))); margin-inline: auto; }

:focus-visible { outline: 3px solid currentColor; outline-offset: 4px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: 12px; z-index: 100; translate: 0 -200%;
  padding: 10px 16px; background: var(--white); color: var(--black); font-weight: 700; text-decoration: none;
}
.skip-link:focus { translate: none; }

/* ---------- Nav ---------- */
/* Inside a phone app's full-screen viewer the page can run under the status bar: the nav sticks
   below it, and this band paints the status-bar strip in the nav's color. Both are 0 elsewhere. */
.nav { position: sticky; top: env(safe-area-inset-top, 0px); z-index: 50; height: var(--nav-h); background: var(--plum); }
body::before {
  content: ""; position: fixed; inset: 0 0 auto; z-index: 60;
  height: env(safe-area-inset-top, 0px); background: var(--plum);
}
.nav > nav { height: 100%; }
.nav ul {
  display: flex; align-items: stretch; height: 100%;
  gap: clamp(10px, 1.76vw, 25px);
  padding-inline: clamp(12px, 1.17vw, 17px);
  overflow-x: auto; scrollbar-width: none;
}
.nav ul::-webkit-scrollbar { display: none; }
.nav li:first-child { margin-left: auto; } /* right-aligned like hers, still scrollable when it overflows */
.nav a {
  display: flex; align-items: center; height: 100%; white-space: nowrap;
  font-size: clamp(13px, 1.76vw, 25px); text-transform: uppercase; text-decoration: none;
  opacity: .6; transition: opacity 150ms ease;
}
.nav a[aria-current] { opacity: 1; }
.nav a:focus-visible { outline-offset: -3px; opacity: 1; }
@media (hover: hover) and (pointer: fine) { .nav a:hover { opacity: 1; } }

/* Reading progress along the bottom of the nav (scroll timeline; absent where unsupported) */
.nav-progress {
  position: absolute; inset: auto 0 0; height: 2px; background: var(--pink);
  transform-origin: 0 50%; scale: 0 1;
}
@supports (animation-timeline: scroll()) {
  .nav-progress { animation: nav-progress linear both; animation-timeline: scroll(root); }
}
@keyframes nav-progress { to { scale: 1 1; } }

/* ---------- Reveal system ----------
   Put data-reveal="<preset>" on an element; site.js adds .is-in when it scrolls into view (once).
   Presets animate opacity, the individual translate/rotate/scale properties, clip-path (wipe)
   and background-size (draw). They never touch `transform`, so a component's own tilt or
   position stays in `transform`. Never set `transition` on a [data-reveal] element: put hover
   motion on a child. Tunables, set on the element or its CSS: --d (delay, ms; or data-delay),
   --dur, --tilt, --rise, --drop, --float, --swing, --step (word stagger).
   A parent with data-stagger="ms" staggers children that enter together. */
:where(.js [data-reveal]) { --dur: 700ms; --ease: var(--ease-out); }
:where(.js [data-reveal="drop"], .js [data-reveal="pop"], .js [data-reveal="float"]) { --dur: 860ms; --ease: var(--spring-playful); }
:where(.js [data-reveal="pin"], .js [data-reveal="slam"]) { --dur: 690ms; --ease: var(--spring); }
:where(.js [data-reveal="swing"]) { --dur: 2200ms; --ease: var(--spring-swing); }
:where(.js [data-reveal="wipe"]) { --dur: 900ms; }
:where([data-reveal="wipe"]) { --from: inset(0 100% 0 0); }
:where([data-reveal="wipe"][data-from="right"]) { --from: inset(0 0 0 100%); }
:where([data-reveal="wipe"][data-from="up"]) { --from: inset(100% 0 0 0); }
:where([data-reveal="wipe"][data-from="down"]) { --from: inset(0 0 100% 0); }
:where([data-reveal="wipe"][data-from="center"]) { --from: inset(50% 50% 50% 50%); }
/* A wipe needs an inset() at rest too: clip-path can't animate from inset() to none.
   Zero specificity, so a section's own settled clip (rounded corners) wins. */
:where(.js [data-reveal="wipe"]) { clip-path: inset(0); }

.w { display: inline-block; }

.js [data-reveal] {
  transition-property: opacity, translate, rotate, scale, clip-path, background-size;
  transition-duration: calc(var(--dur) * .6), var(--dur), var(--dur), var(--dur), var(--dur), var(--dur);
  transition-timing-function: var(--ease-out), var(--ease), var(--ease), var(--ease), var(--ease-in-out), var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.js [data-reveal="words"] .w, .js [data-reveal="slam"] .w {
  transition-property: opacity, translate, scale;
  transition-duration: calc(var(--dur) * .6), var(--dur), var(--dur);
  transition-timing-function: var(--ease-out), var(--ease), var(--ease);
  transition-delay: calc(var(--d, 0ms) + var(--i) * var(--step, 70ms));
}

@media (prefers-reduced-motion: no-preference) {
  .js [data-reveal="fade"]:not(.is-in) { opacity: 0; }
  .js [data-reveal="rise"]:not(.is-in) { opacity: 0; translate: 0 var(--rise, 2rem); }
  .js [data-reveal="drop"]:not(.is-in) { opacity: 0; translate: 0 var(--drop, -4rem); rotate: var(--tilt, 8deg); }
  .js [data-reveal="pop"]:not(.is-in) { opacity: 0; scale: .86; rotate: var(--tilt, -6deg); }
  .js [data-reveal="float"]:not(.is-in) { opacity: 0; translate: 0 var(--float, 9rem); rotate: var(--tilt, -8deg); }
  .js [data-reveal="pin"]:not(.is-in) { opacity: 0; translate: 0 -1.5rem; rotate: var(--tilt, 3deg); scale: 1.06; }
  .js [data-reveal="swing"]:not(.is-in) { opacity: 0; rotate: var(--swing, -16deg); }
  .js [data-reveal="wipe"]:not(.is-in) { clip-path: var(--from); }
  .js [data-reveal="draw"]:not(.is-in) { background-size: 0% 100%; }
  .js [data-reveal="words"]:not(.is-in) .w { opacity: 0; translate: 0 .5em; }
  .js [data-reveal="slam"]:not(.is-in) .w { opacity: 0; scale: 1.4; }
}

/* Reduced motion: the same page, things simply fade in where they stand. */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal]:not([data-reveal="words"], [data-reveal="slam"]):not(.is-in),
  .js [data-reveal="words"]:not(.is-in) .w,
  .js [data-reveal="slam"]:not(.is-in) .w { opacity: 0; }
  .js [data-reveal], .js [data-reveal] .w { transition: opacity 300ms ease; }
}

/* If site.js never runs, show everything after 4s rather than leave the page blank. */
.js:not(.motion-ready) [data-reveal] { animation: reveal-failsafe 0s 4s forwards; }
@keyframes reveal-failsafe { to { opacity: 1; translate: none; rotate: none; scale: none; clip-path: none; background-size: 100% 100%; } }

/* ---------- Idle loops ----------
   Give every looping animation the .idle class: it pauses while its section is off screen,
   and it is switched off for reduced motion. Start it after the element's entrance. */
main > section:not(.is-visible) .idle { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .idle { animation: none !important; } }

/* ---------- Parallax ----------
   .parallax drifts with scroll on a CSS scroll timeline (browsers without one keep it still).
   Use it on background images only, sized 2 x --shift taller than their frame. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .parallax { animation: parallax linear both; animation-timeline: view(); }
  }
}
@keyframes parallax {
  from { transform: translateY(calc(var(--shift, 48px) * -1)); }
  to { transform: translateY(var(--shift, 48px)); }
}
