/* ============================================================================
   KAMENGRAD — Ivory & Graphite
   ----------------------------------------------------------------------------
   THE SYSTEM, IN THREE DECISIONS (all derived from the client, not from us):

   1. TYPE — one superfamily, three voices.
      Archivo (variable: wght 100–900, wdth 62–125%) carries display AND text.
      Display is set on the WIDTH axis (wdth 116%), not just heavier: wider
      letterforms = more visual mass = the "built, load-bearing" register a
      stone/construction brand wants. Text sits at 100%. IBM Plex Mono is the
      third voice, reserved for technical metadata — dimensions, dates, phases,
      figures — because that is how a building is actually annotated.
      Scale: base 16px, ratio ≈1.26, doubling every third step
      (16 → 20 → 25 → 32 → 40 → 51 → 64 → 80). Tracking and leading are
      optically scaled: tighter as size grows.

   2. SHAPE — a system, not one radius.
      --radius 2px is structural (stone doesn't have rounded corners).
      --chamfer 14px is the signature: a 45° cut on the top-right corner,
      the bevel of a dressed stone. It repeats on media, tiles, plates and the
      primary CTA and appears nowhere else. That's the motif.

   3. STRUCTURE OVER DECORATION — 1px hairlines define every grid; there are no
      cards, no soft shadows, no glass. Bands are ruled, not boxed.

   Tokens in :root are the single source of truth. Dark sections (hero, CTA,
   footer, mid-page band) opt into a scoped dark context via .slab-dark /
   .hero / .page-hero / .cta / .footer, which redefines the SAME token names —
   including the accent — so every component adapts automatically.

   One owned accent: weathered bronze. Every ink/accent pair below clears
   WCAG AA (4.5:1) on every ground it is used on.
   Section-scoped animation CSS lives in /assets/animations/.
   ==========================================================================*/

/* ---------------------------------------------------------------------------
   0. FONTS — self-hosted woff2 (no CDN; CSP font-src is 'self')
   Archivo is one variable file per subset covering the whole weight AND width
   range, which is why the display/text voice split costs no extra request.
   ------------------------------------------------------------------------- */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url("../assets/fonts/archivo-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url("../assets/fonts/archivo-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/plexmono-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/plexmono-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../assets/fonts/plexmono-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../assets/fonts/plexmono-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------------------------------------------------------------------------
   1. TOKENS — light (default)
   ------------------------------------------------------------------------- */
:root {
  /* Ground */
  --bg:            #f6f4ef;   /* warm ivory, page */
  --bg-alt:        #edeae1;   /* alternate band — a tonal shift, not a slab */
  --surface:       #fffdf8;   /* inputs and the few real surfaces left */
  --surface-2:     #f1eee6;

  /* Ink — every value below clears AA on --bg, --bg-alt and --surface */
  --ink:           #23211d;   /* graphite, primary text     14.6:1 on --bg */
  --ink-2:         #57534b;   /* secondary                   7.0:1 */
  --ink-3:         #6e6960;   /* labels, captions, metadata  5.0:1 */

  /* Hairlines — structure is drawn, never shadowed */
  --line:          rgba(35, 33, 29, 0.14);
  --line-2:        rgba(35, 33, 29, 0.26);
  --line-faint:    rgba(35, 33, 29, 0.06);

  /* The one owned accent: weathered bronze.
     5.3:1 as text on ivory · 5.6:1 for --accent-ink on a bronze fill. */
  --accent:        #8d5a24;
  --accent-hover:  #6a4419;   /* hover goes DARKER on light ground */
  --accent-ink:    #fdfbf6;   /* text on a bronze fill */
  --accent-soft:   rgba(141, 90, 36, 0.09);

  /* Accent for type sitting on PHOTOGRAPHY rather than on a flat ground. A flat
     band is a known quantity; a photograph is not, so this step is pale enough
     to clear 4.5:1 at a 0.71 scrim even if the image beneath is pure white
     (--accent itself would need 0.87, i.e. hiding the photo almost entirely).
     It lives in :root, not in the dark context, because a project tile sits in
     a LIGHT section while its label still sits on an image. */
  --accent-media:  #f0cd9e;

  /* Type — one superfamily on two axes, plus a mono for technical metadata */
  --font-display: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body:    "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono:    "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;

  --wd-display: 116%;   /* width axis: display voice */
  --wd-wordmark: 125%;  /* width axis: the wordmark, at the family's extreme */
  --wd-text: 100%;

  /* Scale — base 16, ratio ≈1.26, doubling every third step.
     Desktop maxima: 16 · 20 · 25 · 32 · 40 · 51 · 64 · 80 */
  --fs-label:   0.75rem;      /* 12 — mono, tracked uppercase */
  --fs-meta:    0.8125rem;    /* 13 — mono metadata */
  --fs-eyebrow: 0.75rem;
  --fs-sm:      0.875rem;     /* 14 — UI, buttons, nav */
  --fs-base:    1rem;         /* 16 — body */
  --fs-lg:      1.25rem;      /* 20 — lede, large body */
  --fs-xl:      clamp(1.25rem,   1.10rem + 0.55vw, 1.5625rem);  /* 20 → 25 */
  --fs-h3:      clamp(1.5rem,    1.24rem + 0.90vw, 2rem);       /* 24 → 32 */
  --fs-h2:      clamp(1.875rem,  1.42rem + 1.60vw, 2.5rem);     /* 30 → 40 */
  --fs-h2-lead: clamp(2.125rem,  1.40rem + 2.60vw, 3.1875rem);  /* 34 → 51 */
  --fs-h1:      clamp(2.375rem,  1.50rem + 3.20vw, 4rem);       /* 38 → 64 */
  --fs-display: clamp(2.75rem,   1.30rem + 5.20vw, 5rem);       /* 44 → 80 */

  /* Spacing — 8px base */
  --s-1: 0.5rem;   --s-2: 1rem;    --s-3: 1.5rem;  --s-4: 2rem;
  --s-5: 3rem;     --s-6: 4rem;    --s-7: 6rem;    --s-8: 8rem;

  /* Structure */
  --maxw: 1280px;
  --maxw-narrow: 820px;
  --gutter: clamp(1.25rem, 4vw, 3rem);

  /* Shape system: structural + one signature */
  --radius: 2px;
  --chamfer: 14px;

  /* Motion */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 0.5s;

  --header-h: 76px;

  /* Dark tone constant (for scrims composited over photography) */
  --dark-rgb: 22, 21, 17;

  /* Real SVG tick, masked so it inherits the accent. Not a ✓ text glyph. */
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='square'%3E%3Cpath d='M2 8.5 6 12.5 14 3.5'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------------------
   1b. DARK CONTEXT — redefines the same tokens, including the accent, so a
   button or rule placed in a dark band needs no dark-specific rule of its own.
   ------------------------------------------------------------------------- */
.slab-dark, .hero, .page-hero, .cta, .footer {
  --bg:        #171610;
  --bg-alt:    #1e1c15;
  --surface:   #211f18;
  --surface-2: #272419;

  --ink:       #f4f1ea;   /* 16.1:1 */
  --ink-2:     #b7b0a1;   /*  8.4:1 */
  --ink-3:     #938c7d;   /*  5.4:1 */

  --line:       rgba(244, 241, 234, 0.15);
  --line-2:     rgba(244, 241, 234, 0.26);
  --line-faint: rgba(244, 241, 234, 0.07);

  /* The accent lightens on dark ground and its foreground flips to graphite. */
  --accent:       #c69456;   /* 6.7:1 on the dark ground */
  --accent-hover: #dcae72;
  --accent-ink:   #171610;   /* 6.7:1 on a bronze fill */
  --accent-soft:  rgba(198, 148, 86, 0.12);
  /* --accent-media is deliberately NOT redefined here: it is a media-safe
     value, so it is the same in both contexts (see :root). */

  color: var(--ink);
}
.slab-dark, .footer { background: var(--bg); }

/* ---------------------------------------------------------------------------
   2. RESET
   ------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
/* No `scroll-behavior: smooth`, on purpose. The site has no in-page navigation,
   so the only scroll it ever animated was ARRIVING from another page:
   „Saznajte više” → usluge.html#rekonstrukcije painted the top of the page and
   then glided 5,182px down to the section (1.2s, measured at 1440×900). Without
   it the page opens at the section and that section's own reveal is the
   arrival. The skip link stops gliding too, which it never should have. */
html {
  -webkit-text-size-adjust: 100%;
  /* The page scrollbar in the palette instead of OS grey: a graphite thumb on
     the ivory ground. Width is left alone; only its colour is ours. */
  scrollbar-color: rgba(35, 33, 29, 0.32) var(--bg);
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-stretch: var(--wd-text);
  line-height: 1.65;
  font-weight: 400;
  font-synthesis: none;              /* never fake a weight or a width */
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, picture, svg, video { display: block; max-width: 100%; }
img, video { height: auto; }
input, button, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; padding: 0; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
::selection { background: var(--accent); color: var(--accent-ink); }

/* ---------------------------------------------------------------------------
   3. TYPOGRAPHY
   Display voice = the width axis (wdth 116%) + weight. Leading and tracking
   are optically scaled: as size goes up, both come down.
   ------------------------------------------------------------------------- */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-stretch: var(--wd-display);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.022em;
  text-wrap: balance;
  color: var(--ink);
}
h1 { font-size: var(--fs-h1); line-height: 1.02; letter-spacing: -0.028em; }
/* Optical left alignment for display sizes. Measured on Archivo 700 at 116%:
   capitals carry 0.015–0.085em of side bearing (rounds ≈0.058em, stems 0.085em),
   body text about 1.5px. At 64–80px that pushes the heading's ink 3–5px right of
   the paragraph under it. −0.04em puts them on one edge within a pixel for the
   letters these headings actually start with. */
h1, .display, .section-head--lead h2, .cta h2 { margin-inline-start: -0.04em; }
h2 { font-size: var(--fs-h2); line-height: 1.1; }
h3 { font-size: var(--fs-h3); line-height: 1.16; letter-spacing: -0.015em; font-weight: 600; }
h4 { font-size: var(--fs-xl); line-height: 1.2; letter-spacing: -0.01em; font-weight: 600; }
p  { text-wrap: pretty; }
strong { font-weight: 600; }

.display {
  font-family: var(--font-display);
  font-stretch: var(--wd-display);
  font-size: var(--fs-display);
  line-height: 0.95;
  letter-spacing: -0.035em;
  font-weight: 700;
}

/* The one emphasised word per hero. Colour only — italic on top of colour is
   two emphasis mechanisms doing one job, and grotesk italics are a poor fit
   for a masonry brand. On a hero it steps to the media-safe accent. */
.hl { color: var(--accent); }
.hero .hl, .page-hero .hl { color: var(--accent-media); }

/* Larger type wants a shorter measure, not the same one. */
.lede {
  font-size: var(--fs-lg);
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 46ch;
}
.prose, .measure { max-width: 65ch; }

/* Technical metadata voice — the third register. Anything that would be
   annotated on a drawing (dimensions, dates, phases, figures, labels). */
.mono {
  font-family: var(--font-mono);
  font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}
.num { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
   4. LAYOUT PRIMITIVES
   ------------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: var(--maxw-narrow); }

/* A faint 12-column wallpaper grid was tried here and pulled. The playbook's
   own caution applies: overt registration devices only earn their place when
   they sit on a flawlessly clean grid, and a 12th of the container does not
   land on the 1.15/1.4/auto service rows or the 3-up spec columns — the rules
   cut through content instead of framing it, which reads as affectation.
   The structure is exposed the honest way instead: every band is ruled by the
   hairlines that ARE its grid (see .stats, .spec, .services, .process). */
.section { padding-block: clamp(4.5rem, 9vw, 8.5rem); position: relative; }
.section--tight { padding-block: clamp(3rem, 6vw, 5rem); }
.section--flush { padding-block: 0; }
.section--alt, .slab-light { background-color: var(--bg-alt); }

/* Kicker: mono label + a short bronze rule. The rule is the same hairline
   language as every divider on the site, so it reads as part of the structure. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.eyebrow::before { content: ""; width: 26px; height: 1px; background: var(--accent); flex: none; }
.eyebrow--center { justify-content: center; }

/* Measures are always declared on the element whose own font-size defines the
   `ch` — a 24ch cap on a 16px wrapper would strangle a 51px heading inside it. */
.section-head { display: grid; gap: var(--s-3); margin-bottom: clamp(2.5rem, 5vw, 4rem); }
.section-head h2 { max-width: 26ch; }
.section-head .lede { margin-top: var(--s-1); }
/* Major sections get the larger step in the scale; minor ones stay at h2.
   That difference is what stops every band reading at the same pitch. */
.section-head--lead h2 { font-size: var(--fs-h2-lead); letter-spacing: -0.028em; max-width: 23ch; }
/* Heading left, lede right — breaks the centred-stack rhythm. */
.section-head--split {
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
  column-gap: clamp(2rem, 6vw, 5rem);
  align-items: end;
}
.section-head--split .eyebrow { grid-column: 1; }
.section-head--split h2 { grid-column: 1; }
.section-head--split .lede { grid-column: 2; grid-row: 1 / span 2; align-self: end; margin-top: 0; }

.rule { height: 1px; background: var(--line); border: 0; }

/* The signature: a 45° cut on the top-right corner — the bevel of a dressed
   stone. Applied to media, tiles and plates. Never to a focusable element
   (clip-path would clip its focus ring); the primary button gets the same cut
   drawn as a gradient instead. */
.chamfer {
  clip-path: polygon(
    0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%
  );
}

/* ---------------------------------------------------------------------------
   5. BUTTONS
   Primary carries the chamfer (cut as a gradient so the focus ring survives)
   and no border. Secondary is a hairline frame, square. Two shapes, two jobs.
   ------------------------------------------------------------------------- */
.btn {
  --btn-bg: var(--accent);
  --btn-fg: var(--accent-ink);
  --btn-cut: 12px;
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  padding: 0.95em 1.6em;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--btn-fg);
  background-color: transparent;
  background-image: linear-gradient(
    225deg, transparent var(--btn-cut), var(--btn-bg) var(--btn-cut)
  );
  border: 0;
  border-radius: 0;
  transition: color var(--dur) var(--ease), background-image var(--dur) var(--ease);
}
.btn:hover { --btn-bg: var(--accent-hover); }
.btn .arw { transition: transform var(--dur) var(--ease); }
.btn:hover .arw { transform: translateX(4px); }
.btn[disabled] { opacity: 0.55; cursor: default; }

.btn--ghost {
  --btn-fg: var(--ink);
  background-image: none;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  padding: calc(0.95em - 1px) calc(1.6em - 1px);
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn--ghost:hover { border-color: var(--ink); color: var(--ink); }

/* Text link with a wiping bronze underline */
.link {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-weight: 600;
  font-size: var(--fs-sm);
  letter-spacing: 0.01em;
  color: var(--ink);
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  padding-bottom: 2px;
  transition: background-size var(--dur) var(--ease), color var(--dur) var(--ease);
}
.link:hover { background-size: 100% 1px; color: var(--accent); }
.link .arw { transition: transform var(--dur) var(--ease); }
.link:hover .arw { transform: translateX(4px); }

/* Risk reversal at the point of action. Every primary CTA carries the same three
   facts in the metadata voice, so the button is never a leap. It sits inside the
   actions row (flex-basis 100% puts it on its own line) and shares its reveal. */
.cta-note {
  flex-basis: 100%;
  margin-top: calc(-1 * var(--s-1));
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-wrap: balance;   /* two even lines on a phone, never "1 radni dan" alone */
}
/* Over photography, one step lighter (see the scrim table in CLAUDE.md). */
.hero .cta-note, .cta .cta-note { color: var(--ink-2); }

/* ---------------------------------------------------------------------------
   6. HEADER / NAV
   Transparent over the dark hero → solid ivory with a hairline on scroll.
   Solid, not glass: a 1px rule is cheaper and reads hand-built.
   ------------------------------------------------------------------------- */
.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  transition: transform 0.4s var(--ease), background-color 0.4s var(--ease), border-color 0.4s var(--ease);
  border-bottom: 1px solid transparent;
}
.header.is-solid {
  background-color: var(--bg);
  border-bottom-color: rgba(35, 33, 29, 0.14);
}
.header.is-hidden { transform: translateY(-100%); }

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.nav__menu { display: flex; align-items: center; gap: clamp(1rem, 2.4vw, 2.4rem); }

/* Header text is light while transparent over the dark hero. */
.header .logo__word { color: #f4f1ea; transition: color 0.4s var(--ease); }
.nav__link {
  position: relative;
  font-size: var(--fs-sm);
  font-weight: 500;
  color: rgba(244, 241, 234, 0.82);
  padding-block: 0.4rem;
  transition: color 0.3s var(--ease);
}
.nav__link:hover, .nav__link[aria-current="page"] { color: #fff; }
.nav__link[aria-current="page"]::after {
  content: ""; position: absolute; left: 0; bottom: -2px;
  width: 100%; height: 1px; background: currentColor;
}
/* Solid state: graphite on ivory */
.header.is-solid .logo__word { color: var(--ink); }
.header.is-solid .nav__link { color: var(--ink-2); }
.header.is-solid .nav__link:hover,
.header.is-solid .nav__link[aria-current="page"] { color: var(--ink); }
.header.is-solid .nav__link[aria-current="page"]::after { background: var(--accent); }

.nav__right { display: flex; align-items: center; gap: var(--s-3); }
.nav__cta { display: inline-flex; }

/* Burger */
.burger { display: none; width: 44px; height: 44px; position: relative; z-index: 120; }
.burger span {
  position: absolute; left: 10px; right: 10px; height: 1.5px;
  background: #f4f1ea;
  transition: transform 0.4s var(--ease), opacity 0.3s var(--ease), top 0.3s var(--ease), background 0.4s var(--ease);
}
.header.is-solid .burger span { background: var(--ink); }
.burger span:nth-child(1) { top: 17px; }
.burger span:nth-child(2) { top: 22px; }
.burger span:nth-child(3) { top: 27px; }
.burger.is-open span { background: var(--ink); }
.burger.is-open span:nth-child(1) { top: 22px; transform: rotate(45deg); }
.burger.is-open span:nth-child(2) { opacity: 0; }
.burger.is-open span:nth-child(3) { top: 22px; transform: rotate(-45deg); }

/* Mobile overlay menu (ivory) */
/* Centred by `margin: auto` on the list, NOT by `justify-content: center`.
   Both look identical when the panel fits, but centred flex overflow spills
   equally out of both ends and the top is then unreachable by scrolling. Auto
   margins collapse to 0 when there is no spare room, so `overflow-y: auto`
   actually works. This is a real case: at 768x400 (landscape phone, still under
   the 768 breakpoint) the content is 448px tall and the last link was cut off. */
.mobile-menu {
  position: fixed; inset: 0; z-index: 110;
  background: var(--bg);
  display: flex; flex-direction: column;
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--gutter);
  transform: translateY(-100%); visibility: hidden;
  transition: transform 0.6s var(--ease), visibility 0.6s;
}
.mobile-menu.is-open { transform: translateY(0); visibility: visible; }
/* List and CTA are centred as ONE group: the auto margins sit outside the pair
   (top of the list, bottom of the CTA), and still collapse to 0 on overflow. */
.mobile-menu__list { display: grid; margin-top: auto; }
.mobile-menu__link {
  font-family: var(--font-display);
  font-stretch: var(--wd-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 8vw, 2.75rem);
  letter-spacing: -0.025em;
  line-height: 1.25;
  color: var(--ink);
  display: block;
  padding-block: 0.55rem;
  border-bottom: 1px solid var(--line);
}
.mobile-menu__link[aria-current="page"] { color: var(--accent); }
/* The header CTA disappears under 768px with the rest of the nav, which left a
   phone with no way to the one action the site exists for. It lives here now. */
.mobile-menu__cta { align-self: flex-start; margin-top: var(--s-4); margin-bottom: auto; }
.mobile-menu__foot {
  margin-top: var(--s-5);
  display: grid; gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: var(--fs-meta);
  color: var(--ink-2);
}

/* ---------------------------------------------------------------------------
   7. LOGO / WORDMARK
   The wordmark is Archivo at the family's widest (wdth 125%) rather than a
   bold face tracked out — the width axis gives it mass without the airiness
   heavy letterspacing introduces.
   ------------------------------------------------------------------------- */
.logo { display: inline-flex; align-items: center; gap: 0.6rem; }
.logo__mark { width: 28px; height: 28px; flex: none; }
.logo__word {
  font-family: var(--font-display);
  font-stretch: var(--wd-wordmark);
  font-size: 1.0625rem;
  line-height: 1;
}
.logo__word b { font-weight: 700; letter-spacing: 0.05em; }
.footer .logo__word, .slab-dark .logo__word { color: var(--ink); }

/* ---------------------------------------------------------------------------
   8. HERO — the one dramatic top. Full-bleed photography, no chamfer
   (a bevel only reads on a bounded plane).
   ------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  isolation: isolate;
  background-color: var(--bg);
}
.hero__media { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
.hero__media img, .hero__media video { width: 100%; height: 100%; object-fit: cover; }
/* The scrim carries the AA contrast for every element in the content block,
   over the brightest photograph anyone might drop in. Two things make it work:

   1. It is anchored to the BOTTOM, because the content is bottom-aligned.
   2. Every stop is `max(<rem>, <%>)`, which is the part that took two goes to
      get right. Percentages alone drift off the text as the viewport height
      changes (that is how the eyebrow originally ended up on a bright patch).
      But rem alone fails the other way: on a 320px-wide screen the headline and
      lede wrap to far more lines, the block grows to roughly 70rem, the hero
      grows past 100svh with it, and a fixed 47rem floor no longer reaches the
      top of the block — measured at 3.2:1 there. Taking the max of the two
      means the rem value governs the short desktop hero (keeping the top of the
      photograph open) and the percentage governs the tall mobile one.
   3. The floor across the text block is 0.74, above the measured minimum for
      --ink (0.62) and --accent-media (0.71) over a pure-white photo. */
.hero__scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(to top,
      var(--bg) 0,
      rgba(22,21,17,0.94) 2.5rem,
      rgba(22,21,17,0.88) max(20rem, 30%),
      rgba(22,21,17,0.78) max(38rem, 62%),
      rgba(22,21,17,0.74) max(47rem, 78%),
      rgba(22,21,17,0.30) max(58rem, 90%),
      transparent max(68rem, 100%)),
    /* top vignette: the header is transparent over this */
    linear-gradient(to bottom, rgba(22,21,17,0.60) 0, rgba(22,21,17,0.12) 20%, transparent 38%),
    radial-gradient(110% 70% at 12% 100%, rgba(22,21,17,0.34), transparent 62%);
}
.hero__inner { width: 100%; padding-block: clamp(6rem, 12vh, 9rem); }
/* Full --ink, not the usual label grey: this one sits over photography, where
   --ink-3 would need a 0.93 scrim and --ink-2 a 0.80 one. */
.hero__eyebrow { margin-bottom: var(--s-3); color: var(--ink); }
.hero__eyebrow::before { background: var(--accent-media); }
.hero__title { max-width: 17ch; margin-bottom: var(--s-4); }
.hero__sub {
  max-width: 44ch; color: var(--ink);
  font-size: var(--fs-lg); line-height: 1.55; margin-bottom: var(--s-4);
}
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }

/* Hero metadata reads as drawing annotation: mono keys, tabular figures,
   hairline-separated. */
.hero__meta {
  border-top: 1px solid var(--line);
  margin-top: clamp(2.5rem, 6vh, 4.5rem);
  padding-top: var(--s-3);
  display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-6);
}
.hero__meta-item { display: grid; gap: 0.35rem; }
.hero__meta-item .n {
  font-family: var(--font-display);
  font-stretch: var(--wd-display);
  font-weight: 700;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  color: var(--ink); line-height: 1; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
/* --ink-2, not --ink-3: this row sits over photography. It is the lowest block
   on the page, where the scrim is ≥0.88, so 0.80 is comfortably satisfied. */
.hero__meta-item .l {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-2);
}
/* Pause control for the hero clip. Built by assets/animations/hero-video.js and
   only when the clip is actually running, so it is never dead UI. It joins the
   annotation row rather than floating over the photograph: same mono voice, same
   hairline, pushed to the far end of the same rule. --ink-2 for the same reason
   the labels beside it are --ink-2 — this is the lowest band of the hero, where
   the scrim is at least 0.88. */
.hero__toggle {
  margin-inline-start: auto; align-self: center;
  min-height: 44px; padding: 0 var(--s-2);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: transparent; color: var(--ink-2); cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.hero__toggle:hover { color: var(--ink); border-color: var(--line-2); }

/* ---------------------------------------------------------------------------
   9. FIGURES BAND (formerly "stats")
   Ruled, not boxed: hairlines only, open at the outer edges. Values are real
   text in the HTML — nothing here depends on JS to become legible.
   ------------------------------------------------------------------------- */
.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  /* Rules run full-bleed; the figures still land on the container's edge. */
  padding-inline: max(var(--gutter), calc(50% - var(--maxw) / 2 + var(--gutter)));
}
.stat {
  padding: clamp(1.75rem, 3.5vw, 2.75rem) clamp(1.25rem, 2.5vw, 2rem);
  display: grid; gap: 0.6rem; align-content: start;
  border-right: 1px solid var(--line);
}
.stat:first-child { padding-left: 0; }
.stat:last-child { padding-right: 0; border-right: 0; }
.stat__n {
  font-family: var(--font-display);
  font-stretch: var(--wd-display);
  font-weight: 700;
  font-size: clamp(2.125rem, 4.2vw, 3.1875rem);
  line-height: 1; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -0.03em;
}
.stat__n .u { color: var(--accent); }
.stat__l {
  font-family: var(--font-mono);
  font-size: var(--fs-meta);
  line-height: 1.45; color: var(--ink-3);
}
/* Same band, framed — used where it already sits inside a container.
   Chamfered, because here it IS a bounded plane. */
.stats--framed {
  border: 1px solid var(--line);
  padding-inline: 0;
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
.stats--framed .stat:first-child { padding-left: clamp(1.25rem, 2.5vw, 2rem); }
.stats--framed .stat:last-child { padding-right: clamp(1.25rem, 2.5vw, 2rem); }

/* ---------------------------------------------------------------------------
   10. SPLIT EDITORIAL
   ------------------------------------------------------------------------- */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 6vw, 6rem); align-items: center; }
.split--text-first { grid-template-columns: 1.05fr 0.95fr; }
.split__media { position: relative; }
/* The chamfer lives on a frame, not on the <img>, so the stamped plate can hang
   outside the frame without being clipped by it. (--ar was for the 45° image
   unveil, removed 2026-10-03; it is harmless and left in place.) */
.split__frame {
  --ar: 1.25;
  overflow: hidden;
  background-color: var(--surface-2);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
.split__frame img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
/* A 16:9 source in a 4:5 frame: keep the hands and the drawing, not the lamp. */
.split__frame--drawing img { object-position: 38% 50%; }
.split__body > * + * { margin-top: var(--s-3); }
.split__body p { color: var(--ink-2); max-width: 58ch; }
/* End of each service: the proof (a finished project of this kind) and the ask.
   Visitors now land straight in a section from „Saznajte više", so each one has
   to be able to close on its own. */
.split__links {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-4);
  padding-top: var(--s-3); border-top: 1px solid var(--line);
}
.split__body > .split__links { margin-top: var(--s-4); }
.link__k {
  font-family: var(--font-mono); font-size: var(--fs-label); font-weight: 400;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
}

/* A stamped plate, not a floating card: bronze fill, chamfer, no shadow. */
.media-badge {
  position: absolute; left: calc(-1 * var(--s-3)); bottom: var(--s-4);
  background: var(--accent); color: var(--accent-ink);
  padding: var(--s-3) var(--s-4);
  max-width: 230px;
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
.media-badge .n {
  font-family: var(--font-display); font-stretch: var(--wd-display);
  font-weight: 700; font-size: 2rem; line-height: 1; letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.media-badge .l {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.08em; line-height: 1.4; margin-top: 0.5rem;
}

/* ---------------------------------------------------------------------------
   11. SERVICES — editorial index
   Two columns, hairline-ruled. The decorative 01–06 indices are gone: these
   services are not an ordered sequence, so numbering them was ornament.
   ------------------------------------------------------------------------- */
.services { border-top: 1px solid var(--line); }
.service {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.4fr) auto;
  gap: clamp(1.25rem, 3vw, 2.75rem);
  /* First baselines align across the row — the title and its description read
     as one line of the index, not as two stacked blocks. */
  align-items: baseline;
  padding-block: clamp(1.75rem, 3.5vw, 2.75rem);
  border-bottom: 1px solid var(--line);
  transition: background-color 0.4s var(--ease);
}
.service:hover { background-color: var(--accent-soft); }
.service__title { font-size: var(--fs-h3); max-width: 20ch; transition: transform 0.4s var(--ease); }
.service:hover .service__title { transform: translateX(6px); }
.service__desc { color: var(--ink-2); max-width: 52ch; }
.service__link { align-self: center; white-space: nowrap; }
/* The whole row is the link. The hover already promised it (tint + sliding
   title) while only the small text was clickable, 106×25px on a phone. A
   stretched ::after on the one real link keeps a single tab stop and a single
   accessible name per row; the focus ring moves to the row it now represents. */
.service { position: relative; }
.service__link::after { content: ""; position: absolute; inset: 0; }
.service:hover .service__link { background-size: 100% 1px; color: var(--accent); }
.service:hover .service__link .arw { transform: translateX(4px); }
@supports selector(:has(*)) {
  .service:has(.service__link:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
  .service__link:focus-visible { outline-color: transparent; }
}

/* ---------------------------------------------------------------------------
   12. SPEC GRID — replaces the icon-card grid and the pricing cards.
   A ruled grid, open at the outer edges: no boxes, no shadows, no abstract
   icons. The hairlines expose the column structure instead of hiding it.
   ------------------------------------------------------------------------- */
.spec {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--line);
}
.spec--2 { grid-template-columns: repeat(2, 1fr); }
.spec__item {
  padding: clamp(1.5rem, 3vw, 2.25rem) clamp(1.25rem, 2.5vw, 2rem) clamp(1.75rem, 3.5vw, 2.5rem) 0;
  border-bottom: 1px solid var(--line);
  border-right: 1px solid var(--line);
  display: grid; gap: 0.7rem; align-content: start;
}
.spec__item:nth-child(3n) { border-right: 0; }
.spec--2 .spec__item:nth-child(3n) { border-right: 1px solid var(--line); }
.spec--2 .spec__item:nth-child(2n) { border-right: 0; }
.spec__item > * { padding-left: clamp(1.25rem, 2.5vw, 2rem); }
.spec__item:nth-child(3n + 1) > * { padding-left: 0; }
.spec--2 .spec__item:nth-child(3n + 1) > * { padding-left: clamp(1.25rem, 2.5vw, 2rem); }
.spec--2 .spec__item:nth-child(2n + 1) > * { padding-left: 0; }
.spec__k {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent);
}
.spec__title { font-size: var(--fs-xl); }
.spec__item p { color: var(--ink-2); font-size: var(--fs-sm); line-height: 1.6; max-width: 40ch; }

/* ---------------------------------------------------------------------------
   12b. FAQ — the questions people ask before they call
   Native <details>: no script, keyboard and screen-reader behaviour for free.
   Same ruled-list language as the services index. The marker is a mono "+"
   that turns into "×"; the answer is plain prose at a reading measure.
   ------------------------------------------------------------------------- */
.faq { border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__q {
  list-style: none; cursor: pointer;
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s-3); align-items: baseline;
  padding-block: clamp(1.25rem, 2.5vw, 1.75rem);
  font-family: var(--font-display); font-stretch: var(--wd-display);
  font-weight: 600; font-size: var(--fs-xl); line-height: 1.25; letter-spacing: -0.01em;
  transition: color 0.3s var(--ease);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::after {
  content: "+";
  font-family: var(--font-mono); font-weight: 400; font-size: var(--fs-lg);
  color: var(--accent); line-height: 1;
  transition: transform 0.3s var(--ease);
}
.faq__q:hover { color: var(--accent); }
.faq__item[open] > .faq__q::after { transform: rotate(45deg); }
.faq__a { padding-bottom: clamp(1.25rem, 2.5vw, 1.75rem); color: var(--ink-2); max-width: 68ch; }
.faq__a p + p { margin-top: var(--s-2); }
.faq__a strong { color: var(--ink); }

/* ---------------------------------------------------------------------------
   13. SCHEDULE (formerly the "FAZA 01–04" process cards)
   A construction programme, not four numbered boxes: real time anchors set in
   mono, ticks on a rule. The order genuinely matters here, which is the only
   reason a sequence is shown at all.
   The rule used to fill on scroll (process.js). Since 2026-10-03 the build film
   below (§13b) is the section's motion, and two scroll-driven fills one screen
   apart said the same thing twice. The programme is now simply drawn.
   ------------------------------------------------------------------------- */
.process { position: relative; }
.process__track { display: grid; grid-template-columns: repeat(4, 1fr); position: relative; }
.process__line { position: absolute; top: 0; left: 0; right: 0; height: 1px; background: var(--line-2); }
.step {
  position: relative;
  display: grid; gap: 0.65rem; align-content: start;
  padding: var(--s-4) clamp(1.25rem, 2.5vw, 2rem) 0 0;
  border-right: 1px solid var(--line);
}
.step:last-child { border-right: 0; }
.step > * { padding-left: clamp(1.25rem, 2.5vw, 2rem); }
.step:first-child > * { padding-left: 0; }
/* A station mark astride the programme rule at each phase boundary. 2px wide
   so it reads against the 1px column divider it sits on. */
.step::before {
  content: ""; position: absolute; top: -7px; left: 0;
  width: 2px; height: 15px; background: var(--accent);
}
.step__n {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.step__title { font-size: var(--fs-xl); }
.step p { color: var(--ink-2); font-size: var(--fs-sm); line-height: 1.6; }
/* The ask, placed at the most reassuring moment on the page: right after "four
   phases, no surprises", where the reader knows exactly what the first step
   costs them (nothing). */
.process__cta {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-3) var(--s-5);
}
.process__cta > p { font-size: var(--fs-lg); line-height: 1.5; max-width: 38ch; }
.process__cta-act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); }
.process__cta-act .cta-note { flex-basis: 100%; }

/* ---------------------------------------------------------------------------
   13b. BUILD FILM — the visitor builds the house by scrolling
   A drone film pinned in the viewport while its run scrolls past; the frame is
   set by the scroll (assets/animations/build.js), and the five stages beside it
   light up in step. The stage list keeps the programme's language: hairline
   rows, a station mark per row, a bronze rule that fills.
   Static by default (finished-house still + full list). The pinned layout is
   gated on html.js AND motion being welcome, in CSS, so the page has its final
   height from the first paint; build.js adds .build--still to opt out
   (Save-Data, or reduced motion switched on mid-visit).
   No text ever sits on the film, so it needs no scrim.
   ------------------------------------------------------------------------- */
.build { margin-top: clamp(3.5rem, 7vw, 6rem); }
.build__head {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s-3) var(--s-6); align-items: end;
  padding-bottom: var(--s-4); border-bottom: 1px solid var(--line);
}
.build__head .step__n { display: block; margin-bottom: 0.65rem; }
.build__title { max-width: 18ch; }
.build__lede { color: var(--ink-2); max-width: 46ch; }

.build__stage {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(15rem, 19rem);
  gap: clamp(1.5rem, 3vw, 3rem); align-items: center;
  margin-top: var(--s-5);
}
.build__film {
  position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  background-color: var(--surface-2);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
.build__film img,
.build__film video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The video lies over its own matching poster and appears only once a real
   frame is painted. A colour change, not a movement: no reduced-motion case. */
.build__film video { opacity: 0; transition: opacity 0.4s var(--ease); }
.build__film video.is-ready { opacity: 1; }

.build__phases { list-style: none; padding: 0; position: relative; border-left: 1px solid var(--line-2); }
.build__phases li {
  position: relative; display: grid; gap: 0.2rem;
  padding: 0.85rem 0 0.85rem clamp(1rem, 2vw, 1.5rem);
  border-top: 1px solid var(--line);
}
.build__phases li:first-child { border-top: 0; padding-top: 0.25rem; }
.build__phases li:last-child { padding-bottom: 0.25rem; }
/* Station mark astride the rule, as on the programme above. */
.build__phases li::before {
  content: ""; position: absolute; left: -1px; top: 0.85rem; bottom: 0.85rem;
  width: 2px; background: var(--accent);
}
.build__phases li:first-child::before { top: 0.25rem; }
.build__phases li:last-child::before { bottom: 0.25rem; }
.build__k { font-size: var(--fs-lg); font-weight: 600; line-height: 1.3; color: var(--ink); }
.build__d { font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-2); }

@media (prefers-reduced-motion: no-preference) {
  /* The run is the scroll distance the film takes: one stage per ~38svh. */
  .js .build:not(.build--still) .build__run { height: calc(100svh + 190svh); }
  .js .build:not(.build--still) .build__stage {
    position: sticky; top: 0; height: 100svh; margin-top: 0;
    padding-block: calc(var(--header-h) + var(--s-2)) var(--s-3);
    align-content: center;
  }
  /* Never taller than the screen leaves room for: the width follows the height. */
  .js .build:not(.build--still) .build__film {
    width: min(100%, calc((100svh - var(--header-h) - 4rem) * 16 / 9));
  }
  /* The rule fills with the scroll; the marks and type wait for their stage.
     Inactive rows step down to --ink-3, which still clears AA (5.4:1). */
  .js .build:not(.build--still) .build__phases::after {
    content: ""; position: absolute; left: -1px; top: 0; width: 1px; height: 100%;
    background: var(--accent); transform: scaleY(var(--p, 0)); transform-origin: top;
  }
  .js .build:not(.build--still) .build__phases li::before { background: var(--line-2); transition: background-color 0.4s var(--ease); }
  .js .build:not(.build--still) .build__k,
  .js .build:not(.build--still) .build__d { color: var(--ink-3); transition: color 0.4s var(--ease); }
  .js .build:not(.build--still) .build__phases li.is-active::before { background: var(--accent); }
  .js .build:not(.build--still) .build__phases li.is-active .build__k { color: var(--ink); }
  .js .build:not(.build--still) .build__phases li.is-active .build__d { color: var(--ink-2); }
}

/* ---------------------------------------------------------------------------
   13c. 3D MODEL (usluge) — maketa / crtež / osnova
   A studio stage: the ivory model lit on graphite, in a chamfered frame. The
   HUD speaks in the mono voice (it is technical metadata), the controls under
   the stage reuse the filter's language: mono labels, a bronze underline.
   Everything the visitor needs is outside the canvas too: the poster for no
   WebGL, the spec list for everyone.
   ------------------------------------------------------------------------- */
.model3d__viewer { margin-top: var(--s-2); }
.model3d__stage {
  position: relative; overflow: hidden;
  aspect-ratio: 16 / 9; max-height: 82svh; width: 100%;
  background: radial-gradient(ellipse 70% 60% at 50% 42%, #2c2920 0%, #1d1b15 55%, #141310 100%);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
  user-select: none; -webkit-user-select: none;
}
.model3d__poster,
.model3d__canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.model3d__poster { object-fit: cover; transition: opacity 0.6s var(--ease); }
.model3d__canvas { opacity: 0; transition: opacity 0.6s var(--ease); cursor: grab; outline: none; }
.model3d__canvas:active { cursor: grabbing; }
.model3d__stage.is-ready .model3d__canvas { opacity: 1; }
.model3d__stage.is-ready .model3d__poster { opacity: 0; }
.model3d__viewer.is-plan .model3d__canvas { cursor: default; }

/* Labels projected from the model: dimensions in bronze, rooms as plates. */
.model3d__labels { position: absolute; inset: 0; pointer-events: none; }
.model3d__label {
  position: absolute; left: 0; top: 0; white-space: nowrap;
  font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 0.06em;
  color: var(--accent); transition: opacity 0.3s var(--ease);
}
.model3d__label[hidden] { display: none; }
.model3d__label--dim { padding: 0.1rem 0.35rem; background: rgba(20, 19, 16, 0.86); }
.model3d__label--lvl { color: var(--ink-2); }
.model3d__label--room {
  display: grid; justify-items: center; gap: 0.1rem;
  padding: 0.3rem 0.55rem; background: rgba(20, 19, 16, 0.82);
  border: 1px solid var(--line); color: var(--ink-2);
}
.model3d__label--room b {
  font-family: var(--font-body); font-stretch: var(--wd-text); font-weight: 600;
  font-size: var(--fs-sm); letter-spacing: 0; color: var(--ink);
}

/* HUD: corner metadata, zoom, hint, compass. */
.model3d__hud { position: absolute; z-index: 2; display: flex; }
.model3d__hud--tl {
  top: clamp(0.9rem, 2vw, 1.5rem); left: clamp(0.9rem, 2vw, 1.5rem);
  flex-direction: column; gap: 0.2rem;
  font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-2); pointer-events: none;
}
.model3d__hud-k { color: var(--ink); }
.model3d__hud--tr { top: clamp(0.9rem, 2vw, 1.5rem); right: calc(clamp(0.9rem, 2vw, 1.5rem) + var(--chamfer)); gap: 0.4rem; }
.model3d__icon {
  width: 44px; height: 44px; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 1.15rem; line-height: 1; color: var(--ink);
  background: rgba(20, 19, 16, 0.72); border: 1px solid var(--line-2); border-radius: var(--radius);
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.model3d__icon:hover { border-color: var(--accent); color: var(--accent); }
.model3d__hint {
  position: absolute; z-index: 2; left: 50%; bottom: clamp(0.9rem, 2vw, 1.5rem);
  transform: translateX(-50%); white-space: nowrap;
  padding: 0.4rem 0.8rem; background: rgba(20, 19, 16, 0.72); border: 1px solid var(--line);
  font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-2); pointer-events: none;
  transition: opacity 0.5s var(--ease);
}
.model3d__hint:empty,
.model3d__viewer.is-touched .model3d__hint,
.model3d__viewer.is-plan .model3d__hint { opacity: 0; }
.model3d__compass {
  position: absolute; z-index: 2; right: clamp(0.9rem, 2vw, 1.5rem); bottom: clamp(0.9rem, 2vw, 1.5rem);
  width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--line-2);
  display: grid; place-items: center; pointer-events: none;
}
.model3d__needle {
  position: absolute; inset: 6px; transform: rotate(var(--az, 0rad));
  background: linear-gradient(var(--accent), var(--accent)) center 0 / 2px 50% no-repeat;
}
.model3d__n { font-family: var(--font-mono); font-size: 0.625rem; color: var(--ink-2); }
/* No canvas, no controls that would do nothing. */
.model3d__viewer:not(.is-enhanced) .model3d__hud--tr,
.model3d__viewer:not(.is-enhanced) .model3d__compass,
.model3d__viewer:not(.is-enhanced) .model3d__bar { display: none; }

/* Controls under the stage. */
.model3d__bar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-2) var(--s-5);
  border-bottom: 1px solid var(--line);
}
.model3d__modes { display: flex; }
.model3d__mode {
  position: relative; min-height: 48px;
  padding: 0.7rem clamp(0.9rem, 2vw, 1.4rem);
  font-family: var(--font-mono); font-size: var(--fs-meta);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3);
  transition: color 0.3s var(--ease);
}
.model3d__mode:first-child { padding-left: 0; }
.model3d__mode::after {
  content: ""; position: absolute; left: clamp(0.9rem, 2vw, 1.4rem); right: clamp(0.9rem, 2vw, 1.4rem);
  bottom: -1px; height: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: left; transition: transform 0.35s var(--ease);
}
.model3d__mode:first-child::after { left: 0; }
.model3d__mode:hover { color: var(--ink); }
.model3d__mode[aria-pressed="true"] { color: var(--ink); font-weight: 600; }
.model3d__mode[aria-pressed="true"]::after { transform: scaleX(1); }

.model3d__phase {
  display: grid; grid-template-columns: auto minmax(8rem, 15rem) 11rem;
  align-items: center; gap: var(--s-2); padding-block: 0.6rem;
  transition: opacity 0.3s var(--ease);
}
.model3d__viewer.is-plan .model3d__phase { opacity: 0.45; }
.model3d__phase-k {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-3);
}
.model3d__phase-v { font-weight: 600; color: var(--ink); }
/* The range: a hairline programme with a bronze station for a thumb. */
.model3d__range {
  -webkit-appearance: none; appearance: none; width: 100%; height: 44px;
  background: linear-gradient(var(--line-2), var(--line-2)) center / 100% 1px no-repeat;
  cursor: pointer;
}
.model3d__range::-webkit-slider-thumb {
  -webkit-appearance: none; width: 14px; height: 22px; border-radius: var(--radius);
  background: var(--accent); border: 0;
}
.model3d__range::-moz-range-thumb { width: 14px; height: 22px; border-radius: var(--radius); background: var(--accent); border: 0; }
.model3d__range:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Spec: the figures band language, a ruled row. */
.model3d__spec {
  display: grid; grid-template-columns: repeat(5, 1fr);
  margin-top: clamp(2rem, 4vw, 3rem);
}
.model3d__spec > div { padding: 0 clamp(1rem, 2vw, 1.5rem); border-left: 1px solid var(--line); }
.model3d__spec > div:first-child { padding-left: 0; border-left: 0; }
.model3d__spec dt {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-3);
}
.model3d__spec dd { margin-top: 0.35rem; font-size: var(--fs-lg); font-weight: 600; line-height: 1.3; }

@media (max-width: 1024px) {
  .model3d__spec { grid-template-columns: repeat(3, 1fr); row-gap: var(--s-4); }
  .model3d__spec > div:nth-child(4) { padding-left: 0; border-left: 0; }
}
@media (max-width: 768px) {
  /* A phone gets a near-square stage: 16:9 at 390px is a letterbox. */
  .model3d__stage { aspect-ratio: 4 / 5; max-height: 72svh; }
  .model3d__bar { flex-direction: column; align-items: stretch; gap: 0; border-bottom: 0; }
  .model3d__modes { border-bottom: 1px solid var(--line); }
  .model3d__mode { flex: 1; padding-inline: 0.5rem; text-align: center; }
  .model3d__mode:first-child { padding-left: 0.5rem; }
  .model3d__mode::after, .model3d__mode:first-child::after { left: 0; right: 0; }
  .model3d__phase { grid-template-columns: auto 1fr; padding-top: var(--s-2); }
  .model3d__phase-v { grid-column: 1 / -1; grid-row: 1; }
  .model3d__spec { grid-template-columns: repeat(2, 1fr); }
  .model3d__spec > div:nth-child(4) { padding-left: clamp(1rem, 2vw, 1.5rem); border-left: 1px solid var(--line); }
  .model3d__spec > div:nth-child(odd) { padding-left: 0; border-left: 0; }
  .model3d__hud--tl span:last-child { display: none; }
  /* Hint left, compass right: side by side, never on top of each other. */
  .model3d__hint { left: clamp(0.9rem, 2vw, 1.5rem); transform: none; }
}
@media (max-width: 560px) {
  /* The zoom buttons need the top edge; the project name is in the section head. */
  .model3d__hud--tl { display: none; }
  .model3d__label--room { padding: 0.15rem 0.35rem; font-size: 0.625rem; letter-spacing: 0.02em; }
  .model3d__label--room b { font-size: 0.6875rem; }
  .model3d__label--dim { font-size: 0.625rem; }
}

/* ---------------------------------------------------------------------------
   14. PROJECTS
   Asymmetric by design: a lead tile that is genuinely bigger, then supporting
   tiles. A grid of identical thumbnails states that nothing is the best work.
   The scrim is opaque enough at the base that the bronze category label clears
   AA over any photograph, light or dark.
   ------------------------------------------------------------------------- */
.projects-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(1rem, 2.5vw, 2rem); }
.project { position: relative; display: block; background-color: var(--surface); }
.project__media {
  --ar: 0.625;
  overflow: hidden; aspect-ratio: 16 / 10;
  background-color: var(--surface-2);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
/* A slower, shallower settle than the old 1.05 in 0.9s: expensive things move
   less. */
.project__media img { width: 100%; height: 100%; object-fit: cover; transition: transform 1.2s var(--ease); }
.project:hover .project__media img { transform: scale(1.03); }

/* The flagship runs edge to edge, the one place the work gets the whole width
   and the page takes a breath. No chamfer, for the reason the hero has none: a
   bevel only reads on a bounded plane. The caption still lands on the
   container's left edge, like the figures band. */
.project--bleed { margin-bottom: clamp(1rem, 2.5vw, 2rem); background-color: var(--surface-2); }
.project--bleed .project__media { --ar: 0.381; aspect-ratio: 21 / 8; clip-path: none; }
.project--bleed .project__overlay {
  clip-path: none;
  padding-inline: max(var(--gutter), calc(50% - var(--maxw) / 2 + var(--gutter)));
  padding-bottom: clamp(1.5rem, 4vw, 3rem);
}
.project--bleed .project__title { font-size: var(--fs-h2); }

/* Gallery tiles are articles (their h3 stays in the outline); a stretched button
   opens the project view. It is created `hidden` and unhidden by main.js, so it
   never exists as dead UI without JavaScript. */
.project__open {
  position: absolute; inset: 0; z-index: 2;
  width: 100%; padding: 0; background: transparent; border: 0; cursor: pointer;
}
/* The scrim is sized by contrast maths, not by eye, and — like the hero's — it
   is anchored to the BOTTOM with stops in rem. Percentage stops were tried and
   failed: a 62% stop covers the text block on the tall 2×2 lead tile but sits
   well above it on a 16:10 thumbnail, where the same block is a much larger
   share of the height. rem stops track the block, not the tile.
   The floor is 0.78; --accent-media needs 0.71 over a pure-white photo.

   The TAIL was the thing that made a nine-tile portfolio read as nine black
   rectangles: it only reached transparent at 23rem = 368px, taller than a
   gallery tile is (216–288px), so no part of any supporting tile was ever
   unveiled. Measured worst case for the text block after the title step change
   is 9.31rem, so the plateau holds to 10.5rem and then clears by 16rem — which
   is inside the tile, and the photograph finally shows. */
.project__overlay {
  position: absolute; inset: 0;
  background: linear-gradient(to top,
    rgba(18,17,13,0.94) 0,
    rgba(18,17,13,0.88) 4rem,
    rgba(18,17,13,0.78) 10.5rem,
    rgba(18,17,13,0.30) 13.5rem,
    transparent 16rem);
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: clamp(1.25rem, 3vw, 2rem);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
.project__cat {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent-media);
  margin-bottom: 0.5rem;
}
/* Title size follows the tile's ROLE, not the page it sits on. A supporting tile
   is roughly 380px wide in the gallery; a 32px title wrapped "Stambeni kompleks
   „Vidikovac”" onto three lines, the text block grew to 202px inside a 237px tile,
   and the scrim had to cover almost the whole photograph to keep the label at AA
   (measured 4.87:1 — the tightest ratio on the site). Two steps, 25px and 40px,
   shrink the block, open the photograph, and give the lead tile the size
   difference the layout was already claiming. */
.project__title { font-size: var(--fs-xl); color: #f8f5ef; }
.gallery .project--lead .project__title { font-size: var(--fs-h2); }
/* Always visible. The specs used to appear on hover only, which meant never on
   a phone and never by keyboard on the gallery, whose tiles were not focusable.
   The scrim was already sized with this row in the block (opacity kept its
   space), so showing it changes no stop. */
.project__meta {
  display: flex; flex-wrap: wrap; gap: 0.35rem 1.25rem; margin-top: 0.6rem;
  font-family: var(--font-mono); font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
  color: rgba(248,245,239,0.88);
}

/* ---------------------------------------------------------------------------
   14b. PROJECT VIEW — depth behind every tile
   A native <dialog>: focus containment, Escape and an inert page come free.
   Laid out like a drawing's title block: a ruled top bar (category, close), the
   chamfered photograph, the specs as a mono table, three sentences of story and
   the one CTA. Opens with a short rise and fade; under reduced motion it simply
   appears. Filled from the tile's own <template> by main.js.
   ------------------------------------------------------------------------- */
.project-view {
  width: min(1120px, calc(100% - 2 * var(--gutter)));
  max-width: none;
  max-height: calc(100dvh - 2 * var(--gutter));
  margin: auto;
  padding: 0;
  border: 0;
  background: var(--bg);
  color: var(--ink);
  overflow: auto;
  overscroll-behavior: contain;
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out),
    overlay 0.4s allow-discrete, display 0.4s allow-discrete;
}
.project-view[open] { opacity: 1; transform: none; }
@starting-style { .project-view[open] { opacity: 0; transform: translateY(12px); } }
.project-view::backdrop {
  background: rgba(var(--dark-rgb), 0.78);
  opacity: 0;
  transition: opacity 0.4s var(--ease-out), overlay 0.4s allow-discrete, display 0.4s allow-discrete;
}
.project-view[open]::backdrop { opacity: 1; }
@starting-style { .project-view[open]::backdrop { opacity: 0; } }
html:has(.project-view[open]) { overflow: hidden; }

.project-view__bar {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
  padding: 0.5rem 0.5rem 0.5rem clamp(1.25rem, 3vw, 2rem);
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.project-view__cat {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent);
}
.project-view__close {
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line-2); border-radius: var(--radius);
  color: var(--ink); transition: border-color var(--dur) var(--ease);
}
.project-view__close:hover { border-color: var(--ink); }
.project-view__close svg { width: 16px; height: 16px; }
.project-view__grid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr); align-items: start; }
.project-view__media {
  margin: clamp(1.25rem, 3vw, 2rem);
  margin-right: 0;
  background-color: var(--surface-2);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
.project-view__media img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.project-view__body {
  padding: clamp(1.25rem, 3vw, 2rem) clamp(1.25rem, 3vw, 2.5rem) clamp(1.5rem, 3vw, 2.5rem);
  display: grid; gap: var(--s-3); align-content: start;
}
/* Opening focus lands on the title (autofocus, tabindex -1): screen readers
   announce the project, and no focus ring flashes on the close button for a
   mouse user. The title is not interactive, so it needs no visible ring. */
.project-view__title { font-size: var(--fs-h3); }
.project-view__title:focus { outline: none; }
.project-view__spec { display: grid; grid-template-columns: auto minmax(0, 1fr); border-top: 1px solid var(--line); }
.project-view__spec dt, .project-view__spec dd {
  padding-block: 0.6rem; border-bottom: 1px solid var(--line); margin: 0;
}
.project-view__spec dt {
  padding-right: var(--s-3);
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
  align-self: baseline;
}
.project-view__spec dd { font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.project-view__story { color: var(--ink-2); max-width: 52ch; }
.project-view__story p + p { margin-top: var(--s-2); }
.project-view__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3); padding-top: var(--s-1); }

/* ---------------------------------------------------------------------------
   15. QUOTE
   No 5rem decorative quote glyph. At this size the type is the graphic.
   The label sits in a narrow left column so the quote lands off-centre —
   the band gets tension from the asymmetry, not from ornament.
   ------------------------------------------------------------------------- */
.quote-band {
  display: grid;
  grid-template-columns: minmax(0, 0.3fr) minmax(0, 0.7fr);
  grid-template-rows: auto 1fr;
  column-gap: clamp(1.5rem, 5vw, 4rem);
  row-gap: var(--s-4);
  align-items: start;
}
/* The left column used to hold the eyebrow and nothing else, so a third of the
   band was a hole roughly 350px deep. The attribution belongs there: label at the
   top of the rail, who said it at the bottom, the quote spanning both rows beside
   them. Source order is still quote-then-attribution — only the placement moves. */
.quote-band > .eyebrow { grid-column: 1; grid-row: 1; }
.quote { grid-column: 2; grid-row: 1 / span 2; }
.quote__cite { grid-column: 1; grid-row: 2; align-self: end; }
.quote {
  font-family: var(--font-display);
  font-stretch: var(--wd-display);
  font-weight: 500;
  font-size: var(--fs-h2);
  line-height: 1.22; letter-spacing: -0.022em;
  max-width: 28ch;
  text-wrap: pretty;
  quotes: "„" "”";
  /* Hanging punctuation: the opening „ sits outside the measure so the words,
     not the mark, align with the column edge. 0.46em is the glyph's measured
     advance in this voice (Archivo 500 at 116% width). */
  text-indent: -0.46em;
}
.quote::before { content: open-quote; }
.quote::after { content: close-quote; }
/* Two more voices under the lead quote, set a step smaller. One testimonial is
   thin for a decision this size; three project types (house, apartment
   building, restoration) cover the three ways people arrive here. */
.voices {
  grid-column: 2; grid-row: 3;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4) clamp(1.5rem, 4vw, 3rem);
  margin-top: var(--s-4); padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}
.voice { display: grid; gap: var(--s-2); align-content: start; }
.voice__q {
  font-family: var(--font-display); font-stretch: var(--wd-display);
  font-weight: 500; font-size: var(--fs-lg); line-height: 1.4; letter-spacing: -0.01em;
  quotes: "„" "”"; text-indent: -0.46em; max-width: 36ch;
}
.voice__q::before { content: open-quote; }
.voice__q::after { content: close-quote; }
.voice__by { display: grid; gap: 0.2rem; }
.voice__by b { font-weight: 600; font-size: var(--fs-sm); }
.voice__by span { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3); }
.voice__by a { color: var(--ink-3); text-decoration: underline; text-decoration-color: var(--line-2); text-underline-offset: 0.2em; }
.voice__by a:hover { color: var(--accent); text-decoration-color: currentColor; }
.quote__cite {
  display: grid; gap: 0.3rem; font-style: normal;
  padding-top: var(--s-3); border-top: 1px solid var(--line);
}
.quote__cite b { display: block; font-weight: 600; font-size: var(--fs-sm); }
.quote__cite span { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3); }

.statement {
  font-family: var(--font-display);
  font-stretch: var(--wd-display);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.26; letter-spacing: -0.02em;
  margin-top: var(--s-3); max-width: 34ch;
  text-wrap: balance;
  text-indent: -0.46em;   /* the statement opens with „ in the markup: hang it */
}

/* Attribution under a statement: name in text voice, role in the mono voice. */
.byline { display: grid; gap: 0.2rem; }
.byline b { font-weight: 600; font-size: var(--fs-sm); }
.byline span { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3); }

/* ---------------------------------------------------------------------------
   16. TRUST STRIP
   Typographic, hairline-separated. Six copies of one check-circle icon is
   filler, not evidence — the certificate numbers are the evidence.
   ------------------------------------------------------------------------- */
.trust { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding-block: var(--s-4); }
.trust__label {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3);
  margin-bottom: var(--s-3);
}
.trust__row { display: flex; flex-wrap: wrap; column-gap: 0; row-gap: 0.5rem; align-items: baseline; }
.trust__item {
  font-family: var(--font-mono); font-size: var(--fs-meta);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-2);
  padding-inline: clamp(0.75rem, 2vw, 1.5rem);
  border-left: 1px solid var(--line);
}
.trust__item:first-child { padding-left: 0; border-left: 0; }

/* ---------------------------------------------------------------------------
   17. CTA BAND
   Left-aligned, asymmetric: the statement holds the left column, the actions
   and the phone number sit at the right. Centring everything removes tension.
   ------------------------------------------------------------------------- */
.cta { position: relative; overflow: hidden; isolation: isolate; }
.cta__media { position: absolute; inset: 0; z-index: -2; }
.cta__media img { width: 100%; height: 100%; object-fit: cover; }
/* Floor 0.84 even at the open end: --ink-2 needs 0.80 over a pure-white photo,
   and the phone-number block sits in that right-hand column. */
.cta::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg, rgba(22,21,17,0.95) 0%, rgba(22,21,17,0.90) 55%, rgba(22,21,17,0.84) 100%);
}
.cta__inner {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: end;
}
.cta__lead { display: grid; gap: var(--s-3); }
.cta h2 { font-size: var(--fs-h2-lead); letter-spacing: -0.03em; max-width: 22ch; }
.cta .lede { max-width: 40ch; }
.cta__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
.cta__aside { display: grid; gap: var(--s-2); justify-items: start; }
.cta__tel {
  font-family: var(--font-mono); font-size: var(--fs-lg);
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
  color: var(--ink);
  padding-top: var(--s-3); border-top: 1px solid var(--line); width: 100%;
}
.cta__tel:hover { color: var(--accent); }
/* --ink-2 for both labels here, same reason as the hero: photography behind. */
.cta .eyebrow { color: var(--ink-2); }
.cta__tel-k {
  display: block; font-size: var(--fs-label); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-2); margin-bottom: 0.4rem;
}

/* ---------------------------------------------------------------------------
   18. CONTACT
   ------------------------------------------------------------------------- */
.contact-grid { display: grid; grid-template-columns: 0.8fr 1.2fr; gap: clamp(2rem, 6vw, 5rem); align-items: start; }
.contact-info { display: grid; gap: var(--s-4); align-content: start; }
.contact-block { display: grid; gap: 0.4rem; padding-top: var(--s-2); border-top: 1px solid var(--line); }
.contact-block .k {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
}
.contact-block .v { font-size: var(--fs-base); font-variant-numeric: tabular-nums; }
.contact-block a.v:hover { color: var(--accent); }

.form { display: grid; gap: var(--s-3); }
/* Grid items stretch by default; a submit button that spans the whole form
   reads as a mistake, and it puts the chamfer nowhere near the label. */
.form > .btn { justify-self: start; }
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
.field { display: grid; gap: 0.5rem; }
.field label {
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-2);
}
.field input, .field select, .field textarea {
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  padding: 0.85rem 1rem; color: var(--ink);
  transition: border-color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.field input::placeholder, .field textarea::placeholder { color: var(--ink-3); }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"], .field select[aria-invalid="true"] {
  border-color: var(--danger, #a3311c);
}
.field textarea { resize: vertical; min-height: 130px; }
/* The whole <label> is the hit area, so the box itself is not the target — but
   a 13px default checkbox is still an awkwardly small thing to aim at. */
.form__consent { display: flex; gap: 0.75rem; align-items: flex-start; font-size: var(--fs-sm); color: var(--ink-2); cursor: pointer; }
.form__consent input {
  width: 18px; height: 18px; flex: none;
  margin-top: 0.2rem; accent-color: var(--accent);
}
.form__consent input[aria-invalid="true"] { outline: 2px solid var(--danger, #a3311c); outline-offset: 2px; }
.form__note { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3); line-height: 1.6; }
.form__status { display: block; font-size: var(--fs-sm); min-height: 1.2em; }
.form__status.ok { color: var(--accent); font-weight: 600; }
.form__status.err { color: #a3311c; font-weight: 600; }
.field__hint { font-size: var(--fs-sm); color: var(--ink-3); margin-top: calc(-1 * var(--s-2)); }

/* Tap-to-answer choices instead of a dropdown: "what are you building" and "how
   far along are you". Real radio inputs, stretched invisibly over each chip, so
   arrow keys, labels and screen readers behave natively. Qualifying questions
   that cost the visitor one tap each. */
.chips { display: grid; gap: 0.6rem; border: 0; padding: 0; margin: 0; min-width: 0; }
.chips legend {
  padding: 0; margin-bottom: 0.6rem;
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-2);
}
.chips__row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chip { position: relative; display: inline-flex; }
.chip input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.chip span {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 1rem;
  border: 1px solid var(--line-2); border-radius: var(--radius);
  background: var(--surface); color: var(--ink-2); font-size: var(--fs-sm);
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease), background-color 0.3s var(--ease);
}
.chip:hover span { border-color: var(--ink-3); color: var(--ink); }
.chip input:checked + span { border-color: var(--accent); background: var(--accent-soft); color: var(--ink); box-shadow: inset 0 0 0 1px var(--accent); }
.chip input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 3px; }

/* What happens after you press the button, and who reads it. The anxiety a form
   creates is "into which void does this go"; this answers it beside the form. */
.next-steps { display: grid; border-top: 1px solid var(--line); }
.next-steps li {
  display: grid; grid-template-columns: 9rem minmax(0, 1fr); gap: var(--s-2);
  padding-block: 0.9rem; border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm); line-height: 1.55; color: var(--ink-2);
}
.next-steps b { display: block; color: var(--ink); font-weight: 600; }
.next-steps__when {
  padding-top: 0.2em;
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent);
}
.responder { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: var(--s-2); align-items: center; }
.responder img {
  width: 64px; height: 64px; object-fit: cover; object-position: 50% 30%;
  filter: grayscale(1) contrast(1.03);
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 0 100%);
}
.responder p { font-size: var(--fs-sm); line-height: 1.55; color: var(--ink-2); }
.responder b { color: var(--ink); font-weight: 600; }

/* The thank-you state replaces the form: a designed moment, not a status line.
   It names the next step and when it happens, computed from the working hours. */
.form-done {
  display: grid; gap: var(--s-3); align-content: start;
  padding: clamp(1.5rem, 4vw, 2.5rem);
  border: 1px solid var(--line); background: var(--surface);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
/* `hidden` must win over the display these components set for themselves:
   the UA's [hidden] rule loses to any author `display`, so without this the
   thank-you panel showed from the start and a submitted form never left. */
.form[hidden], .form-done[hidden] { display: none; }
.form-done h2 { font-size: var(--fs-h3); }
.form-done h2:focus { outline: none; }
.form-done p { color: var(--ink-2); max-width: 52ch; }

.map {
  border: 1px solid var(--line);
  overflow: hidden; aspect-ratio: 16 / 7; background: var(--surface-2);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
/* OSM's default raster is the most saturated thing on an otherwise ivory site:
   full-strength greens, reds and yellows in a full-bleed band. A mild pull on
   saturation alone — no brightness or contrast touched, so every label and road
   stays exactly as legible — settles it into the palette. Nothing about the map
   depends on this; delete the filter and it simply gets louder again. */
.map img, .map iframe {
  width: 100%; height: 100%; object-fit: cover; border: 0; display: block;
  filter: saturate(0.72);
}
.map__cap { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3); }

/* ---------------------------------------------------------------------------
   19. PAGE HERO (interior pages)
   ------------------------------------------------------------------------- */
.page-hero {
  position: relative;
  padding-top: calc(var(--header-h) + clamp(3rem, 8vh, 6rem));
  padding-bottom: clamp(3rem, 7vh, 5rem);
  overflow: hidden; isolation: isolate;
  background-color: var(--bg);
}
.page-hero__media { position: absolute; inset: 0; z-index: -2; }
.page-hero__media img { width: 100%; height: 100%; object-fit: cover; opacity: 0.4; }
/* The photo is already at 0.4 over the dark ground, so this scrim needs less
   than the hero's. 0.78 at the top clears --ink-2 (needs 0.50) with margin. */
.page-hero::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(22,21,17,0.78), rgba(22,21,17,0.93));
}
.page-hero__inner { display: grid; gap: var(--s-3); justify-items: start; }
/* Labels here step up one level for the same reason as the hero's. */
.page-hero .eyebrow, .page-hero .crumbs { color: var(--ink-2); }
.page-hero__inner h1 { max-width: 19ch; }
.page-hero__inner .lede { max-width: 46ch; }
.crumbs {
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
  font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3);
}
/* WCAG 2.5.8 wants a 24px minimum target; at 13px mono these links measured
   21px high, so they get vertical padding rather than a bigger type size. */
.crumbs a { display: inline-block; padding-block: 0.25rem; }
.crumbs a:hover { color: var(--ink); }
.crumbs .sep { color: var(--line-2); }

/* ---------------------------------------------------------------------------
   20. VALUES / TEAM
   The values are four rules, not four steps — so they carry no numbers.
   ------------------------------------------------------------------------- */
.value-list { display: grid; border-top: 1px solid var(--line); }
.value {
  display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: clamp(1rem, 4vw, 3.5rem);
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
  border-bottom: 1px solid var(--line);
  align-items: baseline;
}
.value h3 { font-size: var(--fs-xl); max-width: 20ch; }
.value p { color: var(--ink-2); font-size: var(--fs-sm); line-height: 1.65; max-width: 56ch; }

.team-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--s-3); }
/* align-content: start, or a card with a shorter note is stretched to the row's
   tallest and the extra height pushes its name down (it did: 11px). */
.member { display: grid; gap: var(--s-2); align-content: start; }
.member__photo {
  aspect-ratio: 3 / 4; overflow: hidden; background: var(--surface-2);
  clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);
}
.member__photo img { width: 100%; height: 100%; object-fit: cover; filter: grayscale(1) contrast(1.03); transition: filter 0.6s var(--ease); }
.member:hover .member__photo img { filter: grayscale(0); }
.member__name { font-size: var(--fs-base); font-weight: 600; }
.member__role { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3); }
.member__note { margin-top: 0.4rem; font-size: var(--fs-sm); line-height: 1.55; color: var(--ink-2); max-width: 30ch; }

/* ---------------------------------------------------------------------------
   20b. PROSE — long-form text pages (privacy policy)
   ------------------------------------------------------------------------- */
.prose > * + * { margin-top: var(--s-2); }
.prose h2 { font-size: var(--fs-xl); margin-top: var(--s-5); }
.prose p, .prose li { color: var(--ink-2); }
.prose ul { display: grid; gap: 0.5rem; }
.prose li { position: relative; padding-left: 1.25rem; }
.prose li::before { content: ""; position: absolute; left: 0; top: 0.8em; width: 0.6rem; height: 1px; background: var(--accent); }
.prose a { text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: 0.2em; }
.prose a:hover { color: var(--accent); }
.prose__meta { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3); }

/* ---------------------------------------------------------------------------
   21. FOOTER
   ------------------------------------------------------------------------- */
.footer { padding-block: clamp(3.5rem, 6vw, 5.5rem) var(--s-4); }
.footer__top {
  display: grid; grid-template-columns: 1.4fr repeat(3, 1fr);
  gap: var(--s-5); padding-bottom: var(--s-6); border-bottom: 1px solid var(--line);
}
.footer__brand { max-width: 34ch; }
.footer__brand p { color: var(--ink-2); font-size: var(--fs-sm); margin-top: var(--s-2); line-height: 1.65; }
/* Marked up as h2, not h4: the footer is a top-level landmark rather than a
   subsection of the last <section>, and h4 here produced an h2->h4 skip on
   every page (h1->h4 on kontakt). It is still typeset as a small mono label. */
.footer__col-h {
  font-family: var(--font-mono); font-stretch: normal;
  font-size: var(--fs-label); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3);
  line-height: 1.4;
  margin-bottom: var(--s-2);
}
.footer__col ul { display: grid; gap: 0.6rem; }
.footer__col a, .footer__col li { color: var(--ink-2); font-size: var(--fs-sm); transition: color 0.3s var(--ease); }
.footer__col a:hover { color: var(--ink); }
.footer__bottom {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center;
  gap: var(--s-2); padding-top: var(--s-4);
  font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.footer__social { display: flex; gap: 0.5rem; }
/* 44px: the comfortable touch minimum. These were 38px. */
.footer__social a {
  width: 44px; height: 44px; display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: var(--radius);
  color: var(--ink-2); transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.footer__social a:hover { border-color: var(--accent); color: var(--accent); }
.footer__social svg { width: 18px; height: 18px; }

/* ---------------------------------------------------------------------------
   22. FILTER + GALLERY (projects)
   ------------------------------------------------------------------------- */
.filter {
  display: flex; flex-wrap: wrap; gap: 0;
  margin-bottom: clamp(2rem, 4vw, 3rem);
  border-bottom: 1px solid var(--line);
}
.filter__btn {
  position: relative;
  padding: 0.7rem clamp(0.75rem, 2vw, 1.25rem);
  font-family: var(--font-mono); font-size: var(--fs-meta);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
  transition: color 0.3s var(--ease);
}
.filter__btn:first-child { padding-left: 0; }
.filter__btn::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform 0.35s var(--ease);
}
.filter__btn:first-child::after { left: 0; }
.filter__btn:hover { color: var(--ink); }
.filter__btn.is-active { color: var(--ink); font-weight: 600; }
.filter__btn.is-active::after { transform: scaleX(1); }

/* Asymmetric gallery: the flagship holds a 2×2 cell, the rest is a uniform
   3-up. `dense` backfills so filtering can never punch a hole in the grid. */
.gallery {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 2vw, 1.5rem);
  grid-auto-flow: row dense;
}
.gallery .project--lead { grid-column: span 2; grid-row: span 2; }
.gallery .project--lead .project__media { aspect-ratio: auto; height: 100%; }
.gallery .project.is-hidden { display: none; }

/* ---------------------------------------------------------------------------
   23. CHECKLIST + UTILITIES
   The tick is a real SVG (a self-contained data URI), not a ✓ text glyph.
   ------------------------------------------------------------------------- */
.checklist { display: grid; gap: 0.75rem; margin-top: var(--s-3); }
/* For a section that carries no image: the list spreads across the full measure
   instead of leaving a column of dead space where the photo used to be. */
.checklist--wide {
  /* 300px caps this at three columns on a 1280 container, so five items break
     3 + 2 rather than 4 + 1 orphan. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  column-gap: clamp(1.5rem, 4vw, 3rem);
  row-gap: 0.85rem;
  margin-top: 0;
}
.checklist li {
  display: grid; grid-template-columns: 16px 1fr; gap: 0.75rem;
  align-items: start; color: var(--ink-2); font-size: var(--fs-sm); line-height: 1.6;
}
.checklist li::before {
  content: ""; width: 16px; height: 16px; margin-top: 0.3em;
  background-color: var(--accent);
  -webkit-mask: var(--tick) center / contain no-repeat;
  mask: var(--tick) center / contain no-repeat;
}

[id] { scroll-margin-top: calc(var(--header-h) + 1.5rem); }
/* A full-bleed section brings its own top padding, so it lands flush under the
   header, tucked 1px beneath the header's hairline so rounding can never expose
   a pixel row of the band above. The generic 1.5rem extra left a strip of the
   PREVIOUS section showing (a different colour next to every .section--alt, a
   stray checklist line on a phone), which read as a mis-scroll. */
.section[id] { scroll-margin-top: calc(var(--header-h) - 1px); }
.u-center { text-align: center; }
.u-mt-3 { margin-top: var(--s-3); }
.u-mt-4 { margin-top: var(--s-4); }
.u-mt-6 { margin-top: var(--s-6); }
.u-mt-8 { margin-top: clamp(3rem, 7vw, 6rem); }
.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; }
.skip-link {
  position: absolute; left: var(--gutter); top: -100px; z-index: 200;
  background: var(--accent); color: var(--accent-ink);
  padding: 0.6rem 1rem; font-weight: 600; transition: top 0.3s var(--ease);
}
.skip-link:focus { top: var(--s-2); }

/* ---------------------------------------------------------------------------
   24. RESPONSIVE — 1280 / 1024 / 768 / 480 (studio standard)
   ------------------------------------------------------------------------- */
@media (max-width: 1280px) {
  .footer__top { gap: var(--s-4); }
}
@media (max-width: 1024px) {
  :root { --header-h: 68px; --chamfer: 12px; }
  .section-head--split { grid-template-columns: 1fr; row-gap: var(--s-3); }
  .section-head--split .lede { grid-column: 1; grid-row: auto; }
  .service { grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr); }
  .service__link { grid-column: 2; justify-self: start; align-self: start; margin-top: var(--s-2); }
  .spec { grid-template-columns: repeat(2, 1fr); }
  .spec__item:nth-child(3n) { border-right: 1px solid var(--line); }
  .spec__item:nth-child(2n) { border-right: 0; }
  .spec__item:nth-child(3n + 1) > * { padding-left: clamp(1.25rem, 2.5vw, 2rem); }
  .spec__item:nth-child(2n + 1) > * { padding-left: 0; }
  .gallery { grid-template-columns: repeat(2, 1fr); }
  .gallery .project--lead { grid-column: 1 / -1; }
  .cta__inner { grid-template-columns: 1fr; }
  .footer__top { grid-template-columns: 1fr 1fr; }
  .footer__brand { grid-column: 1 / -1; }
}
@media (max-width: 768px) {
  .nav__menu, .nav__cta { display: none; }
  .burger { display: block; }
  /* Below ~560px the headline and lede wrap enough that the text block fills
     roughly 95% of the hero, so no single stop can both cover it and leave the
     top of the photograph open — the two requirements genuinely conflict. On a
     phone the block already covers almost the whole image anyway, so the hero
     becomes a near-uniform panel and the photograph reads as texture. Desktop
     keeps its open top through the rule in section 8. */
  .hero__scrim {
    background:
      linear-gradient(to top,
        var(--bg) 0,
        rgba(22,21,17,0.93) 2rem,
        rgba(22,21,17,0.86) 40%,
        rgba(22,21,17,0.82) 100%),
      linear-gradient(to bottom, rgba(22,21,17,0.55) 0, transparent 30%);
  }
  .stats { grid-template-columns: repeat(2, 1fr); }
  .stat:nth-child(2n) { border-right: 0; }
  .stat:nth-child(-n + 2) { border-bottom: 1px solid var(--line); }
  /* Left column on the container edge, as the first figure is at four up. Only
     :first-child was zeroed, so the third figure stood 20px in from the first.
     The framed band keeps its inset: it has a border to clear. */
  .stats:not(.stats--framed) .stat:nth-child(odd) { padding-left: 0; }
  .split, .split--text-first { grid-template-columns: 1fr; }
  .split__media { order: -1; }
  .split__frame { --ar: 0.625; }
  .split__frame img { aspect-ratio: 16 / 10; }
  /* On one column the photo is a 16:10 strip, and the plate at desktop size
     covered most of it. It now hangs off the bottom edge, half on the photo. */
  .media-badge {
    left: var(--s-2); bottom: 0; transform: translateY(50%);
    padding: var(--s-2) var(--s-3);
  }
  .media-badge .n { font-size: 1.5rem; }
  .split__media:has(.media-badge) { margin-bottom: var(--s-5); }
  .process__track { grid-template-columns: 1fr; }
  .process__line { display: none; }
  .step { border-right: 0; border-bottom: 1px solid var(--line); padding-bottom: var(--s-4); }
  .step:last-child { border-bottom: 0; }
  .step > *, .step:first-child > * { padding-left: var(--s-3); }
  /* Stacked: the station mark becomes the vertical rule of its own phase,
     bounded to the content rather than overshooting it. */
  .step::before { top: var(--s-4); bottom: var(--s-2); height: auto; width: 1px; }
  .build__head { grid-template-columns: 1fr; }
  .build__stage { grid-template-columns: 1fr; }
  .projects-grid { grid-template-columns: 1fr; }
  .project--bleed .project__media { --ar: 0.625; aspect-ratio: 16 / 10; }
  .contact-grid { grid-template-columns: 1fr; }
  .value { grid-template-columns: 1fr; gap: 0.6rem; }
  /* One column: drop the explicit placement or the quote would still be told to
     span two rows that no longer exist. */
  .quote-band { grid-template-columns: 1fr; grid-template-rows: auto; }
  .quote-band > .eyebrow, .quote, .quote__cite, .voices { grid-column: 1; grid-row: auto; }
  .voices { grid-template-columns: 1fr; }
  .process__cta { align-items: flex-start; }
  .project-view__grid { grid-template-columns: 1fr; }
  .project-view__media { margin: var(--gutter) var(--gutter) 0; }
}
@media (max-width: 480px) {
  :root { --chamfer: 10px; }
  .stats { grid-template-columns: 1fr; }
  .stat { border-right: 0; border-bottom: 1px solid var(--line); }
  .stat:last-child { border-bottom: 0; }
  .stats:not(.stats--framed) .stat { padding-inline: 0; }
  .form__row { grid-template-columns: 1fr; }
  .spec { grid-template-columns: 1fr; }
  /* `.spec` in front on purpose. The 2-up rules in the 1024 block are
     :nth-child selectors, and a bare `.spec__item` reset lost to them, so on a
     phone the third item kept its right rule and the fourth stood 20px in. */
  .spec .spec__item, .spec--2 .spec__item { border-right: 0; }
  .spec .spec__item > *, .spec--2 .spec__item > * { padding-left: 0; }
  .spec__item { padding-right: 0; }
  .service { grid-template-columns: 1fr; gap: var(--s-2); }
  .service__link { grid-column: 1; }
  .gallery { grid-template-columns: 1fr; }
  .gallery .project--lead .project__media { aspect-ratio: 16 / 10; }
  /* 21:8 is a desktop banner ratio. On one column at 320px it made the lead tile
     a 107px strip while its own text block needed 175px, so the category label
     and the first line of the title overflowed ABOVE the tile, onto the ivory,
     as near-white type on near-white ground — invisible, not merely ugly. On a
     phone the lead tile is just a tile: same ratio, same title step as the rest. */
  .project--bleed .project__title,
  .gallery .project--lead .project__title { font-size: var(--fs-xl); }
  .next-steps li { grid-template-columns: 1fr; gap: 0.2rem; }
  .footer__top { grid-template-columns: 1fr; }
  .footer__bottom { flex-direction: column; align-items: flex-start; }
  /* Dropping the vertical rules here left "ISO 9001  ISO 14001  ISO 45001"
     reading as one run-on string. The list becomes ruled ROWS instead, which is
     the same hairline language rotated 90°. */
  .trust__row { display: grid; row-gap: 0; }
  .trust__item {
    padding: 0.55rem 0; border-left: 0;
    border-top: 1px solid var(--line);
  }
  .trust__item:first-child { border-top: 0; padding-top: 0; }
}

/* Pinned film, compact: on a phone, or on any screen too short for five rows
   beside the film (a landscape phone), only the current stage is shown. All five
   share one grid cell, so the block is as tall as the longest and nothing jumps
   when the stage changes; the rule turns horizontal, above them. */
@media (prefers-reduced-motion: no-preference) and (max-width: 768px),
       (prefers-reduced-motion: no-preference) and (max-height: 560px) {
  .js .build:not(.build--still) .build__run { height: calc(100svh + 150svh); }
  .js .build:not(.build--still) .build__stage { gap: var(--s-3); }
  /* 16:9 at 390px is a 220px strip in a screen of black. 4:3 keeps 75% of the
     width, and 40% across keeps the pit, which sits left of centre, in frame. */
  .js .build:not(.build--still) .build__film { aspect-ratio: 4 / 3; }
  .js .build:not(.build--still) .build__film img,
  .js .build:not(.build--still) .build__film video { object-position: 40% 50%; }
  .js .build:not(.build--still) .build__phases {
    display: grid; border-left: 0; border-top: 1px solid var(--line-2); padding-top: var(--s-2);
  }
  .js .build:not(.build--still) .build__phases::after {
    top: -1px; left: 0; width: 100%; height: 1px;
    transform: scaleX(var(--p, 0)); transform-origin: left;
  }
  .js .build:not(.build--still) .build__phases li {
    grid-area: 1 / 1; border-top: 0; padding: 0;
    opacity: 0; transition: opacity 0.3s var(--ease);
  }
  .js .build:not(.build--still) .build__phases li::before { display: none; }
  .js .build:not(.build--still) .build__phases li.is-active { opacity: 1; }
}
@media (prefers-reduced-motion: no-preference) and (max-height: 560px) and (min-width: 769px) {
  .js .build:not(.build--still) .build__stage { grid-template-columns: minmax(0, 1fr) minmax(15rem, 19rem); }
  .js .build:not(.build--still) .build__film { aspect-ratio: 16 / 9; }
}

/* ---------------------------------------------------------------------------
   25. REDUCED MOTION
   The keyframe animations and the two scripted moments are already gated in
   /assets/animations/. What was still running here are the DECORATIVE hover
   transforms — a 0.9s image scale, a sliding service title, a nudging arrow.
   Colour and border feedback is kept: it is the state change that matters, and
   removing it would cost affordance rather than motion.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .project:hover .project__media img,
  .service:hover .service__title,
  .service:hover .service__link .arw,
  .btn:hover .arw,
  .link:hover .arw,
  .faq__item[open] > .faq__q::after {
    transform: none;
  }
  .project__media img,
  .service__title,
  .arw,
  .member__photo img,
  .filter__btn::after,
  .faq__q::after,
  .project-view,
  .project-view::backdrop,
  .model3d__mode::after {
    transition: none;
  }
  /* The dialog's rise is part of its opening; under reduced motion it appears in
     place, so the transform must not be left behind either. */
  .project-view { transform: none; }
  /* The FAQ marker no longer rotates, so it swaps glyph instead: + becomes −. */
  .faq__item[open] > .faq__q::after { content: "−"; }
}

/* ---------------------------------------------------------------------------
   26. PRINT
   ------------------------------------------------------------------------- */
@media print {
  .header, .mobile-menu, .hero__media, .cta__media, .footer__social { display: none !important; }
  body { background: #fff; color: #000; }
  .section { background-image: none; }
}

/* === XV-DEMO-RIBBON — added by scripts/import-site.py, not part of the site's source ===
   The ExVisio portfolio's DEMO ribbon, in this site's palette
   (scripts/portfolio-sites.json). Hidden only by recording-mode.mjs, for a take. */
.xv-demo-ribbon {
  position: fixed; right: 1rem; bottom: 1rem; z-index: 9999;
  display: inline-flex; align-items: center;
  padding: .5em .85em;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
  font-family: var(--font-mono); font-size: .72rem; font-weight: 600; letter-spacing: .04em;
  color: var(--ink-2); text-decoration: none;
}
.xv-demo-ribbon:hover,
.xv-demo-ribbon:focus-visible { color: var(--accent); border-color: var(--accent); }
/* === end XV-DEMO-RIBBON === */
