/* ============ Base ============ */
/* Visually hidden, still read by crawlers and screen readers (the page's h1) */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

:root {
  --font-body: "Inter", sans-serif;
  --font-label: "IBM Plex Sans Condensed", sans-serif;
  --text-xs: 12px;
  --pad: 24px;
  --gap: 10px;
  /* Easing curves lifted from the reference Framer site */
  --ease-appear: cubic-bezier(0.78, 0.02, 0.16, 0.99);
  --ease-soft: cubic-bezier(0.22, 0.01, 0.73, 0.98);

  /* Theme (light default; [data-theme="dark"] flips the palette) */
  color-scheme: light;
  --bg: #ffffff;
  --ink: #000000;
  --muted: #6f6f6f; /* ~4.9:1 on white — was #9a9a9a (2.8:1, failed WCAG AA) */
  --hairline: rgba(0, 0, 0, 0.08);
  --frost: rgba(255, 255, 255, 0.72);
  --ink-soft: rgba(0, 0, 0, 0.4);
  --ink-faint: rgba(0, 0, 0, 0.28);
  --surface: #ffffff;
  --surface-2: #f5f5f6;
  --surface-3: #ececee;
  --muted-2: #7c7c80;
  --backdrop: rgba(0, 0, 0, 0.45);
  --veil-weak: rgba(255, 255, 255, 0.05);
  --veil-strong: rgba(255, 255, 255, 0.35);
  /* Accent: blue in light, red in dark (map markers + cursor pill) */
  --city: #0000fe;
  --city-glow-a: rgba(0, 0, 254, 0.55);
  --city-glow-b: rgba(0, 0, 254, 0.26);
  --pill-bg: #0000fe;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0e0e0e;
  --ink: #f2f2f2;
  --muted: #8a8a8a;
  --hairline: rgba(255, 255, 255, 0.14);
  --frost: rgba(14, 14, 14, 0.72);
  --ink-soft: rgba(255, 255, 255, 0.48);
  --ink-faint: rgba(255, 255, 255, 0.3);
  --surface: #1a1a1c;
  --surface-2: #26262a;
  --surface-3: #323238;
  --muted-2: #9a9aa0;
  --backdrop: rgba(0, 0, 0, 0.6);
  --veil-weak: rgba(14, 14, 14, 0.12);
  --veil-strong: rgba(14, 14, 14, 0.55);
  --city: #ffffff;
  --city-glow-a: rgba(255, 255, 255, 0.55);
  --city-glow-b: rgba(255, 255, 255, 0.28);
  --pill-bg: #ff0101;
}

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

html {
  scroll-behavior: smooth;
  /* .site-header jest position:fixed (47 px), więc każde scrollIntoView
     wsuwało górę treści POD pasek. scroll-padding zatrzymuje przewijanie
     poniżej niego. */
  scroll-padding-top: 56px;
}

body {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 400;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.35s ease, color 0.35s ease;
}

img { display: block; }

a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; }

.rule {
  border: none;
  border-top: 1px solid var(--ink);
  width: 100%;
}

/* CTA affordance: every clickable text carries a persistent underline
   that retracts on hover — plain text has none */
a,
.text-cta {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: left 100%;
  background-size: 100% 1px;
  transition: background-size 0.4s var(--ease-soft);
}
a:hover,
.text-cta:hover {
  text-decoration: none;
  background-size: 0% 1px;
}

/* Cursor pill: trails the pointer over clickable imagery (cursor.js) */
.cursor-pill {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 90;
  padding: 9px 16px;
  border-radius: 999px;
  /* Monochromatic + always readable: cursor.js samples the image under the
     pill and flips it black/white with contrasting text (adaptive, like a
     difference blend, but guaranteed legible over any tone) */
  background: #fff;
  color: #000;
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  scale: 0.6;
  transition: opacity 0.25s ease, scale 0.3s var(--ease-soft),
    background-color 0.18s ease, color 0.18s ease;
}

.cursor-pill.is-on {
  opacity: 1;
  scale: 1;
}

/* ============ Loader ============ */
.loader {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding: var(--pad);
  background: var(--bg);
  font-size: var(--text-xs);
  transition: transform 0.9s var(--ease-appear);
}

.loader.is-done {
  transform: translateY(-101%);
}

.loader-clock {
  position: absolute;
  top: var(--pad);
  right: var(--pad);
  font-variant-numeric: tabular-nums;
}

/* The 0→100 counter sits dead centre of the screen */
.loader-count {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-variant-numeric: tabular-nums;
}

/* ============ Appear / reveal animations ============ */
[data-reveal] {
  opacity: 0.001;
  transform: translateY(30px);
  transition:
    opacity 0.8s var(--ease-soft),
    transform 0.8s var(--ease-soft);
  transition-delay: var(--reveal-delay, 0s);
}

[data-reveal].in-view {
  opacity: 1;
  transform: translateY(0);
}

/* Rules/lines draw in from the left instead of sliding up */
[data-reveal][data-reveal-variant="line"] {
  transform: none;
  transform-origin: left center;
  scale: 0 1;
  transition: opacity 0.8s var(--ease-soft), scale 1s var(--ease-appear);
}
[data-reveal][data-reveal-variant="line"].in-view {
  scale: 1 1;
}

/* ============ Sticky site header ============ */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px var(--pad);
  /* Difference blend: black over light content, white over dark images —
     always full contrast */
  color: #fff;
  mix-blend-mode: difference;
}

.site-header-right {
  display: flex;
  gap: 40px;
  align-items: center;
}

.site-header-contact {
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  background-color: transparent;
}

/* Theme switch: a half-filled disc, same quiet scale as the header labels.
   Padding + negative margin grow the hit target without moving the icon */
.theme-toggle {
  display: flex;
  align-items: center;
  border: none;
  padding: 10px;
  margin: -10px;
  background-color: transparent;
  color: inherit;
  cursor: pointer;
}
.theme-toggle svg {
  display: block;
}

/* ============ Hero: scattered image grid ============ */
.hero {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  /* Squares touch each other — no separation, cells stay perfectly square */
  gap: 0;
  /* First row sits flush with the very top of the screen */
  align-content: start;
  /* EXACTLY one screen: main.js appends filler rows until the scatter covers
     the viewport height, and the last partial row crops here — a fixed height
     (not min-) keeps the sticky deck timing stable and stops a grown grid
     from feeding ever-larger heights back into the row math */
  height: 100svh;
  overflow: hidden;
  background: var(--bg);
}

.hero-cell {
  position: relative;
  overflow: hidden;
  /* Cells stay square at every viewport width — they scale with the
     column width (Framer-style proportional scaling), never stretch */
  aspect-ratio: 1 / 1;
  /* Intro state: each cell fades in on load, staggered by --intro-delay (set in JS) */
  opacity: 0.001;
  transform: translateY(10px);
  transition:
    opacity 0.8s var(--ease-appear),
    transform 0.8s var(--ease-appear);
  transition-delay: var(--intro-delay, 0s);
}

body.is-loaded .hero-cell {
  opacity: 1;
  transform: translateY(0);
}

/* No-JS fallback: never leave the page blank */
@media (scripting: none) {
  .hero-cell, [data-reveal] { opacity: 1 !important; transform: none !important; scale: 1 1 !important; }
}

.hero-cell img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease-soft);
}

/* One framed cell: the whole photo shown on a mat. The mat colour MATCHES the
   photo's own background (#e3e3e3) and stays fixed in BOTH themes — a theme-
   dependent tint made the white-backed photo float on a dark box in dark mode */
.hero-cell--boxed {
  background: #e3e3e2;
}
.hero-cell--boxed img {
  object-fit: contain;
  padding: 2%;
}

.hero-cell:hover img {
  transform: scale(1.05);
}


/* Desktop cell placement (8 x 4) — mirrors the Figma frame */
.h-117 { grid-area: 1 / 1; }
.h-122 { grid-area: 1 / 3; }
.h-118 { grid-area: 1 / 6; }
/* Kept out of row 1: the top-right corner stays clear under the header nav */
.h-75  { grid-area: 4 / 2; }
.h-121 { grid-area: 2 / 2; }
.h-33  { grid-area: 2 / 4; }
.h-36  { grid-area: 2 / 7; }
.h-123 { grid-area: 3 / 1; }
.h-125 { grid-area: 3 / 2; }
.h-128 { grid-area: 3 / 3; }
.h-26  { grid-area: 3 / 5; }
.h-120 { grid-area: 4 / 4; }
.h-119 { grid-area: 4 / 6; }
.h-30  { grid-area: 4 / 8; }

/* Progressive blur along the hero's bottom edge: the cropped last row melts
   into softness instead of ending on a hard cut. Three stacked backdrop
   layers with growing radii, each gradient-masked, approximate a continuous
   0 -> 100 blur ramp (a single masked layer only fades one radius's opacity).
   Height rides the live cell size (--hero-cell set in main.js fillHero). */
.hero-veil {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(var(--hero-cell, 190px) / 3);
  pointer-events: none;
  z-index: 2;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 45%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 45%, #000 100%);
}
.hero-veil::before,
.hero-veil::after {
  content: "";
  position: absolute;
  inset: 0;
}
.hero-veil::before {
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 30%, #000 75%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 30%, #000 75%, #000 100%);
}
.hero-veil::after {
  -webkit-backdrop-filter: blur(44px);
  backdrop-filter: blur(44px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 62%, #000 96%);
  mask-image: linear-gradient(to bottom, transparent 62%, #000 96%);
}

/* ============ Intro ============ */
.intro {
  position: relative;
  min-height: 100svh;
  background: var(--bg);
}

.intro-meta {
  position: absolute;
  top: 46%;
  left: 0;
  right: 0;
  padding: 10px var(--pad);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.intro-meta-row {
  display: flex;
  gap: 100px;
}

.intro-meta-item {
  width: 90px;
  flex-shrink: 0;
  white-space: nowrap;
}

.intro-meta-desc {
  max-width: 424px;
}

/* ============ Gallery: statement + fan of frames on a wide arc ============ */
.gallery-wrap {
  position: relative;
  /* Extra height = scroll distance while the stage stays pinned */
  height: 480vh;
}

.gallery {
  position: sticky;
  top: 0;
  height: 100svh;
  background: var(--bg);
  overflow: hidden;
}

/* Side frames: frames touching the viewport edges dissolve into the
   background instead of getting clipped mid-photo */
.gallery::before,
.gallery::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(90px, 13vw, 240px);
  z-index: 2;
  pointer-events: none;
}

.gallery::before {
  left: 0;
  background: linear-gradient(to right, var(--bg) 18%, transparent);
}

.gallery::after {
  right: 0;
  background: linear-gradient(to left, var(--bg) 18%, transparent);
}

/* Bottom fade: a frame reaching the stage's lower edge (the birds shot at the
   end of the scroll) melts into the background instead of a hard photo cut.
   Solid bg at the very bottom -> transparent going up. Desktop arc only (the
   phone gallery is a flat horizontal strip with no vertical clip). */
.gallery-fade {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: clamp(120px, 20vh, 260px);
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(to top, var(--bg) 10%, transparent);
}
@media (max-width: 809.98px) {
  .gallery-fade { display: none; }
}

/* Small label above the statement, same family as "(Approach)".
   Centred via left/right + text-align (NOT transform) so the data-reveal
   slide-up doesn't clobber the horizontal centring and shove it off-axis
   from the statement below. */
.gallery-label {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(28% - clamp(72px, 9vh, 100px));
  text-align: center;
  font-family: var(--font-label);
  font-weight: 500;
}

/* The statement reveals word by word as the section scrolls (main.js).
   Approach voice, centred on the stage */
.gallery-statement {
  position: absolute;
  left: 50%;
  top: 28%;
  transform: translate(-50%, -50%);
  width: min(720px, 86vw);
  text-align: center;
  font-size: clamp(18px, 1.6vw + 6px, 28px);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.5;
}

.gallery-statement .w {
  opacity: 0.14;
}

/* The fan: items sit on a circle whose centre lies far below the stage
   (main.js keeps --arc-shift = R - PEEK) and ride its rim via
   rotate + translateY(-R) */
.arc {
  position: absolute;
  left: 50%;
  top: calc(100% + var(--arc-shift, 900px));
  width: 0;
  height: 0;
}

.arc-item {
  position: absolute;
  left: 0;
  top: 0;
  width: min(19vw, 230px);
  margin: 0;
}

.arc-item img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  /* Soften the frame's inner (lower) edge so a frame floating over the
     background dissolves into it instead of ending on a hard photo cut.
     The mask rotates WITH the frame, so it always fades the edge nearest
     the arc centre — the one that shows the cut — at every viewport size. */
  -webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent 97%);
  mask-image: linear-gradient(to bottom, #000 72%, transparent 97%);
}

/* Phones + reduced motion: the fan flattens into a snap filmstrip */
@media (max-width: 809.98px), (prefers-reduced-motion: reduce) {
  .gallery-wrap {
    height: auto;
  }

  .gallery {
    position: static;
    height: auto;
    /* Generous top gap so the frames read as their own screen, well clear
       of the intro above (they used to peek at the bottom of the intro) */
    padding: clamp(96px, 16svh, 180px) 0 24px;
    overflow: visible;
  }

  .gallery::before,
  .gallery::after {
    display: none;
  }

  /* Match the Approach layout: label top-left, statement left-aligned with
     a hanging indent (not centred) */
  .gallery-label {
    position: static;
    transform: none;
    margin-bottom: 20px;
    padding: 0 var(--pad);
    text-align: left;
  }

  .gallery-statement {
    position: static;
    transform: none;
    width: auto;
    padding: 0 var(--pad);
    margin-bottom: 40px;
    text-align: left;
    text-indent: clamp(80px, 25vw, 160px);
    font-size: clamp(18px, 1.6vw + 6px, 28px);
  }

  .gallery-statement .w {
    opacity: 1 !important;
  }

  .arc {
    position: static;
    width: auto;
    height: auto;
    display: flex;
    gap: 10px;
    padding: 12px 0;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
  }
  .arc::-webkit-scrollbar { display: none; }

  .arc-item {
    position: static;
    min-width: min(74vw, 340px);
    transform: none !important;
    opacity: 1 !important;
    scroll-snap-align: center;
  }

  /* Flat filmstrip: full square cards, no inner-edge fade */
  .arc-item img {
    -webkit-mask-image: none;
    mask-image: none;
  }
}

/* ============ Atlas: the map of home ============ */
.atlas {
  position: relative;
  z-index: 1;
  background: var(--bg);
  padding: 120px 0 0;
  overflow: hidden;
}

/* Section label above the copy, same treatment as "(Approach)" */
.atlas-label {
  font-family: var(--font-label);
  font-weight: 500;
  text-align: center;
  margin-bottom: 16px;
}

.atlas-copy {
  max-width: 580px;
  margin: 0 auto 40px;
  padding: 0 var(--pad);
  text-align: center;
  font-size: clamp(17px, 1.1vw + 9px, 25px);
  line-height: 1.55;
}

/* Full-bleed map: the stipple fades out before it reaches any edge,
   so the section reads as open space rather than a framed image */
.atlas-stage {
  position: relative;
  width: 100%;
}

.atlas-map img,
.atlas-map canvas {
  display: block;
  width: 100%;
}

:root[data-theme="dark"] .atlas-map img {
  filter: invert(1);
}

/* City clusters are painted on the canvas itself (accent colour, they
   scatter from the cursor like the rest of the stipple) — see main.js */

/* ============ Sticky section deck ("lepki" scroll) ============
   The hero pins and the intro slides over it. The intro itself is NOT
   pinned — its text scrolls away naturally, keeping negative space
   between the copy and the incoming gallery images. */
.hero {
  position: sticky;
  top: 0;
}

.about {
  position: relative;
  z-index: 1;
}

/* ============ About / Approach ============ */
.about {
  background: var(--bg);
}

.about-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 50px;
  max-width: 894px;
  margin-left: auto;
  padding: 96px 48px 0 var(--pad);
}

.about-label {
  font-family: var(--font-label);
  font-weight: 500;
}

.about-inner > .about-label {
  position: absolute;
  top: 96px;
  left: var(--pad);
}

.about-statement {
  font-size: clamp(18px, 1.6vw + 6px, 28px);
  text-indent: clamp(120px, 30vw, 455px);
  padding-top: 60px;
}

.about-contact {
  display: flex;
  gap: 96px;
}

.about-contact-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 214px;
}

/* ============ Featured projects ============ */
.featured {
  margin-top: 96px;
}

/* Desktop slideshow: pinned caption left, images swap on scroll (right) */
.featured-show-wrap {
  display: none;
}

@media (min-width: 810px) {
  .featured-show-wrap {
    display: block;
    position: relative;
    height: 460svh;
  }

  /* Breathing room between the slideshow and the carousel, sized so the
     heading + carousel + footer (≥490px at every desktop width) fill a
     full screen at the very bottom — the last slide can never peek */
  .featured-show-wrap ~ .featured-heading {
    margin-top: max(96px, calc(100vh - 460px));
    margin-top: max(96px, calc(100svh - 460px));
  }

  .featured-show {
    position: sticky;
    top: 0;
    height: 100svh;
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: stretch;
    /* Top padding keeps the project name ≥72px below the fixed header */
    padding: 60px 0 var(--pad);
    background: var(--bg);
    overflow: hidden;
  }

  .featured-show-info {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 0 var(--pad);
  }

  .featured-title {
    font-family: var(--font-label);
    font-weight: 500;
  }

  #fs-name {
    font-size: clamp(20px, 1.6vw + 8px, 30px);
    font-weight: 400;
    letter-spacing: -0.01em;
    margin-top: 24px;
  }

  #fs-count {
    color: var(--muted);
    font-variant-numeric: tabular-nums;
  }

  /* The link sits right below the counter, where the eye already is */
  #fs-link {
    align-self: flex-start;
    margin-top: 32px;
  }

  .featured-show-media {
    display: flex;
    justify-content: center;
    align-items: flex-end;
    padding-right: var(--pad);
  }

  .featured-show-media a {
    position: relative;
    display: block;
    /* As tall as the column allows (never above the viewport), shrinking
       when the half-width column can't fit the 257/343 frame */
    height: min(100%, calc((50vw - var(--pad)) * 343 / 257));
    max-height: 100svh;
    aspect-ratio: 257 / 343;
    overflow: hidden;
    background-image: none;
  }

  .featured-show-media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.45s var(--ease-soft);
  }

  .featured-show-media img.is-active {
    opacity: 1;
  }
}

.featured-heading {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 10px var(--pad);
}

.featured-strip {
  padding: 12px 0;
  overflow: hidden;
  /* Fade the cards into the background at both edges (the arrows sit on the
     wrapper outside this element, so they stay fully opaque) */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}

.featured-track {
  display: flex;
  gap: 20px;
  width: max-content;
  animation: featured-marquee 60s linear infinite;
}

/* Shift by exactly one set (half the track + half the joining gap) */
@keyframes featured-marquee {
  to { transform: translateX(calc(-50% - 10px)); }
}

.featured-strip:hover .featured-track,
.featured-strip:active .featured-track {
  animation-play-state: paused;
}

.featured-item {
  display: block;
  width: clamp(200px, 17vw, 258px);
  aspect-ratio: 257 / 343;
  overflow: hidden;
  flex-shrink: 0;
  background-image: none;
}

.featured-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease-soft);
}

.featured-item:hover img {
  transform: scale(1.05);
}

/* Grab-and-fling carousel: main.js takes the marquee over with a JS
   transform loop so the strip can be dragged (and thrown, with momentum)
   on both pointer and touch. Vertical page scroll is preserved via
   touch-action: pan-y. */
.featured-strip.is-draggable {
  cursor: grab;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}
.featured-strip.is-draggable:active {
  cursor: grabbing;
}
.featured-strip.is-draggable .featured-track {
  /* JS drives the position now */
  animation: none;
}
.featured-strip.is-draggable img {
  -webkit-user-drag: none;
  pointer-events: none;
}

/* Side arrows overlaid on the carousel photos — mobile only. They sit on
   the non-scrolling .featured-carousel wrapper (NOT inside .featured-strip,
   which becomes a scroll container under reduced motion) so they stay
   pinned to the photo edges. */
.carousel-arrow { display: none; }

@media (max-width: 809.98px) {
  .featured-carousel { position: relative; }
  .carousel-arrow {
    display: grid;
    place-items: center;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    width: 40px;
    height: 40px;
    border: none;
    border-radius: 50%;
    background: var(--frost);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    color: var(--ink);
    cursor: pointer;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.15s ease;
  }
  .carousel-arrow--prev { left: 10px; }
  .carousel-arrow--next { right: 10px; }
  .carousel-arrow:active { transform: translateY(-50%) scale(0.9); }
  .carousel-arrow svg { display: block; }
}

/* ============ Footer ============ */
.footer {
  /* Body-level landmark (moved out of .about for a11y) — needs the same
     opaque cover + stacking over the sticky hero that .about provided */
  position: relative;
  z-index: 1;
  background: var(--bg);
  padding: 48px var(--pad) 24px;
}

.footer-name {
  font-family: var(--font-label);
  font-weight: 500;
  text-align: right;
  margin-bottom: 10px;
}

.footer-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.footer-cols {
  display: flex;
  gap: clamp(80px, 18vw, 280px);
}

.footer-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* ============ Frames filmstrip nav (mobile) — arrows on the photo edges ============ */
.arc-nav-btn { display: none; }

@media (max-width: 809.98px) {
  /* Relative only on phones — on desktop .arc is the absolute fan and must
     keep positioning against .gallery, so .arc-wrap stays static there */
  .arc-wrap { position: relative; }
  .arc-nav-btn {
    display: grid;
    place-items: center;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    width: 42px;
    height: 42px;
    border: none;
    border-radius: 50%;
    background: var(--frost);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    color: var(--ink);
    cursor: pointer;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.15s ease;
  }
  .arc-nav-btn--prev { left: 12px; }
  .arc-nav-btn--next { right: 12px; }
  .arc-nav-btn:active { transform: translateY(-50%) scale(0.9); }
  .arc-nav-btn svg { display: block; }
  /* At the ends the unusable arrow greys out and stops responding */
  .arc-nav-btn.is-disabled {
    opacity: 0.3;
    pointer-events: none;
  }
}

/* Dots for the frames filmstrip (mobile only) */
.arc-dots { display: none; }

@media (max-width: 809.98px) {
  .arc-dots {
    display: flex;
    justify-content: center;
    gap: 7px;
    padding: 22px var(--pad) 0;
  }
  .arc-dot {
    width: 7px;
    height: 7px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: var(--ink-faint);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.25s ease, transform 0.25s ease;
  }
  .arc-dot.is-active {
    background: var(--ink);
    transform: scale(1.3);
  }
}

/* ============ Floating scroll navigator (mobile) ============ */
.scroll-nav { display: none; }

/* .98 so there's no fractional-width band (e.g. 809.5px under zoom) where
   neither this nor the desktop .scroll-cue (min-width:810) would show */
@media (max-width: 809.98px) {
  .scroll-nav {
    position: fixed;
    left: 50%;
    bottom: calc(18px + env(safe-area-inset-bottom, 0px));
    z-index: 45;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 11px 18px;
    border: 1px solid var(--hairline);
    border-radius: 999px;
    background: var(--frost);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    color: var(--ink);
    font-family: var(--font-label);
    font-weight: 500;
    font-size: 12px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    cursor: pointer;
    box-shadow: 0 8px 26px rgba(0, 0, 0, 0.14);
    -webkit-tap-highlight-color: transparent;
    /* Hidden until the loader lifts (JS adds .is-ready) */
    opacity: 0;
    transform: translate(-50%, 14px);
    transition:
      opacity 0.5s var(--ease-soft),
      transform 0.5s var(--ease-soft),
      border-color 0.25s ease;
  }
  .scroll-nav.is-ready {
    opacity: 1;
    transform: translate(-50%, 0);
  }
  .scroll-nav.is-hidden {
    opacity: 0;
    transform: translate(-50%, 14px);
    pointer-events: none;
  }
  .scroll-nav-arrow {
    display: block;
    transition: transform 0.35s var(--ease-appear);
  }
  .scroll-nav.is-up .scroll-nav-arrow {
    transform: rotate(180deg);
  }
  /* Label morph: fades + lifts out, text swaps while invisible, fades back */
  .scroll-nav-label {
    display: inline-block;
    transition: opacity 0.16s var(--ease-soft), transform 0.16s var(--ease-soft);
  }
  .scroll-nav-label.is-morphing {
    opacity: 0;
    transform: translateY(-5px);
  }
}

/* ============ Desktop scroll cue ("scroll down to the frames") ============ */
.scroll-cue { display: none; }

@media (min-width: 810px) {
  .scroll-cue {
    position: fixed;
    left: 50%;
    bottom: 30px;
    transform: translateX(-50%);
    z-index: 35;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    border: none;
    background: transparent;
    /* Difference blend like the header — always legible over hero imagery
       and the plain intro alike */
    mix-blend-mode: difference;
    color: #fff;
    font-family: var(--font-label);
    font-weight: 500;
    font-size: 12px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.6s var(--ease-soft);
  }
  .scroll-cue.is-ready { opacity: 0.8; }
  .scroll-cue.is-ready:hover { opacity: 1; }
  .scroll-cue.is-hidden { opacity: 0; pointer-events: none; }
  .scroll-cue-arrow {
    display: block;
    animation: scroll-cue-bounce 1.8s var(--ease-soft) infinite;
  }
}

@keyframes scroll-cue-bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(5px); }
}

/* ============ Desktop CTA hover affordances ============
   Clear "this is clickable" feedback under a real pointer only — never on
   touch (a stuck :hover after a tap reads as a bug). Text CTAs keep their
   underline-retract; these add motion/emphasis to the button-like CTAs. */
@media (hover: hover) and (pointer: fine) {
  /* Text CTAs (CONTACT, PORTFOLIO, "(Contact Me)", View project, nav links)
     ALL share ONE hover language: the underline retracts (see a:hover /
     .text-cta:hover). No extra darkening — that made CONTACT behave unlike
     the other underlined items. Only the icon-only theme toggle, which has no
     underline to retract, gets a quick fade instead. */
  .theme-toggle { transition: opacity 0.2s ease; }
  .theme-toggle:hover { opacity: 0.6; }

  /* Desktop Frames cue: lifts toward the pointer, full ink, livelier arrow */
  .scroll-cue.is-ready {
    transition: opacity 0.6s var(--ease-soft), transform 0.3s var(--ease-soft);
  }
  .scroll-cue.is-ready:hover {
    opacity: 1;
    transform: translate(-50%, -4px);
  }
  .scroll-cue.is-ready:hover .scroll-cue-arrow { animation-duration: 1.1s; }

  /* Mobile "Frames/Intro" pill can surface on hybrid laptops — give it the
     same lift so it doesn't feel dead when hovered */
  .scroll-nav:hover {
    transform: translate(-50%, -3px);
    border-color: var(--ink-faint);
  }
}

/* ============ Contact sheet (iOS-style bottom sheet) ============ */
.contact-open {
  background-color: transparent;
  border: none;
  padding: 0;
  font: inherit;
  font-family: var(--font-label);
  font-weight: 500;
  /* Hug the text so the persistent underline sits right under it —
     without this the button stretches to the flex row height and the
     underline lands far below the label */
  line-height: 1;
  align-self: flex-start;
  color: inherit;
  cursor: pointer;
  text-align: left;
}

.contact-sheet {
  position: fixed;
  inset: auto 12px 12px;
  margin: 0 auto;
  /* One width for phone + desktop so the card looks the same on both */
  width: min(360px, calc(100vw - 24px));
  border: none;
  border-radius: 36px;
  padding: 20px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--hairline);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
}

.contact-sheet[open] {
  animation: sheet-in 0.45s var(--ease-appear);
}

/* Desktop/tablet: the dialog sits centered instead of docking to the bottom.
   Same width as the phone sheet (base rule) so they look unified. */
@media (min-width: 810px) {
  .contact-sheet {
    inset: 0;
    margin: auto;
    height: fit-content;
  }
  .contact-sheet[open] {
    animation: sheet-in-center 0.4s var(--ease-appear);
  }
  .contact-sheet.is-closing {
    animation: sheet-out-center 0.22s ease-in forwards;
  }
}

@keyframes sheet-in-center {
  from { opacity: 0; transform: translateY(14px) scale(0.96); }
}
@keyframes sheet-out-center {
  to { opacity: 0; transform: translateY(10px) scale(0.97); }
}
.contact-sheet.is-closing {
  animation: sheet-out 0.25s ease-in forwards;
}

@keyframes sheet-in {
  from { opacity: 0; transform: translateY(48px); }
}
@keyframes sheet-out {
  to { opacity: 0; transform: translateY(48px); }
}

.contact-sheet::backdrop {
  background: var(--backdrop, rgba(0, 0, 0, 0.45));
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.contact-sheet[open]::backdrop {
  animation: backdrop-in 0.35s ease;
}
.contact-sheet.is-closing::backdrop {
  animation: backdrop-out 0.25s ease-in forwards;
}
@keyframes backdrop-in {
  from { opacity: 0; }
}
@keyframes backdrop-out {
  to { opacity: 0; }
}

.sheet-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Even rhythm with the option gaps below (was 18) */
  margin-bottom: 14px;
}

/* Title styled like the site's section labels */
.sheet-title {
  font-family: var(--font-label);
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sheet-close {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--muted-2);
  cursor: pointer;
  transition: background 0.2s ease;
}
.sheet-close:hover {
  background: var(--surface-3);
}

.sheet-options {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.sheet-option {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 15px 16px;
  border: 1px solid var(--hairline);
  /* Rounder rows to sit well inside the more-rounded (36px) card */
  border-radius: 16px;
  background: transparent;
  background-image: none;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.25s ease, transform 0.15s ease;
}
.sheet-option:hover {
  border-color: var(--ink);
  text-decoration: none;
  background-image: none;
}
.sheet-option:active {
  transform: scale(0.98);
}
.sheet-option svg {
  flex-shrink: 0;
}

/* ============ Tablet (810px – 1199px), Framer breakpoint ============ */
@media (max-width: 1199px) and (min-width: 810px) {
  .intro-meta-row {
    gap: 48px;
  }

  .about-contact {
    gap: 48px;
  }

  /* Tighter hero grid: 6 columns, re-flow the two rightmost columns */
  .hero {
    grid-template-columns: repeat(6, 1fr);
  }
  .h-118 { grid-area: 1 / 5; }
  .h-75  { grid-area: 2 / 6; }
  .h-36  { grid-area: 2 / 5; }
  .h-119 { grid-area: 4 / 5; }
  .h-30  { grid-area: 5 / 6; }
  .h-120 { grid-area: 5 / 2; }

  .about-inner {
    max-width: none;
    padding: 96px var(--pad) 0;
  }
}

/* ============ Phone (<= 809px), Framer breakpoint ============ */
@media (max-width: 809.98px) {
  .hero {
    grid-template-columns: repeat(4, 1fr);
  }

  /* Re-scatter the images across a 4 x 7 grid */
  .h-117 { grid-area: 1 / 1; }
  .h-122 { grid-area: 1 / 3; }
  .h-121 { grid-area: 2 / 2; }
  .h-75  { grid-area: 2 / 4; }
  .h-123 { grid-area: 3 / 1; }
  .h-128 { grid-area: 3 / 3; }
  .h-33  { grid-area: 4 / 2; }
  .h-36  { grid-area: 4 / 4; }
  .h-26  { grid-area: 5 / 1; }
  .h-125 { grid-area: 5 / 3; }
  .h-118 { grid-area: 6 / 2; }
  .h-30  { grid-area: 6 / 4; }
  .h-120 { grid-area: 7 / 1; }
  .h-119 { grid-area: 7 / 3; }

  /* A full screen of its own — dvh tracks the *current* viewport (URL bar
     in or out), so the intro always fills the screen and the frames below
     never peek in at the bottom, without leaving a dead gap either */
  .intro {
    min-height: 100svh;
    /* lvh, NIE dvh: dvh zmienia się w trakcie chowania paska adresu w iOS
       Safari, więc każda sekcja poniżej .intro przesuwała się pod palcem
       czytelnika w trakcie przewijania. lvh jest stałe (wysokość przy
       schowanych paskach), więc układ nie drga, a galeria dalej nie
       podgląda od dołu (zmierzone 0 px w obu stanach paska). */
    min-height: 100lvh;
  }

  /* Role + city side by side, description below */
  .intro-meta-row {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 40px;
  }

  .intro-meta-item {
    width: auto;
  }

  .intro-meta-desc {
    width: 100%;
    margin-top: 4px;
  }

  /* (Gallery filmstrip rules live in the shared
     max-width 809 / reduced-motion block above) */

  .about-inner {
    padding: 64px var(--pad) 0;
    gap: 32px;
  }

  .about-inner > .about-label {
    position: static;
  }

  .about-statement {
    padding-top: 0;
    text-indent: clamp(80px, 25vw, 160px);
  }

  .about-contact {
    flex-direction: column;
    gap: 16px;
  }

  /* Sized like the desktop slideshow gap: heading + carousel + footer
     (~500-600px depending on width) fill the last screen, so at the very
     bottom the about text never parks under the fixed header */
  .featured {
    margin-top: max(96px, calc(100vh - 460px));
    margin-top: max(96px, calc(100svh - 460px));
  }

  .featured-item {
    width: min(58vw, 260px);
  }

  .footer-cols {
    flex-direction: column;
    gap: 12px;
  }

  /* Slimmer header: drop the non-essential label on small screens */
  .site-header-label {
    display: none;
  }
  .site-header-right {
    gap: 16px;
  }

  /* Frosted-glass header instead of the difference blend: on touch
     screens the bar needs a real backdrop to stay readable */
  .site-header {
    mix-blend-mode: normal;
    color: var(--ink);
    background: var(--frost);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
  }

  /* The city starts in the same column as CONTACT in the header above
     (--contact-left is kept in sync by main.js) */
  .intro-meta-row {
    position: relative;
  }
  .intro-meta-row [data-i18n="intro-city"] {
    position: absolute;
    top: 0;
    /* --contact-left is a viewport coordinate; the row starts at --pad */
    left: calc(var(--contact-left, 62%) - var(--pad));
  }

  /* Milder reveal travel on small screens */
  [data-reveal] {
    transform: translateY(20px);
  }
  [data-reveal][data-reveal-variant="line"] {
    transform: none;
  }
}

/* ============ Reduced motion ============ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .hero-cell,
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    scale: 1 1 !important;
    transition: none !important;
  }

  /* Static, manually scrollable strip instead of the marquee */
  .featured-track {
    animation: none;
  }
  .scroll-cue-arrow {
    animation: none;
  }
  .featured-strip {
    overflow-x: auto;
    scrollbar-width: none;
  }
  .featured-strip::-webkit-scrollbar { display: none; }

  .hero-cell img,
  .featured-item img,
  a {
    transition: none !important;
  }

  .hero-cell:hover img,
  .featured-item:hover img {
    transform: none;
  }

  .contact-sheet[open],
  .contact-sheet[open]::backdrop {
    animation: none;
  }

  .loader {
    display: none;
  }
}
