/* ==========================================================================
   Jenny K Tran — portfolio
   One stylesheet for every page. Tokens first, then base, layout,
   components, and page-specific pieces. Spacing is on a 4px grid and type
   sizes come only from the ramp below.
   ========================================================================== */

/* ---------- Fonts (self-hosted) ---------- */
@font-face {
  font-family: "Satoshi";
  src: url("../fonts/satoshi-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Satoshi";
  src: url("../fonts/satoshi-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Satoshi";
  src: url("../fonts/satoshi-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Cormorant";
  src: url("../fonts/cormorant-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ---------- Tokens ---------- */
:root {
  /* Color */
  --ink: #2a2930;            /* all body and heading text */
  --ink-muted: #515067;      /* meta, captions, secondary text, active nav pill */
  --ink-soft: #393848;       /* footer text */
  --accent: #c54286;         /* the one accent: role highlight + text links */
  --surface: #ffffff;
  --surface-alt: #f7f7f5;    /* intro bands, panels, footer */
  --line: #e4e3df;           /* hairlines and card borders */
  --line-strong: #d6d3d2;    /* footer top border, follower cards */
  --line-card: #e3e3e1;      /* project card border (measured from the original) */
  --line-card-hover: #ccccc4; /* project card border on hover */
  --highlight: rgba(255, 236, 110, 0.28); /* key-sentence marker */

  /* Project colors (cover panels) */
  --sage: #a1c59d;
  --periwinkle: #7b9ecd;
  --butter: #ffde9f;

  /* Type */
  --font-sans: "Satoshi", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-serif: "Cormorant", "Cormorant Garamond", Georgia, "Times New Roman", serif;

  --fs-caption: 14px;  /* tags, meta */
  --fs-small: 16px;    /* captions, table cells, nav */
  --fs-body: 18px;     /* paragraphs */
  --fs-title: 24px;    /* subsection headings, lead paragraphs, card titles */
  --fs-h2: 32px;       /* section headings (serif) */
  --fs-h1: 40px;       /* page titles (serif) */
  --fs-hero: 48px;     /* home hero (serif) */
  --fs-hero-intro: var(--fs-title); /* home hero paragraph */

  /* Space (4px grid) */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  --s-9: 96px;

  /* Rhythm */
  --gap-inline: var(--s-2);    /* within a cluster: title to its text */
  --gap-para: var(--s-5);      /* paragraph to paragraph */
  --gap-block: var(--s-7);     /* figure, card group, table */
  --gap-topic: var(--s-8);     /* subsection to subsection */
  --gap-section: var(--s-9);   /* Overview / Process / Takeaways bands */

  /* Shape */
  --r-sm: 12px;   /* bordered cards */
  --r-stat: 18px; /* About follower cards (measured from the original) */
  --r-md: 16px;   /* media and panels */
  --r-lg: 24px;   /* project cards, experience panels */

  /* Widths */
  --w-narrow: 640px;
  --w-prose: 760px;
  --w-hero: 880px;
  --w-wide: 1040px;
  --w-full: 1360px;
  --gutter: var(--s-6);

  --header-h: 68px;
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-spring: cubic-bezier(0.25, 1.1, 0.5, 1); /* quick settle with a hint of overshoot */
  --ease-pill: cubic-bezier(0.3, 1.2, 0.45, 1);   /* nav pill glide, lands with a soft overshoot */
  --ease-swipe: cubic-bezier(0.4, 0, 0.2, 1);    /* link underline sweep: quick start, soft landing */
  /* Project card hover spring, fitted to the original site's samples
     (damping 0.8, looks settled at ~330ms, overshoots 1.4%). */
  --ease-hover: cubic-bezier(0.4, 1, 0.2, 1.05); /* fallback for browsers without linear() */
  --dur-hover: 655ms;
}
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    --ease-hover: linear(0, 0.052, 0.171, 0.317, 0.465, 0.599, 0.712, 0.804, 0.874,
      0.926, 0.962, 0.986, 1.001, 1.009, 1.013, 1.014, 1.013, 1.012, 1.009, 1.007,
      1.006, 1.004, 1.003, 1.002, 1);
  }
}

/* Phones (under 810px, the original site's breakpoint). Like the original,
   only the home hero shrinks; every other size stays as on desktop. */
@media (max-width: 809px) {
  :root {
    --fs-hero: 30px;
    --fs-hero-intro: 20px;
    --gap-topic: var(--s-7);
    --gap-section: var(--s-8);
    --gutter: var(--s-5);
  }
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--s-4));
}

body {
  margin: 0;
  overflow-x: clip; /* page slides never cause a sideways scrollbar (clip keeps sticky working) */
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

img,
video,
iframe {
  display: block;
  max-width: 100%;
}

img {
  height: auto;
}

h1,
h2,
h3,
h4,
p,
ul,
ol,
dl,
dd,
figure {
  margin: 0;
}

a {
  color: inherit;
}

strong,
b {
  font-weight: 700;
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  top: var(--s-2);
  left: var(--s-2);
  z-index: 100;
  padding: var(--s-2) var(--s-4);
  border-radius: 999px;
  background: var(--ink);
  color: var(--surface);
  font-size: var(--fs-small);
  text-decoration: none;
  transform: translateY(-200%);
}
.skip-link:focus {
  transform: none;
}

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

/* ---------- Header ---------- */
.site-header {
  --header-fade: 0; /* 0 at the top of the page, 1 after ~240px of scroll (set in site.js) */
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--header-h);
}
/* Frosted glass layer, no bottom line. --header-fade (0 to 1) is driven by
   scroll position in site.js, eased and smoothed, so the glass slowly forms
   as you scroll: tint, blur, and saturation all build together.
   (No mask here: Safari drops backdrop-filter on masked elements.) */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(255, 255, 255, 0.7);
  -webkit-backdrop-filter: saturate(calc(100% + 80% * var(--header-fade))) blur(calc(20px * var(--header-fade)));
  backdrop-filter: saturate(calc(100% + 80% * var(--header-fade))) blur(calc(20px * var(--header-fade)));
  opacity: var(--header-fade);
  pointer-events: none;
}
/* Home: header floats over the hero video */
.page-home .site-header {
  position: fixed;
  inset: 0 0 auto;
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  height: 100%;
  padding-inline: 40px;
}

.logo {
  font-family: var(--font-serif);
  font-size: var(--fs-title);
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
}
.logo__short {
  display: none;
}

.nav {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  list-style: none;
  padding: 0;
}
.nav a {
  display: inline-flex;
  align-items: center;
  padding: 8px var(--s-4);
  border-radius: 24px;
  font-size: var(--fs-small);
  line-height: 1.2;
  text-decoration: none;
  box-shadow: inset 0 0 0 1px transparent;
  transition: color 0.3s ease;
}
.nav a:hover {
  color: var(--accent);
}
.nav a[aria-current="page"] {
  font-weight: 500;
  box-shadow: inset 0 0 0 1px var(--ink-muted);
}
.nav a[aria-current="page"]:hover {
  color: inherit; /* the current page's pill doesn't react */
}

/* Sliding pill: with JS, one shared outline glides between nav items on
   click (site.js). Without JS the current link keeps its own outline. */
.nav-wrap {
  position: relative;
}
.nav-wrap.has-pill .nav a[aria-current="page"] {
  box-shadow: none;
}
.nav__pill {
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 24px;
  box-shadow: inset 0 0 0 1px var(--ink-muted);
  pointer-events: none;
}
.nav__pill.is-moving {
  transition:
    transform 0.3s var(--ease-pill),
    width 0.3s var(--ease-pill);
}

@media (max-width: 809px) {
  .site-header__inner {
    padding-inline: var(--gutter);
  }
  .logo__full {
    display: none;
  }
  .logo__short {
    display: inline;
  }
}
/* Very small phones (320px): the full-size nav is ~26px too wide, so the
   links get tighter side padding. 360px and up match the original exactly. */
@media (max-width: 359px) {
  .nav a {
    padding-inline: var(--s-2);
  }
}

/* ---------- Layout helpers ---------- */
.container {
  width: 100%;
  max-width: calc(var(--w-prose) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow {
  max-width: calc(var(--w-narrow) + var(--gutter) * 2);
}
.container--hero {
  max-width: calc(var(--w-hero) + var(--gutter) * 2);
}
.container--wide {
  max-width: calc(var(--w-wide) + var(--gutter) * 2);
}
.container--full {
  max-width: calc(var(--w-full) + var(--gutter) * 2);
}

.band {
  padding-block: var(--gap-section);
  background: var(--surface);
}
/* Pages that open on the off-white band: it shows through the header until you scroll */
.page-case,
.page-about {
  background: var(--surface-alt);
}
.band--alt {
  background: var(--surface-alt);
}

/* ---------- Typography components ---------- */
.display {
  font-family: var(--font-serif);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.005em;
  text-wrap: balance;
}
.page-title {
  font-size: var(--fs-h1);
}
.section-title {
  font-size: var(--fs-h2);
  text-align: center;
}

.lead {
  font-size: var(--fs-title);
  line-height: 1.5;
  text-wrap: pretty;
}

.prose > * + * {
  margin-top: var(--gap-para);
}
.prose p,
.prose li {
  text-wrap: pretty;
}
.prose h3,
.topic-title {
  font-size: var(--fs-title);
  font-weight: 700;
  line-height: 1.25;
}
.prose h3 {
  margin-top: var(--gap-topic);
}
.prose h3 + * {
  margin-top: var(--s-4);
}
.prose ul,
.prose ol {
  padding-left: var(--s-5);
}
.prose li + li {
  margin-top: var(--s-2);
}
.prose > .figure,
.prose > .card-grid,
.prose > .panel,
.prose > .callout,
.prose > .split {
  margin-top: var(--gap-block);
}
.prose > .figure + *,
.prose > .card-grid + *,
.prose > .panel + *,
.prose > .callout + *,
.prose > .split + * {
  margin-top: var(--gap-block);
}
.prose > h3:first-child {
  margin-top: 0;
}
.prose > .split + p {
  margin-top: var(--gap-para);
}
/* Small bold lead-in heading inside prose (e.g. each challenge) */
.prose > h4 {
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 1.4;
}
.prose > h4 + p {
  margin-top: var(--s-1);
}

/* Key-sentence marker. Drawn as a background image so it can sweep in.
   box-decoration-break stays "slice" (the default) on purpose: the stroke
   then runs across the lines in order, like a real highlighter. */
mark {
  color: inherit;
  font-weight: 500;
  padding-block: 2px;
  background-color: transparent;
  background-image: linear-gradient(var(--highlight), var(--highlight));
  background-repeat: no-repeat;
  background-size: 100% 100%;
}
/* Case studies: draw each highlight the first time it scrolls into view (site.js) */
.js .page-case mark {
  background-size: 0% 100%;
  transition: background-size var(--mark-duration, 1000ms) cubic-bezier(0.33, 0.1, 0.25, 1);
}
.js .page-case mark.is-drawn {
  background-size: 100% 100%;
}
.js .page-case mark.is-instant {
  transition: none;
}

/* Text links: on hover the underline swipes in from the left; on leave it
   carries on out to the right. The line is a background so it can grow;
   the position flips instantly while its width animates. */
.text-link {
  --underline: max(1px, 0.075em);
  padding-bottom: 2px;
  background: linear-gradient(currentColor, currentColor) no-repeat right bottom / 0% var(--underline);
  color: var(--accent);
  font-weight: 500;
  text-decoration: none;
  transition: background-size 0.4s var(--ease-swipe);
}
.text-link:hover,
.text-link:focus-visible {
  background-position: left bottom;
  background-size: 100% var(--underline);
}

.caption {
  color: var(--ink-muted);
  font-size: var(--fs-small);
  line-height: 1.4;
  text-align: center;
  text-wrap: balance;
}

.result {
  font-weight: 700;
}

/* Section heading with hairline (Overview / Process / Takeaways) */
.section-head {
  position: relative;
  max-width: var(--w-prose);
  margin: 0 auto var(--s-7);
  padding-bottom: var(--s-5);
}
.section-head::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 1px;
  background: var(--line);
}

/* ---------- Media ---------- */
.figure img,
.figure video,
.figure iframe {
  width: 100%;
}
.figure .caption {
  margin-top: var(--s-4);
}
.figure--panel {
  padding: var(--s-6);
  border-radius: var(--r-md);
  background: var(--surface-alt);
}
.figure--panel img {
  margin-inline: auto;
}
.figure--bleed {
  /* Wider than the prose column, centered on it */
  width: min(var(--bleed, var(--w-wide)), 100vw - var(--gutter) * 2);
  margin-left: 50%;
  transform: translateX(-50%);
}
/* A figure on a full-width grey band (Cross-Sell market research) */
.figure--band {
  width: 100vw;
  margin-left: 50%;
  padding: var(--s-7) var(--gutter);
  background: var(--surface-alt);
  transform: translateX(-50%);
}
.figure--band img {
  max-width: var(--w-full);
  margin-inline: auto;
}
/* A bare video sized to the recording, no box (Cross-Sell journey) */
.embed.embed--bare {
  border-radius: 0;
  background: transparent;
}
.figure--rounded img {
  border-radius: var(--r-md);
}

.embed {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--surface-alt);
}
.embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
/* Phone prototypes: the recordings include a light box and outline around the
   phone. The original clips each one to a phone-shaped window, so we do too.
   The iframe is sized to the video's exact shape (so Vimeo's fit mode doesn't
   matter) and offset so only the window shows. Values are measured from the
   original: --win is the window's aspect, --vid-w/--vid-h the video's size
   and --vid-x/--vid-y its offset, all relative to the window. */
.embed--phone {
  aspect-ratio: var(--win);
  border-radius: var(--win-r);
  background: transparent;
}
.embed--phone iframe {
  inset: auto;
  left: var(--vid-x);
  top: var(--vid-y);
  width: var(--vid-w);
  height: var(--vid-h);
  max-width: none;
}
/* Savings Calculator: video 216:426. The recording zooms in on the phone as
   it plays, so it keeps its box and only the edges are trimmed (the
   original's crop: ~2% off each side, a sliver top and bottom), which hides
   the outline. Corners stay the site's usual radius. */
.embed--phone-sc {
  --win: 277 / 566;
  --win-r: var(--r-md);
  --vid-x: -2.44%;
  --vid-y: -0.71%;
  --vid-w: 104.89%;
  --vid-h: 101.24%;
}
/* Cross-Sell: video 232:426 in a 226x463 window, 32px corners */
.embed--phone-xs {
  --win: 226 / 463;
  --win-r: 14.16% / 6.91%;
  --vid-x: -9.73%;
  --vid-y: -3.6%;
  --vid-w: 119.47%;
  --vid-h: 107.08%;
}

/* ---------- Cards and panels ---------- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: var(--s-5);
}
.card {
  padding: var(--s-6) var(--s-6);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
}
.card h4 {
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 1.4;
}
.card h4 + * {
  margin-top: var(--gap-inline);
}
.card p,
.card li {
  font-size: var(--fs-small);
}
.card ul {
  padding-left: var(--s-5);
}
.card li + li {
  margin-top: var(--s-1);
}

.panel {
  padding: var(--s-6) var(--s-7);
  border-radius: var(--r-md);
  background: var(--surface-alt);
}
.panel > * + * {
  margin-top: var(--s-5);
}
.panel h4 {
  font-size: var(--fs-body);
  font-weight: 700;
}
.panel h4 + p {
  margin-top: var(--s-1);
}

.callout {
  padding: var(--s-6);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.callout h4 {
  font-size: var(--fs-title);
  font-weight: 700;
  line-height: 1.25;
}
.callout h4 + p {
  margin-top: var(--gap-inline);
}

@media (max-width: 809px) {
  .panel,
  .figure--panel {
    padding: var(--s-5);
  }
  .card,
  .callout {
    padding: var(--s-5);
  }
}

/* Data tables */
.table-wrap {
  overflow-x: auto;
}
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-small);
  line-height: 1.5;
}
.data-table th {
  padding-bottom: var(--s-3);
  font-size: var(--fs-body);
  font-weight: 500;
  text-align: left;
  vertical-align: bottom;
}
.data-table td {
  padding-block: var(--s-4);
  vertical-align: top;
}
.data-table tbody tr + tr td {
  border-top: 1px solid var(--line);
}
.data-table th + th,
.data-table td + td {
  padding-left: var(--s-5);
}

/* Project facts (Platform / Timeline / Team / Tools) */
.overview {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 200px;
  gap: var(--s-8);
  max-width: var(--w-prose);
  margin-inline: auto;
}
.facts {
  display: grid;
  gap: var(--s-5);
  align-content: start;
}
.facts dt {
  font-weight: 700;
  line-height: 1.3;
}
.facts dd {
  margin-top: var(--s-1);
  line-height: 1.3;
}
.facts dd span {
  display: block;
}
.facts dd span + span {
  margin-top: var(--s-1);
}
@media (max-width: 800px) {
  .overview {
    grid-template-columns: 1fr;
    gap: var(--s-7);
  }
  .facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    padding: var(--s-5);
    border-radius: var(--r-md);
    background: var(--surface-alt);
  }
}

/* Text beside a figure */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--split-media, 280px));
  gap: var(--s-8);
  align-items: start;
}
.split > .prose > h3:first-child {
  margin-top: 0;
}
@media (max-width: 800px) {
  .split {
    grid-template-columns: 1fr;
    gap: var(--s-6);
  }
  .split .figure {
    max-width: var(--split-media, 280px);
    margin-inline: auto;
  }
}

/* ---------- Step-by-step experience panels ---------- */
.experience {
  width: min(var(--w-full), 100vw - var(--gutter) * 2);
  margin: var(--gap-section) auto;
  padding: var(--s-9) var(--s-8);
  border-radius: var(--r-lg);
  background: var(--surface-alt);
}
/* Outlined variant: on Customized Stage Funding the original alternates
   outlined (agent, borrower) and filled (contractor) walkthroughs */
.experience--outlined {
  border: 1px solid var(--line-strong);
  background: transparent;
}
.experience__head {
  margin-bottom: var(--s-8);
  text-align: center;
}
.steps {
  counter-reset: step;
  display: grid;
  gap: var(--s-9);
  max-width: var(--w-wide);
  margin-inline: auto;
  padding: 0;
  list-style: none;
}
.step {
  counter-increment: step;
}
.step__title {
  display: flex;
  gap: var(--s-3);
  font-size: var(--fs-title);
  font-weight: 700;
  line-height: 1.3;
}
.step__title::before {
  content: counter(step) ".";
  flex: none;
  min-width: 24px;
}
.step__body {
  margin-top: var(--s-3);
  padding-left: 36px; /* aligns with the title text after the number */
}
/* Some walkthroughs on the original have no step numbers */
.steps--plain .step__title::before {
  content: none;
}
.steps--plain .step__body {
  padding-left: 0;
}
.step__body > * + * {
  margin-top: var(--s-6);
}
.step__media {
  display: grid;
  gap: var(--s-5);
}
.step__media--two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.step__media--video {
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.34fr);
  align-items: start;
}
.step__media--video .embed {
  width: 83.7%; /* the original's 226px window inside a 270px column */
  margin-inline: auto;
}
/* Step with text on the left and a figure on the right */
.step--split {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: var(--s-8);
  align-items: start;
}
.step--split .step__body > .figure {
  display: none;
}
.step--split > .figure {
  grid-column: 2;
  grid-row: 1;
}
.step--split > .step__text {
  grid-column: 1;
  grid-row: 1;
}

@media (max-width: 900px) {
  .experience {
    padding: var(--s-8) var(--s-5);
  }
  .step--split {
    grid-template-columns: 1fr;
    gap: var(--s-5);
  }
  .step--split > .figure,
  .step--split > .step__text {
    grid-column: 1;
    grid-row: auto;
  }
  .step__media--two,
  .step__media--video {
    grid-template-columns: 1fr;
  }
  .step__media--video .embed {
    max-width: 280px;
    margin-inline: auto;
  }
}
@media (max-width: 809px) {
  .experience {
    width: 100%;
    border-radius: 0;
  }
  /* Phones: the original's values (measured on jennyktran.design): the box
     sits 10px from the screen edge with 24px inside, which gives the text
     more reading width than lining the box up with the page gutter */
  .experience--inset {
    width: calc(100% - 20px);
    padding: 40px var(--s-5) 30px;
    border-radius: var(--r-lg);
  }
  .steps {
    gap: var(--s-8);
  }
  .step__body {
    padding-left: 0;
  }
}

/* ---------- Feature rows (Deliverables) ---------- */
.features {
  display: grid;
  gap: var(--s-9);
  max-width: var(--w-wide);
  margin: var(--s-9) auto 0;
}
.feature {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s-8);
  align-items: start;
}
.feature .prose {
  padding-top: var(--s-6);
}
@media (max-width: 800px) {
  .feature {
    grid-template-columns: 1fr;
    gap: var(--s-6);
  }
  .feature .prose {
    padding-top: 0;
  }
}

.prototype {
  display: grid;
  /* the original's proportions: phone window 277, desktop image 580 */
  grid-template-columns: minmax(0, 277fr) minmax(0, 580fr);
  gap: var(--s-7);
  align-items: center;
  max-width: var(--w-wide);
  margin: var(--s-7) auto 0;
}
@media (max-width: 800px) {
  .prototype {
    grid-template-columns: 1fr;
  }
  .prototype .embed {
    width: 100%;
    max-width: 280px;
    margin-inline: auto;
  }
}

/* ---------- Case study intro ---------- */
.cs-intro {
  /* Two-tone background: the cover sits across the seam */
  background: linear-gradient(var(--surface-alt) 0 calc(100% - var(--cover-overlap, 280px)), var(--surface) 0);
  padding-top: var(--s-8);
}
.cs-intro .lead {
  margin-top: var(--s-4);
}
/* Phones: the cover is much shorter, so a fixed 280px seam would cut through
   the intro text. Tie it to the cover's width so it always lands inside it. */
@media (max-width: 809px) {
  .cs-intro {
    --cover-overlap: calc((100vw - 2 * var(--gutter)) * 0.3);
  }
}
.cs-cover {
  margin-top: var(--s-7);
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--cover-bg, var(--surface-alt));
}
.cs-cover img {
  width: 100%;
}
.cs-cover--padded img {
  width: 90%;
  margin: 4.5% auto;
}

/* Next project + footer */
.next-project {
  display: flex;
  align-items: center; /* chevron sits in the vertical middle of the card */
  justify-content: space-between;
  gap: var(--s-4);
  /* full width of the text column above, so their edges line up */
  margin: var(--gap-section) auto 0;
  padding: var(--s-4) var(--s-5);
  border: 1px solid var(--line-card);
  border-radius: var(--r-lg);
  background-color: var(--surface);
  text-decoration: none;
  /* hover: fills off-white, border darkens, chevron nudges right. The fill
     eases without overshoot (a spring would flash past the color); the
     border and chevron ride the original's hover spring. No glow. */
  transition:
    background-color 0.5s var(--ease-swipe),
    border-color var(--dur-hover) var(--ease-hover);
}
.next-project:hover {
  border-color: var(--line-card-hover);
  background-color: var(--surface-alt);
}
.next-project__text {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}
/* same chevron and size as the home scroll arrow, pointing right */
.next-project .icon {
  flex: none;
  width: 32px;
  height: 32px;
  transition: transform var(--dur-hover) var(--ease-hover);
}
.next-project:hover .icon {
  transform: translateX(4px);
}
.next-project__label {
  color: var(--ink-muted);
  font-size: var(--fs-small);
}
.next-project__title {
  font-size: var(--fs-title);
  font-weight: 500;
}

.site-footer {
  padding-block: 60px;
  border-top: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink-soft);
  text-align: center;
}
.site-footer__title {
  font-size: 30px; /* matches the live footer; the one size outside the ramp */
  line-height: 1.2;
}
.site-footer__contact {
  margin-top: 6px;
  line-height: 1.2;
}
.site-footer__contact a {
  font-weight: 700;
  text-decoration: none;
}

/* ---------- Icons ---------- */
.icon {
  flex: none;
  width: 16px;
  height: 16px;
}

/* ---------- Home ---------- */
.hero {
  /* Pinned: the projects section scrolls up over it like a curtain */
  position: sticky;
  top: 0;
  z-index: 0;
  display: grid;
  place-items: center;
  min-height: 100vh;
  min-height: 100svh;
  padding: calc(var(--header-h) + var(--s-7)) var(--gutter) var(--s-9);
  overflow: hidden;
  background: #f6e7f1 url("../img/home/hero-poster.webp") center / cover no-repeat;
}
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero__content {
  position: relative;
  max-width: 600px;
}
/* The curtain: projects slide up over the pinned hero. Plain edge, no
   shadow or rounding (owner's preference). */
.page-home #projects {
  position: relative;
  z-index: 1;
  /* The scroll arrow lands with this section fully covering the hero (no
     strip of hero under the nav); its own top padding clears the nav. */
  scroll-margin-top: calc(-1 * (var(--header-h) + var(--s-4)));
  padding-top: max(var(--gap-section), calc(var(--header-h) + var(--s-6)));
}
.hero__title {
  font-size: var(--fs-hero);
}
.nowrap {
  white-space: nowrap;
}
.hero__title em {
  color: var(--accent);
  font-style: normal;
}
.hero__intro {
  margin-top: var(--s-5);
  font-size: var(--fs-hero-intro);
}
.hero__intro strong {
  font-weight: 500;
}
.scroll-cue {
  position: absolute;
  z-index: 1;
  bottom: var(--s-8);
  left: 50%;
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin-left: -32px;
  border-radius: 999px;
}
.scroll-cue .icon {
  width: 32px;
  height: 32px;
}

.projects {
  display: grid;
  gap: var(--s-7);
  max-width: 1000px; /* the original's 960px plus a touch (owner's call) */
  margin: var(--s-7) auto 0;
  padding: 0;
  list-style: none;
}
.project-card {
  box-shadow: 0 0 15px 0 rgba(40, 43, 82, 0);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, min(540px, 58%));
  gap: var(--s-7);
  /* the 1px border sits inside the original's 24px top/bottom padding */
  padding: calc(var(--s-5) - 1px) var(--s-5) calc(var(--s-5) - 1px) var(--s-6);
  border: 1px solid var(--line-card);
  border-radius: var(--r-lg);
  background: rgba(247, 247, 245, 0.6);
  text-decoration: none;
  transition:
    box-shadow var(--dur-hover) var(--ease-hover),
    border-color var(--dur-hover) var(--ease-hover);
}
.project-card:hover {
  border-color: var(--line-card-hover);
  box-shadow: 0 0 15px 0 rgba(40, 43, 82, 0.08);
}
.project-card__text {
  display: flex;
  flex-direction: column;
  padding-block: var(--s-4);
}
.project-card__title {
  margin-top: var(--s-3);
  font-size: var(--fs-title);
  font-weight: 500;
  line-height: 1.3;
}
.project-card__summary {
  margin-top: var(--s-3);
  font-size: var(--fs-small);
}
.project-card__cta {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  margin-top: auto;
  padding-top: var(--s-6);
  font-size: var(--fs-caption);
  font-weight: 500;
}
.project-card__cta .icon {
  transition: transform var(--dur-hover) var(--ease-hover);
}
.project-card:hover .project-card__cta .icon {
  transform: translateX(4px);
}
.project-card__media {
  display: grid;
  place-items: center;
  aspect-ratio: 500 / 384; /* the original's panel shape (owner kept it) */
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--cover-bg);
}
.project-card__media img {
  width: 100%;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) 0;
  padding: 0;
  color: var(--ink-muted);
  font-size: var(--fs-caption);
  list-style: none;
}
/* Separator trails its item, so a wrapped row never starts with a dot */
.tags li:not(:last-child)::after {
  content: "•";
  padding-inline: var(--s-2);
}
/* the original sets the year apart: "Desktop | 2024-2025" */
.tags li:nth-last-child(2)::after {
  content: "|";
}

@media (max-width: 860px) {
  .project-card {
    grid-template-columns: 1fr;
    gap: var(--s-5);
    padding: var(--s-5);
  }
  .project-card__media {
    grid-row: 1;
  }
  .project-card__text {
    padding-block: 0 var(--s-2);
  }
}

/* ---------- About ---------- */
/* Paper texture behind the intro and the header strip above it (so there's
   no seam at the nav). The gallery band below is white and covers it.
   paper.webp is a seamless 1600px tile (drawn at 800px for sharp retina grain)
   cut from Watercolor Paper 36 in Design Assets/TomaszMro_PaperPack. */
.page-about {
  background:
    url("../img/paper.webp?v=3") repeat,
    var(--surface-alt);
  background-size: 800px 800px, auto;
}
.page-about .about-intro.band--alt {
  background: transparent;
}
/* The white gallery sheet has a torn top edge lying over the textured paper.
   tear.svg is a seamless 1440px strip (edge plus a faint fibrous rim); the
   shadow falls up onto the paper so the edge reads as a separate sheet. */
.band--torn {
  position: relative;
}
.band--torn::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: calc(100% - 1px);
  height: 36px;
  background: url("../img/tear.svg?v=2") repeat-x 0 0 / 1440px 36px;
  filter: drop-shadow(0 -1px 1.5px rgba(58, 50, 40, 0.16));
  clip-path: inset(-8px 0 0 0); /* keep the shadow off the strip's flat bottom */
  pointer-events: none;
}
.about-intro {
  padding-block: var(--s-8) var(--gap-section);
}
.about-intro .prose {
  margin-top: var(--s-5);
}
.video-lite {
  position: relative;
  display: block;
  width: 100%;
  margin-top: var(--s-6);
  padding: 0;
  overflow: hidden;
  border: 0;
  border-radius: var(--r-md);
  background: #000;
  aspect-ratio: 16 / 9;
  cursor: pointer;
}
.video-lite img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.video-lite__play {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  place-items: center;
  width: 68px;
  height: 48px;
  margin: -24px 0 0 -34px;
  border-radius: var(--r-sm);
  background: rgba(33, 33, 33, 0.8);
  transition: background-color 0.15s var(--ease);
}
.video-lite:hover .video-lite__play,
.video-lite:focus-visible .video-lite__play {
  background: #f00;
}
.video-lite__play .icon {
  width: 24px;
  height: 24px;
  color: #fff;
}
.video-lite iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  gap: 18px; /* the original's spacing */
  margin-top: var(--s-5);
  padding: 0;
  list-style: none;
}
.stat {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: calc(var(--s-4) - 1px) var(--s-5); /* 1px border inside the original's 16px */
  border: 1px solid var(--line-strong);
  border-radius: var(--r-stat);
  background: var(--surface);
  text-decoration: none;
}
.stat img {
  width: 48px;
  height: 48px;
}
.stat__value {
  display: block;
  font-size: var(--fs-h2);
  font-weight: 500;
  line-height: 1.2;
}
.stat__label {
  display: block;
  font-size: var(--fs-body);
  line-height: 1.2;
}
/* Phones: like the original, one full-width card per row with the count and
   label on a single line ("106K subscribers") */
@media (max-width: 809px) {
  .stats {
    grid-template-columns: 1fr;
  }
  .stat__value,
  .stat__label {
    display: inline;
  }
  .stat__value {
    font-size: var(--fs-body);
    font-weight: 400;
  }
}

.gallery {
  display: grid;
  gap: var(--s-7);
  margin-top: var(--s-7);
  padding: 0;
  list-style: none;
}
.gallery img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--r-md);
}
.gallery .caption {
  margin-top: var(--s-3);
  text-align: left;
}

/* ---------- Lightbox ---------- */
.zoomable {
  cursor: zoom-in;
}
.lightbox {
  width: 100vw;
  max-width: none;
  height: 100vh;
  height: 100dvh;
  max-height: none;
  margin: 0;
  padding: var(--s-5);
  border: 0;
  background: transparent;
  cursor: zoom-out;
}
/* The dark scrim is its own layer so it can fade independently of the
   image (site.js animates both) */
.lightbox::backdrop {
  background: transparent;
}
.lightbox__scrim {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 24, 0.94);
}
.lightbox img {
  position: relative; /* above the scrim */
  transform-origin: top left;
  border-radius: var(--r-sm);
  background: var(--surface);
  width: auto;
  max-width: 100%;
  height: 100%;
  margin-inline: auto;
  object-fit: contain;
}
/* Close cue: a dark frosted round X in the top-right corner. Dark so it
   reads over both the scrim and a white screenshot. Fixed, so it stays put
   while a wide screenshot scrolls sideways on phones. */
.lightbox .lightbox__close {
  position: fixed;
  top: max(var(--s-4), env(safe-area-inset-top));
  right: max(var(--s-4), env(safe-area-inset-right));
  z-index: 1;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 50%;
  background: rgba(20, 20, 24, 0.72);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: #fff;
  cursor: pointer;
  transition: background-color var(--dur-hover) var(--ease-hover), border-color var(--dur-hover) var(--ease-hover);
}
.lightbox .lightbox__close:hover {
  background: rgba(20, 20, 24, 0.9);
  border-color: rgba(255, 255, 255, 0.4);
}
.lightbox .lightbox__close:focus-visible {
  outline-color: #fff;
  outline-offset: 3px;
  border-radius: 50%;
}
/* Phones: let wide screenshots overflow sideways so they can be read */
@media (max-width: 809px) {
  .lightbox {
    overflow: auto;
    padding: var(--s-4);
  }
  .lightbox img {
    width: auto;
    max-width: none;
    height: auto;
    max-height: calc(100dvh - var(--s-8));
    min-height: 60dvh;
    object-fit: contain;
  }
}
.has-lightbox {
  overflow: hidden;
}

/* ---------- 404 ---------- */
.not-found {
  display: grid;
  place-items: center;
  min-height: 60vh;
  text-align: center;
}
.not-found p {
  margin-top: var(--s-4);
}

/* ---------- Motion ----------
   Timings measured from the live Framer site. Everything is wrapped in
   no-preference, and .reveal only hides content once site.js is running. */
@media (prefers-reduced-motion: no-preference) {
  /* Hero and About intro: drop in from 48px above with a tiny overshoot */
  .hero__content,
  .about-intro > .container {
    animation: drop-in 1.5s both;
  }
  /* Hero video and case study intro fade up from nothing */
  .hero__video,
  .cs-intro {
    animation: fade-in 0.6s ease-out both;
  }
  .cs-cover {
    animation: fade-in 0.6s ease-out 0.1s both;
  }
  /* Scroll arrow: fades in after the headline, then bobs 16px forever */
  .scroll-cue {
    animation: fade-in 0.8s ease-out 0.5s both;
  }
  .scroll-cue .icon {
    animation: bob 2s ease-in-out infinite;
  }

  /* Case study sections fade in as they scroll into view */
  /* Fade the section's contents, not the section: its background must stay
     put, or the off-white page shows through a white band as it fades in */
  .js .reveal > * {
    opacity: 0;
    transition: opacity 0.6s ease-out;
  }
  .js .reveal.is-visible > * {
    opacity: 1;
  }
  /* ...and the hairline under each section title grows from the center */
  .js .reveal .section-head::after {
    opacity: 0;
    transform: scale(0.5);
    transition: opacity 0.3s ease-out 0.1s, transform 0.5s var(--ease-spring) 0.1s;
  }
  .js .reveal.is-visible .section-head::after {
    opacity: 1;
    transform: none;
  }
}

/* ---------- Page swap (nav clicks) ----------
   The header stays put while the pages "fade through" (site.js): the old
   page fades out with a small drift, then the new one fades in and settles.
   Only one page is ever visible, so text never ghosts over text. Motion runs
   up toward About and down toward Projects. */
.page-leaving {
  position: fixed;
  inset: 0;
  z-index: 40; /* over the incoming page, under the header */
  overflow: hidden;
  pointer-events: none;
}
.page-leaving__inner {
  position: absolute;
  left: 0;
  right: 0;
}
.page-entering {
  position: relative;
  z-index: 1;
}
main:focus {
  outline: none; /* focus moves to the new page for screen readers; no ring */
}
@media (prefers-reduced-motion: no-preference) {
  .page-leaving.leave-up {
    animation: fade-out-up 0.18s cubic-bezier(0.4, 0, 1, 1) both;
  }
  .page-leaving.leave-down {
    animation: fade-out-down 0.18s cubic-bezier(0.4, 0, 1, 1) both;
  }
  .page-entering.enter-up {
    animation: slide-fade-up 0.42s cubic-bezier(0.22, 1, 0.36, 1) 0.14s both;
  }
  .page-entering.enter-down {
    animation: slide-fade-down 0.42s cubic-bezier(0.22, 1, 0.36, 1) 0.14s both;
  }
  /* The transition is the entrance, so intros skip their own drop-in */
  .arrived-by-swap .hero__content,
  .arrived-by-swap .about-intro > .container {
    animation: none;
  }
}
@keyframes fade-out-up {
  to {
    opacity: 0;
    transform: translateY(-12px);
  }
}
@keyframes fade-out-down {
  to {
    opacity: 0;
    transform: translateY(12px);
  }
}
@keyframes slide-fade-up {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
}
@keyframes slide-fade-down {
  from {
    opacity: 0;
    transform: translateY(-24px);
  }
}

@keyframes drop-in {
  0% {
    opacity: 0;
    transform: translateY(-48px);
    animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
  }
  48% {
    opacity: 1;
    transform: translateY(0);
    animation-timing-function: ease-out;
  }
  60% {
    transform: translateY(0.75px);
    animation-timing-function: ease-in-out;
  }
  100% {
    opacity: 1;
    transform: none;
  }
}
@keyframes fade-in {
  from {
    opacity: 0;
  }
}
@keyframes bob {
  0%,
  100% {
    transform: translateY(-16px);
  }
  50% {
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
