/* Pekara ODRA — shared chrome for every public page (loaded by base.html): tokens, the
   photographic ground, fixed header, overlay, footer, buttons, form baseline and the
   flow-page adaptations. The landing keeps its stage choreography in index.css.
   CSP forbids inline styles. */

/* ===================== foundation =====================
   Self-sufficient: public routes no longer load theme.css (v2 tokens) or site.css (v2 chrome).
   theme.css survives only for the one standalone v2 document that loads it itself —
   coming-soon — which does not extend base.html. (/privatnost joined the board 2026-09-18.)
   Rationale: docs/CHANGELOG.md, 2026-08 entries ("DESIGN.md" was cited here and never existed). */

:root {
  --font-display: "Bricolage Grotesque", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-body: "Hanken Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* ---- type scale: eight steps ----
     One job per step; hierarchy within a step comes from weight and tracking. The clamp()ed
     display sizes (h1/h2/card names) are a separate mechanism, not steps. The only fixed px
     font-size in this world is the 16px form control; any other literal is drift. */
  --t-micro: 11.5px;   /* caps labels, kickers, tags, status pips — always 700 + wide tracking; smaller and they vanish on a flour crest */
  --t-fine: 12.5px;    /* fine print: footer, meta, hints, errors, nav caps, small caps links */
  --t-small: 13.5px;   /* secondary copy: card descriptions, list meta, pill labels */
  --t-body: 15.5px;    /* body: default readable text, values, CTA links */
  --t-lead: 17px;      /* lead paragraphs */
  --t-dsp-s: 21px;     /* small Anton display: lockup, item names, subheads */
  --t-dsp-m: 26px;     /* price, and the lockup wordmark */
  --t-dsp-l: 36px;     /* large price (the detail lockup) */
}

* { box-sizing: border-box; margin: 0; padding: 0; }
/* The canvas behind the rubber-band: iOS rides the fixed board WITH the bounce, so the area
   revealed above the page is this colour. Warm near-black matched to theme-color and the
   board's ambient — the cool --vb-ground there read as a foreign band on a real iPhone. */
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; background-color: #1a1613; }
body {
  font-family: var(--font-body); font-size: var(--t-body); line-height: 1.65;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
img, svg, picture { display: block; max-width: 100%; }
/* inherit, not the v2 red: a link takes its colour from its component */
a { color: inherit; text-decoration: none; }
ul { list-style: none; }

.skip-link {
  position: fixed; top: 10px; left: 10px; z-index: 200;
  /* visibility too, not transform alone: iOS rides position:fixed elements with the
     rubber-band, so the pill parked at -160% above the viewport slid INTO view on every
     pull-down overscroll (owner's second video). Hidden ink cannot be revealed by a bounce. */
  transform: translateY(-160%); visibility: hidden; transition: transform .2s ease;
  background: var(--vb-gold); color: #2a1508;
  padding: 10px 16px; border-radius: 100px; font-weight: 700; font-size: var(--t-small);
}
/* :focus-visible, not :focus: the router's main.focus() after a swap and iOS Safari's
   focus bookkeeping both count as plain :focus, and the pill popped over the status bar on
   a real iPhone (owner report). The skip link exists FOR the keyboard; only keyboard-class
   focus should reveal it. */
.skip-link:focus-visible { transform: translateY(0); visibility: visible; }

/* scroll reveal (site.js adds .is-revealed; no-JS renders everything visible via nojs.css).
   Travel is deliberately far shorter than the fade — 11px in .2s against a .62s fade — so
   movement is over by ~35% opacity and nothing readable is still moving.
   Uses `translate`, not `transform`: `.page-vb main.vbp-pre h1` (0,2,2) outranks this rule
   (0,2,1) and discarded the headline's offset outright during a PJAX swap. `translate` is an
   independent property, so the router's transform composes with it. Firefox 72+ / Chrome 104+. */
html.js [data-reveal] {
  opacity: 0; translate: 0 11px;
  transition: opacity .62s var(--vb-ease), translate .2s var(--vb-ease);
  will-change: opacity, translate;
}
/* hand the layer back once arrived: `will-change` is a standing promise to the compositor, so
   without this 5-6 elements per page hold their own layer for the life of the session */
html.js [data-reveal].is-revealed { opacity: 1; translate: none; will-change: auto; }

.page-vb {
  /* ---- world tokens ---- */
  /* Warm near-black, the same value as theme-color and the html background: iOS Safari colours
     its status bar and toolbar from the page's own background at the top edge, not only from
     the meta, and the old cool blue-grey (#1d2026) showed there as a foreign band above and
     below the board on a real iPhone (owner, 2026-09-18 — "it's still grey" with tinting on).
     The board covers this on every route; it is seen only in Safari's chrome and for a frame
     while the plate decodes, and both should be the board's own dark. */
  --vb-ground: #1a1613;
  --vb-ground-deep: #120f0d;
  --vb-gold: #f5b986;
  --vb-gold-link: #f0ad74;   /* 8.1:1; at the old #d99a5b the smallest text was also the dimmest */
  --vb-flour: #fefefe;
  --vb-flour-dim: #dedbd2;   /* 12.6:1 — the ground is a photograph whose flour band hits ~luma 90, so flat-ground contrast is a floor, not a result */
  /* Raised a second time, 2026-08-13: #b6b2a8 -> #bfbbb1. The first lift (#8e8b83, ~1.4:1 over a
     flour crest) fixed the gross failures; this one is the last 3%. Every failure left in the
     matrix after the desktop reframe was muted ink on the board — 'Zatvoreno' at 4.35 and 4.36
     on /lokacije at 1920x1080 and 1920x900, the pickup note at 4.37 at 2560x1000. Swept
     #b6b2a8 / #bfbbb1 / #c8c4ba / #d1cdc3 / #dedbd2: the first step clears every case at 4.65
     and every brighter value returns the SAME 4.65, because past that point muted is no longer
     the worst run on the page. So this is the smallest lift that works, which is the one that
     costs the colour least of its job — reading dimmer than everything around it. 9.0:1 flat. */
  --vb-muted: #bfbbb1;
  --vb-berry: #a34a56;
  --vb-berry-hi: #b25562;
  --vb-chalk: #cfccc4;
  --vb-error: #e08a96;
  /* vh-aware, because the chrome competes with the composition for the same screen: a flat
     148px eats 21% of a 700px window before a word is drawn. Floor is 116px — the bar itself
     measures ~112px, and head-h below that drops the fixed header onto the page. Everything
     inside the header shrinks on the same curve, so the proportions hold. */
  /* + safe-top: in-browser Safari never paints under the status bar (env = 0 there), but
     a home-screen launch (manifest display: standalone) does — the reserve must grow with
     the chrome or the notch sits on the lockup. Inert everywhere env resolves to 0. */
  --vb-head-h: calc(clamp(116px, 16.5vh, 148px) + env(safe-area-inset-top, 0px));

  /* hairlines and tints derive from --vb-flour / --vb-gold, three weights of each; pages must
     not hand-roll their own rgba() literals */
  --vb-hair-soft: rgba(254, 254, 254, .09);   /* row dividers inside a block */
  --vb-hair: rgba(254, 254, 254, .14);        /* block edges, resting control outlines */
  /* .6, from .46 (design review 2026-09-03: "empty fields nearly vanish"). The old value sat
     15% above the threshold this comment already named; on a flour crest that margin is
     nothing. One token for every form on the site, so kontakt and veleprodaja lift
     together. Hover below moves with it, or rest would outshine hover. */
  --vb-hair-strong: rgba(254, 254, 254, .6);  /* field underlines, chip outlines; below ~.4 they disappear into the flour ring */
  /* How far the focus ring paints OUTSIDE a field's border box: outline-offset + outline-width,
     see .page-flow :focus-visible. Any ancestor that clips its overflow has to give the ring at
     least this much room (`overflow-clip-margin`) or it paints the ring and then cuts it off.
     Keep the two in step — that is the whole reason this is a token and not a literal. */
  --vb-ring-reach: 6px;
  --vb-gold-tint: rgba(245, 185, 134, .09);   /* selected fill */
  --vb-gold-line: rgba(245, 185, 134, .45);   /* hover outline on a control */
  /* berry is the action colour: a berry tint or outline means destructive or errored, never
     decoration */
  --vb-berry-tint: rgba(163, 74, 86, .18);
  --vb-berry-line: rgba(178, 85, 98, .5);
  --vb-pane-bg: rgba(10, 12, 16, .42);        /* the ONE panel fill (selectable surfaces only) */
  --vb-ease: cubic-bezier(.22, .75, .25, 1);
  /* reel easing vocabulary */
  --vbe-in: cubic-bezier(.5, 0, 1, 1);
  --vbe-inout: cubic-bezier(.45, 0, .15, 1);
  --vbe-cascade: cubic-bezier(.16, 1, .3, 1);
  --vbe-head: cubic-bezier(.5, 0, .1, 1);
  --vbe-loaf: cubic-bezier(.16, .9, .15, 1);

  background-color: var(--vb-ground);
  color: var(--vb-flour);
  min-height: 100dvh;
}

/* ---- the worktop: one board for every route (IMG_4730) ----
   The whole frame, ungraded: tools/build_board_states.py rotates, resizes to 3840px (LANCZOS)
   and saves WebP q86, and nothing else. Only its partner worktop-ring.webp is corrected, and
   only so it agrees with this frame — see that script for the numbers.
   No scrim and no filter, by direction. The old scrim+brightness(.62) pipeline also had a hard
   ceiling of ~luma 80 for any plate, which is why every earlier board looked muted regardless
   of grade. Small-copy insurance on this ground is the text-shadow rule below. */
.page-vb::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: url("/images/site/worktop.webp") center / cover no-repeat;
}
/* ---- the board's second state: the loaf lifted, its flour ring left behind ----
   Two whole photographs of one locked-off setup, cross-faded. Nothing is cut, so the bread
   never gains an invented edge; the board changes state under the content instead.
   Sits above .page-vb::before (loaf present) and below .page-vb::after (grain), so the grain
   rides over whichever state shows. Opacity only: a transform here would make this a
   containing block for the fixed chrome. */
.vb-board-ring {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: url("/images/site/worktop-ring.webp") center / cover no-repeat;
  opacity: 0;
  transition: opacity 1.15s var(--vbe-inout);
  /* Keep it composited. Without this Chrome discards the layer's rasterised tiles at opacity 0
     and re-rasters the whole board the moment the fade starts: 370-400ms main-thread freeze on
     every out-transition, against 6.4ms with it. The one-off decode is warmed on idle in
     index.js; board size is the other half, see tools/build_board_states.py. */
  will-change: opacity;
}
.board-lifted .vb-board-ring { opacity: 1; }
/* ---- arriving already lifted: the base board carries the lifted photo too ----
   An arrival is not a cross-fade, and two layers is what flashed. The overlay paints nothing
   until its 3840x2880 image decodes, and ::before is on <body> while .vb-board-ring is a div
   further down it — so the base paints the loaf first and holds the screen alone for the whole
   decode. Pointing the base at the same photo means only one image can paint.
   This covers the return home too. It read `:not(.board-return)` on the reasoning that the
   return fades the overlay OUT to reveal the base, so the base had to stay the loaf — but the
   reveal is safe because releaseBoard (index.js) drops .board-instant, forces a reflow, and only
   then drops .board-lifted: the base is already the loaf before the fade starts, and the overlay
   is still opaque while it swaps. Excluding the return left it with the same hard cut the rule
   exists to kill — measured at ~100ms on a screen recording and 300ms with the ring stalled. */
.page-vb.board-lifted.board-instant::before {
  background-image: url("/images/site/worktop-ring.webp");
}
/* A router navigation is over in 460ms, so the landing's own 1.15s cross-fade is far too slow
   here: vb-router sets .board-lifted on the click, and at the standard duration the ring was
   still at 0.08 by 203ms — the old page slid away with its loaf still showing. html.vbp-lock is
   present for exactly the duration of a router navigation. */
html.vbp-lock .vb-board-ring { transition-duration: .42s; }
/* One frame only, when the landing replays the return (index.js): the ring must ARRIVE at
   opacity 1, not transition up to it. Otherwise adding .board-lifted at boot starts a 1.15s
   fade in that the removal immediately cancels, so nothing animates. Suppress, paint,
   re-enable, then drop the class. */
.board-instant .vb-board-ring { transition: none; }

/* ---- desktop reframes the plate so the nav row stops landing on flour ----
   The one real legibility failure left in the matrix: `.vb-nav a` is #fefefe and at wide
   landscape aspects the band behind it is a flour drift, measured 1.41 at 2560x1000, 1.46 at
   1680x720, 1.70 at 1920x900 and 2.97 at 2560x1251 against a 4.5 floor. The note this rule
   replaces recorded the obvious answer — a permanent gradient behind the header, 1.41 -> 10.36 —
   and rejected it on looks, correctly: a band is the one thing this world does not do.
   It does not need one. In landscape `cover` scales the plate to WIDTH, so a 3840x2880 frame
   renders 1920px tall into a 1000px viewport and 920px of it is simply choice. Moving that
   choice is free of the composition's geometry, which is derived from the loaf's HORIZONTAL
   position (--vb-copy-edge and .vb-h1's clear band in index.css), not its vertical one.
   Swept, worst nav run across 2560x1251 / 2560x1000 / 1920x1080 / 1920x900 / 1680x720 / 1440x900:
       50% (before)  1.41      40%  2.20      30%  4.71      20%  4.62      10%  4.38      0%  1.85
   30% is the only value that clears every one of them, and it clears them by carrying the drift
   below the chrome rather than hiding it. The loaf lands ~5% lower in frame; nothing else moves.

   POSITION IN THE FILE IS LOAD-BEARING. Both plate rules above set `background` as a SHORTHAND,
   which resets background-position to `center` — so this block has to come after BOTH of them or
   it silently loses on the ring. Shipped once from above `.vb-board-ring` and did exactly that:
   every ::before moved to 30% while the landing's cross-fade overlay stayed at 50%, so the board
   slid under the loaf-to-ring fade and slid back on arrival. Same class of fault the portrait
   framing had, which works only because it happens to sit below both declarations.
   Desktop landscape only: portrait has its own framing below, and under 881 there is no nav row
   to protect. */
@media (min-width: 881px) and (orientation: landscape) {
  .page-vb::before,
  .vb-board-ring { background-position: center 30%; }
}

/* ---- flow pages carry ONE plate, not two ----
   A flow page is always lifted and never cross-fades, so stacking the ring plate over the
   landing plate bought nothing and cost a flash: worktop.webp is warm in cache while the ~1.3MB
   ring plate is still on the wire, giving ~1s of bright board with the loaf. Pointing ::before
   straight at the ring plate leaves nothing underneath to show through, so the page is correct
   from its first painted frame. The ring layer is display:none so the browser never fetches it
   twice. The landing keeps both plates — the cross-fade lives there. */
.page-flow.page-vb::before { background-image: url("/images/site/worktop-ring.webp"); }

/* Portrait is a different photograph and a different file.
   The plate is 4:3 landscape, so `cover` scales it to HEIGHT here and only ~35% of the frame is
   ever on screen — and centred, that slice is the flour ring, landing straight down the single
   content column. Measured worst text contrast on the landing by background-position:
       center  2.51 2.39 2.31 1.36 1.21   at 320 / 360 / 375 / 390 / 430
       28%     3.44 4.16 5.18 6.96 6.01   (what the handoff proposed)
       20%     3.39 5.19 5.53 7.51 7.90   (clears four widths instead of three)
   Rather than shift a 3840px plate, portrait loads a plate cropped to that framing at build
   time: 2160px wide, centred on x=1167 where the 20% slice centres, so plain `center` reproduces
   it exactly. 2160 is 0.75 aspect, the squarest portrait viewport in use (768x1024), so nothing
   upscales. A phone was downloading 2.3 MB of board to show a third of it; this is 1.2 MB.
   Keyed on orientation, not width: a tablet upright has the same problem, a phone on its side
   does not. */
@media (orientation: portrait) {
  .page-vb::before { background-image: url("/images/site/worktop-portrait.webp"); }
  .vb-board-ring { background-image: url("/images/site/worktop-ring-portrait.webp"); }
  /* the two states that point the BASE at the ring plate need the portrait ring too */
  .page-flow.page-vb::before,
  .page-vb.board-lifted.board-instant::before {
    background-image: url("/images/site/worktop-ring-portrait.webp");
  }

  /* ---- the bread returns to the edge ----
     Same plate, slid to its right edge. The portrait file is a crop of the original around
     x=1167 (build_board_states.py), i.e. the 20% framing above, so `100%` here lands the visible
     slice against the crop's own right edge at original x=2247 — far enough right to catch the
     left shoulder of the loaf, which starts at 45.2% of the frame. The bread reads as a subject
     at the edge of the board instead of being cropped out of the phone entirely.
     The 20% framing was chosen when the landing copy sat in the MIDDLE of the frame, which is
     where the flour ring is brightest. v3 drops the composition low (index.css) and the copy no
     longer lives there, so the caution it bought is spent on nothing. Measured before and after
     with tools/check_contrast.py — the numbers are in CHANGELOG 2026-08-13.

     EVERY route, not just the landing, and that is load-bearing. The handoff asked for the
     landing alone with flow pages left on `center`, and shipped that way the board SLID 121.5px
     sideways at 390 the instant the body class flipped to .page-flow: the plate is a fixed
     pseudo-element on <body>, outside the <main> the router swaps, so it survives a navigation
     and any difference between two routes reads as the photograph jumping under the content —
     against the one rule this world is built on, that you never leave the same board. Worse, the
     landing's ring OVERLAY is shifted too, so the board held still through the landing's own
     loaf-to-ring cross-fade and then jumped at the swap, which is where it was reported from.
     One position for every route is the only way the surface stays continuous.

     Portrait only: in landscape the plate is the full 3840 frame whose loaf position the whole
     desktop composition is derived against (--vb-copy-edge and .vb-h1 in index.css both read
     it), so shifting there would walk the headline into the crust.
     The height guard is not decoration. Sliding the plate puts a brighter band under the copy,
     and the remedy on the landing is the drop, which costs vertical slack the shortest screens
     do not have: 320x568 has 32px of it against the 220 the drop wants. So below 620 nothing
     shifts — and because the guard covers every route at once, short screens stay in sync too. */
  @media (min-height: 620px) {
    .page-vb::before,
    .vb-board-ring { background-position: 100% center; }
  }
}
.page-flow .vb-board-ring { display: none; }

/* ---- flow routes: the lights come down ----
   A flat dim between the plates and the grain, opacity 1 on every .page-flow route and 0 on the
   landing, where the reel is the board as shot. Every legibility failure in this project's
   history is light ink on a flour crest (the crest reads ~luma 90), and every fix so far lifted
   the ink — --vb-muted twice, --vb-hair-strong on 2026-09-03. This takes the crest down to ~54
   instead and hands the margin to all of that ink at once, on every flow page. (.28 first,
   judged too light on the owner's screen; .40 the same day.)
   Not a second plate: a darker file would swap under the content on a PJAX arrival, a 1.4 MB
   fetch and the flash class of 2026-08. Not a filter on the fixed layer either ("no scrim and
   no filter, by direction" above was about a gradient band behind chrome on the landing and
   about grading the plate; a flat layer is a per-route exposure, what RING_EXPOSURE already
   is at build time, and costs nothing to composite).
   Z-order is DOM order: this div follows .vb-board-ring in base.html, so at the same z-index it
   paints above both plates and below the ::after grain, and the grain still rides on top.
   No transition by default — a hard load paints its final state, never a 300ms brightening.
   FROM THE CLICK, not from the swap: html.vbp-lock is present for exactly one router
   navigation, every PJAX destination is a flow route (the landing is always a real load), so
   the lock alone is the cue to go dark — the same moment the router sets .board-lifted for the
   ring. Keyed instead on .page-flow it started at ~460ms, after the old page had already left,
   and read as a cut against the reel's 1.15s cross-fade (owner, 2026-09-03). .6s from the
   click spans the exit and settles as the new page enters; the lock outlasts it (inMs 800).
   The landing never carries the lock, so the reel never sees this layer move. Contrast:
   measured with tools/check_contrast.py, numbers in the CHANGELOG. */
.vb-board-dim {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: rgba(0, 0, 0, .40);
  opacity: 0;
  transition: none;
}
.page-flow .vb-board-dim { opacity: 1; }
html.vbp-lock .vb-board-dim { opacity: 1; transition: opacity .6s var(--vbe-inout); }

@media (prefers-reduced-motion: reduce) { .vb-board-ring { transition-duration: .01ms; } }

.page-vb::after {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='190' height='190'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23g)' opacity='0.5'/></svg>");
  background-size: 190px 190px;
  opacity: .05;
  mix-blend-mode: screen;
}
.page-vb main { position: relative; z-index: 1; }

/* ---- legibility over a photographic ground: ONE inherited shadow for the whole world ----
   text-shadow inherits, so declaring it once here reaches everything, fixed chrome and footer
   included. Three layers: the 1px edge is what makes 11px caps hold, the 8px halo darkens the
   ground immediately behind the glyph, the 26px halo kills a flour crest without drawing a box.
   A zero-offset halo does not muddy a heading the way an offset shadow would. */
.page-vb {
  text-shadow:
    0 1px 2px rgba(0, 0, 0, .85),
    0 0 8px rgba(0, 0, 0, .82),
    0 0 26px rgba(0, 0, 0, .6);
}

.page-vb ::selection { background: var(--vb-berry); color: #fff; }
.page-vb :focus-visible { outline: 2px solid var(--vb-gold); outline-offset: 3px; border-radius: 3px; }
/* ...but not on containers that only exist to receive focus programmatically. index.js
   and vb-router move focus to tabindex="-1" containers so screen readers announce
   them; when the move follows a keypress the browser counts it as keyboard focus and rings the
   whole box (measured: a 1440x706 gold outline across the landing). tabindex="-1" is never a
   control the user aimed at, so there is no affordance to preserve. */
.page-vb [tabindex="-1"]:focus,
.page-vb [tabindex="-1"]:focus-visible { outline: none; }
/* the shared img{display:block} would defeat the hidden attribute (UA rules lose to author rules) */
.page-vb [hidden] { display: none !important; }
.page-vb .svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.page-vb .sr-only {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ===================== fixed chrome — header ===================== */

.vb-head {
  position: fixed; inset: 0 0 auto; z-index: 50;
  padding: calc(clamp(12px, 2.2vh, 20px) + env(safe-area-inset-top, 0px)) clamp(20px, 4vw, 56px) clamp(9px, 1.6vh, 14px);
}
/* No scrolled veil: the chrome never changes state, it sits on the worktop always.
   A permanent gradient behind the landing header takes the nav from 1.41:1 to 10.36:1 at
   2560x1000 and was rejected on looks. The legibility problem it addressed is still open;
   see tools/check_contrast.py for the numbers. Whatever fixes it is not a band. */

.vb-head__bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 18px; }

/* Empty since the status link went (2026-08-15) — kept as a grid item so the bar's
   `1fr auto 1fr` auto-placement still lands the lockup in the centre track. */
.vb-head__util { display: flex; }

.vb-lockup { display: inline-flex; align-items: center; gap: 16px; justify-self: center; }
/* the tallest thing in the header, so it rides the same vh curve as --vb-head-h */
.vb-lockup__badge { width: clamp(46px, 6.7vh, 60px); height: clamp(46px, 6.7vh, 60px); flex: none; }
.vb-lockup__word { display: grid; justify-items: center; gap: 5px; }
/* nowrap on both, and load-bearing rather than cosmetic: .vb-head__bar is `1fr auto 1fr`, so
   once the fr tracks claim their share the centre track collapses to the lockup's min-content —
   at 375px that is the width of "PEKARA", which broke the wordmark and "OD 1990" to four lines
   and made the header 109px against the 96px --vb-head-h reserves below 880px. */
.vb-lockup__name {
  font-family: 'Anton', var(--font-display); font-size: var(--t-dsp-m); letter-spacing: .1em;
  color: var(--vb-gold); line-height: 1; white-space: nowrap;
}
.vb-lockup__since {
  font-size: var(--t-micro); font-weight: 600; letter-spacing: .34em; color: var(--vb-flour-dim);
  line-height: 1; padding-left: .34em; white-space: nowrap;
}

.vb-head__tools { display: flex; justify-content: flex-end; align-items: center; gap: 16px; }
/* Language switch: on the RIGHT of the bar, sized like a nav item rather than a micro-cap —
   it is the one control a foreign visitor needs to find without hunting. 44px press box. */
.vb-lang {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; margin-block: -10px;
  font-size: 14px; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
  color: var(--vb-flour); transition: color .15s ease;
}
.vb-lang:hover { color: var(--vb-gold); }
.vb-tool { display: inline-flex; padding: 8px; color: var(--vb-flour); transition: color .15s ease; }
.vb-tool:hover { color: var(--vb-gold); }
.page-vb .nav-burger { display: none; width: auto; height: auto; border: 0; background: transparent; flex-direction: column; gap: 5px; cursor: pointer; }
.page-vb .nav-burger span { display: block; width: 22px; height: 2px; border-radius: 2px; background: var(--vb-flour); transition: transform .25s ease, opacity .2s ease; }

.vb-nav { display: flex; justify-content: center; gap: clamp(26px, 3vw, 44px); margin-top: clamp(10px, 2vh, 18px); }
.vb-nav a {
  font-size: var(--t-fine); font-weight: 600; letter-spacing: .26em; color: var(--vb-flour);
  padding-bottom: 5px;
  transition: color .15s ease;
}
.vb-nav a:hover { color: var(--vb-gold); }
.vb-nav a.is-active { color: var(--vb-gold); }
.vb-nav__order { color: var(--vb-gold-link); }

/* mobile overlay */
.nav-overlay {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 28px;
  padding: 32px;
  background: rgba(16, 17, 20, .97);
  opacity: 0; visibility: hidden; transition: opacity .25s ease, visibility .25s ease;
}
.nav-overlay.is-open { opacity: 1; visibility: visible; }
.overlay-close {
  position: absolute; top: 22px; right: 22px;
  width: 48px; height: 48px; display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; cursor: pointer;
  color: var(--vb-flour);
}
.overlay-links { display: flex; flex-direction: column; align-items: center; gap: 18px; }
.overlay-links a {
  font-family: 'Anton', var(--font-display); font-weight: 400;
  font-size: clamp(30px, 9vw, 44px); letter-spacing: .03em; text-transform: uppercase;
  color: var(--vb-flour); transition: color .15s ease;
}
.overlay-links a:hover, .overlay-links a.is-active { color: var(--vb-gold); }
/* (.overlay-links__util, the utility link's lower rank inside the overlay, went with the
   status link on 2026-08-15; proven unmatched by tools/check_dead_css.py, removed 2026-09-18.) */
.overlay-cta {
  margin-top: 6px; width: min(360px, 84vw); text-align: center;
  padding: 16px 24px; border-radius: 100px;
  background: var(--vb-berry); color: #fff; font-weight: 700; font-size: var(--t-body);
  transition: background .18s ease;
}
.overlay-cta:hover { background: var(--vb-berry-hi); }
html.menu-open, html.menu-open body { overflow: hidden; }

/* ===================== footer =====================
   One footer for every route: the landing's one-line credit. The tall sitemap/trust block flow
   pages used to carry broke the illusion of never leaving the same board, and gave a
   single-screen page something to scroll. */

.vb-foot {
  position: fixed; inset: auto 0 0; z-index: 40;
  padding: clamp(14px, 2.6vh, 26px) clamp(20px, 4vw, 56px) clamp(10px, 1.7vh, 16px);
  text-align: center; pointer-events: none;
  /* No scrim. The dark gradient that was here suited the landing plate (bottom strip luma 25.7
     down to 17.4), but the ring plate — the board on every other route — has a bottom strip at
     luma 4.4, so the same gradient ADDED light (4.4 up to 15.7) and cold, reading as a glossy
     grey pane with a visible top edge. Unscrimmed, --vb-muted measures 9.66:1 on the ring plate
     and 8.22:1 on the loaf plate, both clear of AAA, and the inherited text-shadow carries the
     rest. If a future plate ever needs help, the fix is a scrim darker than that plate, not a
     fixed rgba tuned to one exposure. */
}
.vb-foot p { font-size: var(--t-fine); color: var(--vb-muted); letter-spacing: .02em; margin: 0; }
.vb-foot a { pointer-events: auto; }

/* ===================== shared grammar: buttons, links, prices ===================== */

.vb-pill {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px 24px; border: 0; border-radius: 100px; cursor: pointer;
  font-family: var(--font-body); font-size: var(--t-small); font-weight: 700; letter-spacing: .02em;
  color: #fff; background: var(--vb-berry);
  box-shadow: 0 10px 24px -12px rgba(163, 74, 86, .8);
  transition: background .18s ease, transform .18s var(--vb-ease), box-shadow .18s ease;
}
.vb-pill:hover { background: var(--vb-berry-hi); transform: translateY(-1px); box-shadow: 0 14px 28px -12px rgba(178, 85, 98, .85); }
.vb-pill:active { transform: translateY(0); }
.vb-pill--lg { padding: 15px 34px; font-size: var(--t-body); }

.vb-link {
  display: inline-block; padding: 0 0 6px; border: 0; background: transparent; cursor: pointer;
  font-family: var(--font-body); font-size: var(--t-body); font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--vb-gold-link);
  background-image: linear-gradient(var(--vb-gold-link), var(--vb-gold-link));
  background-repeat: no-repeat; background-position: 0 100%; background-size: 100% 2px;
  transition: color .18s ease, background-size .25s var(--vbe-inout);
}
.vb-link:hover { color: var(--vb-gold); background-size: 38% 2px; }
/* the display variant: the landing's hero CTA, the one link standing alone in an empty
   composition rather than inside a column of copy. Finer rule because at this size a 2px
   underline reads as a bar. A modifier, so index.css cannot make .vb-link mean two things. */
.vb-link--display {
  font-size: var(--t-lead); letter-spacing: .22em;
  color: var(--vb-gold);
  background-image: linear-gradient(var(--vb-gold), var(--vb-gold));
  background-size: 100% 1.3px;
}
.vb-link--display:hover { background-size: 38% 1.3px; }

/* the price lockup: number in the display face, € as its own small glyph (reel cards, the
   detail's --lg variant; /ponuda's rows have their own quieter .pn-price) */
.vb-price { display: inline-flex; align-items: baseline; gap: 5px; color: var(--vb-gold); }
.vb-price i { font-style: normal; font-size: var(--t-small); font-weight: 600; }
.vb-price, .vb-price b { font-family: var(--font-display); font-weight: 700; font-size: var(--t-dsp-m); letter-spacing: -.01em; }
.vb-price--lg, .vb-price--lg b { font-size: var(--t-dsp-l); }

/* ---- the anchor price ("sidrena cijena"), Odluka NN 101/2026 (1212), from 2026-10-01 ----
   Wherever a retail price is displayed, the regular price on the reference date has to stand
   next to it, legibly. One rule for every surface: the reel card (under the lockup, inside
   .vb-price-stack so .vb-price keeps its own box for the morph traveler), the detail's --lg
   lockup, and /ponuda's rows. Fine-print size, flour-dim label, the amount in flour: the
   obligation is legibility, not prominence — the retail price stays the loud one. Never
   uppercase: "SIDRENA CIJENA" would read as a tag, and this is a fact, not a label. */
.vb-price-stack { display: grid; gap: 3px; align-content: center; }
.vb-sidrena {
  display: block; font-size: var(--t-fine); line-height: 1.25; letter-spacing: .01em;
  color: var(--vb-flour-dim); white-space: nowrap;
}
.vb-sidrena b { font-weight: 700; color: var(--vb-flour); }
.vb-sidrena--lg { margin-top: 8px; font-size: var(--t-small); }

.vb-arrow { display: inline-flex; }

/* ===================== flow pages: ONE rule, uniform =====================
   Every flow page is as tall as its content, with a one-screen minimum: content that fits gives
   exactly one screen with the composition centred and no scrollbar, content that does not fit
   grows the page. The previous model clipped to one screen and lost two pekarnice on /lokacije
   (and, while it existed, the order form's whole product list) at 1440x900.
   The footer joins the flow and the body is a column, so it sits at the bottom of the viewport
   on short pages and at the end of the document on long ones. */

.page-flow { display: flex; flex-direction: column; min-height: 100dvh; }
.page-flow main {
  flex: 1 0 auto;
  min-height: 0;
  padding-top: var(--vb-head-h); padding-bottom: clamp(12px, 2.6vh, 40px);
  display: grid; align-items: center;
}
.page-flow .vb-foot { position: static; flex: none; pointer-events: auto; }

.page-flow h1 {
  font-family: 'Anton', var(--font-display); font-weight: 400;
  /* 1.08, not 1.02: every flow headline is Croatian and Č/Š/Ž carons collide with the line above.
     Cap 69px, not 74: .vb-cols freezes at max-width 1300 (~1444px viewport), past which 4.8vw
     kept growing the type while its column got narrower. 7.5vh, not 8.4: the vh term governs on
     short windows, and at 8.4 a two-line headline alone claimed 127px of a 700px screen. */
  font-size: clamp(38px, min(4.8vw, 7.5vh), 69px); line-height: 1.08; letter-spacing: .012em;
  text-transform: uppercase;
  color: var(--vb-gold); text-wrap: balance; margin: 0 0 clamp(8px, 1.5vh, 14px);
  margin-left: -.055em;   /* optical: cancel Anton's left side bearing */
}
.page-flow h2 {
  font-family: 'Anton', var(--font-display); font-weight: 400;
  font-size: clamp(28px, 2.6vw, 40px); line-height: 1.06; letter-spacing: .014em;
  text-transform: uppercase;
  color: var(--vb-gold); text-wrap: balance; margin: 0 0 12px;
}
/* Vertical rhythm is vh-aware on purpose: the compositions have to fit, and a fixed 40px gap
   that suits a 1080px screen is 40px of overflow on an 800px one. */
.page-flow .vb-lead {
  font-size: var(--t-lead); line-height: 1.72; font-weight: 500; color: var(--vb-flour);
  max-width: 46ch; margin: 0 0 clamp(10px, 2vh, 34px);
}
/* the Anton subhead under a page h1: form titles, sent-state titles, step heads */
.page-flow .vb-sub {
  font-family: 'Anton', var(--font-display); font-weight: 400;
  font-size: clamp(26px, 2.4vw, 38px); line-height: 1.12; letter-spacing: .014em;
  text-transform: uppercase; color: var(--vb-gold);
  margin: 0 0 4px; margin-left: -.045em;
}

/* ===================== form baseline: underlines on the board =====================
   The board-native form language, defined once: no container, the worktop shows through.
   Pages do not restyle inputs. (select, number and date inputs left the selector lists on
   2026-09-18: they belonged to the order form, which went 2026-09-17, and matched nothing.) */
.page-flow input[type="text"], .page-flow input[type="tel"], .page-flow input[type="email"],
.page-flow textarea {
  background: transparent;
  border: 0; border-bottom: 1px solid var(--vb-hair-strong);
  border-radius: 0;
  color: var(--vb-flour);
  font-family: var(--font-body);
  font-size: 16px;   /* the ONE off-scale size, on purpose: iOS zooms the viewport below 16 */
  padding: 7px 2px;
  transition: border-color .18s ease;
}
.page-flow textarea {
  resize: vertical; min-height: 2.9em; line-height: 1.45; overflow: hidden;
}
.page-flow input::placeholder, .page-flow textarea::placeholder { color: var(--vb-muted); }
.page-flow input:hover, .page-flow textarea:hover { border-bottom-color: rgba(254, 254, 254, .72); }
.page-flow input:focus, .page-flow textarea:focus {
  outline: 0; border-bottom-color: var(--vb-gold); border-bottom-width: 2px; padding-bottom: 6px;
}
/* 2 + 4 = --vb-ring-reach. If either number changes, change the token. */
.page-flow input:focus-visible, .page-flow textarea:focus-visible {
  outline: 2px solid var(--vb-gold); outline-offset: 4px; border-radius: 3px;
}
.page-flow .is-invalid { border-bottom-color: var(--vb-berry-hi); }

/* One field label: gold at 11px. Dim gold-link micro-caps disappear over the photographic ground. */
.page-flow label, .page-flow .vb-flabel {
  font-size: var(--t-micro); font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  color: var(--vb-gold);
}
.page-flow .req { color: var(--vb-gold); }
.page-flow .opt { color: var(--vb-flour-dim); font-weight: 400; letter-spacing: .06em; }
.page-flow .vb-err { display: block; font-size: var(--t-fine); color: var(--vb-error); letter-spacing: 0; text-transform: none; font-weight: 400; }

/* ===================== v3 primitives: stage, pane, scroll, rules =====================
   The shared ones; a page may position them, not restyle them.
   (.vb-col, the single-column sibling of .vb-cols, matched nothing on any route — the
   scrolling documents use their own .pn wrapper — and went 2026-09-18.) */

/* the two-column flow stage (/veleprodaja, /kontakt, /lokacije). Columns share a top edge:
   centring them independently left the second column floating at an unrelated height. */
.vb-cols {
  width: 100%; max-width: 1300px; margin-inline: auto;
  padding-inline: clamp(22px, 5vw, 72px);
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(36px, 5vw, 92px);
  align-items: start;
}
/* A column that stretches to the height of its row, nothing more. It used to carry
   `height: calc(100dvh - head - foot)`, which double-counted: main already reserves the header
   in its padding-top and the footer is a flow sibling, so the document ran ~25px past the screen
   and every flow page scrolled by a sliver. Height comes from .page-flow alone. */
.vb-cols--tall { align-self: stretch; min-height: 0; align-items: stretch; }
.vb-cols--tall > * { min-width: 0; min-height: 0; }

/* An inner scroll region, for a pane that genuinely should scroll inside a fixed-height column
   rather than lengthen the page. Rarely needed under the uniform rule above — a long list simply
   makes its page longer. */
.vb-scroll {
  min-height: 0; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-color: rgba(254, 254, 254, .22) transparent;
}
.vb-scroll::-webkit-scrollbar { width: 6px; }
.vb-scroll::-webkit-scrollbar-thumb { background: rgba(254, 254, 254, .22); border-radius: 100px; }
.vb-scroll::-webkit-scrollbar-track { background: transparent; }

/* The panel is a TOKEN, not a class. Content sits on the worktop with no container (CHANGELOG);
   the exception is a surface you can select, because a control needs a body to click. Reach for
   --vb-pane-bg and compose the border locally. */

/* hairline-ruled list: the board-native alternative to a stack of cards */
.vb-rows { list-style: none; margin: 0; padding: 0; }
.vb-rows > * { border-bottom: 1px solid var(--vb-hair-soft); }
.vb-rows > *:first-child { border-top: 1px solid var(--vb-hair-soft); }

/* the shared small gold caps link (five hand-rolled copies preceded it, at 11.5-13px) */
.vb-tlink {
  display: inline-flex; align-items: center; gap: 8px;
  border: 0; background: transparent; padding: 0; cursor: pointer;
  font-size: var(--t-fine); font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: var(--vb-gold-link); transition: color .15s ease;
}
.vb-tlink:hover { color: var(--vb-gold); }

/* the inline text link: a link inside running copy (the footer credit's privacy link).
   Gold-link like every link in this world; underlined because inside a sentence colour alone
   does not read as a link. Added 2026-09-03 with the privacy link. */
.vb-inline-link {
  color: var(--vb-gold-link); text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--vb-gold-line); transition: color .15s ease;
}
.vb-inline-link:hover { color: var(--vb-gold); }

/* the round check that marks a completed thing (a sent form) */
.vb-check {
  display: grid; place-items: center; width: 50px; height: 50px; border-radius: 50%;
  background: var(--vb-gold); color: #2a1508; margin-bottom: 18px;
}

/* NARROW: the stage relaxes into a column and every inner scroll pane hands its scrolling back
   to the page (nested scrolling on a phone is a trap). */
@media (max-width: 980px) {
  .vb-cols {
    grid-template-columns: 1fr; gap: clamp(28px, 5vw, 44px);
    align-items: start; padding-block: clamp(20px, 4vh, 40px) 0;
  }
  .vb-cols--tall { min-height: 0; align-self: start; align-items: start; }
}

/* SHORT but wide: the opposite response, and it must stay out of the max-width query above.
   Stacking a two-column composition makes it far taller exactly where vertical room is
   scarcest — /veleprodaja at 1366x700 goes from 624px two-up to 1088px stacked, against 469px
   available, which drove the fit-to-screen script in site.js to its .72 floor and gave shrunken
   type AND 314px of scroll. Two columns is what saves a short screen; it spends width instead
   of height, so a short window keeps its columns and pays with gutters and chrome. */
@media (max-height: 720px) and (min-width: 981px) {
  .vb-cols { gap: clamp(24px, 3.2vw, 60px); }
}

/* ===================== responsive chrome ===================== */

@media (max-width: 1080px) {
  /* same curve, lower ceiling — a fixed 132px would cancel the vh clamp on every short
     tablet-width window */
  .page-vb { --vb-head-h: calc(clamp(112px, 15.5vh, 132px) + env(safe-area-inset-top, 0px)); }
  .vb-head__bar { grid-template-columns: 1fr auto 1fr; }
}

/* Everything below is width-independent in principle and bounded at 979px in practice: the
   desktop composition is frozen, and a query that cannot reach 980px is the only place a
   correction may land. Promote these upward whenever that freeze lifts. */
@media (max-width: 979px) {
  /* the home indicator: nothing bottom-anchored may sit under it. Inert until base.html's
     viewport meta carries viewport-fit=cover, since env() resolves to 0 without it. */
  .vb-foot { padding-bottom: calc(clamp(10px, 1.7vh, 16px) + env(safe-area-inset-bottom)); }
  .nav-overlay { padding-top: env(safe-area-inset-top, 0px); padding-bottom: calc(32px + env(safe-area-inset-bottom)); }

  /* A 2px gold ring is the same value as a flour crest and disappears into one. The halo is a
     focus state, not a scrim: it exists only while tabbing, and on the dark plate it is
     invisible because it matches the board. */
  .page-vb { --vb-focus-halo: 0 0 0 6px rgba(0, 0, 0, .6), 0 0 0 9px rgba(0, 0, 0, .3); }
  .page-vb :focus-visible { box-shadow: var(--vb-focus-halo); }
  /* the one control with a resting shadow; restate it or focus deletes the lift */
  .page-vb .vb-pill:focus-visible { box-shadow: var(--vb-focus-halo), 0 10px 24px -12px rgba(163, 74, 86, .8); }
  /* same reason the outline is suppressed on these: a programmatic focus target is not a
     control, and unsuppressed the halo rings the whole of <main> */
  .page-vb [tabindex="-1"]:focus,
  .page-vb [tabindex="-1"]:focus-visible { box-shadow: none; }
}

@media (max-width: 880px) {
  /* 64, not 80. The bar has always PAINTED 64 here — 12px padding either side of a 40px lockup
     row — so the old reserve handed 16px of every phone's content belt to empty board. 80 was
     honest only while .vb-lockup__since was in the chrome, where it wants a second row; with
     the sub-line out (below) the reserve can match the box. The line is not deleted, it moves
     to the bottom of the landing frame — .vb-frame-foot in index.css.
     Deliberately NOT a chrome that hides on scroll: that is JS, another piece of state and one
     more contract with the router, and this project already carries fitFlow as a bet of that
     kind. 64px is paid once, in CSS, and holds on every route. */
  .page-vb { --vb-head-h: calc(64px + env(safe-area-inset-top, 0px)); }
  /* ---- the chrome does not follow the scroll on a phone ----
     It carries no scrim by direction, so on any route long enough to scroll the lockup sat on
     whatever passed beneath it — reported from /veleprodaja, where "RAČUNI I UGOVOR" and its lead
     ran straight through "PEKARA ODRA". A band behind it was rejected on looks, and the reference
     it came from is a desktop composition where the chrome never meets body copy.
     `absolute`, not `static`: the header leaves the viewport with the page but stays OUT of flow,
     so --vb-head-h keeps reserving its room exactly as before and no height model moves — the
     landing is still one screen, .page-flow still centres inside the same box. (The old order
     form used this mechanism from the day it was written; it is now simply the phone rule.)
     The overlay is a separate fixed element, so the burger is reachable from the top of any page
     and the menu itself still covers the screen. */
  .vb-head { position: absolute; }
  /* The sub-line leaves the chrome at 880, not 560. It is what makes the lockup too wide to sit
     beside the burger, and losing it is what buys the 64px bar: badge 40 + gap 12 + Anton caps
     measures 179.7px, so even a 360px screen keeps 320 - 179.7 - 44 = 96px of air between the
     wordmark and the burger. "Sisak · od 1990" is the only claim to trust the page makes, and
     it matters most on the phone — so it is re-hung, not dropped. */
  .vb-lockup__since { display: none; }
  .vb-nav { display: none; }
  /* below this the overlay carries every link */
  .vb-head__util { display: none; }
  .page-vb .nav-burger { display: inline-flex; }
  .vb-head__bar { grid-template-columns: 1fr auto 1fr; }
  /* the hidden utility nav stops being a grid item, so both survivors are placed by hand
     (rows too — sparse auto-placement never backs up a row) */
  .vb-lockup { grid-column: 2; grid-row: 1; }
  .vb-head__tools { grid-column: 3; grid-row: 1; }
  /* the nav row is gone here, so the bar sits in a symmetric band rather than the taller
     asymmetric one that makes room for it */
  .vb-head { padding-block: calc(12px + env(safe-area-inset-top, 0px)) 12px; }
  .vb-tool { flex: none; }
  /* (`.page-vb .vb-tool svg { max-width: none }` lived here to undo the global img/svg cap for a
     toolbar glyph. The only .vb-tool left is the burger and it is drawn with three spans — the
     cart glyph it was written for went when the cart did. Proven unmatched on every public route
     and state by tools/check_dead_css.py.) */
  /* the lockup steps back down where the band is only 64px tall; the 40px badge would take the
     lockup's hit box under 44 with it, so it keeps its own floor the way the burger does */
  .vb-lockup { gap: 12px; min-height: 44px; margin-block: -2px; }
  .vb-lockup__badge { width: 40px; height: 40px; }
  .vb-lockup__name { font-size: var(--t-dsp-s); }
  /* a 44px hit box around a 22x16 glyph: the negative margins hold the bars on the gutter and
     keep the badge, not the button, setting the height of the band */
  .page-vb .nav-burger {
    width: 44px; height: 44px; padding: 0;
    align-items: center; justify-content: center;
    margin: -6px -11px -6px 0;
  }
}

/* ---- the language switch changes sides on true phones ----
   One 44px control per side, or the lockup is not centred: with lang AND burger both in the
   right track its min-content (44 + 16 + 44 = 104px) outgrows the fr share a phone can give
   (85px at 390), the track freezes wide and the lockup slides left — measured 169 against the
   195 centre by shoot_mobile's lockup check. Mirroring 104px on the left instead would
   overflow a 360px screen (179.7px lockup + 2x104 > 320). So the tools wrapper dissolves and
   the two controls are placed by hand: lang left, burger right, symmetric by construction.
   Bounded at 560, not 880: above ~430px the fr share fits both controls and the lockup stays
   centred with the lang beside the burger — and on the 561-880 tablet band the lang MUST stay
   right, because the board's top-LEFT corner is a pale streak there ("EN" measured 4.05:1 at
   768x1024 and 4.22:1 at 845x1129 against the 4.5 floor; the top-right corner is dark). */
@media (max-width: 560px) {
  .vb-head__tools { display: contents; }
  .vb-lockup { grid-column: 2; grid-row: 1; }
  /* -11px start margin mirrors the burger's -11px end margin (880 block): both 44px boxes
     hang 11px past the gutter so the INK, not the press box, aligns with the page edge */
  .vb-lang { grid-column: 1; grid-row: 1; justify-self: start; margin-inline-start: -11px; }
  .page-vb .nav-burger { grid-column: 3; grid-row: 1; justify-self: end; }
}

/* (The sub-line used to be dropped at 560 too. It now leaves the chrome at 880 — see the
   .vb-lockup__since rule in that block.) */

/* ===================== cross-document navigation =====================
   No @view-transition opt-in here, deliberately. vb-router owns flow ⇄ flow, so the only
   navigations left for it to dress were arrivals at the landing — and there it fought the
   stage: the browser holds rendering of the new page until the transition is ready, while
   index.js has already started its reel entrance on rAF against suppressed frames, so the
   entrance appeared mid-flight and jumped. Hard loads simply render, which is correct. */

/* ---- PJAX swap choreography (vb-router.js): live-DOM navigation, One-Surface ----
   The old <main> accelerates out left; the new content enters as live layers — the whole main
   glides in with the long cascade tail while its h1 rides an extra offset released a beat later,
   and site.js reveals stagger the sections beneath. Transformed mains extend the scrollable
   area, so the router locks horizontal overflow for the duration of the swap. */
html.vbp-lock, html.vbp-lock body { overflow-x: hidden; }
.page-vb main.vbp-out { transform: translateX(-72vw); transition: transform .45s var(--vbe-in); }
.page-vb main.vbp-pre { transform: translateX(72vw); transition: none; }
/* `transition: transform 0s`, NOT `none`: this rule only needs the headline to jump to its start
   offset, and `none` wiped every transition on the element — including the scroll-reveal's
   `translate`, which then snapped instead of rising. Disable only the property this rule owns. */
.page-vb main.vbp-pre h1 { transform: translateX(22vw); transition: transform 0s; }
/* 110vw below 880, not 72: 72vw clears a desktop because content is narrower than the
   window, but a phone's content IS the window — the old page parked with its right 28%
   clipped at the left edge for the whole 460ms exit (reported from a real iPhone as
   "leftover words on the left half, broken, then it settles"). Same clearance figure the
   reel's tx-swap uses, for the same reason. The entrance start mirrors it so the incoming
   page also starts fully off-glass. */
@media (max-width: 880px) {
  .page-vb main.vbp-out { transform: translateX(-110vw); }
  .page-vb main.vbp-pre { transform: translateX(110vw); }
}
.page-vb main.vbp-in { transform: none; transition: transform .65s var(--vbe-cascade); }
/* `translate` is listed as well as `transform`: these two rules replace the h1's transition
   outright, and the scroll-reveal animates `translate`. With transform alone the headline's
   reveal had nothing to run on — it arrived sideways with zero vertical travel while the copy
   beneath it rose. Keep both if either is retuned. */
.page-vb main.vbp-in h1 {
  transform: none;
  transition: transform .72s var(--vbe-cascade) .05s, translate .2s var(--vb-ease);
}

/* ===================== reduced motion ===================== */

@media (prefers-reduced-motion: reduce) {
  .page-vb *, .page-vb *::before, .page-vb *::after {
    /* Name the properties. With transition-property left at `all`, a near-zero duration still
       gives every element a transition on everything — transform included — so a value cleared
       one statement earlier has not resolved when the next line measures it. That produced the
       fitFlow oscillation: getBoundingClientRect returned 515.82 for a 548px box, ~120 times in
       9s. site.js reads offsetHeight now, which is correct regardless, but this was the cause. */
    transition-property: opacity, transform, translate, background-color, color, border-color !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
    animation-duration: .01ms !important;
  }
  /* view-transition pseudos hang off the html root, outside .page-vb's reach */
  ::view-transition-group(*), ::view-transition-image-pair(*),
  ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}


/* ===================== touch targets (phones and tablets) =====================
   44px minimum on anything pressable. Grown with padding plus an equal negative margin, so the
   press box expands and the ink stays exactly where the composition puts it.
   Not applied to links inside prose: WCAG exempts inline text links, and padding one would make
   it overlap the lines above and below. The /kontakt honeypot is left alone on purpose. */
@media (max-width: 979.98px) {
  .page-vb input:not([type="checkbox"]):not([type="radio"]),
  .page-vb select,
  .page-vb textarea { min-height: 44px; }
  /* the tertiary "Radije telefonski?" / "Sve lokacije" links read as one line of caps; only the
     hit box grows */
  .page-vb .vb-tlink { display: inline-flex; align-items: center; min-height: 44px; }
  .page-vb .vb-pill { min-height: 44px; }
  /* .vb-link cannot take min-height: it paints its underline as a background-image, and growing
     the box detaches the rule from the caps. Padding plus an equal negative margin instead — the
     press box grows, the ink and the underline stay put — with a content-box origin so the
     underline follows the text and `calc(100% + 6px)` restoring the 6px the base rule got from
     its own padding-bottom. Covers the landing CTA in both orientations: a phone held sideways
     has the same fingers. Measured before this: the landing CTA 28px in landscape. */
  .page-vb .vb-link {
    padding-block: 13px; margin-block: -13px;
    background-origin: content-box; background-position: 0 calc(100% + 6px);
  }
}
