/* Urban Parking Ecosystem — case study
   Monochrome dark layout on a single ~1176px column; colour comes only
   from the imagery. Borders stay in the rgb(36,36,36) range. */

:root {
  --bg: rgb(20, 20, 20);
  --surface: rgb(28, 28, 28);
  --line: rgb(36, 36, 36);
  --text: rgb(222, 222, 222);
  --muted: rgb(150, 150, 150);
  --accent: #ffef93;
  --accent-70: rgb(255 239 147 / 0.7);
  --column: 1176px;
  --side: 40px;
  --radius: 20px;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-std: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Per-page accent: the Alienware case study swaps the yellow for purple.
   Every accent use reads these two variables, so nothing else changes. */
.theme-alienware {
  --accent: #c993ff;
  --accent-70: rgb(201 147 255 / 0.7);
}

/* Urban Parking's yellow, for links to it from other themed pages
   (e.g. the next-project card on the purple Configurator page). */
.theme-urban {
  --accent: #ffef93;
  --accent-70: rgb(255 239 147 / 0.7);
}

/* Alienware Learn Page: cyan accent. */
.theme-learn {
  --accent: #93ffff;
  --accent-70: rgb(147 255 255 / 0.7);
}

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

html { scroll-behavior: smooth; }

/* Smooth scrolling (assets/js/smooth-scroll.js, Lenis). Lenis animates
   the scroll itself, so native smooth scrolling must be off while it
   runs; the rest is Lenis's required base CSS. */
html.lenis { scroll-behavior: auto; }

html.lenis,
html.lenis body { height: auto; }

.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }

.lenis.lenis-smooth iframe { pointer-events: none; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Plus Jakarta Sans", sans-serif;
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { text-wrap: balance; }
p, li { text-wrap: pretty; }

a { color: inherit; }

/* ---------- Top nav ---------- */

.topnav {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10050;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 60px;
  /* Explicit widths (not auto) so the contraction can transition. */
  width: min(880px, calc(100vw - 32px));
  padding: 0 12px 0 16px;
  border-radius: 9999px;
  background: transparent;
  border: 1px solid transparent;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  transition:
    background 0.5s var(--ease-out),
    border-color 0.5s var(--ease-out),
    backdrop-filter 0.5s var(--ease-out),
    -webkit-backdrop-filter 0.5s var(--ease-out),
    box-shadow 0.5s var(--ease-out),
    width 0.5s var(--ease-std),
    padding 0.5s var(--ease-std);
}

.topnav.scrolled {
  width: min(720px, calc(100vw - 32px));
  padding-right: 8px;
  background: rgba(28, 28, 28, 0.7);
  border-color: var(--line);
  box-shadow: 0 2px 16px rgba(0, 0, 0, 0.03);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.topnav-brand {
  display: inline-flex;
  font-weight: 600;
  letter-spacing: -0.02em;
  text-decoration: none;
  /* pre keeps the trailing space in "kshay " as the gap before "M". */
  white-space: pre;
}

/* "kshay " and "ogral" collapse on scroll, leaving "AM". max-width
   animates to/from each span's measured width (--w, set by JS; 6em is
   the no-JS fallback) so the collapse runs for the full duration. */
.brand-fold {
  display: inline-block;
  overflow: hidden;
  max-width: var(--w, 6em);
  opacity: 1;
  transition:
    max-width 0.5s var(--ease-std),
    opacity 0.35s var(--ease-std);
}

.topnav.scrolled .brand-fold {
  max-width: 0;
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .brand-fold { transition: none; }
}

.topnav-right {
  display: flex;
  align-items: center;
  gap: 24px;
}

/* Pinned to the pill's centre. The pill itself is centred on the
   viewport and contracts symmetrically on scroll, so the links stay
   dead centre in both states. */
.topnav-links {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 20px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.topnav-links a {
  font-size: 15px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s ease;
}

.topnav-links a:hover,
.topnav-links a[aria-current="page"] { color: var(--text); }

.topnav-cta {
  padding: 9px 18px;
  border-radius: 9999px;
  background: var(--text);
  color: var(--bg);
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.2s ease;
}

.topnav-cta:hover { background: #fff; }

/* ---------- Page column ---------- */

.page {
  max-width: calc(var(--column) + 2 * var(--side));
  margin: 0 auto;
  padding: 72px var(--side) 0;
}

/* ---------- Intro ---------- */

/* Two columns: title + subtitle on the left, copy + tags on the right. */
.intro-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 32px 64px;
}

h1 {
  margin: 0;
  font-size: 24px;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.48px;
}

.subtitle {
  margin: 12px 0 0;
  font-size: 20px;
  font-weight: 400;
  line-height: 32px;
  color: rgb(190, 190, 190);
}

.intro-copy { margin: 0; }
.intro-copy + .intro-copy { margin-top: 16px; }

/* Plain uppercase line; every tag after the first gets a leading dash,
   so wrapped lines start with "—" like the reference. */
.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 0;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  line-height: 18px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
}

.tags li + li::before {
  content: "—";
  margin: 0 8px;
}

/* ---------- Image slots ---------- */

/* Placeholder frames — swap the <span> for an <img>; the frame keeps
   the radius and crops the image. */
.img-slot {
  position: relative;
  display: grid;
  place-items: center;
  margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  border: 1px dashed var(--line);
  color: var(--muted);
  font-size: 13px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

/* Absolutely filled: height: 100% doesn't resolve inside the auto-sized
   grid row, which let the image overflow and clip from the top. */
.img-slot img,
.img-slot video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Hover caption: a single line of muted text in a compact #191919 (80%)
   pill at the image's bottom-left, faded in on hover. Captions are meant
   to be short; anything too long for the image is cut with an ellipsis
   rather than wrapping. Resets the frame's uppercase placeholder-label
   styling. */
.img-caption {
  position: absolute;
  left: 16px;
  bottom: 12px;
  z-index: 1;
  max-width: calc(100% - 32px);
  padding: 4px 12px;
  border-radius: 9999px;
  background: rgb(25 25 25 / 0.8);
  /* Frosted fill: blurs the photo behind the pill. */
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  /* Near-white, same as body text, so captions read clearly over photos. */
  color: var(--text);
  font-size: 12px;
  line-height: 18px;
  letter-spacing: normal;
  text-align: left;
  text-transform: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s var(--ease-out);
}

/* Award badge on project thumbnails (homepage cards, previous/next):
   the same frosted pill as the captions, pinned bottom left and always
   visible, with a small rosette in the project's accent. It sits
   outside the <img>, so it doesn't zoom with the image on hover. When
   the project is opened, site.js carries a copy along with the growing
   image and fades it out. */
.award-badge {
  position: absolute;
  bottom: 16px;
  left: 16px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px 6px 10px;
  border-radius: 9999px;
  background: rgb(25 25 25 / 0.8);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  line-height: 16px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}

.award-badge svg {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--accent);
}

@media (max-width: 640px) {
  .award-badge { bottom: 12px; left: 12px; }
}

/* White caption text, e.g. over bright UI screenshots. */
.img-caption--light { color: #fff; }

.img-slot:hover .img-caption { opacity: 1; }

/* Video pause/play toggle (added by script): a small round button at the
   bottom right with the same frosted fill as the caption pill, shown on
   hover or keyboard focus. */
.video-toggle {
  position: absolute;
  right: 16px;
  bottom: 12px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 9999px;
  background: rgb(25 25 25 / 0.8);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--text);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.3s var(--ease-out), background 0.2s ease;
}

.video-toggle:hover { background: rgb(40 40 40 / 0.9); }

.video-toggle svg {
  width: 10px;
  height: 10px;
  fill: currentColor;
}

.img-slot:hover .video-toggle,
.video-toggle:focus-visible { opacity: 1; }

.video-toggle:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

/* On videos, keep the caption clear of the toggle. */
.img-slot:has(.video-toggle) .img-caption { max-width: calc(100% - 74px); }

/* Touch screens can't hover, so show captions there all the time. */
@media (hover: none) {
  .img-caption,
  .video-toggle { opacity: 1; }
}

/* Hero crop sits low so the dog, stencil and scooter stay in frame. */
.img-hero img { object-position: 50% 70%; }

.img-slot:has(img),
.img-slot:has(video) { border: 0; }

/* Hero sits above the page column and spans the viewport, inset 40px
   each side; top margin clears the fixed nav (16px + 60px). */
.img-hero {
  margin: 100px 40px 0;
  aspect-ratio: 16 / 9;
}

.img-wide { aspect-ratio: 16 / 9; }

/* Logo or badge with no frame fill, sitting straight on the page. The
   frame drops its fixed ratio and is exactly as tall as the logo; the
   logo shows at its native size (never upscaled), centered horizontally. */
.img-slot.img-logo {
  aspect-ratio: auto;
  background: none;
}

/* Display width is set per logo with --logo-w, so a high-resolution
   file (e.g. 4x) still renders at its intended size instead of filling
   the frame. */
.img-logo img {
  position: static;
  width: var(--logo-w, auto);
  max-width: 100%;
  height: auto;
}

/* Frame takes the image's own proportions instead of a fixed ratio. */
.img-slot.img-natural { aspect-ratio: auto; }
.img-natural img {
  position: static;
  height: auto;
}

.img-wide { margin-top: 56px; }

/* Image set between paragraphs: equal breathing room above and below. */
.img-inline { margin-bottom: 56px; }

.img-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 56px;
}

/* A large image and a pair stacked together read as one group: same
   16px gap as between the pair's own images, whichever comes first. */
.img-pair + .img-wide,
.img-wide + .img-pair { margin-top: 16px; }

/* Scroll fade-in (assets/js/scroll-reveal.js): a plain, soft opacity
   fade. .reveal is only added by the script, so content stays visible
   without it; the transition lives on the revealed state. */
.reveal { opacity: 0; }

.reveal.is-revealed {
  opacity: 1;
  transition: opacity 0.9s cubic-bezier(0.25, 0.1, 0.25, 1);
}

/* ---------- Eyebrow labels: H2 ---------- */

h2 {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 2.24px;
  text-transform: uppercase;
}

/* ---------- Meta ---------- */

/* 2×2 grid of small muted labels over light values, closed off by a
   hairline. */
.meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 64px 48px;
  margin: 72px 0 0;
  padding-bottom: 96px;
  border-bottom: 1px solid var(--line);
}

.meta dt {
  margin-bottom: 16px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--muted);
}

.meta dd {
  margin: 0;
  line-height: 28px;
  color: rgb(210, 210, 210);
}

/* Same muted grey as the meta labels above. */
.meta .muted { color: var(--muted); }

/* Optional full-width meta row (e.g. an award), value in the accent. */
.meta .meta-full { grid-column: 1 / -1; }
.meta .meta-accent { color: var(--accent); }

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

/* ---------- Case study body: sticky titles + content ---------- */

/* Narrow fixed-width title rail so the content column gets the room. */
.study {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: 0 80px;
  align-items: start;
  padding-bottom: 140px;
}

/* Sits below the fixed nav pill (16px + 60px) while the right column
   scrolls; the script marks the in-view section's link .is-active. */
.study-nav {
  position: sticky;
  top: 120px;
  /* Margin (not padding) lines the first title up with the first
     section heading without pushing the stuck list further down. */
  margin-top: 128px;
}

.study-nav ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

.study-nav a {
  display: block;
  padding: 5px 0;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  /* Keep each title on one line; the longest runs a few px into the gap. */
  white-space: nowrap;
  color: rgb(70, 70, 70);
  text-decoration: none;
  transition: color 0.3s var(--ease-out);
}

.study-nav a:hover { color: var(--muted); }
.study-nav a.is-active { color: var(--accent); }

/* ---------- Sections ---------- */

/* Body copy in the case study reads larger than the 16px page base. */
.study-body {
  font-style: normal;
  font-weight: 400;
  font-size: 18px;
  line-height: 32px;
}

.study-body section {
  padding-top: 128px;
  scroll-margin-top: 0;
}

h2 { margin: 0 0 40px; }

.study-body h2 {
  font-style: normal;
  font-weight: 600;
  font-size: 16px;
  line-height: 18px;
  color: var(--muted);
}

h3 {
  margin: 56px 0 8px;
  /* One step above the 18px body copy so sub-headings still stand out. */
  font-size: 20px;
  font-weight: 600;
  line-height: 32px;
}

h2 + h3 { margin-top: 0; }

section p { margin: 0 0 24px; }
section p:last-child { margin-bottom: 0; }
section h3 + p { margin-bottom: 8px; }
section .closing { margin-top: 56px; }

strong { font-weight: 600; }

/* Outbound link line (e.g. to a live prototype), in the page accent. */
.ext-link a {
  color: var(--accent);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  transition: text-decoration-thickness 0.2s ease;
}

.ext-link a:hover { text-decoration-thickness: 2px; }

.ext-link a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Masked word reveal (homepage hero, About intro): JS wraps each word
   in .word-clip (an overflow mask) > .word-inner and slides the inner
   span up into view; page CSS sets the states and timing. */
.word-clip {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* Room for descenders inside the mask without moving the baseline. */
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

.word-inner { display: inline-block; }

/* Text for screen readers only. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Paragraphs whose bold phrases carry the accent colour. */
.accent-bold strong { color: var(--accent); }

/* Large pull-out line (e.g. the guiding question's answer), set on its
   own line in the accent yellow at 70% opacity. */
.pull {
  display: block;
  margin-top: 24px;
  font-size: 40px;
  font-weight: 400;
  font-style: normal;
  line-height: 1.2;
  letter-spacing: -0.8px;
  color: var(--accent-70);
}

/* No top gap when the pull line opens its paragraph. */
.pull:first-child { margin-top: 0; }

/* Opportunity areas: solution in full accent, trade-off at 70%. */
.solution { color: var(--accent); font-weight: 600; }
.tradeoff { color: var(--accent-70); }

section ul {
  margin: 0;
  padding-left: 20px;
}

section li {
  margin-bottom: 16px;
  line-height: 32px;
}

section li:last-child { margin-bottom: 0; }
section li::marker { color: var(--muted); }

/* ---------- Stats ---------- */

/* Metric cards: yellow figure up top, muted caption pinned to the
   bottom so cards line up even when captions wrap differently. */
.stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-top: 56px;
}

.stats > div {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 40px;
  min-height: 170px;
  padding: 32px;
  border-radius: 12px;
  background: var(--surface);
}

.stats p { margin: 0; }

.stat-num {
  font-size: 28px;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.56px;
  color: var(--accent);
  /* Fixed-width digits so the count-up doesn't jitter. */
  font-variant-numeric: tabular-nums;
}

/* Fade-in on scroll. Only armed once JS adds .will-reveal, so the
   cards stay visible without JS. Each card is staggered slightly. */
.stats.will-reveal > div {
  opacity: 0;
  transform: translateY(16px);
}

/* Transition lives on the revealed state only, so arming the hidden
   state is instant instead of visibly fading the cards out first. */
.stats.is-revealed > div {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.6s var(--ease-out),
    transform 0.6s var(--ease-out);
}

.stats.is-revealed > div:nth-child(2) { transition-delay: 0.08s; }
.stats.is-revealed > div:nth-child(3) { transition-delay: 0.16s; }

.stats .stat-label {
  font-size: 16px;
  line-height: 24px;
  color: var(--muted);
}

/* ---------- Previous / next project ---------- */

/* Two columns under a hairline: previous on the left, next on the right
   (alone on the right when there's no previous). Each link carries its
   project's theme class, so the title tints to that accent on hover. */
.next-projects {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  padding: 84px 0 140px;
  border-top: 1px solid var(--line);
}

.next-project a {
  display: block;
  text-decoration: none;
}

.next-project--next {
  grid-column: 2;
  text-align: right;
}

.next-project .img-slot {
  aspect-ratio: 16 / 9;
  border-radius: 12px;
  transform: translateZ(0);
}

/* Same slow zoom as the homepage project cards. */
.next-project .img-slot img {
  transition: transform 1.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.next-project a:hover .img-slot img { transform: scale(1.04); }

/* Same low crop as the Urban Parking hero, keeping the stencil in frame. */
.img-hero-crop img { object-position: 50% 70%; }

.next-label {
  margin: 24px 0 0;
  font-size: 13px;
  font-weight: 600;
  line-height: 18px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--muted);
}

.next-title {
  margin: 6px 0 0;
  font-size: 24px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.02em;
  transition: color 0.3s var(--ease-out);
}

.next-project a:hover .next-title { color: var(--accent); }

.next-project a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: 12px;
}

@media (prefers-reduced-motion: reduce) {
  .next-project .img-slot img { transition: none; }
  .next-project a:hover .img-slot img { transform: none; }
}

/* ---------- Project transition ----------
   JS (site.js) lifts the clicked project image into a fixed clone
   (.hero-morph) and grows it into the case study's hero frame while
   the rest of the page fades, then navigates. */
.hero-morph {
  position: fixed;
  z-index: 10000;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--surface);
  pointer-events: none;
}

.hero-morph img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

body.is-leaving .page,
body.is-leaving .img-hero,
body.is-leaving .site-footer {
  opacity: 0;
  transition: opacity 0.5s var(--ease-std);
}

/* ---------- Footer ---------- */

/* Full-bleed band so the oversized name can run edge to edge; the
   links and legal row are re-constrained to the page column. */
.site-footer {
  background: var(--surface);
  border-top: 1px solid var(--line);
  padding: 110px 0 120px;
  overflow: hidden;
}

.foot-inner {
  max-width: calc(var(--column) + 2 * var(--side));
  margin: 0 auto;
  padding: 0 var(--side);
}

.site-footer a { text-decoration: none; }

/* JS sizes the span so the name spans the viewport exactly; the vw
   value is the no-JS fallback. */
.foot-name {
  margin: 0 8px 36px;
  white-space: nowrap;
  line-height: 1;
  user-select: none;
}

.foot-name span {
  display: inline-block;
  font-size: 15vw;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: rgb(38, 38, 38);
  /* line-height: 1 clips the "y"/"g" descenders out of the box; pad
     them back in so the bottom margin is measured from the glyphs. */
  padding-bottom: 0.14em;
}

.foot-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.foot-pills a {
  display: inline-block;
  padding: 11px 18px;
  border-radius: 9999px;
  background: rgb(35, 35, 35);
  color: rgb(240, 240, 240);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.4;
  transition: background 0.2s ease;
}

.foot-pills a:hover { background: rgb(46, 46, 46); }

.foot-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 24px;
  margin-top: 52px;
  padding-top: 56px;
  border-top: 1px solid rgb(42, 42, 42);
  font-size: 16px;
  color: rgb(154, 154, 154);
}

.foot-legal {
  display: flex;
  gap: 18px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.foot-legal a {
  color: rgb(154, 154, 154);
  transition: color 0.2s ease;
}

.foot-legal a:hover { color: var(--text); }

/* ---------- Responsive ---------- */

/* Below this width each image in a pair would be under ~370px, too
   narrow for a one-line caption, so pairs stack into a single column. */
@media (max-width: 1100px) {
  .img-pair { grid-template-columns: 1fr; }
}

@media (max-width: 860px) {
  .intro-grid { grid-template-columns: 1fr; }

  /* Single column: drop the sticky list; the in-body headings remain. */
  .study { grid-template-columns: 1fr; }
  .study-nav { display: none; }

}

@media (max-width: 640px) {
  :root { --side: 20px; }
  .next-projects { grid-template-columns: 1fr; gap: 56px; padding: 64px 0 96px; }
  .next-project--next { grid-column: auto; }
  .page { padding-top: 56px; }
  .study-body section { padding-top: 88px; }
  .topnav-links { display: none; }
  .stats { grid-template-columns: 1fr; }
  /* Phones are too narrow for longer captions on one line: let them wrap
     inside a softer-cornered box instead of cutting them off. */
  .img-caption {
    white-space: normal;
    border-radius: 12px;
  }
  .site-footer { padding: 80px 0 72px; }
}

@media (max-width: 420px) {
  .meta { grid-template-columns: 1fr; gap: 40px; padding-bottom: 64px; }
}
