/* ============================================================
   KAMIL TOMASZEK — PORTFOLIO
   Brutalist-typographic system inspired by tokenomia.pro:
   oversized condensed headlines, hard black/white section flips,
   duotone imagery, numbered sections, letterspaced micro-labels,
   hairline vertical grid, corner crosses.
   ============================================================ */

:root {
  --ink: #0c0c0c;
  --paper: #e9e7e2;
  --paper-dim: #d9d6cf;
  --ink-soft: #171717;
  --grey: #8a877f;
  --grey-dark: #55534e;
  --accent: #ff3b00;
  /* hover gradient for display type — same family as the About background wash,
     lifted so the darkest stop still clears 3:1 on ink (worst stop 5.5:1) */
  --accent-gradient: linear-gradient(100deg, #ff5f1f 0%, #ff4aa8 34%, #bb5cf0 64%, #3d95f5 100%);
  --line-light: rgba(12, 12, 12, 0.12);
  --line-dark: rgba(233, 231, 226, 0.12);
  /* Section-scoped semantics: components reference these instead of the raw
     ink/paper values, so flipping a section is a single class change.
     Light is the default; .section--dark overrides the whole set. */
  --fg: var(--ink);
  --fg-strong: var(--ink);
  --fg-dim: var(--grey-dark);
  --line: var(--line-light);
  --surface: var(--paper);
  --surface-alt: var(--paper-dim);
  --font-display: "Anton", "Arial Narrow", sans-serif;
  --font-body: "Archivo", "Helvetica Neue", sans-serif;
  --font-mono: "JetBrains Mono", "Courier New", monospace;
  --pad: clamp(1.25rem, 4vw, 4rem);
  /* max grid width — same cap as tokenomia.pro: content and hairlines stop
     at 1920px and centre, while section backgrounds stay full-bleed.
     --bleed is the dead space outside the band on each side; percentages
     resolve against each section's own width, so the scrollbar is excluded. */
  --maxw: 1920px;
  --bleed: max(0px, (100% - var(--maxw)) / 2);
  --gutter: calc(var(--bleed) + var(--pad));
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }
/* while the JS inertial scroller is active it drives window.scrollTo itself,
   so the CSS smooth behaviour must not animate those calls as well */
html.smooth-scroll { scroll-behavior: auto; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

::selection { background: var(--ink); color: var(--paper); }
.section--dark ::selection { background: var(--paper); color: var(--ink); }

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }

/* Gradient-on-hover for the big display links. The gradient is always painted
   and clipped to the glyphs; the solid colour on top fades out on hover, which
   makes the swap animatable (you cannot transition into a background-clip).
   Scoped to :not(.split): split display type gets a different technique below. */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .grad-hover:not(.split) {
    background-image: var(--accent-gradient);
    background-size: 100% 100%;
    background-repeat: no-repeat;
    -webkit-background-clip: text;
    background-clip: text;
    transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .grad-hover:not(.split):hover,
  .grad-hover:not(.split):focus-visible { color: transparent; }

  /* Split display type (.contact-huge, .case-next .display): animating color's
     alpha on an element clipped by background-clip:text makes Chromium
     mis-rasterise the glyph mask mid-transition — at these sizes (Anton, up to
     18rem) a whole word would melt into a solid blob for a frame. main.js
     clones the letter layer into .grad-overlay so the gradient only ever
     fades via opacity, which is a plain compositor blend with no glyph
     re-rasterisation involved. */
  .grad-hover.split { position: relative; }
  .grad-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    background-image: var(--accent-gradient);
    background-size: 100% 100%;
    background-repeat: no-repeat;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .grad-hover.split:hover .grad-overlay,
  .grad-hover.split:focus-visible .grad-overlay { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .grad-overlay { transition: none; }
}

a:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 50%;
  transform: translate(-50%, -300%);
  z-index: 200;
  background: var(--ink);
  color: var(--paper);
  padding: 0.75rem 1.2rem;
  transition: transform 0.2s;
}
.skip-link:focus {
  transform: translate(-50%, 0);
}

/* ---------- primitives ---------- */

.mono {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.spaced { letter-spacing: 0.55em; }

.display {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  line-height: 0.88;
  letter-spacing: 0.01em;
}

/* hairline vertical grid running through every section */
.grid-lines {
  position: relative;
  /* An absolutely positioned pseudo-element paints ABOVE in-flow content, so
     the hairlines used to cut across photos and cards. They sit at -1 instead:
     above the section's own background, below everything in the section.
     `isolation` keeps that negative layer from escaping into the page. */
  isolation: isolate;
}
.grid-lines::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: 0;
  bottom: 0;
  left: var(--bleed);
  right: var(--bleed);
  pointer-events: none;
  background-image:
    linear-gradient(to right, var(--line-light) 1px, transparent 1px);
  background-size: calc(100% / 4) 100%;
  background-position: -1px 0;
}
.section--dark.grid-lines::before {
  background-image:
    linear-gradient(to right, var(--line-dark) 1px, transparent 1px);
}

/* corner crosses */
.cross {
  position: absolute;
  width: 13px;
  height: 13px;
  pointer-events: none;
  z-index: 2;
}
.cross::before, .cross::after {
  content: "";
  position: absolute;
  background: currentColor;
}
.cross::before { left: 6px; top: 0; width: 1px; height: 13px; }
.cross::after { top: 6px; left: 0; height: 1px; width: 13px; }
.cross--tl { top: 14px; left: calc(var(--bleed) + 14px); }
.cross--tr { top: 14px; right: calc(var(--bleed) + 14px); }
.cross--bl { bottom: 14px; left: calc(var(--bleed) + 14px); }
.cross--br { bottom: 14px; right: calc(var(--bleed) + 14px); }

/* section frame */
.section {
  position: relative;
  padding: clamp(4.5rem, 9vw, 8rem) var(--gutter);
}
.section--dark {
  background: var(--ink);
  color: var(--paper);
  --fg: var(--paper);
  --fg-strong: var(--paper-dim);
  --fg-dim: var(--grey);
  --line: var(--line-dark);
  --surface: var(--ink);
  --surface-alt: var(--ink-soft);
}
.section--dark .mono { color: var(--grey); }

/* numbered section label, top-left like tokenomia */
/* block (not flex) so the per-character split can lay the label out in
   normal inline flow — as a flex item every letter would get the gap */
.section-label {
  display: block;
  margin-bottom: clamp(2.5rem, 6vw, 5rem);
}
.section-label .dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  margin-right: 0.7rem;
  vertical-align: middle;
}

/* reveal animations — hidden start states are gated behind html.js,
   so content stays fully visible when JavaScript is unavailable.
   Distances mirror tokenomia.pro: 14px for blocks and small text,
   50px for display headings, opacity easing from 0.001. */
.reveal {
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.js .reveal {
  opacity: 0;
  transform: translateY(14px);
}
.js .reveal.is-in { opacity: 1; transform: none; }
.reveal-line { display: block; }

/* per-character reveal — tokenomia.pro's signature: letters rise and fade in
   one after another. Words stay atomic so line breaking is unaffected. */
.split .word { display: inline-block; white-space: nowrap; }
.js .split .char {
  display: inline-block;
  opacity: 0.001;
  transform: translateY(var(--rise, 14px));
  transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.65s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: var(--d, 0ms);
}
.js .split.is-in .char { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal, .split .char {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  html { scroll-behavior: auto; }
}

/* ============================================================
   HEADER
   ============================================================ */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 1.1rem var(--pad);
  mix-blend-mode: difference;
  color: #fff;
}
.brand {
  font-family: var(--font-display);
  font-size: 1.05rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
/* header is blended with `difference`, so any hue would invert on light
   sections — keep it strictly monochrome there */
.brand em { font-style: normal; }
.menu-toggle {
  justify-self: end;
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.35em;
  text-transform: uppercase;
  padding: 0.4rem 0.2rem;
}
.menu-toggle .plus {
  font-size: 0.9rem;
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.menu-toggle:hover .plus { transform: rotate(45deg); }
.header-note {
  justify-self: center;
  text-align: center;
  line-height: 1.7;
  /* the header is blended with `difference`, so opacity — not colour — is the
     only hover cue that stays legible over both light and dark sections */
  opacity: 0.75;
  transition: opacity 0.25s;
}
.header-note:hover,
.header-note:focus-visible { opacity: 1; }

@media (max-width: 760px) {
  .site-header { grid-template-columns: 1fr auto; }
  .header-note { display: none; }
}

/* fullscreen menu overlay */
.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--ink);
  color: var(--paper);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--pad);
  clip-path: inset(0 0 100% 0);
  visibility: hidden;
  transition: clip-path 0.7s cubic-bezier(0.83, 0, 0.17, 1),
              visibility 0s 0.7s;
}
.menu-overlay.is-open {
  clip-path: inset(0 0 0 0);
  visibility: visible;
  transition: clip-path 0.7s cubic-bezier(0.83, 0, 0.17, 1);
}
.menu-close {
  position: absolute;
  top: 1.1rem;
  left: 50%;
  transform: translateX(-50%);
  background: none;
  border: 0;
  color: var(--paper);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.35em;
  text-transform: uppercase;
  padding: 0.4rem;
}
.menu-nav { display: flex; flex-direction: column; gap: 0.3rem; }
.menu-nav a {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 9vw, 6.5rem);
  line-height: 1;
  text-transform: uppercase;
  display: flex;
  align-items: baseline;
  gap: 1.2rem;
  width: fit-content;
}

.menu-nav .idx {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  color: var(--grey);
}
.menu-foot {
  position: absolute;
  bottom: 1.4rem;
  left: var(--pad);
  right: var(--pad);
  display: flex;
  justify-content: space-between;
  color: var(--grey);
}

/* ============================================================
   HERO
   ============================================================ */

.hero {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0;
  background: var(--paper);
  overflow: hidden;
}
.hero-media {
  position: absolute;
  top: 0;
  /* full-bleed like tokenomia's hero image: it anchors to the viewport edge
     while the headline and hairlines stay inside the 1920px band */
  right: 0;
  width: min(40vw, 760px);
  height: 80%;
  /* box-sizing is border-box globally, so this pushes the portrait down from
     the top edge instead of growing the box */
  padding-top: 150px;
  opacity: 0.8;
  overflow: hidden;
}
.hero-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* anchor the crop and the scale to the top: the padding above narrows the box,
     and centred cropping cut the top of the head off */
  object-position: center top;
  transform-origin: top center;
  filter: grayscale(1) contrast(1.35) brightness(0.92);
  transform: scale(1.05);
}
/* bottom fade that melts the portrait into the paper background.
   The img is transformed (its own stacking layer), so this needs an explicit
   z-index to paint above it. */
.hero-media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(180deg, transparent 40%, var(--paper) 88%);
}
.hero-titles {
  position: relative;
  z-index: 3;
  padding: clamp(4.5rem, 8vh, 6rem) var(--gutter) clamp(2.5rem, 5vh, 4rem);
  width: 100%;
}
.hero-h1 {
  /* scaled so the hero pitch + CTAs stay above the fold on 900px-tall screens */
  font-size: clamp(4.2rem, 13vw, 13rem);
  mix-blend-mode: darken;
  text-align: left;
}
/* All three lines read from the left, but each starts at a different offset so
   the left edge stays ragged instead of forming a tidy block. Indents are in
   `em`, so they scale with the clamped headline instead of drifting.
   The longest line sits flush left — indenting it would push it under the
   photo on the right. */
.hero-h1 .reveal-line:nth-child(1) { padding-left: 0.5em; }
.hero-h1 .reveal-line:nth-child(2) { padding-left: 0; }
.hero-h1 .reveal-line:nth-child(3) { padding-left: 0.26em; }
.hero-sub {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
  margin-top: clamp(1.2rem, 2.5vh, 2rem);
}
.hero-pitch .who {
  max-width: 26rem;
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  font-weight: 500;
  line-height: 1.5;
}
.hero-pitch .industries {
  margin-top: 0.9rem;
  color: var(--fg-dim);
}
.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  justify-content: flex-end;
}
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.9rem 1.3rem;
  border: 1px solid var(--fg);
  transition: background 0.25s, color 0.25s, border-color 0.25s;
}
.btn--primary {
  background: var(--fg);
  color: var(--surface);
}
.btn--primary:hover { background: transparent; color: var(--fg); }
/* translucent so it reads lighter than --fg-dim; the hero is always light */
.btn--ghost { border-color: rgba(12, 12, 12, 0.35); }
.btn--ghost:hover { border-color: var(--fg); }

@media (max-width: 760px) {
  /* opacity inherited from the base rule (0.8); a 150px inset would eat most of
     the 46%-tall box on a phone, so the offset is scaled down here */
  .hero-media { width: 100%; height: 46%; padding-top: 60px; }
  .hero-h1 { font-size: clamp(3.4rem, 16vw, 6rem); }
  .hero-sub { flex-direction: column; align-items: flex-start; }
  .hero-ctas { justify-content: flex-start; }
}

/* ============================================================
   SERVICES (dark)
   ============================================================ */

.services-intro {
  max-width: 46rem;
  margin-left: auto;
  font-size: clamp(1.1rem, 1.9vw, 1.5rem);
  line-height: 1.6;
}
.services-intro .dim { color: var(--fg-dim); }
.services-intro strong { font-weight: 600; color: var(--fg); }

.service-list {
  list-style: none;
  margin-top: clamp(3rem, 7vw, 6rem);
  border-top: 1px solid var(--line);
}
.service-item {
  display: grid;
  grid-template-columns: 4rem 1fr 1.2fr;
  gap: 1.5rem;
  align-items: baseline;
  padding: 1.6rem 0;
  border-bottom: 1px solid var(--line);
  transition: background 0.3s, padding-left 0.3s;
}
.service-item:hover {
  background: var(--surface-alt);
  padding-left: 1rem;
}
.service-item .num { color: var(--fg-dim); }
.service-item h3 {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(1.5rem, 3.4vw, 2.6rem);
  line-height: 1;
}
.service-item p { color: var(--fg-dim); max-width: 34rem; }

.process-block { margin-top: clamp(4rem, 8vw, 7rem); }
.process-kicker { color: var(--fg-dim); margin-bottom: 1.4rem; }
/* Pulled out to the full band width so the four columns land exactly on the
   background hairlines (25% / 50% / 75%). The section pads by --bleed + --pad,
   so cancelling --pad reaches the band edge at any viewport size. */
.process {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  margin-left: calc(var(--pad) * -1);
  margin-right: calc(var(--pad) * -1);
  border-bottom: 1px solid var(--line);
}
/* no vertical rules and no fill: the section's own grid lines do the dividing,
   which is what keeps the block locked to the grid */
.process-step {
  border-top: 1px solid var(--line);
  padding: clamp(1.5rem, 2.4vw, 2.1rem) 1.2rem clamp(2rem, 3vw, 2.8rem) var(--pad);
}
.process-step .mono { display: block; margin-bottom: clamp(2rem, 3.4vw, 3.2rem); color: var(--fg-dim); }
.process-step p {
  margin-top: 1rem;
  color: var(--fg-dim);
  font-size: 0.92rem;
  line-height: 1.55;
  max-width: 22rem;
}
.process-step h4 {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(1.05rem, 1.5vw, 1.45rem);
  line-height: 1.05;
  letter-spacing: 0.04em;
  color: var(--fg-strong);
}
.tools-line {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  color: var(--fg-dim);
  text-align: center;
  line-height: 2.2;
}

@media (max-width: 900px) {
  .process { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 760px) {
  .service-item { grid-template-columns: 2.4rem 1fr; }
  .service-item p { grid-column: 2; }
}

/* ============================================================
   WORK (light)
   ============================================================ */

/* Reused as-is for the Experience heading — a shared "big section title"
   treatment, not exclusive to Work. */
.work-head {
  font-size: clamp(3rem, 9vw, 9rem);
  margin-bottom: clamp(3rem, 6vw, 5rem);
}
.work-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1.5rem, 3vw, 3rem);
}
.work-card {
  position: relative;
  display: grid;
  grid-template-areas: "media" "info";
}
.work-card .frame { grid-area: media; }
.work-info { grid-area: info; }
.work-card:nth-child(odd) { transform: translateY(clamp(0rem, 3vw, 3.5rem)); }
.work-card .frame {
  position: relative;
  overflow: hidden;
  background: var(--ink);   /* covers are dark artwork in both themes */
  aspect-ratio: 4 / 3;
}
.work-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.15);
  transform: scale(1.001);
  transition: filter 0.5s ease, transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.work-card:hover img {
  filter: grayscale(0) contrast(1);
  transform: scale(1.045);
}
.work-card .frame::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--line);
  pointer-events: none;
}
.work-info { padding-top: 0.9rem; }
.work-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}
.work-meta .view {
  color: var(--fg-dim);
  white-space: nowrap;
  transition: color 0.25s;
}
.work-card:hover .view,
.work-card:focus-visible .view { color: var(--fg); }
.work-desc {
  margin-top: 0.4rem;
  font-size: 0.95rem;
  max-width: 30rem;
}
.work-card .tags { display: block; margin-top: 0.4rem; color: var(--fg-dim); }
.work-card--static { cursor: default; }
.work-card--static:hover img {
  filter: grayscale(1) contrast(1.15);
  transform: scale(1.001);
}
.work-card--static .frame { opacity: 0.85; }
.work-meta .coming { color: var(--fg-dim); white-space: nowrap; }
.work-meta h3 {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(1.3rem, 2.6vw, 2rem);
  line-height: 1;
}
.work-meta .tags { color: var(--fg-dim); white-space: nowrap; }

/* Hover-capable pointers: collapse the card to a single grid area so the
   caption lies on the artwork and reveals on hover/focus. Touch devices keep
   the caption below the image permanently — nothing is hidden behind a
   gesture they cannot perform. */
@media (hover: hover) {
  .work-card { grid-template-areas: "media"; }
  .work-info {
    grid-area: media;
    align-self: end;
    z-index: 2;
    padding: clamp(1.1rem, 2vw, 1.7rem);
    /* extra head room so the gradient's fade-out lands above the text, not on it */
    padding-top: clamp(2.6rem, 5vw, 4rem);
    /* the scrim is always dark, so these colours are fixed rather than themed.
       It stays near-opaque across the whole text area — Smyk's cover is a light
       photo, and a softer scrim dropped the title to ~1.9:1 contrast there. */
    color: var(--paper);
    background: linear-gradient(
      to top,
      rgba(12, 12, 12, 0.96) 0%,
      rgba(12, 12, 12, 0.92) 65%,
      rgba(12, 12, 12, 0.62) 88%,
      rgba(12, 12, 12, 0) 100%
    );
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1),
                transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .work-card:hover .work-info,
  .work-card:focus-visible .work-info { opacity: 1; transform: none; }

  /* a light stagger, same easing language as the rest of the page */
  .work-info > * {
    transform: translateY(10px);
    transition: transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .work-info > *:nth-child(2) { transition-delay: 0.05s; }
  .work-info > *:nth-child(3) { transition-delay: 0.1s; }
  .work-card:hover .work-info > *,
  .work-card:focus-visible .work-info > * { transform: none; }

  /* on the scrim the themed text colours would be dark-on-dark */
  .work-info .work-desc { color: var(--paper); }
  .work-info .view,
  .work-info .tags,
  .work-info .coming { color: rgba(233, 231, 226, 0.72); }
  .work-card:hover .work-info .view { color: var(--paper); }
}

@media (prefers-reduced-motion: reduce) {
  .work-info, .work-info > * { transition: none !important; }
}

@media (max-width: 760px) {
  .work-grid { grid-template-columns: 1fr; }
  .work-card:nth-child(odd) { transform: none; }
}

/* ============================================================
   EXPERIENCE (dark)
   ============================================================ */

.xp-table { border-top: 1px solid var(--line); }
.xp-row {
  display: grid;
  grid-template-columns: 3.5rem 1.4fr 1fr auto;
  column-gap: 1.2rem;
  row-gap: 0;
  align-items: baseline;
  padding: 1.6rem 0;
  border-bottom: 1px solid var(--line);
  transition: background 0.25s;
}
/* no padding-left shift on hover any more: it recomputed the column widths and
   nudged the role and dates sideways. Background alone carries the hover. */
.xp-row:hover { background: var(--surface-alt); }
.xp-row .num { color: var(--fg-dim); }
.xp-row .org {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(1.05rem, 2.4vw, 1.7rem);
  line-height: 1.05;
}
.xp-row .role { color: var(--fg-dim); }
.xp-row .dates { color: var(--fg-dim); text-align: right; }
/* Industry line: its space is reserved at all times, so revealing it cannot
   change the row height. Only opacity animates, plus a small translate — a
   transform never triggers reflow. */
.xp-row .desc {
  grid-column: 2 / -1;
  color: var(--fg-dim);
  margin-top: 0.55rem;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.3s ease,
              transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.xp-row:hover .desc,
.xp-row:focus-within .desc { opacity: 1; transform: none; }
/* achievement line — brighter than .desc so results read before context */
.xp-row .result {
  grid-column: 2 / -1;
  margin-top: 0.5rem;
  color: var(--fg-strong);
  max-width: 52rem;
  line-height: 1.8;
}
.xp-row .result::before {
  content: "\2197";
  color: var(--accent);
  margin-right: 0.6rem;
}
.xp-row--compact { padding: 1.15rem 0; }
.xp-row--compact .org { font-size: clamp(0.95rem, 1.8vw, 1.2rem); }
.xp-row--current .org::after {
  content: "NOW";
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.2em;
  color: var(--accent);
  vertical-align: super;
  margin-left: 0.6rem;
}

@media (hover: none) {
  .xp-row .desc { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .xp-row .desc { transition: opacity 0.2s ease; transform: none; }
}

@media (max-width: 760px) {
  .xp-row { grid-template-columns: 2.2rem 1fr; }
  .xp-row .role, .xp-row .dates { grid-column: 2; text-align: left; }
  /* stacked layout needs the row gap back */
  .xp-row { row-gap: 0.75rem; }
}

/* ============================================================
   CLIENTS marquee (light)
   ============================================================ */

/* logo strip sits flush under the hero — the marquee's own rules carry the band */
.clients { padding: 0 var(--bleed); }
.marquee {
  overflow: hidden;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 2.2rem 0;
}
.marquee-track {
  display: flex;
  align-items: center;
  gap: clamp(3rem, 7vw, 6rem);
  width: max-content;
  animation: marquee 30s linear infinite;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-track img {
  height: clamp(1.4rem, 2.6vw, 2.2rem);
  width: auto;
}
@keyframes marquee {
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; flex-wrap: wrap; width: auto; }
}

/* ============================================================
   ABOUT (light)
   ============================================================ */

.about-grid {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: clamp(2rem, 6vw, 6rem);
  align-items: start;
}
.about-copy .lede {
  font-size: clamp(1.35rem, 2.6vw, 2.1rem);
  line-height: 1.35;
  font-weight: 500;
  margin-bottom: 1.6rem;
}
.about-copy p + p { margin-top: 1rem; }
/* a touch darker than --fg-dim: this paragraph sits on the colour wash,
   where the token value measured only 3.75:1 */
.about-copy .dim { color: #423f3a; }
.about-photos {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.9rem;
}
.about-photos figure {
  overflow: hidden;
  aspect-ratio: 3 / 4;
  background: var(--ink);
}
.about-photos figure:nth-child(2) { transform: translateY(1.6rem); }
.about-photos figure:nth-child(3) { transform: translateY(-0.4rem); }
.about-photos img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.2);
  transition: filter 0.5s;
}
.about-photos figure:hover img { filter: grayscale(0) contrast(1); }

/* ---- Soft colour wash behind the section, in the spirit of the gradient on
   tokenomia.pro's contact block. Theirs is a baked PNG; this is layered radial
   gradients under a heavy blur, so no asset is needed and it scales freely.
   z-index -1 keeps it above the section background but below all content, and
   `isolation` stops that negative layer escaping the section. ---- */
#about { isolation: isolate; overflow: hidden; }
#about::after {
  content: "";
  position: absolute;
  z-index: -2;   /* under the hairlines, which are at -1 */
  /* weighted towards the photo column: the copy sits on the left and the wash
     would otherwise eat its contrast (measured 4.4:1 on the dim paragraph) */
  left: 58%;
  top: clamp(0rem, 3vw, 3rem);
  width: min(135%, 1750px);
  aspect-ratio: 16 / 10;
  transform: translateX(-50%);
  pointer-events: none;
  filter: blur(80px) saturate(1.25);
  opacity: 0.72;
  /* hot cores sit right of the copy column, so only their tails reach the text */
  background:
    radial-gradient(34% 42% at 40% 24%, rgba(255, 74, 12, 0.62), transparent 70%),
    radial-gradient(30% 38% at 26% 68%, rgba(176, 42, 224, 0.56), transparent 72%),
    radial-gradient(40% 44% at 62% 72%, rgba(22, 110, 232, 0.52), transparent 74%),
    radial-gradient(32% 38% at 88% 46%, rgba(214, 58, 186, 0.46), transparent 72%),
    radial-gradient(26% 30% at 74% 16%, rgba(255, 138, 40, 0.4), transparent 70%);
}
/* the hairline grid reads over the wash because the wash sits at -2 and the
   grid at -1 — both below the copy */

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

/* ============================================================
   CONTACT + FOOTER (dark)
   ============================================================ */

.contact {
  min-height: 92svh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.contact-huge {
  font-size: clamp(4rem, 17vw, 18rem);
  display: block;
  width: fit-content;
  margin-top: auto;
}

.contact-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1.1rem 2.4rem;
  margin-top: 1.8rem;
}
.contact-links .contact-mail { margin-top: 0; }
.contact-mail {
  display: inline-block;
  margin-top: 1.8rem;
  border-bottom: 1px solid var(--fg-dim);
  padding-bottom: 0.35rem;
  color: var(--fg-dim);
  transition: color 0.25s, border-color 0.25s;
}
.contact-mail:hover { color: var(--fg); border-color: var(--fg); }

.site-footer {
  margin-top: clamp(4rem, 8vw, 7rem);
  padding-top: 1.4rem;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  color: var(--fg-dim);
}
.footer-nav { display: flex; gap: 1.8rem; }
.footer-nav a { transition: color 0.25s; }
.footer-nav a:hover { color: var(--fg); }
.back-top { cursor: pointer; background: none; border: 0; color: var(--fg-dim); }
.back-top:hover { color: var(--fg); }

/* ============================================================
   PROJECT PAGES
   ============================================================ */

.case-hero {
  padding-top: clamp(7rem, 14vw, 11rem);
  padding-bottom: 0;
}
.case-hero h1 { font-size: clamp(3rem, 11vw, 11rem); }
/* Same treatment as .process on the home page: pulled out to the full band
   width so the four columns land exactly on the background hairlines
   (25% / 50% / 75%). The section pads by --bleed + --pad, so cancelling --pad
   reaches the band edge at any viewport. Before this the table sat inside the
   content box, which put its dividers --pad/2 off the grid on every case page —
   only the middle one ever matched. */
.case-meta {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  margin: clamp(2rem, 5vw, 4rem) calc(var(--pad) * -1);
  border-bottom: 1px solid var(--line);
}
/* no vertical rules and no fill: the section's own grid lines do the dividing,
   which is what keeps the table locked to the grid. border-top per cell also
   draws the row divider once this wraps to two columns. */
.case-meta > div {
  border-top: 1px solid var(--line);
  padding: 1.1rem 1.2rem 1.5rem var(--pad);
}
.case-meta .mono { display: block; color: var(--fg-dim); margin-bottom: 0.7rem; }
.case-meta .val { font-weight: 500; }
.case-meta a.val { border-bottom: 1px solid currentColor; }

@media (max-width: 900px) {
  .case-meta { grid-template-columns: repeat(2, 1fr); }
}

.case-about {
  max-width: 44rem;
  font-size: clamp(1.05rem, 1.7vw, 1.35rem);
  line-height: 1.6;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}
/* Photo Website case only: every text block on the page shares one 640px measure */
.case--gp-hero .case-about { max-width: 720px; }
.case-about .mono { display: block; color: var(--fg-dim); margin-bottom: 0.8rem; }
.case-about + .case-about { margin-top: -1rem; }

/* the slices are one continuous page cut into pieces, so they butt up
   against each other: no grid gap, block images (no inline baseline gap),
   and the frame drawn once around the strip rather than around every slice */
.case-gallery { display: grid; gap: 0; padding-top: 0; }
.case-gallery img {
  display: block;
  width: 100%;
  /* the width/height attributes are on the tags, so the browser reserves the
     exact box before a lazy slice loads — otherwise the document grows by tens
     of thousands of pixels mid-scroll and legend jumps land off target */
  height: auto;
  border: 1px solid var(--line);
}
.case-gallery img { border-top: 0; border-bottom: 0; }
.case-gallery figure:first-child img { border-top: 1px solid var(--line); }
.case-gallery figure:last-child img { border-bottom: 1px solid var(--line); }

/* Scroll-scrubbed cover (TPRO Token Panel): the same "head assembling out
   of particles" sequence panel.tpro.pro plays. The figure becomes a 250vh
   runway with the canvas stuck under the header, and the scroll position
   through that runway picks the frame. Without JS, on narrow screens or
   under reduced motion the static cover image stays. */
.face-scrub-stage { position: relative; }
.face-scrub-stage canvas {
  display: none;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  background: #000;
  border: 1px solid var(--line);
  border-bottom: 0;
}
.face-scrub.is-scrub { height: 250vh; background: #000; } /* the runway reads as one dark stage, like the original's pinned section */
/* --face-top is set by the script: the canvas sits vertically centred in
   the viewport (as the original's pinned section does), never under the header */
.face-scrub.is-scrub .face-scrub-stage { position: sticky; top: var(--face-top, 4.25rem); }
.face-scrub.is-scrub .face-scrub-stage canvas { display: block; }
.face-scrub.is-scrub img { display: none; }

.case-next {
  display: block;
  padding: clamp(4rem, 9vw, 7rem) var(--gutter);
  background: var(--ink);
  color: var(--paper);
  position: relative;
}
.case-next .mono { color: var(--grey); display: block; margin-bottom: 1.2rem; }
.case-next .display {
  font-size: clamp(2.8rem, 10vw, 9rem);
  /* colour listed here too: this selector outranks .grad-hover */
  transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  display: inline-block;
}
.case-next:hover .display { transform: translateX(1.5rem); }

/* Gallery slices get a real caption each (screen-reader only — sighted readers
   get the same names from the legend rail, so showing both would just repeat). */
/* anchor targets for the legend rail clear the fixed header the same way,
   whether they are gallery slices or story blocks */
.case-story [data-legend] { scroll-margin-top: 5.5rem; }

.case-gallery figure {
  margin: 0;
  /* the fixed header measures ~67px; anchor jumps read this value back
     (see js/main.js) so a clicked slice lands clear of it, not underneath */
  scroll-margin-top: 5.5rem;
}

/* ------------------------------------------------------------------
   Legend rail — the section index that follows the gallery.
   fixed, not sticky: body has overflow-x: hidden, which turns body into
   the scrollport and stops a sticky child from ever unsticking.
   z-index sits in the free 4-49 band (header 50, menu 100, skip-link 200).
   ------------------------------------------------------------------ */
.case-legend {
  position: fixed;
  top: 50%;
  right: var(--pad);
  z-index: 40;
  background: var(--surface);
  border: 1px solid var(--line);
  /* opaque on purpose: over the dark screenshots a translucent panel drags
     the dimmed entries below 4.5:1. On --surface they measure 7.0:1 and
     15.9:1 (dim and active). */
  opacity: 0;
  visibility: hidden;
  transform: translate(0.6rem, -50%);
  transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.5s cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0.5s;
}
.case-legend.is-on {
  opacity: 1;
  visibility: visible;
  transform: translate(0, -50%);
}
.case-legend ol {
  list-style: none;
  margin: 0;
  padding: 0.7rem 0;
}
.case-legend a {
  display: grid;
  grid-template-columns: 0.55rem auto 1fr;
  align-items: center;
  gap: 0.6rem;
  padding: 0.42rem 1rem 0.42rem 0.8rem;
  color: var(--fg-dim);
  transition: color 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.case-legend .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0;
  transition: opacity 0.25s;
}
.case-legend .idx {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  color: var(--fg-dim);
}
.case-legend .name {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}
.case-legend a:hover { color: var(--fg); }
.case-legend a[aria-current="true"] { color: var(--fg); font-weight: 500; }
.case-legend a[aria-current="true"] .dot { opacity: 1; }
.case-legend a[aria-current="true"] .idx { color: var(--fg); }

.case-legend-nav {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.8rem;
  border-top: 1px solid var(--line);
}
.case-legend-nav button {
  cursor: pointer;
  background: none;
  border: 0;
  padding: 0.1rem 0.3rem;
  font-size: 0.8rem;
  line-height: 1;
  color: var(--fg-dim);
  transition: color 0.25s;
}
.case-legend-nav button:hover:not(:disabled) { color: var(--fg); }
.case-legend-nav button:disabled { opacity: 0.35; cursor: default; }
.case-legend-count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  color: var(--fg-dim);
}

/* narrow viewports: keep the rail but drop the names, so it stops covering
   the screenshots (which run the full width of the band) */
@media (max-width: 1100px) {
  .case-legend .name { display: none; }
  .case-legend a { grid-template-columns: 0.55rem auto; padding-right: 0.8rem; }
  /* the counter is what keeps the panel wide once the names are gone, and the
     highlighted numeral already says where you are */
  .case-legend-count { display: none; }
  .case-legend-nav { justify-content: center; padding-inline: 0.4rem; }
}
/* phones: the rail would eat the frame, and there is no hover to explain it */
@media (max-width: 560px) {
  .case-legend { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .case-legend {
    transform: translateY(-50%);
    transition: none;
  }
  .case-legend.is-on { transform: translateY(-50%); }
  .case-legend a, .case-legend .dot, .case-legend-nav button { transition: none; }
}

/* ------------------------------------------------------------------
   Narrative case study (GoPhoto): text-led sections instead of a
   screenshot gallery. Same tokens and hairline grid as everything else;
   copy sits on the same measure as .case-about.
   ------------------------------------------------------------------ */
/* keeps .section's own top padding: this section changes background colour,
   so it needs the normal section rhythm rather than the gallery's flush top */
.story {
  max-width: 720px;
  margin-bottom: clamp(3.5rem, 7vw, 6rem);
}
.story-kicker {
  display: block;
  color: var(--fg-dim);
  margin-bottom: 1.1rem;
}
.story h3 {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  line-height: 0.95;
  letter-spacing: 0.01em;
  font-size: clamp(1.7rem, 3.6vw, 3rem);
  margin-bottom: 1.4rem;
  color: var(--fg-strong);
}
.story p {
  font-size: clamp(1.02rem, 1.5vw, 1.25rem);
  line-height: 1.65;
}
.story p + p { margin-top: 1.05rem; }

/* the one number that decided the project */
.story-stat { margin-top: 2.2rem; }
.story-stat .display { font-size: clamp(3.2rem, 9vw, 7.5rem); display: block; }
.story-stat .mono { display: block; color: var(--fg-dim); margin-top: 0.7rem; }
/* p + p spacing does not apply across the stat block, so give it its own */
.story-stat + p { margin-top: 2.2rem; }

/* belief block — small accents stay flat --accent (gradient is for hovers) */
.story-quote {
  border-left: 2px solid var(--accent);
  padding-left: clamp(1.2rem, 2.5vw, 1.8rem);
}

/* "never shipped" marker */
.story-chip {
  display: inline-block;
  border: 1px solid var(--line);
  padding: 0.3rem 0.7rem;
  color: var(--fg-dim);
  margin-bottom: 1.2rem;
}

.story-figure { margin: 0 0 clamp(3.5rem, 7vw, 6rem); }
.story-figure img {
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: 12px;
}
/* full-width band with a scroll parallax: a tall portrait source cropped
   to a cinematic strip, moving slower than the page so it reads with depth
   rather than as a flat banner. The 130% inner height is the travel budget
   the JS shifts within — see the .parallax comment in main.js. */
.story-figure--parallax {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 12px;
}
.story-figure--parallax .parallax {
  position: absolute;
  inset: -30% 0;
  height: 160%;
}
.story-figure--parallax img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
  display: block;
}
@media (prefers-reduced-motion: reduce) {
  .story-figure--parallax .parallax { inset: 0; height: 100%; transform: none !important; }
}
/* the full-width band (the swing photo under "what can be said") */
.story-figure--parallax-wide { aspect-ratio: 16 / 9; }
.story-figure--parallax-wide img { object-position: 50% 30%; }
/* the side column photo (the QR card handoff) — a near-square source, so a
   4:3 frame leaves enough spare height for the parallax to actually move */
.story-figure--parallax-side { aspect-ratio: 4 / 3; }
.story-figure--parallax-side img { object-position: 50% 35%; }
/* the sunset panorama — wide already, with the rider and the crowd right at
   its edges, so cropping the sides is off the table. 2.4:1 sits right at the
   width-driven/height-driven boundary for this source: cover fills the
   width with no horizontal loss, and the spare vertical budget below
   comes out of sky and water that were already in frame, not a crop. A
   smaller travel budget than the other two bands, because a 900x450
   source simply has less spare height to give. */
.story-figure--parallax-edge {
  aspect-ratio: 2.4 / 1;
}
.story-figure--parallax-edge .parallax {
  inset: -10% 0;
  height: 120%;
}

/* a lone phone screen must not blow up to band width */
.story-figure--phone img { width: min(23rem, 100%); }
/* the rejected-slideshow sketch already has its own frame drawn in the file */
.story-figure--flat img { border: 0; border-radius: 12px; }
.story-figure figcaption { margin-top: 0.85rem; color: var(--fg-dim); }

/* four phone screens side by side — one row on the desktop band, folding
   to 2 then 1 column so a 375pt screen never has to render at 80px wide */
.pw-screens {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(0.9rem, 2vw, 1.8rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.pw-screens > li { min-width: 0; }

/* pointer-tracked 3D tilt, the way the upshot.cards hero card behaves:
   rotateY runs +/-6deg across the width, rotateX +/-5deg across the height,
   with a glare tied to the cursor and a foil sheen over it. Hover only —
   the effect has no meaning without a fine pointer, and it is switched
   off entirely under reduced motion. */
.pw-screens { perspective: 1400px; }
.pw-card {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: 12px;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.pw-card.is-live {
  transition: transform 0.18s ease-out, box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1);
  box-shadow: 0 1.3rem 2.8rem -1rem rgba(33, 37, 76, 0.35);
  will-change: transform;
}
.pw-card img { display: block; }
@media (prefers-reduced-motion: reduce) {
  .pw-card, .pw-card.is-live { transition: none; transform: none; box-shadow: none; }
}
.pw-screens .mono {
  display: block;
  margin-top: 0.8rem;
  color: var(--fg-dim);
  font-size: 0.82rem;
}
@media (max-width: 900px) { .pw-screens { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .pw-screens { grid-template-columns: 1fr; } }
@media (max-width: 480px) { .pw-screens > li { max-width: 20rem; } }

/* extra breathing room around "the company" chapter (globe + copy): more
   space below the four screens above it, and more before the section hands
   off to A'DAM Lookout's dark photo band below */
.story-figure--screens { margin-bottom: clamp(9rem, 16vw, 16rem); }
.story-split:has(#gp-02) { margin-bottom: clamp(9rem, 16vw, 16rem); }

/* same treatment where the page opens: more clearance below the navy hero
   before "fewer people in, more money out" starts, and more room before the
   four screens that follow it */
.case--gp:has(#gp-01) { padding-top: clamp(9rem, 16vw, 16rem); }
.story-split:has(#gp-01) { margin-bottom: clamp(8rem, 14vw, 16rem); }

/* "how the visitor gets to the site" chapter: 250px more clearance below
   the A'DAM Lookout photo gallery band above it */
.case--gp:has(#gp-04) { padding-top: calc(clamp(4.5rem, 9vw, 8rem) + 250px); }
.story-split:has(#gp-04) { margin-bottom: clamp(9rem, 16vw, 16rem); }

/* "what I believed" chapter, with the before/after slider: clearance above
   and below the block itself */
.story-split:has(#gp-08) { padding-top: 150px; padding-bottom: 250px; }

/* "what I rejected" chapter: clearance below before the result panel */
.story-split:has(#gp-09) { padding-bottom: 200px; }

/* ------------------------------------------------------------------
   Before / after reveal. Both photographs are the same frame at the same
   size, stacked; the top one is clipped at --pos and a horizontal handle
   drags that line up and down. Driven by a custom property so the drag
   only ever writes one value and the browser does the compositing.
   ------------------------------------------------------------------ */
.cmp {
  --pos: 50%;
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 12px;
  touch-action: pan-y;          /* a vertical page scroll still gets through */
  cursor: ns-resize;
  user-select: none;
  -webkit-user-select: none;
}
.cmp img {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;         /* the drag belongs to the container */
  -webkit-user-drag: none;
}
/* the revealed photograph sits in flow and gives the box its height */
.cmp-under { position: relative; z-index: 0; }
/* the covering one is pinned over it and clipped to everything above --pos */
.cmp-over {
  position: absolute;
  inset: 0;
  z-index: 1;
  clip-path: inset(0 0 calc(100% - var(--pos)) 0);
}
.cmp-handle {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--pos);
  z-index: 2;
  height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
/* the line itself, drawn either side of the grip */
.cmp-handle::before,
.cmp-handle::after {
  content: "";
  position: absolute;
  top: 0;
  height: 2px;
  background: #ffffff;
  box-shadow: 0 0 0 1px rgba(33, 37, 76, 0.28);
}
.cmp-handle::before { left: 0; right: 50%; margin-right: 1.5rem; }
.cmp-handle::after  { right: 0; left: 50%; margin-left: 1.5rem; }
.cmp-grip {
  position: relative;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 2px 10px rgba(33, 37, 76, 0.35);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.28rem;
}
/* two chevrons, so the grip reads as "drag me up or down" */
.cmp-arrow {
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--gp-navy);
  border-bottom: 2px solid var(--gp-navy);
}
.cmp-arrow--up { transform: rotate(-135deg); margin-bottom: -0.12rem; }
.cmp-arrow--down { transform: rotate(45deg); margin-top: -0.12rem; }

.cmp:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.cmp.is-dragging { cursor: grabbing; }

.story-credits {
  max-width: 720px;
  border-top: 1px solid var(--line);
  padding-top: 1.3rem;
  color: var(--fg-dim);
  font-size: clamp(0.95rem, 1.3vw, 1.1rem);
  line-height: 1.6;
}

/* ------------------------------------------------------------------
   GoPhoto brand skin — scoped to the Photo Website case study only.
   Palette lifted from GoPhoto's own deck: navy #21254c, blue #057eb5,
   deep blue #105e82, pale blue #a3d7ef / #e8f5fb, cool grey #eaecf0.
   Applied the same way .section--dark works: by overriding the
   semantic tokens, so every component follows without being touched.
   Measured on the actual surfaces — brand blue is 3.8:1, so it carries
   the big stat and the rules but never small text; small brand text
   uses --gp-blue-deep (6.0:1) and body uses --gp-navy (12.4:1).
   ------------------------------------------------------------------ */
.case--gp,
.case--gp-hero,
.case--gp-feature,
.gp-band {
  --gp-navy: #21254c;
  --gp-blue: #057eb5;
  --gp-blue-deep: #105e82;
  --gp-blue-pale: #a3d7ef;
  --gp-tint: #e8f5fb;
  --gp-grey: #eaecf0;
}

/* hero: the deck's cover — full navy, paper type.
   It keeps .section--dark, so the hairline grid and every token flip
   come for free; only the surface colour and the dim tone change. */
.case--gp-hero {
  background: var(--gp-navy);
  --fg-strong: #ffffff;
  --fg-dim: var(--gp-blue-pale);
}

/* story: the deck's content pages — cool grey instead of warm paper.
   --line-light is redefined here because .grid-lines::before reads that
   raw token directly, not --line. */
.case--gp {
  background: var(--gp-grey);
  /* set explicitly, the way .section--dark does: the tokens alone do not
     colour inherited body copy, which would stay ink on a navy-tinted page */
  color: var(--gp-navy);
  --fg: var(--gp-navy);
  --fg-strong: var(--gp-navy);
  --fg-dim: #51547a;
  --line: rgba(33, 37, 76, 0.16);
  --line-light: rgba(33, 37, 76, 0.14);
  --surface: var(--gp-grey);
}

.case--gp .story-kicker { color: var(--gp-blue-deep); }

/* the deck's signature: a short brand rule sitting under each heading */
.case--gp .story h3::after {
  content: "";
  display: block;
  width: 2rem;
  height: 3px;
  margin-top: 1rem;
  background: var(--gp-blue);
}

.case--gp .story-stat .display { color: var(--gp-blue); }

.case--gp .story-chip {
  background: var(--gp-tint);
  border-color: var(--gp-blue-pale);
  color: var(--gp-blue-deep);
}


/* The hero's last child has a bottom margin and .case-hero pads 0 at the
   bottom, so that margin collapses out of the section. Harmless when both
   sections are the same colour — here it punched a 64px band of body paper
   between the navy hero and the grey story. Move the space inside. */
.case--gp-hero { padding-bottom: clamp(2.5rem, 5vw, 4rem); }
.case--gp-hero > *:last-child { margin-bottom: 0; }

/* Copy beside a full-bleed-ish image. Equal columns with no gap, because the
   content box is symmetric inside the band — so the second column starts
   exactly on the 50% hairline and the image edge lands on the grid line.
   The text column carries its own right padding instead of a grid gap, which
   keeps that alignment while still separating the two. */
.story-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
  margin-bottom: clamp(3.5rem, 7vw, 6rem);
}
/* grid items default to min-width:auto, so a wide child (the device frame)
   would stretch its track and push the whole page sideways instead of
   scrolling inside its own box */
.story-split > * { min-width: 0; }
/* wider than the standalone .story measure (44rem): in a two-column split
   the text has a whole grid track to itself, and 44rem was leaving ~192px
   of the column empty on the right before its own padding even started */
.story-split .story { max-width: 720px; margin-bottom: 0; padding-right: var(--pad); }
.story-figure--side { margin-bottom: 0; }
.story-figure--side img { width: 100%; }

/* Mirrored split: visual is image-left / copy-right, but the DOM keeps the
   copy first so a screen reader still meets the heading before the figure. */
.story-split--flip > .story {
  grid-column: 2;
  grid-row: 1;
  padding-right: 0;
  padding-left: var(--pad);
}
.story-split--flip > .gp-globe-wrap,
.story-split--flip > .story-figure {
  grid-column: 1;
  grid-row: 1;
}

@media (max-width: 900px) {
  .story-split { grid-template-columns: 1fr; }
  .story-split .story {
    padding-right: 0;
    margin-bottom: clamp(2rem, 4vw, 3rem);
  }
  /* single column: drop the explicit placement so they stack in DOM order */
  .story-split--flip > .story,
  .story-split--flip > .gp-globe-wrap,
  .story-split--flip > .story-figure {
    grid-column: auto;
    grid-row: auto;
  }
  .story-split--flip > .story { padding-left: 0; }
}

/* ------------------------------------------------------------------
   Results panel — the deck's "stat card + labelled notes" slide.
   Gold is the deck's accent for this kind of slide; it measures 8.8:1
   on the navy card but only 2.2:1 on the light grey, so it appears
   inside the card and as the closing rule, never as light-background
   text. Those labels keep the page's deep blue (6.0:1).
   ------------------------------------------------------------------ */
.result-panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}

.result-card {
  max-width: 26rem;
  margin-right: var(--pad);
  background: var(--gp-navy);
  color: var(--paper);
  padding: clamp(1.6rem, 3vw, 2.2rem);
}
.result-label {
  display: block;
  color: #f9bf14;
  margin-bottom: 1rem;
}
.result-figure {
  display: block;
  font-size: clamp(2.8rem, 6.5vw, 4.6rem);
  color: #ffffff;
  line-height: 1;
}
.result-list {
  margin-top: clamp(1.6rem, 3vw, 2.2rem);
  border-top: 1px solid rgba(233, 231, 226, 0.22);
}
.result-list > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid rgba(233, 231, 226, 0.22);
}
.result-list > div:last-child { border-bottom: 0; padding-bottom: 0; }
.result-list dt { color: var(--gp-blue-pale); }
.result-list dd {
  font-family: var(--font-mono);
  font-weight: 500;
  white-space: nowrap;
  color: #ffffff;
}
.result-list dd.down { color: var(--gp-blue-pale); }

.result-notes { display: grid; gap: clamp(1.4rem, 2.6vw, 2rem); max-width: 720px; }
.result-note {
  border-left: 4px solid var(--gp-blue);
  border-radius: 0 12px 12px 0;
  background: var(--gp-tint);
  padding: clamp(1rem, 2vw, 1.4rem) clamp(1.2rem, 2.4vw, 1.6rem);
}
.result-note > .mono {
  display: block;
  color: var(--gp-blue-deep);
  font-weight: 500;
  font-size: 0.8125rem;
  margin-bottom: 0.6rem;
}
.result-note p { font-size: 1.1rem; font-weight: 500; line-height: 1.6; }

/* the deck closes the slide with an accent rule, a bold line and a paragraph */
.result-close {
  max-width: 720px;
  border-top: 3px solid #f9bf14;
  padding-top: 1.2rem;
  margin-bottom: clamp(3.5rem, 7vw, 6rem);
}
.result-close h4 {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  font-size: clamp(1.15rem, 2vw, 1.6rem);
  margin-bottom: 0.6rem;
}
.result-close p { font-size: clamp(1.02rem, 1.5vw, 1.25rem); line-height: 1.6; }

@media (max-width: 900px) {
  .result-panel { grid-template-columns: 1fr; gap: clamp(2rem, 4vw, 3rem); }
  .result-card { max-width: none; margin-right: 0; }
}

/* two phone screens side by side, sharing one caption */
.story-figure--pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.6rem);
  max-width: 48rem;
}
.story-figure--pair figcaption { grid-column: 1 / -1; }

/* a phone screen used as the side column of a split: cap it at phone width
   instead of filling the half-band, which would upscale a 375pt screen */
.story-figure--side.story-figure--phone img { width: min(20rem, 100%); }

@media (max-width: 560px) {
  .story-figure--pair { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------------
   A'DAM Lookout chapter — a photograph bleeding into navy, the way the
   GoPhoto deck covers are built. The copy never sits on the photo: the
   section pads past the image band, so text contrast stays the flat
   11.9:1 of paper on navy instead of depending on what is in the frame.
   ------------------------------------------------------------------ */
.case--gp-feature {
  --gp-band: min(56vw, 38rem);
  --gp-photo: url("../assets/projects/gophoto-10.jpg");
  /* scrim ramp: tuned per photograph, because the copy sits on the image and
     the contrast it can carry depends on how bright that image is */
  --gp-scrim-0: 0.20;
  --gp-scrim-1: 0.38;
  --gp-scrim-2: 0.55;
  --gp-scrim-3: 0.72;
  position: relative;
  isolation: isolate;
  background-color: var(--gp-navy);
  color: var(--paper);
  --fg: var(--paper);
  --fg-strong: #ffffff;
  --fg-dim: var(--gp-blue-pale);
  --line: var(--line-dark);
  --line-light: var(--line-dark);
  /* the copy sits ON the photograph now, so the scrim below carries the
     contrast rather than the padding clearing the band */
  padding-top: clamp(6rem, 13vw, 11rem);
}
.case--gp-feature::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--gp-band);
  z-index: -2;                 /* under the hairlines at -1, under the copy */
  pointer-events: none;
  background-image:
    linear-gradient(180deg,
      rgba(33, 37, 76, var(--gp-scrim-0)) 0%,
      rgba(33, 37, 76, var(--gp-scrim-1)) 26%,
      rgba(33, 37, 76, var(--gp-scrim-2)) 55%,
      rgba(33, 37, 76, var(--gp-scrim-3)) 82%,
      var(--gp-navy) 100%),
    var(--gp-photo);
  background-size: cover, cover;
  background-position: center top, center 35%;
  background-repeat: no-repeat, no-repeat;
}

/* deep blue is unreadable on navy (2.1:1) — the dark section takes the
   light end of the brand scale instead */
.case--gp-feature .story-kicker { color: var(--gp-blue-pale); }
.case--gp-feature .story h3::after { background: #1a9cd8; }
.case--gp-feature .story-stat .display { color: #1a9cd8; }

/* second feature chapter: same treatment, a phone in hand instead of the tower */
/* the four paragraphs read as two beats, so they run side by side above
   the phone instead of one long single column */
.case--gp-feature--gallery .story { max-width: none; }
.story-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.5rem, 4vw, 3rem);
  max-width: 62rem;
}
.story-cols p + p { margin-top: 1.05rem; }
@media (max-width: 720px) {
  .story-cols { grid-template-columns: 1fr; }
}

/* the switcher and the device frame centre under the two-column copy */
.case--gp-feature--gallery .pw-switch { margin-inline: auto; }
/* The device can't use margin:auto — at 1582px (desktop view) it is wider
   than the stage on most screens, and a plain auto margin goes negative,
   pushing the frame's left edge outside the scrollable area where it can
   never be reached. `safe center` centres it only while it fits, and falls
   back to flex-start (so the full frame stays reachable by scroll) once it
   doesn't; flex: 0 0 auto keeps the device at its true width instead of the
   flex item shrinking it. */
.case--gp-feature--gallery .pw-stage {
  display: flex;
  justify-content: safe center;
}
.case--gp-feature--gallery .pw-device { flex: 0 0 auto; }

/* fixed frame height at every width: the photo grid scrolls internally,
   so the mock never pushes the section's height around */
.pw-device {
  height: 622px;
  display: flex;
  flex-direction: column;
}
.pw-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  cursor: grab;
}
.pw-body.is-dragging {
  cursor: grabbing;
  user-select: none;
  scroll-behavior: auto; /* an in-progress drag sets scrollTop directly */
}

.case--gp-feature--gallery {
  --gp-photo: url("../assets/projects/gophoto-g2.jpg");
  /* a bright sky, not a dusk tower: measured on the real pixels, the tower
     ramp leaves the kicker at 2.4:1. These values are the lightest ramp that
     clears 4.5:1 for it (4.9:1) while the photograph still reads. */
  --gp-scrim-0: 0.52;
  --gp-scrim-1: 0.70;
  --gp-scrim-2: 0.82;
  --gp-scrim-3: 0.92;
}

/* the device switcher inherits a light-section palette that collapses on
   navy (pale blue on pale grey is 1.3:1), so the dark section restates it */
.case--gp-feature .pw-switch {
  background: rgba(255, 255, 255, 0.22);
  border-color: rgba(255, 255, 255, 0.22);
}
.case--gp-feature .pw-switch button {
  background: rgba(255, 255, 255, 0.06);
  color: var(--gp-blue-pale);
}
.case--gp-feature .pw-switch button:hover { color: #ffffff; }
.case--gp-feature .pw-switch button[aria-pressed="true"] {
  background: #ffffff;
  color: var(--gp-navy);
}

/* ------------------------------------------------------------------
   Lookout gallery — the centre-mode carousel from gophoto.nl, rebuilt
   without jQuery/Slick. Measured off the original: 577px slides at
   1076x762, 110px between them, the neighbours bleeding in from both
   sides, autoplay every 2s with a 500ms glide, no arrows or dots.
   The band is split navy/grey down the middle so the gallery sits on
   the seam between the two sections rather than inside either one.
   ------------------------------------------------------------------ */
.gp-band {
  background: linear-gradient(to bottom, var(--gp-navy) 0 50%, var(--gp-grey) 50% 100%);
  overflow: hidden;
}
.gp-slider { overflow: hidden; }
.gp-slider-track {
  display: flex;
  align-items: center;
  gap: var(--gp-gap);
  list-style: none;
  margin: 0;
  padding: 0;
  width: max-content;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.gp-slide {
  --gp-slide-w: min(36rem, 58vw);
  --gp-gap: clamp(1.25rem, 5vw, 6.875rem);
  flex: 0 0 var(--gp-slide-w);
  width: var(--gp-slide-w);
}
.gp-slider-track { --gp-gap: clamp(1.25rem, 5vw, 6.875rem); }
.gp-slide img {
  width: 100%;
  height: auto;
  aspect-ratio: 1076 / 762;
  object-fit: cover;
  display: block;
  border-radius: 12px;
}

/* No JS, or reduced motion: a plain scrollable row. Nothing moves on its
   own, and the whole set is still reachable by dragging or the keyboard. */
.gp-slider.is-static {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
.gp-slider.is-static .gp-slider-track {
  transform: none !important;
  padding: 0 var(--pad);
}
.gp-slider.is-static .gp-slide { scroll-snap-align: center; }

@media (prefers-reduced-motion: reduce) {
  .gp-slider-track { transition: none; }
}

/* ------------------------------------------------------------------
   Glass card, as used on the GoPhoto deck: rounded, translucent, a thin
   light edge, the background reading faintly through it. The fill is
   navy-based rather than plain white, so that over the brightest part
   of the photograph it darkens the backdrop instead of lifting it —
   white at 6% alone drops body text to about 3.3:1 there.
   ------------------------------------------------------------------ */
.gp-card {
  align-self: center;
  max-width: 30rem;
  padding: clamp(1.6rem, 3.4vw, 2.6rem);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 1.5rem;
  background-color: rgba(23, 26, 56, 0.72);
  background-image: linear-gradient(rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.06));
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.gp-card-label {
  display: block;
  color: var(--gp-blue-pale);
  margin-bottom: 1rem;
}
.gp-card-figure {
  display: block;
  font-size: clamp(3rem, 7vw, 5.5rem);
  line-height: 1;
  color: #1a9cd8;
  margin-bottom: 1.1rem;
}
.gp-card p { line-height: 1.6; }
.gp-card p + p { margin-top: 1rem; }

@media (max-width: 900px) {
  .gp-card { max-width: none; }
}

/* ------------------------------------------------------------------
   Reach globe — a wireframe sphere drawn on canvas, in the spirit of
   the Gumloop "company knowledge" card: dotted meridians, markers that
   shrink and fade as they turn away. Decorative, so the canvas is
   aria-hidden and the wrapper carries the description.
   ------------------------------------------------------------------ */
.gp-globe-wrap { margin: 0; }
.gp-globe {
  width: min(40.8rem, 100%);
  margin-inline: auto;          /* centred in its column, not pinned to the edge */
  aspect-ratio: 1;
  cursor: grab;
  touch-action: pan-y;          /* drag spins it, vertical swipe still scrolls */
}
.gp-globe.is-dragging { cursor: grabbing; }
.gp-globe-canvas { width: 100%; height: 100%; display: block; }
.gp-globe-wrap figcaption {
  margin-top: 1rem;
  color: var(--fg-dim);
  max-width: min(40.8rem, 100%);
  margin-inline: auto;          /* stays flush with the globe above it */
  text-align: center;
}

/* The outcome figures carry the whole result, so they are lifted out of the
   sentence. Weight alone is the base, so the class stays readable on any
   background; the brand blue is added only where it has been measured. */
.fig { font-weight: 600; }
.case--gp .fig { color: var(--gp-blue-deep); }

/* ------------------------------------------------------------------
   Entry-path diagram — pill nodes joined by dashed connectors, in the
   spirit of the Gumloop workflow graphic. Drawn as inline SVG so it
   follows the page tokens and stays crisp at any size, rather than
   shipping another bitmap.
   ------------------------------------------------------------------ */
.flow {
  width: 100%;
  height: auto;
  display: block;
  font-family: var(--font-body);
}
/* Centred in its column, matching the globe's treatment on the same page.
   The clamp that used to keep it clear of the section rail is gone: the
   diagram now sits in the left column, which the rail never reaches. */
.story-figure--flow .flow,
.story-figure--flow figcaption {
  width: min(34rem, 100%);
  margin-inline: auto;
}
.flow-line {
  fill: none;
  stroke: rgba(33, 37, 76, 0.32);
  stroke-width: 1.5;
  stroke-dasharray: 5 5;
  stroke-linecap: round;
}
.flow-node rect {
  fill: #ffffff;
  stroke: rgba(33, 37, 76, 0.16);
  stroke-width: 1;
}
.flow-node text {
  fill: var(--gp-navy);
  font-size: 15px;
  font-weight: 500;
}
.flow-dot { fill: var(--gp-blue); }

/* the route that was designed but never shipped reads as an outline */
.flow-node--planned rect {
  fill: var(--gp-tint);
  stroke: var(--gp-blue-pale);
  stroke-dasharray: 6 4;
}
.flow-node--planned .flow-dot { fill: var(--gp-blue-pale); }
.flow-node--planned text { fill: var(--gp-blue-deep); }

.flow-chip rect,
.flow-tag rect {
  fill: var(--gp-tint);
  stroke: var(--gp-blue-pale);
  stroke-width: 1;
}
.flow-chip text,
.flow-tag text {
  fill: var(--gp-blue-deep);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-anchor: middle;
}
.flow-tag text { font-size: 9px; }

/* ------------------------------------------------------------------
   Photo Website gallery, rebuilt in code with a device switcher.
   Geometry taken from the design file, not eyeballed:
     mobile  375  – one column, 16px gutter, natural heights
     tablet  768  – rows 228 high, 16px gap
     desktop 1582 – rows 195 high, 14px gap, grid capped at 907 centred
   Tiles are sized by aspect ratio (--r) against a fixed row height, so a
   row fills across and wraps: photos keep their proportions, never cropped
   into a square grid — which is the point the surrounding copy makes.
   ------------------------------------------------------------------ */
.pw-demo { margin: 0 0 clamp(3.5rem, 7vw, 6rem); }

.pw-switch {
  display: flex;
  gap: 1px;
  width: fit-content;
  margin-bottom: 1.2rem;
  background: var(--line);
  border: 1px solid var(--line);
}
.pw-switch button {
  cursor: pointer;
  border: 0;
  padding: 0.6rem 1.1rem;
  background: var(--gp-grey);
  color: var(--fg-dim);
  transition: background 0.2s, color 0.2s;
}
.pw-switch button:hover { color: var(--fg); }
.pw-switch button[aria-pressed="true"] {
  background: var(--gp-navy);
  color: #ffffff;
}

/* The frame is always drawn at its true device width — never re-flowed and
   never shrunk — so the three views stay dimensionally honest. When it is
   wider than the column it scrolls sideways instead. */
.pw-stage {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;   /* a sideways flick stays in the widget */
}
.pw-stage:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.pw-device {
  width: var(--pw-w);
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  font-family: var(--font-body);
  color: #1a1a1a;
}
.pw-device[data-view="mobile"]  { --pw-w: 375px;  --pw-gap: 16px; --pw-pad: 16px; }
.pw-device[data-view="tablet"]  { --pw-w: 768px;  --pw-gap: 16px; --pw-pad: 16px; --pw-row: 228px; }
.pw-device[data-view="desktop"] { --pw-w: 1582px; --pw-gap: 14px; --pw-pad: 14px; --pw-row: 195px; }

/* Matches the file: 48px bar, 12px/16px padding, 24x24 icon slots. */
.pw-bar {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px var(--pw-pad);
  border-bottom: 1px solid rgba(0, 0, 0, 0.07);
}
.pw-burger, .pw-cart { position: relative; width: 24px; height: 24px; }
/* three 1px lines at 25/50/75% of the box, the bottom one shortened —
   the exact geometry and #232429 stroke from the file's icon layer.
   Two lines come off ::before (the second cloned via box-shadow) so a
   single childless <span> can still draw all three without extra markup. */
.pw-burger::before {
  content: "";
  position: absolute;
  left: 16.67%;
  right: 16.67%;
  top: 6px;
  height: 1px;
  background: #232429;
  box-shadow: 0 6px 0 #232429;
}
.pw-burger::after {
  content: "";
  position: absolute;
  left: 16.67%;
  right: 37.5%;
  top: 18px;
  height: 1px;
  background: #232429;
}
.pw-cart {
  display: block;
  background: url("../assets/projects/pw-menu/cart.svg") center / 24px no-repeat;
}

.pw-body { padding: var(--pw-pad); }
.pw-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--pw-gap);
}
/* mobile: one photo per row, full width, height from its own ratio */
.pw-device[data-view="mobile"] .pw-tile {
  flex: 0 0 100%;
  aspect-ratio: var(--r);
}
/* tablet & desktop: fixed row height, width follows the ratio */
.pw-device[data-view="tablet"] .pw-tile,
.pw-device[data-view="desktop"] .pw-tile {
  height: var(--pw-row);
  width: calc(var(--pw-row) * var(--r));
  flex: 0 0 auto;
}
/* the gallery column is capped and centred on desktop, as in the file */
.pw-device[data-view="desktop"] .pw-grid {
  max-width: 907px;
  margin: 0 auto;
}

.pw-tile {
  position: relative;
  overflow: hidden;
  background: #eceff3;
}
.pw-tile img { width: 100%; height: 100%; object-fit: cover; }

/* repeated watermark, the way an unpurchased photo ships */
.pw-mark {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='84'%3E%3Ctext x='4' y='22' font-family='Helvetica,Arial' font-size='17' font-weight='700' fill='%23ffffff' fill-opacity='.85'%3EGO%3C/text%3E%3Ctext x='40' y='22' font-family='Helvetica,Arial' font-size='17' fill='none' stroke='%23ffffff' stroke-opacity='.85' stroke-width='.6'%3EPHOTO%3C/text%3E%3Ctext x='58' y='66' font-family='Helvetica,Arial' font-size='17' font-weight='700' fill='%23ffffff' fill-opacity='.85'%3EGO%3C/text%3E%3Ctext x='94' y='66' font-family='Helvetica,Arial' font-size='17' fill='none' stroke='%23ffffff' stroke-opacity='.85' stroke-width='.6'%3EPHOTO%3C/text%3E%3C/svg%3E");
}

.pw-share {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 40px;
  height: 40px;
  padding: 0;
  cursor: pointer;
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid #ffffff;
  border-radius: 4px;
  box-shadow: 0 12px 16px -4px rgba(16, 24, 40, 0.08),
              0 4px 6px -2px rgba(16, 24, 40, 0.03);
}
.pw-share::before {
  content: "";
  position: absolute;
  inset: 0;
  background: no-repeat center/24px 24px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='1' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 16V4M12 4 7 9M12 4l5 5M4 15v4a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-4'/%3E%3C/svg%3E");
}

.pw-play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 54px;
  height: 54px;
  border: 4px solid rgba(255, 255, 255, 0.85);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.22);
}
.pw-play::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 0;
  height: 0;
  border-left: 17px solid rgba(255, 255, 255, 0.95);
  border-top: 11px solid transparent;
  border-bottom: 11px solid transparent;
  transform: translateX(3px);
}
.pw-360 {
  position: absolute;
  top: 8px;
  right: 42px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 15px;
  color: #e2231a;
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.6);
}

.pw-foot {
  margin-top: 14px;
  text-align: center;
  font-size: 11px;
  color: #6b7280;
}
.pw-foot a { color: #057eb5; text-decoration: underline; }

/* the file's "Frame 12": 16px padding, 12px between the price row and the
   button row, both fixed values rather than the gallery's responsive
   --pw-pad/--pw-gap (this drawer's spacing doesn't scale with device) */
.pw-drawer {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 16px;
  border-top: 1px solid rgba(0, 0, 0, 0.07);
}
.pw-drawer-copy { font-size: 16px; line-height: 24px; color: #232429; }
.pw-price { font-size: 18px; line-height: 24px; font-weight: 700; color: #232429; }
.pw-btn {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  padding: 4px 12px;
  font-size: 14px;
  font-weight: 500;
  border-radius: 4px;
}
.pw-btn--ghost { border: 1px solid #057eb5; color: #057eb5; }
.pw-btn--solid { background: #057eb5; color: #ffffff; }
/* the two actions are equal halves under the price row, as in the file */
.pw-actions {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
/* on desktop the drawer shares the gallery's centred column */
.pw-device[data-view="desktop"] .pw-drawer {
  max-width: 907px;
  margin: 0 auto;
  padding-inline: 0;
}

/* Side menu from the file's "Photo gallery - open menu": a 238px white
   panel over the left edge, the rest of the screen dimmed 24% and blurred. */
.pw-device { position: relative; }
.pw-burger {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.pw-burger:focus-visible { outline: 2px solid #057eb5; outline-offset: 2px; }
.pw-dim {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: rgba(0, 0, 0, 0.24);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s;
}
.pw-device.is-dim-on .pw-dim { pointer-events: auto; }
.pw-menu {
  position: absolute;
  inset: 0 auto 0 0;
  z-index: 4;
  width: 238px;
  display: flex;
  flex-direction: column;
  background: #ffffff;
  color: #232429;
  font-size: 16px;
  line-height: 24px;
  transform: translateX(-100%);
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.pw-dim[hidden], .pw-menu[hidden] { display: none; }
.pw-device.is-dim-on .pw-dim { opacity: 1; }
.pw-device.is-menu-open .pw-menu { transform: none; }
.pw-menu-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 16px;
  background: #f8fafc;
  text-transform: uppercase;
  letter-spacing: 1.28px;
  line-height: normal;
}
.pw-menu-toggle { display: block; width: 58.667px; height: 32px; padding: 0; border: 0; background: none; cursor: pointer; }
.pw-menu-toggle img { display: block; width: 58.667px; height: 32px; }
.pw-menu-toggle:focus-visible { outline: 2px solid #057eb5; outline-offset: 2px; border-radius: 16px; }
.pw-menu-list { list-style: none; margin: 0; padding: 0; }
.pw-menu-list li { border-bottom: 1px solid #cdcfd4; }
.pw-menu-list button {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 16px 16px 15px; /* 56px rows including the 1px divider, as in the file */
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.pw-menu-list button:hover { background: #f8fafc; }
.pw-menu-list button:focus-visible { outline: 2px solid #057eb5; outline-offset: -2px; }
.pw-menu-list img { display: block; width: 24px; height: 24px; }
.pw-menu-flag { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; }
.pw-menu-flag img { width: 18.667px; height: 14px; object-fit: cover; }
.pw-menu-foot {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: auto 0 0;
  padding: 16px;
  font-size: 12px;
  line-height: 16px;
  color: #565964;
  white-space: nowrap;
}
.pw-menu-foot img { display: block; width: 70.286px; height: 12px; }

/* Screens the menu opens under the nav bar ("Photo gallery -> ..." frames):
   white layer over the gallery, 16px content padding, 24px between blocks,
   ghost "Back to gallery" pinned at the bottom. Colours are the file's
   tokens: text #232429/#565964, stroke #cdcfd4, blues #057eb5/#1a9cd8. */
.pw-screen {
  position: absolute;
  inset: 48px 0 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  background: #ffffff;
  color: #232429;
  font-size: 16px;
  line-height: 24px;
}
.pw-screen[hidden], .pw-page[hidden], .pw-sheet[hidden] { display: none; }
.pw-page {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 16px;
}
.pw-page-title {
  margin: 0;
  font-size: 18px;
  line-height: 24px;
  font-weight: 600;
  color: #232429;
}
.pw-back { margin: 16px; flex: 0 0 auto; }
.pw-back[hidden] { display: none; } /* .pw-btn's display:flex would otherwise beat the attribute */
button.pw-btn { font: inherit; cursor: pointer; }
.pw-btn--ghost { background: none; }
.pw-btn--auto { display: inline-flex; margin-top: 8px; }

/* language list: same rows as the menu, active row tinted with a check */
.pw-lang li.is-active button { background: #f1f9fe; }
.pw-lang li:last-child { border-bottom: 0; }
.pw-lang li.is-active button::after {
  content: "";
  width: 24px;
  height: 24px;
  margin-left: auto;
  background: url("../assets/projects/pw-menu/check.svg") center / 24px no-repeat;
}

/* My orders */
.pw-alert {
  display: flex;
  align-items: flex-start;
  gap: 4px;
  padding: 12px 16px;
  border: 1px solid #1a9cd8;
  border-radius: 4px;
  background: #e8f5fb;
}
.pw-alert-copy { flex: 1 1 auto; min-width: 0; }
.pw-alert-copy strong { display: block; font-size: 16px; line-height: 24px; font-weight: 600; color: #232429; }
.pw-alert-copy p { margin: 4px 0 0; font-size: 14px; line-height: 20px; color: #565964; }
.pw-alert > img { display: block; width: 76.4px; height: 61.9px; flex: 0 0 auto; }
.pw-order { display: flex; flex-direction: column; gap: 6px; }
.pw-order-head { display: flex; align-items: center; gap: 6px; }
.pw-order-head > span:first-child { padding-right: 6px; font-size: 16px; line-height: 24px; color: #565964; }
.pw-order-head i { flex: 1 1 auto; height: 1px; background: #cdcfd4; }
.pw-chip {
  padding: 4px 16px;
  border-radius: 9999px;
  background: #e8f5fb;
  font-size: 14px;
  line-height: 20px;
  color: #232429;
}
.pw-chip--success { background: #e3f2ea; }
.pw-order dl { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 8px 0; }
.pw-order dl > div { display: flex; align-items: center; justify-content: space-between; font-size: 14px; line-height: 20px; }
.pw-order dt { color: #565964; }
.pw-order dd { margin: 0; color: #232429; }
.pw-link { color: #057eb5; text-decoration: underline; }

/* empty state */
.pw-empty {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
}
.pw-empty img { display: block; width: 149.1px; height: 139.67px; margin-bottom: 16px; }
.pw-empty p { margin: 0; }
.pw-empty-title { font-size: 16px; line-height: 24px; color: #000000; }
.pw-empty-copy { max-width: 191px; font-size: 14px; line-height: 20px; color: #565964; }

/* Log out bottom sheet over the dimmer */
.pw-sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  background: #ffffff;
  color: #232429;
  border-radius: 16px 16px 0 0;
  transform: translateY(100%);
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.pw-sheet.is-open { transform: none; }
.pw-sheet-head {
  margin: 0;
  padding: 16px 24px;
  font-size: 20px;
  line-height: 26px;
  font-weight: 600;
  border-bottom: 1px solid #cdcfd4;
}
.pw-sheet-body { margin: 0; padding: 24px 24px 0; font-size: 16px; line-height: 24px; color: #565964; }
.pw-sheet-foot { display: flex; gap: 12px; padding: 24px; }
.pw-sheet-foot .pw-btn { flex: 1 1 0; }

/* Purchase flow ("Section 2" in the file): drawer -> Package details sheet
   -> Your order -> Purchase Confirmed -> gallery with download buttons. */
button.pw-btn--solid { border: 0; }
.pw-cart { padding: 0; border: 0; cursor: pointer; }
.pw-cart:focus-visible { outline: 2px solid #057eb5; outline-offset: 2px; }
.pw-badge {
  position: absolute;
  top: 0;
  right: 0;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #0a95d4;
  color: #ffffff;
  font-size: 8px;
  line-height: 12px;
  font-weight: 700;
  text-align: center;
}
.pw-badge[hidden] { display: none; }

/* Package details sheet */
.pw-package { display: flex; flex-direction: column; gap: 16px; padding: 16px; }
.pw-package-banner { display: block; width: 100%; height: auto; aspect-ratio: 343 / 89; border-radius: 4px; }
.pw-package strong { font-size: 18px; line-height: 24px; font-weight: 600; color: #232429; }
.pw-package p { margin: 0; font-size: 16px; line-height: 24px; color: #565964; }
.pw-subtotal {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  border-radius: 4px;
  background: #f5f7fa;
  font-size: 16px;
  line-height: 24px;
  color: #232429;
}
.pw-subtotal b { font-size: 20px; line-height: 28px; font-weight: 600; }
.pw-package-actions { display: flex; gap: 16px; }
.pw-package-actions .pw-btn { flex: 1 1 0; }

/* Your order */
.pw-page--order { padding: 0; gap: 0; overflow: hidden; } /* the body scrolls, the bottom bar stays pinned */
.pw-page--order > .pw-order-body { min-height: 0; overflow-y: auto; }
.pw-steps {
  display: flex;
  align-items: center;
  height: 55px;
  padding: 8px 16px;
  border-top: 1px solid #cdcfd4;
  border-bottom: 1px solid #cdcfd4;
}
.pw-step {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: 0 30px;
  border: 1px solid #232429;
  border-radius: 50%;
  flex: 0 0 auto;
}
.pw-step.is-current { background: #232429; }
.pw-step img { display: block; width: 20px; height: 20px; }
.pw-steps i { flex: 1 1 auto; height: 1px; margin: 0 -22px; background: #232429; }
.pw-order-body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 16px; padding: 16px; }
.pw-item { display: flex; flex-direction: column; gap: 12px; padding-bottom: 12px; border-bottom: 1px solid #cdcfd4; }
.pw-item-top { display: flex; align-items: flex-start; justify-content: space-between; }
.pw-item-top > img { display: block; width: 88px; height: 58.8px; object-fit: cover; border-radius: 4px; }
.pw-icon-btn { display: block; padding: 0; border: 0; background: none; cursor: pointer; }
.pw-icon-btn img { display: block; width: 24px; height: 24px; }
.pw-item-row { display: flex; justify-content: space-between; gap: 16px; font-size: 16px; line-height: 24px; color: #232429; }
.pw-order-bottom { flex: 0 0 auto; }
.pw-coupon {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border: 0;
  border-top: 1px solid #cdcfd4;
  font-size: 16px;
  line-height: 24px;
  color: #232429;
}
.pw-coupon img { display: block; width: 24px; height: 24px; }
.pw-coupon img:last-child { margin-left: auto; }
.pw-totals { display: flex; flex-direction: column; gap: 8px; padding: 16px; border-top: 1px solid #cdcfd4; }
.pw-totals > div { display: flex; align-items: center; justify-content: space-between; font-size: 14px; line-height: 20px; }
.pw-totals > div > span:first-child { color: #565964; }
.pw-totals > div > span:last-child { color: #232429; }
.pw-totals-sum > span { font-weight: 600; color: #232429 !important; }
.pw-totals-sum > span:first-child { font-size: 16px; line-height: 24px; }
.pw-totals-sum > span:last-child { font-size: 20px; line-height: 28px; }
.pw-totals-actions { gap: 12px; padding-top: 8px; }
.pw-totals-actions .pw-btn { flex: 1 1 0; }

/* Purchase confirmed */
.pw-page--confirm { align-items: center; gap: 32px; padding: 128px 16px 0; }
.pw-page--confirm > img { display: block; width: 149.1px; height: 139.67px; }
.pw-confirm-copy { display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; }
.pw-confirm-copy p { margin: 0; font-size: 16px; line-height: 24px; color: #565964; }
.pw-confirm-title { font-size: 18px; font-weight: 600; color: #000000 !important; }
.pw-order-no {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
  min-height: 40px;
  margin-top: auto;
  padding: 4px 12px;
  border-radius: 4px;
  background: #f5f7fa;
  font-size: 16px;
  line-height: 24px;
  color: #565964;
}
.pw-order-no b { font-weight: 600; color: #232429; }
.pw-order-no img { display: block; width: 24px; height: 24px; }

/* after purchase: download button beside share, drawer gone, powered-by shown */
.pw-download {
  position: absolute;
  top: 4px;
  right: 56px;
  width: 40px;
  height: 40px;
  padding: 0;
  cursor: pointer;
  background: rgba(0, 0, 0, 0.4) url("../assets/projects/pw-menu/download.svg") center / 24px no-repeat;
  border: 1px solid #ffffff;
  border-radius: 4px;
  box-shadow: 0 12px 16px -4px rgba(16, 24, 40, 0.08),
              0 4px 6px -2px rgba(16, 24, 40, 0.03);
}
.pw-download[hidden] { display: none; }
.pw-device.is-purchased .pw-drawer { display: none; }
.pw-powered {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 16px 0 0;
  font-size: 16px;
  line-height: 1.5;
  color: #7f8491;
}
.pw-powered[hidden] { display: none; }
.pw-powered img { display: block; width: 120px; height: 20.5px; }

/* Checkout ("Phase 1 - eCommerce" in the file): swipe-to-delete item,
   coupon sheet + applied state, details form, gateway stand-in, failed
   state and the two info sheets. */
.pw-item { position: relative; overflow: hidden; touch-action: pan-y; }
.pw-item-inner {
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: #ffffff;
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
  user-select: none;
}
.pw-item.is-dragging .pw-item-inner { transition: none; }
.pw-item.is-open .pw-item-inner { transform: translateX(-80px); }
.pw-item-del {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 12px;
  width: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: #dc2626;
  cursor: pointer;
  z-index: -1;
}
.pw-item-del img { display: block; width: 32px; height: 32px; }
.pw-item.is-open .pw-item-del, .pw-item.is-dragging .pw-item-del { z-index: 0; }
.pw-item-inner { position: relative; z-index: 1; }
.pw-item-inner .pw-item-top { padding-right: 0; }
.pw-item-inner .pw-item-row { padding-bottom: 0; }
.pw-item { padding-bottom: 12px; }

.pw-coupon-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 16px;
  border-radius: 24px;
  background: #e3f2ea;
  font-size: 14px;
  line-height: 20px;
  color: #3fa670;
}
.pw-coupon-chip[hidden] { display: none; }
.pw-coupon-chip img { display: block; width: 20px; height: 20px; }
.pw-device.has-coupon .pw-strike { text-decoration: line-through; }
button.pw-coupon { width: 100%; text-align: left; cursor: pointer; font: inherit; background: #ffffff; }
button.pw-coupon:focus-visible { outline: 2px solid #057eb5; outline-offset: -2px; }

/* text fields, as in the file: 16/24 label, 12x16 padding, #cdcfd4 stroke,
   #057eb5 on focus, #dd252b in error, 12/16 hint on a 16px reserved line */
.pw-form { gap: 6px; }
.pw-form .pw-page-title { margin-bottom: 10px; }
/* the file keeps each field at a fixed 343px (mobile's own content width) and
   left-aligned, rather than stretching it across the wider tablet/desktop
   column — narrow it explicitly so it stops growing once the column does */
.pw-field { display: flex; flex-direction: column; gap: 8px; width: 343px; max-width: 100%; align-self: flex-start; }
.pw-field > span { font-size: 16px; line-height: 24px; color: #232429; }
.pw-field input {
  box-sizing: border-box;
  width: 100%;
  padding: 12px 16px;
  border: 1px solid #cdcfd4;
  border-radius: 4px;
  background: #ffffff;
  font: inherit;
  font-size: 16px;
  line-height: 24px;
  color: #232429;
  outline: none;
}
.pw-field input::placeholder { color: #7f8491; }
.pw-field input:focus { border-color: #057eb5; }
.pw-field.is-error input { border-color: #dd252b; }
.pw-field small { display: block; min-height: 16px; padding: 0 12px; font-size: 12px; line-height: 16px; color: #dd252b; }

/* Adyen stand-in */
.pw-page--gateway {
  align-items: center;
  justify-content: center;
  gap: 24px;
  margin: 0;
  background: #f1f9fe;
  border: 1px dashed #0a95d4;
}
.pw-page--gateway > img { display: block; width: 120px; height: 120px; margin-top: auto; }
.pw-page--gateway > p { margin: 0 0 auto; font-size: 32px; line-height: 40px; font-weight: 600; color: #057eb5; text-align: center; }
.pw-gateway-mock {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  text-align: center;
}
.pw-gateway-mock > span { font-size: 12px; line-height: 16px; color: #565964; }
.pw-gateway-mock > div { display: flex; gap: 12px; }
.pw-gateway-mock .pw-btn { flex: 1 1 0; padding-inline: 8px; white-space: nowrap; }

button.pw-link { padding: 0; border: 0; background: none; font: inherit; cursor: pointer; }

/* sheets with a close icon in the header, and the two text sheets */
.pw-sheet-head--close { display: flex; align-items: center; justify-content: space-between; }
.pw-sheet-head--close h4 { margin: 0; font: inherit; }
.pw-sheet-head--tight { padding-inline: 16px; }
.pw-sheet-close { display: flex; padding: 4px; border: 0; border-radius: 8px; background: none; cursor: pointer; }
.pw-sheet-close img { display: block; width: 24px; height: 24px; }
.pw-sheet-form { display: flex; flex-direction: column; gap: 8px; padding: 16px; }
.pw-field--bare { gap: 8px; }
.pw-sheet-stack { display: flex; flex-direction: column; gap: 24px; padding: 16px 16px 0; }
.pw-sheet-stack--wide { padding: 24px; }
.pw-sheet-text { margin: 0; font-size: 16px; line-height: 24px; color: #565964; }
.pw-sheet-text--dark { color: #232429; }
.pw-blue { color: #057eb5; }
.pw-sheet-foot--tight { padding: 16px; }
.pw-sheet-foot--tight .pw-btn { flex: 1 1 0; text-decoration: none; }
.pw-sheet-list { margin: 0; padding-left: 24px; font-size: 16px; line-height: 24px; color: #232429; }

/* iOS share sheet ("Photo gallery - share" in the file), opened from a
   tile's Share button. A system sheet, so it keeps its own light palette
   (rgba(250,250,250,.93) + blur, #f2f2f7 secondary, #000 / rgba(60,60,67,.6)
   labels) in both themes. SF Pro is substituted by the site's body face. */
.pw-sheet--share {
  height: 562px;
  max-height: calc(100% - 48px);
  overflow: hidden;
  border-radius: 13px 13px 0 0;
  background: rgba(250, 250, 250, 0.93);
  backdrop-filter: blur(40px);
  -webkit-backdrop-filter: blur(40px);
  color: #000000;
}
.pw-device.is-dark .pw-sheet--share { background: rgba(250, 250, 250, 0.93); color: #000000; }
.pw-ss-head {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 8px;
  height: 72px;
  padding: 16px;
  border-bottom: 0.5px solid rgba(60, 60, 67, 0.18);
}
.pw-ss-thumb {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 1px;
  background: #ffffff;
  filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.05));
}
.pw-ss-app { position: absolute; top: 11px; left: 11px; width: 18px; height: 18px; border-radius: 3.9px; }
.pw-ss-photo {
  position: absolute;
  top: 7px;
  left: 0;
  width: 40px;
  height: 26.7px;
  object-fit: cover;
  box-shadow: 0 0 0 0.12px rgba(0, 0, 0, 0.2), 0 0.24px 0.71px rgba(0, 0, 0, 0.1);
}
.pw-ss-title { display: flex; flex-direction: column; }
.pw-ss-title b { font-size: 16px; line-height: 21px; font-weight: 600; letter-spacing: -0.32px; color: #000000; }
.pw-ss-title small { font-size: 13px; line-height: 18px; letter-spacing: -0.078px; color: rgba(60, 60, 67, 0.6); }
.pw-ss-close { position: absolute; top: 21px; right: 16px; width: 30px; height: 30px; padding: 0; border: 0; background: none; cursor: pointer; }
.pw-ss-close img { display: block; width: 30px; height: 30px; }
.pw-ss-row {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  list-style: none;
  margin: 0;
  padding: 20px 16px 16px;
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 0.5px solid rgba(60, 60, 67, 0.18);
}
.pw-ss-row::-webkit-scrollbar { display: none; }
.pw-ss-row li {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex: 0 0 72px;
  font-size: 11px;
  line-height: 13px;
  letter-spacing: 0.066px;
  text-align: center;
  color: #000000;
}
.pw-ss-row li small { font-size: 11px; line-height: 13px; color: rgba(60, 60, 67, 0.6); }
.pw-ss-airdrop { flex-basis: 60px; height: 77px; }
.pw-ss-airdrop img { display: block; width: 72px; height: 72px; margin: -6px 0 -10px; }
.pw-ss-avatar {
  position: relative;
  display: block;
  width: 63px;
  height: 63px;
  border-radius: 50%;
  overflow: visible;
  filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.05));
}
.pw-ss-avatar > img { display: block; width: 63px; height: 63px; border-radius: 50%; object-fit: cover; }
/* the 20px Messages badge at (45,44) */
.pw-ss-avatar i {
  position: absolute;
  left: 45px;
  top: 44px;
  width: 20px;
  height: 20px;
  border-radius: 4.33px;
  background: url("../assets/projects/pw-share/messages.png") center / cover;
}
.pw-ss-group { background: #f2f2f7; }
.pw-ss-group > img { position: absolute; border-radius: 50%; }
.pw-ss-group > img:nth-child(1) { left: 15.08%; top: 7.94%; width: 47.6%; height: 47.6%; }
.pw-ss-group > img:nth-child(2) { left: 59.52%; top: 38.1%; width: 34.9%; height: 34.9%; }
.pw-ss-group > img:nth-child(3) { left: 29.37%; top: 60.32%; width: 28.6%; height: 28.6%; }
.pw-ss-apps { gap: 32px; }
.pw-ss-apps li { flex-basis: 60px; }
.pw-ss-apps li > img { display: block; width: 60px; height: 60px; border-radius: 13px; object-fit: cover; }
.pw-ss-apps li > img.pw-ss-contain { object-fit: contain; }
.pw-ss-actions { display: flex; flex-direction: column; gap: 16px; padding: 8px 16px 16px; } /* 16 from the row + 8 = the file's 24 */
.pw-ss-actions ul { list-style: none; margin: 0; padding: 0; background: #ffffff; border-radius: 13px; overflow: hidden; }
.pw-ss-actions li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 13px 16px;
  font-size: 17px;
  line-height: 22px;
  letter-spacing: -0.408px;
  color: #000000;
}
.pw-ss-actions li + li { border-top: 0.5px solid rgba(60, 60, 67, 0.18); }
.pw-ss-actions li img { display: block; }

/* ============================================================
   Tablet (768) and desktop (1582) variants, from the file's "Photo gallery"
   responsive section. Tablet keeps the 238px menu drawer and full-width
   screens; desktop centres nav icons, screens and drawer in a 942px column
   and widens the menu to 285px. On both, sheets become centred 375px
   dialogs and "Back to gallery" is a centred 343px button.
   ============================================================ */
.pw-device:not([data-view="mobile"]) .pw-sheet {
  left: 50%;
  right: auto;
  bottom: auto;
  top: 50%;
  width: 375px;
  max-height: calc(100% - 48px);
  overflow-y: auto;
  border-radius: 8px;
  transform: translate(-50%, -45%);
  opacity: 0;
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.3s;
}
.pw-device:not([data-view="mobile"]) .pw-sheet.is-open { transform: translate(-50%, -50%); opacity: 1; }
.pw-sheet-close--wide { display: none; }
.pw-device:not([data-view="mobile"]) .pw-sheet-close--wide { display: flex; }
.pw-device:not([data-view="mobile"]) .pw-back,
.pw-device:not([data-view="mobile"]) .pw-order-no { width: 343px; margin-left: auto; margin-right: auto; }
.pw-device:not([data-view="mobile"]) .pw-order-no { margin-bottom: 0; }
.pw-device[data-view="desktop"] .pw-menu { width: 285px; }
/* the 942px column: side padding grows once the frame is wider than it */
.pw-device[data-view="desktop"] .pw-bar,
.pw-device[data-view="desktop"] .pw-page,
.pw-device[data-view="desktop"] .pw-page--order > .pw-order-body,
.pw-device[data-view="desktop"] .pw-page--order > .pw-order-bottom > * {
  padding-left: max(var(--pw-pad), calc(50% - 471px));
  padding-right: max(var(--pw-pad), calc(50% - 471px));
}
.pw-device[data-view="desktop"] .pw-page--order { padding: 0; }
/* the file keeps the status bar's rules full width and puts the badge
   groups at the column edges (x=320) with no extra inset */
.pw-device[data-view="desktop"] .pw-steps { padding-left: max(0px, calc(50% - 471px)); padding-right: max(0px, calc(50% - 471px)); }
.pw-device[data-view="desktop"] .pw-page--gateway { padding-left: 16px; padding-right: 16px; }
.pw-device[data-view="desktop"] .pw-gateway-mock { max-width: 343px; }
.pw-device[data-view="desktop"] .pw-page--confirm { padding-left: 16px; padding-right: 16px; }
.pw-device[data-view="desktop"] .pw-confirm-copy { max-width: 343px; }
.pw-device[data-view="desktop"] .pw-foot, .pw-device[data-view="desktop"] .pw-powered { max-width: 910px; margin-left: auto; margin-right: auto; }
@media (prefers-reduced-motion: reduce) {
  .pw-device:not([data-view="mobile"]) .pw-sheet { transition: none; }
}

/* ============================================================
   Dark theme for the mock ("Photo gallery - dark mode" in the file),
   toggled by the switch in the menu header. Values are the file's dark
   variable mode: surface #0b1220 / #111827 / #1f2937, stroke #334155,
   text #f5f5f6 / #cbd5e1 / #64748b, accent green #3fa670 with #232429
   label on solid buttons, success chip #acd9c1 on #19422d, error #fca5a5.
   The file's dark icons are #f5f5f6, so the light stroke icons are
   inverted with a filter rather than shipped twice.
   ============================================================ */
.pw-device.is-dark { background: #0b1220; color: #f5f5f6; border-color: #334155; }
.pw-device.is-dark .pw-bar { border-bottom-color: #334155; }
.pw-device.is-dark .pw-burger::before, .pw-device.is-dark .pw-burger::after { background: #f5f5f6; box-shadow: 0 6px 0 #f5f5f6; }
.pw-device.is-dark .pw-burger::after { box-shadow: none; }
.pw-device.is-dark .pw-cart { filter: brightness(0) invert(0.96); }
.pw-device.is-dark .pw-badge { background: #3fa670; color: #232429; }
.pw-device.is-dark .pw-tile { background: #1f2937; }
.pw-device.is-dark .pw-foot { color: #cbd5e1; }
.pw-device.is-dark .pw-foot a, .pw-device.is-dark .pw-link, .pw-device.is-dark .pw-blue { color: #3fa670; }
.pw-device.is-dark .pw-powered { color: #64748b; }
.pw-device.is-dark .pw-drawer { border-top-color: #334155; }
.pw-device.is-dark .pw-drawer-copy, .pw-device.is-dark .pw-price { color: #f5f5f6; }
.pw-device.is-dark .pw-btn--ghost { border-color: #3fa670; color: #3fa670; }
.pw-device.is-dark .pw-btn--solid { background: #3fa670; color: #232429; }
.pw-device.is-dark .pw-burger:focus-visible, .pw-device.is-dark .pw-cart:focus-visible,
.pw-device.is-dark .pw-menu-list button:focus-visible, .pw-device.is-dark .pw-menu-toggle:focus-visible { outline-color: #3fa670; }

/* icons: light #232429 strokes -> the file's #f5f5f6 */
.pw-device.is-dark .pw-menu-list button > img,
.pw-device.is-dark .pw-menu-foot img,
.pw-device.is-dark .pw-powered img,
.pw-device.is-dark .pw-coupon img,
.pw-device.is-dark .pw-icon-btn img,
.pw-device.is-dark .pw-sheet-close img,
.pw-device.is-dark .pw-order-no img,
.pw-device.is-dark .pw-step:not(.is-current) img { filter: brightness(0) invert(0.96); }
.pw-device.is-dark .pw-step { border-color: #f5f5f6; }
.pw-device.is-dark .pw-step.is-current { background: #f5f5f6; }
.pw-device.is-dark .pw-step.is-current img { filter: brightness(0) invert(0.14); }
.pw-device.is-dark .pw-steps i { background: #f5f5f6; }

/* menu */
.pw-device.is-dark .pw-menu { background: #0b1220; color: #f5f5f6; }
.pw-device.is-dark .pw-menu-head { background: #111827; }
.pw-device.is-dark .pw-menu-list li { border-bottom-color: #334155; }
.pw-device.is-dark .pw-menu-list button:hover { background: #111827; }
.pw-device.is-dark .pw-menu-foot { color: #cbd5e1; }
.pw-device.is-dark .pw-lang li.is-active button { background: #111827; }
.pw-device.is-dark .pw-lang li.is-active button::after {
  background: #75bf98;
  -webkit-mask: url("../assets/projects/pw-menu/check.svg") center / 24px no-repeat;
  mask: url("../assets/projects/pw-menu/check.svg") center / 24px no-repeat;
}

/* screens */
.pw-device.is-dark .pw-screen { background: #0b1220; color: #f5f5f6; }
.pw-device.is-dark .pw-page-title { color: #f5f5f6; }
.pw-device.is-dark .pw-alert { background: #1f2937; border-color: #3fa670; }
.pw-device.is-dark .pw-alert-copy strong { color: #f5f5f6; }
.pw-device.is-dark .pw-alert-copy p { color: #cbd5e1; }
.pw-device.is-dark .pw-order-head > span:first-child { color: #cbd5e1; }
.pw-device.is-dark .pw-order-head i, .pw-device.is-dark .pw-item, .pw-device.is-dark .pw-steps,
.pw-device.is-dark .pw-coupon, .pw-device.is-dark .pw-totals { border-color: #334155; }
.pw-device.is-dark .pw-chip { background: #1f2937; color: #f5f5f6; }
.pw-device.is-dark .pw-order dt { color: #cbd5e1; }
.pw-device.is-dark .pw-order dd { color: #f5f5f6; }
.pw-device.is-dark .pw-empty-title { color: #f5f5f6; }
.pw-device.is-dark .pw-empty-copy, .pw-device.is-dark .pw-confirm-copy p { color: #cbd5e1; }
.pw-device.is-dark .pw-confirm-title { color: #f5f5f6 !important; }
.pw-device.is-dark .pw-order-no { background: #1f2937; color: #64748b; }
.pw-device.is-dark .pw-order-no b { color: #f5f5f6; }
.pw-device.is-dark .pw-item-inner { background: #0b1220; }
.pw-device.is-dark .pw-item-row { color: #f5f5f6; }
.pw-device.is-dark button.pw-coupon { background: #0b1220; color: #f5f5f6; }
.pw-device.is-dark .pw-totals > div > span:first-child { color: #cbd5e1; }
.pw-device.is-dark .pw-totals > div > span:last-child, .pw-device.is-dark .pw-totals-sum > span { color: #f5f5f6 !important; }
.pw-device.is-dark .pw-coupon-chip { background: #acd9c1; color: #19422d; }
.pw-device.is-dark .pw-coupon-chip img { filter: brightness(0) invert(0.2); }
.pw-device.is-dark .pw-field > span { color: #f5f5f6; }
.pw-device.is-dark .pw-field input { background: #1f2937; border-color: #334155; color: #f5f5f6; }
.pw-device.is-dark .pw-field input::placeholder { color: #64748b; }
.pw-device.is-dark .pw-field input:focus { border-color: #3fa670; }
.pw-device.is-dark .pw-field.is-error input { border-color: #fca5a5; }
.pw-device.is-dark .pw-field small { color: #fca5a5; }
.pw-device.is-dark .pw-page--gateway { background: #111827; border-color: #3fa670; }
.pw-device.is-dark .pw-page--gateway > p { color: #3fa670; }
.pw-device.is-dark .pw-page--gateway > img { filter: hue-rotate(-60deg) saturate(0.7); }
.pw-device.is-dark .pw-gateway-mock > span { color: #cbd5e1; }

/* sheets */
.pw-device.is-dark .pw-sheet { background: #111827; color: #f5f5f6; }
.pw-device.is-dark .pw-sheet-head { border-bottom-color: #334155; color: #f5f5f6; }
.pw-device.is-dark .pw-sheet-body, .pw-device.is-dark .pw-sheet-text, .pw-device.is-dark .pw-sheet-list { color: #cbd5e1; }
.pw-device.is-dark .pw-sheet-text--dark { color: #f5f5f6; }
.pw-device.is-dark .pw-package strong { color: #f5f5f6; }
.pw-device.is-dark .pw-package p { color: #cbd5e1; }
.pw-device.is-dark .pw-subtotal { background: #1f2937; color: #f5f5f6; }
@media (prefers-reduced-motion: reduce) {
  .pw-dim, .pw-menu, .pw-sheet { transition: none; }
}

.pw-demo figcaption {
  margin-top: 1rem;
  color: var(--fg-dim);
  max-width: 44rem;
  margin-inline: auto;
  text-align: center;
}

/* beside the copy the demo fills its column; the caption keeps the text measure */
.story-split > .pw-demo { margin-bottom: 0; }
.story-split > .pw-demo figcaption { max-width: none; }

/* ------------------------------------------------------------------
   Rail collapse control. Folded, the panel keeps only the toggle and the
   position counter, so it stops covering the page but still says where
   you are; the arrows and the list come back on expand.
   ------------------------------------------------------------------ */
.case-legend-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.9rem;
  width: 100%;
  cursor: pointer;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 0.6rem 0.8rem;
  color: var(--fg-dim);
  transition: color 0.25s;
}
.case-legend-toggle:hover { color: var(--fg); }
.case-legend-toggle .case-legend-count { margin-left: 0; }

.case-legend-chevron {
  width: 8px;
  height: 8px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(225deg);   /* points up when open */
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.case-legend.is-collapsed .case-legend-chevron {
  transform: translateY(-2px) rotate(45deg);    /* points down when folded */
}

.case-legend.is-collapsed ol,
.case-legend.is-collapsed .case-legend-nav { display: none; }
.case-legend.is-collapsed .case-legend-toggle { border-bottom: 0; }

/* folded, the numerals-only tier has nothing left to show but the chevron */
@media (max-width: 1100px) {
  .case-legend-toggle { justify-content: center; }
  .case-legend-toggle .case-legend-count { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .case-legend-chevron { transition: none; }
}
