/* ============================================================
   Dmitrii Andreenko — portfolio
   Editorial / typographic system · EN / RU
   ------------------------------------------------------------
   1. Tokens     4. Header    7. About      10. Contact
   2. Base       5. Hero      8. Practice   11. Footer
   3. Layout     6. Work      9. PDF        12. Responsive
   ============================================================ */

/* ── 1. TOKENS ───────────────────────────────────────────── */
:root{
  /* palette — light.
     --paper is the acid field the cards float on; --card is the
     surface content actually sits on. Ink is warm-shifted to sit
     with the lime rather than fight it.                        */
  --paper:      #f4f5f8;              /* the field panels sit on */
  --card:       #ffffff;              /* panel surface */
  --paper-2:    #eef0f6;              /* inset surfaces on a panel */
  --ink:        #0b0d15;
  --ink-2:      #454a5d;
  --ink-3:      #686e83;
  --rule:       rgba(11,13,21,.15);
  --rule-soft:  rgba(11,13,21,.08);
  --accent:     #1b4ef5;
  --accent-2:   #1b4ef5;              /* accent for large fills */
  --accent-ink: #ffffff;
  --accent-wash: rgba(27,78,245,.08);
  --stamp:      #ff6a13;
  --stamp-ink:  #241703;
  /* The stamp orange is 2.87:1 — a fill colour, never a text colour.
     --warm-ink is the same hue darkened until it passes AA (4.85 on
     paper), for anywhere orange has to be read rather than seen.   */
  --warm-ink:   #b8480c;
  --warm-wash:  rgba(255,106,19,.07);
  /* atmospheric field behind the hero */
  --field-tint:  rgba(27,78,245,.17);
  --field-deep:  rgba(27,78,245,.24);
  --grain-op:    .16;
  /* lifted off the page vs pressed into it — same number of
     drop-shadow() functions in both so the filter interpolates */
  --stamp-lift:  drop-shadow(0 10px 20px rgba(24,14,0,.30)) drop-shadow(0 2px 5px rgba(24,14,0,.18));
  --stamp-press: drop-shadow(0 1px 2px rgba(24,14,0,.22))   drop-shadow(0 0 1px rgba(24,14,0,.10));
  /* white on light grey is a small value step, so the panel edge is
     carried by a hairline border and the shadow supplies the lift */
  --card-shadow: 0 14px 34px rgba(11,13,21,.09), 0 2px 6px rgba(11,13,21,.05);

  /* type */
  --serif: "Instrument Serif", "Playfair Display", "Iowan Old Style", Georgia, serif;
  --sans:  "Golos Text", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono:  "Victor Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* fluid scale */
  --t-xs:   0.75rem;
  --t-sm:   0.875rem;
  --t-base: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  --t-lg:   clamp(1.125rem, 1.04rem + 0.42vw, 1.375rem);
  --t-xl:   clamp(1.625rem, 1.35rem + 1.2vw, 2.375rem);
  --t-2xl:  clamp(2.125rem, 1.55rem + 2.6vw, 3.75rem);
  --t-3xl:  clamp(2.5rem, 1.15rem + 5.7vw, 6.5rem);

  /* space */
  /* one radius scale — panels set the outer curve, everything
     nested inside steps down from it so corners stay concentric */
  --radius:     clamp(14px, 2vw, 22px);
  --radius-md:  clamp(9px, 1.3vw, 14px);
  --radius-sm:  6px;

  --gap:   clamp(1.25rem, 0.9rem + 1.6vw, 2.25rem);
  --sect:  clamp(4.5rem, 3rem + 7vw, 9.5rem);
  --wrap:  76rem;

  --ease: cubic-bezier(.2,.7,.25,1);
}

/* Instrument Serif has no Cyrillic, so Russian was falling through to
   Playfair for some strings and staying Instrument for others — two
   serifs in one heading. Russian gets Playfair for all of it.      */
[lang="ru"]{
  --serif: "Playfair Display", "Iowan Old Style", Georgia, serif;
}

[data-theme="dark"]{
  --paper:      #090b12;
  --card:       #141823;
  --paper-2:    #1e2230;
  --ink:        #edeff6;
  --ink-2:      #a5abbf;
  --ink-3:      #868da3;
  --rule:       rgba(237,239,246,.18);
  --rule-soft:  rgba(237,239,246,.09);
  --accent:     #7391ff;
  --accent-2:   #2f5cf7;
  --accent-ink: #070910;
  --accent-wash: rgba(115,145,255,.12);
  --stamp:      #ff8330;
  --stamp-ink:  #241703;
  /* on a dark page the stamp orange is already legible as text */
  --warm-ink:   #ff8330;
  --warm-wash:  rgba(255,131,48,.10);
  /* on a near-black page the tint has to add light, not subtract it,
     so the grain switches from multiply to overlay */
  --field-tint:  rgba(115,145,255,.15);
  --field-deep:  rgba(115,145,255,.22);
  --grain-op:    .13;
  /* a cast shadow reads as nothing on a near-black page, so the
     lift is carried by a warm halo instead */
  --stamp-lift:  drop-shadow(0 0 26px rgba(255,131,48,.45)) drop-shadow(0 10px 18px rgba(0,0,0,.55));
  --stamp-press: drop-shadow(0 0 6px rgba(255,131,48,.18))  drop-shadow(0 1px 3px rgba(0,0,0,.40));
  --card-shadow: 0 18px 40px rgba(0,0,0,.5), 0 3px 10px rgba(0,0,0,.35);
}

/* ── 2. BASE ─────────────────────────────────────────────── */
*,*::before,*::after{ box-sizing:border-box; }

html{
  -webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  scroll-padding-top:2rem;
  scroll-padding-bottom:6rem;
}

body{
  margin:0;
  padding-bottom:clamp(5rem,9vw,7rem);   /* dock clearance */
  background:var(--paper);
  color:var(--ink);
  font-family:var(--sans);
  font-size:var(--t-base);
  line-height:1.6;
  font-weight:400;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
  transition:background .5s var(--ease), color .5s var(--ease);
}

img,svg{ display:block; max-width:100%; }
ul,ol{ margin:0; padding:0; list-style:none; }
h1,h2,h3,p,dl,dd{ margin:0; }
a{ color:inherit; text-decoration:none; }

::selection{ background:var(--accent-2); color:#fff; }

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

.skip-link{
  position:absolute; left:-999px; top:0; z-index:100;
  background:var(--accent-2); color:#fff;
  padding:.75rem 1.25rem; font-size:var(--t-sm);
}
.skip-link:focus{ left:0; }

/* Instrument Serif has no Cyrillic — Playfair picks it up per-character.
   Slight optical correction so the two faces sit at a similar size.    */
:lang(ru) .hero__title,
:lang(ru) .section-title,
:lang(ru) .card__title,
:lang(ru) .about__text .lead,
:lang(ru) .contact-line,
:lang(ru) .pdf__lede{ letter-spacing:-.018em; }

/* ── ATMOSPHERE ──────────────────────────────────────────
   A blue vignette over the first screen that dissolves into the
   flat page colour further down. Absolutely positioned rather
   than fixed, so it scrolls away with the hero instead of
   following the viewport. z-index:-1 puts it above the body
   background but below all content.                          */
.field{
  position:absolute;
  top:0; left:0; right:0;
  height:min(155vh, 1400px);
  z-index:-1;
  pointer-events:none;
  /* clear in the middle, tinted toward the edges — the vignette */
  background:
    radial-gradient(115% 85% at 50% 12%,
      transparent 0 38%,
      var(--field-tint) 78%,
      var(--field-deep) 100%);
  /* and the whole thing fades out on the way down */
  -webkit-mask-image:linear-gradient(#000 0 42%, transparent 100%);
          mask-image:linear-gradient(#000 0 42%, transparent 100%);
}
/* the gallery has no hero to sit behind, so its field is shallower */
body:has(.section--page) .field{ height:min(85vh, 780px); }
/* Grain. Sits inside .field so it inherits the same fade — a mask
   applies to an element's whole painted subtree, pseudo-elements
   included, so the noise dissolves exactly where the tint does.
   No mix-blend-mode: the mask makes .field a stacking context, so
   the grain would blend against the gradient instead of the page.
   Desaturated noise at low opacity gets the same look and behaves
   identically everywhere.                                       */
.field::after{
  content:"";
  position:absolute; inset:0;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:180px 180px;
  opacity:var(--grain-op);
}

/* ── 3. LAYOUT ───────────────────────────────────────────── */
.wrap{
  width:min(100% - clamp(1.5rem,5vw,5rem)*2, var(--wrap));
  margin-inline:auto;
}

.section{ padding-block:clamp(1rem,2.5vw,2rem); }


.section-head{
  display:grid;
  grid-template-columns:auto 1fr auto;
  align-items:baseline;
  gap:clamp(.75rem,2vw,1.5rem);
  padding-bottom:1.25rem;
  margin-bottom:clamp(2.5rem,5vw,4.5rem);
  border-bottom:1px solid var(--rule);
}
.section-num{
  font-family:var(--mono);
  font-size:var(--t-xs);
  color:var(--accent);
  letter-spacing:.08em;
}
.section-title{
  font-family:var(--serif);
  font-size:var(--t-xl);
  font-weight:400;
  letter-spacing:-.015em;
  line-height:1.1;
}

/* Content resolves out of focus rather than sliding up. Nothing moves,
   so there's no step to catch the eye — the page just comes into
   focus at the speed you scroll it.
   The stamp is excluded because it already owns `filter` for its
   drop-shadow, and a second declaration would clobber the lift.   */
.reveal{
  opacity:0;
  transition:opacity .75s var(--ease) var(--d,0s);
  will-change:opacity;
}
.reveal:not(.stamp){
  filter:blur(12px);
  transition:opacity .75s var(--ease) var(--d,0s),
             filter  .75s var(--ease) var(--d,0s);
  will-change:opacity, filter;
}
.reveal.is-in{ opacity:1; }
.reveal:not(.stamp).is-in{ filter:blur(0px); will-change:auto; }

@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  .reveal, .reveal:not(.stamp){ opacity:1; filter:none; transition:none; }
  .ticker__track{ animation:none !important; }
  *{ animation-duration:.01ms !important; transition-duration:.01ms !important; }
}

/* ── SCROLL VEIL ─────────────────────────────────────────
   A band of graduated glass across the foot of the viewport.
   Everything scrolls up through it, so a line of type resolves
   out of blur instead of arriving already sharp.

   Four stacked backdrop layers rather than one: each blurs more
   than the last and is masked to a shorter band, so the falloff
   reads as depth. A single frosted strip reads as a bar.      */
.veil{
  position:fixed;
  left:0; right:0; bottom:0;
  height:clamp(88px, 13vh, 150px);
  z-index:40;                    /* over the page, under the dock */
  pointer-events:none;
}
.veil i{
  position:absolute; inset:0; display:block;
  -webkit-backdrop-filter:blur(var(--b));
  backdrop-filter:blur(var(--b));
  -webkit-mask-image:linear-gradient(to top,
    #000 0, #000 calc(var(--stop) * .3), transparent var(--stop));
  mask-image:linear-gradient(to top,
    #000 0, #000 calc(var(--stop) * .3), transparent var(--stop));
}
.veil i:nth-child(1){ --b:1px;  --stop:100%; }
.veil i:nth-child(2){ --b:3px;  --stop:72%;  }
.veil i:nth-child(3){ --b:7px;  --stop:46%;  }
.veil i:nth-child(4){ --b:16px; --stop:24%;  }
/* the page colour closing in at the very bottom edge — this is what
   makes it read as glass and not just as soft focus */
.veil::after{
  content:"";
  position:absolute; inset:0;
  background:linear-gradient(to top, var(--paper) 0%, transparent 72%);
  opacity:.6;
}

/* no backdrop-filter, no veil — a plain gradient would just look like
   a smudge over the footer */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .veil{ display:none; }
}
@media (prefers-reduced-transparency:reduce), (prefers-reduced-motion:reduce){
  .veil{ display:none; }
}

/* ── 4. DOCK ─────────────────────────────────────────────── */

/* Floating dock, macOS-style: the only chrome on the page.
   Sits over the lime field, so it needs its own surface.      */
.dock-zone{
  position:fixed;
  left:50%; bottom:clamp(.9rem,2.5vw,1.6rem);
  transform:translateX(-50%);
  z-index:60;
  display:flex; align-items:center; gap:.6rem;
  max-width:calc(100% - 1.5rem);
}
.dock{
  position:relative;
  display:flex; align-items:center; gap:.5rem;
  padding:.45rem .55rem;
  border-radius:99px;
  background:color-mix(in srgb, var(--card) 82%, transparent);
  border:1px solid var(--rule-soft);
  box-shadow:var(--card-shadow);
  backdrop-filter:blur(18px) saturate(1.5);
  -webkit-backdrop-filter:blur(18px) saturate(1.5);
}
/* The bar has to be clipped to the pill's curve, but the dock itself
   can't clip — the mobile menu pops out of it. So the bar gets its
   own rail that inherits the radius and does the clipping locally. */
.dock__rail{
  position:absolute; inset:0;
  border-radius:inherit;
  overflow:hidden;
  pointer-events:none;
}
.dock__progress{
  position:absolute; left:0; top:0; height:2px;
  width:0%; background:var(--accent-2);
  transition:width .1s linear;
}
.dock__nav{ display:flex; align-items:center; gap:.1rem; }
.dock__link{
  display:block;
  padding:.5rem .8rem;
  border-radius:99px;
  font-size:var(--t-sm);
  color:var(--ink-2);
  white-space:nowrap;
  transition:color .25s var(--ease), background .25s var(--ease);
}
.dock__link:hover{ color:var(--ink); background:var(--rule-soft); }
.dock__link.is-active{ color:#fff; background:var(--accent-2); }
.dock__sep{
  width:1px; height:1.4rem;
  background:var(--rule);
  margin-inline:.2rem;
  flex:none;
}
/* ── BURGER ──────────────────────────────────────────────
   Five links, a language pair and a theme toggle do not fit
   across an iPhone. Below 640 the links fold into a panel
   above the dock and the dock keeps only three controls. */
.dock__burger{ display:none; }
.dock__burger-i{
  position:relative; display:block;
  width:15px; height:1.5px; border-radius:2px;
  background:currentColor;
  transition:background .2s var(--ease);
}
.dock__burger-i::before,
.dock__burger-i::after{
  content:""; position:absolute; left:0;
  width:100%; height:100%; border-radius:2px;
  background:currentColor;
  transition:transform .3s var(--ease);
}
.dock__burger-i::before{ top:-5px; }
.dock__burger-i::after { top: 5px; }
.dock__burger[aria-expanded="true"] .dock__burger-i{ background:transparent; }
.dock__burger[aria-expanded="true"] .dock__burger-i::before{ transform:translateY(5px)  rotate(45deg); }
.dock__burger[aria-expanded="true"] .dock__burger-i::after { transform:translateY(-5px) rotate(-45deg); }

.dock .lang{ border:0; padding:0; }
.dock .icon-btn{ width:2.1rem; height:2.1rem; border:0; }
.dock .icon-btn:hover{ background:var(--rule-soft); }

/* back to top — its own pill, anchored beside the dock */
.dock-top{
  display:grid; place-items:center;
  width:2.9rem; height:2.9rem;
  flex:none;
  border-radius:50%;
  background:color-mix(in srgb, var(--card) 82%, transparent);
  border:1px solid var(--rule-soft);
  box-shadow:var(--card-shadow);
  backdrop-filter:blur(18px) saturate(1.5);
  -webkit-backdrop-filter:blur(18px) saturate(1.5);
  color:var(--ink);
  opacity:0; pointer-events:none;
  transform:translateY(6px) scale(.9);
  transition:opacity .35s var(--ease), transform .35s var(--ease),
             background .25s var(--ease), color .25s var(--ease);
}
.dock-top.is-shown{ opacity:1; pointer-events:auto; transform:none; }
.dock-top:hover{ background:var(--accent-2); color:#fff; border-color:transparent; }
.dock-top svg{
  width:1.1rem; height:1.1rem;
  fill:none; stroke:currentColor; stroke-width:1.7;
  stroke-linecap:round; stroke-linejoin:round;
}

/* masthead — the name, now that there is no header bar */
.masthead{
  font-family:var(--serif);
  font-size:var(--t-lg);
  letter-spacing:-.015em;
  margin-bottom:clamp(2rem,5vw,3.5rem);
}
.masthead a{ transition:color .25s var(--ease); }
.masthead a:hover{ color:var(--accent); }

/* ── FLOATING PANELS ─────────────────────────────────────
   Content sits on a card; the lime shows around it. The
   lime shows around it as the field.                      */
.panel{
  position:relative;
  isolation:isolate;
  background:var(--card);
  border-radius:var(--radius);
  padding:clamp(1.75rem,4vw,3.5rem);
  border:1px solid var(--rule-soft);
  box-shadow:var(--card-shadow);
}

.lang{
  display:inline-flex; align-items:center; gap:.15rem;
  padding:.2rem .3rem;
  border:1px solid var(--rule);
  border-radius:99px;
}
.lang__btn{
  font-family:var(--mono);
  font-size:.6875rem;
  font-weight:500;
  letter-spacing:.06em;
  color:var(--ink-3);
  background:transparent;
  border:0; border-radius:99px;
  padding:.32rem .55rem;
  cursor:pointer;
  transition:color .25s var(--ease), background .25s var(--ease);
}
.lang__btn:hover{ color:var(--ink); }
.lang__btn.is-active{ background:var(--accent-2); color:#fff; }
.icon-btn{
  display:grid; place-items:center;
  width:2.35rem; height:2.35rem;
  border:1px solid var(--rule);
  border-radius:50%;
  background:transparent; color:var(--ink);
  cursor:pointer;
  transition:border-color .25s var(--ease), background .25s var(--ease), color .25s var(--ease);
}
.icon-btn:hover{ border-color:var(--accent); color:var(--accent); }
.icon{
  width:1rem; height:1rem;
  fill:none; stroke:currentColor; stroke-width:1.6;
  stroke-linecap:round; stroke-linejoin:round;
}
.icon--moon{ display:none; }
[data-theme="dark"] .icon--sun{ display:none; }
[data-theme="dark"] .icon--moon{ display:block; }

/* ── 5. HERO ─────────────────────────────────────────────── */
.hero{
  padding-top:clamp(3.5rem,8vh,6rem);
  padding-bottom:clamp(3rem,6vw,5rem);
}
.hero .wrap{ position:relative; }

.hero__title{
  font-family:var(--serif);
  font-weight:400;
  font-size:var(--t-3xl);
  line-height:.94;
  letter-spacing:-.028em;
  margin-bottom:clamp(2.5rem,6vw,4.5rem);
  /* Reserve the stamp's column so long lines can never run under it.
     At 5° the rotated footprint (w·cos5 + h·sin5) is only ~10px
     wider than upright, so the guard can be narrower than it was
     at 15° and the headline gets the width back.                */
  max-width:calc(100% - clamp(11.5rem, 15vw, 13rem));
}
.hero__title .line{ display:block; }
.hero__title em{
  font-style:italic;
  color:var(--accent);
}

.hero__foot{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  align-items:end;
  gap:var(--gap);
  padding-top:clamp(1.5rem,3vw,2.25rem);
  border-top:1px solid var(--rule);
}
.hero__lede{
  max-width:48ch;
  font-size:var(--t-lg);
  line-height:1.5;
  color:var(--ink-2);
  text-wrap:pretty;
}
.hero__cta{
  display:inline-flex; align-items:center; gap:.6rem;
  font-size:var(--t-sm);
  font-weight:500;
  letter-spacing:.01em;
  padding:.85rem 1.4rem;
  border:1px solid var(--accent-2);
  color:var(--accent);
  border-radius:99px;
  white-space:nowrap;
  transition:background .3s var(--ease), color .3s var(--ease);
}
.hero__cta svg{
  width:1rem; height:1rem; fill:none; stroke:currentColor;
  stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round;
  transition:transform .35s var(--ease);
}
.hero__cta:hover{ background:var(--accent-2); color:#fff; }
.hero__cta:hover svg{ transform:translateY(3px); }

/* ── STAMP STICKER ───────────────────────────────────────
   Wrapper carries position, rotation and drop-shadow.
   The inner .stamp__paper carries the mask — filter is
   applied before mask, so the shadow has to live one level
   up to follow the perforated edge instead of the box.     */
.stamp{
  position:absolute;
  top:clamp(.5rem,3vw,2.5rem); right:0;
  z-index:2;
  display:inline-block;
  /* NB: the rotation lives on .stamp__paper, not here. This element
     carries .reveal, so anything it sets that .reveal also sets gets
     fought over on a single-class tie.                             */
  filter:var(--stamp-lift);
  /* both halves spelled out: this rule wins the tie with .reveal on
     source order, so omitting opacity here would kill its fade-in */
  transition:filter .5s var(--ease),
             opacity .75s var(--ease) var(--d,0s);
}
/* hover = pressing it down: straightens, shrinks, shadow collapses */
.stamp:hover, .stamp:focus-visible{
  filter:var(--stamp-press);
  transition-duration:.2s;
}
.stamp:active{ filter:var(--stamp-press); }

.stamp__paper{
  --notch:5px;
  --step:16px;
  /* Small circles on a tight pitch leave only (step/2 - notch) = 3px
     of solid at each corner, so any mismatch between the horizontal
     and vertical tiling would show. main.js rounds this box up to an
     exact multiple of --step, which makes both axes share one step
     and the four corners identical by construction.               */
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:.3rem;
  padding:1.4rem 1.5rem 1.25rem;
  /* rest: tilted and lifted. hover: flat against the page. */
  transform:rotate(-5deg) skewY(-.3deg);
  transition:transform .5s var(--ease);
  background:var(--stamp);
  color:var(--stamp-ink);
  text-align:center;

  -webkit-mask:
    radial-gradient(var(--notch) at 50% 0,    #0000 97%, #000) 0 0    / var(--step) 100%,
    radial-gradient(var(--notch) at 50% 100%, #0000 97%, #000) 0 100% / var(--step) 100%,
    radial-gradient(var(--notch) at 0 50%,    #0000 97%, #000) 0 0    / 100% var(--step),
    radial-gradient(var(--notch) at 100% 50%, #0000 97%, #000) 100% 0 / 100% var(--step);
  mask:
    radial-gradient(var(--notch) at 50% 0,    #0000 97%, #000) 0 0    / var(--step) 100%,
    radial-gradient(var(--notch) at 50% 100%, #0000 97%, #000) 0 100% / var(--step) 100%,
    radial-gradient(var(--notch) at 0 50%,    #0000 97%, #000) 0 0    / 100% var(--step),
    radial-gradient(var(--notch) at 100% 50%, #0000 97%, #000) 100% 0 / 100% var(--step);
  -webkit-mask-composite:source-in;
  mask-composite:intersect;
  /* `round` rescales each tile so a whole number fits the edge.
     With plain `repeat` the final notch is cut mid-circle wherever
     the box isn't an exact multiple of --step — which is what was
     chipping the corners.                                        */
  -webkit-mask-repeat:round;
  mask-repeat:round;

  /* dashed frame, held clear of the perforations */
  outline:1px dashed color-mix(in srgb, var(--stamp-ink) 45%, transparent);
  outline-offset:-10px;
}
.stamp:hover .stamp__paper,
.stamp:focus-visible .stamp__paper{
  transform:rotate(0deg) skewY(0deg) scale(.93);
  transition-duration:.2s;
}
.stamp:active .stamp__paper{ transform:rotate(0deg) skewY(0deg) scale(.88); }

.stamp__eyebrow{
  font-family:var(--mono);
  font-size:.5625rem; font-weight:500;
  letter-spacing:.18em; text-transform:uppercase;
  opacity:.75;
}
.stamp__main{
  font-family:var(--serif);
  font-size:clamp(1.35rem,2.2vw,1.75rem);
  line-height:1;
  letter-spacing:-.015em;
  white-space:nowrap;
}
.stamp__rule{
  width:2rem; height:1px;
  background:color-mix(in srgb, var(--stamp-ink) 40%, transparent);
  margin:.15rem 0;
}
.stamp__sub{
  display:inline-flex; align-items:center; gap:.3rem;
  font-family:var(--mono);
  font-size:.625rem; letter-spacing:.06em;
  opacity:.8;
}
.stamp__sub svg{
  width:.75rem; height:.75rem;
  fill:none; stroke:currentColor; stroke-width:2;
  stroke-linecap:round; stroke-linejoin:round;
}

/* ticker */
.ticker{
  margin-top:clamp(3.5rem,8vw,6rem);
  padding-block:1rem;
  border-block:1px solid var(--rule-soft);
  overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
.ticker__track{
  display:flex; gap:1.5rem; width:max-content;
  animation:marquee 46s linear infinite;
  font-family:var(--serif);
  font-size:clamp(1.25rem,3vw,2rem);
  letter-spacing:-.01em;
  color:var(--ink-2);
  white-space:nowrap;
}
/* Drawn rather than typed. U+2733 is inside the emoji range, so iOS
   swapped it for a colour glyph that had nothing to do with the page.
   A path renders identically on every client.                     */
.ticker__sep{
  flex:none; align-self:center;
  width:.42em; height:.42em;
  fill:none; stroke:var(--accent);
  stroke-width:1.4; stroke-linecap:round;
}
@keyframes marquee{ to{ transform:translateX(-50%); } }

/* ── 6. WORK ─────────────────────────────────────────────── */
.filters{
  display:flex; flex-wrap:wrap; gap:.5rem;
  margin-bottom:clamp(1.75rem,4vw,2.75rem);
}
.filter{
  font-family:var(--sans);
  font-size:var(--t-sm);
  color:var(--ink-2);
  padding:.45rem 1rem;
  border:1px solid var(--rule);
  border-radius:99px;
  background:transparent;
  cursor:pointer;
  transition:all .25s var(--ease);
}
.filter:hover{ border-color:var(--accent); color:var(--accent); }
.filter.is-active{ background:var(--accent-2); border-color:var(--accent-2); color:#fff; }

/* Two colours, so the split is ink-on-paper against everything on a
   screen: print wears the orange, the rest stays blue. The chip uses
   --warm-ink, not the stamp orange, which is 2.87:1 and would fail
   as a background for white type.                                 */


.work-grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  /* the row gap has to beat the image-to-text gap by a clear margin,
     or a caption reads as belonging to the card below it */
  gap:clamp(3.25rem,7vw,5.5rem) var(--gap);
}
.card--wide{ grid-column:span 2; }
.card[hidden]{ display:none; }
.card__link{ display:block; }

.card__visual{
  position:relative;
  aspect-ratio:4/3;
  overflow:hidden;
  border-radius:var(--radius-md);
  background:var(--accent-wash);
  color:var(--ink);
  margin-bottom:.85rem;
}
/* 16/8 cropped a third off the cover photos, which are 4:3 and have
   the object centred with margin — 16/10 keeps the whole subject */
.card--wide .card__visual{ aspect-ratio:16/10; }

.art{ width:100%; height:100%; transition:transform 1.1s var(--ease); }
.art-bg{ fill:var(--paper-2); }
.art-fg{ fill:currentColor; }
.card__link:hover .art{ transform:scale(1.035); }

/* Real cover artwork. Every frame is stacked in the same box and
   cross-faded, so the card previews the project's images on hover
   instead of tinting. Extra frames are injected by main.js on first
   hover — building them upfront would download every spread on the
   page before anyone asked to see one.                            */
.card__visual picture{
  display:block;
  position:absolute; inset:0;
  width:100%; height:100%;
  transition:opacity .55s var(--ease);
}
/* Only hide frames once JS has actually built the stack. Defaulting
   them to opacity:0 would leave every card blank with JS disabled,
   or during the gap before first hover.                          */
.card__visual.is-slideshow picture{ opacity:0; }
.card__visual.is-slideshow picture.is-on{ opacity:1; }
.card__visual picture img{
  width:100%; height:100%;
  object-fit:cover;
  display:block;
  transition:transform 1.1s var(--ease);
}
.card__link:hover .card__visual picture img{ transform:scale(1.03); }

/* a quiet progress row, so a multi-image card says so */
.card__dots{
  position:absolute; left:.7rem; bottom:.7rem; z-index:2;
  display:flex; gap:.28rem;
  opacity:0; transition:opacity .3s var(--ease);
}
.card__link:hover .card__dots,
.card__link:focus-visible .card__dots{ opacity:1; }
.card__dots i{
  width:16px; height:2px; border-radius:2px;
  background:color-mix(in srgb, var(--card) 55%, transparent);
  transition:background .3s var(--ease);
}
.card__dots i.is-on{ background:var(--card); }


.card__meta{
  display:flex; justify-content:space-between; gap:1rem;
  font-family:var(--mono);
  font-size:var(--t-xs);
  letter-spacing:.05em;
  color:var(--ink-3);
  padding-bottom:.65rem;
  margin-bottom:.85rem;
  border-bottom:1px solid var(--rule-soft);
}
/* The category word takes its ink, so the grid separates into printed
   work and screen work before a single label is read. Colour is never
   the only signal — the word is still the word.                     */
.card__meta span:first-child{ color:var(--accent); }

/* Surface, not just mark: the well a cover sits in takes the same
   tint. Mostly seen at the corners and while the image loads, which
   is the point — it should register without being noticed.        */


.card__title{
  font-family:var(--serif);
  font-weight:400;
  font-size:var(--t-lg);
  line-height:1.2;
  letter-spacing:-.012em;
  margin-bottom:.55rem;
  text-wrap:balance;
}
.card--wide .card__title{ max-width:22ch; font-size:var(--t-xl); }
.card__desc{
  color:var(--ink-2);
  font-size:var(--t-sm);
  line-height:1.6;
  max-width:52ch;
  margin-bottom:1rem;
}
.card__more{
  display:inline-flex; align-items:center; gap:.4rem;
  font-size:var(--t-xs);
  font-weight:500;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--accent);
}
.card__more svg{
  width:.9rem; height:.9rem; fill:none; stroke:currentColor;
  stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round;
  transition:transform .35s var(--ease);
}
.card__link:hover .card__more svg{ transform:translateX(4px); }

.grid-empty{
  text-align:center; color:var(--ink-3);
  font-size:var(--t-sm); padding-block:3rem;
}

.card__badge{
  position:absolute; top:.85rem; left:.85rem;
  font-family:var(--mono); font-size:.625rem; font-weight:500;
  letter-spacing:.12em; text-transform:uppercase;
  background:var(--accent-2); color:#fff;
  padding:.32rem .6rem; border-radius:99px;
  z-index:1;
}

/* ── GALLERY PAGE ────────────────────────────────────────── */
.section--page{ padding-top:clamp(3rem,7vh,5rem); }

.back-link{
  display:inline-flex; align-items:center; gap:.45rem;
  font-size:var(--t-sm); color:var(--ink-2);
  margin-bottom:clamp(1.5rem,3vw,2.25rem);
  transition:color .25s var(--ease);
}
.back-link svg{
  width:1rem; height:1rem; fill:none; stroke:currentColor;
  stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round;
  transition:transform .3s var(--ease);
}
.back-link:hover{ color:var(--accent); }
.back-link:hover svg{ transform:translateX(-3px); }

.page-lede{
  font-family:var(--serif);
  font-size:var(--t-xl); font-weight:400;
  line-height:1.26; letter-spacing:-.015em;
  max-width:32ch;
  margin-bottom:clamp(2.5rem,5vw,4rem);
  text-wrap:balance;
}

/* Columns rather than a grid: these are posters, slides and square
   social crops all mixed together, and a fixed tile ratio cropped the
   landscape ones in half. Each piece keeps its own proportions.    */
.gal-grid{
  columns:3;
  column-gap:var(--gap);
}
.gal{
  break-inside:avoid;
  margin-bottom:var(--gap);
}
.gal__fig{ margin:0; }
.gal__fig picture{ display:block; overflow:hidden; border-radius:var(--radius-md); }
.gal__fig img{
  width:100%; height:auto; display:block;
  background:var(--paper-2);
  transition:transform .6s var(--ease);
}
.gal__fig:hover img{ transform:scale(1.02); }
.gal__cap{
  display:block;
  margin-top:.7rem;
  font-family:var(--mono); font-size:var(--t-xs);
  color:var(--ink-3); letter-spacing:.04em;
}

/* ── 7. ABOUT ────────────────────────────────────────────── */
.about{
  display:grid;
  grid-template-columns:minmax(0,5fr) minmax(0,7fr);
  gap:clamp(2rem,5vw,4.5rem);
  align-items:start;
}
.portrait{
  position:relative;
  border-radius:var(--radius-md); overflow:hidden;
  background:var(--paper-2); color:var(--ink);
  aspect-ratio:4/5;
}
.portrait__ph{ width:100%; height:100%; }
.portrait__img{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
}
/* caption only shows while the photo is missing */

/* Instagram highlight */
.ig{
  display:flex; align-items:center; gap:.8rem;
  margin-top:.9rem;
  padding:.85rem 1rem;
  border:1px solid var(--rule);
  border-radius:var(--radius-md);
  transition:border-color .3s var(--ease), background .3s var(--ease);
}
.ig:hover{ border-color:var(--accent); background:var(--accent-wash); }
.ig__icon{
  width:1.3rem; height:1.3rem; flex:none;
  fill:none; stroke:var(--accent); stroke-width:1.6;
  stroke-linecap:round; stroke-linejoin:round;
}
.ig__text{ display:flex; flex-direction:column; line-height:1.3; min-width:0; }
.ig__label{ font-size:var(--t-sm); font-weight:500; }
.ig__handle{
  font-family:var(--mono); font-size:.6875rem;
  color:var(--ink-3); overflow-wrap:anywhere;
}
.ig__arrow{
  width:.95rem; height:.95rem; margin-left:auto; flex:none;
  fill:none; stroke:var(--ink-3); stroke-width:1.6;
  stroke-linecap:round; stroke-linejoin:round;
  transition:transform .35s var(--ease), stroke .3s var(--ease);
}
.ig:hover .ig__arrow{ transform:translate(3px,-3px); stroke:var(--accent); }

/* Icon links in the contact list */
.link--icon{ display:inline-flex; align-items:center; gap:.45rem; }
.link--icon svg{
  width:.95rem; height:.95rem; flex:none;
  fill:none; stroke:var(--accent); stroke-width:1.6;
  stroke-linecap:round; stroke-linejoin:round;
}

.about__text > p{
  margin-bottom:1.15em;
  color:var(--ink-2);
  max-width:62ch;
  text-wrap:pretty;
}
.about__text .lead{
  font-family:var(--serif);
  font-size:var(--t-xl);
  line-height:1.26;
  font-weight:400;
  letter-spacing:-.015em;
  color:var(--ink);
  margin-bottom:1.4rem;
}

.facts{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  margin-top:clamp(2rem,4vw,3rem);
  border-top:1px solid var(--rule);
}
.fact{ padding:1rem 0; border-bottom:1px solid var(--rule-soft); }
.fact:nth-child(odd){ padding-right:1rem; }
.fact:nth-child(even){ padding-left:1rem; border-left:1px solid var(--rule-soft); }
.fact dt{
  font-family:var(--mono); font-size:var(--t-xs);
  letter-spacing:.06em; color:var(--ink-3);
  margin-bottom:.3rem;
}
.fact dd{ font-size:var(--t-sm); }

/* ── 8. PRACTICE ─────────────────────────────────────────── */
.practice{
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  column-gap:clamp(1.75rem,4vw,3.5rem);
}
/* No rule above: the section head's own underline sits just there,
   and a second line beneath it read as a double rule.
   No horizontal padding either — every column aligns to its own
   grid line, and the first sits flush with the container edge.
   Separation comes from column-gap, so the blocks stay identical. */
/* Grid stretches the three columns to equal height, but the copy in
   them isn't equal, so the tag rows used to stop wherever the text
   ran out while the rules underneath stayed level. Flex column plus
   margin-top:auto on the tags anchors all three to the same line. */
.practice__item{
  display:flex; flex-direction:column;
  padding-block:0 clamp(1.75rem,3vw,2.5rem);
  border-bottom:1px solid var(--rule-soft);
}
.practice__item h3{
  font-family:var(--serif); font-weight:400;
  font-size:var(--t-lg); line-height:1.18;
  letter-spacing:-.012em;
  margin-bottom:.75rem;
}
.practice__item p{
  color:var(--ink-2); font-size:var(--t-sm);
  line-height:1.65; margin-bottom:1.35rem;
}
.tags{ display:flex; flex-wrap:wrap; gap:.4rem; margin-top:auto; }
.tags li{
  font-family:var(--mono); font-size:.6875rem;
  letter-spacing:.04em; color:var(--ink-3);
  padding:.28rem .6rem;
  border:1px solid var(--rule);
  border-radius:99px;
}

/* ── 9. PDF PORTFOLIO ────────────────────────────────────── */
.pdf{
  display:grid;
  grid-template-columns:minmax(0,4fr) minmax(0,7fr);
  gap:clamp(2rem,5vw,4.5rem);
  align-items:center;
}

.pdf__preview{
  position:relative;
  padding:clamp(1.5rem,3vw,2.5rem);
  background:var(--paper-2);
  border-radius:var(--radius-md);
  perspective:1000px;
}
.pdf__sheet{
  position:absolute; inset:clamp(1.5rem,3vw,2.5rem);
  border-radius:2px;
  background:var(--card);
  box-shadow:0 1px 2px rgba(0,0,0,.1);
  transition:transform .6s var(--ease);
}
.pdf__sheet--3{ transform:translate(14px,-14px) rotate(3.5deg); opacity:.4; }
.pdf__sheet--2{ transform:translate(7px,-7px) rotate(1.75deg); opacity:.7; }
.pdf__sheet--1{ position:relative; inset:auto; }
.pdf__sheet--1 svg{ width:100%; height:auto; display:block; }
.pdf-page{ fill:var(--card); }
.pdf-ink{ fill:var(--ink); }

.pdf:hover .pdf__sheet--3{ transform:translate(22px,-22px) rotate(5deg); }
.pdf:hover .pdf__sheet--2{ transform:translate(11px,-11px) rotate(2.5deg); }

/* The pill became the same postage stamp as the hero CTA, franked onto
   the corner of the document stack. Fixed 70x70 at a 10px step, so the
   box is an exact multiple of --step and the four corners come out
   identical without the JS quantiser the hero one needs.

   As on the hero: the drop-shadow lives on the wrapper, because filter
   is applied before mask and would otherwise trace the unmasked box
   rather than the perforated edge.                                   */
.pdf__stamp{
  position:absolute;
  left:77%; top:47.75%;
  transform:translate(-50%,-50%);
  filter:var(--stamp-lift);
  transition:filter .5s var(--ease);
  pointer-events:none;
}
.pdf__stamp-paper{
  --notch:3.5px;
  --step:10px;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:.1rem;
  width:70px; height:70px;
  background:var(--stamp);
  color:var(--stamp-ink);
  transform:rotate(-8deg);
  transition:transform .5s var(--ease);

  -webkit-mask:
    radial-gradient(var(--notch) at 50% 0,    #0000 97%, #000) 0 0    / var(--step) 100%,
    radial-gradient(var(--notch) at 50% 100%, #0000 97%, #000) 0 100% / var(--step) 100%,
    radial-gradient(var(--notch) at 0 50%,    #0000 97%, #000) 0 0    / 100% var(--step),
    radial-gradient(var(--notch) at 100% 50%, #0000 97%, #000) 100% 0 / 100% var(--step);
  mask:
    radial-gradient(var(--notch) at 50% 0,    #0000 97%, #000) 0 0    / var(--step) 100%,
    radial-gradient(var(--notch) at 50% 100%, #0000 97%, #000) 0 100% / var(--step) 100%,
    radial-gradient(var(--notch) at 0 50%,    #0000 97%, #000) 0 0    / 100% var(--step),
    radial-gradient(var(--notch) at 100% 50%, #0000 97%, #000) 100% 0 / 100% var(--step);
  -webkit-mask-composite:source-in;
  mask-composite:intersect;
  -webkit-mask-repeat:round;
  mask-repeat:round;

  outline:1px dashed color-mix(in srgb, var(--stamp-ink) 45%, transparent);
  outline-offset:-7px;
}
.pdf__stamp-main{
  font-family:var(--mono); font-size:.8125rem; font-weight:500;
  letter-spacing:.1em;
}
.pdf__stamp-sub{
  font-family:var(--mono); font-size:.5rem;
  letter-spacing:.08em;
  opacity:.7;
}
/* the sheets fan out and the stamp presses down, together */
.pdf:hover .pdf__stamp{ filter:var(--stamp-press); transition-duration:.2s; }
.pdf:hover .pdf__stamp-paper{ transform:rotate(0deg) scale(.93); transition-duration:.2s; }

/* ── the block runs on the stamp's orange, not the site blue ──
   The fill is 2.87:1, so it only ever carries near-black ink or
   acts as a shape. Anything that has to be read as orange text
   uses --warm-ink, the darkened sibling at 4.85:1 on paper.   */
#portfolio .section-num{ color:var(--warm-ink); }
#portfolio .btn--solid{ background:var(--stamp); color:var(--stamp-ink); }
#portfolio .btn--solid:hover{ background:var(--stamp-ink); color:var(--card); }
#portfolio .btn--ghost:hover{ border-color:var(--warm-ink); color:var(--warm-ink); }

.pdf__lede{
  font-family:var(--serif);
  font-size:var(--t-xl);
  font-weight:400;
  line-height:1.24;
  letter-spacing:-.015em;
  margin-bottom:1rem;
  max-width:26ch;
  text-wrap:balance;
}

.pdf__specs{
  display:grid;
  grid-template-columns:repeat(4,minmax(0,auto));
  gap:clamp(1.25rem,3vw,2.5rem);
  justify-content:start;
  padding-block:1.15rem;
  border-block:1px solid var(--rule-soft);
  margin-bottom:clamp(1.5rem,3vw,2rem);
}
.pdf__specs dt{
  font-family:var(--mono); font-size:.6875rem;
  letter-spacing:.07em; color:var(--ink-3);
  margin-bottom:.25rem;
}
.pdf__specs dd{ font-size:var(--t-sm); font-weight:500; white-space:nowrap; }

.pdf__actions{ display:flex; flex-wrap:wrap; gap:.6rem; }
.btn{
  display:inline-flex; align-items:center; gap:.55rem;
  font-size:var(--t-sm); font-weight:500;
  padding:.85rem 1.4rem;
  border-radius:99px;
  border:1px solid transparent;
  transition:background .3s var(--ease), color .3s var(--ease),
             border-color .3s var(--ease), transform .3s var(--ease);
}
.btn svg{
  width:1rem; height:1rem; fill:none; stroke:currentColor;
  stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round;
  transition:transform .35s var(--ease);
}
.btn--solid{ background:var(--accent-2); color:#fff; }
.btn--solid:hover{ background:var(--ink); }
.btn--solid:hover svg{ transform:translateY(3px); }
.btn--ghost{ border-color:var(--rule); color:var(--ink-2); }
.btn--ghost:hover{ border-color:var(--accent); color:var(--accent); }
.btn--ghost:hover svg{ transform:translate(3px,-3px); }


/* ── 10. CONTACT ─────────────────────────────────────────── */


.contact-line{
  font-family:var(--serif);
  font-size:var(--t-xl);
  font-weight:400;
  letter-spacing:-.015em;
  line-height:1.24;
  max-width:26ch;
  margin-bottom:clamp(1.5rem,3vw,2.25rem);
  text-wrap:balance;
}

.mailto{
  display:inline-flex; align-items:center; gap:clamp(.5rem,2vw,1.25rem);
  font-family:var(--serif);
  font-weight:400;
  font-size:var(--t-2xl);
  letter-spacing:-.025em;
  line-height:1.05;
  padding-bottom:.35rem;
  margin-bottom:1.6rem;
  border-bottom:1px solid var(--rule);
  transition:color .3s var(--ease), border-color .3s var(--ease);
  max-width:100%;
}
.mailto__text{ overflow-wrap:anywhere; }
.mailto svg{
  width:clamp(1.25rem,3vw,2rem); height:clamp(1.25rem,3vw,2rem);
  flex:none;
  fill:none; stroke:currentColor; stroke-width:1.4;
  stroke-linecap:round; stroke-linejoin:round;
  transition:transform .4s var(--ease);
}
.mailto:hover{ color:var(--accent); border-color:var(--accent); }
.mailto:hover svg{ transform:translate(4px,-4px); }



/* telegram / whatsapp */
.reach{
  display:flex; flex-wrap:wrap; gap:.6rem;
  margin-bottom:clamp(3rem,7vw,5.5rem);
}
.reach a{
  display:inline-flex; align-items:center; gap:.55rem;
  padding:.7rem 1.1rem;
  border:1px solid var(--rule);
  border-radius:99px;
  transition:border-color .25s var(--ease), background .25s var(--ease);
}
.reach a:hover{ border-color:var(--accent); background:var(--accent-wash); }
.reach svg{
  width:1.05rem; height:1.05rem; flex:none;
  fill:none; stroke:var(--accent); stroke-width:1.5;
  stroke-linecap:round; stroke-linejoin:round;
}
.reach__name{ font-size:var(--t-sm); font-weight:500; }
.reach__val{
  font-family:var(--mono); font-size:.6875rem;
  color:var(--ink-3); white-space:nowrap;
}

.contact-grid{
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--gap);
  padding-top:clamp(1.75rem,3vw,2.5rem);
  border-top:1px solid var(--rule-soft);
}
.contact-block h3{
  font-family:var(--mono); font-weight:400;
  font-size:var(--t-xs); letter-spacing:.09em;
  text-transform:uppercase; color:var(--ink-3);
  margin-bottom:1rem;
}
.links li{ margin-bottom:.5rem; }
.links a{
  font-size:var(--t-sm);
  background-image:linear-gradient(var(--accent),var(--accent));
  background-size:0% 1px; background-repeat:no-repeat;
  background-position:0 100%;
  transition:background-size .35s var(--ease), color .25s var(--ease);
  padding-bottom:2px;
}
.links a:hover{ background-size:100% 1px; color:var(--accent); }
.addr{ font-size:var(--t-sm); color:var(--ink-2); line-height:1.7; }

/* ── LIGHTBOX ────────────────────────────────────────────── */
.lb{
  position:fixed; inset:0; z-index:100;
  display:grid; place-items:start center;
  padding:clamp(.75rem,3vw,2.5rem);
  overflow-y:auto;
  overscroll-behavior:contain;
}
.lb[hidden]{ display:none; }

.lb__backdrop{
  position:fixed; inset:0;
  background:rgba(6,8,14,.72);
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
  animation:lb-fade .3s var(--ease);
}
@keyframes lb-fade{ from{ opacity:0; } }
@keyframes lb-rise{ from{ opacity:0; transform:translateY(14px); } }

.lb__panel{
  position:relative;
  width:min(100%, 66rem);
  background:var(--card);
  border:1px solid var(--rule-soft);
  border-radius:var(--radius);
  padding:clamp(1.25rem,3vw,2.25rem);
  animation:lb-rise .4s var(--ease);
}

.lb__close{
  position:absolute; top:.75rem; right:.75rem; z-index:3;
  display:grid; place-items:center;
  width:2.4rem; height:2.4rem;
  border:1px solid var(--rule);
  border-radius:50%;
  background:color-mix(in srgb, var(--card) 85%, transparent);
  color:var(--ink); cursor:pointer;
  transition:border-color .25s var(--ease), color .25s var(--ease);
}
.lb__close:hover{ border-color:var(--accent); color:var(--accent); }
.lb__close svg{
  width:1.05rem; height:1.05rem; fill:none; stroke:currentColor;
  stroke-width:1.7; stroke-linecap:round;
}

/* stage */
.lb__stage{ position:relative; }
.lb__frame{
  aspect-ratio:16/9;
  background:var(--paper-2);
  border-radius:var(--radius-md);
  overflow:hidden;
}
.lb__frame picture, .lb__frame img{
  width:100%; height:100%; display:block;
  object-fit:contain;
}
.lb__frame img{ animation:lb-fade .35s var(--ease); }

.lb__nav{
  position:absolute; top:50%; z-index:2;
  transform:translateY(-50%);
  display:grid; place-items:center;
  width:2.6rem; height:2.6rem;
  border:0; border-radius:50%;
  background:color-mix(in srgb, var(--card) 88%, transparent);
  box-shadow:0 2px 10px rgba(0,0,0,.18);
  color:var(--ink); cursor:pointer;
  transition:background .25s var(--ease), color .25s var(--ease), opacity .25s var(--ease);
}
.lb__nav--prev{ left:.75rem; }
.lb__nav--next{ right:.75rem; }
.lb__nav:hover{ background:var(--accent-2); color:#fff; }
.lb__nav svg{
  width:1.1rem; height:1.1rem; fill:none; stroke:currentColor;
  stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round;
}

.lb__counter{
  position:absolute; right:.75rem; bottom:.75rem; z-index:2;
  font-family:var(--mono); font-size:.6875rem; letter-spacing:.08em;
  background:color-mix(in srgb, var(--card) 88%, transparent);
  color:var(--ink-2);
  padding:.3rem .6rem; border-radius:99px;
}

.lb__caption{
  margin-top:.85rem;
  font-family:var(--mono); font-size:var(--t-xs);
  letter-spacing:.04em; color:var(--ink-3);
}

/* thumbnails */
.lb__thumbs{
  display:flex; gap:.5rem; flex-wrap:wrap;
  margin-top:1rem;
}
.lb__thumbs button{
  width:5.5rem; aspect-ratio:16/9;
  padding:0; border:1px solid var(--rule-soft); border-radius:var(--radius-sm);
  background:var(--paper-2); overflow:hidden; cursor:pointer;
  opacity:.5;
  transition:opacity .25s var(--ease), border-color .25s var(--ease);
}
.lb__thumbs button img{ width:100%; height:100%; object-fit:cover; }
.lb__thumbs button:hover{ opacity:.85; }
.lb__thumbs button.is-active{ opacity:1; border-color:var(--accent); }

/* info */
.lb__info{
  display:grid;
  grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);
  gap:clamp(1.5rem,4vw,3rem);
  margin-top:clamp(1.5rem,3vw,2.25rem);
  padding-top:clamp(1.25rem,3vw,1.75rem);
  border-top:1px solid var(--rule);
}
.lb__meta{
  font-family:var(--mono); font-size:var(--t-xs);
  letter-spacing:.06em; color:var(--accent);
  margin-bottom:.5rem;
}
.lb__title{
  font-family:var(--serif); font-weight:400;
  font-size:var(--t-xl); line-height:1.16;
  letter-spacing:-.015em;
  margin-bottom:.75rem;
  text-wrap:balance;
}
/* Descriptions are set with textContent, so a paragraph break in the
   dictionary would collapse. pre-line keeps the author's breaks and
   still wraps normally; single-line entries are unaffected.       */
.lb__desc{
  color:var(--ink-2); font-size:var(--t-sm);
  line-height:1.65; max-width:56ch;
  white-space:pre-line;
  text-wrap:pretty;
}
.lb__credits{ display:grid; gap:0; align-content:start; }
.lb__credits > div{
  display:grid; grid-template-columns:auto 1fr; gap:1rem;
  padding:.5rem 0;
  border-bottom:1px solid var(--rule-soft);
}
.lb__credits dt{
  font-family:var(--mono); font-size:.6875rem;
  letter-spacing:.05em; color:var(--ink-3);
  white-space:nowrap;
}
.lb__credits dd{ font-size:var(--t-sm); text-align:right; }
/* a credit can carry a link — the global rule strips underlines, so
   it needs its own affordance to read as one */
.lb__credits dd a{
  color:var(--accent);
  border-bottom:1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  transition:border-color .25s var(--ease);
}
.lb__credits dd a:hover{ border-bottom-color:var(--accent); }

body.lb-open{ overflow:hidden; }

@media (max-width:820px){
  .lb__info{ grid-template-columns:1fr; gap:1.5rem; }
  .lb__credits dd{ text-align:left; }
  .lb__credits > div{ grid-template-columns:1fr; gap:.15rem; }
  .lb__nav{ width:2.2rem; height:2.2rem; }
  .lb__thumbs button{ width:4.25rem; }
}

/* ── CASE STUDY ──────────────────────────────────────────
   Long-form reading. Prose is held to a measure; figures
   break out wider, so the page alternates between a column
   you read and a band you look at.                        */
.case{ margin-top:clamp(1.5rem,3vw,2.5rem); }

/* No rule of its own: section 01 already draws one on its top edge, and
   the two were landing a few rem apart as a pair of parallel lines. The
   padding matches .case__sec's, so the first divider sits in the same
   rhythm as every divider after it.                                  */
.case__head{
  padding-bottom:clamp(1.75rem,3.5vw,2.75rem);
}
.case__eyebrow{
  font-family:var(--mono); font-size:var(--t-xs);
  letter-spacing:.09em; text-transform:uppercase;
  color:var(--accent);
  margin-bottom:.9rem;
}
.case__title{
  font-family:var(--serif); font-weight:400;
  font-size:var(--t-2xl); line-height:1.02;
  letter-spacing:-.025em;
  margin-bottom:.5rem;
}
.case__sub{
  font-family:var(--serif); font-weight:400;
  font-size:var(--t-xl); line-height:1.24;
  letter-spacing:-.015em;
  color:var(--ink-2);
  max-width:30ch;
  text-wrap:balance;
  margin-bottom:clamp(1.5rem,3vw,2.25rem);
}
.case__meta{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(15rem, 1fr));
  gap:1rem clamp(1.5rem,3vw,2.5rem);
}
.case__meta dt{
  font-family:var(--mono); font-size:.6875rem;
  letter-spacing:.07em; text-transform:uppercase;
  color:var(--ink-3);
  margin-bottom:.35rem;
}
.case__meta dd{ font-size:var(--t-sm); line-height:1.5; color:var(--ink-2); }
.case__meta a{ color:var(--accent); }
.case__meta a:hover{ text-decoration:underline; }

/* Two columns: the heading sits in a narrow left rail and stays
   with you while the body scrolls past it. Figures span both, so
   images get full width while prose stays at a readable measure. */
.case__sec{
  display:grid;
  grid-template-columns:minmax(0,13rem) minmax(0,1fr);
  column-gap:clamp(1.5rem,4vw,3.5rem);
  row-gap:clamp(1.25rem,2.5vw,1.75rem);
  padding-block:clamp(1.75rem,3.5vw,2.75rem);
  border-top:1px solid var(--rule-soft);
}
.case__sec--end{ padding-bottom:0; }

/* The heading is stuck inside a wrapper rather than being the sticky
   element itself. A sticky grid item is supposed to be bounded by its
   grid area, but WebKit bounds it by the whole grid — so the rail was
   travelling down the section and passing over the figures. Sticking
   a child of a normal grid item is bounded correctly everywhere.   */
.case__aside{ grid-column:1; }
.case__rail{
  position:sticky;
  top:clamp(1.5rem,4vh,2.5rem);
}
.case__num{
  display:block;
  font-family:var(--mono); font-size:var(--t-xs);
  letter-spacing:.08em; color:var(--accent);
  margin-bottom:.4rem;
}
.case__body{ grid-column:2; }
/* the only figures that break out of the prose column: five shots in a
   row and the handover/release pair both need the extra width. Safe to
   cross the rail's column now that the rail can't travel out of row 1 */
.case__sec > .fig--wide{ grid-column:1 / -1; }



.case__h2{
  font-family:var(--serif); font-weight:400;
  font-size:var(--t-lg); line-height:1.14;
  letter-spacing:-.015em;
  text-wrap:balance;
}
.case__h3{
  font-family:var(--serif); font-weight:400;
  font-size:var(--t-lg); line-height:1.2;
  letter-spacing:-.012em;
  margin-bottom:.7rem;
}
.case__body > .case__h3:not(:first-child){ margin-top:.5rem; }
.case__p{
  max-width:64ch;
  color:var(--ink-2);
  margin-bottom:1em;
  text-wrap:pretty;
}
.case__p:last-child{ margin-bottom:0; }
.case__p strong{ color:var(--ink); font-weight:600; }
/* the sentence a section turns on */
.case__p--lead{
  color:var(--ink);
  padding-left:1rem;
  border-left:2px solid var(--accent);
}

.case__quote{
  font-family:var(--serif);
  font-size:var(--t-lg);
  line-height:1.35;
  letter-spacing:-.012em;
  color:var(--ink);
  max-width:52ch;
  margin:0 0 1.1rem;
  padding-left:1.1rem;
  border-left:2px solid var(--rule);
}

.case__points{ counter-reset:pt; max-width:64ch; }
.case__points li{
  counter-increment:pt;
  position:relative;
  padding-left:2.4rem;
  margin-bottom:1.1rem;
  color:var(--ink-2);
}
.case__points li::before{
  content:counter(pt);
  position:absolute; left:0; top:.05em;
  font-family:var(--mono); font-size:var(--t-xs);
  color:var(--accent);
  border:1px solid var(--rule);
  border-radius:50%;
  width:1.6rem; height:1.6rem;
  display:grid; place-items:center;
}
.case__points strong{ color:var(--ink); display:block; margin-bottom:.15rem; }





/* ── FIGURE SLOTS ────────────────────────────────────────
   Dashed placeholders that describe what belongs in them.
   Figures sit in the prose column; multi-image ones break out.  */
.fig{ margin:0 0 clamp(1.25rem,2.5vw,1.75rem); }
.fig:last-child{ margin-bottom:0; }
/* for figures that set a subject rather than prove a point —
   full width would promise evidence the picture doesn't carry */
/* Single images live inside the prose column rather than spanning the
   section, so every one of them lines up with the paragraph it belongs
   to. Only the multi-image figures — the version strip, the release
   pair, the v1/v2/v3 stack — still break out to the full width.     */
.fig--inline{ margin-block:clamp(1.25rem,2.5vw,1.75rem) 0; }
.fig--inline:last-child{ margin-bottom:0; }
.fig__grid{
  display:grid;
  gap:clamp(.6rem,1.5vw,1rem);
}


.fig__step{
  font-family:var(--mono); font-size:var(--t-xs);
  color:var(--accent);
  border:1px solid var(--rule);
  border-radius:99px;
  padding:.2rem .55rem;
}
/* Stacked comparison. Side-by-side is the obvious layout for a
   before/after, but these crops carry body copy — at a third of
   the column each one renders ~290px wide and the text turns to
   grey mush. Stacking keeps every version at full column width. */
.fig--stack .fig__step-row{ margin-bottom:clamp(1.25rem,2.5vw,1.75rem); }
.fig--stack .fig__step-row:last-of-type{ margin-bottom:0; }
/* the step caption sits under its picture, like every other caption */
.fig__step-cap{
  display:flex; align-items:baseline; gap:.6rem;
  max-width:68ch;
  margin-top:.85rem;
  font-size:var(--t-sm);
  color:var(--ink-2);
  line-height:1.55;
  text-wrap:pretty;
}
.fig__step-cap .fig__step{ flex:none; }

/* Screenshots of a white interface sitting on a white panel read as
   page content rather than as evidence. Every figure image is set on
   a tinted plate and given its own edge and lift, so it's obviously
   a picture of a screen and not the screen you're on.             */
/* Scoped to the figure's own picture and to the rows of a stacked
   comparison — the reels inside .shot and .strip are <picture> too,
   and a plate with padding inside a crawling window would show as a
   grey border sliding under the crop. */
.fig > picture,
.fig__step-row > picture{
  display:block;
  padding:clamp(.9rem,2vw,1.6rem);
  background:var(--paper-2);
  border-radius:var(--radius-md);
}
.fig__img{
  display:block;
  width:100%; height:auto;
  border:1px solid var(--rule);
  border-radius:var(--radius-sm);
  background:#fff;
  box-shadow:0 6px 18px rgba(11,13,21,.10), 0 1px 3px rgba(11,13,21,.06);
}
[data-theme="dark"] .fig__img{ background:var(--card); }

/* ── BLOCK SCHEME ────────────────────────────────────────
   A wireframe silhouette of the page beside the question each
   block answers. Drawn in markup so the labels come from the
   dictionary and the colours from the theme — an exported file
   would need four versions (two languages × two themes).     */
.fig--scheme > .scheme{
  display:block;
  width:100%; height:auto;
  padding:clamp(1rem,2.2vw,1.75rem);
  background:var(--paper-2);
  border-radius:var(--radius-md);
}
.scheme__page rect{ fill:none; stroke:var(--rule); stroke-width:1; }
.scheme__blk{ fill:var(--rule-soft); }
.scheme__blk.is-key{ fill:var(--accent-wash); stroke:var(--accent); stroke-width:1; }
.scheme text{ font-family:var(--sans); fill:var(--ink-2); }
.scheme__n{
  font-family:var(--mono); font-size:12px;
  fill:var(--ink-3); letter-spacing:.06em;
}
.scheme__n.is-key{ fill:var(--accent); }
.scheme__b{ font-size:15px; font-weight:600; fill:var(--ink); }
.scheme__b.is-key{ fill:var(--accent); }
.scheme__q{ font-size:13.5px; fill:var(--ink-3); }
.scheme__mark path{ stroke:var(--accent); stroke-width:1.5; stroke-linecap:round; }
.scheme__key{ font-size:11.5px; fill:var(--ink-3); }
/* the SVG scales down with the column, so past a point the 13.5px
   question text is sub-10px on screen — stack it instead */
@media (max-width:620px){
  .fig--scheme > .scheme{ padding:clamp(.6rem,2vw,1rem); }
}

/* ── SCROLLING SCREENSHOT ────────────────────────────────
   A tall page shot crawling through a short window. The
   travel distance is a percentage of the image's own height,
   which translateY resolves against — so it stays correct at
   every viewport width with no JS and no hard-coded pixels. */
.shot{
  border:1px solid var(--rule-soft);
  border-radius:var(--radius-md);
  overflow:hidden;
  background:var(--paper-2);
  box-shadow:var(--card-shadow);
}
.shot__window{
  position:relative;
  aspect-ratio:16/10;
  overflow:hidden;
}
.shot__reel{
  display:block;
  will-change:transform;
  animation:shot-crawl var(--dur,34s) ease-in-out infinite alternate;
}
.shot__reel img{ width:100%; height:auto; display:block; }
@keyframes shot-crawl{
  from{ transform:translateY(0); }
  to  { transform:translateY(var(--travel,-78%)); }
}
/* let people stop it and look */
.shot:hover .shot__reel,
.shot:focus-within .shot__reel{ animation-play-state:paused; }

/* ── VERSION STRIP ───────────────────────────────────────
   Five full-page shots in a row. At a fifth of the column each
   one is ~150px wide, so reading them is off the table — the
   job is comparing silhouettes. Tall windows show the shape of
   each version, and hovering one crawls that shot alone rather
   than animating five things at once.                        */
.fig--strip .fig__grid{
  grid-template-columns:repeat(5,minmax(0,1fr));
  align-items:start;
  gap:clamp(.5rem,1.2vw,.9rem);
  padding:clamp(.9rem,2vw,1.6rem);
  background:var(--paper-2);
  border-radius:var(--radius-md);
}
.strip__item{
  display:block; width:100%;
  padding:0; border:0; background:none;
  font:inherit; color:inherit; text-align:left;
  cursor:zoom-in;
}
.strip__item:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:4px;
  border-radius:var(--radius-sm);
}
.strip__desc{
  display:block;
  overflow-wrap:anywhere;   /* no single word can widen its column */
  margin-top:.5rem;
  font-size:.6875rem; line-height:1.45;
  color:var(--ink-3);
  text-wrap:pretty;
}
.strip__label{
  display:block;
  margin-bottom:.45rem;
  font-family:var(--mono); font-size:.625rem;
  letter-spacing:.08em; text-transform:uppercase;
  color:var(--ink-3);
  text-align:center;
}
.strip__win{
  display:block;
  position:relative;
  aspect-ratio:3/5;
  overflow:hidden;
  border:1px solid var(--rule);
  border-radius:var(--radius-sm);
  background:#fff;
  box-shadow:0 4px 12px rgba(11,13,21,.10);
}
[data-theme="dark"] .strip__win{ background:var(--card); }
.strip__win img,
.strip__reel{
  display:block;
  animation:shot-crawl var(--dur,26s) ease-in-out infinite alternate;
  animation-play-state:paused;          /* still until asked */
}
.strip__item:hover .strip__reel,
.strip__item:focus-within .strip__reel{ animation-play-state:running; }
@media (prefers-reduced-motion:reduce){
  .strip__reel{ animation:none; }
}
@media (max-width:760px){
  .fig--strip .fig__grid{ grid-template-columns:repeat(3,minmax(0,1fr)); }
}
@media (max-width:480px){
  .fig--strip .fig__grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}

/* ── CLOSING PAIR ────────────────────────────────────────
   Handover beside release. Both windows are 3/4 rather than
   16/10 — at half the column each shot is narrow, and a taller
   window keeps enough of the page in view to compare. Neither
   carries a browser bar, so the two crops start level.      */
.fig--pair .fig__grid{
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(.75rem,1.8vw,1.25rem);
  padding:clamp(.9rem,2vw,1.6rem);
  background:var(--paper-2);
  border-radius:var(--radius-md);
}
.fig--pair .shot__window{ aspect-ratio:3/4; }
.fig--pair .shot{ background:#fff; }
[data-theme="dark"] .fig--pair .shot{ background:var(--card); }
.pair__item{ display:block; }
.pair__label{
  display:block;
  margin-bottom:.5rem;
  font-family:var(--mono); font-size:.625rem;
  letter-spacing:.07em; text-transform:uppercase;
  color:var(--ink-3);
}

/* ── VERSION VIEWER ──────────────────────────────────────
   A full-page shot is ~3,200px tall. Fitting it to the viewport
   would shrink it back to unreadable, so the overlay scrolls the
   image at full width instead, with the controls pinned on top. */
.zoom{
  position:fixed; inset:0; z-index:120;
  background:rgba(11,13,21,.92);
  display:flex; flex-direction:column;
}
.zoom[hidden]{ display:none; }
.zoom__bar{
  flex:none;
  display:flex; align-items:center; gap:.75rem;
  padding:.75rem clamp(.9rem,3vw,1.5rem);
  border-bottom:1px solid rgba(255,255,255,.14);
  background:rgba(11,13,21,.55);
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
}
.zoom__label{
  flex:none;
  font-family:var(--mono); font-size:var(--t-xs);
  letter-spacing:.08em; text-transform:uppercase;
  color:#fff;
}
.zoom__desc{
  flex:1 1 auto; min-width:0;
  font-size:var(--t-sm); line-height:1.4;
  color:rgba(255,255,255,.72);
}
.zoom__btn{
  flex:none;
  width:2.25rem; height:2.25rem;
  display:grid; place-items:center;
  border:1px solid rgba(255,255,255,.22);
  border-radius:50%;
  background:none; color:#fff;
  cursor:pointer;
  transition:background .2s var(--ease), border-color .2s var(--ease);
}
.zoom__btn:hover{ background:rgba(255,255,255,.14); border-color:rgba(255,255,255,.4); }
.zoom__btn:focus-visible{ outline:2px solid #fff; outline-offset:2px; }
.zoom__btn svg{
  width:16px; height:16px;
  fill:none; stroke:currentColor; stroke-width:1.8;
  stroke-linecap:round; stroke-linejoin:round;
}
.zoom__btn--close{ margin-left:.25rem; }
.zoom__scroll{
  flex:1 1 auto;
  overflow-y:auto;
  overscroll-behavior:contain;
  padding:clamp(1rem,3vw,2rem);
  cursor:zoom-out;
}
.zoom__pic{ display:block; }
/* anything that opens full size says so on hover and takes focus */
.is-zoomable{ cursor:zoom-in; }
.is-zoomable:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:3px;
}
.zoom__pic img{
  display:block;
  width:100%; max-width:1100px;
  margin:0 auto;
  height:auto;
  border-radius:var(--radius-sm);
  background:#fff;
  cursor:default;
}
@media (max-width:560px){
  .zoom__desc{ display:none; }
}

@media (prefers-reduced-motion:reduce){
  .shot__reel{ animation:none; }
}

.fig__cap{
  display:flex; align-items:baseline; gap:.6rem;
  margin-top:.85rem;
  font-size:var(--t-sm);
  color:var(--ink-2);
  max-width:68ch;
  line-height:1.55;
  text-wrap:pretty;
}
.fig__cap .fig__step{ flex:none; }

@media (max-width:820px){
  .case__sec{ grid-template-columns:1fr; row-gap:1rem; }
  .case__rail{ position:static; }
  .case__body{ grid-column:1; }
  .case__num{ display:inline; margin-right:.6rem; }
}
@media (max-width:760px){
  .case__p--lead{ font-size:var(--t-base); }
}
@media (max-width:480px){
  /* single column, but the window keeps its 3/4 ratio so the
     --travel values stay exact */
  .fig--pair .fig__grid{ grid-template-columns:1fr; }
}

/* ── 11. FOOTER ──────────────────────────────────────────── */
.site-footer{
  padding-inline:clamp(1.5rem,5vw,5rem);
  padding-block:2rem;
  font-family:var(--mono);
  font-size:var(--t-xs);
  color:var(--ink-3);
  letter-spacing:.03em;
}
.footer-inner{
  display:flex; justify-content:space-between; align-items:center;
  gap:1rem; flex-wrap:wrap;
}
.to-top{ transition:color .25s var(--ease); }
.to-top:hover{ color:var(--accent); }

/* ── 12. RESPONSIVE ──────────────────────────────────────── */
@media (max-width:1000px){
  .pdf{ grid-template-columns:minmax(0,5fr) minmax(0,7fr); }
  .pdf__specs{ grid-template-columns:repeat(2,minmax(0,auto)); gap:1rem 2rem; }
}

@media (max-width:900px){
  .about{ grid-template-columns:1fr; }
  .practice{ grid-template-columns:1fr; row-gap:clamp(1.75rem,4vw,2.5rem); }
  .contact-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); row-gap:2rem; }
  .pdf{ grid-template-columns:1fr; gap:2.5rem; }
  .pdf__preview{ max-width:20rem; }
  .pdf__specs{ grid-template-columns:repeat(4,minmax(0,auto)); }
  .about__aside{ max-width:22rem; }
  .gal-grid{ columns:2; }
}

@media (max-width:820px){
  .hero__foot{ grid-template-columns:1fr; align-items:start; gap:1.75rem; }
  .hero__cta{ justify-self:start; }

  /* Stamp rejoins the flow. Spacing is deliberately tighter above
     than below, so it reads as attached to the headline rather
     than floating loose against the lede beneath it.            */
  /* stamp is back in the flow, so the headline gets its width back */
  .hero__title{ margin-bottom:1.75rem; max-width:none; }

  .stamp{
    position:static;
    display:inline-block;
    margin:0 0 2.5rem;
  }
  .stamp__paper{
    padding:1.15rem 1.3rem 1rem;
    transform:translateX(.3rem) rotate(-5deg) skewY(-.3deg);
  }
  .stamp:hover .stamp__paper,
  .stamp:focus-visible .stamp__paper{
    transform:translateX(.3rem) rotate(0deg) skewY(0deg) scale(.94);
  }
  .stamp:active .stamp__paper{ transform:translateX(.3rem) rotate(0deg) skewY(0deg) scale(.9); }
  .stamp__main{ font-size:1.3rem; }

  .work-grid{ grid-template-columns:1fr; }
  .card--wide{ grid-column:span 1; }
  .card--wide .card__visual{ aspect-ratio:4/3; }
  .card--wide .card__title{ font-size:var(--t-lg); max-width:none; }

  .section-head{ grid-template-columns:auto 1fr; row-gap:.35rem; }

  .reach a{ flex:1 1 100%; }
}

/* ── MOBILE DOCK + STAMP ─────────────────────────────────── */
@media (max-width:640px){
  .dock__burger{
    display:grid; place-items:center;
    width:2.1rem; height:2.1rem;
    flex:none;
    padding:0; border:0; border-radius:99px;
    background:none; color:var(--ink-2);
    cursor:pointer;
    transition:background .25s var(--ease), color .25s var(--ease);
  }
  .dock__burger:hover{ background:var(--rule-soft); color:var(--ink); }
  .dock__burger:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

  .dock__nav{
    position:absolute;
    left:0; right:0; bottom:calc(100% + .55rem);
    flex-direction:column; align-items:stretch; gap:.1rem;
    padding:.4rem;
    border-radius:var(--radius-md);
    background:color-mix(in srgb, var(--card) 92%, transparent);
    border:1px solid var(--rule-soft);
    box-shadow:var(--card-shadow);
    backdrop-filter:blur(18px) saturate(1.5);
    -webkit-backdrop-filter:blur(18px) saturate(1.5);
    opacity:0; visibility:hidden;
    transform:translateY(6px) scale(.98);
    transform-origin:bottom center;
    transition:opacity .25s var(--ease),
               transform .25s var(--ease),
               visibility .25s;
  }
  .dock.is-open .dock__nav{ opacity:1; visibility:visible; transform:none; }
  .dock__link{ text-align:center; padding:.62rem .8rem; }

  /* Stamp back to the top-right corner, sized down to sit beside the
     masthead. The headline clears it with a margin rather than a
     padding, so its lines still run the full measure.            */
  .stamp{
    position:absolute;
    top:0; right:0;
    margin:0;
  }
  .stamp__paper{
    padding:.8rem .9rem .7rem;
    transform:rotate(-5deg) skewY(-.3deg);
  }
  .stamp:hover  .stamp__paper,
  .stamp:focus-visible .stamp__paper{ transform:rotate(0deg) skewY(0deg) scale(.94); }
  .stamp:active .stamp__paper{ transform:rotate(0deg) skewY(0deg) scale(.9); }
  .stamp__main{ font-size:1.05rem; }
  .stamp__eyebrow, .stamp__sub{ font-size:.5625rem; }

  .hero__title{ margin-top:clamp(4.75rem,20vw,7rem); }
}

@media (max-width:480px){
  .facts{ grid-template-columns:1fr; }
  .fact:nth-child(odd){ padding-right:0; }
  .fact:nth-child(even){ padding-left:0; border-left:0; }
  .contact-grid{ grid-template-columns:1fr; }
  .footer-inner{ justify-content:flex-start; }
  .pdf__specs{ grid-template-columns:repeat(2,minmax(0,auto)); }
  .pdf__actions .btn{ flex:1 1 100%; justify-content:center; }
  .lang__btn{ padding:.32rem .45rem; }
  .gal-grid{ columns:1; }
}

@media print{
    body{ background:#fff; color:#000; }
  .reveal{ opacity:1; transform:none; }
}
