/* ===== fonts ===== */
@font-face { font-family: 'Fraunces'; src: url('../fonts/fraunces-latin.woff2') format('woff2'); font-weight: 300 700; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2013-2014, U+2018-201A, U+201C-201E, U+2026; }
@font-face { font-family: 'Fraunces'; src: url('../fonts/fraunces-latin-ext.woff2') format('woff2'); font-weight: 300 700; font-display: swap; unicode-range: U+0100-024F, U+1E00-1EFF, U+0259; }
@font-face { font-family: 'Inter'; src: url('../fonts/inter-latin.woff2') format('woff2'); font-weight: 100 900; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2013-2014, U+2018-201A, U+201C-201E, U+2026; }
@font-face { font-family: 'Inter'; src: url('../fonts/inter-latin-ext.woff2') format('woff2'); font-weight: 100 900; font-display: swap; unicode-range: U+0100-024F, U+1E00-1EFF, U+0259; }

/* ===== tokens ===== */
:root {
  --bg: #0A0A0A; --bg-2: #161616; --ink: #EDEDEF; --muted: #8A8A8E; --accent: #3B5BFF;
  --accent-text: #6E88FF;   /* lighter cobalt for small-text roles — clears WCAG AA (4.5:1) on --bg */
  --font-display: 'Fraunces', Georgia, serif;
  --font-body: 'Inter', -apple-system, sans-serif;
  --header-h: 72px;
  --space: clamp(20px, 4vw, 64px);
  --radius: 14px;
  --ease: cubic-bezier(.22, 1, .36, 1);
  --line-soft: rgba(237, 237, 239, .1);
  --bg-3: #1C1C1F; --line-2: rgba(237,237,239,.18);
  --maxw: 1280px;
}

/* ===== reset + base ===== */
*, *::before, *::after { box-sizing: border-box; margin: 0; }
html { scroll-behavior: smooth; scrollbar-width: none; -ms-overflow-style: none; }
html::-webkit-scrollbar { display: none; }
body { background: var(--bg); color: var(--ink); font-family: var(--font-body); line-height: 1.6; -webkit-font-smoothing: antialiased; overflow-x: clip; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }
ul, ol { list-style: none; padding: 0; }
::selection { background: var(--accent); color: #fff; }

/* ===== grain (kinowe ziarno ~4%) ===== */
/* Ziarno jest statyczne (brak keyframe'ów je ruszających), więc inset:0 +
   translateZ(0) rastrują szum RAZ do buforowanej tekstury GPU zamiast
   przemalowywać pełne, 1.5× powiększone pasmo. Wygląd identyczny: opacity .04,
   baseFrequency 0.9, pełne pokrycie (fixed na cały viewport). */
body::after {
  content: ''; position: fixed; inset: 0; z-index: 9999; pointer-events: none; opacity: .04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  transform: translateZ(0);
}

/* ===== skip link ===== */
.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 10000;
  padding: .7em 1.1em; border-radius: 8px;
  background: var(--accent); color: #fff;
  font-size: .9rem; font-weight: 600; text-decoration: none;
  /* iOS przesuwa warstwę fixed (klawiatura/rubber-band) — sam transform-parking
     wjeżdżał w kadr; opacity+pointer-events kryją go, nie zdejmując z tab-order */
  transform: translateY(-160%); opacity: 0; pointer-events: none;
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.skip-link:focus-visible { transform: translateY(0); opacity: 1; pointer-events: auto; outline: none; }

/* ===== a11y utility ===== */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ===== progress ===== */
.progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 9000;
  pointer-events: none;
}
.progress__bar {
  display: block; height: 100%;
  transform: scaleX(0); transform-origin: left;
  background: var(--accent); will-change: transform;
}

/* ===== header = „notch": jedna szklana płyta wrastająca w lewy górny róg =====
   Bez right:0 → szerokość z zawartości; margin -1px chowa obrys na krawędziach
   styku z ekranem; zaokrąglony WYŁĄCZNIE prawy dolny róg. Szkło stałe od
   wejścia (koniec materializacji po scrollu — po scrollu pogłębia się tylko
   cień). BEZ overflow:hidden / contain:paint — przycięłyby focus ringi
   i podkreślenia .nav__link::after (bottom:-5px). */
.header {
  position: fixed; top: 0; left: 0; z-index: 8000;
  height: var(--header-h);
  display: flex; align-items: center;
  gap: clamp(.6rem, 1.4vw, 1rem);
  padding: 0 clamp(20px, 2.4vw, 30px) 0 clamp(14px, 1.6vw, 22px);   /* lewy odstęp skrócony: logo bliżej krawędzi ekranu */
  margin: -1px 0 0 -1px;
  border: 1px solid rgba(255, 255, 255, .10);
  border-radius: 0 0 22px 0;
  background: rgba(18, 18, 20, .55);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .06) inset, 0 10px 30px rgba(0, 0, 0, .32);
  -webkit-backdrop-filter: blur(18px) saturate(1.3); backdrop-filter: blur(18px) saturate(1.3);
  transition: transform .45s cubic-bezier(.22, 1, .36, 1), box-shadow .45s var(--ease);
}
.header.is-hidden { transform: translateY(-100%); }
.header.is-scrolled { box-shadow: 0 1px 0 rgba(255, 255, 255, .06) inset, 0 14px 38px rgba(0, 0, 0, .42); }

/* wklęsłe łuki notcha — płyta przechodzi w krawędzie ekranu (spec „Karty" 2026-07-12).
   Szkło identyczne z płytą, wycięte maską do ćwiartki; hairline-łuk SVG kontynuuje
   obrys (stroke = kolor bordera płyty). Dzieci .header → jadą razem z .is-hidden.
   1px nakładki na border płyty rozmywa backdrop-filter filletu (zweryfikowane). */
.header__fillet {
  position: absolute; width: 22px; height: 22px; pointer-events: none;
  background: rgba(18, 18, 20, .55);
  -webkit-backdrop-filter: blur(18px) saturate(1.3); backdrop-filter: blur(18px) saturate(1.3);
  -webkit-mask: radial-gradient(circle 22px at 22px 22px, transparent 21.4px, #000 22px);
  mask: radial-gradient(circle 22px at 22px 22px, transparent 21.4px, #000 22px);
  transition: opacity .45s var(--ease);
}
.header__fillet svg { position: absolute; inset: 0; width: 22px; height: 22px; overflow: visible; }
.header__fillet path { fill: none; stroke: rgba(255, 255, 255, .10); stroke-width: 1; }
.header__fillet--r { top: 0; left: 100%; }
.header__fillet--b { top: 100%; left: 0; }
/* header schowany (scroll w dół): fillety gasną razem z płytą. Sam
   translateY(-100%) nie wystarcza — dolny fillet zwisa 22px POD płytą
   (top:100%), więc po zjeździe płyty zostawiłby ~20px szklanego łuku
   w lewym górnym rogu ekranu. Fade synchroniczny z .45s slajdem płyty. */
.header.is-hidden .header__fillet { opacity: 0; }

/* logo i nav leżą PŁASKO na płycie notcha (własne szkło zdjęte — plate ma je
   na sobie) na KAŻDEJ szerokości: mobile (≤760px) dziedziczy tę samą płytę,
   tylko z kompaktowymi paddingami (koniec „dwóch monet").
   translateZ(0) usunięty — blur siedzi teraz na .header, nie na dzieciach. */
.header__logo, .header__nav {
  border-radius: 999px;
  background: transparent; border: 1px solid transparent;
  transition: background .45s var(--ease), border-color .45s var(--ease),
              box-shadow .45s var(--ease);
}

/* logo — wordmark „JG" + kobaltowy separator (zamiast bańki) */
.header__logo {
  position: relative; display: inline-flex; align-items: center;
  font-family: var(--font-display); font-weight: 600;
  font-size: 1.3rem; letter-spacing: .01em; text-decoration: none;
  color: var(--ink); padding: 9px 0; will-change: transform;
}
.header__logo::after {
  content: ''; width: 2px; height: 1.05em; margin-left: .38em;
  border-radius: 2px; background: var(--accent); flex: none;
}
/* jawny ring fokusa — poza hover-gate ink-reveal; ujednolica afordancję z kartami
   i przełącznikiem języka (outline var(--accent-text)) i domyka WCAG 2.4.7 */
.header__logo:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 4px; }

/* wordmark = odręczny monogram „JG" (SVG, jeden currentColor).
   Dwie nałożone warstwy: biała baza + kobaltowa „warstwa atramentu", którą
   hover odsłania L→P (clip-path) — logo „podpisuje się" akcentem marki. */
.header__logo:has(.jg-mark)::after { content: none; }   /* monogram bez osobnego separatora */
.jg-mark {
  --jg-size: 2.15rem;
  position: relative; display: block; line-height: 0;
}
.jg-mark__layer { display: block; }
.jg-mark__base { height: var(--jg-size); width: auto; color: var(--ink); }  /* w flow → nadaje rozmiar */
.jg-mark__ink {
  position: absolute; inset: 0; height: 100%; width: 100%;   /* nakładka nad bazą */
  color: var(--accent);
  clip-path: inset(0 100% 0 0);            /* schowany do czasu najechania */
  transition: clip-path .62s var(--ease);
  will-change: clip-path; pointer-events: none;
}
@media (hover: hover) {
  .header__logo:hover .jg-mark__ink,
  .header__logo:focus-visible .jg-mark__ink { clip-path: inset(0 0 0 0); }
}
@media (prefers-reduced-motion: reduce) {
  .jg-mark__ink { clip-path: none; opacity: 0; transition: opacity .25s var(--ease); }
  .header__logo:hover .jg-mark__ink,
  .header__logo:focus-visible .jg-mark__ink { opacity: 1; }
}

/* menu — w notchu, na lewo, zaraz za logo */
.header__nav {
  display: flex; align-items: center; gap: 7px;
  padding: 9px 0;
}
/* separator logo ↔ linki: pionowa kreska jako pierwszy element flexa
   (gap headera daje jej symetryczny oddech z obu stron) */
.header__nav::before {
  content: ''; flex: none; width: 1px; height: 1.15em;
  background: rgba(255, 255, 255, .14);
}
/* linki nawigacji = karty dokumentów: spoczynek wyciszony, hover „duch",
   strona bieżąca = podniesiony klawisz z kobaltową ikoną (jedyny akcent).
   Ikony 1:1 ze sprite'u menu mobilnego; na wąskim desktopie etykieta znika
   wizualnie (media niżej), ale zostaje w drzewie dostępności. */
.nav__link {
  position: relative; display: inline-flex; align-items: center; gap: 8px;
  font-size: .78rem; font-weight: 500; letter-spacing: .01em;
  text-decoration: none; white-space: nowrap;
  color: var(--muted); background: transparent;
  padding: 8px 13px; border: 1px solid transparent; border-radius: 9px;
  transition: background .2s var(--ease), color .2s var(--ease);
}
.nav__link__ico { flex: none; width: 14px; height: 14px; transition: color .2s var(--ease); }
.nav__link:hover:not(.is-active) { background: rgba(255, 255, 255, .045); color: #CFCFD4; }
.nav__link.is-active {
  color: var(--ink); background: rgba(28, 28, 31, .92);
  border-color: rgba(255, 255, 255, .10);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .05) inset, 0 6px 18px rgba(0, 0, 0, .35);
}
.nav__link.is-active .nav__link__ico { color: var(--accent); }
.nav__link:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }

/* przełącznik języka: separator + dwie mini-karty tekstowe PL/EN (wszystkie strony) */
.lang-switch { display: inline-flex; align-items: center; gap: 6px; }
.lang-switch::before {
  content: ''; flex: none; width: 1px; height: 16px; margin-right: 6px;
  background: rgba(255, 255, 255, .14);
}
/* mini-karty języka (PL/EN) — typograficzny zamiennik flag */
.lang-switch__item {
  padding: 6px 10px; border-radius: 7px;
  font-size: .69rem; font-weight: 600; letter-spacing: .05em;
  color: var(--muted); text-decoration: none;
  transition: background .2s var(--ease), color .2s var(--ease);
}
.lang-switch__item:hover { color: #CFCFD4; }
.lang-switch__item.is-on { background: rgba(237, 237, 239, .08); color: var(--ink); cursor: default; }
.lang-switch__item:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }

/* wąski desktop / tablet poziomo: karty tylko z ikonami — etykieta znika
   wizualnie (klip .sr-only), zostaje w drzewie dostępności; title = tooltip.
   Galeria (dodatkowy filtr kategorii w listwie) chowa etykiety już ≤1180px. */
@media (max-width: 1080px) and (min-width: 761px) {
  .header { gap: .5rem; padding-right: 16px; }
  .header__nav { gap: 4px; }
  .nav__link { padding: 8px 11px; }
  .nav__link__ico { width: 16px; height: 16px; }
  .nav__link__label {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .lang-switch::before { margin-right: 4px; }
}
@media (max-width: 1180px) and (min-width: 1081px) {
  .header--gallery .nav__link { padding: 8px 11px; }
  .header--gallery .nav__link__ico { width: 16px; height: 16px; }
  .header--gallery .nav__link__label {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

/* ===== nav toggle (hamburger) — mobile only ===== */
.nav-toggle {
  display: none;                    /* shown ≤760px */
  position: relative; z-index: 8100;  /* above the overlay so it stays clickable */
  width: 44px; height: 44px; padding: 0;
  align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer; color: var(--ink);
}
.nav-toggle__line {
  position: absolute; left: 50%; width: 22px; height: 1.5px;
  background: currentColor; border-radius: 1px;
  transform: translateX(-50%);
  transition: transform .35s var(--ease), opacity .35s var(--ease);
}
.nav-toggle__line:nth-child(1) { transform: translate(-50%, -3.5px); }
.nav-toggle__line:nth-child(2) { transform: translate(-50%, 3.5px); }
.nav-toggle.is-open .nav-toggle__line:nth-child(1) { transform: translate(-50%, 0) rotate(45deg); }
.nav-toggle.is-open .nav-toggle__line:nth-child(2) { transform: translate(-50%, 0) rotate(-45deg); }

/* ===== mobile menu overlay ===== */
.mobile-menu {
  position: fixed; inset: 0; z-index: 7900;   /* below header (8000) so logo/toggle stay live */
  display: flex; flex-direction: column; justify-content: center;
  gap: clamp(1.6rem, 5vh, 3rem); overflow-y: auto;
  padding: calc(var(--header-h) + var(--space)) var(--space) calc(var(--space) + env(safe-area-inset-bottom));
  background: rgba(10, 10, 10, .97); backdrop-filter: blur(18px);
  opacity: 0; visibility: hidden;
  transition: opacity .45s var(--ease), visibility 0s linear .45s;
}
.mobile-menu.is-open {
  opacity: 1; visibility: visible;
  transition: opacity .45s var(--ease), visibility 0s;
}
.mobile-menu__nav { display: flex; flex-direction: column; gap: clamp(.35rem, 1.4vh, .9rem); }
.mobile-menu__link {
  display: flex; align-items: center; gap: clamp(.75rem, 3.6vw, 1.15rem);
  padding: clamp(.3rem, 1vh, .55rem) 0; border-radius: 10px;
  font-family: var(--font-display); font-weight: 550;
  font-size: clamp(2.1rem, 9.2vw, 3.1rem); line-height: 1.05; letter-spacing: -0.015em;
  color: var(--ink); text-decoration: none;
  opacity: 0; transform: translateY(14px);
  transition: opacity .5s var(--ease), transform .5s var(--ease), color .3s var(--ease);
}
.mobile-menu__ico {
  width: clamp(24px, 6.6vw, 30px); height: clamp(24px, 6.6vw, 30px);
  flex: none; color: var(--muted);
  transition: color .3s var(--ease), transform .3s var(--ease);
}
.mobile-menu__label { display: block; transition: transform .3s var(--ease); }
.mobile-menu.is-open .mobile-menu__link { opacity: 1; transform: none; }
.mobile-menu.is-open .mobile-menu__link:nth-child(1) { transition-delay: .08s; }
.mobile-menu.is-open .mobile-menu__link:nth-child(2) { transition-delay: .13s; }
.mobile-menu.is-open .mobile-menu__link:nth-child(3) { transition-delay: .18s; }
.mobile-menu.is-open .mobile-menu__link:nth-child(4) { transition-delay: .23s; }
.mobile-menu.is-open .mobile-menu__link:nth-child(5) { transition-delay: .28s; }
.mobile-menu.is-open .mobile-menu__link:nth-child(6) { transition-delay: .33s; }
@media (hover: hover) {
  .mobile-menu__link:hover { color: #fff; }
  .mobile-menu__link:hover .mobile-menu__ico { color: var(--accent); transform: scale(1.08); }
  .mobile-menu__link:hover .mobile-menu__label { transform: translateX(4px); }
}
.mobile-menu__link:focus-visible { outline: none; color: #fff; }
.mobile-menu__link:focus-visible .mobile-menu__ico { color: var(--accent); }
.mobile-menu__link[aria-current="page"] { color: #fff; }
.mobile-menu__link[aria-current="page"] .mobile-menu__ico { color: var(--accent); }

.mobile-menu__meta {
  display: flex; flex-direction: column; gap: .9rem;
  opacity: 0; transform: translateY(14px);
  transition: opacity .5s var(--ease) .34s, transform .5s var(--ease) .34s;
}
.mobile-menu.is-open .mobile-menu__meta { opacity: 1; transform: none; }
.mobile-menu__lang {
  align-self: flex-end;
  font-family: var(--font-body); font-weight: 600; font-size: .78rem; letter-spacing: .06em;
  color: var(--muted); text-decoration: none;
  padding: .38em .95em; border: 1px solid var(--line-soft); border-radius: 999px;
  transition: border-color .25s var(--ease), color .25s var(--ease);
}
.mobile-menu__lang:hover { border-color: var(--muted); color: var(--ink); }
.mobile-menu__cta { display: flex; flex-direction: column; gap: .7rem; }
.menu-btn {
  display: flex; align-items: center; gap: .8rem;
  padding: 1rem 1.15rem; border-radius: 15px;
  border: 1px solid var(--line-soft);
  font-family: var(--font-body); font-size: 1rem; color: var(--ink);
  text-decoration: none;
  transition: border-color .25s var(--ease), background-color .25s var(--ease), color .25s var(--ease);
}
.menu-btn__ico { width: 20px; height: 20px; flex: none; color: var(--muted); transition: color .25s var(--ease); }
.menu-btn > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-btn__lock { width: 16px; height: 16px; flex: none; color: var(--muted); opacity: .85; }
.menu-btn--mail {
  border-color: rgba(59, 91, 255, .5); background: rgba(59, 91, 255, .1);
  color: #fff; font-weight: 500;
}
.menu-btn--mail .menu-btn__ico { color: var(--accent); }
.menu-btn--mail:hover { border-color: var(--accent); background: rgba(59, 91, 255, .16); }
.menu-btn--phone:hover { border-color: var(--muted); color: #fff; }
.menu-btn--phone.is-unlocked .menu-btn__ico { color: var(--accent); }
.menu-btn--phone.is-unlocked .menu-btn__lock { display: none; }

/* scroll lock while overlay is open */
html.menu-open { overflow: hidden; }

/* ===== panel sukcesu — drawer po udanej wysyłce ===== */
html.panel-open { overflow: hidden; }
.cpanel { position: fixed; inset: 0; z-index: 9500; visibility: hidden; pointer-events: none; }
.cpanel[hidden] { display: none; }
.cpanel.is-open { visibility: visible; pointer-events: auto; }
.cpanel__scrim {
  position: absolute; inset: 0; background: rgba(10, 10, 10, .6);
  -webkit-backdrop-filter: blur(6px) saturate(1.1); backdrop-filter: blur(6px) saturate(1.1);
  opacity: 0; transition: opacity .35s var(--ease);
}
.cpanel.is-open .cpanel__scrim { opacity: 1; }
.cpanel__sheet {
  position: absolute; top: 0; left: 0; height: 100dvh; width: min(92vw, 440px);
  display: flex; flex-direction: column; gap: clamp(1rem, 3.4vh, 1.7rem);
  padding: clamp(1.6rem, 5vw, 2.6rem);
  padding-top: max(clamp(1.6rem, 5vw, 2.6rem), env(safe-area-inset-top, 0px));
  background: var(--bg-2); color: var(--ink);
  border-right: 1px solid var(--line-2); box-shadow: 24px 0 60px -20px rgba(0, 0, 0, .7);
  overflow-y: auto; overscroll-behavior: contain;
  transform: translateX(-100%); transition: transform .6s var(--ease);
}
.cpanel.is-open .cpanel__sheet { transform: translateX(0); }
/* GSAP steruje transformem/opacity inline → wyłącz CSS-transition, by nie animować podwójnie. */
.cpanel--gsap .cpanel__sheet, .cpanel--gsap .cpanel__scrim { transition: none; }
@media (min-width: 761px) { .cpanel__sheet { width: clamp(360px, 34vw, 480px); } }
@media (prefers-reduced-motion: reduce) { .cpanel__sheet, .cpanel__scrim { transition: none; } }
.cpanel__close {
  position: absolute; top: clamp(1rem, 3vw, 1.4rem); right: clamp(1rem, 3vw, 1.4rem);
  width: 40px; height: 40px; display: grid; place-items: center;
  background: transparent; border: 1px solid var(--line-2); border-radius: 50%;
  color: var(--muted); cursor: pointer;
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.cpanel__close:hover, .cpanel__close:focus-visible { color: var(--ink); border-color: var(--muted); outline: none; }
.cpanel__close-ico { width: 18px; height: 18px; }
.cpanel__eyebrow {
  margin: 0; font-family: var(--font-display); font-size: .8rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--accent-text);
}
.cpanel__title {
  margin: 0; font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1.7rem, 5.4vw, 2.3rem); line-height: 1.12; color: var(--ink);
}
.cpanel__lead { margin: 0; max-width: 34ch; font-size: .98rem; line-height: 1.6; color: var(--muted); }
.cpanel__call {
  display: flex; align-items: center; gap: .9rem; min-height: 56px;
  padding: clamp(.8rem, 2.6vw, 1.05rem) clamp(1rem, 3vw, 1.3rem);
  border: 1px solid var(--line-2); border-radius: var(--radius);
  background: rgba(237, 237, 239, .02); color: var(--ink); text-decoration: none;
  transition: border-color .3s var(--ease), background .3s var(--ease), transform .3s var(--ease);
}
.cpanel__call:hover, .cpanel__call:focus-visible {
  border-color: var(--accent); background: rgba(59, 91, 255, .06);
  transform: translateY(-1px); outline: none;
}
.cpanel__call-ico { width: 1.5rem; height: 1.5rem; flex: none; color: var(--accent-text); }
.cpanel__call-txt { display: flex; flex-direction: column; line-height: 1.1; }
.cpanel__call-kicker { font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.cpanel__call-num {
  font-family: var(--font-display); font-size: clamp(1.5rem, 6vw, 2rem);
  font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink);
}
.cpanel .signature--form { margin-top: auto; max-width: 260px; }

/* ===== layout helpers ===== */
.projects, .about, .contact { padding-inline: var(--space); }

/* ===== hero ===== */
/* Compositing order (z): bg (glow + scrim) < portrait (+ white halo) < veil < content.
   The section is full-bleed; content is constrained to --maxw, left-aligned and
   bottom-anchored. The portrait is a transparent cut-out backlit by a soft white
   halo, so its edge fringing reads as an intentional rim-light instead of a bad cut. */
.hero {
  position: relative;
  min-height: 100svh;
  overflow: hidden;                 /* clip portrait bleed + parallax */
  display: grid; align-content: end;
  padding-inline: var(--space);
  padding-top: calc(var(--header-h) + var(--space));
  padding-bottom: clamp(3rem, 9svh, 7rem);
}

/* legibility veil — sits above the portrait, below the text, so type keeps a
   dark backing wherever the composition tightens (mostly felt on mobile) */
.hero::before {
  content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(to top, rgba(10, 10, 10, .38), transparent 26%);
}
/* bottom-edge frost — a soft depth-of-field band along the hero's lower edge.
   Sits above the portrait/glow (z 3) but below the content (z 4), so the CTA
   and scroll cue stay crisp on a subtly frosted backing; masked to fade out
   toward the top so there's no hard seam. Disabled where blur is unsupported. */
.hero__fade {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  height: clamp(72px, 13vh, 140px); pointer-events: none;
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  -webkit-mask-image: linear-gradient(to top, #000 0, #000 20%, transparent 100%);
  mask-image: linear-gradient(to top, #000 0, #000 20%, transparent 100%);
}

/* -- background layer: warm ambient glow + grounding scrim, behind portrait -- */
.hero__bg {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  overflow: hidden;
}
.hero__field { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* rentgenowa soczewka: dysk portalu nad portretem (z2) — widoczny też na zdjęciu,
   pod tekstem/CTA (z4). Powierzchnia witrażu zostaje na .hero__field (z0). */
.hero__lens { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; pointer-events: none; }
.hero__cursor { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 5; pointer-events: none; }
.hero--lens { cursor: none; }
/* Ambient layers are horizontally offset via negative margins (not transform)
   so the transform property is left free for GSAP's parallax/entrance channels. */
.hero__glow {
  position: absolute; top: 2%; left: 64%; width: min(72vw, 720px); height: min(66vh, 680px);
  margin-left: calc(min(72vw, 720px) * -0.5);   /* bias behind the portrait */
  background: radial-gradient(ellipse 52% 48% at center, rgba(59, 91, 255, .10), transparent 70%);
  filter: blur(52px);
  will-change: transform, opacity;
}
/* grounding scrim: darkens the bottom-left (behind the name) so white type never
   competes with the ambient glow; portrait renders above it and stays bright */
.hero__scrim {
  position: absolute; inset: 0;
  background:
    radial-gradient(115% 85% at 8% 82%, rgba(0, 0, 0, .55), transparent 55%),
    linear-gradient(to top, rgba(0, 0, 0, .5), transparent 40%);
}

/* -- portrait: anchored to the section's bottom-right, backlit by a white halo -- */
.hero__portrait {
  position: absolute; z-index: 1; bottom: 0; right: 0;
  right: max(0px, calc((100% - var(--maxw)) / 2));  /* align to --maxw right edge */
  pointer-events: none;
  will-change: transform, opacity;
}
.hero__portrait img {
  position: relative; z-index: 1;
  height: clamp(440px, 82svh, 860px); width: auto; max-width: none;
  filter: contrast(1.02) saturate(.97);
  /* soft bottom fade so the cut edge never reads as a hard line under parallax */
  -webkit-mask-image: linear-gradient(to top, transparent 0, #000 10%);
  mask-image: linear-gradient(to top, transparent 0, #000 10%);
}

/* -- content: on top, constrained + centred like the rest of the site -- */
.hero__content {
  position: relative; z-index: 4;
  width: 100%; max-width: var(--maxw); margin: 0 auto;
}
.hero__eyebrow {
  display: inline-flex; align-items: center; gap: .9em;
  font-size: .8rem; font-weight: 500; letter-spacing: .22em; text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: clamp(1.1rem, 2.8vh, 1.9rem);
}
.hero__eyebrow::before {
  content: ''; width: 2.4rem; height: 1px;
  background: var(--accent); opacity: .55;
}
.eyebrow__pre { white-space: nowrap; }
/* Cycling word: hidden reserve variants stack in ONE grid cell, so the box holds
   the widest word's width. The live word is taken out of flow (absolute), so its
   scrambled glyphs — some wider than any real letter — can never grow the track
   or reflow the line. No mask / no overflow window, so no sibling can bleed. */
.eyebrow__cycle { position: relative; display: inline-grid; }
.eyebrow__cycle > * { grid-area: 1 / 1; justify-self: start; white-space: nowrap; }
.eyebrow__rsv { visibility: hidden; }
.eyebrow__live {
  position: absolute; left: 0; top: 0;
  color: var(--accent-text); transition: color .28s var(--ease);
}
/* During the mono-scramble the glyphs recede to muted, then snap back to cobalt
   on settle — a quiet chromatic "lock", the accent as a signature not a fill. */
.eyebrow__cycle.is-scrambling .eyebrow__live { color: var(--muted); }
/* mobile-only static stand-in for the cycler (shown where the cycle is hidden) */
.eyebrow__fixed { display: none; color: var(--accent-text); }
.hero__name {
  font-family: var(--font-display);
  font-size: clamp(3rem, 9.5vw, 8.4rem); font-weight: 550;
  --wght: 550; font-variation-settings: 'wght' var(--wght);
  line-height: .9; letter-spacing: -0.028em;
  max-width: 62%;                    /* wraps to 2 lines, stays clear of the face */
  text-shadow: 0 2px 44px rgba(0, 0, 0, .55);   /* lifts type off any glow spill */
}
.hero__tagline {
  margin-top: clamp(1.3rem, 3vh, 2.1rem);
  font-size: clamp(1.1rem, 1.9vw, 1.5rem); font-weight: 400;
  color: rgba(237, 237, 239, .74); max-width: 34ch; line-height: 1.5;
}
.hero__actions {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: clamp(1.4rem, 3vw, 2.6rem);
  margin-top: clamp(1.9rem, 5vh, 3.2rem);
}
.hero__cta {
  display: inline-flex; align-items: center; gap: .6em;
  padding: .92em 1.55em; border-radius: 999px;
  background: var(--ink); color: var(--bg);
  font-size: .96rem; font-weight: 600; letter-spacing: .01em;
  text-decoration: none; white-space: nowrap;
  transition: transform .3s var(--ease), background .3s var(--ease), color .3s var(--ease);
  will-change: transform;
}
.hero__cta::after { content: '→'; transition: transform .3s var(--ease); }
.hero__cta:hover { background: var(--accent); color: #fff; }
.hero__cta:hover::after { transform: translateX(4px); }
/* ===== cta tv ===== */
.cta-tv { height: 1.25em; width: auto; flex: none; }
.cta-tv__panel { color: var(--ink); transition: color .3s var(--ease); }
.hero__cta:hover .cta-tv__panel { color: var(--bg); }
.cta-tv__wave { animation: ctaTvWave 2.6s linear infinite; }
@keyframes ctaTvWave {
  0%   { transform: translateX(0);      opacity: .85; }
  50%  { opacity: 1; }
  100% { transform: translateX(-15.6px); opacity: .85; }
}
.cta-tv__noise { opacity: 0; }
.hero__cta:hover .cta-tv__signal { animation: ctaTvBlipWave .36s steps(1, end) both; }
.hero__cta:hover .cta-tv__noise  { animation: ctaTvBlipNoise .36s steps(1, end) both; }
@keyframes ctaTvBlipWave {
  0%, 82%   { opacity: 0; }
  83%, 100% { opacity: 1; }
}
@keyframes ctaTvBlipNoise {
  0%   { opacity: 1;  transform: translate(0, 0); }
  34%  { opacity: .7; transform: translate(-1.2px, .8px); }
  67%  { opacity: 1;  transform: translate(.9px, -.7px); }
  100% { opacity: 0;  transform: translate(0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .cta-tv__wave, .cta-tv__signal, .cta-tv__noise { animation: none !important; }
}
.hero__scroll {
  display: inline-flex; align-items: center; gap: .85em;
  font-size: .76rem; font-weight: 500; letter-spacing: .2em; text-transform: uppercase;
  color: rgba(237, 237, 239, .68);   /* brighter than --muted so the cue reads as active */
}
/* animated scroll cue: a hairline that leaves the eyebrow, rounds a corner and
   drops into a downward arrow — a literal "keep scrolling down" gesture. A
   cobalt highlight traces the path on a loop and the arrowhead gives a soft
   downward bob as the trace lands. Both are stilled under reduced-motion. */
.hero__scroll-cue {
  flex: none; align-self: center;
  width: 3.6rem; height: auto; overflow: visible;
}
.hero__scroll-cue path {
  fill: none; stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
}
.hero__scroll-track { stroke: rgba(237, 237, 239, .3); }
.hero__scroll-trace {
  stroke: var(--accent-text);
  stroke-dasharray: 18 100; stroke-dashoffset: 18;
  animation: heroScrollTrace 2.4s var(--ease) infinite;
}
.hero__scroll-arrow {
  stroke: rgba(237, 237, 239, .3);
  transform-box: fill-box; transform-origin: center;
  animation: heroScrollArrow 2.4s var(--ease) infinite;
}
@keyframes heroScrollTrace {
  0%       { stroke-dashoffset: 18;   opacity: 0; }
  20%, 66% { opacity: 1; }
  100%     { stroke-dashoffset: -100; opacity: 0; }
}
@keyframes heroScrollArrow {
  0%, 52%  { transform: translateY(0);     stroke: rgba(237, 237, 239, .3); }
  76%      { transform: translateY(2.5px); stroke: var(--accent-text); }
  100%     { transform: translateY(0);     stroke: rgba(237, 237, 239, .3); }
}
/* Gdy pageline realnie startuje (desktop, bez reduced-motion), initPageline()
   przejmuje kontrolę nad grotem jedną osią czasu GSAP: cobalt płynie po cue,
   przez grot i przelewa się na linię strony. CSS-pętle wyłączone, ale ZOSTAJĄ
   zdefiniowane — bez JS grają dalej jako fallback. */
.hero__scroll--handoff .hero__scroll-trace,
.hero__scroll--handoff .hero__scroll-arrow { animation: none; }

/* ===== manifest ===== */
.manifest { height: 220svh; }
.manifest__stage {
  position: sticky; top: 0; height: 100svh;
  /* kolumna wyśrodkowana: tekst zostaje w centrum kadru, CTA siada pod nim */
  display: grid; place-content: center; justify-items: center;
  row-gap: clamp(2.4rem, 5.5vh, 3.6rem); padding-inline: var(--space);
}
.manifest__text {
  position: relative; z-index: 1;   /* tekst nad tłem sekcji i pageline */
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1.8rem, 5vw, 4rem); line-height: 1.18;
  max-width: 20ch; text-align: center;
  color: var(--muted);
}
.manifest__text .mword { color: var(--muted); transition: none; display: inline-block; }
.mword.is-filled { color: var(--ink); }

/* CTA do oferty: cicha pigułka pod manifestem — hairline, nie krzyczy. Hover
   dobarwia obrys na kobalt i popycha strzałkę (idiom .hero__cta). data-magnetic
   dodaje magnetyczny hover z initMagnetic. Stan wejścia ustawia JS (gsap.set),
   więc no-JS / anim-failed zostawia przycisk widoczny. */
.manifest__cta {
  display: inline-flex; align-items: center; gap: .5em;
  padding: .8em 1.4em; border-radius: 999px;
  border: 1px solid var(--line-soft); color: var(--accent-text);
  font-size: .95rem; text-decoration: none; white-space: nowrap;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.manifest__cta span { transition: transform .3s var(--ease); }
.manifest__cta:hover { border-color: var(--accent); background: rgba(59, 91, 255, .12); }
.manifest__cta:hover span { transform: translateX(4px); }

.mword--accent { position: relative; }
.mword--accent::after {
  content: attr(data-word); position: absolute; inset: 0; pointer-events: none;
  color: var(--accent);
  clip-path: inset(0 calc(100% - var(--wipe, 0%)) 0 0);
}
.mword--accent.is-filled::after { --wipe: 100%; }

/* Orbita: odręczna kobaltowa elipsa okrąża „problem." — rysowana scrubbed scrollem
   (stroke-dashoffset). Spójna z językiem rysowanych linii strony (hero scroll-trace,
   pageline, tor „O mnie"). SVG wstrzykuje JS w accentSpan (position:relative); leży
   ZA glifami (z-index:-1) i wystaje poza słowo (inset), stąd overflow:visible. */
.manifest__orbit {
  /* Jawne width/height, NIE inset: SVG bez atrybutów wymiarów to element replaced —
     przy samych insetach wysokość bierze z proporcji viewBoxu (200/90), ignoruje
     bottom i elipsa zjeżdża pod słowo (muskała CTA). preserveAspectRatio="none"
     i tak rozciąga rysunek do boxu. */
  position: absolute; left: -14%; top: -27.5%;
  width: 128%; height: 155%;
  overflow: visible; z-index: -1;
}
.manifest__orbit path {
  /* Bez vector-effect: WebKit liczy wtedy dash w innej przestrzeni niż geometria
     i fragment „schowanej" ścieżki prześwitywał przed animacją. Szerokość w
     jednostkach viewBoxu — nierówne skalowanie (preserveAspectRatio:none) daje
     lekko kaligraficzną, zmienną grubość kreski, jak pociągnięcie długopisem. */
  fill: none; stroke: var(--accent); stroke-width: 1.3; stroke-linecap: round;
  opacity: .9;
}

/* ===== seam strings — struny na szwie sekcji ===== */
/* Zero-height marker straddling a section boundary. The <canvas> injected by
   initSeams() overflows the marker (±BAND/2) so the strings sit ON the seam
   without adding vertical space. Rest = flat hairlines; twang tracks scroll
   velocity. Baseline ::before hairline is the no-JS / anim-failed fallback;
   hidden once initSeams() takes a seam live. */
.seam { position: relative; height: 0; z-index: 5; }
.seam::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0;
  height: 1px; background: var(--line-soft); pointer-events: none;
}
.seam__field {
  position: absolute; left: 0; top: 0; width: 100%; height: 120px;
  transform: translateY(-50%); overflow: visible;
  pointer-events: none; display: block;
}
.seam--live::before { opacity: 0; }

/* ===== pageline — kreska prowadząca przez sekcje ===== */
/* containing block dla .pageline — sekcje wewnątrz są relative/static, więc
   nic innego nie zmienia swojego punktu odniesienia */
main { position: relative; }
/* SVG injected by initPageline() into <main>. z-index 1 keeps it above the
   section grounds but UNDER the seam strings (z5), so scene lighting still
   applies to it. The route itself is
   computed in JS from live rects; here only the paint. Track = full route at
   hairline strength, draw = the scroll-scrubbed run, head = short cobalt comet
   riding the pen tip (same voice as .hero__scroll-trace). Trasa nie kończy się
   już kropką — nurkuje w przycisk „Zobacz pełną galerię" (.projects__cta), który
   siedzi wyżej (z2) i „zapala się" klasą .projects__cta--lit, gdy pióro dojedzie. */
.pageline {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  pointer-events: none; overflow: visible;
}
.pageline path { fill: none; stroke-width: 1.2; vector-effect: non-scaling-stroke; }
.pageline__track { stroke: rgba(237, 237, 239, .12); }
/* Gradientowy zjazd jasności na pierwszym pionowym odcinku: .18 przy grocie
   (nad trackiem .12 → dokładnie .3 alfy cue) wygasza się do 0 w dół, więc styk
   grot→linia nie skacze jasnością. Współrzędne gradientu przelicza build(). */
.pageline__stem  { stroke: url(#plStemGrad); }
.pageline__draw  { stroke: rgba(237, 237, 239, .4); }
/* Kometa: opacity rampuje per-frame z długości pióra (JS), więc bez CSS-owej
   tranzycji, która ciągle by ją retargetowała i rozmazywała. */
.pageline__head  { stroke: var(--accent-text); }
/* Idle: kobaltowy impuls wyłaniający się z grota — sterowany osią czasu GSAP. */
.pageline__pulse { stroke: var(--accent-text); opacity: 0; }
/* Grot-pilot: chevron cue jadący na czubku komety (transform + opacity ustawia
   render() per-frame). Ten sam głos co .hero__scroll-arrow w błysku kobaltu. */
.pageline__tip   { stroke: var(--accent-text); }
@media (prefers-reduced-motion: reduce) {
  /* rest state = fully drawn route (JS sets full length); no travelling comet */
  .pageline__head, .pageline__tip { opacity: 0 !important; }
}

/* ===== plroots — korzenie od CTA galerii w stronę bridge ===== */
/* Zwieńczenie pageline: z dołu pigułki CTA wyrasta system korzeni i schodzi
   w górną strefę bridge. Box drzewa pozycjonuje build() (top/height w px);
   pionowa maska wygasza czubki tam, gdzie rosną wstęgi latent-stream —
   gałęzie rozpuszczają się w strumieniu zamiast kończyć twardą krawędzią.
   Paleta = język pageline: szkielet-track jako zapowiedź, tusz gaśnie
   z głębokością (stroke-opacity per poziom ustawia JS), ostatni poziom
   przejmuje kobalt wstęg. */
.plroots {
  position: absolute; left: 0; width: 100%; z-index: 1;
  pointer-events: none; overflow: visible;
  /* no-repeat: jitter długości gałęzi może wystawać pod box (H×~1.13 w
     najgorszym łańcuchu) — bez tego maska by się tam POWTÓRZYŁA i czubki
     wracałyby pełną jasnością; z no-repeat obszar pod boxem zostaje pusty
     (gradient i tak schodzi do 0 przy 98%). */
  -webkit-mask-image: linear-gradient(180deg, #000 52%, transparent 98%);
          mask-image: linear-gradient(180deg, #000 52%, transparent 98%);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  /* Dryf przez pin bridge (translateY z JS) aktualizuje się co klatkę —
     stała warstwa kompozycji zapobiega też zamrażaniu maskowanego SVG
     w Safari podczas długiego pinu. */
  will-change: transform;
}
.plroots path {
  fill: none; stroke-width: 1.2; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.plroots__track { stroke: rgba(237, 237, 239, .045); }
.plroots__branch { stroke: rgb(237, 237, 239); }
.plroots__branch--tip { stroke: var(--accent-text); }

/* ===== projects / chapters ===== */
.projects {
  max-width: var(--maxw); margin: 0 auto;
  padding-top: clamp(5rem, 14svh, 11rem);
}
.projects__heading {
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(2rem, 5vw, 3.6rem); line-height: 1.05; letter-spacing: -0.015em;
  margin-bottom: clamp(3rem, 9svh, 7rem);
}

/* Outro: cel pageline — linia nurkuje w pigułkę CTA. z-index 2 > .pageline (z1),
   żeby tor chował się POD przyciskiem, nie przecinał go. Tło --bg domyka iluzję. */
.projects__outro {
  position: relative; z-index: 2;
  display: flex; justify-content: center;
  padding-block: clamp(4rem, 14svh, 9rem) clamp(3rem, 10svh, 7rem);
}
.projects__cta {
  display: inline-flex; align-items: center; gap: .6em;
  font-family: var(--font-display); font-size: 1.08rem;
  color: var(--ink); text-decoration: none;
  background: var(--bg); border: 1px solid var(--line-2); border-radius: 999px;
  padding: .75em 1.6em;
  transition: border-color .4s var(--ease), background .3s var(--ease),
              color .3s var(--ease), box-shadow .4s var(--ease);
  will-change: transform;
}
.projects__cta-arrow { transition: transform .3s var(--ease); }
.projects__cta:hover, .projects__cta:focus-visible {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.projects__cta:hover .projects__cta-arrow,
.projects__cta:focus-visible .projects__cta-arrow { transform: translateX(4px); }
/* Pióro pageline dojechało do końca → przycisk „zapala się". */
.projects__cta--lit { border-color: var(--accent); box-shadow: 0 8px 30px rgba(59, 91, 255, .16); }

.chapter {
  min-height: 100svh;
  display: flex; align-items: center;
  padding-block: clamp(4rem, 10svh, 8rem);
}
.chapter__stage {
  display: grid;
  grid-template-columns: 58% 1fr;
  grid-template-areas:
    "head head"
    "media body";
  column-gap: clamp(2rem, 5vw, 5rem);
  row-gap: clamp(1.8rem, 5vh, 3.4rem);
  width: 100%; align-items: start;
}
.chapter__head { grid-area: head; }
.chapter__media { grid-area: media; }
.chapter__body { grid-area: body; }

.chapter__num {
  display: block;
  /* Box hugs the glyph (not the full head width). The scroll-velocity skewY
     pivots around the box centre; a full-width block put that pivot ~600px from
     the digits, turning a small skew into a ~55px vertical LEAP of the glyph that
     rode up over the content above. Hugging the glyph keeps skew a gentle lean. */
  width: fit-content;
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(4rem, 10vw, 8rem); line-height: 1;
  color: var(--bg-2); -webkit-text-stroke: 1px var(--muted);
  margin-bottom: .1em;
  /* Promoted on purpose: parallax (yPercent) + velocity skew run continuously
     while a chapter is on screen. Without a layer, every scroll frame repaints
     the stroked glyphs on the main thread — the main source of projects jank. */
  will-change: transform; transform: translateZ(0);
}
.chapter__title {
  font-family: var(--font-display); font-weight: 550;
  font-size: clamp(2.2rem, 6vw, 4.2rem); line-height: 1; letter-spacing: -0.02em;
  margin-bottom: .5rem;
}
.chapter__hook {
  font-size: clamp(1.1rem, 2.2vw, 1.5rem); color: var(--muted);
  max-width: 34ch;
}

.chapter__media {
  display: grid; gap: clamp(1rem, 2vw, 1.5rem);
}
.shot {
  overflow: hidden; border-radius: var(--radius);
  background: var(--bg-2);
  aspect-ratio: 16 / 10; position: relative;
  border: 1px solid var(--line-soft);
}
.shot::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: radial-gradient(42% 42% at var(--mx, 30%) var(--my, 20%), rgba(59, 91, 255, .12), transparent 62%);
  opacity: .55; transition: opacity .35s var(--ease); pointer-events: none;
}
.shot:hover::after { opacity: 1; }
.shot img { width: 100%; height: 100%; object-fit: cover; will-change: transform; }

.chapter__body {
  display: grid; gap: clamp(1.4rem, 3vh, 2.2rem);
  align-content: start;
}
.chapter__desc {
  font-size: clamp(1rem, 1.3vw, 1.12rem); color: var(--ink);
  max-width: 42ch; line-height: 1.65;
}
.chapter__stack {
  display: flex; flex-wrap: wrap; gap: .55rem;
}
.chapter__stack li {
  border: 1px solid var(--muted); border-radius: 999px;
  padding: .3em .9em; font-size: .85rem; color: var(--muted);
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.chapter__stack li:hover { color: var(--ink); border-color: var(--ink); }

/* Zamknięcie panelu: hairline + przycisk galerii. Wrapper (a nie border na
   samym <a>) — anchor jest inline-flex i przylega do treści, więc środek boxa
   = środek wizualny; to naprawia magnetyczny hover, który przy rozciągniętym
   gridowym dziecku liczył przyciąganie od środka pustej, pełnej kolumny. */
.chapter__cta {
  border-top: 1px solid var(--line-soft);
  padding-top: clamp(1.3rem, 2.8vh, 2rem);
}
.chapter__gallery {
  display: inline-flex; align-items: center; gap: .6em;
  padding: .6em 1.25em;
  border: 1px solid var(--line-2); border-radius: 999px;
  font-size: .92rem; font-weight: 500; color: var(--ink); text-decoration: none;
  transition: border-color .3s var(--ease), background .3s var(--ease), color .3s var(--ease);
  will-change: transform;
}
.chapter__gallery-count {
  font-size: .74rem; line-height: 1; color: var(--muted);
  padding: .32em .6em; border: 1px solid var(--line-soft); border-radius: 999px;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}
.chapter__gallery-arrow { transition: transform .3s var(--ease); }
.chapter__gallery:hover, .chapter__gallery:focus-visible {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.chapter__gallery:hover .chapter__gallery-count,
.chapter__gallery:focus-visible .chapter__gallery-count { color: #fff; border-color: rgba(255, 255, 255, .45); }
.chapter__gallery:hover .chapter__gallery-arrow,
.chapter__gallery:focus-visible .chapter__gallery-arrow { transform: translateX(4px); }
.chapter__status {
  font-size: .82rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted);
  display: inline-flex; align-items: center; gap: .6em; align-self: start;
}
.chapter__status::before {
  content: ''; width: .5em; height: .5em; border-radius: 50%;
  background: var(--accent); flex: none;
}
.chapter__link {
  justify-self: start;
  font-size: .95rem; color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--accent); padding-bottom: .15em;
  transition: color .25s var(--ease);
}
.chapter__link:hover { color: var(--accent-text); }

/* ===== bridge — „brama głębi": typographic Z-tunnel + latent-stream ribbons ===== */
.bridge {
  position: relative;
  height: 100svh; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
/* Latent stream — canvas ribbons behind the words. Invisible at rest (no JS /
   anim-failed / reduced-motion leaves a clean stroked column, zero artefacts);
   fades in only once JS enables the live 3D mode. */
.bridge__flow {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; opacity: 0;
}
.bridge--live .bridge__flow { opacity: 1; transition: opacity .6s var(--ease); }
/* Resting state (no JS / anim-failed / reduced-motion): a legible vertical
   column of stroked words, centred. `bridge--live` (added by JS on desktop)
   swaps this for the perspective-stacked 3D scene. */
.bridge__scene {
  position: relative; z-index: 1; /* words above the flow canvas */
  display: flex; flex-direction: column; align-items: center;
  gap: 0.15em;
}
.bridge__word {
  font-family: var(--font-display); font-weight: 600;
  --bwght: 600; font-variation-settings: 'wght' var(--bwght);
  font-size: clamp(3rem, 9vw, 8.5rem); line-height: 1.05;
  white-space: nowrap;
  color: var(--bg-2); -webkit-text-stroke: 1.5px var(--muted);
}
/* Progress rail: only meaningful in the animated 3D mode. Hidden in the
   resting column so the no-JS fallback has no dead/half-lit chrome. */
.bridge__meta { display: none; }

.bridge--live { perspective: 900px; }
.bridge--live .bridge__scene {
  display: grid; place-items: center;
  transform-style: preserve-3d;
}
.bridge--live .bridge__word {
  grid-area: 1 / 1;
  font-size: clamp(4rem, 13vw, 12rem);
  will-change: transform, opacity; backface-visibility: hidden;
}
.bridge--live .bridge__meta {
  display: block;
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
}
.bridge__rail {
  position: absolute; left: 0; right: 0; bottom: var(--space);
  display: block; height: 1px; background: var(--line-soft);
}
.bridge__rail i {
  display: block; height: 100%; width: 100%;
  transform-origin: left; transform: scaleX(0);
  background: var(--accent);
}

/* Mobile: no pin / no 3D — the resting column IS the section. Fit it to the
   content instead of a full-viewport black void, give the stacked words room to
   breathe, and shrink the type so the longest gate-word ("Automatyzacja") never
   crowds the screen edges (the base clamp bottomed out at 3rem → ~317px wide,
   edge-to-edge on a 375px phone and clipped below it). */
@media (max-width: 760px) {
  .bridge {
    height: auto; min-height: 56svh;
    padding-block: clamp(4rem, 15svh, 7rem); padding-inline: var(--space);
  }
  .bridge__scene { gap: clamp(0.55rem, 3.2vh, 1.35rem); }
  .bridge__word {
    font-size: clamp(2.1rem, 9vw, 3rem);
    -webkit-text-stroke-width: 1px;
  }
  /* Tryb żywy (klasę dodaje initBridge): pełnoekranowy tunel portretowy.
     Bez JS / anim-failed / reduced-motion klasa nie istnieje i zostaje
     spokojna kolumna wyżej. Ciaśniejsza perspektywa niż desktop (900px) —
     wąski kadr potrzebuje mocniejszej głębi. Kegel nadpisze fit-to-width
     inline'em; clamp to bezpieczny start przed pierwszym pomiarem. */
  .bridge--live {
    height: 100svh; min-height: 0; padding: 0;
    perspective: 720px;
  }
  .bridge--live .bridge__word { font-size: clamp(2.2rem, 15vw, 5rem); }
}

/* ===== about — „Convergence" / schemat zbiegu ===== */
/* Stan STATYCZNY jest źródłem prawdy: tor narysowany (stroke-dashoffset:0), diamenty
   pełne, tekst widoczny. Warstwę animacji dokłada JS tylko pod html.anim-on (initAboutSchematic).
   reduced-motion / anim-failed wymuszają stan gotowy (bloki niżej). */
.about { position: relative; max-width: 1080px; margin: 0 auto; padding-block: clamp(5rem, 14svh, 11rem); }
/* treść nad warstwami dekoracyjnymi (siatka / probe / tor) */
.about > :not(.about__grid):not(.about__probe):not(.about__trace) { position: relative; z-index: 2; }

/* — siatka blueprint: dwa hairline-gradienty (~56px), wygaszone u góry i dołu — */
.about__grid {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(237,237,239,.04) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(237,237,239,.04) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
}
/* — probe-light: radialna kobaltowa poświata pod kursorem; --mx/--my z JS (rAF) — */
.about__probe {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 50%), rgba(59,91,255,.06), transparent 62%);
  transition: opacity .4s var(--ease); will-change: opacity;
}
.about.is-probing .about__probe { opacity: 1; }

/* — tor (mierzony SVG): dwie szare trasy wejściowe + jeden kobaltowy tor po zbiegu — */
.about__trace { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.about__trace-line { fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-dashoffset: 0; }
.about__trace-in { stroke: var(--line-2); stroke-width: 1.5; }
.about__trace-merged { stroke: url(#aboutTraceGrad); stroke-width: 2; filter: drop-shadow(0 0 8px rgba(59,91,255,.30)); }
.about__trace-g0 { stop-color: var(--accent); }
.about__trace-g1 { stop-color: var(--accent-text); }

/* — lede (kicker / statement / bio) — wyśrodkowana kolumna czytania — */
.about__lede { text-align: center; }
.about__kicker { font-size: .78rem; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); margin-bottom: 1.4rem; }
.about__statement {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.8rem, 4.4vw, 3.1rem); line-height: 1.16; letter-spacing: -.01em;
  max-width: 18ch; margin: 0 auto 1rem; text-wrap: balance;
}
.about__fill { position: relative; color: var(--ink); white-space: nowrap; }
.about__fill::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -.08em; height: 2px;
  background: var(--accent); transform: scaleX(var(--u, 1)); transform-origin: left; will-change: transform;
}
.about__bio { font-size: clamp(1.05rem, 1.8vw, 1.3rem); color: var(--muted); line-height: 1.55; max-width: 54ch; margin: 0 auto clamp(2.4rem, 6svh, 4rem); }

/* — dwie trasy wejściowe: etykiety (realny tekst, --accent-text) + kotwice u dołu — */
.about__feed { display: flex; justify-content: space-between; width: min(460px, 76%); margin: 0 auto; }
.trace-label { position: relative; padding-bottom: 20px; font-size: .8rem; letter-spacing: .14em; text-transform: uppercase; color: var(--accent-text); }
.trace-label--ai { transform: translateX(-8%); }
.trace-label--product { transform: translateX(8%); }
.about__anchor { position: absolute; width: 1px; height: 1px; }
.trace-label .about__anchor { left: 50%; bottom: 0; }

/* — węzeł zbiegu: tu rodzi się kobalt; nagłówek „Jak pracuję" = start procesu — */
.about__junction { position: relative; text-align: center; margin: clamp(1.4rem, 3.5svh, 2.4rem) 0 clamp(2.4rem, 6svh, 3.6rem); }
.about__junction-mark {
  display: block; width: 13px; height: 13px; margin: 0 auto;
  background: var(--accent); border: 1.5px solid transparent; transform: rotate(45deg);
  box-shadow: 0 0 0 5px rgba(59,91,255,.06);
  transition: background .3s var(--ease), border-color .3s var(--ease), transform .45s var(--ease), opacity .3s var(--ease), box-shadow .3s var(--ease);
}
/* nagłówek pływa na PRAWO od diamentu (diament zostaje wyśrodkowany → trasy zbiegają
   symetrycznie), więc zejście junction→stacja 01 (w lewo) nie przecina liter „Jak pracuję" */
.about__process-heading { position: absolute; top: 50%; left: calc(50% + .85rem); margin-top: -.5em; line-height: 1; white-space: nowrap; font-size: .82rem; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); }

/* — 4 stacje na torze (zygzak): 01/03 na lewo od osi, 02/04 na prawo — */
.about__steps { --ax: clamp(72px, 11vw, 120px); --agap: 32px; position: relative; display: flex; flex-direction: column; gap: clamp(2.6rem, 7svh, 4.6rem); }
.step { position: relative; }
.step__anchor { position: absolute; top: .55em; width: 14px; height: 14px; }
.step--left  .step__anchor { left: calc(50% - var(--ax)); transform: translateX(-50%); }
.step--right .step__anchor { left: calc(50% + var(--ax)); transform: translateX(-50%); }
/* diament stacji (kotwica = środek): rombowy ::before, jednorazowy impuls poświaty ::after */
.step__anchor::before {
  content: ""; position: absolute; inset: 0; background: var(--accent); border: 1.5px solid transparent;
  transform: rotate(45deg); transform-origin: center;
  transition: background .3s var(--ease), border-color .3s var(--ease), transform .45s var(--ease), opacity .3s var(--ease);
}
.step__anchor::after { content: ""; position: absolute; inset: 0; transform: rotate(45deg); box-shadow: 0 0 0 0 rgba(59,91,255,0); }
.step__body { display: flex; flex-direction: column; gap: .45rem; max-width: 34ch; }
.step--left  .step__body { margin-left: auto;  margin-right: calc(50% + var(--ax) + var(--agap)); align-items: flex-end;   text-align: right; }
.step--right .step__body { margin-right: auto; margin-left:  calc(50% + var(--ax) + var(--agap)); align-items: flex-start; text-align: left; }
.step__idx { font-family: var(--font-display); font-size: .82rem; letter-spacing: .14em; color: var(--accent-text); font-variant-numeric: tabular-nums; }
.step__t { font-family: var(--font-display); font-weight: 500; font-size: 1.4rem; line-height: 1.2; color: var(--ink); }
.step__d { font-size: .95rem; color: var(--muted); line-height: 1.55; }
.step__idx, .step__t, .step__d { transition: opacity .8s var(--ease), transform .8s var(--ease); }

/* — punkt pośredni toru: kotwica na osi stacji prawej (x = 50% + ax), TUŻ POD creds.
     Tor schodzi pionowo za blok creds (creds są po lewej → korytarz po prawej wolny),
     i dopiero POD nimi zakręca do podpisu — bez przecięcia opisu stacji 04 ani creds.
     Mobile nadpisuje kotwicę na szynę (blok niżej). — */
.about__waypoint { display: block; height: 0; }
.about__waypoint .about__anchor { left: calc(50% + clamp(72px, 11vw, 120px)); top: 16px; }

/* — zejście toru → podpis: piąty „krok" (05 · <słowo>) w rytmie stacji 01–04; kotwica
     „sign" ląduje na starcie odręcznego podpisu (finał pod krokami) — */
.about__sign { position: relative; width: 280px; max-width: 100%; margin: clamp(2.4rem, 6svh, 3.6rem) 0 0 auto; }
.about__sign-cap { position: relative; display: inline-flex; align-items: baseline; gap: .5rem; padding-top: 16px; transition: opacity .8s var(--ease), transform .8s var(--ease); }
.about__sign-idx { font-family: var(--font-display); font-size: .82rem; letter-spacing: .14em; color: var(--accent-text); font-variant-numeric: tabular-nums; }
.about__sign-sep { color: var(--line-2); transform: translateY(-.06em); }
.about__sign-word { font-family: var(--font-display); font-weight: 500; font-size: 1.15rem; line-height: 1.2; color: var(--ink); }
/* kotwica TUŻ NAD liczbą „5" (nie na 100% podpisu): tor kończy tu pionowym zejściem,
   endpoint LEŻY NAD glifami → tekst jest zawsze poniżej końca toru, brak nachodzenia */
.about__sign .about__anchor { top: 4px; left: 5px; }

/* — stany animacji (pre-hide) — dokładają się TYLKO gdy animacje aktywne (html.anim-on) — */
html.anim-on .about .step__anchor::before { background: transparent; border-color: var(--line-2); transform: rotate(0deg) scale(.5); opacity: .5; }
html.anim-on .about .about__junction-mark { background: transparent; border-color: var(--line-2); box-shadow: none; transform: rotate(45deg) scale(.6); opacity: .5; }
html.anim-on .about .step__idx, html.anim-on .about .step__t, html.anim-on .about .step__d { opacity: 0; transform: translateY(24px); }
/* Punkt „05 · Sukces" chowany jak stacje — zapala się dopiero, gdy tor do niego dojedzie. */
html.anim-on .about .about__sign-cap { opacity: 0; transform: translateY(24px); }
html.anim-on .about .about__fill { --u: 0; }
/* aktywacja stacji (JS: .is-active na .step, .is-lit na węźle) — wyższa specyficzność niż pre-hide */
html.anim-on .about .step.is-active .step__anchor::before { background: var(--accent); border-color: transparent; transform: rotate(45deg) scale(1); opacity: 1; }
html.anim-on .about .step.is-active .step__anchor::after { animation: aboutNodePulse .34s var(--ease) 1; }
html.anim-on .about .step.is-active .step__idx, html.anim-on .about .step.is-active .step__t, html.anim-on .about .step.is-active .step__d { opacity: 1; transform: none; }
/* JS zapala .about__sign.is-active gdy mergP→1 (linia dojechała do kotwicy „sign"). */
html.anim-on .about .about__sign.is-active .about__sign-cap { opacity: 1; transform: none; }
html.anim-on .about .about__junction-mark.is-lit { background: var(--accent); border-color: transparent; transform: rotate(45deg) scale(1); opacity: 1; box-shadow: 0 0 0 5px rgba(59,91,255,.06); animation: aboutNodePulse .4s var(--ease) 1; }
@keyframes aboutNodePulse { 0% { box-shadow: 0 0 0 0 rgba(59,91,255,.5); } 100% { box-shadow: 0 0 0 16px rgba(59,91,255,0); } }
/* anim-failed → wymuszony stan gotowy (w źródle PO pre-hide, więc przy równej specyficzności wygrywa) */
html.anim-failed .about .step__anchor::before, html.anim-failed .about .about__junction-mark { background: var(--accent); border-color: transparent; transform: rotate(45deg); opacity: 1; }
html.anim-failed .about .step__idx, html.anim-failed .about .step__t, html.anim-failed .about .step__d { opacity: 1; transform: none; }
html.anim-failed .about .about__sign-cap { opacity: 1; transform: none; }
html.anim-failed .about .about__fill { --u: 1; }

/* — mobile: kroki w jednej kolumnie, tor = pionowa szyna ~20px od lewej — */
@media (max-width: 760px) {
  .about { max-width: 100%; }
  .about__grid { background-size: 40px 40px; }
  .about__lede { text-align: left; }
  .about__statement { margin-inline: 0; max-width: 18ch; }
  .about__bio { margin-inline: 0; }
  .about__fill { white-space: normal; }
  .about__feed { width: 150px; margin: clamp(1.6rem, 4svh, 2.4rem) 0 0; }
  .trace-label--ai, .trace-label--product { transform: none; }
  .about__junction { display: flex; align-items: center; gap: .55rem; text-align: left; margin-left: 13px; }
  .about__junction-mark { margin: 0; }
  /* mobile: nagłówek wraca do statyki, obok diamentu (na desktopie pływa absolutnie) */
  .about__process-heading { position: static; transform: none; }
  .about__steps { --ax: 20px; }
  .step--left .step__anchor, .step--right .step__anchor { left: 20px; }
  .step--left .step__body, .step--right .step__body { margin: 0 0 0 48px; max-width: none; align-items: flex-start; text-align: left; }
  /* punkt pośredni toru: kotwica na szynie (x≈20 = środek stacji) w przerwie przed
     podpisem → tor schodzi pionowo i dopiero POD nim zakręca do podpisu (koniec diagonali) */
  .about__waypoint { display: block; }
  .about__waypoint .about__anchor { left: 20px; top: 16px; }
  .about__sign { width: 220px; }
}

/* ===== contact — konfigurator ===== */
.contact {
  position: relative;
  max-width: var(--maxw); margin: 0 auto; padding-block: clamp(3rem, 8svh, 6rem);
}
.cc {
  position: relative; display: grid; grid-template-columns: 1fr 1.05fr; gap: clamp(24px, 4vw, 52px);
  background: var(--bg-2); border: 1px solid var(--line-soft);
  border-radius: 18px; padding: clamp(26px, 4vw, 48px);
}
.cc__col { position: relative; z-index: 1; min-width: 0; }
.cc__h { font-family: var(--font-display); font-weight: 500; font-size: clamp(2rem, 4vw, 3rem); line-height: 1.03; letter-spacing: -.02em; }
.cc__sub { color: var(--muted); font-size: 1.02rem; margin: .7rem 0 1.8rem; max-width: 34ch; }
.offerlabel { font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); margin-bottom: 1rem; }

/* Fotorealistyczna „kurtyna" chmur: warstwa ma szerokość viewportu, choć sama
   sekcja jest ograniczona do --maxw. Czerń assetu znika przez screen blend;
   karta pozostaje nad całą dekoracją i chroni czytelność formularza. */
.contact-clouds {
  position: absolute; left: 50%; top: 0; bottom: 0; width: 100vw;
  z-index: 1; transform: translateX(-50%); overflow: hidden;
  pointer-events: none; user-select: none; mix-blend-mode: screen;
}
.contact-cloud {
  --cloud-top: clamp(120px, 18%, 220px);
  position: absolute; top: var(--cloud-top);
  width: clamp(400px, 38vw, 720px); aspect-ratio: 2 / 3;
}
.contact-cloud--left { left: clamp(-120px, -6vw, -70px); }
.contact-cloud--right {
  right: clamp(-132px, -6.5vw, -78px);
  top: calc(var(--cloud-top) - clamp(8px, .65vw, 12px));
}
.contact-cloud__reveal,
.contact-cloud__reveal picture { display: block; width: 100%; height: 100%; }
.contact-cloud__image {
  display: block; width: 100%; height: 100%; object-fit: contain;
  opacity: 1;
}
/* Publicznie bez cen: cicha pionowa lista pozycji (nie kafle). Separator = hairline
   ::before między wierszami; zaznaczenie = cienka ramka akcentu + kobaltowa ikona. */
.pkg { display: flex; flex-direction: column; }
.pkgbtn {
  position: relative;
  display: flex; align-items: center; gap: .85rem; width: 100%; text-align: left; cursor: pointer; color: var(--ink); font: inherit;
  background: transparent; border: 1px solid transparent; border-radius: 10px; padding: .8rem .65rem;
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.pkgbtn + .pkgbtn::before {
  content: ""; position: absolute; left: .65rem; right: .65rem; top: -1px; height: 1px;
  background: var(--line-soft); pointer-events: none;
}
/* ===== signature ===== */
.signature { display: block; }
.signature__img { display: block; height: auto; }
.signature--about {
  /* width (nie max-width): dziecko to inline SVG bez wymiaru wewnętrznego,
     więc wrapper musi mieć określoną szerokość, inaczej zapada się do 0. */
  width: 280px; max-width: 100%; margin: clamp(1.2rem, 3.4svh, 1.9rem) 0 0 auto;
  transform: rotate(-3deg); opacity: .92; color: var(--ink);
}
.signature--about .signature__img { width: 100%; }
@media (max-width: 760px) {
  .signature--about { width: 220px; }
}
/* wipe fallback: gradientowa maska lewo→prawo sterowana --sig-wipe (0→100).
   :not([data-signature-svg]) — wariant handwriting (inline SVG) ma własną maskę
   wewnętrzną; zewnętrzny gradient z --sig-wipe zamrożonym na 0% ciąłby go podwójnie. */
.signature--about[data-signature-state="pending"] .signature__img:not([data-signature-svg]),
.signature--about[data-signature-state="drawing"] .signature__img:not([data-signature-svg]) {
  --sig-wipe: 0%;
  -webkit-mask-image: linear-gradient(to right, #000 0%, #000 var(--sig-wipe), transparent calc(var(--sig-wipe) + 8%));
  mask-image: linear-gradient(to right, #000 0%, #000 var(--sig-wipe), transparent calc(var(--sig-wipe) + 8%));
}
.signature--about[data-signature-state="drawn"] .signature__img {
  -webkit-mask-image: none; mask-image: none;
}
/* reduced-motion / anim-failed → od razu w pełni widoczny, bez maski */
html.anim-failed .signature--about .signature__img { -webkit-mask-image: none !important; mask-image: none !important; }
@media (prefers-reduced-motion: reduce) {
  .signature--about .signature__img { -webkit-mask-image: none !important; mask-image: none !important; }
}
/* handwriting: pociągnięcia maski (inline SVG) — pre-hide przed animacją.
   stroke-dashoffset 100→0 (GSAP) odsłania <image> wzdłuż linii pisma. */
.signature--about[data-signature-state="pending"] .sig-stroke,
.signature--about[data-signature-state="drawing"] .sig-stroke {
  stroke-dasharray: 100; stroke-dashoffset: 100;
}
/* Kropki-widmo: przy stroke-dashoffset:100 (pełne ukrycie) round-linecap i tak rysuje
   ~20px punkt na granicy dasha = starcie KAŻDEGO pociągnięcia → 3 białe kropki przeciekają
   przez maskę na tusz PNG, zanim animacja ruszy. Zerowa krycie kreski w masce = brak
   wkładu w luminancję = brak kropek. Podczas „drawing" JS podnosi stroke-opacity per run
   dokładnie w chwili jego onStart (patrz setupSignature), więc już rysowane runy są widoczne,
   a te przed pierwszym pociągnięciem — nie. */
.signature--about[data-signature-state="pending"] .sig-stroke { stroke-opacity: 0; }
/* Maska handwriting nakładana WYŁĄCZNIE w stanach przejściowych (przez CSS,
   nie atrybut w markupie) — w spoczynku (brak stanu / drawn) obraz jest bez
   maski, więc 100% tuszu widoczne nawet tam, gdzie pociągnięcia nie sięgają. */
.signature--about[data-signature-state="pending"] [data-signature-svg] image,
.signature--about[data-signature-state="drawing"] [data-signature-svg] image {
  -webkit-mask: url(#sigMaskAbout);
  mask: url(#sigMaskAbout);
}
/* SVG podpis skaluje się jak dawny <img>; inline SVG nie ma wymiaru
   wewnętrznego jak <img>, więc wysokość liczymy z aspect-ratio viewBox
   (800×281), inaczej wrapper zapada się do 0×0. */
.signature--about svg.signature__img { display: block; width: 100%; height: auto; aspect-ratio: 800 / 281; }
/* reduced-motion / anim-failed → maska handwriting zdjęta w całości (100% tuszu,
   nie tylko pociągnięcia) — nawet gdy stan utknął na pending/drawing. */
html.anim-failed .signature--about .sig-stroke { stroke-dashoffset: 0 !important; }
html.anim-failed .signature--about [data-signature-svg] image { -webkit-mask: none !important; mask: none !important; }
@media (prefers-reduced-motion: reduce) {
  .signature--about .sig-stroke { stroke-dashoffset: 0 !important; }
  .signature--about [data-signature-svg] image { -webkit-mask: none !important; mask: none !important; }
}
/* Czoło pióra — kobaltowy punkt jadący po froncie odsłaniania (JS steruje cx/cy/opacity
   przez getPointAtLength). Poza maską: nie odsłania tuszu, to tylko sygnaturowy akcent.
   Gaśnie na końcu (drawn) i całkiem znika w RM / anim-failed (statyczny podpis, brak pióra). */
.signature--about .sig-tip,
.signature--form .sig-tip {
  fill: var(--accent, #3B5BFF); opacity: 0; pointer-events: none;
  filter: drop-shadow(0 0 5px rgba(59, 91, 255, .5));
}
.signature--about[data-signature-state="drawn"] .sig-tip,
.signature--form[data-signature-state="drawn"] .sig-tip { opacity: 0 !important; }
html.anim-failed .sig-tip { display: none !important; }
@media (prefers-reduced-motion: reduce) { .sig-tip { display: none !important; } }
/* Form signature — inline-SVG „atrament" (jak about), żyje w panelu sukcesu;
   rysowany przy otwarciu panelu (patrz reguły maski/tip poniżej). */
.signature--form {
  margin-top: clamp(1.4rem, 4vh, 2rem); max-width: 240px;
  color: var(--ink); text-align: left;
}
.signature--form .signature__img { width: 100%; }
.signature--form[data-signature-state="drawn"] .signature__img {
  -webkit-mask-image: none; mask-image: none;
}
/* reduced-motion / anim-failed → od razu w pełni widoczny, bez maski.
   UWAGA: to DWIE osobne reguły — reguła anim-failed NIE może być sklejona
   przecinkiem z blokiem @media (poprawiony błąd składni z briefu). */
html.anim-failed .signature--form .signature__img { -webkit-mask-image: none !important; mask-image: none !important; }
@media (prefers-reduced-motion: reduce) {
  .signature--form .signature__img { -webkit-mask-image: none !important; mask-image: none !important; }
}
/* podpis w panelu = inline-SVG „atrament" (jak about): pociągnięcia maski + kobaltowy tip. */
.signature--form svg.signature__img { display: block; width: 100%; height: auto; aspect-ratio: 800 / 281; }
.signature--form[data-signature-state="pending"] .sig-stroke,
.signature--form[data-signature-state="drawing"] .sig-stroke { stroke-dasharray: 100; stroke-dashoffset: 100; }
.signature--form[data-signature-state="pending"] .sig-stroke { stroke-opacity: 0; }
.signature--form[data-signature-state="pending"] [data-signature-svg] image,
.signature--form[data-signature-state="drawing"] [data-signature-svg] image {
  -webkit-mask: url(#sigMaskForm); mask: url(#sigMaskForm);
}
html.anim-failed .signature--form .sig-stroke { stroke-dashoffset: 0 !important; }
html.anim-failed .signature--form [data-signature-svg] image { -webkit-mask: none !important; mask: none !important; }
@media (prefers-reduced-motion: reduce) {
  .signature--form .sig-stroke { stroke-dashoffset: 0 !important; }
  .signature--form [data-signature-svg] image { -webkit-mask: none !important; mask: none !important; }
}
.signature--footer {
  width: 130px; color: var(--ink); flex: none;
}
/* Opacity lives on the IMG, not the wrapper: the [data-reveal] tween drives the
   wrapper's opacity 0→1, so a wrapper opacity would be clobbered to 1 on reveal.
   Keeping the fade on the img means the signature settles at .7 after reveal. */
.signature--footer .signature__img { width: 100%; opacity: .7; }
/* W nowej stopce (main) podpis żyje w .footer__bottom (flex, space-between):
   podpis po lewej, copyright w środku, przycisk „na górę" po prawej. Odbieramy
   sygnaturze domyślny margines i porządkujemy kolejność, by przycisk został skrajnie prawy. */
.footer__bottom .signature--footer { order: 0; margin: 0; margin-right: auto; }
.footer__bottom .footer__copy { order: 1; }
.footer__bottom .footer__top { order: 2; }
@media (max-width: 620px) {
  /* mobile: podpis mniejszy, wiersz się zawija (flex-wrap) — sygnatura nad copy/przyciskiem */
  .footer__bottom .signature--footer { width: 104px; margin-right: auto; }
}
/* Reachability safety net: this signature is the LAST element on the page, so the
   generic ScrollTrigger start ('top 90%') can sit a few px past max-scroll and the
   batch reveal may never fire — leaving the wrapper stuck at opacity:0. When JS is
   on we still start hidden (FOUC guard via html.anim-on [data-reveal]), but a short
   self-running fade guarantees the wrapper reaches opacity:1 regardless. If the real
   [data-reveal] tween does fire, GSAP's inline opacity/transform overrides this. */
html.anim-on .signature--footer[data-reveal] {
  animation: sigFooterReveal .9s ease .6s both;
}
@keyframes sigFooterReveal {
  from { opacity: 0; transform: translateY(28px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Contact watermark: oversized signature sitting behind the heading. No transform
   in the base rule — X/Y centring is done by GSAP (xPercent/yPercent) so the scrub
   tween can add a ±20px vertical drift on top. The two fallbacks below re-centre it
   statically when GSAP never runs (anim-failed) or motion is reduced. */
.signature--watermark {
  position: absolute; top: 50%; left: 50%;
  width: min(70vw, 900px); height: auto;
  opacity: .05; color: var(--ink);
  pointer-events: none; z-index: 0;
}
html.anim-failed .signature--watermark { transform: translate(-50%, -50%); }
@media (prefers-reduced-motion: reduce) {
  .signature--watermark { transform: translate(-50%, -50%); }
}

/* ===== contact — podpis-watermark w tle sekcji (stacking) ===== */
/* Lift interactive content above watermark + clouds. The cloud layer keeps its
   own absolute positioning and can never intercept the form. The success panel
   (.cpanel) is a fixed overlay with its own z-index (9500) — exempt it too, so
   this content-stacking rule never demotes it to relative/z-index:2. */
.contact > :not(.signature--watermark):not(.contact-clouds):not(.cpanel) { position: relative; z-index: 2; }
.pkgbtn:hover { background: rgba(255,255,255,.025); }
.pkgbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pkgbtn__ic { width: 24px; height: 24px; display: grid; place-items: center; color: var(--muted); flex: none; transition: color .25s var(--ease); }
.pkgbtn__ic svg { width: 22px; height: 22px; }
.pkgbtn__tx { display: grid; gap: .12rem; min-width: 0; }
.pkgbtn__tx b { font-weight: 600; font-size: .98rem; }
.pkgbtn__tx small { color: var(--muted); font-size: .82rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pkgbtn[aria-pressed="true"] { border-color: var(--accent); background: rgba(59,91,255,.06); }
.pkgbtn[aria-pressed="true"] .pkgbtn__ic { color: var(--accent-text); }
/* zaznaczony wiersz: schowaj przylegające separatory, by ramka akcentu czytała czysto */
.pkgbtn[aria-pressed="true"]::before,
.pkgbtn[aria-pressed="true"] + .pkgbtn::before { background: transparent; }
.offer-foot { margin-top: 1.3rem; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .6rem; }
.care { font-size: .88rem; color: var(--muted); }
.offerlink { display: inline-flex; align-items: center; gap: .4em; font-size: .92rem; color: var(--accent-text); text-decoration: none; }
.offerlink:hover { text-decoration: underline; }
.channels { display: flex; flex-wrap: wrap; gap: 1.3rem; margin-top: 1.4rem; padding-top: 1.2rem; border-top: 1px solid var(--line-soft); }

/* channels — layout listy (stopka); .channel współdzielony z .channels sekcji */
.footer__channels {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: clamp(1rem, 3vw, 2.4rem);
}
.channel {
  display: inline-flex; align-items: center; gap: .55em;
  color: var(--ink); text-decoration: none; font-size: .95rem;
  transition: color .3s var(--ease);
}
.channel__icon { width: 1.2em; height: 1.2em; flex: none; }
.channel:hover, .channel:focus-visible { color: var(--accent-text); }
.channel--phone.is-unlocked { color: var(--accent-text); }

/* form */
.contact-form {
  width: 100%; max-width: 560px; margin-top: clamp(1rem, 3vh, 2rem);
  text-align: left; display: grid; gap: 1.35rem;
}
.contact-form__row { display: grid; gap: .45rem; }
/* etykiety edytorialnie: małe, wersaliki, rozstrzelone — jak .offerlabel / .footer__col-title */
.contact-form__label { font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
/* pola „podkreślenia": bez kart, tylko dolna linia; focus → akcent */
.contact-form__input, .contact-form__textarea {
  width: 100%; background: transparent; color: var(--ink);
  border: 0; border-bottom: 1px solid var(--line-soft); border-radius: 0;
  padding: .5em .1em; font: inherit; font-size: 1rem;
  transition: border-color .25s var(--ease);
}
.contact-form__input::placeholder, .contact-form__textarea::placeholder { color: var(--muted); opacity: .7; }
.contact-form__input:focus, .contact-form__textarea:focus { outline: none; border-bottom-color: var(--accent); }
.contact-form__textarea { resize: vertical; min-height: 96px; }
.contact-form__hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* submit spokojny: outline + strzałka, wzór .footer__cta (nie pełny kobaltowy blok) */
.contact-form__submit {
  justify-self: start; display: inline-flex; align-items: center; gap: .5em;
  font-family: var(--font-display); font-size: 1.1rem;
  color: var(--ink); background: transparent;
  border: 1px solid var(--accent); border-radius: 999px;
  padding: .55em 1.5em; cursor: pointer; margin-top: .2rem;
  transition: background .3s var(--ease), color .3s var(--ease);
}
.contact-form__submit::after { content: "\2192"; font-size: .95em; line-height: 1; }
.contact-form__submit:hover, .contact-form__submit:focus-visible { background: var(--accent); color: #fff; }
.contact-form__submit:disabled { opacity: .55; cursor: default; }
.contact-form__note { font-size: .78rem; color: var(--muted); line-height: 1.5; }
.contact-form__status { font-size: .98rem; min-height: 1.4em; }
.contact-form__status:empty { display: none; }
.contact-form__status[data-state="ok"] { color: var(--accent-text); }
.contact-form__status[data-state="err"] { color: var(--ink); }
.contact-form__status a { color: var(--accent-text); text-decoration: underline; }

/* ===== footer ===== */
.footer {
  padding: clamp(3rem, 8svh, 5.5rem) var(--space) clamp(1.6rem, 4svh, 2.4rem);
  border-top: 1px solid var(--line-soft);
  color: var(--ink);
}
.footer__inner { max-width: var(--maxw); margin: 0 auto; }
.footer__brand { display: flex; flex-direction: column; gap: 1.4rem; align-items: flex-start; }
.footer__tick { position: relative; height: 1px; background: var(--line-soft); margin: clamp(2rem, 4vh, 2.8rem) 0 clamp(1.8rem, 3.5vh, 2.4rem); }
.footer__tick::before { content: ''; position: absolute; left: 0; top: -1px; width: 34px; height: 3px; border-radius: 2px; background: var(--accent); }
.footer__sign {
  margin: 0; font-family: var(--font-display); font-weight: 600;
  font-size: clamp(2rem, 5.2vw, 3.6rem); line-height: .98; letter-spacing: -.015em; color: var(--ink);
}
/* kobaltowa kropka na końcu ostatniej linii wordmarku (jedzie z maską data-split) */
.footer__sign .line:last-child .line__inner::after { content: '.'; color: var(--accent); }
.footer__tagline { display: flex; align-items: stretch; gap: .85em; }
.footer__tagline__bar { width: 2px; align-self: stretch; min-height: 1.6em; background: var(--accent); border-radius: 2px; flex: none; }
.footer__tagline__txt { font-size: .95rem; color: var(--muted); line-height: 1.55; max-width: 46ch; }
.footer__cta {
  display: inline-flex; align-items: center; gap: .55em;
  font-family: var(--font-display); font-size: 1.1rem; color: var(--ink);
  background: transparent; border: 1px solid var(--accent); border-radius: 999px;
  padding: .6em 1.4em; text-decoration: none;
  transition: background .3s var(--ease), color .3s var(--ease); will-change: transform;
}
.footer__cta:hover, .footer__cta:focus-visible { background: var(--accent); color: #fff; }
.footer__cta__arrow { width: 1.05em; height: 1.05em; flex: none; }

.footer__grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: clamp(1.2rem, 2.5vw, 2rem); }
.footer__col { display: flex; flex-direction: column; gap: .8rem; }
.footer__col-title {
  margin: 0 0 .3rem; font-family: var(--font-body); font-size: .72rem; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
.footer__link {
  display: inline-flex; align-items: center; gap: .6em; width: fit-content;
  color: var(--ink); text-decoration: none; font-size: .9rem; transition: color .3s var(--ease);
}
.footer__link__icon { width: 1.05em; height: 1.05em; flex: none; color: var(--muted); transition: color .3s var(--ease); }
.footer__link__lbl { position: relative; }
.footer__link__lbl::after {
  content: ''; position: absolute; left: 0; bottom: -3px; height: 1px; width: 100%;
  background: var(--accent); transform: scaleX(0); transform-origin: right; transition: transform .35s var(--ease);
}
.footer__link:hover, .footer__link:focus-visible { color: var(--accent-text); }
.footer__link:hover .footer__link__icon, .footer__link:focus-visible .footer__link__icon { color: var(--accent-text); }
.footer__link:hover .footer__link__lbl::after,
.footer__link:focus-visible .footer__link__lbl::after { transform: scaleX(1); transform-origin: left; }

.footer .channel { font-size: .9rem; }
.footer .channel__icon { color: var(--muted); transition: color .3s var(--ease); }
.footer .channel:hover .channel__icon, .footer .channel:focus-visible .channel__icon { color: var(--accent-text); }

.footer__bottom {
  max-width: var(--maxw); margin: clamp(2.4rem, 5vh, 3.5rem) auto 0;
  padding-top: 1.4rem; border-top: 1px solid var(--line-soft);
  display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
}
.footer__copy { font-size: .82rem; color: var(--muted); margin: 0; }
.footer__top {
  display: inline-flex; align-items: center; gap: .5em; cursor: pointer;
  font: inherit; font-size: .82rem; color: var(--muted);
  background: transparent; border: 1px solid var(--line-soft); border-radius: 999px;
  padding: .5em 1.1em; transition: color .3s var(--ease), border-color .3s var(--ease); will-change: transform;
}
.footer__top:hover, .footer__top:focus-visible { color: var(--accent-text); border-color: rgba(59, 91, 255, .5); }
.footer__top__arrow { width: 1em; height: 1em; flex: none; }

/* elegancki reveal tagline'u — zbrajany przez JS (initFooter), GSAP-niezależny */
.footer.is-armed .footer__tagline__bar { transform: scaleY(0); transform-origin: top; transition: transform .55s var(--ease) .12s; }
.footer.is-armed .footer__tagline__txt {
  opacity: 0; transform: translateY(8px); letter-spacing: .16em;
  transition: opacity .85s var(--ease) .1s, transform .85s var(--ease) .1s, letter-spacing .85s var(--ease) .1s;
}
.footer.is-in .footer__tagline__bar { transform: scaleY(1); }
.footer.is-in .footer__tagline__txt { opacity: 1; transform: none; letter-spacing: .01em; }

@media (max-width: 620px) { .footer__grid { grid-template-columns: 1fr 1fr; gap: 1.6rem; } }
@media (max-width: 380px) { .footer__grid { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) {
  .footer.is-armed .footer__tagline__bar { transform: scaleY(1); transition: none; }
  .footer.is-armed .footer__tagline__txt { opacity: 1; transform: none; letter-spacing: .01em; transition: none; }
}

/* ===== focus visibility ===== */
:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px;
  box-shadow: 0 0 0 5px rgba(59, 91, 255, .16);
  transition: outline-offset .15s var(--ease), box-shadow .15s var(--ease);
}

/* Programowe cele fokusu: nagłówki i landmarki z tabindex="-1" są fokusowane
   przez JS (zmiana kroku oferty, kotwice) wyłącznie po to, by przenieść pozycję
   czytnika ekranu — nigdy nie są w kolejności Tab. Nie pokazujemy na nich
   pierścienia, bo wyglądał jak przypadkowe zaznaczenie elementu interfejsu.
   Realne kontrolki (przyciski, linki, kafle galerii z roving tabindex) NIE są
   w tym selektorze, więc zachowują swój pierścień dla nawigacji klawiaturą. */
:where(h1, h2, h3, h4, [data-step-head], main, section, article, aside)[tabindex="-1"]:focus,
:where(h1, h2, h3, h4, [data-step-head], main, section, article, aside)[tabindex="-1"]:focus-visible {
  outline: none;
  box-shadow: none;
}

/* ===== mobile ===== */
@media (max-width: 760px) {
  .manifest { height: auto; }
  .manifest__stage { position: relative; height: auto; padding: 20svh 0; }
  /* Pełnoekranowy dwell — klasę dodaje initManifest wyłącznie w żywym trybie
     animacji, więc no-JS / anim-failed / reduced-motion zostają przy zwartym
     przepływie powyżej (bez pustego scrolla nad niepodzielonym akapitem). */
  .manifest--dwell { height: 170svh; }
  .manifest--dwell .manifest__stage { position: sticky; top: 0; height: 100svh; padding: 0; }

  .chapter { min-height: auto; padding-block: clamp(4rem, 10svh, 6rem); }
  .chapter__stage {
    grid-template-columns: 1fr;
    grid-template-areas:
      "head"
      "media"
      "body";
  }
  /* zapas na parallax kadru: picture wyższy o 8% i wycentrowany przez top:-4%
     (procentowy top liczy się od WYSOKOŚCI containing blocku — margin-top:-4%
     liczyłby się od szerokości i przy 16/10 zjadałby dolny zapas), dryf ±3.6%
     nigdy nie odsłania krawędzi zdjęcia w .shot (overflow:hidden) */
  .shot picture { display: block; position: relative; top: -4%; height: 108%; }
  /* Galeria: dotykowy wiersz pełnej szerokości — etykieta przy lewej, licznik
     i strzałka przy prawej, tap ≥48px. Strzałka odpowiada na dotyk (:active).
     Prefiks .chapter__body podbija specyficzność nad bazową pigułką. */
  .chapter__body .chapter__cta { padding-top: 1.1rem; }
  .chapter__body .chapter__gallery {
    display: flex; width: 100%; align-items: center;
    min-height: 48px;
  }
  .chapter__body .chapter__gallery .chapter__gallery-label { margin-right: auto; }
  .chapter__body .chapter__gallery:active .chapter__gallery-arrow { transform: translateX(4px); }
  .projects__outro { padding-block: clamp(2rem, 7svh, 3.5rem); }

  .cc { grid-template-columns: 1fr; }
  .contact-clouds { opacity: .5; }
  .contact-cloud { top: 22%; width: 360px; }
  .contact-cloud--left { left: -330px; }
  .contact-cloud--right { right: -330px; }

  /* header: hide inline nav (moves into overlay), reveal hamburger */
  .nav__link, .nav__lang, .lang-switch { display: none; }
  .nav-toggle { display: flex; }

  /* mobilny notch (spec 2026-07-12): TA SAMA szklana płyta co na desktopie —
     mobile nie nadpisuje tła/bordera/radiusu/blur, więc szkło i fillety są
     widoczne od wejścia, a szerokość wynika z zawartości (bez right:0).
     Monogram + kreska navu + hamburger leżą razem po lewej. Kompakt: niższa
     płyta przez --header-h (zmienną czytają też offsety scrolla, hero
     i overlay menu, więc dopasują się same) i ciaśniejsze paddingi;
     max(14px, safe-area) chroni landscape z systemowym wycięciem. */
  :root { --header-h: 60px; }
  .header {
    gap: .5rem;
    padding: 0 8px 0 max(14px, env(safe-area-inset-left));
  }

  /* monogram płasko na płycie, mniejszy → kompakt niszy */
  .header__logo .jg-mark { --jg-size: 1.7rem; }

  /* separator logo ↔ hamburger: desktopowa kreska .header__nav::before
     zostaje włączona (linki i lang-switch są ukryte, ale nav stoi w DOM
     między logo a toggle) */
  .header__nav { padding: 0; }

  /* hamburger płasko na płycie: bez tła/ramki „monety"; radius wyłącznie
     pod okrągły focus ring */
  .nav-toggle { border-radius: 50%; }

  /* hero (mobile): no horizontal room for a side-by-side split, so stack it —
     backlit portrait anchored to the TOP, text block bottom-anchored on clean
     dark ground below it. Guarantees legibility with zero text-over-face.
     „Nisza": hero stops at ~86svh so the next section peeks from below — the
     peek itself is the scroll cue, so the "Przewiń" label is dropped here. */
  .hero { align-content: end; min-height: 86svh; padding-bottom: clamp(2rem, 5svh, 3rem); }
  /* full-figure cutout (portrait-full.webp): anchored to the hero's BOTTOM edge,
     where the seam strings run — the frame's thigh-crop dissolves right above
     them, so the figure reads as standing in/on the waves. Text overlays the
     dark suit; the veil below keeps it legible. */
  .hero__portrait {
    top: auto; bottom: 0;
    right: -6%;
  }
  .hero__portrait img {
    height: min(78svh, calc(86svh - var(--header-h))); width: auto; max-width: none; max-height: none;
    /* short fade: the crop edge melts into the seam strings, not a mid-air cut */
    -webkit-mask-image: linear-gradient(to top, transparent 1%, #000 9%);
    mask-image: linear-gradient(to top, transparent 1%, #000 9%);
  }
  .hero__glow { top: 3%; left: 60%; width: 96vw; margin-left: -48vw; }
  /* deliberate two-line lockup: at ~375px a single line measures ~347px against
     335px of column — it either kisses the screen edge or wraps at random. 8ch
     commits to the stack instead of gambling on it. */
  .hero__name { font-size: clamp(2.9rem, 13vw, 5rem); max-width: 8ch; }
  .hero__tagline { max-width: 34ch; font-size: 1.05rem; margin-top: 1rem; }
  .hero__actions { margin-top: 1.4rem; }
  .hero__scroll { display: none; }
  /* the cycling word can't fit 375px (scramble glyphs overflow the edge) —
     swap it for the static stand-in; IO never fires on display:none, so the
     cycler simply stays paused on mobile with no JS changes. */
  .eyebrow__cycle { display: none; }
  .eyebrow__fixed { display: inline; }
  /* veil bridging the portrait into the text zone. The full-figure cutout puts
     the bright laptop + shirt right behind the eyebrow/name (left-centre), so a
     radial pool of darkness sits over that zone specifically — the face at the
     top-right stays outside its radius and keeps its backlit brightness. */
  .hero::before {
    background:
      radial-gradient(92% 40% at 30% 62%, rgba(10, 10, 10, .8), transparent 72%),
      linear-gradient(to top,
        rgba(10, 10, 10, .82) 8%, rgba(10, 10, 10, .45) 42%, transparent 60%);
  }
}

/* stany początkowe TYLKO gdy animacje aktywne */
/* will-change zdjęte z CSS — trzymane od załadowania strony utrzymywało 36+ warstw
   kompozytora żywcem przez całą sesję i zacinało scroll. Stany opacity/transform
   ZOSTAJĄ (ochrona przed FOUC); will-change nadaje teraz JS just-in-time w onEnter/
   onStart rewelacji i czyści w onComplete (initReveals + applySplitReveal). */
html.anim-on [data-reveal] { opacity: 0; transform: translateY(28px); }
html.anim-on .line__inner { transform: translateY(110%); opacity: 0; display: inline-block; }
html.anim-on .contact-cloud__reveal { opacity: 0; }
.line { display: block; overflow: hidden; }
.line__inner { display: inline-block; }
/* hero entrance: portrait (+ its halo) + ambient glow hidden before JS drives them in */
html.anim-on [data-portrait] { opacity: 0; }
html.anim-on .hero__glow { opacity: 0; }
/* failsafe: jeśli GSAP nie wstał — wszystko widoczne */
html.anim-failed [data-reveal], html.anim-failed .line__inner { opacity: 1 !important; transform: none !important; transition: opacity .4s ease; }
html.anim-failed .contact-cloud__reveal { opacity: 1 !important; transform: none !important; }
/* ambient hero layers carry layout-critical transforms (centring offsets) —
   only restore opacity here, never clobber transform */
html.anim-failed [data-portrait],
html.anim-failed .hero__glow { opacity: 1 !important; }
@media (prefers-reduced-motion: reduce) {
  /* About „Convergence": schemat renderowany od razu w stanie gotowym (§9 spec). */
  .about .step__anchor::before, .about .about__junction-mark {
    background: var(--accent) !important; border-color: transparent !important; transform: rotate(45deg) !important; opacity: 1 !important;
  }
  .about .step__idx, .about .step__t, .about .step__d { opacity: 1 !important; transform: none !important; }
  .about .about__sign-cap { opacity: 1 !important; transform: none !important; }
  .about .about__fill { --u: 1 !important; }
  .about__probe { display: none !important; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
  [data-reveal], .line__inner { opacity: 1 !important; transform: none !important; }
  .contact-cloud, .contact-cloud__reveal, .contact-cloud__image { opacity: 1 !important; transform: none !important; animation: none !important; transition: none !important; }
  [data-portrait], .hero__glow { opacity: 1 !important; }
  .mword--accent::after { --wipe: 100% !important; }
  .hero__scroll-trace { animation: none !important; opacity: 0 !important; }
  .hero__scroll-arrow { animation: none !important; transform: none !important; }
}

/* ===== view transitions — soft dissolve monogramu „JG" (shared nav-logo, ta sama marka po obu stronach) ===== */
@view-transition { navigation: auto; }
.header__logo { view-transition-name: nav-logo; }
::view-transition-old(nav-logo) { animation: vtLogoOut .5s var(--ease) both; }
::view-transition-new(nav-logo) { animation: vtLogoIn .55s var(--ease) both; }
@keyframes vtLogoOut { to   { opacity: 0; filter: blur(6px); } }
@keyframes vtLogoIn  { from { opacity: 0; filter: blur(6px); } }
::view-transition-group(root) { animation-duration: .34s; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation-duration: .001ms !important; }
}

/* ===== media "peek" cursor — only over project imagery ===== */
.cursor-follow {
  position: fixed; top: 0; left: 0; z-index: 9500; pointer-events: none;
  display: inline-flex; align-items: center; gap: .55em;
  padding: .78em 1.1em; border-radius: 999px;
  border: 1px solid var(--accent); color: var(--ink);
  background: rgba(10, 10, 10, .42); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  font-family: var(--font-body); font-size: .68rem; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; white-space: nowrap;
}
.cursor-follow__arrow { color: var(--accent); }
.chapter__media.is-peek { cursor: none; }

/* ===== gallery ===== */
.gallery-hero { max-width: var(--maxw); margin: 0 auto; padding: calc(var(--header-h) + clamp(48px, 12vh, 120px)) var(--space) clamp(28px, 5vh, 56px); }
.gallery-hero__eyebrow { font-size: .8rem; letter-spacing: .22em; text-transform: uppercase; color: var(--accent); margin-bottom: .9rem; }
.gallery-hero__heading { font-family: var(--font-display); font-weight: 500; font-size: clamp(2.6rem, 8vw, 5rem); line-height: 1.02; letter-spacing: -.02em; }
.gallery-hero__intro { color: var(--muted); max-width: 46ch; margin-top: 1.1rem; font-size: 1.05rem; }

.gallery-hero__meta {
  display: inline-block; margin-top: 1.6rem; padding-top: 1rem;
  border-top: 1px solid var(--line-soft);
  color: var(--muted); font-size: .85rem; letter-spacing: .06em;
  font-variant-numeric: tabular-nums;
}

/* ===== kategoria w belce: szklana pigułka + rozwijany panel (strony galerii) ===== */
/* lewy klaster: monogram JG + pigułka kategorii obok siebie; panel kotwiczony
   do lewej krawędzi klastra (= lewy padding belki), więc mieści się na wąskim ekranie */
.header__left { position: relative; display: flex; align-items: center; gap: .55rem; min-width: 0; }
.gcat { display: flex; min-width: 0; }
.gcat__btn {
  display: inline-flex; align-items: center; gap: .55em; max-width: min(52vw, 320px);
  font-family: var(--font-body); font-size: .92rem; letter-spacing: .01em; color: var(--ink);
  background: rgba(18, 18, 20, .55); border: 1px solid rgba(255, 255, 255, .10);
  border-radius: 999px; padding: .62em 1.05em; cursor: pointer;
  -webkit-backdrop-filter: blur(18px) saturate(1.3); backdrop-filter: blur(18px) saturate(1.3);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .06) inset, 0 10px 30px rgba(0, 0, 0, .32);
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.gcat__btn:hover { border-color: rgba(255, 255, 255, .22); }
.gcat__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.gcat__label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gcat__c { color: var(--accent-text); font-size: .85em; font-variant-numeric: tabular-nums; }
.gcat__chev { flex: none; color: var(--muted); transition: transform .3s var(--ease); }
.gcat.is-open .gcat__chev { transform: rotate(180deg); }

/* kompaktowy notch (761–1080px): pigułka kategorii skraca się razem z resztą
   wyspy; reguła stoi PO bazowej .gcat__btn, bo przy równej specyficzności
   o wyniku decyduje kolejność w pliku. */
@media (max-width: 1080px) and (min-width: 761px) {
  .gcat__btn { max-width: 180px; font-size: .88rem; }
}

.gcat__menu {
  position: absolute; top: calc(100% + 10px); left: 0; z-index: 8050;
  width: min(320px, calc(100vw - 2 * var(--space)));
  padding: .5rem; border-radius: 16px;
  background: var(--bg-2); border: 1px solid var(--line-soft);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .45);
  opacity: 0; visibility: hidden;
  transform: translateY(-6px) scale(.98); transform-origin: top left;
  transition: opacity .18s var(--ease), transform .18s var(--ease), visibility 0s linear .18s;
}
.gcat.is-open .gcat__menu {
  opacity: 1; visibility: visible;
  transform: translateY(0) scale(1);
  transition: opacity .18s var(--ease), transform .18s var(--ease);
}
.gcat__menu[hidden] { display: none; }   /* stan no-JS zanim gallery.js zdejmie hidden */
html.no-js .gcat { display: none; }

.gcat__item {
  position: relative; display: flex; align-items: center; width: 100%; text-align: left;
  font-family: var(--font-body); font-size: .98rem; color: var(--muted);
  background: none; border: 0; border-radius: 10px; padding: .6em .9em; cursor: pointer;
  transition: color .2s var(--ease), background .2s var(--ease);
}
.gcat__item::before {
  content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 3px; height: 0; border-radius: 3px; background: var(--accent);
  transition: height .25s var(--ease);
}
.gcat__item:hover { color: var(--ink); background: rgba(237, 237, 239, .05); }
.gcat__item.is-active { color: #fff; }
.gcat__item.is-active::before { height: 58%; }
.gcat__item__c {
  margin-left: auto; padding-left: 1em;
  font-size: .82em; font-variant-numeric: tabular-nums; color: var(--muted); opacity: .65;
  transition: color .2s var(--ease), opacity .2s var(--ease);
}
.gcat__item.is-active .gcat__item__c { color: var(--accent-text); opacity: 1; }
.gcat__item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ===== kolekcje na pełnej szerokości ===== */
.collections {
  max-width: var(--maxw); margin: 0 auto; min-width: 0;
  padding: clamp(20px, 4vh, 44px) var(--space) clamp(60px, 12vh, 140px);
}
.collection { margin-top: clamp(48px, 9vh, 110px); }
.collection:first-child { margin-top: 0; }
.collection[hidden] { display: none; }
.collection__head {
  display: flex; align-items: baseline; gap: 1.1rem; flex-wrap: wrap;
  margin-bottom: 1.6rem; padding-top: 1.2rem;
  border-top: 1px solid var(--line-soft);
}
.collection__num {
  font-family: var(--font-body); font-size: .8rem; letter-spacing: .12em;
  color: var(--accent-text); font-variant-numeric: tabular-nums;
}
.collection__title { font-family: var(--font-display); font-weight: 500; font-size: clamp(1.8rem, 3vw, 2.6rem); letter-spacing: -.01em; }
.collection__hook { color: var(--muted); font-size: .98rem; margin-left: auto; }

/* 3-across contact sheet, feature = 2/3 szerokości. Spany 4 i 8 dzielą 12,
   więc grid-auto-flow: dense układa mix feature/zwykłych bez dziur
   niezależnie od kolejności ujęć. */
.grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: clamp(10px, 1vw, 16px); grid-auto-flow: row dense; }
.gtile-wrap { display: contents; }
.gtile {
  position: relative; grid-column: span 4; display: block; width: 100%;
  aspect-ratio: 16 / 10; padding: 0; border: .5px solid var(--line-soft);
  border-radius: 12px; overflow: hidden; background: var(--bg-2); cursor: pointer;
  transition: border-color .3s var(--ease);
}
.gtile--feature { grid-column: span 8; }
.gtile picture, .gtile img { position: absolute; inset: 0; width: 100%; height: 100%; }
.gtile img { object-fit: cover; will-change: transform; transition: transform .6s var(--ease); }
.gtile:hover { border-color: rgba(237, 237, 239, .22); }
.gtile:hover img { transform: scale(1.045); }
.gtile__label {
  position: absolute; left: 10px; bottom: 9px; z-index: 3;
  font-size: .78rem; color: var(--ink);
  background: rgba(10, 10, 10, .62); backdrop-filter: blur(6px);
  border: .5px solid var(--line-soft); border-radius: 999px; padding: .25em .7em;
}
.gtile:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

@media (max-width: 760px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
  .gtile { grid-column: span 1; }
  .gtile--feature { grid-column: span 2; }
  .collection__hook { margin-left: 0; width: 100%; }
  .gcat__btn { font-size: .88rem; padding: .55em .9em; }
}

/* prehide kafli — kill FOUC zanim JS odsłoni (spójnie z .line__inner) */
html.anim-on .grid .gtile { clip-path: inset(100% 0 0 0); }
html.anim-failed .grid .gtile, html:not(.anim-on) .grid .gtile { clip-path: none; }

/* ===== lightbox ===== */
.lightbox {
  position: fixed; inset: 0; z-index: 10001;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(16px, 4vw, 56px);
  background: rgba(8, 8, 8, .94); backdrop-filter: blur(8px);
}
.lightbox[hidden] { display: none; }
.lightbox::after {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: .04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.lightbox__figure { position: relative; z-index: 1; margin: 0; max-width: 100%; max-height: 100%; display: flex; flex-direction: column; align-items: center; gap: .8rem; }
.lightbox__img { max-width: 100%; max-height: 82vh; border-radius: 10px; border: .5px solid var(--line-soft); will-change: transform; }
.lightbox__cap { font-size: .85rem; color: var(--muted); display: flex; gap: .7em; align-items: baseline; }
.lightbox__cap__title::after { content: '·'; margin-left: .7em; opacity: .6; }
.lightbox__cap__shot { color: var(--ink); }
.lightbox__counter { font-variant-numeric: tabular-nums; opacity: .7; }
.lightbox__close, .lightbox__nav {
  position: absolute; z-index: 2; color: var(--ink);
  background: rgba(18, 18, 20, .55); border: 1px solid rgba(255, 255, 255, .10);
  -webkit-backdrop-filter: blur(18px) saturate(1.3); backdrop-filter: blur(18px) saturate(1.3);
  border-radius: 999px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.lightbox__close:hover, .lightbox__nav:hover { background: var(--accent); border-color: var(--accent); }
.lightbox__close { top: clamp(12px, 3vw, 28px); right: clamp(12px, 3vw, 28px); width: 44px; height: 44px; font-size: 1.5rem; line-height: 1; }
.lightbox__nav { top: 50%; transform: translateY(-50%); width: 52px; height: 52px; font-size: 2rem; line-height: 1; }
.lightbox__nav--prev { left: clamp(8px, 2vw, 24px); }
.lightbox__nav--next { right: clamp(8px, 2vw, 24px); }
.lightbox__close:focus-visible, .lightbox__nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

@media (max-width: 760px) {
  .lightbox__nav { width: 44px; height: 44px; font-size: 1.6rem; }
  .lightbox__img { max-height: 74vh; }
}

@media (prefers-reduced-motion: reduce) {
  html.anim-on .grid .gtile { clip-path: none; }
  .gtile img, .lightbox__img { will-change: auto; }
  .gtile img { transition: none; }
  .gtile:hover img { transform: none; }
  .gcat__menu, .gcat.is-open .gcat__menu { transition: none; transform: none; }
  .gcat__chev { transition: none; }
  .gcat__btn, .gcat__item, .gcat__item::before, .gcat__item__c { transition: none; }
}

/* -- lightbox scroll-lock (Lenis held in a closure, so lock via a class on <html>) -- */
html.lb-open { overflow: hidden; }
