/* Self-hosted, subset to Latin + Cyrillic only. Both SIL Open Font License.
   unicode-range means a Bulgarian visitor pulls only the Cyrillic cuts
   (~57 KB total); the Latin files load only if Latin glyphs appear.
   No third-party requests, so no CDN dependency and nothing to leak. */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;               /* never hide text waiting on a font */
  src: url('assets/fonts/inter-400-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;       /* cyrillic */
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;               /* never hide text waiting on a font */
  src: url('assets/fonts/inter-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;       /* latin */
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;               /* never hide text waiting on a font */
  src: url('assets/fonts/inter-600-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;       /* cyrillic */
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;               /* never hide text waiting on a font */
  src: url('assets/fonts/inter-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;       /* latin */
}

@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 500;
  font-display: swap;               /* never hide text waiting on a font */
  src: url('assets/fonts/playfair-display-500-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;       /* cyrillic */
}

@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 500;
  font-display: swap;               /* never hide text waiting on a font */
  src: url('assets/fonts/playfair-display-500-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;       /* latin */
}
/* Italic 400, for the rotated gallery captions and nothing else. 400 is the
   LIGHTEST weight Playfair Display has — its variable axis starts there — so
   the airier reference look at weight 250 is not reachable with this typeface
   at all, only approached. 34kB for both subsets. */
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400;
  font-display: swap;               /* never hide text waiting on a font */
  src: url('assets/fonts/playfair-display-400-italic-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;       /* cyrillic */
}
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400;
  font-display: swap;               /* never hide text waiting on a font */
  src: url('assets/fonts/playfair-display-400-italic-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;       /* latin */
}

/* IB Technologies — wedding arch rental
   Same five colours the film gave us — dusk navy, bulb amber, sage tulle,
   honey pine — with cream promoted from the text to the ground.

   The old scheme was justified as "sampled from the film", which validated it
   against itself and never against the category. Near-black plus amber is the
   language of premium consumer electronics and agency portfolios, not weddings,
   and it fought the photographs: every gallery shot is daylight, so each one
   sat on a near-black page with a hard luminance step at its edge.
   Navy is now the ink and the heavy structural element. The film and the footer
   stay dark on purpose — two deliberate bands framing a light page.

   The hexagon is the product, so it is also the visual grammar — nav mark,
   callout dots, tier flags, dividers. */

:root {
  --ink:        #0B1524;
  --ink-2:      #101E30;
  --ink-3:      #17293F;
  --cream:      #F4EFE7;
  --cream-dim:  #C9C4BC;
  --amber:      #E9A94F;
  --amber-soft: #F5C87E;
  --sage:       #9DB0A0;
  --honey:      #C08343;
  /* Hero overlays over the film: ink warmed 20% toward honey pine. Cold navy
     read as the old scheme even though the film itself stays dark. */
  --film-veil:  47, 43, 42;

  /* ---- values the light scheme needs and the dark one never did ----
     Three paper tints, because the dark scheme had three grounds (page,
     alternating band, card) and --cream-dim is a text colour, far too dark
     to serve as a band. */
  --paper:      #F4EFE7;
  --paper-2:    #EEE7DC;
  --paper-3:    #E6DCCD;

  /* Secondary text: ink at 70% over paper. 6.45:1 on the ground and 5.44:1 on
     paper-3, so it clears AA body on every surface it can land on. 62% looked
     better and failed at 4.13:1 on cards. */
  --ink-soft:   #51565F;
  /* The dark scheme had a THIRD text step, between cream and cream-dim, used
     for ledes and long prose. It was a raw hex rather than a token, so the
     inversion swept past it and those paragraphs came out nearly invisible.
     Ink at 85% is its mirror: 10.67:1 here against 12.43:1 there. */
  --text-soft:  #2E3641;

  /* Amber cannot be read as TEXT on cream — #E9A94F is 1.79:1 there. This is
     the same hue burnt down to 6.21:1, for the asterisk and link hovers. */
  --amber-ink:  #7C4E0D;
  /* Raw sage is 2.00:1 on cream, so labels use it darkened to 6.18:1. */
  --sage-deep:  #4D5B5C;

  --line:       rgba(11, 21, 36, 0.16);

  /* Height of the CONTRACTED masthead bar. The header is fixed, so anything
     that wants to fill the viewport has to subtract this or run underneath it.
     Measured at 66px; :target/section scroll-margin uses a looser 7rem because
     a jump only needs to clear the bar, not sit flush against it. */
  --mh-bar:     4.125rem;

  /* Surfaces by JOB rather than by shade, so a scope can flip its ground and
     its text together in one place — see the dark-island rule below. */
  --surface:    var(--paper);
  --surface-2:  var(--paper-2);
  --surface-3:  var(--paper-3);

  /* Palette above is named by appearance. Roles below are named by JOB, so
     the call-to-action and the decorative accent can diverge later — they
     are the same amber today, but --amber was previously doing both jobs at
     once, which meant restyling the CTA silently restyled every annotation
     line, tier flag and bullet too. */
  --color-primary:      var(--amber);      /* CTA fill — one job only */
  --color-primary-hover:var(--amber-soft);
  --color-on-primary:   #1A1206;
  /* An amber fill is only 1.79:1 against cream, so on a light ground the CTA
     is found by its EDGE, not by its brightness. Against navy it was 8.94:1
     and needed no help. */
  --color-primary-edge: var(--ink);
  /* THE ONE WARM POINT RULE — amber is reserved for things you can act on,
     the brand mark, and unfinished-work warnings. It appeared on nine
     elements, so the CTA competed with eight decorations for the same
     warmth. Decoration is the ink; section labels are sage. */
  --color-mark:         var(--amber);      /* brand hexagon only */
  --color-warning:      var(--amber-ink);  /* [TOKEN] chips, required marks */
  --color-accent:       var(--ink);        /* decoration: rules, dots, flags */
  --color-on-accent:    var(--paper);      /* text sitting ON an accent fill */
  --color-eyebrow:      var(--sage-deep);
  --color-text:         var(--ink);
  --color-text-soft:    var(--text-soft);
  --color-text-dim:     var(--ink-soft);
  --color-focus:        var(--ink);
  --color-link-hover:   var(--amber-ink);
  /* Ghost buttons and hairline insets, as an alpha of whatever the ink is. */
  --edge:       rgba(11, 21, 36, .32);
  --edge-wash:  rgba(11, 21, 36, .06);

  /* z-index scale. Only four layers exist; naming them stops the fifth
     being invented as "9999". */
  --z-below:  -1;
  --z-story:   3;
  --z-annots:  4;
  --z-nav:    40;
  --z-skip:  100;

  /* Playfair Display: a high-contrast display serif with real Cyrillic —
     Georgia is a 1993 screen face built for 72dpi legibility, and on a page
     whose pitch is "this is beautiful" it looked exactly as chosen as it was.
     Inter is exceptional at 13–17px and covers Cyrillic properly. */
  --serif: 'Playfair Display', Georgia, "Times New Roman", serif;
  --sans: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --pad: clamp(1.25rem, 4vw, 4rem);

  /* Icon shapes used through mask-image, where an inline <svg> cannot go.
     Single-quoted attributes and %23 for the hash, so the whole thing stays a
     legal unquoted url() token. */
  --ico-phone: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 3h3l2 5-2 1.5a11 11 0 0 0 5.5 5.5L16 13l5 2v3a2 2 0 0 1-2 2A16 16 0 0 1 4 5a2 2 0 0 1 2-2z'/%3E%3C/svg%3E");
  --measure: 34rem;

  /* Type scale — 8 steps.
     The page previously rendered 14 distinct sizes, seven of them crammed
     between 12.8px and 15.2px. A 0.3px difference is not a hierarchy, it
     just reads as drift, so those seven collapse into --fs-sm and --fs-xs. */
  --fs-display: clamp(2.4rem, 6vw, 3.75rem);
  --fs-section: clamp(1.9rem, 4.5vw, 2.5rem);
  --fs-card:    1.5rem;
  --fs-lede:    1.1875rem;   /* 19px */
  --fs-body:    1.0625rem;   /* 17px */
  --fs-sm:      0.9375rem;   /* 15px */
  --fs-xs:      0.8125rem;   /* 13px */
  --fs-eyebrow: 0.8125rem;   /* 13px, was 11.2 — under the 12px floor */

  /* Weight carries hierarchy alongside size. Previously everything was 400
     except the eyebrows, so size was doing all the work alone. */
  --fw-display: 500;
  --fw-heading: 500;
  --fw-label:   600;

  /* Cyrillic has descenders Latin does not (р у ф ц щ). 1.06 risked
     collisions on wrapped headings; 1.12 clears them without going loose. */
  --lh-display: 1.12;
  --lh-body:    1.6;

  /* 0.3em tracking on uppercase Cyrillic dissolves word shapes — the caps
     are more rectangular and uniform than Latin, so they lose their outline
     faster. 0.14em is about the practical ceiling. */
  --track-eyebrow: 0.14em;
}

/* ---------- dark islands ----------
   A light page with one deliberate dark band — the film — plus the lightbox
   overlay, which dims by definition. Re-pointing the role tokens
   here is the whole switch — every child reads the roles, not the palette, so
   nothing inside these needs to know which scheme it is in.
   This is what the earlier split of colour-by-appearance from colour-by-job
   bought: the inversion is a value change, not a rewrite. */
.hero, .lb {
  /* Re-pointing the custom properties is NOT enough on its own. `body` resolves
     `color: var(--color-text)` to ink once, and descendants inherit that
     computed colour — not the variable — so a scope that only swaps the token
     reaches nothing that does not explicitly re-read it. The hero titles
     inherited ink and went invisible against the film.
     Setting `color` here re-resolves it inside the scope and inherits down. */
  color: var(--color-text);
  --surface:            var(--ink);
  --surface-2:          var(--ink-2);
  --surface-3:          var(--ink-3);
  --line:               rgba(244, 239, 231, .14);
  --color-accent:       var(--cream);
  --color-on-accent:    var(--ink);
  --color-eyebrow:      var(--sage);
  --color-text:         var(--cream);
  --color-text-soft:    var(--color-text-soft);
  --color-text-dim:     var(--cream-dim);
  --color-focus:        var(--cream);
  --color-link-hover:   var(--amber);
  --color-warning:      var(--amber);
  /* On navy the CTA already separates at 8.94:1, so it needs no edge. */
  --color-primary-edge: transparent;
  --edge:               var(--edge);
  --edge-wash:          var(--edge-wash);
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--surface);
  color: var(--color-text);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;

  /* Cyrillic letterform consistency.
     This originally fixed a real mismatch: the system sans on macOS/iOS
     substituted Bulgarian localized glyphs (locl — д renders g-like, т
     m-like) while the Georgia serif used for headings had none, so headings
     and body disagreed. With Playfair + Inter self-hosted that is solved
     structurally instead — measured against both, neither Google Fonts
     subset responds to a locl toggle, so both render the same Cyrillic.

     The declaration stays as a guard: if either face is ever swapped for one
     that does carry BG alternates, this keeps the page consistent rather
     than letting one role silently diverge. Features not listed keep their
     defaults. To adopt authentic Bulgarian forms, remove this line and use
     faces that support them in BOTH roles — not just one. */
  font-feature-settings: "locl" 0;

  /* No overflow-x:hidden here. On body it propagates to the viewport and,
     combined with the sticky stage + negative-margin story, leaves the
     document with no usable scroll height until a forced reflow. Anything
     that would overflow sideways is clipped at its own container instead. */
}

img, video { max-width: 100%; display: block; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: var(--z-skip);
  background: var(--color-primary); color: var(--color-on-primary); padding: .75rem 1rem;
}
.skip:focus { left: 0; }

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

/* ---------- buttons ---------- */
.btn {
  /* inline-flex + min-height so the tap area clears 44px regardless of the
     font size inside. Padding alone gave 42px, which fails the minimum. */
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: .75rem 1.6rem;
  background: var(--color-primary); color: var(--color-on-primary);
  box-shadow: inset 0 0 0 1px var(--color-primary-edge);
  font: var(--fw-label) var(--fs-sm)/1 var(--sans); letter-spacing: .02em;
  text-decoration: none; border: 0; cursor: pointer;
  clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
  transition: background .2s ease, transform .2s ease;
}
.btn:hover { background: var(--color-primary-hover); transform: translateY(-1px); }
.btn--ghost {
  background: var(--edge-wash); color: var(--color-text);
  box-shadow: inset 0 0 0 1px var(--edge);
}
.btn--ghost:hover { background: var(--edge-wash); }

/* ---------- masthead ----------
   A field guide opens on a title page. The wordmark sits at display scale
   over a hairline rule with the sections as a small-caps row beneath, then
   CONTRACTS to a slim bar once the visitor leaves the film. Params baked
   from the accepted variant: masthead scale 1.6, hairline rule on. */
.mh {
  position: fixed; inset: 0 0 auto 0; z-index: var(--z-nav);
  display: grid; justify-items: center;
  padding: .65rem var(--pad);
  /* A near-solid light bar rather than the old gradient that faded into the
     film. The masthead is fixed and starts life over the dark hero, so a
     translucent light wash would leave navy text muddy exactly where the page
     is most cinematic. Solid reads as a deliberate frame around the film, and
     it needs no state switch as the header crosses the dark/light boundary. */
  background: rgba(244, 239, 231, .94);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}


.mh__row {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: clamp(.25rem, 1.4vw, 1.1rem);
}
.mh__group {
  display: flex; align-items: center;
  gap: clamp(.25rem, 1.4vw, 1.1rem);
}

/* Once there is room, the number is pinned to the header's right edge while the
   nav and CTA stay optically centred: equal flexible columns either side of the
   group.
   Full width, NOT capped to .mh__rule's 62rem. Matching the rule looked tidier
   in the abstract but on a wide screen it parked the number ~800px short of the
   edge, which is not what "anchored right" means.
   `1fr`, not `minmax(0, 1fr)` — the flexible columns must not shrink below
   their content, or the number would be clipped at awkward widths.
   1000px, not the 861px nav breakpoint: between those two the number is wider
   than the free space either side, so anchoring it shoves the nav up to 22px
   off centre. In that band the row stays a centred flex line and the number
   simply follows Запази. */
@media (min-width: 1000px) {
  .mh__row {
    display: grid; width: 100%;
    grid-template-columns: 1fr auto 1fr;
  }
  .mh__group { grid-column: 2; }
  .mh__chan--call { grid-column: 3; justify-self: end; }
}

.mh__nav { display: flex; align-items: center; gap: .1rem; }
.mh__nav > a {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 .72rem;      /* 44px tap target, not text height */
  color: var(--color-text-dim); text-decoration: none;
  font: var(--fw-label) var(--fs-eyebrow)/1 var(--sans);
  letter-spacing: var(--track-eyebrow); text-transform: uppercase;
  border-bottom: 2px solid transparent;
  transition: color .2s ease, border-color .2s ease;
}
.mh__nav > a:hover { color: var(--color-text); }
/* Active section: colour AND an underline, never hue alone. */
.mh__nav > a[aria-current="true"] {
  color: var(--color-text);
  border-bottom-color: var(--color-mark);
}

.mh__cta {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; margin-left: .4rem; padding: .5rem 1.15rem;
  background: var(--color-primary); color: var(--color-on-primary);
  box-shadow: inset 0 0 0 1px var(--color-primary-edge);
  font: var(--fw-label) var(--fs-eyebrow)/1 var(--sans);
  letter-spacing: .06em; text-transform: uppercase; text-decoration: none;
  clip-path: polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
  transition: background .2s ease, transform .2s ease;
}
.mh__cta:hover { background: var(--color-primary-hover); transform: translateY(-1px); }

/* Header contact channels. Ghost, never filled: the header spends its one warm
   point on Запази, and a second amber element would leave neither reading as
   the primary action. */
.mh__chan {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  min-height: 44px; min-width: 44px; padding: 0 .6rem;   /* full target even icon-only */
  color: var(--color-text); text-decoration: none;
  font: var(--fw-label) var(--fs-sm)/1 var(--sans);
  box-shadow: inset 0 0 0 1px var(--edge);
  clip-path: polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
  transition: background .2s ease;
}
.mh__chan svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.7; flex: none; }
.mh__chan:hover { background: var(--edge-wash); }
.mh__chan.is-unset { opacity: .38; cursor: not-allowed; }
.mh__num { white-space: nowrap; }

/* Below the nav breakpoint the row is already tight enough that three of the
   four section links are hidden, so the number collapses to its icon. Hidden
   visually, NOT removed — display:none would strip it from the accessible name
   and leave the link announcing nothing useful. */
@media (max-width: 860px) {
  .mh__num {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .mh__chan { padding: 0; }
}

/* The masthead contracts as the title page scrolls away. Guarded twice: no
   scroll-timeline support, or reduced motion, leaves a static masthead —
   never a header that is mid-animation and therefore illegible. */

/* The masthead is fixed, so anything jumped to must clear it. Sized against
   the CONTRACTED bar (~106px), which is what is on screen by the time any
   in-page jump completes. Lost once already by living in the old nav block —
   it belongs with whatever header is current. */
:target,
section[id] { scroll-margin-top: 7rem; }

@media (max-width: 860px) {
  /* A display wordmark plus a rule is too much bar on a phone: at 375px it
     took 106px of an 812px screen before the content started. */

  /* Keep the two highest-intent destinations; the rest are a scroll away. */
  .mh__nav > a:nth-child(1),
  .mh__nav > a:nth-child(3),
  .mh__nav > a:nth-child(4) { display: none; }
}

/* ================= HERO ================= */
.hero { position: relative; background: var(--surface); }

.hero__stage {
  position: sticky; top: 0;
  height: 100dvh; width: 100%;
  overflow: hidden;
  background: var(--surface);
}

.hero__media { position: absolute; inset: 0; }
.hero__media > picture,
.hero__poster {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 50%;
}
/* The canvas carries the sequence and sits over the poster, which stays
   visible until the first frame is drawn — so the stage is never blank.
   No transition: the poster and frame one are the same image, so there is
   nothing to fade, and a transition begun in a hidden tab freezes part-way. */
.hero__canvas {
  position: absolute; inset: 0;
  opacity: 0;
}
[data-hero][data-seq-painted="true"] .hero__canvas { opacity: 1; }

/* Copy sits over a bright green lawn for the first half of the film, so it
   needs its own ground. Stronger on the left, where the chapters live. */
/* Gentle vignette only — the copy carries its own ground, so the film does
   not have to be dimmed globally to stay readable. */
.hero__scrim {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(100deg, rgba(11,21,36,.45) 0%, rgba(11,21,36,.18) 40%, transparent 70%),
    linear-gradient(to bottom, rgba(11,21,36,.5) 0%, transparent 20%, transparent 76%, rgba(11,21,36,.5) 100%);
}

.hero__hint {
  position: absolute; left: 50%; bottom: 2rem; transform: translateX(-50%);
  font: var(--fw-label) var(--fs-eyebrow)/1 var(--sans); letter-spacing: var(--track-eyebrow); text-transform: uppercase;
  color: var(--color-text-dim); opacity: .8; transition: opacity .4s ease;
}
.hero__hint::after {
  content: ""; display: block; width: 1px; height: 34px; margin: .7rem auto 0;
  background: linear-gradient(to bottom, var(--color-text-dim), transparent);
}
.hero.has-scrolled .hero__hint { opacity: 0; }

/* pointer-events:none so the story never swallows clicks meant for the
   stage (including the ?debug=1 coordinate picker). */
.hero__story { position: relative; margin-top: -100dvh; z-index: var(--z-story); pointer-events: none; }

/* The band is taller than the pin, so the pin STICKS for most of the band
   instead of spending it sliding up from the bottom edge. Without the extra
   30dvh the copy is half off-screen through every transition. */
.chapter { position: relative; min-height: 130dvh; }

.chapter__pin {
  position: sticky; top: 0;
  min-height: 100dvh;
  display: flex; align-items: center;
  padding: clamp(5rem, 9vw, 8rem) var(--pad);
}

.chapter__copy {
  position: relative;
  width: min(38rem, 52vw);
  pointer-events: auto;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .45s ease, transform .45s ease;
}
.chapter.is-lit .chapter__copy { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .chapter__copy { opacity: 1; transform: none; transition: none; }
}

/* Local ground under the copy — the film is bright green for its first half
   and text needs somewhere to sit that does not dim the whole frame. */
.chapter__copy::before {
  content: ""; position: absolute; z-index: var(--z-below); inset: -3rem -5rem;
  /* Centred, unsized, fading out at 78% — well inside the box, which is what
     keeps it a soft splash. Do NOT give the ellipse an explicit size or an
     off-centre position: at 115%/38% it reached the element edge while still
     opaque and clipped into a hard rectangle. */
  background: radial-gradient(ellipse at center,
    rgba(var(--film-veil),.9) 0%, rgba(var(--film-veil),.62) 52%, transparent 78%);
  pointer-events: none;
}

@media (max-width: 860px) {
  /* At 375px the copy column is ~335px wide, which put the 19px lede at 33
     characters per line — under the 35–60 readable range. Dropping the lede
     to body size brings it to ~41. */
  :root { --fs-lede: var(--fs-body); }

  .chapter__pin { align-items: flex-end; padding-bottom: calc(4rem + env(safe-area-inset-bottom)); }
  .chapter__copy { width: min(100%, 36rem); }
  /* The horizontal bleed is the page gutter, never a guessed value. At -1.5rem
     against a --pad that clamps to 1.25rem on a 390px screen, this box ran to
     x=394 and gave the whole document 4px of horizontal scroll — invisible when
     you go looking, because a pseudo-element is not an element. Tied to --pad
     it reaches the screen edge exactly and can never pass it. */
  .chapter__copy::before {
    inset: -2.5rem calc(-1 * var(--pad));
    background: linear-gradient(to top, rgba(var(--film-veil),1) 0%, rgba(var(--film-veil),.82) 58%, transparent 100%);
  }
}

.chapter__title {
  margin: 0 0 .9rem;
  font-family: var(--serif); font-weight: var(--fw-display);
  font-size: var(--fs-display); line-height: var(--lh-display);
  letter-spacing: -.015em;
  text-wrap: balance;
  text-shadow: 0 2px 24px rgba(11,21,36,.75);
}
.chapter__body {
  margin: 0; max-width: var(--measure);
  font-size: var(--fs-lede); color: var(--color-text);
  text-shadow: 0 1px 14px rgba(11,21,36,.8);
}
.chapter__actions { margin: 2rem 0 0; }
/* The still-hero CTA. Off by default; the still-hero block below turns it on. */
.chapter__actions--still { display: none; }

/* ================= THE STILL HERO =================
 * No film on a touch device, but the FIRST FRAME survives perfectly well —
 * which is the whole reason this works.
 *
 * The film is a 16:9 frame set. Cover-cropped into a portrait viewport it
 * shows about a quarter of its own width: measured, x 0.370..0.630 on a
 * 390x844 screen. Frame one is the widest shot in the trimmed film, and the
 * arch sits inside that band with room to spare — both feet visible, the
 * hexagon reading clearly. What does NOT survive is everything after it. The
 * camera pushes in, and by mid-film the arch's widest corners are measured at
 * x 0.353 and 0.656, outside the crop on both sides. The callouts go with
 * them: their anchors live in that same 16:9 space, so all but `geometry`
 * resolve off-screen and leave leaders pointing past the edge of the phone.
 *
 * So the phone keeps the one frame that reads and drops the rest: frame one
 * (a plain <img> in the markup), the opening copy, and the CTA. main.js never
 * builds the sequence here, so nothing else under assets/seq/ is requested —
 * 123 frames and ~9.3 MB that a phone was paying for and could not see.
 *
 * Chapters two through five are dropped rather than restacked as plain copy.
 * Each one narrates a moment of the film, and every FACT in them is already
 * carried further down the page — the width by the spec table, the footing by
 * #stands, the tulle and the delivery by the package tiers. Restacked they
 * would only say all of it a second time, with no film left to caption.
 *
 * THE QUERY MUST STAY IN STEP WITH TWO OTHER PLACES: the `matchMedia` call in
 * main.js and the <source media> on the hero <picture>. All three decide the
 * same thing. If the script drops the film where this block does not collapse
 * the hero, the page is left with four chapters of copy pinned over a still
 * that never moves — and, because `is-lit` is what reveals them and only the
 * scroll loop sets it, they sit at opacity 0 forever. Hence the coarse-pointer
 * arm: a tablet is past 860px but still has no film worth scrubbing.        */
@media (hover: none) and (pointer: coarse), (max-width: 860px) {
  .chapter:not([data-chapter="arch"]) { display: none; }

  /* One chapter at exactly one viewport, against a sticky stage of the same
     height and the story's -100dvh pull, leaves the hero exactly 100dvh tall.
     The stage is then left with no scroll room to stick through, which is what
     makes a sticky element behave as a static one — no extra rule needed. */
  .chapter[data-chapter="arch"] { min-height: 100dvh; }

  /* Without this the copy waits on an `is-lit` that never arrives. */
  .chapter__copy { opacity: 1; transform: none; transition: none; }

  .chapter__actions--still { display: block; }

  /* The cue would be pointing at the page's own scrollbar. */
  .hero__hint { display: none; }

  /* ---- Let the veil do the work, not the shadow ----
   * The copy was grounded twice over: a veil beneath it AND a heavy drop
   * shadow on every glyph. On the moving film that shadow earns its keep,
   * because whatever a letter sits on changes frame to frame. Against ONE
   * still it is redundant, and a 24px blur at .75 offset 2px down stops
   * reading as a lift and starts reading as a halo welded to the letterforms.
   * So it is cut to a whisper and the veil carries the contrast — as one even
   * scrim, not a ramp into a slab.
   *
   * Measured against the worst ground each block actually sits on — a sky gap
   * behind the title at rgb(221,232,236), sunlit grass behind the body at
   * rgb(174,177,111) — .50 of this veil is what carries the title to 3:1 and
   * the body to 4.5:1. Both. The previous ramp ran .82 to 1.0, roughly twice
   * what the measurement asks for, which is why it stopped reading as a scrim
   * and started reading as a filled panel with a photograph above it.
   *
   * So: .58 flat across the whole text band, with margin over the .50 floor,
   * and no stop anywhere near opaque — 42% of the photograph still comes
   * through everywhere the copy sits. It fades out above the title rather than
   * across it, which is what the old ramp got wrong: .82 straight to
   * transparent left the largest type on the page with the least ground under
   * it, measured at 2.37:1.
   *
   * The bottom no longer stops short. It used to end 2.5rem under the copy,
   * and once the pin's bottom padding grew to clear the floating bar that left
   * 94px of bare photograph below a hard horizontal edge — the odd strip. It
   * now runs to the hero's own bottom edge, easing to .25 so it lands rather
   * than stops. The bottom offset is set with the pin's padding, in the block
   * that owns that measurement. */
  .chapter__copy::before {
    inset: -5.5rem calc(-1 * var(--pad)) -2.5rem;
    background: linear-gradient(to top,
      rgba(var(--film-veil), .25) 0%,
      rgba(var(--film-veil), .58) 18%,
      rgba(var(--film-veil), .58) 86%,
      transparent 100%);
  }
  .chapter__title { text-shadow: 0 1px 14px rgba(11, 21, 36, .32); }
  .chapter__body  { text-shadow: 0 1px 10px rgba(11, 21, 36, .34); }
}

/* ---------- annotation callouts ---------- */
.annots { position: absolute; inset: 0; z-index: var(--z-annots); pointer-events: none; }
.annots__svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Hairline, and deliberately below full strength: the leader's job is to be
   followed, not looked at. `round` on the join keeps the elbow from cutting a
   notch out of itself at this weight. */
.annots__line {
  fill: none;
  stroke: rgba(244,239,231,.62); stroke-width: 1;
  stroke-linecap: round; stroke-linejoin: round;
}
/* The reticle: ring lighter than its core, so the core is what the eye lands on
   and the ring only draws the boundary around what is being pointed at. */
.annots__ring { fill: none; stroke: rgba(244,239,231,.55); stroke-width: 1; }
.annots__dot { fill: var(--color-accent); }
/* Dimension rule: heavier than the leader line so the measurement reads as
   the measurement, not as more pointer. */
.annots__rule { stroke: rgba(244,239,231,.85); stroke-width: 1.75; stroke-linecap: round; }

/* A glass pill, on one line. Two lines and a chamfered box read as a plaque
   laid over the film; a single-line capsule reads as a tag attached to the
   thing its leader touches, which is what a callout is.

   The fill is a scrim, not a colour — it exists only so cream text clears its
   own background, and the film stays visible through it. It cannot go much
   lighter than this: the labels sit at 93% of the width, where the film is
   sunlit grass and pine as often as it is shadow. */
.annots__label {
  position: absolute; top: 0; left: 0;
  display: flex; align-items: center; gap: .55rem;
  padding: .44rem .95rem .48rem;
  border-radius: 999px;
  background: rgba(var(--film-veil),.34);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  backdrop-filter: blur(14px) saturate(130%);
  /* Inset, so the hairline sits inside the radius rather than growing the box
     — the leader starts at that edge and a real border would shift it by 1px.
     The outer shadow is what separates the pill from bright grass. */
  box-shadow:
    inset 0 0 0 1px rgba(244,239,231,.28),
    0 8px 26px rgba(11,21,36,.28);
  white-space: nowrap;
  will-change: transform, opacity;
}
.annots__key {
  font: var(--fw-label) var(--fs-xs)/1.25 var(--sans); letter-spacing: var(--track-eyebrow);
  text-transform: uppercase; color: rgba(244,239,231,.72);
}
/* The divider does the work the old stacked layout did — it separates the
   category from the fact without a second line. */
.annots__value {
  padding-left: .55rem;
  border-left: 1px solid rgba(244,239,231,.22);
  font: var(--fw-label) var(--fs-sm)/1.25 var(--sans); color: var(--color-text);
}

.annots--debug .annots__readout {
  position: absolute; left: 1rem; bottom: 1rem;
  padding: .5rem .75rem; background: #000; color: #0F0;
  font: 500 .8rem/1 ui-monospace, monospace;
}
.annots--debug { pointer-events: auto; }

@media (max-width: 860px) {
  /* Leader lines to a moving anchor are unreadable at phone width; the same
     facts are one scroll away in the spec table. */
  .annots { display: none; }
}

/* ================= SECTIONS ================= */
.section { padding: clamp(4rem, 10vw, 8rem) var(--pad); }
.section:nth-of-type(even) { background: var(--surface-2); }

.section__head { max-width: 46rem; margin: 0 0 clamp(2rem, 5vw, 3.5rem); }
.section__title {
  margin: 0 0 1rem; font-family: var(--serif); font-weight: var(--fw-heading);
  font-size: var(--fs-section); line-height: var(--lh-display); letter-spacing: -.01em;
}
.section__lede { margin: 0; max-width: var(--measure); font-size: var(--fs-lede); color: var(--color-text-soft); }

/* A head whose lede has moved elsewhere needs a bottom edge, or the title
   floats over a gap with nothing tying it to what follows. Full content width,
   not the head's own 46rem, because it is dividing the section and not the
   paragraph. */
.section__head--rule {
  max-width: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: clamp(1.5rem, 4vw, 2.75rem);
}
.section__head--rule .section__title { margin-bottom: 0; }

/* Two weights and two styles inside one title, the way the reference site sets
   "OUR collections" — the italic word carries the voice, the roman word carries
   the noun. 400 italic against 500 roman: the italic is genuinely the lighter
   of the two, which is what stops the pair reading as one emphasised phrase.
   The extra word gap is deliberate — at display size a normal space closes up
   and the two treatments start to look like one word set badly. */
.section__title--split i {
  font-style: italic; font-weight: 400;
  margin-right: .18em;
}

/* ---------- how it stands ---------- */
.stands { display: grid; gap: clamp(2rem, 5vw, 3.5rem); }
/* Two spacers were stacking under the rule: the head's own bottom margin AND
   the grid gap, which measured 100px of empty ground between the line and the
   photo. The gap is the one that belongs to a grid, so the margin goes. */
.stands .section__head { margin-bottom: 0; }
.stands__figure { margin: 0; }
.stands__figure img { width: 100%; height: auto; }

@keyframes stand-in {
  from { opacity: 0; transform: translateY(1.5rem); }
  to   { opacity: 1; transform: none; }
}
/* Guarded twice, and the default state is VISIBLE: without scroll-timeline
   support, or with reduced motion, the items simply sit there. A reveal that
   fails closed would hide the section's whole argument.

   `cover`, not `entry`. The `entry` phase spans only the subject's OWN height,
   so on a 51px list item the entire fade ran over 26px of scroll — running
   correctly and completely imperceptibly. `cover` spans the scrollport plus the
   subject (771px here), so 5%->25% gives ~154px of travel.
   It still finishes low on the screen — at cover 25% the item has risen only a
   quarter of the way up — so nothing is still fading while you read it. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .stands__list li {
      animation: stand-in linear both;
      animation-timeline: view();
      animation-range: cover 5% cover 25%;
    }
  }
}
.stands__figure figcaption {
  margin-top: .7rem; font-size: var(--fs-xs); color: var(--color-text-dim);
}
/* A numbered spec index rather than a bulleted list. Both label and value sat
   at 15px — caption type doing section-level work beside a 911px photo. The
   editorial baseline is ~24px for this rank of heading and 17px for body, and
   the index numbers give the column a spine the hexagon bullets never did.
   Counters, so the numbering survives adding or removing a row. */
.stands__list { list-style: none; margin: 0; padding: 0; counter-reset: stand; }
.stands__list li {
  counter-increment: stand;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "num label" "num value";
  column-gap: clamp(1rem, 2vw, 1.9rem);
  row-gap: .35rem;
  padding: clamp(1.35rem, 2.6vw, 2.25rem) 0;
  border-top: 1px solid var(--line);
}
.stands__list li:last-child { border-bottom: 1px solid var(--line); }
.stands__list li::before {
  content: counter(stand, decimal-leading-zero);
  grid-area: num;
  font: var(--fw-heading) clamp(1.5rem, 2.4vw, 2.25rem)/1 var(--serif);
  font-variant-numeric: tabular-nums;
  /* Dimmed: an index should be findable, not competitive with the label. */
  color: var(--color-text-dim);
}
.stands__list strong {
  grid-area: label;
  font-family: var(--serif); font-weight: var(--fw-heading);
  font-size: clamp(1.25rem, 1.7vw, 1.5rem); line-height: 1.25;
  letter-spacing: -.005em;
}
.stands__list span {
  grid-area: value;
  font-size: var(--fs-body); line-height: 1.55; color: var(--color-text-soft);
}

/* The photo is a 1100x1100 square, so its height follows entirely from the
   column width — and a square is ~340px SHORTER than the portrait that used to
   sit here. That cannot be closed by pacing: widening the figure column to grow
   the square also narrows the index beside it, which wraps more and grows
   TALLER, so the void chases itself. The photo goes sticky instead — it holds
   the left column while the six entries pass it, and the leftover height turns
   into the travel rather than into empty ground.

   These queries sit AFTER every base .stands rule on purpose. A media query
   adds no specificity, so when they sat above, the later `gap: 1.1rem` on
   .stands__list silently beat the paced gap and the list came out 443px against
   a 911px photo. Same trap as the header arrow earlier. */
@media (min-width: 1080px) {
  /* The intro takes the SAME two columns as the content beneath it: title left
     over the photo, lede right over the index. Spanning the head across both
     left the lede stranded at its 34rem measure with two-thirds of the row
     empty beside it — a narrow ragged column floating in the middle of the
     section. Now the whole section reads on one spine. */
  /* 36rem, up from 32rem: a square at the old width read as a small inset
     beside a 672px column of type. The index keeps its 42rem measure either
     way, so the extra 4rem comes out of that column's slack, not out of it. */
  .stands { grid-template-columns: minmax(0, 36rem) minmax(0, 1fr);
            grid-template-areas: "title lede" "fig list";
            align-items: start;
            /* Was clamp(2.5rem, 5vw, 4.5rem), which paced a head that carried a
               whole paragraph. A rule and one line of title need far less air
               beneath them before the photo and the index start — this puts
               the line-to-photo distance on the gallery head's ~56px. */
            row-gap: clamp(2rem, 3.6vw, 3.5rem); }
  /* The wrapper is a div with no semantics, so dissolving it lets its two
     children be placed independently without touching the markup. */
  .stands .section__head { display: contents; }
  .stands .section__title { grid-area: title; margin: 0; }
  .stands .section__lede  { grid-area: lede; max-width: 42rem; }
  /* A ruled head cannot be dissolved: `display: contents` removes the box, and
     the border goes with it. So it stays a real element and takes the whole
     first row — title left, rule running the full content width, exactly as the
     gallery head reads. Placed after the rule above because equal specificity
     means source order decides. */
  .stands .section__head--rule { display: block; grid-row: 1; grid-column: 1 / -1; }
  /* A sticky grid item is constrained by its GRID AREA, not by the viewport, so
     the photo can only travel the row's leftover height and stops dead level
     with the last entry — no separate end-point to maintain. 6rem clears the
     66px fixed masthead with a little air under it. */
  .stands__figure { grid-area: fig; position: sticky; top: 6rem; }
  /* Short lines, so a measure rather than the whole remaining column — same
     treatment as .section__head above it. The row padding, not a gap, is what
     paces the six entries down the photo's 911px and puts each far enough from
     the last that it enters the viewport on its own. */
  .stands__list { grid-area: list; max-width: 42rem; }
  /* Only at this width does the list sit DIRECTLY under the head's rule, and
     the first row's own border-top then reads as a second line a gap below the
     first. Dropping it takes the top padding with it — that is what puts the
     first label level with the top of the photo instead of 36px under it.
     Stacked, the photo comes between the two, so the row keeps its edge. */
  .stands__list li:first-child { border-top: 0; padding-top: 0; }
}
/* The 24rem cap here existed because the portrait would otherwise run 1245px
   down a narrow screen. A square at 30rem is only 480px tall, so the cap can
   loosen and the photo stops reading as a thumbnail when stacked. */
@media (max-width: 1079px) {
  .stands__figure { max-width: 30rem; }
}

/* ---------- tiers ---------- */
.tiers { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.tier {
  position: relative; padding: 1.9rem 1.6rem;
  /* One tint step off the page, so a card reads as a card at all. This was
     --surface, which is the SAME value as the ground on an odd section — and
     packages IS an odd section, so all three cards were floating on the page
     with nothing but a 16% hairline between them and it. */
  background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--line);
  display: flex; flex-direction: column;
}
/* On an even section the page is already --surface-2, so cards take the next
   step down to hold the same one-step relationship. */
.section:nth-of-type(even) .tier { background: var(--surface-3); }
/* THE ONE EMPHASIZED CARD RULE — exactly one tier is emphasized, and it moves
   on two channels: one further tint step, and a full-strength accent edge
   against its siblings' 16% hairline. Emphasis is a contrast effect, so
   tinting a second card would cancel both.
   It was previously the accent at 55% and nothing else — a 1px edge sitting
   between the siblings' 16% and this 100%, on a card with no ground of its
   own. Real, but small enough to read as a rendering artifact rather than a
   decision.
   The edge is --color-accent, not raw --ink, so it stays the same colour as
   the flag it pairs with and inverts with it inside a dark scope. */
.tier--featured {
  background: var(--surface-3);
  box-shadow: inset 0 0 0 1px var(--color-accent);
}
/* Only three paper tints exist, so on an even section the ground step above
   collapses (cards and featured card both land on --paper-3) and the ink edge
   plus the flag carry the emphasis alone. Recheck if packages ever moves. */
.tier__flag {
  position: absolute; top: -.65rem; left: 1.6rem;
  margin: 0; padding: .25rem .7rem;
  background: var(--color-accent); color: var(--color-on-accent);
  font: var(--fw-label) var(--fs-xs)/1.4 var(--sans); letter-spacing: .1em; text-transform: uppercase;
}
.tier__name { margin: 0 0 .5rem; font-family: var(--serif); font-weight: var(--fw-heading); font-size: var(--fs-card); }
.tier__price { margin: 0 0 1.2rem; font-size: var(--fs-body); font-variant-numeric: tabular-nums; color: var(--color-text); }
.tier__list { list-style: none; margin: 0 0 1.2rem; padding: 0; display: grid; gap: .55rem; flex: 1; }
.tier__list li { font-size: var(--fs-sm); color: var(--color-text-soft); padding-left: 1.1rem; position: relative; }
.tier__list li::before { content: "·"; position: absolute; left: .25rem; color: var(--sage); }
.tier__note { margin: 0; font-size: var(--fs-xs); color: var(--color-text-dim); font-style: italic; }
/* The card's own way into the form, sitting under a price the visitor has
   just read. It bottom-aligns for free: .tier is a flex column and
   .tier__list takes the slack, so uneven list lengths do not stagger the
   three buttons.
   Ghost on the siblings, solid on the featured card — the emphasis rule
   above works by contrast, and three identical solid buttons would spend
   the contrast that makes „Най-търсено“ read as the recommendation. */
.tier__act { margin: 1.4rem 0 0; }
.tier__act .btn { width: 100%; }
.tiers__foot { margin: 1.75rem 0 0; font-size: var(--fs-sm); color: var(--color-text-dim); }

/* ---------- spec ---------- */
.spec { display: grid; gap: clamp(1.5rem, 4vw, 3rem); }
@media (min-width: 900px) { .spec { grid-template-columns: 1.2fr .8fr; align-items: start; } }
.spec__table {
  width: 100%; border-collapse: collapse; font-size: var(--fs-sm);
  /* Tabular figures so 2,70 / 2,35 align down the column. */
  font-variant-numeric: tabular-nums;
}
.spec__table caption {
  caption-side: top; text-align: left; padding-bottom: .8rem;
  font-size: var(--fs-xs); color: var(--color-text-dim);
}
.spec__table th, .spec__table td {
  text-align: left; padding: .8rem 0; border-bottom: 1px solid var(--line);
  vertical-align: baseline;
}
.spec__table th { font-weight: 400; color: var(--color-text-dim); width: 42%; }
.spec__alt { display: block; font-size: var(--fs-xs); color: var(--color-text-dim); }
.spec__table--pending { opacity: .85; }

/* Section padding stays at the shared rhythm — the gap that reads as too big is
   between the lede and the plates, not above the heading. See the pinned block. */

/* ---------- gallery: the plate run ----------
   A field guide read sideways. Plates share one height and keep their true
   aspect, so the rhythm comes from the photographs rather than from a grid
   imposed on them.

   Base is a real horizontal scroller — swipeable on touch, arrow-key
   scrollable when focused, and correct with no JS and no scroll timelines.
   The pinned scroll-driven run is layered on top of that, never under it. */
.gx { position: relative; }

.gx__pin {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  padding-block: .5rem;
}
.gx__pin:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 4px; }

.gx__track {
  display: flex; align-items: center;
  gap: clamp(.75rem, 1.6vw, 1.5rem);
  /* Right side only. The track already sits inside the section's own --pad, so
     padding both sides applied the page gutter TWICE and started the first
     plate 115px in while every heading above it starts at 58.
     The right side keeps its gutter because that is what stops the last plate
     ending flush against the viewport edge — the gx-run keyframe translates the
     track to `100vw - 100% - var(--pad)`, so the trailing space has to be part
     of the track's own width. */
  /* padding-left must be stated, not omitted: this is a <ul>, and the UA
     default padding-inline-start of 40px reappears the moment the shorthand
     stops covering it. */
  margin: 0; padding-left: 0; padding-right: var(--pad);
  list-style: none;
}

/* The lede as the first column of the track, so it travels out to the left as
   the photographs come in — the reference site's arrangement. It is a real <li>
   of the same list rather than a sibling of it, which is what keeps the run's
   end position correct for free: the keyframe translates by `100% - 100vw`, so
   widening the track by this column is already accounted for.
   stretch, not center: the copy has to start level with the top edge of the
   photographs, and the track centres its items. */
.gx__intro {
  flex: none; align-self: stretch;
  display: flex; flex-direction: column; gap: 1.1rem;
  width: clamp(15rem, 21vw, 20rem);
  padding-right: clamp(1rem, 2.5vw, 2.5rem);
  scroll-snap-align: center;
}
.gx__eyebrow {
  margin: 0;
  font: var(--fw-label) var(--fs-eyebrow)/1.3 var(--sans);
  letter-spacing: var(--track-eyebrow); text-transform: uppercase;
  color: var(--color-eyebrow);
}
.gx__lede { margin: 0; font-size: var(--fs-body); line-height: 1.85; color: var(--color-text-soft); }


.gx__plate { flex: none; scroll-snap-align: center; }
/* The caption is lifted out of flow and onto the photograph, so the figure
   becomes its positioning context. The grid and its .7rem gap are gone with it:
   there is no second row left to space. */
.gx__plate figure { margin: 0; position: relative; isolation: isolate; }
.gx__plate img {
  display: block;
  /* 56vh inside a 100dvh pin left ~181px dead above and below every photo.
     72vh brings that to ~109px, which still clears the 66px fixed masthead. */
  height: clamp(20rem, 72vh, 44rem);
  width: auto; max-width: none;          /* aspect drives the width */
  background: var(--surface-3);
}
/* Scrim. The caption is cream on a photograph, and a photograph has no fixed
   luminance — so the only way to GUARANTEE contrast rather than hope for it is
   to put a known floor underneath.
   Stops are in rem, not %: at one shared height these photos run from 528px
   (1125×1500) to 972px (1684×1219) wide, so a percentage band would cover the
   text on the portraits and fall short of it on the landscapes.
   .74 over a pure white pixel resolves to rgb(77,84,95), and cream on that is
   6.3:1 — the worst case a photograph can produce still clears AA. */
.gx__plate figure::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(to right,
    rgba(11, 21, 36, .74) 0, rgba(11, 21, 36, .74) 3.6rem, rgba(11, 21, 36, 0) 9rem);
}

/* writing-mode, not the reference site's rotate(90deg): the same result for
   Cyrillic, but the caption stays in normal flow, so max-height clamps it and a
   long one wraps to a second column instead of running off the bottom of the
   photograph. Theirs are two-word collection names; ours reach 40 characters
   ("Горна гирлянда · рози и сребристи листа"), which is why rotate() was safe
   for them and is not for us. -lr rather than -rl so a second column grows
   into the image instead of off its left edge.

   Cream is a literal here, not a role token: the ground is a photograph under a
   fixed scrim, not one of the themed surfaces, so it must not follow a scope. */
.gx__plate figcaption {
  position: absolute; z-index: 2;
  top: 1.5rem; left: 1.15rem;
  max-height: calc(100% - 3rem);
  writing-mode: vertical-lr;
  font-family: var(--serif); font-style: italic; font-weight: 400;
  /* Sized off vh because the plate it sits on is sized off vh — a rem-only
     value would be a display statement on a short screen and a footnote on a
     tall one. The ceiling is where the longest caption we have (39 characters)
     still fits one column inside the shortest plate: measured 539px of 656px
     available at 2.4vh. The reference site runs 40–60px here, which it can
     afford because its captions are two-word collection names. */
  font-size: clamp(1.1rem, 2.4vh, 1.9rem);
  line-height: 1.15;
  color: var(--cream);
}

/* Mobile: the lede is lifted out of the track by gallery.js and lands here,
   between the title and the photographs. Centred, because it is now a block
   under a centred rule rather than a column standing beside something.
   Selectors are on the element itself, not on `.gx > .gx__intro`, so they hold
   whichever parent it is currently in — the rules above still size it as a
   track column, and these have to beat them at any source position. */
@media (max-width: 860px) {
  /* The title centres with the copy it now sits above. Left-ranged over a
     centred lede and a centred set of arrows, it was the only thing in the
     section still hanging off the left edge — the reference sets this whole
     head on one axis and it is the axis that makes the arrangement read.
     The rule keeps its full width: it divides the section, and a centred
     heading over a short centred rule is a different, fussier device. */
  #gallery .section__head { text-align: center; }

  .gx__intro {
    width: auto;
    padding: 0 var(--pad) clamp(1.5rem, 4vw, 2.25rem);
    align-items: center; text-align: center;
    gap: .8rem;
  }
  /* The measure still has to hold — centred text that runs the full width of a
     phone is no more readable for being centred. */
  .gx__lede { max-width: var(--measure); }

  /* ---- One card at a time ----
     Sized by height with an automatic width, these plates measured 342px to
     839px against a 350px strip — the two landscape detail shots ran nearly
     two and a half screens across. "Next photograph" is not something a
     visitor can act on at those widths, so the card becomes the fixed thing
     and the photograph fits itself to it.

     82%, so the following card shows at the edge. That peek is the only
     honest signal that the strip continues; dots say how many there are, but
     they do not say the next one is RIGHT THERE. */
  .gx__plate { width: 82%; max-width: 22rem; }

  /* 3:4 is the aspect four of the six photographs already are, so they land
     uncropped and only the two landscape close-ups are cut — which they can
     afford, being close-ups. The full-arch shots keep their whole silhouette.
     Anything cropped here is still one tap from the lightbox, at full frame. */
  /* No `height: 100%` chain down li -> figure -> button here. That exists in
     the desktop run because the pin is `align-items: stretch` and gives the
     plates a definite height to inherit; on mobile the track centres them and
     the height is content-driven, so a percentage resolves against auto and
     collapses the whole card to nothing. The image is the only thing sized,
     and everything above it takes its height from that. */
  .gx__plate img {
    width: 100%; height: auto;
    aspect-ratio: 3 / 4;
    object-fit: cover;
  }

  /* ---- Arrows and dots (built by gallery.js) ---- */
  .gx__nav {
    display: flex; align-items: center; justify-content: center;
    gap: clamp(1rem, 5vw, 2rem);
    padding: 1.5rem var(--pad) 0;
  }
  .gx__arrow {
    flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    /* 48px, not the visual 44: this is a primary control on a touch screen
       and it sits between two other tap targets. */
    width: 48px; height: 48px; padding: 0;
    border: 1px solid var(--line);
    border-radius: 50%;
    background: transparent; color: var(--color-text-soft);
    cursor: pointer;
    transition: color .2s ease, border-color .2s ease, opacity .2s ease;
  }
  .gx__arrow > svg {
    width: 20px; height: 20px;
    fill: none; stroke: currentColor; stroke-width: 1.5;
    stroke-linecap: round; stroke-linejoin: round;
  }
  .gx__arrow:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }
  /* Dimmed, not disabled — see the note in gallery.js. It still focuses, still
     announces, and pressing it at the end is simply a no-op. */
  .gx__arrow[aria-disabled="true"] { opacity: .3; }

  .gx__dots { display: flex; align-items: center; gap: .5rem; }
  .gx__dot {
    /* The dash is drawn by the border so the tap target can be far bigger than
       the mark — 12px of height either side of a 1px rule, which is invisible
       and thumb-sized at once. */
    width: 18px; height: 24px; padding: 0;
    border: 0; border-top: 1px solid var(--line);
    background: none; cursor: pointer;
    background-clip: padding-box;
    transition: border-color .25s ease, width .25s ease;
  }
  .gx__dot.is-on { border-top-color: var(--color-text); width: 26px; }
  .gx__dot:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
}

/* Scroll-driven horizontal run. Enhancement only: it needs view timelines,
   room to be worth it, and a visitor who has not asked for less motion.
   Everything below falls back to the scroller declared above. */
@supports (animation-timeline: view()) {
  @media (min-width: 861px) and (prefers-reduced-motion: no-preference) {
    /* The plates now FILL the pin rather than being centred inside it, so the
       dead space that used to sit above and below them is gone — and with it
       the two compensations that existed only to claw it back (a zeroed head
       margin and a -5vh pull-up on .gx). Their premise no longer holds: there
       is nothing left to pull up into. */
    .gx {
      /* Taller plates mean a wider track; without more scroll to spend, the
         horizontal run would speed from 1.11 to 1.67 px per px of scroll.
         Raised to 340vh for the plate height, to 370vh when the lede moved into
         the track as its first column (+313px of width, 1.245 px per px), and
         again here for full-height plates: the track went from 4329px to 5444px,
         and 480vh is what measures back to 1.10 px per px at 1490×950. */
      height: 480vh;
      view-timeline: --gx-run block;
    }
    .gx__pin {
      position: sticky; top: 0;
      height: 100dvh;
      /* stretch, not center: the photographs run the full height of the pin.
         The top padding is the fixed masthead — it is 66px once contracted,
         which is its state by the time this section is on screen, and without
         clearing it the first photograph slides under the bar. */
      display: flex; align-items: stretch;
      padding: var(--mh-bar) 0 0;
      overflow: hidden;                  /* the page scroll drives it now */
      scroll-snap-type: none;
    }
    /* The height has to be handed down the whole chain — li, figure, button —
       or the img has no definite height to resolve 100% against. */
    .gx__track { align-items: stretch; }
    .gx__plate figure,
    .gx__plate .gx__open { height: 100%; }
    /* width stays auto, so this scales the photograph rather than cropping it:
       the arch is the product and its full silhouette has to survive. Cropping
       is what the reference site does, and it can only afford to because it
       pairs 100dvh with a FIXED container width. */
    .gx__plate img { height: 100%; }
    .gx__track {
      animation: gx-run linear both;
      animation-timeline: --gx-run;
      animation-range: contain 0% contain 100%;
      will-change: transform;
    }
    /* Percentage translate resolves against the track's own width, so the
       run always ends with its last plate flush to the right gutter — no
       measured pixel value, no JS, correct at every viewport width. */
    @keyframes gx-run {
      to { transform: translateX(min(0px, calc(100vw - 100% - var(--pad)))); }
    }
  }
}

/* ---------- lightbox ----------
   Native <dialog>: focus trap, Escape, inert background and ::backdrop all
   come free. Blur is used here for its sanctioned purpose — signalling that
   the surface behind is dismissable — not as decoration. */
.lb {
  /* 100%, not 100vw: `vw` counts the classic scrollbar's gutter, so on a desktop
     browser that reserves one the open lightbox is ~15px wider than the visible
     viewport and the page gains a horizontal scroll under it. The dialog is
     fixed, so 100% already resolves against the viewport — minus the scrollbar. */
  max-width: 100%; max-height: 100dvh;
  width: 100%; height: 100dvh;
  margin: 0; padding: 0; border: 0;
  background: transparent; color: var(--color-text);
  overflow: hidden;
}
.lb::backdrop {
  background: rgba(11, 21, 36, .88);
  backdrop-filter: blur(10px);
}

.lb__figure {
  margin: 0; height: 100%;
  /* minmax(0, 1fr), not 1fr: a bare fr row takes the image's INTRINSIC height
     as its min-content contribution, so a 1500px photo made the row 1500px
     and pushed the caption off-screen. */
  display: grid; grid-template-rows: minmax(0, 1fr) auto;
  /* items, not content: the rows already fill the dialog, so the picture has
     to be centred inside its own row or it hangs from the top. */
  align-items: center; justify-items: center;
  gap: 1rem; padding: clamp(3.5rem, 7vh, 5rem) clamp(1rem, 5vw, 5rem) clamp(1.5rem, 4vh, 3rem);
}
.lb__img {
  min-height: 0;                       /* allow the grid row to shrink it */
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
  background: var(--surface-3);
}
.lb__cap {
  display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap;
  justify-content: center; text-align: center;
  font-size: var(--fs-sm); color: var(--color-text-dim);
}
.lb__count { font-variant-numeric: tabular-nums; color: var(--color-eyebrow); }

/* Chrome stays neutral: amber is the conversion CTA's colour, and three
   amber buttons in a viewer would spend it on navigation. */
.lb__btn {
  position: absolute; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; padding: 0;
  background: rgba(11, 21, 36, .55); color: var(--color-text);
  border: 0; cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(244, 239, 231, .28);
  clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
  transition: background .2s ease, box-shadow .2s ease;
}
.lb__btn:hover { background: rgba(11, 21, 36, .85); box-shadow: inset 0 0 0 1px rgba(244, 239, 231, .5); }
.lb__btn > svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
.lb__close { top: clamp(.75rem, 2vh, 1.25rem); right: clamp(.75rem, 2vw, 1.5rem); }
.lb__prev  { left:  clamp(.75rem, 2vw, 1.5rem); top: 50%; translate: 0 -50%; }
.lb__next  { right: clamp(.75rem, 2vw, 1.5rem); top: 50%; translate: 0 -50%; }

.gx__open {
  display: block; padding: 0; border: 0; background: none;
  cursor: zoom-in; color: inherit;
  overflow: hidden;                    /* the frame the photograph moves inside */
}
.gx__open:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 4px; }

/* Hover choreography, replacing a flat opacity fade. The tilt is the whole
   point: 1.04 alone reads as a zoom, the degree of rotation makes it read as a
   photograph being picked up and looked at.
   The scale is not a taste value. Rotating a box by θ inside a fixed frame
   needs scale ≥ cos θ + (h/w)·sin θ or the corners pull away and show through.
   Our tallest plate is 922×1640, h/w = 1.779, so at 1° that floor is 1.031 —
   1.04 clears it, and clears the landscape plates by far more. The reference
   site's own 1.025 at 1.25° is BELOW its floor of 1.033 for its 810/1222
   slides, which is a sliver of exposed background, not a value to copy. */
@media (prefers-reduced-motion: no-preference) {
  .gx__open > img { transition: transform 1s cubic-bezier(.2, .7, .2, 1); }
  .gx__plate figure:hover .gx__open > img,
  .gx__plate figure:focus-within .gx__open > img { transform: scale(1.04) rotate(-1deg); }
}

/* The caption and its scrim are revealed on hover — but only where there IS a
   hover to enter, and only where motion is welcome. On touch, and under
   prefers-reduced-motion, both are simply always on. That is the same call the
   reference site makes, and it is what keeps the caption from becoming
   unreachable information on the devices most of this traffic arrives on.
   :focus-within is here so the reveal also fires for a keyboard visitor
   tabbing onto the zoom button. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .gx__plate figure::before { opacity: 0; transition: opacity .45s ease; }
  .gx__plate figcaption {
    opacity: 0; translate: 0 .9rem;
    transition: opacity .45s ease, translate .5s cubic-bezier(.2, .7, .2, 1);
  }
  .gx__plate figure:hover::before,
  .gx__plate figure:focus-within::before { opacity: 1; }
  .gx__plate figure:hover figcaption,
  .gx__plate figure:focus-within figcaption { opacity: 1; translate: 0 0; }
}

@media (prefers-reduced-motion: no-preference) {
  .lb, .lb::backdrop { transition: opacity .22s ease, display .22s allow-discrete, overlay .22s allow-discrete; }
  .lb:not([open]), .lb:not([open])::backdrop { opacity: 0; }
  @starting-style { .lb[open], .lb[open]::backdrop { opacity: 0; } }
}

/* ---------- booking ----------
   Stacked on mobile, channels first: that is where the audience arrives, and
   where one thumb press beats six form fields. Side by side on desktop, because
   stacking there leaves half the viewport empty — and a desktop visitor is more
   likely to type anyway. Priority is then carried by weight: the channels are
   56px amber buttons, the form is quiet. */
.book { display: block; }
/* No max-width: .tiers, .spec and .stands above all span the full content
   width, so capping this one would make the page visibly narrow at the bottom.
   The panel stays a fixed 23rem and the form absorbs whatever is left. */
.book__cols { display: grid; gap: clamp(2rem, 4vw, 3rem); }

@media (min-width: 900px) {
  /* Fractional, not a fixed left column. With the panel pinned at 23rem the
     form absorbed every extra pixel alone, so a wide viewport gave 600px text
     boxes — and capping the fields instead just moved the empty band inside the
     form. Sharing the surplus keeps both sides plausible at any width. */
  .book__cols {
    grid-template-columns: minmax(19rem, 0.8fr) minmax(0, 1.7fr);
    align-items: start;
  }
  /* The form carries a 38rem measure for when it is the only thing on the page.
     Inside the two-column layout that cap stopped it short of its own column
     and left a band of dead space on the right, so here it fills the column. */
  .book__formwrap .form { max-width: none; }
}

/* The contact channels are a PANEL, not loose elements beside a divider.
   A hairline between two columns reads as "these were pushed together"; a
   surface with its own edge reads as a considered block. It also means the
   short column ending above the tall form looks finished rather than empty. */
.book__reach {
  padding: clamp(1.4rem, 3vw, 1.9rem);
  background: var(--surface-3);
  box-shadow: inset 0 0 0 1px var(--line);
}
.form { display: grid; gap: 1rem; max-width: 38rem; }
.form__row { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.field { display: grid; gap: .35rem; }

/* Each .field is its own little grid of label-then-input, so two fields side by
   side align only by coincidence — they agree as long as both labels happen to
   occupy the same height, and drift the moment one does not.
   Subgrid makes the label row and the input row belong to the ROW, not to each
   field, so every input in a row starts on the same line by construction. Also
   survives a label wrapping to two lines, which the old version would not. */
@supports (grid-template-rows: subgrid) {
  .form__row { grid-template-rows: auto auto; gap: .35rem 1rem; }
  .form__row > .field { grid-row: span 2; grid-template-rows: subgrid; row-gap: 0; }
}
.field > span { font-size: var(--fs-xs); font-weight: var(--fw-label); letter-spacing: .06em; color: var(--color-text-dim); text-transform: uppercase; }
.field input, .field select, .field textarea {
  width: 100%; padding: .75rem .85rem;
  background: var(--surface-2); color: var(--color-text);
  border: 1px solid var(--edge); border-radius: 0;
  font: inherit; font-size: var(--fs-body); /* >=16px: smaller triggers iOS auto-zoom on focus */
}
/* WebKit does not honour `box-sizing: border-box` on input[type="date"]. With
   `width: 100%` it lays the control out as 100% of the column PLUS its own
   padding and border, so on a 393px iPhone the field measured 353 + 27.2 + 2 =
   382px inside a 353px column and handed the WHOLE DOCUMENT 9px of horizontal
   scroll. Measured on the device: `box` computes to content-box no matter what
   is authored, and re-declaring `box-sizing: border-box !important` on the
   control changes nothing.
   The usual suspects are all dead ends here, so do not "simplify" this back:
   min-width:0 and max-width:100% both fail, because the constraint is not a
   minimum — it is padding being ADDED to a width that was already the full
   column. `width: calc(100% - 1.7rem - 2px)` does fix WebKit, but Blink honours
   border-box and renders the field 29px short of its siblings.
   `width: auto` is the one cure that is correct in both: .field is a grid, so
   the control stretches to its grid area, and stretch alignment subtracts
   padding and border properly whatever the engine thinks box-sizing means.
   DEPENDS on .field staying a grid (or flex) container — as a plain block,
   `width: auto` on a form control resolves to its INTRINSIC width instead, and
   the overflow comes straight back. */
.field input[type="date"] { width: auto; }

.section:nth-of-type(even) .field input,
.section:nth-of-type(even) .field select,
.section:nth-of-type(even) .field textarea { background: var(--surface-3); }
.field textarea { resize: vertical; }
/* Marks the package a pricing card just filled in. Border colour alone would
   be the only signal, and it is the same channel this field uses at rest —
   the added ring is what makes it a change rather than a shade. */
.field select { transition: box-shadow .35s ease; }
.field select.is-prefilled { box-shadow: 0 0 0 2px var(--color-accent); }
.req { color: var(--color-warning); font-weight: var(--fw-label); }
.form__legend { margin: 0; font-size: var(--fs-xs); color: var(--color-text-dim); }

/* A submit button stretched across a 900px column is a 900px target for a
   single action — past useful and it stops reading as a button. Wide enough to
   be obvious, capped so it stays one. */
.btn--submit { width: 100%; max-width: 22rem; justify-self: start; }
.form__note { margin: 0; font-size: var(--fs-xs); color: var(--color-text-dim); }

/* ---------- enquiry form states ---------- */

/* The honeypot. `display: none` is what most bots check for, and the good ones
   skip anything hidden that way — so this is hidden by position instead, which
   costs them a layout pass to detect. `aria-hidden` and `tabindex="-1"` in the
   markup keep it away from people; this only keeps it away from the eye. */
.form__trap {
  position: absolute; left: -9999px;
  width: 1px; height: 1px; overflow: hidden;
}

/* Empty by default and it must occupy nothing then — an always-present live
   region would otherwise open a permanent gap above the legend. :empty does
   that without JS having to toggle a class for the same fact. */
.form__status { margin: 0; font-size: var(--fs-sm); line-height: 1.5; }
.form__status:empty { display: none; }
.form__status--bad {
  color: var(--color-warning);
  /* A rule on the leading edge rather than a filled box: the form sits on two
     different surfaces depending on section parity, and a tinted panel would
     have to be redefined for each. A border inherits the text colour. */
  border-inline-start: 2px solid currentColor;
  padding-inline-start: .7rem;
}

/* Colour is not the signal — the message in .form__status is. This just points
   at which field it means, which is why it is a border weight change and not a
   red wash: at 2px it reads as emphasis in any colour vision. */
.field--bad input, .field--bad select, .field--bad textarea {
  border-color: var(--color-warning);
  box-shadow: inset 0 0 0 1px var(--color-warning);
}

/* Replaces the form in place. Sized to roughly the form's own width so the
   column does not visibly collapse at the moment of success. */
.form__done {
  display: grid; gap: .6rem; max-width: 38rem;
  padding: 1.4rem 1.5rem;
  background: var(--surface-2);
  border-inline-start: 2px solid var(--color-primary);
}
.section:nth-of-type(even) .form__done { background: var(--surface-3); }
.form__done:focus { outline: none; }
.form__done-h { margin: 0; font-size: var(--fs-lede); font-weight: var(--fw-label); color: var(--color-text); }
.form__done-p { margin: 0; font-size: var(--fs-sm); color: var(--color-text-soft); }

/* ---------- placeholder token chip ---------- */
.token {
  display: inline-block; padding: .05em .5em;
  /* As a direct grid item a chip stretched to its whole column, turning a
     placeholder into the loudest element on the page. */
  justify-self: start;
  background: repeating-linear-gradient(45deg, rgba(233,169,79,.42) 0 6px, rgba(233,169,79,.16) 6px 12px);
  box-shadow: inset 0 0 0 1px rgba(124,78,13,.55);
  color: var(--color-warning);   /* amber-soft was 1.36:1 on paper */
  font: 600 .78em/1.4 ui-monospace, monospace; letter-spacing: .04em;
}
.token::before { content: "["; }
.token::after { content: "]"; }

/* ---------- reach: messaging before the form ----------
   Most visitors arrive from a social link on a phone. Tapping Viber is one
   thumb press; the form is six fields of typing. So the channels come first
   and the form is offered second, not the other way round. */
/* Side by side, sized to their labels. Stretched full-width they became two
   slabs with a short word floating in the middle of each. */
.reach {
  display: grid; gap: .6rem;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  margin: 0 0 1.5rem;
}
.reach__btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  min-height: 52px; padding: .75rem 1rem;   /* still well past the 44px target */
  font: var(--fw-label) var(--fs-sm)/1 var(--sans); letter-spacing: .02em;
  text-decoration: none; white-space: nowrap;
  clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
  transition: background .2s ease, transform .2s ease;
}
/* ONE warm point. Two amber buttons made neither of them the primary action,
   and messaging is the one this section is built to push. */
.reach__btn--msg {
  background: var(--color-primary); color: var(--color-on-primary);
  box-shadow: inset 0 0 0 1px var(--color-primary-edge);
}
.reach__btn--msg:hover { background: var(--color-primary-hover); transform: translateY(-1px); }
.reach__btn--call {
  color: var(--color-text);
  box-shadow: inset 0 0 0 1px var(--edge);
}
.reach__btn--call:hover { background: var(--edge-wash); transform: translateY(-1px); }
.reach__btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.7; flex: none; }

/* An unset channel has no href, so it cannot be tapped. It has to *look* that
   way too — a live-looking button that goes nowhere is worse than none. */
.reach__btn.is-unset,
.reach__btn.is-unset:hover {
  background: transparent; color: var(--color-text-dim); cursor: not-allowed;
  box-shadow: inset 0 0 0 1px rgba(233, 169, 79, .45);
  transform: none;
}

/* Each line is its own tap target. Padding rather than min-height on an
   inline-flex: min-height forced a 40px box around a 15px number and left a
   visible hole between the two phones. */
.reach__list { margin: 0; padding: 0; list-style: none; }
.reach__list li + li { border-top: 1px solid var(--line); }
.reach__list a {
  display: block; padding: .7rem 0;
  font-size: var(--fs-body); color: var(--color-text);
  text-decoration: none;
}
.reach__list a:hover { color: var(--color-link-hover); }
.reach__hint { margin: 1rem 0 0; font-size: var(--fs-xs); color: var(--color-text-dim); }

/* "или изпратете запитване" — the form is the alternative now, and the label
   has to say so without shouting. Small, not a section title. It sits under the
   contact panel and points across, so it reads as a signpost rather than a
   heading sitting on top of the form. */
/* The label and the hook are one gesture, so they share one geometry: these two
   values place both, and the label centres over the hook's own box rather than
   over the column. Change them here and the pair moves together. */
.book__or {
  --hook-indent: .5rem;
  --hook-width: clamp(12rem, 22vw, 20rem);
  margin: 1.5rem 0 0;
  font: var(--fw-label) var(--fs-sm)/1.3 var(--sans);
  letter-spacing: .04em; color: var(--color-text-dim);
}

/* A fishhook that drops out of the middle of the label, falls left, curls round
   the bottom and rises right to the form. Drawn big on purpose — a small polite
   arrow reads as an icon, and this one is doing a gesture, which only works at
   gesture scale.
   These rules stay together, in this order. A media query adds no specificity,
   so an earlier @media block loses to a later base rule — which is exactly what
   happened when the desktop sizing sat up in the booking section and the
   shorthand here quietly reset its margin-left. */
.book__arrow {
  display: block;
  width: var(--hook-width); height: auto;
  /* Longhands, not the `margin` shorthand, so the queries below can set one
     side without silently resetting the others. */
  margin-block: .75rem 0;
  margin-left: var(--hook-indent);
  stroke: var(--color-accent);          /* decoration — cream, never the CTA amber */
  stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .7;
}

/* Sized against the COLUMN, not the viewport: the two grow at different rates,
   so a viewport-based width overshot into the gutter at ~1280 and fell short of
   it again past ~1700. As percentages the head finishes just inside the column
   edge — right next to the form — at every width. */
@media (min-width: 900px) {
  .book__or {
    --hook-indent: 24%;
    --hook-width: min(74%, 26rem);
    /* Where the stroke actually begins: 12% into the hook's own box. Centring
       the label on THIS, rather than on the box, is what puts the text over the
       start of the line instead of over the middle of the curve. */
    --hook-tail: calc(var(--hook-indent) + 0.12 * var(--hook-width));
  }
  .book__or > span {
    display: block;
    /* A box twice the tail offset, centred: its midpoint lands on the tail.
       min-width stops it squeezing the text into two lines on narrow desktops —
       there the label simply sits flush left instead of wrapping. */
    width: calc(2 * var(--hook-tail));
    min-width: max-content;
    text-align: center;
  }
}

/* Stacked layout: the form is below, not beside, so the hook turns to point
   down-right. The label stays left-aligned here — centring text over a rotated
   arrow lines it up with nothing. */
@media (max-width: 899px) {
  .book__or {
    display: grid; justify-items: start;
    --hook-indent: 2rem; --hook-width: clamp(9rem, 42vw, 13rem);
  }
  .book__arrow {
    transform: rotate(58deg);
    transform-origin: 35% 45%;
    margin-block: 1.5rem 2rem;
  }
}

/* ---------- for agencies ----------
   The one block on the page aimed at a repeat buyer rather than a couple. It
   earns the warm point because the download IS the action. */
/* Centred column, not a two-column spine. The construction section pairs its
   text with a photograph, so a spine is the right answer there. This section
   has no image — it has one card and one action, and centring lets the card be
   the object on the page rather than a block pushed to one side. */
.agency .section__head { max-width: 46rem; margin-inline: auto; text-align: center; }
.agency .section__lede { margin-inline: auto; }

.agency__grid {
  display: grid; justify-items: center;
  gap: clamp(.6rem, 1.2vw, 1rem);
}
/* The action still gets a touch more air than the fine print above it, but the
   whole stack sits close under the card it belongs to. */
.agency__get { margin-top: clamp(.35rem, .7vw, .5rem); }

/* This section ends on a small centred button with nothing after it, so the
   standard 8rem section floor read as a void rather than as breathing room.
   Every other section ends on full-width content and still wants the 8rem. */
.agency { padding-bottom: clamp(2.5rem, 6vw, 5rem); }

/* Same treatment as the contact panel beside the booking form — a surface with
   its own edge, so the trade terms read as one object rather than as loose
   rows on the page. */
.agency__terms {
  width: min(100%, 46rem);
  padding: clamp(1.5rem, 3vw, 2.5rem);
  background: var(--surface-3);
  /* --line is 1.18:1 against paper here and reads as a smudge. The panel beside
     the booking form gets away with it because the form frames it; this one is
     alone in the middle of the section and has to hold its own shape. */
  box-shadow: inset 0 0 0 1px var(--edge);
  text-align: left;
}

/* The action sits under the card it belongs to. */
.agency__get { justify-items: center; text-align: center; }
.agency__foot { text-align: center; }
.agency__get { display: grid; justify-items: start; position: relative; }

/* Revealed on hover AND focus — keyboard users get it too, and it is absolutely
   positioned so it costs no layout space when hidden. */
.agency__meta {
  position: absolute; top: calc(100% + .55rem); left: 50%;
  transform: translateX(-50%);
  margin: 0; padding: .45rem .75rem; white-space: nowrap;
  background: var(--ink); color: var(--paper);
  font-size: var(--fs-xs); line-height: 1.4;
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
.agency__get:hover .agency__meta,
.agency__get:focus-within .agency__meta { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .agency__meta { transition: none; } }

/* The nowrap line measures ~353px and is centred on the page, so under a ~393px
   viewport it hangs past the edge — and because it is absolutely positioned in
   normal flow's blind spot, it does that INVISIBLY (opacity: 0), giving the
   whole document a horizontal scroll with nothing on screen to explain it.
   Below the width where one line fits, it wraps and takes a hard cap.
   `width: max-content` first: shrink-to-fit against `left: 50%` would otherwise
   resolve to half the button's width and stack the text into a column. */
@media (max-width: 460px) {
  .agency__meta {
    white-space: normal;
    width: max-content; max-width: 17rem;
    text-align: center;
  }
}

.agency__terms > * { margin: 0; }
.agency__terms {
  margin: 0; display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
  gap: 0 clamp(1rem, 2vw, 2rem);
  /* NOT baseline: baseline aligns the first line of each cell, so a two-line
     value left its label stranded at the top of the row. Stretching the cells
     keeps the rule running unbroken across both columns, and centring inside
     each cell is what actually lines the label up with its value. */
  align-items: stretch;
}
.agency__terms dt, .agency__terms dd {
  display: flex; align-items: center;
  padding: .9rem 0; border-top: 1px solid var(--line);
}
/* First row loses only the rule — keeping its padding holds every row the
   same height, which is what makes the column scan. */
.agency__terms > dt:first-of-type,
.agency__terms > dt:first-of-type + dd { border-top: 0; }
.agency__terms dt {
  font: var(--fw-label) var(--fs-xs)/1.4 var(--sans);
  letter-spacing: .04em; text-transform: uppercase; color: var(--color-text-dim);
}
.agency__terms dd { margin: 0; color: var(--color-text); }
.agency__terms a { color: var(--color-text); text-decoration: underline; text-underline-offset: .2em; }
.agency__terms a:hover { color: var(--color-link-hover); }

/* Phone glyph on the direct number.
   A mask rather than a background-image, so the glyph is painted in
   currentColor and follows the link into its hover state — a background SVG
   keeps whatever colour was baked into the data URI and drifts the moment the
   palette moves.
   It has to be a pseudo-element: renderTokens() fills this link with
   `el.textContent = value`, which would wipe an inline <svg> child. A
   pseudo-element is not a child, and it sits inside the link, so it is part of
   the same tap target.
   gap, not a margin — the link is already an inline-flex row further down. */
.agency__tel::before {
  content: "";
  flex: none; width: 1.05em; height: 1.05em;
  background: currentColor;
  -webkit-mask: var(--ico-phone) center / contain no-repeat;
          mask: var(--ico-phone) center / contain no-repeat;
}
.agency__tel { gap: .55rem; }

.agency__foot { margin: 0; font-size: var(--fs-sm); color: var(--color-text-dim); }
.agency__foot a, .spec__get a {
  color: var(--color-text); text-decoration: underline; text-underline-offset: .2em;
}
.agency__foot a:hover, .spec__get a:hover { color: var(--color-link-hover); }
.spec__get { margin: 2rem 0 0; font-size: var(--fs-sm); }
/* Standalone links, not words inside a sentence — so they are targets. */
.spec__get a, .agency__terms dd a {
  display: inline-flex; align-items: center; min-height: 44px;
}

/* Stacked, the rule has to change what it divides.
 *
 * Two columns, a dt and its dd share one grid row, so one border-top draws a
 * single rule across the pair. One column and they are separate rows — but
 * both still carried that border, so the rule fell BETWEEN each label and its
 * own value and bound every value to the NEXT label instead. "По договаряне"
 * read as belonging to "Фактура". Only the first pair escaped, because
 * `dt:first-of-type + dd` happens to zero its rule, which is what made the
 * card look inconsistent as well as wrong.
 *
 * So the rule moves onto the dt alone: it divides pairs, which is the thing it
 * was always drawing on desktop. Then proximity does the grouping — .3rem
 * between a label and its value against 1.05rem before the next rule, so the
 * pair reads as one object with a clear gap either side of it.
 */
@media (max-width: 540px) {
  .agency__terms { grid-template-columns: 1fr; gap: 0; }

  .agency__terms dt {
    display: block;
    border-top: 1px solid var(--line);
    padding: 1.05rem 0 .3rem;
  }
  .agency__terms dd {
    display: block;
    border-top: 0;
    padding: 0 0 1.05rem;
  }
  /* Nothing above it to divide. Overrides the shared rule above, which is why
     it stays here rather than relying on the desktop first-of-type pair. */
  .agency__terms > dt:first-of-type { padding-top: 0; }
}

/* ---------- footer ----------
   Full company identification, because a Bulgarian buyer looks for ЕИК and a
   registered address before trusting anyone with a deposit. Their absence is
   read as a hobby, not as an omission. */
.footer {
  padding: 3rem var(--pad) 3.5rem;
  /* A dark slab under a cream page read as a leftover from the old scheme
     rather than as a deliberate band — the film earns that exception because
     it IS the film; a footer does not. paper-2 separates it from the page
     without changing register. */
  background: var(--surface-2);
  border-top: 1px solid var(--line);
  font-size: var(--fs-sm); color: var(--color-text-dim);
}
.footer p { margin: 0 0 .3rem; }
.footer strong { color: var(--color-text); }

/* Two blocks, not three equal columns. Identity is the long one and it is what
   a buyer reads top to bottom, so it stays at the left edge; contact and
   documents are short lists people scan for, so they pair up and sit against
   the right edge — where the fine-print rule underneath already ends. The old
   68rem cap made all three huddle in the left two thirds of a wide screen
   while that rule ran full width, which is what read as lopsided. */
.footer__grid {
  display: flex; flex-wrap: wrap;
  justify-content: space-between; align-items: start;
  gap: 2.5rem clamp(2rem, 5vw, 5rem);
}
/* Below the wide layout the identity block just takes what is left beside the
   aside; the address wraps once there, which costs a line but keeps the two
   blocks on the same row. Stacking them instead would leave the top row half
   empty — the very thing this layout exists to fix. */
.footer__col--id { flex: 1 1 22rem; }

/* Wide enough for both: hold the identity block at the width where the
   registered address sets on one line. Measured — the address is 482px and its
   label column 85px, so 37rem clears the pair with room for a longer street.
   Shrinkable rather than fixed, so a future longer address wraps to two lines
   instead of overflowing the page. Measured breakpoint, not a guessed one:
   below ~1270px the aside no longer fits beside a 37rem identity block and
   drops to a row of its own. */
@media (min-width: 1280px) {
  .footer__col--id { flex: 0 1 37rem; }
}
.footer__aside {
  display: flex; flex-wrap: wrap;
  gap: 2.5rem clamp(2rem, 4vw, 4rem);
}
.footer__aside .footer__col { min-width: 13rem; }
.footer__h {
  margin: 0 0 .9rem;
  font: var(--fw-label) var(--fs-eyebrow)/1 var(--sans);
  letter-spacing: var(--track-eyebrow); text-transform: uppercase;
  color: var(--color-accent);
}
.footer__name { margin-bottom: .6rem; }
.footer__muted { color: var(--color-text-dim); }

/* A phone number and an email address already identify themselves, so these
   icons are not labels — they are what makes the two lines findable at a
   glance in a block of small grey text. Dim, so they mark the line without
   competing with the number itself. */
.footer__reach { display: flex; align-items: center; gap: .55rem; }
.footer__ico {
  width: 17px; height: 17px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  color: var(--color-text-dim);
}

/* Definition list as a label/value pair per row. Grid rather than float so a
   long registered address wraps under its own label instead of under ЕИК. */
.footer__dl {
  margin: 0; display: grid; grid-template-columns: auto 1fr;
  gap: .3rem .9rem; align-items: baseline;
}
.footer__dl dt {
  font: var(--fw-label) var(--fs-xs)/1.4 var(--sans);
  letter-spacing: .04em; text-transform: uppercase; color: var(--color-text-dim);
}
.footer__dl dd { margin: 0; color: var(--color-text); }
/* Secondary within its own row: the ЕИК is the number people check, the ДДС
   number is the derived one they only need for an invoice. */
.footer__vat { color: var(--color-text-dim); white-space: nowrap; }

.footer a { color: var(--color-text); text-decoration: underline; text-underline-offset: .2em; }
.footer a:hover { color: var(--color-link-hover); }

/* Each of these stands alone on its own line rather than sitting inside a
   sentence, so it is a target, not a word — and on a phone the number and the
   terms link are exactly what gets tapped. Text alone gives an 18px box. */
.footer__col a, .footer--slim a {
  display: inline-flex; align-items: center; min-height: 44px;
}

.footer__fine {
  margin: 2.5rem 0 0; padding-top: 1.5rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-xs); color: var(--color-text-dim);
}

.footer--slim { padding-top: 2.5rem; }

/* ---------- legal document pages ----------
   Static prose, so they skip the fixed masthead: a document that a visitor
   may print or read end to end should not lose a band of every screen to
   navigation it does not need. */
.dh {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1rem; padding: 1.5rem var(--pad);
  border-bottom: 1px solid var(--line);
}
.dh__brand {
  display: inline-flex; align-items: center; gap: .6rem;
  color: var(--color-text); text-decoration: none;
  font: var(--fw-heading) var(--fs-body)/1 var(--serif);
}
.dh__brand svg { width: 22px; height: 22px; fill: none; stroke: var(--color-mark); stroke-width: 1.6; }
.dh__back {
  min-height: 44px; display: inline-flex; align-items: center;
  color: var(--color-text-dim); font-size: var(--fs-sm);
  text-decoration: underline; text-underline-offset: .2em;
}
.dh__back:hover { color: var(--color-text); }

.doc {
  max-width: 44rem; margin: 0 auto;
  padding: clamp(3rem, 7vw, 5rem) var(--pad) clamp(4rem, 8vw, 6rem);
}
.doc__title {
  margin: 0 0 .6rem; font-family: var(--serif); font-weight: var(--fw-heading);
  font-size: var(--fs-section); line-height: var(--lh-display); letter-spacing: -.01em;
}
.doc__meta { margin: 0 0 2rem; font-size: var(--fs-sm); color: var(--color-text-dim); }
.doc__lede { margin: 0 0 1rem; font-size: var(--fs-lede); color: var(--color-text-soft); }

.doc__sec { margin-top: 2.75rem; }
.doc__sec h2 {
  margin: 0 0 .9rem; font-family: var(--serif); font-weight: var(--fw-heading);
  font-size: var(--fs-card); line-height: 1.25;
}
.doc__sec p, .doc__sec li { color: var(--color-text-soft); }
.doc__sec p { margin: 0 0 .9rem; }
.doc__sec ul { margin: 0 0 .9rem; padding-left: 1.1rem; }
.doc__sec li { margin-bottom: .45rem; }
.doc__sec ::marker { color: var(--color-accent); }
.doc__sec strong { color: var(--color-text); }
.doc a { color: var(--color-text); text-decoration: underline; text-underline-offset: .2em; }
.doc a:hover { color: var(--color-link-hover); }

.doc__block { padding: 1rem 1.2rem; background: var(--surface-2); }

.doc__dl {
  margin: 0 0 1rem; display: grid; grid-template-columns: auto 1fr;
  gap: .5rem 1.2rem; align-items: baseline;
}
.doc__dl dt {
  font: var(--fw-label) var(--fs-xs)/1.4 var(--sans);
  letter-spacing: .04em; text-transform: uppercase; color: var(--color-text-dim);
}
.doc__dl dd { margin: 0; color: var(--color-text); }

.doc__table { width: 100%; border-collapse: collapse; margin: 0 0 1rem; font-size: var(--fs-sm); }
.doc__table th, .doc__table td {
  padding: .7rem .9rem; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--line); color: var(--color-text-soft);
}
.doc__table thead th {
  font: var(--fw-label) var(--fs-xs)/1.4 var(--sans);
  letter-spacing: .04em; text-transform: uppercase; color: var(--color-text-dim);
}

@media (max-width: 540px) {
  .footer__dl, .doc__dl { grid-template-columns: 1fr; gap: .15rem; }
  .footer__dl dd, .doc__dl dd { margin-bottom: .7rem; }
}

/* A legal page is the one thing on this site people actually print.
   Browsers drop background colours when printing but keep text colour, so
   the cream-on-navy screen palette would print as cream on white — i.e.
   blank. Invert to ink on paper explicitly. */
@media print {
  .dh, .footer--slim { display: none; }
  .doc { max-width: none; padding: 0; }
  .doc__sec { break-inside: avoid; }

  /* The screen scheme is already ink on paper, so nothing needs inverting
     here any more — this block only removes chrome and saves toner. */
  body { background: #fff; }
  .doc__block { background: transparent; border: 1px solid var(--edge); }
}

/* ================= BOTTOM BAR (mobile only) =================
 * REPLACES the masthead on a phone rather than joining it. Two fixed bars
 * would spend ~16% of an 844px screen on chrome before any content, and the
 * top one is the hardest place on a modern phone for a thumb to reach — which
 * is where both money actions used to live.
 *
 * The Меню slot is not a style choice. Laid out in a row the four section
 * links measure 469px against the 350px a 390px phone actually offers, which
 * is why three of the four were hidden up there. Behind a sheet all six fit,
 * so the phone gets MORE navigation than it had, not less.
 *
 * SHAPE: this one place follows the reference site rather than the page's own
 * chamfered, opaque language — a floating frosted capsule with icon-above-label
 * slots. The chamfer is a print device and it reads as a frame around content;
 * floating chrome that sits ON the photograph wants the opposite, something
 * that looks like glass laid over the image rather than a panel cut into it.
 */
.bn { display: none; }

@media (max-width: 860px) {
  /* The masthead goes. Its controls are all in the bar below, and the hero
     gets its full screen back — the still was being cropped by a 66px bar it
     had no relationship with. */
  .mh { display: none; }

  /* The jump offset existed to clear that fixed masthead. With no bar at the
     top, 7rem of it drops every anchored section a hundred-odd pixels low. */
  :target,
  section[id] { scroll-margin-top: 1rem; }

  /* Clearance for a bar that floats: its own height plus the gap beneath it.
     env() and not a flat number — the home indicator is 34px on some phones
     and 0 on others, and guessing either way is wrong on the rest. */
  /* One source for how far the capsule stands off the bottom, because four
     rules depend on it: the bar's own offset, the page's end clearance, the
     hero copy above it and the sheet that sits on top of it. Nudge --bn-gap
     and all four follow; hand-tuned numbers drift apart on the first change.
     --bn-h is the capsule itself: a 52px slot plus .3rem of padding either
     side, measured, not guessed. */
  :root {
    --bn-gap: 1.5rem; --bn-h: 3.9rem;
    /* How far the hero's copy sits off the bottom, so the pin's padding and
       the veil that has to reach past it cannot disagree. */
    --hero-copy-bottom: calc(var(--bn-gap) + var(--bn-h) + 3rem + env(safe-area-inset-bottom));
  }

  body { padding-bottom: calc(var(--bn-gap) + var(--bn-h) + 1.6rem + env(safe-area-inset-bottom)); }

  /* The hero copy is bottom-aligned, so its CTA ran straight under the
     capsule. The bar stands ~75px off the bottom (14px gap + 61px of pill), so
     4rem was never going to clear it. */
  .chapter__pin { padding-bottom: var(--hero-copy-bottom); }
  /* The veil has to reach the hero's bottom edge, not stop under the copy —
     see the note on .chapter__copy::before above. Same variable, so raising
     the bar's clearance cannot re-open the strip. */
  .chapter__copy::before { bottom: calc(-1 * var(--hero-copy-bottom)); }

  /* ---- the capsule ---- */
  .bn {
    position: fixed; z-index: var(--z-nav);
    left: max(.9rem, env(safe-area-inset-left));
    right: max(.9rem, env(safe-area-inset-right));
    bottom: calc(var(--bn-gap) + env(safe-area-inset-bottom));
    display: flex; align-items: stretch;
    padding: .3rem;
    border-radius: 999px;
    /* Glass, not a panel. The fill has to stay light enough to read as
       translucent over the photograph and opaque enough for navy labels to
       hold their contrast over the bright lawn underneath — .72 is where both
       are true. saturate() puts back the colour the blur washes out, which is
       the difference between frosted glass and grey plastic. */
    background: rgba(244, 239, 231, .72);
    backdrop-filter: blur(20px) saturate(1.4);
    -webkit-backdrop-filter: blur(20px) saturate(1.4);
    /* A light inner top edge reads as the lit rim of a curved surface; the
       shadow underneath is what lifts it off the page. */
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, .65),
      inset 0 0 0 1px rgba(11, 21, 36, .07),
      0 10px 30px rgba(11, 21, 36, .16);
  }

  /* No backdrop-filter (older iOS, Firefox without the flag): the capsule has
     to stop being translucent, or navy labels sit on a bright lawn at 2:1. */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .bn { background: rgba(244, 239, 231, .97); }
  }

  .bn__item {
    flex: 1 1 0;                       /* four equal slots, like the reference */
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .28rem;
    min-height: 52px; padding: .45rem .2rem;
    border: 0; border-radius: 999px;
    background: transparent; color: var(--color-text);
    text-decoration: none; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background .2s ease, color .2s ease;
  }
  .bn__item:active { background: rgba(11, 21, 36, .06); }
  .bn__item:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }

  .bn__ico { display: block; position: relative; width: 21px; height: 21px; }
  .bn__ico svg {
    position: absolute; inset: 0; width: 100%; height: 100%;
    fill: none; stroke: currentColor; stroke-width: 1.5;
    stroke-linecap: round; stroke-linejoin: round;
  }

  /* Small, wide-tracked and NOT uppercase — the reference sets these in
     sentence case, and at 10px Cyrillic uppercase loses the letterform
     differences that make a word recognisable at a glance. */
  .bn__label {
    font: var(--fw-label) 10px/1 var(--sans);
    letter-spacing: .04em;
    color: var(--color-text-soft);
    white-space: nowrap;
  }

  /* The booking slot is emphasised by WEIGHT, not by the brand amber.
     Measured: #E9A94F on this glass lands at 1.52:1 over the lawn, 1.75:1 over
     sky and 1.07:1 over the dark trees — under 4.5:1 for a 10px label and
     under even the 3:1 an icon needs. Translucency is what kills it; there is
     no fill behind the glass to hold the colour up. Ink runs 6.4:1 to 15.6:1
     across the same three grounds, so weight is the only emphasis available
     here that survives the photograph underneath.
     No loss: the hero carries a full-size amber Резервирай of its own, and the
     tiers and the form carry theirs. The reference puts no CTA in the bar. */
  .bn__item--mark .bn__label { color: var(--color-text); font-weight: 600; }

  .bn__call.is-unset { opacity: .38; cursor: not-allowed; }

  /* Hamburger crosses to an X while the sheet is open — the affordance the
     reference uses to say the same control closes it again. Both glyphs are in
     the markup and only one is drawn, so nothing reflows on the swap. */
  .bn__ico-b { opacity: 0; transform: rotate(-90deg); transition: opacity .2s ease, transform .25s ease; }
  .bn__ico-a { opacity: 1; transform: none;            transition: opacity .2s ease, transform .25s ease; }
  .bn__menu[aria-expanded="true"] .bn__ico-a { opacity: 0; transform: rotate(90deg); }
  .bn__menu[aria-expanded="true"] .bn__ico-b { opacity: 1; transform: none; }

  /* ---- the sheet ----
     Stacked UNDER the bar on purpose: the whole look depends on the capsule
     staying crisp and lit while the page behind it goes soft. */
  .bns {
    position: fixed; inset: 0; z-index: calc(var(--z-nav) - 1);
    display: flex; align-items: flex-end;
    padding: 0 max(.9rem, env(safe-area-inset-left))
             calc(var(--bn-gap) + var(--bn-h) + .7rem + env(safe-area-inset-bottom));
    background: rgba(11, 21, 36, .38);
    backdrop-filter: blur(14px) saturate(1.1);
    -webkit-backdrop-filter: blur(14px) saturate(1.1);
    animation: bns-in .22s ease both;
  }
  .bns[hidden] { display: none; }
  @keyframes bns-in { from { opacity: 0 } to { opacity: 1 } }

  .bns__panel {
    width: 100%;
    padding: 1.35rem 1.25rem 1.6rem;
    /* Softly rounded, not chamfered, and not a hard-edged card: it is the same
       piece of glass as the capsule under it, one size larger. */
    border-radius: 26px;
    background: rgba(244, 239, 231, .7);
    backdrop-filter: blur(22px) saturate(1.4);
    -webkit-backdrop-filter: blur(22px) saturate(1.4);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, .6),
      inset 0 0 0 1px rgba(11, 21, 36, .06),
      0 18px 44px rgba(11, 21, 36, .26);
    animation: bns-rise .26s cubic-bezier(.2, .7, .2, 1) both;
  }
  @keyframes bns-rise { from { transform: translateY(14px); opacity: 0 } to { transform: none; opacity: 1 } }

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

  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .bns { background: rgba(11, 21, 36, .62); }
    .bns__panel { background: rgba(244, 239, 231, .98); }
  }

  /* "– МЕНЮ –": a rule either side of the word, the reference's header. Drawn
     with flex and two borders rather than literal dashes, so the rules scale
     with the panel instead of being a fixed number of hyphens. */
  .bns__eyebrow {
    display: flex; align-items: center; gap: .7rem;
    margin: 0 0 .35rem;
    font: var(--fw-label) var(--fs-eyebrow)/1 var(--sans);
    letter-spacing: var(--track-eyebrow); text-transform: uppercase;
    color: var(--color-eyebrow);
  }
  .bns__eyebrow::before,
  .bns__eyebrow::after {
    content: ""; flex: 1 1 0; height: 1px;
    background: rgba(11, 21, 36, .22);
  }

  /* No rules between the items — the reference keeps this list open, and on
     glass a stack of hairlines reads as a table rather than a menu. */
  .bns__list { margin: 0; padding: 0; list-style: none; text-align: center; }
  .bns__list a {
    display: block; padding: .72rem .25rem;
    color: var(--color-text); text-decoration: none;
    font-family: var(--serif); font-weight: 400;
    font-size: 1.25rem; line-height: 1.25;
    border-radius: 12px;
  }
  .bns__list a:active { background: rgba(11, 21, 36, .06); }
  .bns__list a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }
}
