/* Black Market — generated by layouts/styles.css.liquid from styles/.
 * Do not edit: change the part, not the bundle. */

/* ═══ base ═══ */
/* Document-level defaults. Not scoped — shared by design.
 *
 * Deliberately small: the components carry their own type, and a base that
 * paints every element (as app/src/assets/default.less did) is a base that
 * every component then has to undo. */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--c-white);
    color: var(--c-text);
    font-family: var(--font-body);
    font-size: 16px;
    line-height: 1.3;
}

h1, h2, h3, h4, h5, h6 {
    margin: 0 0 0.5em;
    font-family: var(--font-display);
    line-height: 1.1;
    /* No colour. A heading takes the colour of whatever it sits in, which is
     * white over a photograph and dark on a panel. Painting it here is exactly
     * what default.less did — `color: #FFF` on every heading — and every
     * section then had to undo it. */
}

p { margin: 0 0 1em; }

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

a { color: inherit; }

/* A visible focus ring. default.less set `*:focus { outline: none }` with
 * nothing in its place, which leaves a keyboard user with no idea where they
 * are. :focus-visible shows it for keyboard navigation and not for a mouse
 * click, which is what the original was trying to avoid. */
:focus-visible {
    outline: 2px solid var(--c-gold);
    outline-offset: 2px;
}

/* Reveal-on-scroll starts hidden ONLY when scripting is on. Unconditional
 * `opacity: 0` renders the page blank with JS off, so the class is gated on
 * `.js`, set by an inline script in chrome/head.liquid before first paint. */
.js .rv { opacity: 0; }

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

/* ═══ components ═══ */
/* Design-system component CSS. Not scoped — shared by design.
 *
 * These ship in the stylesheet rather than being injected by the components
 * themselves: on a server-rendered page, JS-injected CSS means unstyled markup
 * until the scripts run. */

/* The button — ui/btn.liquid.
 *
 * Ported from app/src/components/MainButton.vue. The label slides up on hover
 * and a bolder copy of itself rises into its place: that is what the
 * `data-hover` attribute the markup carries is for, drawn as a ::before that
 * sits one line below and has no height of its own. `overflow: hidden` on the
 * button is what makes it a reveal rather than two lines of text. */
/* `hidden` has to beat the display below it.
 *
 * The attribute's `display: none` comes from the user agent's stylesheet, and
 * an author rule — any author rule — outranks the whole user-agent origin. So
 * `.main-button { display: inline-block }` was quietly undoing `hidden` on
 * every button that carried it, and the gallery's reveal stood on a desktop
 * page it has no business being on.
 *
 * Here rather than beside the one button that showed it: this is true of every
 * button on the site, and the next one to be hidden would have met the same
 * rule. */
.main-button[hidden] { display: none; }

.main-button {
    display: inline-block;
    overflow: hidden;
    /* The gap above it belongs to the BUTTON, not to whatever precedes it.
     * MainButton always renders inside `.info-wrapper` and the rule is
     * `.info-wrapper .main-button { margin-top: 45px }`, so every button in
     * the app carries it. Ours renders no wrapper, so it goes here — and
     * without it the button sat flush against the paragraph above. */
    margin-block-start: 45px;
    padding: 0 49px;
    border: 1px solid currentColor;
    background: none;
    font-family: var(--font-condensed);
    font-size: 35px;
    font-weight: 300;
    line-height: 1.2;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
}

.main-button {
    /* The tracking the hover copy carries. A variable because the button has
     * two copies of that copy — the one you see and the one that reserves room
     * for it below — and they have to agree; a page that wants different
     * tracking sets this on the button rather than on one of the two. */
    --hover-tracking: -2px;
    /* The button's own vertical padding, moved onto the span below.
     *
     * The hover copy sits at `top: 100%` of the span, and `overflow: hidden`
     * on the button clips at the button's PADDING box — which is lower. So
     * whatever padding the button kept underneath the span showed as a sliver
     * of the rising text sitting under the resting one. Carried by the span
     * instead, the two edges are the same edge and there is nothing to show.
     *
     * A variable because the figure differs: 2px here and on the banner, none
     * on a button inside a block's copy. */
    --btn-pad-y: 2px;
}

.main-button span {
    position: relative;
    display: inline-block;
    padding-block: var(--btn-pad-y);
    font-family: var(--font-condensed);
    font-weight: 500;
    line-height: normal;
    transition: transform 0.3s;
}

.main-button span::before {
    content: attr(data-hover);
    position: absolute;
    top: 100%;
    /* Stretched across the label and centred in it, rather than nudged off
     * its left edge by a fixed -4px.
     *
     * The two copies are never the same width — this one is 700 against 500,
     * and on a wide button it carries -2px of tracking as well — so a constant
     * offset lines them up at exactly one label length and hangs the rising
     * text off one side at every other. Edge to edge with `text-align: center`
     * makes the difference fall evenly whatever it is, and the button's
     * padding absorbs it.
     *
     * blocks.css reached this first and said it for buttons inside a block's
     * copy, where a 110px minimum width made the -4px visibly wrong. It is the
     * right answer for all of them, so it moves here and that override goes. */
    /* The box is the TEXT's width and its middle is put on the label's
     * middle. `left: 0; right: 0` with `text-align: center` says the same
     * thing only while the copy fits: this one is 700 against the label's
     * 300-to-500 and is therefore wider than the box it would be measured
     * against, and a line that overflows its box is not reliably centred in
     * it. Sized to its own content there is no overflow left to align. */
    left: 50%;
    width: max-content;
    transform: translateX(-50%);
    height: 0;
    /* The hover copy must not wrap, and it will without this.
     *
     * It is absolutely positioned inside the span, so it shrink-to-fits within
     * the span's width — and the span is only as wide as the resting label.
     * This copy is heavier (700 against 500), so for any label long enough it
     * came out wider than the box it is measured against and broke onto a
     * second line. That line is invisible: `height: 0` here and `overflow:
     * hidden` on the button. "Запази час за среща" lost "среща" on hover and
     * nothing anywhere said so.
     *
     * It stays within the button because of the -2px of tracking below, which
     * buys back more than the weight costs. */
    white-space: nowrap;
    font-family: var(--font-condensed);
    font-weight: 700;
    letter-spacing: var(--hover-tracking);
}

/* The button is as wide as the WIDER of its two labels.
 *
 * It used to be as wide as the resting one, and the hover copy is 700 against
 * a label that inherits 300 to 500 — so it was always the wider of the two and
 * always had to go somewhere. It wrapped onto an invisible second line, or
 * overflowed and argued about where its centre was, depending on what the rule
 * above said that week.
 *
 * This twin is the same string at the same weight and tracking, laid out and
 * then taken out of sight: no height, no ink, no reading by anything that
 * speaks the page. All it does is make the span wide enough that neither copy
 * has anywhere to spill. */
.main-button span::after {
    content: attr(data-hover);
    display: block;
    height: 0;
    overflow: hidden;
    visibility: hidden;
    white-space: nowrap;
    font-family: var(--font-condensed);
    font-weight: 700;
    letter-spacing: var(--hover-tracking);
}

.main-button:hover span { transform: translateY(-100%); }

/* The three variants are a border and a text colour, nothing else. */
.main-button--light { border-color: var(--c-white); color: var(--c-white); }
.main-button--dark  { border-color: var(--c-black); color: var(--c-black); }
.main-button--grey  { border-color: #808284; color: var(--c-ink); }

/* A reader who has asked for less motion gets the label without the slide;
 * the bolder copy still arrives, because that is the state change. */
@media (prefers-reduced-motion: reduce) {
    .main-button span { transition: none; }
}

/* ── the carousels' arrows ────────────────────────────────────────────── */
/* Three rows scroll sideways — the reel of styling entries, the strip of
 * stories, and a gallery inside an entry — and ONE script builds the buttons
 * for all three: scripts/page/rail.js walks `.reel-track, .stories-track,
 * .gallery` and appends the same two elements to each. So the look is stated
 * once, here, and a section says only what differs, through four custom
 * properties.
 *
 * It was three copies of this block with three sets of numbers. That is how
 * the gallery ended up with a 45px chevron 52px in from the edge and a hit
 * area a third of the strip wide, while the other two carried a 22px glyph on
 * the page's own column — the same component, three looks, and any change to
 * be made three times.
 *
 *   --rail-width   the hit area                     (default 120px)
 *   --rail-inset   where the glyph sits from the edge (default 0)
 *   --rail-glyph   the chevron                      (default 22px)
 *   --rail-lean    how far it leans on hover        (default 15px)
 */
.rail-btn {
    /* Hidden until the script says there is something to scroll to. A wide
     * screen with few entries needs no arrows, and two permanently disabled
     * ones would be worse than none. */
    display: none;
    position: absolute;
    /* The box is the full height of the row and the glyph centres in it, so
     * on a row whose cards carry a caption the glyph centres against picture
     * AND caption — which reads low, because the eye centres on the picture.
     * `--rail-shift` lifts it by half the caption's height. */
    top: calc(-1 * var(--rail-shift, 0px));
    z-index: 3;
    width: var(--rail-width, 120px);
    height: 100%;
    padding: 0;
    border: 0;
    background: none;
    color: var(--c-gold);
    cursor: pointer;
    opacity: 0.8;
    transition: opacity 0.3s;
}
[data-arrows] .rail-btn { display: flex; align-items: center; }
.rail-btn:hover { opacity: 1; }
.rail-btn:disabled { opacity: 0.25; cursor: default; pointer-events: none; }

/* The glyph sits ON the content line rather than in the middle of the button:
 * its outer edge lines up with the row's first card and with the heading over
 * it. The box stays wide, so there is still something to aim at — only the
 * glyph moves inside it. */
.rail-prev { left: 0; justify-content: flex-start; padding-left: var(--rail-inset, 0); }
.rail-next { right: 0; justify-content: flex-end; padding-right: var(--rail-inset, 0); }
.rail-btn svg { width: var(--rail-glyph, 22px); height: auto; transition: margin 0.2s; }
.rail-next svg { transform: rotate(180deg); }

/* It leans outward on hover — the arrow leans the way it will take you. Only
 * where there is a pointer to lean for: `.not-mobile &:hover` in all three of
 * the components this comes from. */
@media (min-width: 1024px) {
    .rail-prev:hover svg { margin-left: calc(-1 * var(--rail-lean, 15px)); }
    .rail-next:hover svg { margin-right: calc(-1 * var(--rail-lean, 15px)); }
}

/* The arrows stay on a touch screen. Georgi's call: a carousel is a carousel
 * everywhere, and the strips have carried arrows on a phone for as long as he
 * has been looking at them.
 *
 * All three components say otherwise — `@media (hover: none) { .rail-btn {
 * display: none } }`, on the reasoning that the swipe is the affordance and a
 * column of button over the cards swallows taps meant for a card. None of the
 * three ever rendered it: each wrote a bare `.rail-btn` at 0-1-0 against its
 * own `[data-arrows] .rail-btn { display: flex }` at 0-2-0 and lost the tie.
 * So the phone has always shown them, and merging the three copies is what
 * would have taken them away. The rule is left out rather than fixed. */

/* The audio player — ui/audio.liquid. Ported from MusicPlayer.vue.
 *
 * Both glyphs ship and the CSS shows one, keyed off the button's pressed
 * state: swapping innerHTML on click would put the markup back in the
 * script's hands. */
.block-audio {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-block-start: 20px;
}
.block-audio-toggle {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background-color: var(--c-black);
    color: var(--c-white);
    cursor: pointer;
    transition: transform 0.2s;
}
.block-audio-toggle:hover { transform: scale(1.05); }
.block-audio-title {
    font-family: var(--font-body);
    font-size: 16px;
    color: var(--c-ink);
}
.block-audio-pause { display: none; }
.block-audio-toggle[aria-pressed="true"] .block-audio-play { display: none; }
.block-audio-toggle[aria-pressed="true"] .block-audio-pause { display: inline; }

/* The play control over a clip — ported from VideoButton.vue. It fades out
 * while the clip runs and comes back on hover, so it never covers the picture
 * for longer than it is useful. */
.block-video-toggle {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 60px;
    height: 60px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background-color: rgb(0 0 0 / 0.3);
    color: var(--c-white);
    cursor: pointer;
    transform: translate(-50%, -50%);
    transition: transform 0.2s, opacity 0.3s;
}
.block-video-toggle:hover { transform: translate(-50%, -50%) scale(1.05); }
.block-video-pause { display: none; }
.block-video-toggle[aria-pressed="true"] .block-video-play { display: none; }
.block-video-toggle[aria-pressed="true"] .block-video-pause { display: inline; }
/* Out of the way while it plays, back on hover. */
.block-video-toggle[aria-pressed="true"] { opacity: 0; }
.block-figure--video:hover .block-video-toggle { opacity: 1; }

/* The sound control, in the corner rather than the middle.
 *
 * It does NOT fade with the play button. That one is for a clip that is not
 * running yet and has done its job once it is; a mute is wanted at the moment
 * the sound is bothering you, which is exactly while the clip runs. Smaller
 * and quieter instead — 44 against 60, and the ground a little lighter — so
 * two discs over one picture do not read as a pair of equals. */
/* The two corner controls — back to the beginning, and the sound. They share
 * a size and a ground, because they are the same kind of thing: small, always
 * reachable, and secondary to the play mark in the middle.
 *
 * Both sit at the foot on the right, the restart to the left of the sound, 16
 * from the edge and 60 apart — one disc plus its gap, stated here rather than
 * with a flex row, because each is positioned against the picture and a
 * wrapper would be a box with nothing else in it. */
.block-video-restart,
.block-video-sound {
    position: absolute;
    bottom: 16px;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background-color: rgb(0 0 0 / 0.4);
    color: var(--c-white);
    cursor: pointer;
    transition: transform 0.2s, background-color 0.2s;
}
.block-video-sound { right: 16px; }
.block-video-restart { right: 76px; }
.block-video-restart:hover,
.block-video-sound:hover {
    background-color: rgb(0 0 0 / 0.6);
    transform: scale(1.05);
}
/* Pressed means the sound is ON, so the crossed-out speaker is what you see
 * while it is OFF — the glyph shows the state, not the action. */
.block-video-mute { display: none; }
.block-video-sound[aria-pressed="true"] .block-video-unmute { display: none; }
.block-video-sound[aria-pressed="true"] .block-video-mute { display: inline; }

/* The zoom trigger and its badge — ported from ZoomableImage.vue. */
.block-zoom {
    position: relative;
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    line-height: 0;
    background: none;
    cursor: zoom-in;
}
.block-zoom-icon {
    position: absolute;
    right: 10px;
    bottom: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background-color: rgb(0 0 0 / 0.45);
    color: var(--c-white);
    transition: transform 0.2s, background-color 0.2s;
}
.block-zoom:hover .block-zoom-icon {
    background-color: rgb(0 0 0 / 0.65);
    transform: scale(1.1);
}

/* The lightbox, built by scripts/page/block-media.js on first use. Absent
 * entirely with JS off — the photograph under it is already full width, so
 * nothing is lost. */
.lightbox {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 40px;
    background: rgb(0 0 0 / 0.85);
    /* The ground closes it, so the cursor says so. */
    cursor: zoom-out;
}
.lightbox[open] { display: flex; }
.lightbox img {
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    box-shadow: 0 0 40px rgb(0 0 0 / 0.5);
    cursor: default;
}
.lightbox-close {
    position: absolute;
    top: 20px;
    right: 30px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--c-white);
    font-size: 40px;
    line-height: 1;
    cursor: pointer;
}

@media (max-width: 880px) {
    .block-audio { gap: 10px; }
    .lightbox { padding: 16px; }
}

/* ═══ chrome/breadcrumb ═══ */
/* The ancestor trail — layouts/chrome/breadcrumb.liquid.
 *
 * There is no original to port: the Vue build had no breadcrumb at all, since
 * its router had no notion of a page's parent. This follows the navigation's
 * idiom — condensed, uppercase, white — because it sits in the same band, over
 * the same photograph.
 *
 * Not scoped: chrome is shared by design.
 *
 * It is positioned inside the nav's gradient rather than above the content: on
 * a page that opens with a full-bleed photograph there is nothing else up
 * there, and a trail on its own white strip would push the picture down. */
.breadcrumb {
    position: absolute;
    top: 14px;
    left: var(--gutter);
    z-index: 31;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-condensed);
    font-size: 14px;
    text-transform: uppercase;
    color: var(--c-white);
    /* The photograph behind it is not uniform, so the trail carries its own
     * shadow rather than relying on the gradient alone. */
    text-shadow: 0 1px 6px rgb(0 0 0 / 0.6);
}

.breadcrumb a {
    text-decoration: none;
    border-bottom: 1px solid rgb(255 255 255 / 0.5);
}
.breadcrumb a:hover { border-bottom-color: var(--c-white); }

.breadcrumb-sep { opacity: 0.6; }

/* The current page is named, not linked — it is where you are. */
.breadcrumb [aria-current="page"] { opacity: 0.75; }

@media (max-width: 1023px) {
    /* The burger occupies the left edge on a narrow screen. */
    .breadcrumb { left: calc(var(--gutter) / 2 + 40px); font-size: 12px; }
}

/* ═══ chrome/credit ═══ */
/* The studio credit — layouts/chrome/credit.liquid.
 *
 * Ported from app/src/components/Copyright.vue, dimensions and all: a 25px
 * orange square fixed to the bottom-left corner, carrying the mark, which
 * widens to 128px on hover and lets the words out from behind `overflow`.
 *
 * Not scoped: chrome is shared by design.
 *
 * The mark is a background rather than an <img> because it is decoration on a
 * coloured tile — the link's text is its name, and the drawing adds nothing a
 * reader needs. It is `media/raw/`, so it ships as authored: an SVG has no
 * useful derivative.
 */
.credit {
    position: fixed;
    left: 40px;
    bottom: 30px;
    z-index: 999;
    width: 25px;
    padding: 5px 0 5px 38px;
    overflow: hidden;
    background-color: #f75f2d;
    background-image: url('../media/raw/decor/almero-mark.svg');
    background-repeat: no-repeat;
    background-size: 28px;
    background-position: 5px 6px;
    font-family: var(--font-body);
    font-size: 18px;
    line-height: 31px;
    color: var(--c-white);
    text-decoration: none;
    transition: width 0.5s;
}

.credit:hover,
.credit:focus-visible { width: 128px; }

@media (prefers-reduced-motion: reduce) {
    .credit { transition: none; }
}

/* Below 680 it stops following the viewport and settles at the end of the
 * page: fixed furniture over a phone screen costs more than it gives. */
@media (max-width: 680px) {
    .credit {
        position: absolute;
        right: 10px;
        bottom: 20px;
        left: auto;
        overflow: visible;
        padding-right: 90px;
    }
}

@media (max-width: 480px) {
    .credit {
        right: auto;
        left: 16px;
        bottom: 14px;
    }
}

/* ═══ chrome/footer ═══ */
/* The footer — layouts/chrome/footer.liquid.
 *
 * Ported from app/src/components/Footer.vue. Three columns: book an
 * appointment, the prices, and the frame motif. Below them the addresses.
 *
 * Not scoped: chrome is shared by design.
 *
 * The two photographs are chrome, not content — the design chose them and
 * they are the same on every page — so unlike a document's photograph they
 * stay backgrounds. The narrow one is a different crop, not the same file
 * scaled, which is why it is a second image rather than a background-size. */
.site-footer {
    position: relative;
    background-color: #d5d5d5;
    background-image: url('../media/web/chrome/footer.jpg');
    background-repeat: no-repeat;
    background-size: auto 100%;
    background-position: top center;
}

.sf-info {
    display: flex;
    max-width: 1280px;
    margin-inline: auto;
}

.sf-info-block,
.sf-frame {
    position: relative;
    width: calc(100% / 3);
}

.sf-info-block {
    /* No height: the block is as tall as what it holds, plus its padding.
     *
     * It was a flat 510. The 230 at the foot is what the frame beside it needs
     * to ride over, and with a fixed height on top of that the taller of the
     * two blocks had nowhere to go — the copy ran into the padding and the
     * foot of the block closed on it. */
    padding: 40px 0 230px 40px;
}

.sf-info-block h2 {
    width: 203px;
    max-width: 70%;
    margin: 0;
    font-family: var(--font-display);
    font-size: 35px;
    font-weight: 900;
    line-height: 1;
    text-transform: uppercase;
    color: var(--c-text);
}

.sf-info-block p {
    max-width: 70%;
    margin-block-start: 25px;
    font-family: var(--font-body);
    font-size: 16px;
    line-height: 20px;
    color: var(--c-text);
}

/* The button sits at the foot of its column rather than after the copy. */
.sf-info-block .main-button {
    position: absolute;
    bottom: 140px;
}

/* The second column carries the white ground the design puts behind it. */
.sf-info-block:nth-child(2) { background-color: var(--c-white); }

.sf-frame {
    overflow: hidden;
}
.sf-frame::after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 497px;
    background-image: url('../media/raw/decor/footer-frame.svg');
    background-repeat: no-repeat;
    background-size: 100% auto;
    background-position: right bottom -30px;
}

/* The drawing widens past its box before the layout changes — 120%, clipped by
 * the frame's own `overflow: hidden`. */
@media (max-width: 1080px) {
    .sf-frame::after { width: 120%; }
}

/* ── the addresses ────────────────────────────────────────────────────── */
/* The addresses sit on their own white ground, BELOW the photograph — in the
 * Vue markup they were a sibling of <footer> rather than inside it, which is
 * what gave them that. Inside the footer they inherit the photograph, and
 * dark text over a picture of a suit is unreadable. */
.sf-contacts {
    position: relative;
    z-index: 1;
    display: flex;
    justify-content: space-around;
    background-color: var(--c-white);
}

.sf-address {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 40px 20px;
    font-style: normal;
    text-align: center;
}

/* 25px and NOT uppercased. This read `font-size: 18px; font-weight: 700;
 * text-transform: uppercase` — three inventions, and the last one against a
 * `text-transform: none` the component states outright. */
.sf-address h2 {
    margin: 0;
    font-family: var(--font-condensed);
    font-size: 25px;
    font-weight: 900;
    color: var(--c-black);
    text-transform: none;
}

.sf-address p {
    margin: 0;
    font-family: var(--font-body);
    font-size: 16px;
    line-height: 1.6;
    color: var(--c-ink);
}

/* AFTER `.sf-address p { margin: 0 }`, and on `p` rather than `*`.
 *
 * It was `h2 + *` and it sat above that rule. Equal specificity — one class
 * and one element each way — so the later one won and the heading's 15px was
 * simply never applied: measured 0px between the heading and the line under
 * it on all three blocks. A reset that follows the rule it is meant to be
 * reset BY is the whole failure. */
.sf-address h2 + p { margin-block-start: 15px; }

.sf-address a { color: var(--c-black); }

@media (max-width: 760px) {
    .site-footer {
        background-image: url('../media/web/chrome/footer-narrow.jpg');
        background-size: cover;
        background-position: 0 350px;
    }

    .sf-info { flex-wrap: wrap; }

    .sf-info-block {
        width: 100%;
        /* No height at all: the block is as tall as what it holds.
         *
         * It was a flat 368, which the copy filled — two lines of heading,
         * five of paragraph and the button — so the only air under the button
         * was the 20px of padding and the block ended all but against it. A
         * minimum was the first answer and still a number to defend; without
         * one the two blocks size themselves and the padding is the whole of
         * the spacing. */
        padding: 20px 5% 56px;
    }
    /* Prices first on a narrow screen, then the appointment — the order the
     * design puts them in when they stack. */
    .sf-info-block:nth-child(1) { order: 2; }
    .sf-info-block:nth-child(2) { order: 1; }
    /* `margin-top: -128px` with the rest: the frame rides up into the block
     * above it, which is what closes the gap the reordering opens. */
    .sf-frame { order: 3; width: 70%; height: 576px; margin-top: -128px; margin-left: 21%; }

    /* A different drawing below 760 — the wide SVG is a landscape composition
     * and the mobile PNG is drawn for a tall box. It fills the frame from the
     * top rather than hanging off its bottom edge. */
    .sf-frame::after {
        width: 100%;
        height: 100%;
        background-image: url('../media/raw/decor/footer-frame-mobile.png');
        background-position: center top;
    }

    .sf-info-block .main-button { position: static; }

    .sf-contacts { flex-direction: column; padding: 0 5% 60px; }

    /* Stacked, left-aligned, and ruled apart — centring three blocks one
     * under another leaves them looking unrelated. */
    .sf-address {
        flex: none;
        width: 100%;
        align-items: flex-start;
        padding: 24px 0;
        text-align: left;
    }
    .sf-address + .sf-address { border-block-start: 1px solid #e0e0e0; }
}

@media (max-width: 640px) {
    .sf-frame { height: 436px; width: 60%; margin-left: 33%; }
}

@media (max-width: 480px) {
    /* The band of photograph that closes the picture sits ABOVE the
     * addresses, not under them.
     *
     * The component writes this as `padding-bottom` on the footer itself and
     * gets away with it because there the addresses are a SIBLING of
     * <footer>, outside it — the padding is the last of the picture and the
     * white block follows. Ours are inside, which is what gives them their
     * own white ground, so the same padding landed below them and the page
     * ended with a strip of suit under the contact details. It belongs to the
     * picture, so it goes on the picture's block. */
    .sf-info { padding-bottom: 80px; }
    .sf-info-block { padding-inline: 4%; }
    .sf-frame { width: 86%; margin-top: -26px; margin-left: 7%; }
}

/* Half the band on the narrowest screens. The component says so and it had
 * not come across. */
@media (max-width: 380px) {
    .sf-info { padding-bottom: 40px; }
}

/* ═══ chrome/nav ═══ */
/* The navigation — layouts/chrome/nav.liquid.
 *
 * Ported from the style block of app/src/components/Navigation.vue, with one
 * deliberate translation: every `.mobile &` / `.not-mobile &` becomes a media
 * query.
 *
 * Those classes were put on a root element by vue-easy-responsiveness, from a
 * width MEASURED IN JAVASCRIPT — `app.use(responsiveness, { laptop: 1023 })`
 * in main.js. A server-rendered page cannot know the viewport, so the first
 * paint would be styled for the wrong one until the script ran and reclassed
 * the document. The breakpoint is the same 1023px the app declared.
 *
 * Not scoped: chrome is shared by design.
 *
 * The nav sits OVER the page rather than above it — every page opens with a
 * full-bleed photograph and the menu is white on top of it, fading out of a
 * gradient. That is why it is fixed and the page does not reserve space. */
/* `display: contents`, so the two bars are children of <body> and not of a box
 * of their own. That is what lets the links bar be `position: sticky`: sticky
 * travels inside its containing block, and a wrapper only as tall as the nav
 * gives it nowhere to go. The element stays for the script to attach to.
 * lmed-web does the same with its own site-nav. */
site-nav { display: contents; }

/* ── the links: the bar that stays ────────────────────────────────────── */
/* `nav.desktop` in the component — sticky at the top, with its own dark
 * ground, above everything. It is FIRST in the document for that reason. */
.nav-menu {
    position: sticky;
    top: 0;
    z-index: 9999;
    text-align: center;
    /* Transparent until the bar pins, then the ground fades in under it —
     * scripts/page/nav-stuck.js puts `is-stuck` on at that moment and the
     * rule below paints it.
     *
     * The ground is what keeps white links legible, so between the two states
     * they are over the photograph with nothing behind them. It reads while
     * the page opens on a dark picture, which every one of them does; a light
     * one would swallow them, and the answer then is to carry the wordmark's
     * gradient down over this bar rather than to paint it solid again. */
    background-color: rgb(0 0 0 / 0);
    /* No transition on the way out, against the 0.25 that brings it in. A
     * transition belongs to the state it goes TO, so this one governs the
     * leaving — and leaving is the half nobody asked to watch: the reader has
     * scrolled back to the top and is looking at the picture, not at the
     * chrome getting out of the way.
     *
     * 0 rather than a small number. It was 0.25, then 0.1, and 0.1 read as no
     * different from 0.25 — which is fair, since a fade that short is at the
     * edge of being seen at all. Below about 0.08 the choice is not between
     * two speeds but between a fade and none, so this takes none. */
    transition: none;
    padding-block: 10px 4px;
    /* The pull that puts the page under the chrome, and it lives on the LAST
     * of the two bars — whichever that is. It used to be the wordmark's; with
     * the wordmark first, a negative margin there would drag this bar up onto
     * it instead of the page up under both.
     *
     * 166 is what the two measure: 24 of padding, an 84px mark (150 wide at
     * the viewBox's 78.3 by 43.8) and its 15 below it, then this bar's 10 and
     * 4 around a 19px link with 6 under it. Calculated, not measured on a
     * screen — if the first photograph starts a hair high or low, this is the
     * number to move. */
    margin-bottom: -166px;
}

/* Desktop only, and by a media query rather than by a check in the script.
 * Below 1023 this same element is the drawer, `position: fixed; inset: 0`,
 * which never reports a full ratio and therefore always carries the class —
 * and `.nav-menu.is-stuck` at 0-2-0 would repaint the panel's own 0.8 ground
 * with this 0.6 wherever it applied. */
@media (min-width: 1024px) {
    .nav-menu.is-stuck {
        background-color: rgb(0 0 0 / 0.6);
        transition: background-color 0.25s ease;
    }

    /* And the submenu with it: pinned, the bar is a surface and the panel is
     * part of it; unpinned, both are over the photograph and neither takes a
     * ground. */
    .nav-menu.is-stuck .nav-subitems { background: rgb(0 0 0 / 0.6); }
}

/* ── the wordmark: the block that leaves ──────────────────────────────── */
/* `.main-menu` in the component. It scrolls away under the links bar, and it
 * pulls the page up beneath it so the first photograph starts at the top of
 * the window rather than below a strip of chrome.
 *
 * The component writes that pull as `.main-menu + * { margin-top: -226px }`.
 * Here it is a negative margin-bottom on the block itself, which does the
 * same thing without having to know what follows — a breadcrumb on some
 * pages, an <h1> on others, <main> on the rest. */
.nav-bar {
    position: relative;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Flat at the top of the page now. The 44px lift, the 80px skirt and the
     * 73px the mark was nudged down by were all one arrangement: the mark hung
     * BELOW the links bar and had to be lowered past it and given room to hang
     * in. First in the document it needs none of that — it opens the page and
     * the links sit under it. */
    padding-block: 24px 0;
    background: linear-gradient(to bottom, rgb(0 0 0 / 0.6) 0, rgb(0 0 0 / 0) 100%);
    /* The transparent half of the gradient must not swallow clicks meant for
     * the photograph under it. */
    pointer-events: none;
}
.nav-bar a,
.nav-bar button { pointer-events: auto; }

.nav-brand { display: inline-block; }
.nav-logo {
    /* 150px, not 78. The component says `width: 150px` and 110 below the
     * breakpoint; 78 was a guess and the mark had been half its size on every
     * page since. */
    width: 150px;
    height: auto;
    margin-bottom: 15px;
    fill: var(--c-white);
}

/* ── the menu ─────────────────────────────────────────────────────────── */
.nav-items {
    /* The component says this outright — `nav.desktop ul { line-height:
     * normal }` — and it was missing, so the list inherited base.css's 1.3.
     * It matters because the sliding rule animates to `top: 0`, the top edge
     * of the link's own box: a shorter line box puts that edge closer to the
     * letters. */
    line-height: normal;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The language switcher, at the end of the row. Smaller than a nav link and
 * without the sliding rule: it is a control, not one of the destinations the
 * menu is a list of, and giving it the same weight would read as an eighth
 * page. The current language is stated and not offered — a link to the page
 * you are on is a dead end wherever it appears. */
.nav-langs {
    line-height: normal;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 6px 20px;
    padding: 0;
    list-style: none;
}
.nav-lang > a,
.nav-lang > span {
    font-family: var(--font-body);
    font-size: 15px;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--c-white);
}
.nav-lang > a { border-bottom: 1px solid rgb(255 255 255 / 0.5); }
.nav-lang > a:hover { border-bottom-color: var(--c-white); }
/* Dimmed rather than hidden: the pair is what says the site has two
 * languages, and showing only the other one says nothing about which you
 * are in. */
.nav-lang-current { opacity: 0.55; }

.nav-item { position: relative; display: inline-block; }

.nav-item > a {
    position: relative;
    display: inline-block;
    margin: 0 10px 6px;
    /* 2rem in the source, under `html { font-size: 60% }` — so 19.2px, not
     * 32px. The root font-size is not changed here, so the value is written
     * in px rather than carried over and silently multiplied. */
    font-size: 19px;
    font-family: var(--font-body);
    text-transform: uppercase;
    text-decoration: none;
    color: var(--c-white);
    border-bottom: 1px solid var(--c-white);
}

/* A second rule slides down from above the word on hover. */
.nav-item > a::after {
    content: "";
    position: absolute;
    left: 0;
    top: -12px;
    width: 100%;
    height: 1px;
    background: var(--c-white);
    opacity: 0;
    transition: top 0.3s, opacity 0.3s;
}
/* Lands 2px ABOVE the link's top edge, not on it.
 *
 * A DELIBERATE departure: the component animates to `top: 0`, which is the
 * top of the inline box and therefore about half a pixel above the letters —
 * the rule reads as touching the word rather than sitting over it. Restoring
 * the component's `line-height: normal` above bought 0.6px, so the closeness
 * is the design's, not a porting slip.
 *
 * 2px reads as a separate rule and stays inside the bar's 10px top padding.
 * 10px is the ceiling here and it is the WINDOW, not the bar: the menu is
 * stuck to the top of the viewport and the link starts 10px below it, so
 * anything above -10px animates off-screen. 11px was tried and the line
 * simply vanished. */
.nav-item > a:hover::after { top: -2px; opacity: 1; }

/* The page you are on is named, not linked. */
.nav-item > a[aria-current="page"] {
    border-bottom: none;
    pointer-events: none;
}

/* ── submenu ──────────────────────────────────────────────────────────── */
.nav-subitems {
    position: absolute;
    top: 36px;
    left: 0;
    min-width: 280px;
    margin: 0;
    padding: 0 0 6px;
    list-style: none;
    /* No ground of its own. It used to carry the same 0.6 black the bar wore
     * at all times, so the two read as one surface; with the bar transparent
     * until it pins, that panel hung under it as a loose rectangle on the
     * photograph. It takes the ground only when the bar has one — see the
     * stuck rule below. */
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
}
/* A gap between the item and its submenu would end the hover, so the panel
 * reaches up to meet it. */
.nav-subitems::before {
    content: "";
    position: absolute;
    top: -6px;
    left: 0;
    width: 100%;
    height: 6px;
}
.nav-item:hover .nav-subitems,
.nav-item:focus-within .nav-subitems {
    opacity: 1;
    pointer-events: auto;
}
.nav-subitems li { display: block; margin-top: 6px; text-align: left; }
.nav-subitems a {
    display: inline-block;
    margin: 0 10px 6px;
    font-size: 15px;
    font-family: var(--font-body);
    text-transform: uppercase;
    text-decoration: none;
    color: var(--c-white);
    /* The rule underneath, same as the top row has.
     *
     * In the component this is not written for the submenu at all — the
     * `border-bottom` sits on a plain `a` nested inside `nav.desktop`, so it
     * reaches every link in there, and the submenu block overrides only the
     * font size and the sliding `::after`. Flattening that nesting into a
     * separate `.nav-subitems a` rule here dropped the border with it, and the
     * two levels stopped looking like the same kind of thing. */
    border-bottom: 1px solid var(--c-white);
}

/* ── the burger, and the drawer it opens ──────────────────────────────── */
/* Fixed to the viewport at 38px, not centred in the bar: it stays put while
 * the wordmark and the page scroll under it. */
.nav-burger {
    display: none;
    position: fixed;
    top: 38px;
    /* Not the gutter itself: what the eye lines up on is the white disc
     * behind the bars, and that disc is 48px wide on a 26px button — it
     * overhangs 11px each side. Offsetting the button by those 11px puts the
     * DISC's left edge on the gutter, where the page's content starts. */
    left: calc(var(--gutter) + 11px);
    width: 26px;
    height: 26px;
    padding: 0;
    border: 3px solid transparent;
    background: none;
    cursor: pointer;
}

/* A white disc behind the bars. Without it the burger is black-on-black over
 * a dark photograph and black-on-white over a light one — it had been white
 * bars on nothing here, which is the same problem the other way round. */
.nav-burger::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    /* 48px: the component writes `width: 24px; padding: 12px`, and its reset
     * is `* { box-sizing: border-box }` — which does not match `::after`, so
     * that padding is added on top. 24 + 2 x 12. */
    width: 48px;
    height: 48px;
    background-color: var(--c-white);
    border-radius: 50%;
    z-index: 20;
}

/* The bars are skewed, which is the mark's own slant. Black, because they sit
 * on the disc. */
.nav-burger span {
    position: absolute;
    left: 0;
    z-index: 30;
    width: 100%;
    height: 4px;
    background: var(--c-black);
    transform: rotate(0deg) skewX(-40deg);
    transition: 0.2s ease-in-out;
}
.nav-burger span:nth-child(1) { top: 0; }
.nav-burger span:nth-child(2),
.nav-burger span:nth-child(3) { top: 7px; }
.nav-burger span:nth-child(4) { top: 15px; }

/* Open: the outer two fade into the middle and the inner two cross. */
site-nav.open .nav-burger span:nth-child(1),
site-nav.open .nav-burger span:nth-child(4) { top: 10px; opacity: 0; }
site-nav.open .nav-burger span:nth-child(2) { transform: rotate(50deg) skewX(-40deg); }
site-nav.open .nav-burger span:nth-child(3) { transform: rotate(-44deg) skewX(-40deg); }

/* The mark inverts with them: a white cross, and the disc goes.
 *
 * The disc exists to keep a black mark legible over a photograph nobody
 * chose — it is a patch of known ground. Once the panel is down the ground IS
 * known and it is dark, so the disc becomes a white blot with an invisible
 * black cross in it. That is the "black icon" in the open menu. */
site-nav.open .nav-burger::after { opacity: 0; }
site-nav.open .nav-burger span { background: var(--c-white); }

@media (max-width: 1023px) {
    .nav-burger { display: block; }

    .nav-brand { transform: translateY(24px); }
    .nav-logo { width: 110px; }

    /* The wordmark block sits flat below the breakpoint: no 44px lift, no
     * 80px skirt, and the page is pulled up 80px instead of 226 — the two
     * figures the component gives for the collapsed layout. */
    /* A different gradient here — .7 down to nothing by 80%, against the
     * desktop .6 down to nothing at 100%. `.main-menu` states both. */
    .nav-bar {
        top: 0;
        /* Both sides, not just the foot. The wide screen gives this block 24px
         * above the mark now that it opens the page rather than hanging below
         * the links; here it needs none, and the 24 it was inheriting made the
         * header that much taller while the pull below stayed at -80 — so the
         * first photograph started a grey band short of the top of the window.
         *
         * The mark is still lowered here, by the 24px transform on
         * `.nav-brand` further down, which moves it without taking height. */
        padding-block: 0;
        margin-bottom: -80px;
        background: linear-gradient(to bottom, rgb(0 0 0 / 0.7) 0, rgb(0 0 0 / 0) 80%);
        /* ABOVE the drawer, which is the whole point of the burger.
         *
         * The drawer is `position: fixed; inset: 0` at z-index 9999, and the
         * burger is its SIBLING with no stacking of its own, so the open
         * drawer painted over the button: the icon went black under 95%
         * black, and the tap that should close the menu landed on the panel
         * instead. The menu could be opened and not shut.
         *
         * The component has the same two layers the other way round —
         * `.main-menu-wrapper { z-index: 30 }` over `nav.mobile { z-index:
         * 10 }` — because there the burger lives inside the menu block. It
         * cannot here: site-nav is `display: contents` and the two bars are
         * children of <body>, so the order is written out instead.
         *
         * Only the burger and the wordmark take clicks (pointer-events
         * above); the rest of this box stays transparent to them, so the
         * drawer's links are still reachable underneath. */
        z-index: 10000;
    }

    /* Below the breakpoint the menu IS the drawer: it stops being a bar with
     * a ground of its own and becomes a panel over the page. */
    .nav-menu {
        position: fixed;
        inset: 0;
        display: block;
        /* Not the desktop pull. `inset: 0` pins all four edges, and a negative
         * bottom margin on top of that hangs the panel 166px below the window
         * — a drawer taller than the screen it covers. */
        margin-bottom: 0;
        /* The list starts at the top, not in the middle of the screen: 120px
         * down, which clears the wordmark, and on the page's own margin. The
         * component says `padding-top: 120px` and `padding: 0 calc(var(
         * --space-between) * 2)`; the 24px it spells out there is its gutter,
         * and ours is `--gutter`, the same one the burger now sits on.
         *
         * `text-align: left` because the bar above inherits `center` from the
         * desktop layout, where the links are a centred row. Here they are a
         * column and each one's rule should end where its word does. */
        padding: 120px var(--gutter) 0;
        text-align: left;
        /* .8, not .95. The photograph is meant to read through the panel. */
        background: rgb(0 0 0 / 0.8);
        transform: translateY(-100%);
        transition: transform 0.3s cubic-bezier(0.2, 0, 0.2, 1);
    }
    site-nav.open .nav-menu { transform: translateY(0); }

    .nav-items { display: block; }
    .nav-item { display: block; }

    /* 2rem at the old 60% root, which is 19.2px — not the 22 this had. */
    /* 8px aside. The component gives 4 below the breakpoint, against the
     * desktop row's 10 — this is Georgi's figure, chosen looking at the
     * drawer: 4 sat the column a shade too close to the edge. */
    .nav-item > a { font-size: 19.2px; margin: 0 8px 6px; }

    /* 14px between items, which had not come across: the drawer's links were
     * running together on their own line heights. */
    .nav-item + .nav-item > a { margin-top: 14px; }

    /* On a small screen a submenu is not a hover panel; it is simply the
     * items under their heading. */
    /* The submenu is indented under its heading and set smaller — 18px in,
     * 15px type — and loses the rule that animates in over a desktop link. */
    .nav-subitems {
        padding-left: 18px;
        position: static;
        opacity: 1;
        pointer-events: auto;
        min-width: 0;
        background: none;
    }

    /* The same 4px aside as the row above it, so the indent the eye reads is
     * the submenu's own 18px and not 18 plus a leftover desktop margin. */
    .nav-subitems a { font-size: 15px; margin: 0 4px 6px; }
    .nav-subitems a::after { content: none; }
}

/* ═══ sections/article-header ═══ */
/* The interview header — layouts/sections/article-header.liquid.
 *
 * Ported from the style block of app/src/components/Celebrity/CelebrityHeader.vue.
 * The photograph was a background there, chosen between desktop and mobile by
 * two custom properties and a media query; it is a <picture> here, so the
 * browser picks the file before any script runs — see the partial. */
@scope (.article-header) {
    :scope {
        position: relative;
        height: 793px;
        color: var(--c-white);
        /* The blocks below carry 50px at their foot and nothing at their head,
         * so the gap between any two of them is 50px. The header had none, and
         * its photograph ends where the first block's begins — the copy is
         * absolute inside it, so nothing else was holding the two apart. */
        margin-block-end: 50px;
    }

    .article-header-bg,
    .article-header-bg img {
        position: absolute;
        inset: 0;
        z-index: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        /* `center top`, which is CelebrityHeader's default. */
        object-position: center top;
    }

    /* The copy sits at 60% of the height and is anchored to the CONTENT COLUMN,
     * exactly as the page headers are — `.summary` is absolute inside
     * `<Content>`, which is a centred 1280 with 50px of padding. This was a
     * centred grid with a gutter, so on a wide screen the copy stood some 320px
     * too far left and a good deal too high. Same calc as sections/hero.css,
     * and the same reason it is a calc: the column exists here only to place
     * one block. */
    .article-header-content {
        position: absolute;
        top: 60%;
        left: calc(max(0px, (100% - 1280px) / 2) + 50px);
        z-index: 1;
        width: 456px;
        transform: translateY(-50%);
    }

    /* The series title, and the one heading on this site set in Intro — the
     * base gives every h1 that family and CelebrityHeader is the only component
     * that does not override it. See styles/tokens/fonts.css. */
    .article-series {
        margin: 0;
        font-family: var(--font-intro);
        font-weight: 900;
        /* 24, down from 35. It is the kicker above the name, and at 35 it was
         * the largest thing in the header — bigger than the 28.8px name it
         * introduces — so it read as the headline and took two lines doing it. */
        font-size: 24px;
        line-height: 1.1;
        text-transform: uppercase;
        color: var(--c-white);
    }

    /* The subject's name. */
    .article-name {
        margin-block-start: 20px;
        font-family: var(--font-body);
        font-weight: 900;
        /* 44. The port had 3rem at the old 60% root, which is 28.8px — faithful
         * to the Vue build, but it left the name smaller than the kicker above
         * it. The weight was already 900, so size is the only lever the header
         * has left for it. */
        font-size: 44px;
        line-height: 1.1;
        text-transform: uppercase;
        color: var(--c-white);
    }

    /* Upright and 20px, as the text under the title is in every page
     * header — see `.hero-content p` in sections/hero.css. */
    .article-standfirst p {
        margin-block-start: 25px;
        font-family: var(--font-body);
        font-size: 20px;
        line-height: 1.4;
    }

    /* Open Sans, not the condensed cut; 15px above it, not 25; and the colour
     * is stated rather than reached with opacity — `opacity` fades the whole
     * box, which here is the same thing only by accident. */
    .article-date {
        margin-block-start: 15px;
        font-family: var(--font-display);
        font-style: normal;
        font-size: 14px;
        letter-spacing: 1px;
        text-transform: uppercase;
        color: rgb(255 255 255 / 0.7);
    }

    /* The credits under the date, in the date's voice — it is the same small
     * print — but spaced as a group with it rather than as another paragraph.
     * `flex` with a gap, so two credits sit apart without a separator written
     * into the markup, and a narrow screen wraps them instead of overflowing. */
    .article-credits {
        display: flex;
        flex-wrap: wrap;
        gap: 4px 16px;
        margin-block-start: 6px;
        font-family: var(--font-display);
        font-size: 14px;
        letter-spacing: 1px;
        text-transform: uppercase;
        color: rgb(255 255 255 / 0.7);
    }

    /* The same as a page header below 1024: the screen's height rather than a
     * fixed 793, in `svh` so the browser's bars are counted in and nothing
     * moves while you scroll, with a floor for a phone held sideways. See the
     * long note in sections/hero.css. */
    @media (max-width: 1023px) {
        :scope {
            height: 100vh;
            height: 100svh;
            /* 640, not the page headers' 560 — the same reason the contacts
             * header carries it: the copy here runs long. Below 761 it is no
             * longer what keeps the copy off the logo; see the next block. */
            min-height: 640px;
        }
    }

    /* On a phone the copy is anchored to the FOOT and grows upward, and a
     * fixed height can only be measured against the text of the day. 640 was:
     * it cleared the logo by 23px at 320 wide when it was set, then the name
     * went to 44px and the credits arrived, and the series title ran 26px
     * into the mark at 360. So here the copy is in flow at the foot and the
     * header grows to hold it — the screen, 640, or the copy plus the room
     * above it, whichever is tallest. Where the copy fits, nothing moves:
     * box-sizing is border-box, so the padding sits inside the min-height.
     *
     * 125 above: the bar pulls the page up under itself by 80px and lowers
     * the mark a further 24 by a transform (styles/chrome/nav.css), so the
     * mark's foot is some 100px down the header. 125 leaves 21 clear of it,
     * measured, close to the 23 that 640 once gave. */
    @media (max-width: 760px) {
        :scope {
            display: flex;
            flex-direction: column;
            justify-content: flex-end;
            height: auto;
            min-height: max(640px, 100vh);
            min-height: max(640px, 100svh);
            /* Nothing under the copy — asked for. It sits on the foot of the
             * header, and the header's own foot is where the picture ends.
             *
             * Written as `0svh` beneath the shorthand, keeping the pair this
             * block already used: the shorthand is the fallback for a browser
             * without the unit, the second line is what the rest run. */
            padding-block: 125px 0;
            padding-block-end: 0svh;
        }
        .article-header-content {
            position: relative;
            inset: auto;
            margin-inline-start: 5%;
            width: 300px;
            max-width: 90%;
            transform: none;
        }
        /* 30 rather than 44 — asked for, in two steps. The copy box is 300px
         * name ran to three lines at 44, which made it the whole header; at
         * 30 it sets on two at every phone width down to 320 and still stands
         * half as large again as the kicker over it. */
        .article-name { font-size: 30px; }
        /* The kicker with it — 18 from 24. It still takes two lines at 300px
         * wide, which 27 characters will at any size the header can give
         * them, but it stops competing with the name under it. */
        .article-series { font-size: 18px; }
        /* The date and the credits come down with it — 12 from 14. They are
         * the small print of the header and at 14 they held their own against
         * the standfirst above them, which is the line that matters. */
        .article-date,
        .article-credits { font-size: 12px; }
        .article-standfirst p { margin-block-start: 20px; font-size: 16px; line-height: 1.3; }
    }
}

/* ═══ sections/banner ═══ */
/* The full-bleed teaser band — layouts/sections/banner.liquid.
 * Ported from app/src/components/Banner.vue. */
@scope (.banner) {
    /* NOT full-bleed. In Home.vue the banner sits INSIDE `<Content>` —
     * `.content-wrapper`, max-width 1280 with 50px padding — so it is at most
     * 1180px and centred. `Cta` and the rest come after `</Content>` and those
     * ARE full width; this one is not, and the comment in the partial claiming
     * otherwise was wrong with it.
     *
     * Written as the column's content box: min(1280, viewport) − 100. */
    :scope {
        position: relative;
        width: calc(100% - 100px);
        max-width: 1180px;
        margin-inline: auto;
        /* Collapses with the text block's 50 above it to the single 50 the
         * wrapper puts between its children; below is the wrapper's own
         * bottom padding, 75 under 1280px. */
        margin-block: 50px;
        min-height: 380px;
        height: 80vh;
        max-height: 400px;
        color: var(--c-white);
    }

    /* See the note in sections/hero.css: a document's photograph is an <img>. */
    .banner-bg {
        position: absolute;
        inset: 0;
        z-index: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        object-position: center;
    }

    .banner-inner {
        position: relative;
        z-index: 1;
        height: 100%;
    }

    /* The stacked wordmark, set left of centre. */
    .banner-label {
        position: absolute;
        top: 50%;
        left: 240px;
        transform: translateY(-50%);
        margin: 0;
        text-align: center;
    }

    .banner-word {
        display: block;
        font-family: 'Bodoni', Georgia, serif;
        font-size: 80px;
        font-weight: 500;
        line-height: 0.9;
        /* The photograph behind it is not uniform, so the words carry their
         * own shadow rather than relying on a scrim. */
        text-shadow: 6px 4px 14px #000;
    }

    /* The middle word is smaller, spaced, and flanked by two rules. */
    .banner-word--accent {
        position: relative;
        display: inline-block;
        margin-top: 12px;
        font-size: 40px;
        font-weight: 100;
        font-style: normal;
        text-transform: uppercase;
    }
    .banner-word--accent::before,
    .banner-word--accent::after {
        content: "";
        position: absolute;
        top: 50%;
        width: 40px;
        height: 3px;
        transform: translateY(-50%);
        background-color: var(--c-white);
        box-shadow: 6px 4px 16px #000;
    }
    .banner-word--accent::before { left: -50px; }
    .banner-word--accent::after { right: -50px; }

    /* Vertically centred and 344px wide, against the right edge — not pinned
     * to the bottom corner, which is where this had it. */
    .banner-info {
        position: absolute;
        top: 50%;
        right: 112px;
        width: 344px;
        transform: translateY(-50%);
    }

    /* DARK, and no shadow. The band's right-hand side is the light part of the
     * photograph, and the component sets `#303030` here — it turns this white
     * only below 760px, where the block moves onto the picture itself. This
     * had it white with a shadow at every width, which is the mobile treatment
     * applied everywhere. */
    .banner-intro {
        margin: 0;
        font-family: var(--font-body);
        font-size: 16px;
        line-height: 1.3;
        color: var(--c-ink);
    }

    @media (max-width: 1280px) {
        :scope { margin-block-end: 75px; }
    }

    @media (max-width: 1080px) {
        .banner-label { left: 160px; }
        .banner-info { right: 60px; }
    }

    @media (max-width: 760px) {
        /* Edge to edge. The band was 90% wide with the page's margin around
         * it, so the photograph sat in a box with white down both sides; it
         * runs the full width of the screen and the 5% padding indents the
         * words INSIDE the picture instead. */
        :scope { width: 100%; height: auto; max-height: none; padding: 5%; margin-block-end: 20px; }
        /* 26px, the figure the component gives `.info` when it goes static. */
        .banner-inner { display: grid; gap: 26px; }
        .banner-label, .banner-info {
            position: static;
            transform: none;
            width: 100%;
        }
        /* CENTRED, both of them.
         *
         * The label is centred at every width — `.banner-content { text-align:
         * center }` is never overridden — and below 760 the component centres
         * the block under it too. This had both set to left, which is the one
         * arrangement the design never asks for. */
        .banner-label, .banner-info { text-align: center; }
        /* On the picture now, so it takes the light treatment the component
         * gives it here and nowhere else. */
        .banner-intro {
            color: var(--c-white);
            text-shadow: 6px 4px 16px #000;
        }
        /* And the button turns with it. `dark` is black on the light right-
         * hand side of the photograph; over the picture itself it has to be
         * white, which the component states here and only here. Ours kept the
         * black one, so the outline all but vanished into the suit. */
        .banner-info .main-button {
            border-color: var(--c-white);
            color: var(--c-white);
        }
    }
}

/* Outside the scope on purpose: `@scope` cannot reach a sibling of its own
 * root, and this rule is about what FOLLOWS the band — `& + * { margin-top:
 * 25px }` in the component. */
.banner + * { margin-block-start: 25px; }

/* ═══ sections/blocks ═══ */
/* The editorial sequence — layouts/sections/blocks.liquid.
 *
 * One scope for the run, because the block types share their two cells and
 * differ only in how those are placed. `.blocks` is the root; `.block` and its
 * modifiers are descendants, so they are addressed as bare classes here. */
@scope (.blocks) {
    /* `flow-root` rather than `block`: the two `shot` blocks float, and this is
     * what keeps them inside the sequence instead of under the section that
     * follows it. A no-op for every other block type. */
    :scope { display: flow-root; }

    /* One measure for the whole sequence, as BlogSection.vue set on each
     * block: 1280px with a 50px gutter, 5% below 880px. */
    .block {
        display: flex;
        max-width: 1280px;
        margin-inline: auto;
        padding: 0 50px 50px;
    }

    /* ── the prose cell ───────────────────────────────────────────────── */
    /* `.block-heading` is the cross block's upper cell. It is a `.blog-content`
     * in the original — same element, same rules — and here it has a name of
     * its own so the two cells of a cross can be addressed separately. That
     * name meant it matched none of the heading rules below and fell back to
     * the base: Open Sans at the browser's default size, no uppercase, no
     * raised initial. Every cross block on the site, in the wrong typeface. */
    .block-content h2,
    .block-heading h2 {
        font-family: var(--font-display);
        font-size: 25px;
        font-weight: 900;
        color: var(--c-black);
    }
    .block-content p {
        margin-block-start: 12px;
        font-family: var(--font-body);
        font-size: 16px;
        font-weight: 400;
        line-height: 1.3;
        color: var(--c-ink);
    }
    .block-content > :first-child { margin-block-start: 0; }

    /* A link inside prose is a link, not a button — the block's own button
     * styling would otherwise swallow it. */
    /* The last paragraph gives up its bottom margin.
     *
     * default.less opened with `* { margin: 0 }`, so in the component NOTHING
     * carries a bottom margin and every gap in a block is a top margin — 12px
     * between paragraphs, 20px above a button. Our base gives `p` a 1em
     * bottom instead. Between two paragraphs it collapses into the 12 above
     * the next one and never shows; against the BUTTON it does, because
     * `.main-button` is `inline-block` and an inline-level box does not
     * collapse margins with its siblings. So the gap over a button was
     * 16 + 20 = 36px.
     *
     * The same fault, and the same fix, as `.gift-body > :last-child` in
     * sections/gift.css. */
    .block-content p:last-of-type { margin-block-end: 0; }

    .block-content p a {
        font-family: var(--font-body);
        font-size: 16px;
        line-height: 1.3;
        text-decoration: underline;
        color: inherit;
    }

    /* ── the pull quote ───────────────────────────────────────────────── */
    /* Three gold rules around the passage, with the attribution straddling
     * the open top edge — the <em> elements either side of the name ARE the
     * top rule, which is why they are empty and why they grow. */
    .block-content blockquote {
        position: relative;
        margin: 0;
        text-align: center;
        border-right: 8px solid var(--c-gold);
        border-bottom: 8px solid var(--c-gold);
        border-left: 8px solid var(--c-gold);
    }
    .block-content blockquote h2 {
        position: absolute;
        top: 0;
        width: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        margin: 0;
        transform: translateY(-50%) translateY(4px);
        font-family: var(--font-body);
        text-align: center;
    }
    .block-content blockquote h2 em {
        flex-grow: 1;
        min-width: 10%;
        height: 7px;
        background-color: var(--c-gold);
    }
    .block-content blockquote h2 strong { padding: 8px; }
    .block-content blockquote p {
        padding: 30px 30px 25px;
        font-style: italic;
    }
    .block-content blockquote p + p { padding-top: 0; }

    /* The button inside a block is its own design, not the shared one:
     * BlogSection.vue styles a bare `a` rather than reaching for MainButton —
     * a size down at 24px, the lightest weight, a grey hairline instead of
     * the text colour, and a minimum width so two buttons on a page match
     * whatever their labels say. Only the differences are stated; the
     * slide-on-hover comes from .main-button. */
    .block-content .main-button {
        /* No vertical padding here, so the span carries none either — see the
         * note on `--btn-pad-y` in components.css. */
        --btn-pad-y: 0;
        margin-block-start: 20px;
        padding: 0 36px;
        font-size: 24px;
        font-weight: 100;
        text-align: center;
    }
    /* The label's weight comes from the button, as it does in the original —
     * a bare `a` whose span inherits `lighter`. `.main-button span` in
     * components.css sets 500, which drew this one bolder than the border
     * around it. `line-height: normal` for the same reason: the shared button
     * states 1.2 and the original leaves it alone. */
    .block-content .main-button {
        line-height: normal;
    }
    .block-content .main-button span {
        min-width: 110px;
        font-weight: inherit;
    }

    /* ── the media cell ───────────────────────────────────────────────── */
    .block-figure { margin: 0; position: relative; }
    .block-figure img { display: block; width: 100%; }
    .block-figure figcaption {
        margin-block-start: 8px;
        font-family: var(--font-condensed);
        font-size: 14px;
        line-height: 1.2;
        color: var(--c-text);
    }

    /* Never taller than the window. A clip plays only while it is in view
     * (scripts/page/block-media.js) and it loops, so one that runs past the
     * foot of the window is never seen whole — and a vertical one at the
     * column's width does: 9:16 across the article's 590px figure is 1050
     * tall. Capped, the box keeps the column's width and `contain` centres
     * the picture in it, under the play button, which is centred already. */
    /* The box is the PICTURE, not the cell.
     *
     * `width: 100%` with `object-fit: contain` kept a 590-wide element and
     * fitted a 456-wide picture inside it, so the clip carried 67px of dead
     * ground either side on a 1440x900 screen and 93 on a 1280x800 one — and
     * the sound control, anchored to that element, stood in the dead ground
     * instead of on the picture.
     *
     * Letting the width follow from the height does the same sum and gives the
     * element the answer: a replaced element keeps its ratio between
     * `max-width` and `max-height`, so the box IS 456x810 and there is nothing
     * to fit anything inside.
     *
     * The cap stays. A clip plays only while in view (block-media.js) and it
     * loops, so one running past the foot of the window is never seen whole —
     * and 9:16 across this column is 1049 tall. */
    .block-video-frame {
        position: relative;
        width: fit-content;
        max-width: 100%;
        margin-inline: auto;
    }
    .block-video {
        display: block;
        width: auto;
        max-width: 100%;
        max-height: 90vh;
        max-height: 90svh;
    }

    /* The Spotify player — SpotifyEmbed.vue. 352px tall, rounded and clipped,
     * with 20px above it. Ours had no rule at all and the iframe carried a
     * default height of 152, which is Spotify's compact player: the artwork
     * and one row of controls, with the track list cut off below the fold of
     * the frame. */
    .block-figure--spotify {
        margin-block-start: 20px;
        border-radius: 12px;
        overflow: hidden;
    }
    .block-spotify {
        display: block;
        width: 100%;
        height: 352px;
        border: 0;
    }

    /* ── the arrangements ─────────────────────────────────────────────── */
    /* The prose is FIRST in the markup for every type, so a screen reader and
     * a crawler get it first; where the design puts the photograph on the
     * left, `order` moves it rather than the source. */

    /* large — a wide photograph with a narrow column of prose beside it. */
    .block--large .block-figure { width: 70%; flex-shrink: 0; padding-right: 2%; order: -1; }
    .block--large .block-content { width: 30%; align-self: center; }
    .block--large.block--reverse .block-figure { order: 1; padding-right: 0; padding-left: 2%; }

    /* small — the prose takes the wider half. */
    .block--small .block-content { width: 50%; padding-right: 10%; align-self: center; }
    .block--small .block-figure { width: 40%; }
    /* The opening line of a `small` block is set as a lead rather than a
     * heading. The cross block's upper cell gets it too: that cell IS a
     * `.small` in the original, so the same rule reached it.
     *
     * It had a raised initial too — `::first-letter` at 40px — removed along
     * with the matching one on the article's questions further down. A lead
     * is already distinguished by its size and weight; the enlarged letter on
     * top of that was read as an error. */
    .block--small .block-content h2,
    .block--cross .block-heading h2 {
        font-family: var(--font-body);
        font-size: 22px;
        font-weight: 100;
    }
    .block--small.block--reverse .block-content { order: 1; padding-right: 0; padding-left: 10%; }
    .block--small.block--reverse .block-figure { margin-left: 10%; }

    /* middle — `.small.inside` in BlogSection.vue. On a wide screen it is the
     * `small` arrangement with the cells evened up: prose at 50% on the left,
     * photograph at 50% on the right.
     *
     * The wrap-around float this used to have at every width is the MOBILE
     * layout — the original puts `float` and `shape-outside` inside
     * `@media (max-width: 880px)` and nowhere else, so every desktop reader
     * has been getting the phone arrangement. See the mobile block below.
     *
     * `order: 2` rather than source order, and note that `reverse` does
     * nothing here on desktop: the original flips this type only below 880,
     * where the float changes sides. */
    .block--middle .block-content { width: 50%; padding-right: 10%; align-self: center; }
    .block--middle .block-figure { width: 50%; order: 2; }

    /* cross — a small figure with a heading, and a large one below it that
     * slides under by half the gutter. */
    .block--cross { display: block; }
    .block--cross .block-cross-small { position: relative; z-index: 1; display: flex; }
    .block--cross .block-cross-small .block-heading {
        width: 50%;
        padding-right: 10%;
        padding-bottom: 50px;
        align-self: center;
    }
    /* No `margin-left` on this one. It had 10%, which pushed the figure flush
     * to the right edge; the original leaves the row 10% short, because the
     * heading cell already carries its own 10% of right padding. */
    .block--cross .block-cross-small .block-figure { width: 40%; }

    /* `display: flex`, which was missing — the cell had none, so its
     * photograph and its prose were two block boxes at 50% and 40% width
     * STACKED one above the other instead of sitting side by side. The
     * cross is the one type whose whole point is the two rows overlapping,
     * and the lower row was twice as tall as it should be. */
    .block--cross .block-cross-large {
        position: relative;
        z-index: 0;
        display: flex;
        margin-top: -50px;
    }
    .block--cross .block-cross-large .block-figure { width: 50%; transform: translateX(10%); }
    .block--cross .block-cross-large .block-content { width: 30%; margin-left: 20%; align-self: center; }

    /* quote — the `large` arrangement with a framed passage in place of the
     * prose: photograph at 70% on the left, quote at 30% beside it, centred
     * against the picture's height.
     *
     * `<div class="large quote">` in BlogSection.vue — it takes everything
     * from `.large` and adds only a mobile treatment. This had it as
     * `display: block` with the quote at full measure above the photograph,
     * which is a different block entirely: the two stacked, and the passage
     * that is meant to sit beside the picture ran across the whole column. */
    .block--quote .block-figure { width: 70%; flex-shrink: 0; padding-right: 2%; order: -1; }
    .block--quote .block-content { width: 30%; align-self: center; }

    @media (max-width: 880px) {
        .block {
            flex-wrap: wrap;
            padding: 0 5% 20px;
        }

        /* quote — the arrangement inverts here. The photograph stops being a
         * cell beside the passage and becomes the block's ground, with a 0.36
         * black wash over it, and the quote lies on top in white.
         *
         * None of this had been ported, so the block kept its desktop 30/70 at
         * every width. A flex item does not shrink below its content, so the
         * passage held its column open at 105px on a 390px screen and pushed
         * the whole document sideways — which is what made wedding-suits,
         * celebrity and premium-textiles scroll horizontally on a phone. */
        .block--quote {
            position: relative;
            display: block;
            /* The same air under it as over it. The block that follows brings
             * none of its own, so the heading after the passage started
             * against the bottom edge of the photograph. */
            margin-block: 24px;
            overflow: hidden;
        }
        .block--quote .block-figure {
            position: absolute;
            /* Inset by the block's own gutter, not `inset: 0`.
             *
             * An absolutely positioned box measures from the PADDING box, and
             * `.block` carries its 5% as padding — so `inset: 0` reached
             * straight past it and the photograph ran from one edge of the
             * window to the other while every block above and below it sat in
             * a column. The 5% and the 20px are `.block`'s own figures at this
             * width, repeated here because there is nothing else to inherit
             * them from. */
            inset: 0 5% 20px;
            z-index: 1;
            /* `auto`, so the insets size the box. An absolutely positioned
             * element given left, right AND a width is over-constrained and
             * the browser drops `right`: with the 100% that stood here the
             * figure kept its full 419px and simply moved 21px over, hanging
             * out of the window. Dropping the declaration is not enough
             * either — the desktop rule's `width: 70%` then reaches down here
             * and the photograph comes out two thirds of the column. */
            width: auto;
            height: auto;
            padding-right: 0;
        }
        /* The zoom button has to carry the height too, or the `100%` below it
         * has nothing to resolve against: a percentage height against an
         * `auto` parent computes to `auto`, so the photograph fell back to its
         * own aspect, came up short of the frame, and left a band of the 36%
         * wash over the page's white — the grey strip under the quote. */
        .block--quote .block-figure .block-zoom { height: 100%; }
        .block--quote .block-figure img { height: 100%; object-fit: cover; }
        .block--quote .block-figure::before {
            content: "";
            position: absolute;
            inset: 0;
            z-index: 2;
            background-color: rgb(0 0 0 / 0.36);
        }
        .block--quote .block-content {
            position: relative;
            z-index: 3;
            width: 100%;
            padding: 60px 30px;
        }
        .block--quote .block-content blockquote,
        .block--quote .block-content blockquote h2,
        .block--quote .block-content blockquote p { color: var(--c-white); }
        /* The photograph is the ground here, so its caption lies on it too, in
         * white at the foot of the frame, with the passage making room. */
        .block--quote .block-figure figcaption {
            position: absolute;
            /* Clear of the magnifier in the corner. */
            inset-inline: 30px 60px;
            bottom: 20px;
            z-index: 3;
            margin: 0;
            color: var(--c-white);
        }
        .block--quote:has(figcaption) .block-content { padding-bottom: 120px; }

        .block--large .block-figure,
        .block--large .block-content,
        .block--small .block-figure,
        .block--small .block-content {
            width: 100%;
            padding-inline: 0;
            margin-left: 0;
        }
        /* Prose first on a narrow screen, whichever side it took. */
        /* `padding-left: 0` with them: the mirrored `large` sets `padding-left:
         * 2%` on its figure at 0-3-0, and the shared phone reset above is a
         * plain `.block--large .block-figure` at 0-2-0 — so the 2% survived
         * and stood the photograph 8px right of the paragraph over it, on a
         * screen where the two cells are one above the other and share a
         * margin. */
        .block--large .block-figure,
        .block--large.block--reverse .block-figure { order: 1; padding-left: 0; margin-top: 12px; }

        /* Except the mirrored `small`, which leads with its photograph.
         *
         * BlogSection.vue turns that one into `display: block` below 880 and
         * lets source order do it — the figure comes first in its markup.
         * Ours keeps the prose first in the DOM on purpose, so a reader who
         * hears the page gets the words before the picture whichever side it
         * takes; `order` puts the picture first on screen without moving it in
         * the document.
         *
         * And the picture is 66vw, not the whole column: `.mirror figure {
         * width: 66vw }`. It had been full width, which is why the block read
         * as prose-then-a-full-bleed-photograph instead of a photograph with
         * its caption and the paragraph under it. */
        /* `margin-left: 0` restated here and not left to the shared reset
         * above it: the desktop mirror rule is `.block--small.block--reverse
         * .block-figure { margin-left: 10% }` at 0-3-0, and the reset is a
         * plain `.block--small .block-figure` at 0-2-0 — so the 10% survived
         * into the phone layout and stood the picture 38px off the margin. */
        /* No `margin-top` on whichever of the two comes FIRST.
         *
         * The 12px is the gap BETWEEN the picture and the words. On the one
         * that leads the block it is a leading margin instead, and it stacks
         * on whatever the block above already left — the gap under the
         * gallery came out 24 + 12 rather than the 24 the gallery declares.
         * So the first cell carries none and the second carries the 12,
         * whichever way round the document asks for them. */
        .block--small.block--reverse .block-figure { order: 0; width: 66vw; margin-left: 0; }
        /* `padding-left: 0` for the same reason as the figure's margin: the
         * desktop mirror sets `padding-left: 10%` at 0-3-0 and the shared
         * reset is 0-2-0, so the prose sat 38px in from the margin on a phone
         * while everything above and below it started at the edge. */
        .block--small.block--reverse .block-content { order: 1; padding-left: 0; margin-top: 12px; }

        /* ...unless the document asks for the words on top. The block before
         * one of these on premium-textiles ends with a caption, and a caption,
         * a picture and another caption stack into one run of small grey type
         * with nothing to break it. */
        .block--small.block--reverse.block--prose-first .block-figure { order: 1; margin-top: 12px; }
        .block--small.block--reverse.block--prose-first .block-content { order: 0; margin-top: 0; }

        /* Here is the wrap-around: below 880 the photograph floats and the
         * prose runs around it. `shape-outside` so the text follows the box
         * rather than a rectangle, and the padding is the gutter between the
         * two — on the side the text arrives from. */
        .block--middle { display: flow-root; }
        .block--middle .block-content { width: auto; padding-right: 0; }
        .block--middle .block-figure {
            float: left;
            width: 50%;
            margin-top: 46px;
            /* `border-box`, not `content-box`. The 12px on the right is the
             * gutter between the picture and the words that run past it, and
             * with the exclusion drawn round the CONTENT box that padding is
             * not excluded — the text wrapped flush against the photograph
             * and the gutter did nothing.
             *
             * The component hangs the padding on the IMG and keeps
             * `content-box`, which comes to the same shape: the figure's
             * content box is then the img's border box, padding included. One
             * box either way; this is the one that does not have to be
             * repeated on the zoom button that wraps the picture here. */
            shape-outside: border-box;
            padding: 0 12px 8px 0;
        }
        /* No mirrored variant here.
         *
         * BlogSection.vue floats the mirrored one right, and every `middle`
         * block on this site is mirrored — all five of them, on four pages —
         * so that rule would decide the side for every one. The page the
         * design comes from floats them LEFT, which is the unmirrored value,
         * so the override is gone rather than kept as the only branch that
         * ever runs. */

        /* `display: block` only — the -50px stays.
         *
         * BlogSection.vue resets the lower cell's `margin-top` to 0 here, and
         * with it the two pictures stop overlapping and become a plain column.
         * The page overlaps them at every width, so the pull stays: the lower
         * picture rides up under the upper one by half the gutter, which with
         * one pushed right and one left is the whole figure of this block. */
        .block--cross .block-cross-small,
        .block--cross .block-cross-large { display: block; }
        /* `margin-left: 0` and `padding-right: 0` with the width, which is what
         * BlogSection.vue resets here and we did not. The lower cell keeps a
         * 20% left margin from the desktop rule, so `width: 100%` on top of it
         * made the row 120% wide and pushed the whole document sideways —
         * every page with a cross block scrolled horizontally on a phone. */
        .block--cross .block-cross-small .block-heading,
        .block--cross .block-cross-large .block-content {
            width: 100%;
            margin-left: 0;
            padding-right: 0;
            padding-bottom: 20px;
        }
        .block--cross .block-cross-small .block-figure,
        .block--cross .block-cross-large .block-figure {
            width: 100%;
            margin-left: 0;
            transform: none;
        }

        /* The cross stays a cross on a phone: the two photographs keep their
         * sizes and their sides, one pushed right and one left, rather than
         * both going full width and stacking into a column.
         *
         * BlogSection.vue says it on the pictures and not on the cells — the
         * upper figure is `display: flex; justify-content: flex-end` with its
         * image at 50%, the lower one leaves its image at 60% and therefore
         * on the left. We had reset the cells to 100% and stopped there, so
         * the block lost the offset that gives it its name.
         *
         * Named on the zoom button as well as on a bare <img>: the button
         * wraps the picture when the page allows the lightbox, and
         * premium-textiles and wedding-suits turn that off. */
        .block--cross .block-cross-small .block-figure {
            display: flex;
            justify-content: flex-end;
        }
        .block--cross .block-cross-small .block-figure > .block-zoom,
        .block--cross .block-cross-small .block-figure > img { width: 50%; }
        .block--cross .block-cross-large .block-figure > .block-zoom,
        .block--cross .block-cross-large .block-figure > img { width: 60%; }

        /* 24px over the copy, not the component's 12 — Georgi's call, looking
         * at ПРОБИ. The lower cell's heading sat almost against the picture
         * above it, and the container is where the space belongs: two of the
         * three crosses on this page open with a heading and the third with a
         * paragraph, and both want the same air under the photograph. */
        .block--cross .block-cross-large .block-content { margin-top: 24px; }
    }

    /* The gallery's arrows are the shared component in components.css, and
     * from here on they are the SAME arrows as the reel's and the strip's: a
     * 22px chevron on the page's own column, in a box of one width.
     *
     * They were a third of the strip wide with a 45px glyph 52px in from the
     * edge — DetailsSlider's own drawing, ported faithfully and then left to
     * diverge, so one page showed the reader two different carousels. The
     * lean stays 35, which is what that component asks for.
     *
     * The scrollbar and the drag cursor below belong to the STRIP, not to the
     * button, so they stay here. */
    .block--gallery { --rail-inset: var(--gutter); --rail-lean: 35px; }

    @media (max-width: 640px) {
        .block--gallery { --rail-inset: 5%; }
    }

    /* One look: a photograph with its passage of copy set OVER it, and two of
     * them side by side. Every styling entry carries exactly two and nothing
     * else uses the type.
     *
     * This had been built as an ordinary `.block`: inside the 1280 column,
     * photograph and prose in two cells beside each other, one block per row,
     * no gradient, the heading black. StylingShots.vue is none of those — it
     * is full-bleed, the copy sits at the foot of the picture in white over a
     * gradient, and the heading is gold.
     *
     * It also carried `@media (min-width: 48rem) { grid-template-columns }` on
     * an element that is `display: flex`, where the declaration does nothing
     * at all. `check:scopes` weighs selectors, not whether a property applies
     * to the box it lands on, so a dead declaration like that is invisible to
     * every check in this repo. */
    .block--shot {
        position: relative;
        display: block;
        float: left;
        width: 50%;
        max-width: none;
        margin-inline: 0;
        padding: 0;
        overflow: hidden;
    }

    /* Floated rather than inline-block, which is what the original used. Two
     * inline-blocks of 50% fit on one line only while NO whitespace separates
     * them, and whether the markup emits a newline between two sections is not
     * something this layout should depend on. `.blocks` gets `flow-root` below
     * so it still contains them. */

    .block--shot::before {
        content: "";
        position: absolute;
        left: 0;
        bottom: 0;
        z-index: 0;
        width: 100%;
        height: 330px;
        background: linear-gradient(to bottom, rgb(0 0 0 / 0) 0%, rgb(0 0 0 / 0.5) 100%);
    }

    .shot-photo { display: block; width: 100%; }

    .shot-content {
        position: absolute;
        right: 0;
        bottom: 0;
        z-index: 1;
        width: 100%;
        padding: 0 40px 80px;
        color: var(--c-white);
    }

    .shot-content h2 {
        margin: 0;
        padding-bottom: 15px;
        font-family: var(--font-body);
        font-size: 23px;
        font-weight: 900;
        line-height: 25px;
        text-transform: uppercase;
        /* Gold, not the black every other block's heading takes. */
        color: var(--c-gold);
    }

    .shot-content p {
        margin: 0;
        font-family: var(--font-body);
        font-size: 16px;
        font-weight: 400;
        line-height: 1.4;
    }

    .shot-content p + p { margin-block-start: 12px; }

    /* Below 880 one shot per row, and the copy moves off the right edge into a
     * narrower column on the left — there is no room for 40px of gutter either
     * side of a full-width picture.
     *
     * Here rather than in the shared 880 block above, which is where it was
     * written and where it did nothing: that block comes EARLIER in the file
     * than the `.block--shot` rules it was meant to override, and at equal
     * specificity the later rule wins whether or not it sits in a media query.
     * So the shots stayed floated at 50% on a phone. check:scopes caught the
     * float and the width; `.shot-content` it could not, because that rule
     * also sets `left`, which nothing later touches — a rule that wins for one
     * property and loses for three reads as alive. */
    /* `.mobile &` is 1023, not the 880 these were written at — 880 is
     * BlogSection's own breakpoint and StylingShots does not share it. The two
     * cards stack and the copy moves off the right edge a class earlier than
     * we had it. */
    @media (max-width: 1023px) {
        .block--shot { float: none; width: 100%; }
        .shot-content {
            right: auto;
            left: 6%;
            width: 60%;
            padding: 0 0 40px;
        }
    }

    /* `.phone &` — 640, where the copy takes nearly the whole card. */
    @media (max-width: 640px) {
        .shot-content { left: 4%; width: 92%; }
    }

    /* The details strip — DetailsSlider.vue. Full-bleed, cards butted against
     * each other at 35% of the viewport, and a gold rule above and below.
     *
     * Four things were wrong and each was a decision the original did not
     * make: a 1rem gap (there is none — the photographs touch), `var(--gutter)`
     * of padding (there is none — it runs edge to edge), `min(28rem, 80%)`
     * cards instead of 35%, and `aspect-ratio: 3/4` with `object-fit: cover`,
     * which CROPPED every photograph to portrait. These are detail shots of a
     * lapel or a cuff, and they are not all portrait.
     *
     * No scroll-snap either. Same reason as the reel: the last cards can never
     * satisfy `scroll-snap-align: start`, so the strip arrives and then
     * corrects itself, which reads as a jump. */
    .block--gallery {
        position: relative;
        display: block;
        max-width: none;
        margin-inline: 0;
        padding: 19px 0 15px;
        overflow: hidden;
    }

    /* The gold rules above and below the strip, stretched the full width.
     *
     * `background-size` has to be stated. The file carries a `viewBox` and no
     * `width`/`height`, so it has a ratio — 1056:20 — and no intrinsic size,
     * and `background-size: auto` on an image like that behaves as `contain`:
     * the band came out 1056px wide and centred, with bare ground either side
     * of it on any screen wider than that. The original could leave it alone
     * because its slider was about that wide; this one runs edge to edge.
     *
     * `100% 20px` rather than `cover`, which is what the original reached for
     * below 1100. Cover preserves the ratio, so filling a 1920px box would
     * make the band 36px tall and crop it — and what would go is the 1px
     * highlight along the top and the 1px shadow along the bottom, which are
     * the whole reason it reads as a milled edge rather than a gold stripe.
     * Stretching only spreads the sheen in the middle, which is a gradient
     * and does not care.
     *
     * And `background-size` alone does NOT do it. The file needs
     * `preserveAspectRatio="none"`, which our copy carries and the original in
     * mikser/files does not. Without it the SVG defaults to `xMidYMid meet`
     * and letterboxes ITSELF inside whatever box it is given: the background
     * box was a full-width 1920x20 and the drawing still came out 1056 wide,
     * centred in it, which is indistinguishable from the size never having
     * been applied. Two different mechanisms, both of which have to agree
     * before a vector rule will stretch.
     *
     * Only the horizontal scale changes — 1056 to the viewport, 20 to 20 — so
     * the 1px lines stay 1px. */
    .block--gallery::before,
    .block--gallery::after {
        content: "";
        position: absolute;
        left: 0;
        width: 100%;
        height: 20px;
        background-image: url('../media/raw/decor/slider-rule.svg');
        background-repeat: no-repeat;
        background-size: 100% 20px;
    }
    .block--gallery::before { top: 0; z-index: 2; }
    .block--gallery::after { bottom: 0; z-index: 1; }

    .gallery {
        display: flex;
        gap: 0;
        margin: 0;
        padding: 0;
        list-style: none;
        overflow-x: auto;
        overscroll-behavior-x: contain;
    }

    .gallery-item { flex: 0 0 35%; }

    /* No crop and no forced ratio: the photographs keep the shape they were
     * taken in, which is what `img { width: 100%; display: block }` gave. */
    .gallery-item img { display: block; width: 100%; height: auto; }

    /* The scrollbar goes once the arrows are there.
     *
     * The same bargain the reel and the strip make — one affordance or the
     * other, never neither — except that this rail never got its half of it.
     * `rail.js` handles all three tracks and sets `data-arrows` on the section
     * for all three; only `.reel-track` and `.stories-track` were told to hide
     * the bar, so the gallery carried a grey scrollbar under the photographs
     * on every styling entry, in both languages, while the two rows above and
     * below it had none. */
    .block--gallery[data-arrows] .gallery { scrollbar-width: none; }
    .block--gallery[data-arrows] .gallery::-webkit-scrollbar { display: none; }

    /* `.mobile &` — 1023. */
    @media (max-width: 1023px) {
        /* Two thirds of the strip wide and 400 tall. The height is Georgi's:
         * left to the photograph's own shape the tile came out square — 279
         * by 280 at 419 — and a row of squares reads as a contact sheet
         * rather than as a strip of details. With a height stated the picture
         * crops instead of dictating, which is what the interview's gallery
         * does at every width. 380 is Georgi's figure. */
        .gallery-item { flex: 0 0 66.67%; height: 380px; }
        .gallery-item img { height: 100%; object-fit: cover; }

        /* `.not-mobile &:hover` — the arrow leans outward only where there is
         * a pointer to lean for. */
        .block--gallery .rail-prev:hover svg { margin-left: 0; }
        .block--gallery .rail-next:hover svg { margin-right: 0; }
    }

    /* The wedding gallery — ported from WeddingSuitsCollection.vue.
     *
     * A staggered wall: five columns, each tile pushed down by a different
     * amount so the top edge is ragged. It is `flex-flow: column wrap` with a
     * COMPUTED HEIGHT — the container has to be tall enough for the wrap to
     * break where the columns want it, and that height comes from the tile
     * count, which is why the markup carries `--collection-size`.
     *
     * The stagger is `order` plus a translate per nth-of-type. Only above
     * 1380px, because below it the columns are too narrow for the offsets to
     * read as anything but misalignment. */
    /* Full-bleed. `.block` caps every type at 1280 and centres it;
     * WeddingSuitsCollection is a sibling of `<Content>` in the view, not
     * inside it, so the wall runs to both edges. Capped, its five columns were
     * 18% of 1280 instead of 18% of the window — every tile a third narrower
     * than it should be, and every caption wrapping onto twice the lines. */
    .block--collection {
        max-width: none;
        margin-inline: 0;
        padding: 0;
    }

    .collection {
        --tile-height: 520px;
        --tile-gap: 30px;
        --columns: 5;
        --row-count: calc(var(--collection-size) / var(--columns));

        position: relative;
        display: flex;
        flex-flow: column wrap;
        align-content: space-between;
        height: calc((var(--row-count) * (var(--tile-height) + var(--tile-gap))) + 280px);
        margin: 0 0 48px;
        padding-inline: var(--tile-gap);
        list-style: none;
    }

    .collection-item {
        position: relative;
        width: 18%;
        height: var(--tile-height);
        margin-top: var(--tile-gap);
        overflow: hidden;
    }

    /* A scrim from the foot of each photograph, so the caption over it reads. */
    .collection-item--photo::before {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 10;
        background: linear-gradient(to top, rgb(0 0 0 / 0.45) 0%, rgb(0 0 0 / 0) 24%);
        pointer-events: none;
    }

    .collection-link { display: block; height: 100%; text-decoration: none; color: var(--c-white); }

    .collection-image {
        width: 100%;
        height: 100%;
        object-fit: cover;
        object-position: center;
        transition: transform 0.4s;
    }
    .collection-link:hover .collection-image { transform: scale(1.1); }

    /* The caption over a photograph. It fills the tile and settles at the
     * foot rather than being pinned there, which is what keeps a two-line
     * title and a one-line title on the same baseline.
     *
     * Its type was missing altogether: the tile's h2 and p matched no rule
     * here — `.collection-content` is not `.block-content` — so both fell
     * through to the base, at Open Sans and the browser's own sizes, where
     * the original sets 12.5px and 14px of PT Serif in white. */
    .collection-item--photo .collection-content {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 20;
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        height: 100%;
        padding: 0 12px 12px;
    }

    .collection-item--photo .collection-content h2 {
        font-weight: 500;
        color: var(--c-white);
    }

    /* 12px above every paragraph in a tile — `--space-between` in the
     * component, applied by `.item .markdown p` to all of them. It was stated
     * only for the photograph tiles, so on the text tiles the copy sat flush
     * against the heading above it. */
    .collection-content p {
        margin-block-start: 12px;
        color: var(--c-black);
    }

    .collection-item--photo .collection-content p {
        font-family: var(--font-body);
        font-size: 14px;
        line-height: 18px;
        color: var(--c-white);
    }

    .collection-content > :first-child { margin-block-start: 0; }

    /* And the last paragraph gives up its bottom margin, for the same reason
     * as `.block-content p:last-of-type` further up: in the component nothing
     * carries one, every gap is a top margin, and `.collection-more` is
     * `inline-block` so the base's 1em does not collapse into the button's 12
     * — it adds to it. The gap over the button was 26px. */
    .collection-content p:last-of-type { margin-block-end: 0; }

    /* "Виж повече" is a bordered gold tag, not a line of text — it had lost
     * its border, its width and all of its type. */
    .collection-more {
        display: inline-block;
        max-width: 120px;
        margin-block: 12px 6px;
        padding: 0 4px;
        border: 1px solid var(--c-gold);
        font-family: var(--font-display);
        font-size: 13px;
        font-weight: 500;
        text-align: center;
        text-transform: uppercase;
        color: var(--c-white);
    }

    /* The two text-only tiles, each framed on two sides in gold — the intro
     * opens with a raised initial, the mid-wall one is bordered the other way
     * round. */
    .collection-item--one,
    .collection-item--three { overflow: visible; }
    .collection-item--one::before,
    .collection-item--three::before { content: none; }

    /* Centred in the tile, as `.item .collection-content` has it — a text
     * panel that started at the top read as a caption that had lost its
     * photograph. */
    .collection-item--one .collection-content,
    .collection-item--three .collection-content {
        display: flex;
        flex-direction: column;
        justify-content: center;
        height: 100%;
        padding-left: 24px;
        padding-bottom: 24px;
    }
    .collection-item--one .collection-content {
        border-left: 8px solid var(--c-gold);
        border-bottom: 8px solid var(--c-gold);
    }
    .collection-item--three .collection-content {
        border-top: 8px solid var(--c-gold);
        border-left: 8px solid var(--c-gold);
    }
    /* 21px is `2.2rem !important` on `.markdown h2`, which outranks the
     * 1.3rem the tile asks for — and the old root was 60%, so 1rem is 9.6px
     * throughout this component. Uppercase comes from the same rule and
     * survives, because the tile overrides only the family, size and weight;
     * we had turned it off. The raised initial is 2.4rem, which is 23px, not
     * the 38 we had — barely larger than the line it opens, which is the
     * effect the original is after. */
    /* Every tile's heading, whatever kind of tile it is — `.item .markdown h2`
     * in the component. 21px is its `2.2rem !important`, and `!important`
     * carries: the photograph tiles ask for 1.3rem and do not get it, which is
     * what I read wrong the first time and why their titles came out at half
     * size. Uppercase and the family come from here too. */
    .collection-content h2 {
        margin: 0;
        font-family: var(--font-body);
        font-size: 21px;
        font-weight: 900;
        text-transform: uppercase;
        color: var(--c-black);
    }

    .collection-item--one h2,
    .collection-item--three h2 { font-weight: 500; }
    .collection-item--one h2::first-letter {
        font-size: 23px;
        text-transform: uppercase;
    }

    /* The second line of the intro tile — `Black Market` under `Сватбите на`. */
    .collection-content h3 {
        margin: 0;
        font-family: var(--font-display);
        font-size: 25px;
        font-weight: 900;
        line-height: 1;
        text-transform: uppercase;
        color: var(--c-black);
    }
    .collection-item--three p { font-size: 18px; line-height: 1.3; }

    @media (min-width: 1381px) {
        .collection-item:nth-of-type(5n+1) { order: 3; }
        .collection-item:nth-of-type(5n+2) { order: 2; transform: translateY(140px); }
        .collection-item:nth-of-type(5n+3) { order: 1; transform: translateY(280px); }
        .collection-item:nth-of-type(5n+4) { order: 4; transform: translateY(60px); }
        .collection-item:nth-of-type(5n)   { order: 5; transform: translateY(210px); }
    }

    @media (max-width: 1380px) {
        .collection { --tile-height: 480px; height: calc(var(--row-count) * (var(--tile-height) + var(--tile-gap))); }
        .collection-item { width: 23%; }
    }
    @media (max-width: 1200px) { .collection { --tile-height: 440px; } }
    @media (max-width: 1100px) { .collection { --columns: 4; } }

    /* Below this the wall becomes a single column: the stagger, the computed
     * height and the scrim all go, because a list of full-width photographs
     * needs none of them. */
    @media (max-width: 980px) {
        .collection { display: block; height: auto; --tile-gap: 15px; }
        .collection-item { width: 100%; height: auto; margin-top: 24px; }
        .collection-item--photo::before { content: none; }
        .collection-item--photo .collection-content {
            position: static;
            padding: 10px 0 0;
            color: var(--c-text);
        }
        /* The caption's own two rules have to turn with it.
         *
         * Over a photograph the heading and the line under it are white, and
         * they say so at 0-3-0 — `.collection-item--photo .collection-content
         * h2`. Setting the colour on the CONTENT here weighs 0-2-0 and never
         * reached them, so below 980, where the caption steps off the picture
         * and onto the page, it was white type on white paper: eighteen
         * tiles' worth of headings and captions that took up their space and
         * could not be read. */
        .collection-item--photo .collection-content h2,
        .collection-item--photo .collection-content p { color: var(--c-text); }
        /* And the button with them. `Виж повече` is white inside a gold
         * hairline, which is a frame over a photograph; on the page it was an
         * empty gold box. The border stays gold — it is the accent — and only
         * the words turn. */
        .collection-item--photo .collection-more { color: var(--c-text); }

        /* The two text tiles need air over their copy here.
         *
         * On the wall they are 520px tall and the words are centred in that
         * height, so the gold rule above never touches them. Below 980 the
         * tile takes the height of its own text, `height: 100%` resolves to
         * auto and there is nothing left to centre — the first line sits
         * straight on the border. 24px, the same as the left and the bottom
         * the tile already carries. */
        /* Both text tiles keep a tighter frame here — Georgi's 12 and 12,
         * against the 24 the wall uses. At full width the copy runs the whole
         * column, so the gold rules read as a frame around it rather than as
         * two sides of a tile, and 24 left the words floating inside it. */
        .collection-item--one .collection-content,
        .collection-item--three .collection-content {
            padding-top: 12px;
            padding-left: 12px;
        }
        /* And the bottom with them on the intro tile, because that is where
         * its second gold rule runs — the mid-wall tile is open at the foot
         * and keeps the wall's 24 there. */
        .collection-item--one .collection-content { padding-bottom: 12px; }
        .collection-image { height: auto; aspect-ratio: 3 / 4; }
    }

    /* The tile that is only a call to action — no photograph, so it fills its
     * cell with its own ground and centres. */
    .collection-item--cta {
        display: grid;
        place-items: center;
        min-height: 200px;
        padding: 24px;
        background-color: var(--c-panel-2);
        text-align: center;
    }
    .collection-item--cta h3 {
        font-family: var(--font-condensed);
        font-size: 16px;
        font-weight: 700;
        text-transform: uppercase;
        color: var(--c-grey-light);
    }
    .collection-item--cta h2 {
        margin-block-start: 6px;
        font-family: var(--font-display);
        font-size: 22px;
        font-weight: 900;
        text-transform: uppercase;
        color: var(--c-black);
    }
    .collection-item--cta .collection-more {
        margin-block-start: 16px;
        padding: 4px 24px;
        border: 1px solid var(--c-ink);
        font-family: var(--font-condensed);
        font-size: 20px;
        text-transform: uppercase;
        text-decoration: none;
        color: var(--c-ink);
    }

    /* The mid-sequence teaser: full bleed, its own ground. */
    /* banner — a full-bleed photograph with a passage of copy over it and,
     * on the fabric pages, the mills' logos beside it. PriceBanner.vue.
     *
     * This was built as a grid that stacked its two cells and washed the
     * photograph with nothing at all. The original is a centred 1280 row with
     * the copy at 360px and the logos at 460px, over two layers of shade. */
    /* Full-bleed. `.block` caps every type at 1280 and centres it, which is
     * right for the editorial blocks and wrong for this one: PriceBanner is
     * rendered as a sibling of `<Content>` in the Vue views, not inside it, so
     * the photograph runs to both edges. The 1280 column lives on the inner
     * row instead — same as `shot` and `gallery`, which are full-bleed for the
     * same reason. */
    .block--banner {
        max-width: none;
        margin-inline: 0;
        padding: 0;
    }

    .block-banner {
        position: relative;
        width: 100%;
        min-height: 380px;
        height: 80vh;
        max-height: 760px;
        margin-block-end: 50px;
        color: var(--c-white);
    }

    /* ONE wash, not two: a gradient from the left, which is what makes white
     * copy legible over a photograph nobody chose for that.
     *
     * The component appears to carry a second — `.image::after`, flat black
     * at 0.2 over the whole frame — and it was ported as a second background
     * layer here. It never paints. That pseudo is `z-index: -10`, and neither
     * the frame nor the image box makes a stacking context (both are
     * `position: relative` with `z-index: auto`), so it joins the ROOT one at
     * -10 and is painted before every positioned box on the page — including
     * the very photograph it is meant to darken. Reading it out of the
     * stylesheet and into ours put a fifth of a stop over every banner that
     * the page it was copied from does not have. */
    .block-banner::before {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 1;
        background: linear-gradient(to right, rgb(0 0 0 / 0.45) 0%, rgb(0 0 0 / 0) 100%);
    }

    /* See the note in sections/hero.css: a document's photograph is an <img>.
     * `center top` for `background-position: top center`. */
    .block-banner-bg {
        position: absolute;
        inset: 0;
        z-index: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        object-position: center top;
    }

    /* The row: a centred 1280 column, its middle pinned at 45% of the frame. */
    .block-banner-inner {
        position: relative;
        z-index: 2;
        display: flex;
        justify-content: space-evenly;
        max-width: 1280px;
        top: 45%;
        left: 50%;
        transform: translate(-50%, -50%);
    }

    .block-banner .block-content { position: relative; z-index: 2; width: 360px; }

    /* A banner without mills keeps its copy where a banner with them has it.
     * `space-evenly` above distributes TWO cells; handed one it centres it, so
     * taking the logos off a banner walked the words into the middle of the
     * photograph. An auto trailing margin eats the free space instead, which
     * leaves the column exactly where the two-cell version starts it. */
    .block-banner .block-content:only-child {
        /* Lined up with the PICTURE in the block above, not merely with the
         * column.
         *
         * Two insets make that edge. `.block` keeps 50px of gutter, and a
         * reverse `small` block — which is what sits over both banners here —
         * pushes its figure a further 10% of the column in. The banner's inner
         * column is the same 1280 but carries no padding at all, so copy at
         * its left edge began outside both.
         *
         * The 10% is of the COLUMN, which is this element's container less
         * that gutter on each side — hence `100% - 100px`, not a bare 10%,
         * which would take it from the wider box and land 10px out. The two
         * agree at every width the flex layout covers, not only at 1280. */
        margin-inline-start: calc(50px + (100% - 100px) / 10);
        margin-inline-end: auto;
    }

    /* The banner's heading is an h1 — the copy opens with `#` here where every
     * other block opens with `##`, so none of the heading rules above reached
     * it and it fell through to the base: Open Sans at the browser's own size,
     * no uppercase, dark. 35px, as the component has it.
     *
     * h2 with it. The English made-to-measure writes its banner heading as
     * `##`, which is what every other kind of block uses, and on a banner
     * that meant the word fell through to `.block-content h2` — black type
     * on a dark photograph, beside a paragraph this block had already turned
     * white. Which level an editor happens to type is not something a heading
     * should disappear over, so the banner takes both. */
    .block-banner .block-content :is(h1, h2) {
        margin: 0;
        font-family: var(--font-display);
        font-size: 35px;
        font-weight: 900;
        line-height: 1;
        text-transform: uppercase;
        color: var(--c-white);
    }

    /* White, and 16px above rather than 12. `.block-content p` paints
     * `--c-ink` outright, which beat the white this block sets on itself:
     * #303030 body copy on a dark photograph. */
    .block-banner .block-content p {
        margin-block-start: 16px;
        color: var(--c-white);
    }

    /* The banner's button is the full-size white one, not the block button —
     * PriceBanner styles it 35px with a white hairline, which is exactly
     * `.main-button--light`. Restated because the block button rule above
     * would otherwise shrink it to 24px and grey it out. Three banners carry
     * one. */
    .block-banner .block-content .main-button {
        margin-block-start: 45px;
        padding: 0 49px;
        border-color: var(--c-white);
        font-size: 35px;
        line-height: 1.2;
        color: var(--c-white);
    }
    .block-banner .block-content .main-button span { min-width: 0; }

    /* The mills. 60px tall, each as wide as it wants to be — the logos vary
     * in aspect and boxing them to one width would dwarf the square crests. */
    .block-brands {
        position: relative;
        z-index: 2;
        width: 460px;
        margin: 0;
        padding: 0;
        list-style: none;
    }
    .block-brands li { display: inline-block; }
    .block-brands li + li { padding-right: 12px; }
    .block-brands a { display: inline-block; margin-bottom: 12px; }
    .block-brands img {
        width: auto;
        height: 60px;
    }

    /* ── the banner below 980 ─────────────────────────────────────────────
     *
     * None of this had come across, and the block had no narrow treatment at
     * all: a 360px column of copy beside a 460px column of logos, 820px of
     * row centred by `left: 50%` in a 419px screen. Both halves hung off both
     * edges — the heading and the button ran off to the left and the mills
     * sat on top of the words.
     *
     * PriceBanner.vue stacks them instead. The row stops being a centred
     * flex row pinned at 45% and becomes an ordinary block with its own
     * padding; each half takes the full width; and the logos become a wrapped
     * flex row of their own, 50px apart, with the 12px spacing they carry
     * when they are a column dropped. */
    @media (max-width: 980px) {
        .block-banner-inner {
            display: block;
            top: auto;
            left: auto;
            transform: none;
            padding: 50px 4%;
        }

        .block-banner .block-content { width: 100%; }
        /* The gutter above is the wide screen's. Here the inner column is a
         * block with 4% of padding of its own, and a margin on top of it would
         * indent the copy twice. */
        .block-banner .block-content:only-child { margin-inline: 0; }

        .block-brands {
            width: 100%;
            margin-top: 50px;
            display: inline-flex;
            flex-wrap: wrap;
            gap: 24px;
        }
        .block-brands li + li { padding-right: 0; }
        .block-brands a { margin-bottom: 0; }
    }

    /* The frame stops being a fixed height and takes the height of what is in
     * it — with the row stacked, 80vh would either crop the logos or leave a
     * field of photograph under them. */
    @media (max-width: 760px) {
        .block-banner {
            height: auto;
            max-height: none;
            padding: 0;
        }
        .block-brands img { height: 50px; }
    }

    /* And the wash goes flat. The gradient runs from the left, which works
     * when the copy is a column on that side; once it is the full width the
     * right-hand words sit on bare photograph, so the component swaps it for
     * an even 36% black across the frame. */
    @media (max-width: 720px) {
        .block-banner::before { background: rgb(0 0 0 / 0.36); }
    }

    /* ── the interview's proportions ──────────────────────────────────────
     *
     * Celebrity.vue restates the size of nearly every cell on that page: the
     * figures narrow and the prose widens, because it is a page of reading
     * with pictures rather than pictures with captions. Declared by the
     * document as `blocksVariant: article`, so the page says what it is
     * instead of the stylesheet naming it.
     *
     * `:scope.blocks--article`, not `.blocks--article`. The modifier is on the
     * ROOT of this scope, and inside `@scope (.blocks)` a bare class means a
     * DESCENDANT — so the first version of these rules asked for a
     * `.blocks--article` inside `.blocks`, matched nothing at all, and the
     * page kept the default proportions while the CSS sat in the bundle
     * looking correct. It is the first of the four rules in CLAUDE.md.
     *
     * The numbers are the component's, not rounded or evened out. */
    /* Half the row for the photograph, asked for — the interview's pictures
     * are the point of it and the component's 42 and 28 left them looking
     * like illustrations beside the prose.
     *
     * 52 and not 50 on the large block: `box-sizing` is border-box and the 2%
     * padding is the gutter between picture and text, so the PICTURE is 50%
     * of the row and the gutter is the two points above it. The small block
     * has no padding, so it says 50 outright.
     *
     * The gallery keeps its own width — it is a rail, not a block, and was
     * excluded by name. */
    :scope.blocks--article .block--large .block-figure {
        flex: 0 0 52%;
        width: 52%;
        padding-right: 2%;
    }
    :scope.blocks--article .block--large .block-content { flex: 1 1 auto; width: auto; }

    /* And the indent the reverse small block carries goes with it: 50% of the
     * row plus a 10% margin plus the prose is more than a row, and what gave
     * way was the picture. */
    :scope.blocks--article .block--small .block-figure {
        flex: 0 0 50%;
        width: 50%;
        margin-left: 0;
    }
    :scope.blocks--article .block--small .block-content { flex: 1 1 auto; width: auto; }

    /* An interview's questions are set the way the cross block's question is
     * already set — PT Serif at 22, light, uppercase, with the opening letter
     * raised to 40. Asked for.
     *
     * They were written as a bold run at the head of the answer's paragraph,
     * which is how the source had them, and they came out as a 20px inline
     * strong: a question that reads as emphasis inside the answer rather than
     * as the thing being answered. They are headings now, in the document as
     * well as on the page.
     *
     * Scoped to the article, and to a DIRECT child. `.block-content h2`
     * elsewhere is a block's own title, in the display face at 25 and weight
     * 900 — a different object, and it does not move. The `>` keeps the
     * pull-quote's attribution out of it too: that h2 lives inside the
     * blockquote and is a label straddling a gold rule, not a question. */
    :scope.blocks--article .block-content > h2 {
        font-family: var(--font-body);
        font-size: 22px;
        font-weight: 100;
    }

    /* The quote's photograph is cropped to a fixed frame here rather than
     * shown whole — 80vh between 380 and 650 — with the copy at 40% and a 5%
     * gap between them. */
    @media (min-width: 881px) {
        :scope.blocks--article .block--quote { gap: 5%; }
        :scope.blocks--article .block--quote .block-figure {
            width: 50%;
            min-height: 380px;
            height: 80vh;
            max-height: 650px;
            padding-right: 0;
            overflow: hidden;
        }
        :scope.blocks--article .block--quote .block-figure img { height: 100%; object-fit: cover; }
        /* A caption shares the fixed frame with the picture: the picture takes
         * what the caption leaves, or the caption is pushed out and clipped. */
        :scope.blocks--article .block--quote .block-figure:has(figcaption) {
            display: flex;
            flex-direction: column;
        }
        :scope.blocks--article .block--quote .block-figure:has(figcaption) > .block-zoom,
        :scope.blocks--article .block--quote .block-figure:has(figcaption) > img {
            flex: 1 1 auto;
            min-height: 0;
        }
        :scope.blocks--article .block--quote .block-content { width: 40%; }
    }

    /* The cross closes up: its lower figure rides 220px up into the row above
     * rather than the 50 the default overlaps by, and the prose beside it
     * starts at the top instead of centring.
     *
     * Both figures are sized here rather than in the default above, because
     * five other pages carry a cross on the default rules and none of them was
     * part of this. */
    :scope.blocks--article .block--cross .block-cross-small .block-figure { width: 50%; }
    :scope.blocks--article .block--cross .block-cross-large { margin-top: 0; }
    /* Flush left, on the same line the heading above it starts from — both
     * open the row at 0. At 45% it then ends on 45%, short of the 50% where
     * the upper photograph begins, so the 220px it rides up into that row
     * cannot put it over the picture. The prose opens where it ends.
     *
     * A margin and not the `translateX(8%)` this used to carry. A transform
     * percentage resolves against the ELEMENT's own width, so it would have to
     * be recomputed every time the picture is resized; a margin resolves
     * against the row and stays true. The 8% was 8% of 33%, which is why the
     * old value says nothing about where the picture actually sat. */
    :scope.blocks--article .block--cross .block-cross-large .block-figure {
        width: 45%;
        margin-left: 0;
        margin-top: -220px;
        /* `none`, and not simply left out. The default rule above declares
         * `translateX(10%)`, so dropping the line here does not remove the
         * shift — it only stops overriding it, and the picture slid 53px back
         * over the photograph above. A property is only gone when something
         * says so. */
        transform: none;
    }
    /* The prose opens on 50%, under the left edge of the photograph in the row
     * above rather than against the picture beside it: the figure ends at 45%,
     * so 5% carries it the rest of the way. */
    :scope.blocks--article .block--cross .block-cross-large .block-content {
        position: relative;
        z-index: 2;
        width: 30%;
        margin-left: 5%;
        margin-top: 60px;
        align-self: flex-start;
    }

    /* A lead sentence inside a paragraph, set a size up. */
    :scope.blocks--article .block-content p strong {
        display: inline-block;
        margin-bottom: 4px;
        font-size: 20px;
        line-height: 1.3;
    }

    /* And the button is smaller again than the sequence's own. */
    :scope.blocks--article .block-content .main-button {
        margin-block-start: 20px;
        padding: 0 24px;
        font-size: 18px;
    }
    /* -2px is measured for a 35px button; at 18 it closes the words up. Set on
     * the BUTTON so the hidden twin that reserves the width gets it too — on
     * the visible copy alone the two would disagree and the button would be
     * sized for tracking it does not use. */
    :scope.blocks--article .block-content .main-button { --hover-tracking: normal; }

    @media (max-width: 880px) {
        /* `flex` with the width, not the width alone. The half-row figures
         * above state a flex-basis, and a basis is what sizes a flex item —
         * `width: 100%` beside it changes nothing, so the pictures stayed at
         * half width on a phone, where the block is one column. */
        :scope.blocks--article .block--large .block-figure,
        :scope.blocks--article .block--large .block-content,
        :scope.blocks--article .block--small .block-figure,
        :scope.blocks--article .block--small .block-content { flex: 0 0 100%; width: 100%; }

        /* The gutter goes once the block stacks. That 2% is the gap between
         * the picture and the prose BESIDE it; at full width there is nothing
         * beside it, so all it does is hold the picture short of the text
         * underneath by a hair — which reads as a misalignment rather than as
         * a margin.
         *
         * The shared phone reset says `padding-inline: 0` for exactly this,
         * and it has been losing: it is 0-2-0 against the interview's own
         * 0-4-0 above, which states the 2% for every width. */
        :scope.blocks--article .block--large .block-figure { padding-right: 0; }

        /* A stacked picture stands 12px clear of the text above it.
         *
         * The shared phone rules give that gap to a REVERSE block, where the
         * two cells swap and the one that moves takes the margin with it, and
         * to the cross. A plain small block was never given one, so on this
         * page the photograph sat flush against the last line of the answer —
         * two of the six, which is why it read as an oddity rather than as a
         * setting.
         *
         * On the figure rather than the content, and 12 because that is the
         * gap every other pair here keeps. */
        :scope.blocks--article .block--large .block-figure,
        :scope.blocks--article .block--small .block-figure { margin-top: 12px; }

        :scope.blocks--article .block--cross .block-cross-large .block-figure {
            width: 100%;
            margin-top: 12px;
            transform: none;
        }

        /* Both photographs full width here, for the interview only.
         *
         * The shared rule above keeps the cross a cross on a phone — the upper
         * picture at 50% pushed right, the lower at 60% left — which is what
         * BlogSection.vue does and is right where the two are a composition.
         * They are not one here: this is an interview and they are two of its
         * pictures, so at 50% of a phone the upper one arrives as a thumbnail
         * with nothing beside it and the offset reads as a mistake.
         *
         * The width is on the PICTURE and not the cell, because that is where
         * the shared rule puts it; and named on `.block-zoom` too, since the
         * button wraps the image wherever the lightbox is on. */
        :scope.blocks--article .block--cross .block-cross-small .block-figure {
            display: block;
            /* The cell too, not only the picture inside it. The 50% this
             * carries on a wide screen is stated OUTSIDE any query, so it was
             * still holding the cell at half a phone while the image filled
             * that half — the shared rule that sets both cells to 100% here is
             * 0-3-0 and loses to it. */
            width: 100%;
        }
        :scope.blocks--article .block--cross .block-cross-small .block-figure > .block-zoom,
        :scope.blocks--article .block--cross .block-cross-small .block-figure > img,
        :scope.blocks--article .block--cross .block-cross-large .block-figure > .block-zoom,
        :scope.blocks--article .block--cross .block-cross-large .block-figure > img {
            width: 100%;
        }
        :scope.blocks--article .block--cross .block-cross-large .block-content {
            position: static;
            width: 100%;
            margin-left: 0;
            margin-top: 12px;
        }
        :scope.blocks--article .block-content .main-button { margin-bottom: 24px; }
    }

    /* The interview's photo strip is a StylingSlider, not the details strip
     * the other pages use — Celebrity.vue passes it the page's own `gallery`
     * covers and resizes it: 500px slides, 80vh tall between 380 and 650, the
     * photographs cropped to fill, and 8px between them.
     *
     * So it loses the details strip's furniture here: no gold rules above and
     * below, no 19/15 of padding, and a 55px gap under it instead. Same
     * element, a different strip. */
    :scope.blocks--article .block--gallery {
        margin-block-end: 55px;
        padding: 0;
    }
    /* 24px under the strip on a phone rather than 55. On a wide screen that
     * gap reads as the pause between an interlude and the interview resuming;
     * on a narrow one, where the strip is a third of the height and what
     * follows opens with a photograph of its own, the same 55 is a hole.
     *
     * Stated HERE, right after the rule it overrides, and not up in the
     * article block's own `@media (max-width: 880px)` — that block comes
     * EARLIER in this file, both selectors weigh 0-3-0, and the later 55
     * simply won. Third time today. */
    @media (max-width: 880px) {
        :scope.blocks--article .block--gallery { margin-block-end: 24px; }
    }
    :scope.blocks--article .block--gallery::before,
    :scope.blocks--article .block--gallery::after { content: none; }

    /* One photograph under the other on a phone, for the interview only.
     *
     * The rail is a flex row that scrolls sideways, and below 1023 each frame
     * is two thirds of the screen cropped to 380px. That reads as a strip of
     * details beside prose, which is what it is on a styling entry — but here
     * the twelve frames ARE the shoot, and a reader gets them as thumbnails to
     * drag through. Stacked, each is the width of the page and keeps the shape
     * it was taken in.
     *
     * Nothing here hides the arrows: rail.js sets `data-arrows` from
     * `scrollWidth - clientWidth > 2`, and a column does not overflow
     * sideways, so it takes them off by itself. `object-fit: cover` from the
     * rule above survives and is inert — with the height back to `auto` the
     * box already has the picture's own ratio, so there is nothing to crop.
     *
     * 1023 and not the 880 the article block uses elsewhere: 1023 is where the
     * gallery switches to its phone treatment, and this replaces that
     * treatment rather than sitting beside it. */
    @media (max-width: 1023px) {
        /* Into the column the prose keeps. The rule above takes the padding
         * off so the rail can run edge to edge, which is what a rail should
         * do; a stacked photograph is a picture in the article and lines up
         * with everything else in it. 50px is `.block`'s own gutter at this
         * width — restated rather than inherited, because that `padding: 0`
         * is 0-3-0 and `.block` is 0-1-0. */
        :scope.blocks--article .block--gallery { padding-inline: 50px; }
        :scope.blocks--article .block--gallery .gallery {
            display: block;
            overflow-x: visible;
        }
        /* All three, not just the height: the interview's own tile rules below
         * also state `min-height: 380px` and `max-height: 650px`, and those
         * are 0-3-0 against this 0-4-0 — the height gives way and the two
         * bounds do not, which leaves a portrait clamped and a landscape
         * padded out to 380. A stacked picture is as tall as it is. */
        :scope.blocks--article .block--gallery .gallery-item {
            height: auto;
            min-height: 0;
            max-height: none;
        }
        /* The reveal under the stack. `block` rather than the button's own
         * inline-block, so it sits on its own line; centred, because the
         * photographs above it run the full width and an edge to line up with
         * would be arbitrary. Hidden until gallery-more.js has collapsed the
         * list — see the note in the partial. */
        :scope.blocks--article .block--gallery .gallery-more {
            display: block;
            width: fit-content;
            margin: 16px auto 0;
            /* The interview's own button size — the same 18px and 2px/24px
             * the prose buttons on this page take. The shared button is 35px
             * in a 49px gutter, which beside a column of photographs read as
             * the loudest thing on the page. */
            padding: 2px 24px;
            font-size: 18px;
        }
        :scope.blocks--article .block--gallery .gallery-more[hidden] { display: none; }

        :scope.blocks--article .block--gallery .gallery-item + .gallery-item {
            margin-block-start: 12px;
        }
        :scope.blocks--article .block--gallery .gallery-item img { height: auto; }
    }
    /* `.block` narrows to 5% at 880 and the stack follows it.
     *
     * AFTER the block above and not inside the 880 rule further up, which sets
     * this section's bottom margin: that one comes earlier and weighs the same
     * 0-3-0, so the 50px would simply win there and the pictures would keep a
     * desktop gutter on a phone. Same ordering trap as the margin it carries,
     * noted in its own comment. */
    @media (max-width: 880px) {
        :scope.blocks--article .block--gallery { padding-inline: 5%; }
    }

    :scope.blocks--article .gallery { gap: 8px; }

    :scope.blocks--article .gallery-item {
        flex: 0 0 500px;
        min-height: 380px;
        height: 80vh;
        max-height: 650px;
    }
    :scope.blocks--article .gallery-item img {
        height: 100%;
        object-fit: cover;
    }

    @media (max-width: 880px) {
        :scope.blocks--article .gallery-item { flex: 0 0 240px; }
    }
    @media (max-width: 480px) {
        :scope.blocks--article .gallery-item {
            flex: 0 0 160px;
            min-height: 0;
            height: 330px;
            max-height: none;
        }
    }
}

/* ═══ sections/children ═══ */
/* The index listing — layouts/sections/children.liquid. */
@scope (.children) {
    :scope {
        display: grid;
        gap: var(--gutter);
        padding: var(--gutter);
        grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    }

    .child-link { display: block; text-decoration: none; }

    .child-image {
        width: 100%;
        aspect-ratio: 3 / 4;
        object-fit: cover;
        object-position: center;
    }

    .child-title { margin-block: 1rem 0.25rem; }
    .child-summary { margin: 0; color: var(--c-grey); }
}

/* ═══ sections/contact-details ═══ */
/* Address and telephone — layouts/sections/contact-details.liquid. */
@scope (.contact-details) {
    :scope {
        display: grid;
        gap: var(--gutter);
        padding: var(--gutter);
    }

    .cd-call-label { margin-block: 0 0.5rem; }
    .cd-phone { font-size: 1.5rem; }
    .cd-call-info { color: var(--c-grey); }

    /* <address> is italic by default in every browser, which is not what this
     * is for — it is the element for contact details, not for emphasis. */
    .cd-address { font-style: normal; }
    .cd-street { margin-block-start: 0; }

    .cd-mailboxes { display: grid; gap: 0.25rem 1rem; margin: 0; }
    .cd-mailboxes dt { color: var(--c-grey); }
    .cd-mailboxes dd { margin: 0; }

    @media (min-width: 48rem) {
        :scope { grid-template-columns: 1fr 1fr; }
        .cd-mailboxes { grid-template-columns: auto 1fr; }
    }
}

/* ═══ sections/cta ═══ */
/* The call to action — layouts/sections/cta.liquid.
 * Ported from app/src/components/Cta.vue. */
@scope (.cta) {
    :scope {
        position: relative;
        width: 100%;
        min-height: 380px;
        height: 80vh;
        max-height: 760px;
        /* 8 with the strip below it — the gap that row keeps between its own
         * cards. See the note in sections/stories.css. Adjacent margins
         * collapse to the larger, so this one had to come down with it, and
         * it is stated as the same variable rather than as an 8 that would
         * quietly stop matching. Below 1024 the block keeps its own 14. */
        margin-block-end: var(--story-gap, 8px);
        color: var(--c-white);
    }

    /* See the note in sections/hero.css: a document's photograph is an <img>. */
    .cta-bg {
        position: absolute;
        inset: 0;
        z-index: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        object-position: center;
    }

    /* A tenth of black over the photograph, so white copy holds on a light
     * frame without darkening the picture. */
    :scope::after {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 1;
        background-color: #000;
        opacity: 0.1;
        pointer-events: none;
    }

    .cta-content {
        position: absolute;
        top: 45%;
        left: 50%;
        z-index: 2;
        width: 100%;
        max-width: 1280px;
        padding-inline: 50px;
        transform: translate(-50%, -50%);
    }

    .cta-title {
        margin: 0;
        font-family: var(--font-display);
        font-size: 35px;
        font-weight: 900;
        line-height: 1;
        text-transform: uppercase;
        color: var(--c-white);
    }

    .cta-intro {
        max-width: 32rem;
        margin-block-start: 25px;
        font-family: var(--font-body);
        font-size: 20px;
        line-height: 1.4;
    }

    /* Reaches sideways, so it names the root again — inside the block a bare
     * modifier would be looked for as a descendant. */
    :scope.cta--right .cta-content { text-align: right; }
    :scope.cta--right .cta-intro { margin-inline-start: auto; }

    @media (max-width: 980px) {
        :scope { margin-block-end: 14px; }
        /* 50px above and below with the 4% — this had inline padding only, so
         * the copy sat against the top and bottom of its band. */
        /* The band grows with its words here instead of holding 80vh.
         *
         * The copy is absolutely positioned and centred on 45% of the height,
         * which is right while it fits. On a 360px screen the heading takes
         * two lines and the paragraph eight, and that is taller than 80vh — so
         * the box overflowed the band at both ends: the title against the top
         * edge of the photograph, the button cut off at the bottom. Padding
         * could not help, being inside a box that centres rather than grows.
         *
         * In flow, the band is as tall as what it holds and the padding is the
         * air around it. `min-height` stays, so a short one still reads as a
         * band. The photograph is `inset: 0` and follows the new height.
         *
         * The banner blocks take the same arrangement at this width. */
        :scope {
            height: auto;
            max-height: none;
        }
        .cta-content {
            /* `relative`, not `static`. The copy carries `z-index: 2` to sit
             * over the photograph and the wash above it, and z-index is
             * ignored on a static box — so static put the words behind both
             * and the band came up empty but for the button, which has
             * positioning of its own.
             *
             * Relative keeps the stacking and still leaves the box in flow,
             * which is the point; `top` and `left` have to be given back
             * though, or the 45%/50% from the rule above would offset it. */
            position: relative;
            top: auto;
            left: auto;
            transform: none;
            max-width: none;
            padding: 50px 4%;
        }
        /* The mirrored variant stops mirroring — `justify-content: flex-start`
         * in the component, which is a flex row there. Ours aligns with
         * `text-align`, so the reset is the same idea in the same terms. */
        :scope.cta--right .cta-content { text-align: left; }
        :scope.cta--right .cta-intro { margin-inline-start: 0; }

        /* 17 rather than 20. At 20 on a 360px screen this paragraph runs to
         * eight lines and fills the band on its own — it is a teaser for
         * another page, not the page. The heading keeps its size; the two were
         * close enough on a phone that the copy read as a second headline. */
        .cta-intro { font-size: 17px; }
    }
}

/* ═══ sections/enquiry ═══ */
/* The enquiry forms — layouts/sections/enquiry.liquid.
 * Ported from app/src/components/Contacts/ContactsForm.vue. */
@scope (.enquiry) {
    :scope { background-color: var(--c-panel); }

    .enquiry-inner {
        max-width: 1280px;
        margin-inline: auto;
        padding: 0 50px 50px;
    }

    /* No width cap here. The 720px in ContactsForm.vue is on `.section` — the
     * block of checkboxes or fields — and NOT on `.features`, which carries
     * the heading and the note. Ours is one fieldset holding both, so the cap
     * sat on all of it and squeezed the copy into half the column it should
     * have: the two-column notes could not spread, and the form read as
     * narrow. The cap moves to the controls below. */
    .enquiry-group {
        margin: 0;
        padding: 0;
        border: 0;
    }

    .enquiry-legend {
        padding: 40px 0 0;
        font-family: var(--font-display);
        font-size: 25px;
        font-weight: 900;
        text-transform: uppercase;
        color: var(--c-text);
    }

    .enquiry-note {
        margin: 0;
        padding-top: 20px;
        font-family: var(--font-body);
        font-size: 16px;
        line-height: 1.4;
        color: var(--c-text);
    }

    /* ── choices ──────────────────────────────────────────────────────── */
    /* `.section` in the component: the controls, capped at 720 where the copy
     * above them runs the full column. */
    .enquiry-options { padding-top: 20px; max-width: 720px; }

    .enquiry-option {
        display: inline-block;
        margin-top: 15px;
        margin-right: 4px;
        vertical-align: text-top;
    }

    /* The box and the dot are one sprite, positioned per state.
     *
     * The original hid the real input inside a styled <div> and moved a class
     * onto it from JavaScript — so with no script the control had no visible
     * state at all. `appearance: none` lets the input itself carry the sprite,
     * which means :checked and :hover do the work and nothing has to run. */
    .enquiry-option input {
        appearance: none;
        display: inline-block;
        width: 22px;
        height: 22px;
        margin: 0;
        vertical-align: bottom;
        transform: translateY(-2px);
        background: url('../media/raw/decor/checkbox-sprite.png') no-repeat;
        cursor: pointer;
    }
    .enquiry-option input[type="checkbox"] { background-position: 0 0; }
    .enquiry-option input[type="checkbox"]:hover { background-position: -24px 0; }
    .enquiry-option input[type="checkbox"]:checked { background-position: -48px 0; }
    .enquiry-option input[type="radio"] { background-position: -120px 0; }
    .enquiry-option input[type="radio"]:hover { background-position: -144px 0; }
    .enquiry-option input[type="radio"]:checked { background-position: -168px 0; }

    .enquiry-option label {
        display: inline;
        padding-inline: 8px 20px;
        font-family: var(--font-body);
        font-size: 20px;
        color: var(--c-text);
        cursor: pointer;
    }

    .enquiry-price { color: var(--c-grey); }

    /* ── text fields ──────────────────────────────────────────────────── */
    /* The fields share a row. Each is its own <p>, which is a block box, so
     * they stacked — and the phone rule below has been undoing a side-by-side
     * row that never happened: it lifts the max-width and drops the right
     * margin "for the desktop row, where three fields sit side by side". The
     * row was described in a comment and nowhere else.
     *
     * `inline-block` rather than a flex container, because the fields are
     * siblings of the legend and the note inside the fieldset and those are
     * not part of the row. They wrap by themselves when the column runs out,
     * which is what should happen with four of them on a narrow desktop. */
    .enquiry-field { display: inline-block; vertical-align: top; margin: 0; padding-top: 20px; }

    .enquiry-field input,
    textarea {
        min-width: 200px;
        max-width: 720px;
        padding: 12px 10px;
        border: 1px solid #737373;
        border-radius: 0;
        background-color: var(--c-white);
        font-family: var(--font-body);
        font-size: 13px;
        color: var(--c-text);
    }
    .enquiry-field input { margin-right: 10px; }
    textarea { display: block; width: 100%; resize: none; margin-top: 20px; }

    ::placeholder {
        font-family: var(--font-body);
        font-size: 13px;
        color: var(--c-text);
        opacity: 1;
    }

    /* ── the upload, and the submit ───────────────────────────────────── */
    .enquiry-upload {
        display: inline-block;
        margin-top: 20px;
        padding: 5px 35px;
        border: 1px solid var(--c-text);
        border-radius: 15px;
        font-family: var(--font-condensed);
        font-size: 20px;
        font-weight: 900;
        text-transform: uppercase;
        color: var(--c-text);
        cursor: pointer;
    }
    /* Under the button, not beside it — Georgi's. The component sets it on
     * the same line, which is where ours was too.
     *
     * The type is the component's and ours was not: 20px of the condensed
     * face in caps, against the 16px of PT Serif it had inherited from the
     * form. The button beside it is the same size at weight 900, so the two
     * read as one control with its state under it. */
    /* The button and its counter, on a row of their own. Below 1024 the
     * counter sits UNDER the button; from 1024 it moves to its right, on the
     * same line, which is where the component keeps it at every width. */
    .enquiry-upload-row {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
    }

    @media (min-width: 1024px) {
        .enquiry-upload-row {
            flex-direction: row;
            align-items: center;
            gap: 16px;
        }
        .enquiry-upload-count { margin-block-start: 0; }
    }

    .enquiry-upload-count {
        display: block;
        margin-block-start: 12px;
        font-family: var(--font-condensed);
        font-size: 20px;
        text-transform: uppercase;
        color: var(--c-grey);
    }

    .enquiry-submit {
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        gap: 20px;
        margin-top: 40px;
    }

    .enquiry-submit button {
        padding: 5px 35px;
        border: 1px solid var(--c-text);
        border-radius: 15px;
        background-color: var(--c-panel);
        font-family: var(--font-condensed);
        font-size: 20px;
        font-weight: 900;
        text-transform: uppercase;
        color: var(--c-text);
        cursor: pointer;
        transition: all 0.5s;
    }
    /* A wash over the panel, not an inversion — the original goes to
     * `rgba(158, 158, 158, 0.3)` and keeps its dark words. Ours flipped to
     * solid #404040 with light text, which is a much louder change than the
     * button is making. */
    .enquiry-submit button:not([disabled]):hover {
        background-color: rgb(158 158 158 / 0.3);
    }
    /* Disabled until the form is wired, and it should look it. */
    .enquiry-submit button[disabled] { opacity: 0.5; cursor: not-allowed; }

    .enquiry-kind,
    .enquiry-pending {
        font-family: var(--font-condensed);
        font-size: 20px;
        text-transform: uppercase;
        color: var(--c-grey);
    }
    .enquiry-pending { font-style: italic; text-transform: none; }

    /* What the form says back — scripts/page/enquiry.js.
     *
     * In the submit row's own voice rather than a colour of its own: the
     * difference between "sent" and "went wrong" is carried by the words, and
     * a green tick beside a black-and-gold form would be the only green on
     * the site. Only the failure takes a colour, because that one has to stop
     * a reader who is about to walk away believing it went.
     *
     * It is `hidden` until there is something to say, so the row does not
     * reserve a line for a message that may never come. */
    .enquiry-result {
        margin-block-start: 16px;
        font-family: var(--font-condensed);
        font-size: 20px;
        text-transform: uppercase;
        color: var(--c-grey);
    }
    .enquiry-result.is-bad { color: #b3261e; }
    .enquiry-result[hidden] { display: none; }

    /* `.mobile &` — 1023 — carries the gutter. The 270px field is the
     * component's own `@media (max-width: 760px)` and stays there; the two
     * were sharing a block. */
    @media (max-width: 1023px) {
        .enquiry-inner { padding: 0 5% 5%; }
    }

    @media (max-width: 760px) {
        /* Full width, not the component's 270px — Georgi's. On a narrow
         * screen a field that stops 100px short of the panel it sits in reads
         * as a mistake rather than as a measure. `max-width` is lifted with
         * it, and the right margin goes: both are for the desktop row, where
         * three fields sit side by side. */
        /* One to a line again, and the <p> goes back to being a block —
         * `inline-block` above would otherwise hold each field to its own
         * width and sit them two abreast on a phone. */
        .enquiry-field { display: block; }
        .enquiry-field input {
            display: block;
            width: 100%;
            max-width: none;
            margin-right: 0;
        }
    }

    /* ── the dark form ────────────────────────────────────────────────────
     *
     * `:scope.enquiry--dark`, not `.enquiry--dark`. The scope root here is the
     * FORM, and the modifier sits on it — a bare class would ask for one
     * nested inside another, which is nothing. Second time today: the same
     * mistake took out the whole interview variant in blocks.css.
     *
     * The second of the two is set on #404040 with white copy —
     * `.article.optional` in ContactsForm.vue. None of it had come across, so
     * both forms stood on the same light panel and the page lost the division
     * between the short enquiry and the full one.
     *
     * The checkbox sprite changes with it: `gray.png` rather than
     * `square.png`, because a dark-on-light tick is invisible on #404040.
     * Copied in beside the other one. */
    :scope.enquiry--dark { background-color: var(--c-text); }

    :scope.enquiry--dark .enquiry-legend,
    :scope.enquiry--dark .enquiry-note,
    :scope.enquiry--dark .enquiry-option label,
    :scope.enquiry--dark .enquiry-field label,
    :scope.enquiry--dark .enquiry-kind,
    :scope.enquiry--dark .enquiry-pending,
    :scope.enquiry--dark .enquiry-result { color: var(--c-white); }
    /* The failure keeps its own colour on the dark panel too — a lighter red,
     * because #b3261e on #404040 is barely a colour at all. */
    :scope.enquiry--dark .enquiry-result.is-bad { color: #ff8a80; }

    :scope.enquiry--dark .enquiry-option input[type="checkbox"],
    :scope.enquiry--dark .enquiry-option input[type="radio"] {
        background-image: url('../media/raw/decor/checkbox-sprite-dark.png');
    }

    /* `.enquiry-upload` IS the control — a <label> for the hidden file input,
     * not a button inside a wrapper. Written as `.enquiry-upload button` it
     * matched nothing, so on the dark panel the label kept its #404040 border
     * and words and disappeared into the ground behind it. */
    :scope.enquiry--dark .enquiry-upload,
    :scope.enquiry--dark .enquiry-submit button {
        border-color: var(--c-panel);
        background-color: var(--c-text);
        color: var(--c-panel);
    }
    :scope.enquiry--dark .enquiry-upload:hover,
    :scope.enquiry--dark .enquiry-submit button:not([disabled]):hover {
        background-color: rgb(255 255 255 / 0.1);
    }

    /* White on the dark form, where the component puts it — a shade brighter
     * than the #f1f1f1 button above it, which is what separates the state
     * from the control. */
    :scope.enquiry--dark .enquiry-upload-count { color: var(--c-white); }
    :scope.enquiry--dark .enquiry-price { color: var(--c-panel); }

    /* A note set in two columns — `.features-content` in ContactsForm.vue.
     * Two of the dark form's three notes carry it and the third does not, so
     * the document says which rather than the stylesheet counting them. */
    /* On a <p> where the note is one paragraph, and on a wrapper where it is
     * two — so a pair runs from the foot of the left column into the head of
     * the right as one passage, rather than each splitting on its own. */
    .enquiry-note--columns {
        padding-top: 20px;
        columns: 2;
        column-gap: 40px;
        column-rule: 1px outset var(--c-line);
    }

    /* The paragraphs inside a wrapped pair carry the note's own type. */
    .enquiry-note--columns .enquiry-note { padding-top: 0; }

    @media (max-width: 760px) {
        .enquiry-note--columns { columns: auto; column-rule: none; }
    }
}

/* ═══ sections/features ═══ */
/* The introduction block — layouts/sections/features.liquid.
 * Ported from app/src/components/FeatureHero.vue. */
@scope (.features) {
    /* The section is the BAND, not the column. It carries the grey gradient
     * across the full width — in the component that is the `Features` wrapper,
     * with `FeatureHero` holding the column inside it. Porting the two as one
     * element left nothing to paint the band on, and the band was missing. */
    :scope {
        position: relative;
        max-width: var(--max);
        margin-inline: auto;
    }

    /* 479px tall and no taller, so it stops partway down the reel below —
     * which is where it stops in the component too, because the reel is INSIDE
     * that wrapper there. Absolute rather than a background, because a
     * background is clipped to this section's own box and the band has to
     * outlive it: the section is about 300px tall and the band is 479. */
    :scope::before {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        height: 479px;
        background: linear-gradient(to right, var(--c-white) 0%, var(--c-line) 100%);
        pointer-events: none;
    }

    /* The column. Positioned so it paints over the band rather than under it. */
    .features-inner {
        position: relative;
        display: flex;
        max-width: 1280px;
        margin-inline: auto;
        padding: 50px;
    }

    /* The copy sits at the FOOT of its half, aligned to the inner edge, so
     * the heading meets the bottom of the photograph beside it. */
    .features-content {
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        align-items: flex-end;
        width: 50%;
        text-align: right;
    }

    .features-links a {
        display: block;
        font-family: var(--font-condensed);
        font-size: 23px;
        text-transform: uppercase;
        text-decoration: none;
        color: var(--c-black);
    }

    .features-title {
        margin: 0;
        font-family: var(--font-display);
        font-size: 25px;
        font-weight: 900;
        line-height: 1;
        text-transform: uppercase;
        color: var(--c-black);
    }

    .features-image { width: 50%; }

    /* The mirrored variant puts the photograph first and the copy left. */
    :scope.features--mirror .features-image { order: -1; }

    @media (min-width: 1620px) { :scope::before { height: 534px; } }
    :scope.features--mirror .features-content { align-items: flex-start; text-align: left; }

    @media (max-width: 760px) { :scope::before { height: 460px; } }

    /* `.mobile &` in FeatureHero — 1023, and 4% of gutter rather than the 5%
     * this had at 880. The picture takes the full width and the copy moves
     * BELOW it with `order: 1`, 40px down. */
    @media (max-width: 1023px) {
        .features-inner {
            flex-direction: column;
            flex-wrap: nowrap;
            padding-inline: 4%;
        }
        .features-image { width: 100%; }
        .features-content {
            order: 1;
            width: 100%;
            margin-top: 40px;
            align-items: flex-start;
            text-align: left;
        }
    }

    /* `.phone &` — 640. The band tightens and the copy steps down a size. */
    @media (max-width: 640px) {
        .features-inner { padding: 30px 5%; }
        .features-content { margin-top: 24px; }
        .features-content .main-button { font-size: 20px; }
        .features-content h2 { margin-top: 8px; font-size: 22px; }
    }
}

/* ═══ sections/gift ═══ */
/* The voucher block — layouts/sections/gift.liquid, and the shared promotion
 * in sections/promo.liquid, which is the same shape.
 *
 * Ported from app/src/components/BannerGift.vue. */
@scope (.gift) {
    :scope {
        position: relative;
        min-height: 412px;
        margin-block-start: 55px;
        color: var(--c-white);
    }

    /* The ground is a texture the DESIGN chose — the same on every page that
     * shows this block — so unlike the voucher photograph it stays a
     * background. */
    :scope::before {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 0;
        background-image: url('../media/web/chrome/promo-bg.jpg');
        background-repeat: no-repeat;
        background-size: cover;
    }

    /* The cut-out stands on the lower edge and overflows the block upward,
     * which is why it is taller than its container. */
    .gift-image {
        position: absolute;
        bottom: 0;
        left: 0;
        z-index: 1;
        width: 60%;
        max-width: 800px;
        height: calc(100% + 50px);
        object-fit: contain;
        object-position: center bottom;
    }

    .gift-info {
        position: relative;
        z-index: 2;
        padding: 4% var(--gift-pad-end) 4% var(--gift-pad-start);
    }

    .gift-title {
        margin: 0;
        font-family: var(--font-display);
        font-size: 35px;
        font-weight: 900;
        /* 1, not the base's 1.1. default.less set `line-height: 1` on every
         * heading and this component never overrode it, so a two-line title
         * here is 70px in the original and 77px with the base left alone. */
        line-height: 1;
        text-transform: uppercase;
        color: var(--c-white);
    }

    .gift-body p {
        margin-block-start: 25px;
        font-family: var(--font-body);
        font-size: 16px;
        line-height: 1.3;
    }

    /* The last paragraph gives up its bottom margin. default.less opened with
     * `* { margin: 0 }`, so in the original NOTHING carries one and every gap
     * in this block is a top margin — including the button's 45px.
     *
     * Our base gives `p` a 1em bottom instead. Between paragraphs it collapses
     * into the 25px above the next one and never shows; against the BUTTON it
     * does, because `.main-button` is `inline-block` and an inline-level box
     * does not collapse margins with its siblings. So the gap above the button
     * was 16 + 45 = 61px, and the whole block 16px taller than the original —
     * a difference that is invisible in the rules and only appears at the one
     * place where the collapsing stops. */
    .gift-body > :last-child { margin-block-end: 0; }

    /* The pull-quote, which the shared promotion carries on the voucher page
     * and on all 62 styling entries. Invented here the first time round — a
     * 2px rule and a 1rem indent — where the original is a heavy 8px gold bar
     * with real padding, a size of its own and a shadow to lift it off the
     * photograph behind. All four were doing work, the shadow most of all.
     *
     * The inner `margin: 0` is not tidying. `.gift-body p` gives every
     * paragraph 25px above it, and with no block padding or border on the
     * blockquote itself that margin would COLLAPSE THROUGH it — the gap would
     * appear above the bar rather than inside it, and the bar would start
     * below the first line of the quote. */
    .gift-body blockquote { margin: 30px 0 0; }

    .gift-body blockquote > p {
        margin: 0;
        padding: 30px 15px 20px;
        border-inline-start: 8px solid var(--c-gold);
        font-size: 19px;
        font-style: italic;
        text-shadow: 3px 3px 3px rgb(0 0 0 / 0.3);
    }

    /* 30px after the quote, not the 25px every other paragraph gets. */
    .gift-body blockquote + * { margin-block-start: 30px; }

    /* `gift--left` is the MIRRORED variant: cut-out on the right, copy on the
     * left. The modifier names where the COPY goes, not the photograph — that
     * is the old build's meaning of the `position` field, and the reason both
     * documents say `left`. BannerGift.vue applied `:class="position"` and
     * styled `&.left` with `right: 0` on the image.
     *
     * The two names were swapped here, so `gift--left` matched nothing at all
     * and both pages fell through to the default — photograph left, copy
     * right, the mirror image of the old site. Renaming the field in the
     * documents was the other possible fix and the worse one: the value came
     * across from the old data, and a migration that quietly redefines a field
     * it copied is how two builds stop agreeing about what a word means.
     *
     * The padding swaps through two custom properties rather than a second,
     * heavier `.gift-info` rule. `check:scopes` caught why that matters: the
     * mobile block also sets the padding, as a plain `.gift-info` at 0-1-0,
     * and a mirror rule at 0-2-0 beat it on every page in the build — so the
     * mobile block had to repeat itself under the modifier to take effect.
     * Variables leave ONE rule owning the padding, and the later mobile rule
     * simply overwrites it. A pair of rules kept in step by hand is what this
     * file had before, and the pair had already fallen out of step. */
    :scope { --gift-pad-start: 56%; --gift-pad-end: 13%; }
    :scope.gift--left { --gift-pad-start: 13%; --gift-pad-end: 56%; }
    :scope.gift--left .gift-image { left: auto; right: 0; }

    /* The cut-out gives way before the copy does: below 1200 it takes half the
     * width instead of 60% and stands a little shorter, so the text column
     * keeps its measure rather than the two overlapping.
     *
     * The original wrote the height as `background-size: auto calc(90% + 40px)`
     * — 90% of the ELEMENT's own height plus 40px, where the element is
     * `calc(100% + 50px)` of the section. An <img> cannot be painted smaller
     * than its box, so the box shrinks instead, which comes to the same thing:
     * 0.9 x (100% + 50px) + 40px = 90% + 85px, measured against the section.
     * `object-position: center bottom` keeps it standing on the floor. */
    @media (max-width: 1200px) {
        .gift-image { width: 50%; height: calc(90% + 85px); }
    }

    /* `.mobile &` is 1023 and carries only the margin. The component's own
     * `@media (max-width: 980px)` carries the height, so the two are split —
     * they were both sitting at 980. */
    @media (max-width: 1023px) {
        :scope { margin-block-start: 20px; }
    }

    @media (max-width: 980px) {
        :scope { min-height: 0; }

        /* 60% of the block, centred, standing on the floor.
         *
         * The component writes it as `background-size: 60% auto !important`
         * on a box the full width of the block — so the drawing is 60% of the
         * block wide, its own height, centred at the bottom. An <img> says
         * the same thing by BEING that size: 60% wide, height from the
         * picture, and the box centred between left: 0 and right: 0.
         *
         * Ours was the full width with `object-fit: contain`, which scales
         * the drawing until one side meets the box — on this tall a picture
         * that is the height, and the suit came out half again too wide and
         * ran up into the copy.
         *
         * The mirrored variant is named again here: it sets `left: auto;
         * right: 0` at 0-2-0, which would beat a plain `left: 0` and push the
         * cut-out back into the corner instead of centring it. */
        .gift-image,
        :scope.gift--left .gift-image {
            top: auto;
            bottom: 0;
            left: 0;
            right: 0;
            width: 60%;
            max-width: none;
            height: auto;
            margin-inline: auto;
        }

        .gift-info { padding: 20px 4% 80%; }

        /* No button on a narrow screen — Georgi's call, against the design
         * he sent: the block ends with the cut-out standing on the floor.
         *
         * A departure from the component, which keeps it and centres it with
         * `.info-wrapper { text-align: center }`. That wrapper holds the
         * button ALONE — the heading and the paragraphs are its siblings,
         * outside it — which is why the centring here had been wrong in the
         * other direction: ours sat on the whole block and took the copy with
         * it, the one arrangement this design never uses.
         *
         * The destination is still reachable: this block advertises the
         * voucher page, which the menu names outright. */
        .main-button { display: none; }
    }
}

/* On a styling entry the promotion is spaced the way everything else on the
 * page now is — 8, the gap the strip below it keeps from its own cards.
 *
 * `Styling.vue` said `.styling .banner-gift { margin-top: 0 }` and this was 0
 * to match, but the strip under the block already carried its 8: the block sat
 * flush against the editorial above and 8 clear of the row below, which reads
 * as stuck rather than as deliberate. Asked for. An even 8 on both sides is
 * what the original's 0 was after — the block belonging to the page rather
 * than floating in it — and it is the measure the rest of the page already
 * uses.
 *
 * `.showcase` is the thing that makes a page a styling entry, so the sibling
 * combinator says it exactly: true on all 61 and on nothing else. The voucher
 * page keeps the 55px it has in the original. */
.showcase ~ .gift { margin-block-start: var(--story-gap, 8px); }

/* Under the home page's strip the block keeps the same 8 the strip keeps
 * everywhere else — the row is spaced by the gap its own cards use, and a 55
 * below it would have made that even margin even on one side only. Adjacent
 * margins collapse to the larger, which is why this is stated here and not
 * only on `.stories`. From 1024 up, as the strip's own rule is. */
@media (min-width: 1024px) {
    .stories + .gift { margin-block-start: 8px; }
}


/* ═══ sections/hero ═══ */
/* The page header — layouts/sections/hero.liquid.
 *
 * @scope keeps these rules off every other section's markup. Four rules govern
 * it, all documented in CLAUDE.md; the one that bites first is that a bare
 * class inside the block means a DESCENDANT of the root, so the root itself is
 * addressed as :scope. */
@scope (.hero) {
    /* 793px is the standard. The Vue build had SEVEN header components — one
     * per page family — and six of them were the same 793px block with the
     * same copy geometry, differing only in what else they carried. The
     * seventh, `Header.vue`, is 1200px and is used by exactly two pages: the
     * home page.
     *
     * We had 1200 for all nine, which is Header.vue's number applied to
     * everyone: seven pages were 407px taller than the design, with the
     * photograph stretched to fill the difference. The tall one is a modifier
     * now, and the document asks for it.
     *
     * The 888/680 steps below belong to the tall variant alone — none of the
     * 793px headers changes height on a narrow screen. */
    :scope {
        position: relative;
        height: 793px;
        color: var(--c-white);
    }

    :scope.hero--tall { height: 1200px; }

    /* The photograph fills the header from behind the copy. It is an <img>
     * rather than a background because it comes from the document — see the
     * comment in the partial — so it has to be placed, not merely sized. */
    .hero-bg {
        position: absolute;
        inset: 0;
        z-index: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        /* `center top`, as `background-position` has it. It only shows on a
         * window wider than the photograph's own ratio, where the crop turns
         * vertical — but then the component anchors the top and this centred
         * it, which moves the subject's head out of frame. */
        object-position: center top;
    }

    /* A wash under the copy, where the photograph is light behind it.
     *
     * The voucher's envelope lies across the middle of its frame, which is
     * where the heading goes, and white type on white paper is no heading at
     * all. Moving the picture was the first answer and the wrong one: it has
     * one composition, the copy has one place, and shifting the first far
     * enough to clear the second takes the envelope half out of frame.
     *
     * So the picture stays as it was shot and the words get a ground. It
     * fades out by 60%, which is past the copy and well before the subject,
     * so the photograph is untouched where anyone is looking at it.
     *
     * z-index 1 puts it over `.hero-bg` at 0 and under `.hero-content`, which
     * is also 1 and comes later. `pointer-events: none` because a sheet of
     * gradient has no business catching clicks. */
    /* Zoomed in and pushed right, away from the copy.
     *
     * Two knobs, and they are not the same one. `width` is the zoom: the
     * header is far wider than these pictures' own ratio, so `cover` already
     * scales them to the width and a wider box scales them further. `left` is
     * the shift: at 160% with the left edge on the frame's, the picture's
     * middle sits at 80% of the header instead of 50%.
     *
     * `object-position` can do neither — with the crop running vertical there
     * is no horizontal slack for it to take up, as the note on `.hero-bg`
     * says.
     *
     * The deeper the zoom the narrower the band of the photograph that
     * survives: the height is fixed and the width is what grows. */
    :scope.hero--shift-right .hero-bg {
        width: 160%;
        left: 0;
    }

    :scope.hero--wash-left::before {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 1;
        background: linear-gradient(to right,
            rgb(0 0 0 / 0.72) 0,
            rgb(0 0 0 / 0.5) 30%,
            rgb(0 0 0 / 0) 60%);
        pointer-events: none;
    }

    /* The copy sits at 60% of the height, not centred — the photographs are
     * framed so the subject occupies the upper two thirds.
     *
     * HORIZONTALLY it is anchored to the CONTENT COLUMN, not to the window.
     * In the component the block is absolute inside `<Content>`, which renders
     * `.content-wrapper.content`: `max-width: 1280px; margin: 0 auto;
     * padding: 50px` and `position: relative`. So the 50px is measured from the
     * edge of a centred 1280px column, and on a 1920px window the copy starts
     * at 370px — not 50. This had `left: 50px` against the header itself and
     * the text sat 320px too far left on any wide screen.
     *
     * Written as a calc rather than by adding a wrapper element: the column
     * exists here only to position one block, and `max(0px, …)` collapses it
     * to a plain 50px once the window is narrower than the column. Every other
     * section that needs this column states `max-width: 1280px` outright,
     * because there the column holds the content rather than positioning it. */
    .hero-content {
        position: absolute;
        top: 60%;
        left: calc(max(0px, (100% - 1280px) / 2) + 50px);
        z-index: 1;
        width: 456px;
        transform: translateY(-50%);
    }

    .hero-content h1 {
        margin: 0;
        font-family: var(--font-display);
        font-size: 35px;
        font-weight: 900;
        line-height: 1;
        text-transform: uppercase;
        color: var(--c-white);
    }

    /* On the paragraph that FOLLOWS the heading, as the component has it —
     * `h1 + p`. Applied to every p, a second one would get the heading's gap
     * rather than a paragraph's. */
    .hero-content h1 + p {
        margin-block-start: 25px;
    }

    .hero-content p {
        margin: 0;
        font-family: var(--font-body);
        font-size: 20px;
        font-weight: 400;
        line-height: 1.4;
    }

    /* The framed telephone, at the right of the contacts header. Anchored to
     * the same content column as the copy — 25% of it, a tenth in from its
     * right edge, centred against the picture.
     *
     * `pointer-events` is the original's and worth keeping: the wrapper lets
     * clicks through to the header behind it and only the frame itself takes
     * them, so a 456px-wide invisible box does not sit over the photograph. */
    .hero-call {
        position: absolute;
        top: 50%;
        right: calc(max(0px, (100% - 1280px) / 2) + 50px);
        z-index: 1;
        width: 25%;
        max-width: 320px;
        transform: translateY(-50%);
        pointer-events: none;
    }

    /* Three gold rules with the label straddling the open top edge — the same
     * frame as a styling entry's pull-quote. The <em>s either side of the
     * label are the missing fourth rule. */
    .hero-call-frame {
        position: relative;
        margin: 0;
        padding-top: 6px;
        border-inline: 8px solid var(--c-gold);
        border-bottom: 8px solid var(--c-gold);
        text-align: center;
        pointer-events: auto;
    }

    .hero-call-frame h2 {
        position: absolute;
        left: 0;
        width: 100%;
        display: flex;
        flex-flow: row nowrap;
        align-items: center;
        justify-content: center;
        margin: 0;
        padding-bottom: 0;
        font-family: var(--font-body);
        font-size: 23px;
        font-weight: 400;
        line-height: 25px;
        text-transform: uppercase;
        color: var(--c-white);
        transform: translateY(-50%) translateY(-3px);
    }

    .hero-call-frame h2 em {
        flex-grow: 1;
        min-width: 10%;
        height: 8px;
        background-color: var(--c-gold);
    }

    /* 700, not the component's 400 — asked for. PT Serif ships 400 and 700,
     * so this is the next weight it actually has rather than a synthesised
     * one. Noted because it is a change from ContactsHeader.vue, not a port:
     * the pull-quote in a styling entry keeps its 900 and the showcase its
     * own, so nothing else moves with it. */
    .hero-call-frame h2 strong { padding: 8px; font-weight: 700; }

    .hero-call-number {
        display: block;
        font-family: var(--font-display);
        font-size: 46px;
        font-weight: 100;
        line-height: 2.1;
        text-transform: uppercase;
        text-decoration: none;
        color: var(--c-white);
    }

    .hero-call-frame p {
        margin: -13px 0 0;
        padding: 0 15px 20px;
        font-family: var(--font-body);
        font-size: 17px;
        color: var(--c-white);
    }

    @media (max-width: 1024px) {
        .hero-call-number { font-size: 44px; }
    }

    :scope.hero--right .hero-content {
        left: auto;
        right: calc(max(0px, (100% - 1280px) / 2) + 50px);
    }

    /* Below the breakpoint the copy leaves the middle of the picture and sits
     * at the FOOT of it, narrower and smaller. All of this was missing: the
     * block kept its 60% anchor, its centring transform, its 456px width and
     * every desktop type size, so on a phone the text sat across the middle of
     * the photograph at full size. */
    /* `.mobile &` in ContactsHeader — 1023. The frame leaves the right-hand
     * column and settles at the foot, and the bars either side of its label
     * thin with it. This was written at 760 together with Header.vue's own
     * mobile block, which is a different breakpoint belonging to a different
     * component. */
    @media (max-width: 1023px) {
        /* On a phone the header is the screen, not a fixed 793 — Georgi's.
         *
         * `svh` and not `vh`: on iOS Safari and Chrome for Android `100vh` is
         * the viewport with the browser's bars HIDDEN, so it is some 110px
         * taller than what the reader can see and the foot of the header
         * would still sit under the address bar — the very thing this is
         * meant to fix. `dvh` tracks the bars and resizes the photograph
         * while you scroll. `svh` is the viewport with the bars showing: it
         * always fits and never moves. The plain `vh` above it is the
         * fallback for a browser that has none of the three.
         *
         * The floor is for a phone held sideways, where the screen is about
         * 390 tall and the copy has nowhere to go.
         *
         * The tall variant comes with them. It is the home page's alone, and
         * its 888/680 steps were an approximation of exactly this. */
        :scope,
        :scope.hero--tall {
            height: 100vh;
            height: 100svh;
            min-height: 560px;
        }

        /* The contacts header needs more than the floor.
         *
         * It is the only one carrying furniture: the copy is anchored 240 off
         * the foot and grows UPWARD, and under it sits the telephone frame,
         * pinned 40 off the bottom. On a screen whose visible height is around
         * 553 — an iPhone SE, a 360x640 Android — a three-line heading pushed
         * the copy straight through the logo: 35px of overlap on the Bulgarian
         * page at 360 and 56 at 320.
         *
         * 640 is measured rather than chosen. It is the floor at which the
         * worst case, the Bulgarian heading at 320, clears the logo by 24px;
         * at 360 it clears by 45 and the English by 65. The cost is that on
         * the shortest phones the foot of this one header falls below the
         * fold, which is the right way round: a header you scroll the last
         * inch of, rather than a heading written over the logo. */
        :scope.hero--m-high { min-height: 640px; }

        .hero-call {
            position: absolute;
            top: auto;
            bottom: 40px;
            left: 5%;
            right: 5%;
            width: auto;
            max-width: none;
            transform: none;
        }
        .hero-call-frame h2 em { height: 7px; }

        /* The copy comes with it. Between 761 and 1023 the frame is already
         * pinned to the foot and spans the full width, while the copy kept the
         * desktop's 50% anchor — so the two overlapped by 4px on every width
         * in that band. Anchored to the bottom, the same way the phone layout
         * does it below 760. */
        :scope.hero--m-high .hero-content,
        :scope.hero--m-high.hero--right .hero-content {
            top: auto;
            bottom: 240px;
            transform: none;
        }
    }

    /* The component's own — 768 for the number, 1024 above it. */
    @media (max-width: 768px) {
        .hero-call-number { font-size: 33px; }
    }

    @media (max-width: 760px) {
        .hero-content,
        :scope.hero--right .hero-content {
            left: 5%;
            right: auto;
            width: 300px;
            max-width: 90%;
            top: auto;
            bottom: 5%;
            transform: none;
        }

        .hero-content h1 { font-size: 28px; }

        .hero-content h1 + p {
            margin-block-start: 20px;
        }

        /* The four header components disagree about the copy below 760, so the
         * document says which it wants. Header.vue's is the default above. */

        /* MadeToMeasure and PremiumTextiles: at the foot like the rest, but
         * across the WHOLE column rather than in a 300px box.
         *
         * This was read out of the component as `position: static` — the
         * `.summary` there carries no positioning, so on paper it sits at the
         * top of the header in `<Content>`'s gutter. On paper. What the page
         * actually shows is the copy low on the photograph and full width,
         * and that is what it is here. The copy had been falling out of the
         * picture entirely and landing under it. */
        :scope.hero--m-wide .hero-content,
        :scope.hero--m-wide.hero--right .hero-content {
            top: auto;
            bottom: 5%;
            left: 5%;
            right: 5%;
            width: auto;
            max-width: none;
            transform: none;
        }

        /* The voucher page: at the foot as usual, but 280px. */
        :scope.hero--m-narrow .hero-content { width: 280px; }

        /* Contacts: across the full width between 5% gutters, and anchored to
         * the BOTTOM rather than 240px down from the top.
         *
         * The telephone frame under it is pinned 40px off the foot of the
         * header, and the header is a fixed 793 — so with the copy measured
         * from the top the space between them was whatever the copy's own
         * height left over: 90px at 360, 139 at 419, 223 at 640. Measured from
         * the bottom it is one gap at every width, and the copy grows upward
         * into the picture where there is nothing to collide with. */
        :scope.hero--m-high .hero-content,
        :scope.hero--m-high.hero--right .hero-content {
            top: auto;
            bottom: 240px;
            left: 5%;
            right: 5%;
            width: auto;
            max-width: none;
            transform: none;
        }

        /* Three of the four want 16px under the heading rather than 20. */
        :scope.hero--m-wide .hero-content h1 + p,
        :scope.hero--m-narrow .hero-content h1 + p,
        :scope.hero--m-high .hero-content h1 + p {
            margin-block-start: 16px;
        }

        .hero-content p {
            font-size: 16px;
            line-height: 1.3;
        }
    }
}

/* ═══ sections/notice ═══ */
/* The 404 message — layouts/sections/notice.liquid.
 * Ported from the style block of app/src/views/NotFound.vue. */
@scope (.notice) {
    :scope {
        display: grid;
        justify-items: center;
        align-content: center;
        gap: 1rem;
        min-height: 60vh;
        padding: 80px 5%;
        text-align: center;
    }

    .notice-code {
        margin: 0;
        font-family: var(--font-display);
        font-weight: 900;
        font-size: 80px;
        line-height: 1;
        color: var(--c-text);
    }

    .notice-message {
        margin: 0;
        font-family: var(--font-body);
        font-size: 18px;
        color: var(--c-text);
    }

    /* Lower than the middle on a phone — asked for. Desktop keeps its even
     * 80px and stays centred.
     *
     * An uneven padding rather than a different alignment: that way the block
     * still centres on a short screen, where the min-height is all there is. */
    @media (max-width: 1023px) {
        :scope { padding-top: 200px; }
    }
}

/* ═══ sections/prices ═══ */
/* The price list — layouts/sections/price-table.liquid.
 * Ported from app/src/components/Price/CustomerPrice.vue. */
@scope (.prices) {
    :scope {
        display: grid;
        justify-items: center;
        padding-block: 30px 32px;
        background-color: var(--c-panel);
    }

    .price-table {
        font-family: var(--font-condensed);
        font-size: 35px;
        line-height: 1;
        text-align: center;
        border-collapse: collapse;
    }

    /* The two column headings are not the same size in the original, and that
     * is worth stating because it looks like an oversight and is not ours to
     * decide: `Основна линия` is written as a `<td>` there and inherits the
     * 28px every price cell has, at weight 900 via
     * `tr:first-of-type > td:last-of-type`; `Exclusive Selection` is a `<th>`
     * and keeps the table's own 35px at the browser's bold.
     *
     * Our markup makes both proper `<th scope="col">` cells — the old pair was
     * a heading and a data cell doing the same job — so the sizes have to be
     * asked for rather than falling out of which tag each happened to be. */
    /* The two column headings are the SAME size — Georgi's, against the
     * component, where they differ because of the tags they happened to be
     * written with: `Основна линия` is a `<td>` there and inherits the price
     * column's 28, while `Exclusive Selection` is a `<th>` and keeps the
     * table's 35. On a phone that came out 14 beside 24, which reads as one
     * heading and one caption rather than as two columns of the same list.
     *
     * The weights keep their difference — 900 against 700 — because that is
     * the one thing the component states on purpose. */
    thead th {
        padding: 5px 0 5px 20px;
        font-size: 35px;
        font-weight: 900;
        text-align: right;
        vertical-align: bottom;
    }

    thead .price-exclusive { font-weight: 700; }

    tbody tr { height: 72px; }

    th, td { padding: 5px 0 5px 20px; }

    /* The item's name takes the left column. */
    tbody th {
        width: 300px;
        text-align: left;
        /* Bold, as a `<th>` is by default and as the component leaves it —
         * it states a width, an alignment and nothing about weight. `inherit`
         * here took the table's 400 and set every item's name in the same
         * face as the prices beside it. Measured on the old build: 700 at
         * every width. */
        font-weight: 700;
    }

    td {
        width: 260px;
        font-size: 28px;
        text-align: right;
    }

    /* A superseded price sits BESIDE the one that replaced it, not above it.
     * `<del>` is inline to begin with; the original puts `display: block` on
     * its `<s>` and that is what stacked them.
     *
     * The pair has to fit the cell's 260px: at 28px the longest here —
     * "от 1337€" twice over — lands close to it, so if a longer figure ever
     * arrives this is where it shows, by wrapping back onto two lines with
     * the break in a worse place than the old layout had it. The column or
     * the size is what gives then, not this rule. */
    .price-was { margin-right: 10px; }
    .price-now { text-decoration: none; }

    /* What a set includes — a continuation of the row above, so it loses the
     * row height and sits tight under it. */
    .price-note td {
        width: auto;
        height: auto;
        padding-block: 0 22px;
        font-size: 28px;
        /* 10px against a 28px face, which is the original's — it pulls the
         * note up under the row it belongs to instead of letting it stand as
         * a line of its own. Ours was 1.1, some 21px taller per note. */
        line-height: 10px;
        text-align: left;
    }
    .price-note { height: auto; }

    /* `.mobile &` on the panel — 1023, not the 480 this used to sit at, so a
     * tablet and a small laptop were getting the desktop padding.
     *
     * The numbers are added rather than copied. In the component the outer
     * padding is on `.customer-price` and the 30/32 is on `tbody`, and at this
     * width both apply: 45 + 30 above, 20 + 32 below. Ours is one element, so
     * it carries the sum. Below 480 the tbody's half goes to zero and what is
     * left is the component's own 45/20 — see the block after next. */
    @media (max-width: 1023px) {
        :scope { padding: 75px 5% 52px; }
    }

    @media (max-width: 740px) {
        tbody th { width: 270px; padding-left: 0; }
        /* 16, not the component's 14 — Georgi's: the column fills the panel
         * exactly at every narrow width, so the room for it is the room the
         * type itself takes, and 14 read as small print beside a 24px
         * heading. The current price is bold with it; the superseded one
         * above stays light, which is what tells them apart at a glance. */
        td { font-size: 20px; white-space: nowrap; }
        .price-now { font-weight: 700; }

        /* Stacked again below the breakpoint, and that is what pays for the
         * 20px above. Side by side the pair needs the width of two figures in
         * one cell, and a narrow column has not got it: at 16 it already broke
         * mid-pair — "от 594€ от" on one line and "544€" on the next, which is
         * the worst of both arrangements. One figure to a line has the whole
         * column to grow into. */
        .price-was { display: block; margin-right: 0; }
        .price-note td { font-size: 22px; padding-left: 0; }
    }

    @media (max-width: 480px) {
        /* The tbody's 30/32 goes, leaving the panel's own 45/20. */
        :scope { padding: 45px 5% 20px; }
        .price-table { font-size: 24px; line-height: 26px; }
        /* Both headings down to the table's own size, together. */
        thead th { font-size: 24px; }
        td { min-width: 92px; white-space: normal; }
        .price-note td { font-size: 18px; line-height: 16px; }
    }

    @media (max-width: 350px) {
        th, td { padding-left: 0; min-width: 78px; }
    }
}

/* ═══ sections/reel ═══ */
/* The styling reel — layouts/sections/reel.liquid.
 *
 * Ported from app/src/components/StyleReelSlider.vue, whose dimensions this
 * keeps: a 500px row, a 440px picture, a card 14.5vw wide and never under
 * 260px, and the title in condensed uppercase beneath.
 *
 * One correction on the way over. The original said
 *
 *     width: 14,5vw;
 *
 * with a comma, which is not a length — so the declaration was dropped and
 * every card fell back to its `min-width: 260px`. The row has been fixed-width
 * since it was written, on every viewport, and nothing said so: an invalid
 * declaration is not an error, it is silence. Written with a dot here, which
 * makes the cards scale with the viewport as they were meant to.
 */
@scope (.reel) {
    :scope {
        position: relative;
        /* The line the row is measured from — the cards' outer edge and the
         * arrows' glyphs both sit on it.
         *
         * It is the section above's inset, not the page gutter: `features`
         * indents its heading by 4% and by 5% below 640, while `--gutter` is
         * 4vw with a 16px floor. The two agree between 641 and 1023 and part
         * company below that, which is where the row visibly started a few
         * pixels left of the heading over it. */
        --reel-inset: var(--gutter);

        /* The arrows are the shared component in components.css; this row
         * states only what differs — the inset, the lean, and the height it
         * wants them to stand in. */
        --rail-inset: var(--reel-inset);
        --rail-lean: 15px;
        /* The picture's height, so the arrows can be told to stand on the
         * PICTURE rather than on the card. See the note by `.rail-btn`. */
        --reel-frame: 440px;
        /* No top margin. StyleReelSlider declares `margin-top: 55px` for
         * itself, and `Features` — which contains it — overrides that to 0.
         * Ours kept the 55, so the row sat that much lower than the grey band
         * above it expects. The `stories` row keeps its 55px: nothing wraps
         * that one. */
        margin-block-start: 0;
        /* The row bleeds to both edges: a reel that stops at the text column
         * reads as a component, and this one is meant to read as a shelf. */
        overflow: hidden;
    }

    .reel-track {
        display: flex;
        gap: 0;
        margin: 0;
        padding: 0 var(--reel-inset);
        list-style: none;
        overflow-x: auto;
        overscroll-behavior-x: contain;
    }

    .reel-item {
        flex: 0 0 auto;
        width: 14.5vw;
        min-width: 260px;
    }

    .reel-link {
        display: flex;
        flex-direction: column;
        align-items: center;
        height: 500px;
        text-decoration: none;
        color: var(--c-black);
    }

    .reel-frame {
        display: block;
        width: 100%;
        height: var(--reel-frame);
        overflow: hidden;
    }

    /* The arrows stand on the photograph, not on the card.
     *
     * The shared component centres them in the SECTION and offers
     * `--rail-shift` to lift them back onto the picture — and that shift is a
     * number somebody has to keep true: it is half the title block, so it
     * moved with the breakpoint, with the type size and with the language's
     * longest name. Four values, each of them right until a name changed.
     *
     * Given the picture's own height the arrows centre on it by construction,
     * at every width, in every language, whatever the titles do.
     *
     * `:scope` prefixed rather than a bare `.rail-btn`: inside `@scope` the
     * implicit prefix carries no specificity, so a bare class would tie with
     * the component's own rule and be decided by which file the bundle
     * happens to concatenate last. */
    :scope .rail-btn {
        top: 0;
        height: var(--reel-frame);
    }

    .reel-image {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: cover;
        /* Top, not centre: these are portraits and the face is at the top of
         * the frame. Centring them crops the head off. */
        object-position: top;
    }

    .reel-title {
        flex: 1;
        display: flex;
        align-items: flex-start;
        padding: 14px 6px 0;
        font-family: var(--font-condensed);
        font-size: 23px;
        font-weight: 900;
        text-transform: uppercase;
        text-align: center;
    }

    /* The original's `.mobile &`, `.phone &` and `.mini &` were classes a JS
     * observer put on the body, so they are media queries here — and the
     * widths are the ones that observer used: 1023, 640 and 360. This had them
     * collapsed onto 960 and 480, which put the card width and the picture
     * height at the wrong screens and moved the row's own height a whole
     * breakpoint early.
     *
     * The three do different things, which is why they cannot be merged:
     * `.mobile` narrows the CARD, `.phone` shortens the ROW, `.mini` shortens
     * it again. */
    @media (max-width: 1023px) {
        /* (500 - 310) / 2 — the row keeps its height here and the picture
         * loses 130px of it, so the caption's share trebles. */
        /* Two figures and a half in view, on any screen — the third card cut
         * down its middle, which is what says the row goes on.
         *
         * The cards do not overlap. They were pulled into each other for a
         * while, to close the white the photographs carry around their
         * figures, and it read as the men standing on top of one another —
         * so the row is back to plain, touching cards and the width is simply
         * the space from the inset to the far edge, in two and a half.
         *
         * That leaves no freedom for how big a picture is: with a fixed
         * source and 2.5 in view, the card width IS the size. The frame keeps
         * the component's 310. */
        .reel-item { width: calc((100vw - var(--reel-inset)) / 2.5); min-width: 0; }
        :scope { --reel-frame: 310px; }

        /* The titles set on two lines rather than one long one. Widening the
         * cards had straightened them out — a name that used to break under
         * its picture now ran the whole width of it — so the measure is
         * narrowed to 60% of the card and they fold back.
         *
         * 60 and not less: the longest single word in the row is
         * "Класическият" at about 145px, and a measure under that would leave
         * it hanging out of its own column with nowhere to break. That word
         * is also why the ceiling is 160 rather than tighter: a percentage
         * alone keeps pace with the card, so at 768 the measure came out at
         * 219px and the names straightened out again. */
        .reel-title { max-width: clamp(120px, 60%, 160px); }

        /* The component narrows the hit area to 80px here and lifts the glyph
         * 18px out of the title's line. Neither has ever rendered: both were
         * written before the 120px base in the same file and lost the tie, so
         * the row Georgi approved is the 120px box with the glyph centred.
         * Left out rather than revived — reviving it would move a thing he
         * has already looked at, and nobody asked for that. */
    }

    @media (max-width: 640px) {
        :scope { margin-block-start: 29px; --reel-inset: 5%; }
        .reel-link { height: 370px; }

        /* A long name no longer takes its height out of its own photograph.
         *
         * The card is a flex column of a fixed height with the picture on top,
         * and the picture is a flex item like any other: when a title runs
         * past the two lines its share allows, the overflow comes out of the
         * PHOTOGRAPH. Six Bulgarian names need more than two lines and the
         * longest — `Моята голяма и луда ирландска сватба` — needs five, so
         * the pictures in one row measured anywhere from 207 to 310, the men
         * stood at different heights and the titles began on different lines.
         * The English row had it worse and was fixed first.
         *
         * So: the picture keeps its height whatever the name does, and the
         * card takes whatever height it needs. Every photograph is then the
         * same size and every title starts on the same line; a long name runs
         * down into the space under the row rather than up into its own
         * picture, and nothing is cut.
         *
         * The arrows need nothing said about it: they are given the
         * picture's height directly, so they stay on its middle however tall
         * the card turns out. */
        .reel-link { height: auto; }
        .reel-frame { flex: none; }

        /* 21 rather than 23, both languages — asked for. It buys back a line
         * on several names and takes the tallest from five to four, which is
         * what the row's height is set by. */
        .reel-title { font-size: 21px; }
    }

    @media (max-width: 360px) {
        /* The picture alone. The card used to state a height of 290 here,
         * which put back exactly what the band above undoes: the frame held
         * its 240 and the title ran out of the bottom of the card instead,
         * where the row's `overflow: hidden` cut it. The card takes its own
         * height at every width now. */
        :scope { --reel-frame: 240px; }
    }

    /* The scrollbar stays until scripts/page/rail.js has added the arrows and
     * marked the section. Hiding it unconditionally is what made this row
     * unusable with a mouse: no scrollbar, no arrows, and a pointer has no
     * horizontal gesture. One affordance or the other, never neither. */
    :scope[data-arrows] .reel-track { scrollbar-width: none; }
    :scope[data-arrows] .reel-track::-webkit-scrollbar { display: none; }

    /* Drag-to-pan, for a mouse. `grab` is the standing invitation; `grabbing`
     * is the held state, set by the script only once the pointer has actually
     * moved, so a click on a card still reads as a click.
     *
     * The cursor appears only under [data-arrows] — the same attribute that
     * says there is anything to scroll. A grab cursor over a row that cannot
     * move is a promise the row does not keep. */
    :scope[data-arrows] .reel-track { cursor: grab; }
    /* `:scope[data-arrows]` repeated rather than just `.reel-track.is-dragging`,
     * which is what this said first and which never won. Inside @scope the
     * implicit `:scope` prefix contributes NO specificity, so the grab rule
     * above weighs 0-3-0 and the bare dragging rule 0-2-0 — the cursor stayed
     * `grab` all the way through a drag. check-scopes cannot catch this one:
     * `.is-dragging` is added by a script and never appears in the built HTML,
     * so pass 2 has no element to weigh the rules against. */
    :scope[data-arrows] .reel-track.is-dragging { cursor: grabbing; }

    /* A drag over a row of pictures would otherwise select text and, worse,
     * pick up the images and start a native file drag halfway through the
     * throw. Both are turned off while the hand is down, not permanently:
     * the titles stay selectable when nobody is dragging. */
    .reel-track.is-dragging, .reel-track.is-dragging * { user-select: none; }
    .reel-track.is-dragging a, .reel-track.is-dragging img { -webkit-user-drag: none; }

    /* The arrows are the shared component in components.css; this row says
     * only where its glyph sits and how far it leans. The 80px hit area the
     * mobile block used to ask for never applied — it was written before the
     * 120px base in the same file and lost the tie — so the box is one width
     * at every screen and always has been. */

}

/* ═══ sections/showcase ═══ */
/* The opening of a styling entry — layouts/sections/showcase.liquid.
 * Ported from app/src/components/Styling/ShowcaseSection.vue.
 *
 * The effect is worth reading, because it is pure CSS and easy to break:
 *
 *   the photograph is sticky at the top for one viewport height, and then
 *   pulls itself back out of the flow with `margin-bottom: -100vh`
 *
 *   each passage of copy is 110vh tall and scrolls OVER it
 *
 *   every passage after the first carries a near-black ground, so the
 *   photograph shows through the first one and the rest read on their own
 *
 * No script anywhere. `position: sticky` and a negative margin do all of it,
 * which is why it works on the first paint. */
@scope (.showcase) {
    :scope {
        position: relative;
        width: 100%;
    }

    .showcase-photo {
        position: sticky;
        top: 0;
        z-index: 0;
        width: 100%;
        height: 100vh;
        /* Takes itself back out of the flow, so the copy below starts at the
         * top of the picture rather than after it. */
        margin-bottom: -100vh;
        object-fit: cover;
        /* Top, not centre: these are full-length figures and the face matters
         * more than the shoes. */
        object-position: center top;
    }

    .showcase-copy { position: relative; z-index: 1; }

    .showcase-part {
        position: relative;
        min-height: 110vh;
    }
    /* No ground on any passage: the photograph stays visible behind all of
     * them, from the first line to the last.
     *
     * ShowcaseSection.vue gave every passage after the first `background:
     * #111`, so the picture was revealed for one screen and then covered for
     * the rest of the entry. Georgi asked for it transparent throughout —
     * a deliberate departure from the original, not something missed in the
     * port, which is why the rule is gone rather than set to `transparent`:
     * a declaration that paints nothing is one more thing to read.
     *
     * The copy stays white over an unknown photograph, as it already did on
     * the first passage. */

    /* The copy is a narrow column on the right, vertically centred in its own
     * passage. It is ONE wrapper, not every child: markdown renders a heading
     * and its paragraphs as siblings, so positioning each of them put the
     * whole passage on the same spot. */
    .showcase-text {
        position: absolute;
        top: 50%;
        right: 10%;
        width: 25%;
        transform: translateY(-50%);
        color: var(--c-white);
    }

    /* The opening passage sits lower than the rest — 60%, not 50%. It is the
     * one that reads over the photograph, and centring it put the words across
     * the face. */
    .showcase-part:first-child .showcase-text { top: 60%; }

    .showcase-part h1 {
        margin: 0;
        font-family: var(--font-display);
        font-size: 35px;
        font-weight: 900;
        line-height: 1;
        text-transform: uppercase;
        color: var(--c-white);
        /* A title that is one long word breaks rather than pushing the PAGE
         * sideways. "Концертмайсторът" is 397px against the 331 it has at
         * 360, and the 66 it could not fit came out as 51px of horizontal
         * scroll on the whole document — on a page with nothing to scroll
         * sideways to.
         *
         * `break-word` and not `hyphens: auto`: hyphenation would give a
         * prettier break, and Chrome does carry Bulgarian patterns, but it
         * also re-wraps titles that fit today — measured over all 122 styling
         * entries, five of them moved, one of which had no overflow at all.
         * This one acts only on a word that cannot fit a line by itself, and
         * over the same 122 it moves exactly the title that is broken. */
        overflow-wrap: break-word;
    }

    .showcase-part h2 {
        margin: 0;
        padding-bottom: 15px;
        font-family: var(--font-body);
        font-size: 23px;
        font-weight: 900;
        line-height: 25px;
        text-transform: uppercase;
        color: var(--c-white);
    }

    /* Nothing carries a margin of its own and every gap is stated below, which
     * is the original's model: default.less opened with `* { margin: 0 }`, so
     * the only spacing in this column comes from the four rules that ask for
     * it. Ours gave EVERY paragraph 20px above it, which is right after an h1
     * and wrong everywhere else — 20px instead of 12px between paragraphs, and
     * 35px after an h2 where the original has only the h2's own 15px of
     * padding. */
    .showcase-part p {
        margin: 0;
        font-family: var(--font-body);
        font-size: 16px;
        line-height: 1.3;
    }

    .showcase-part h1 + * { margin-block-start: 20px; }
    .showcase-part p + p { margin-block-start: 12px; }

    /* Above 1400 the copy steps up a size. The column is a fixed 25% of the
     * viewport, so on a wide screen 16px in a 400px column reads small. */
    @media (min-width: 1400px) {
        .showcase-part p { font-size: 20px; }
    }

    /* A link inside the copy — six of them across the entries, each pointing at
     * another styling. White, no underline, and a gold rule beneath: the
     * default underline in white over a photograph is nearly invisible. */
    .showcase-part p a {
        color: var(--c-white);
        text-decoration: none;
        border-bottom: 1px solid var(--c-gold);
    }

    /* The pull-quote, on 56 of the 61 entries, and the one thing in this
     * section that is a DRAWING rather than type. A gold frame open at the
     * top, with the attribution sitting on the gap:
     *
     *     ────────  ПОЛ ФЕЙГ  ────────
     *     |                          |
     *     |   "Ако костюмът ти…"     |
     *     |__________________________|
     *
     * The two bars either side of the name are the markdown's `_&nbsp;_` — a
     * pair of empty <em>s, flexed to fill whatever the name leaves and painted
     * gold at exactly the 8px of the border, so the line appears to run
     * through. The h2 is pulled up by half its own height to straddle the
     * edge.
     *
     * This had been ported as a 2px rule down the left side and italics, which
     * is what a blockquote looks like when you do not know what it is. The
     * empty <em>s would have read as stray markup rather than as the missing
     * half of the frame. */
    .showcase-part blockquote {
        position: relative;
        margin: 0;
        padding-top: 6px;
        border-inline: 8px solid var(--c-gold);
        border-bottom: 8px solid var(--c-gold);
        text-align: center;
    }

    /* No `top`: it keeps its static position, at the top of the content box,
     * and the transform lifts it onto the border line. The extra -3px is the
     * original's, and centres the 8px bars on an 8px border. */
    .showcase-part blockquote h2 {
        position: absolute;
        left: 0;
        width: 100%;
        display: flex;
        flex-flow: row nowrap;
        align-items: center;
        justify-content: center;
        padding-bottom: 0;
        font-weight: 400;
        transform: translateY(-50%) translateY(-3px);
    }

    .showcase-part blockquote h2 em {
        flex-grow: 1;
        min-width: 10%;
        height: 8px;
        background-color: var(--c-gold);
    }

    .showcase-part blockquote h2 strong {
        padding: 8px;
        font-size: 23px;
        font-weight: 900;
        text-transform: uppercase;
    }

    .showcase-part blockquote p {
        padding: 30px 15px 20px;
        font-size: 24px;
        font-style: italic;
        line-height: 1.4;
    }

    .showcase-part blockquote p + p { margin: 0; padding-top: 0; }

    /* The button is its own design here, not the shared one: the original
     * styled `.action` rather than reaching for MainButton — a gold hairline
     * instead of a white one, a size down, and a lighter face. Only the
     * differences are stated; the slide-on-hover comes from .main-button. */
    .showcase-action {
        margin-block-start: 30px;
        padding: 4px 33px;
        border-color: var(--c-gold);
        font-size: 32px;
        font-weight: 100;
        line-height: 1.05;
        color: var(--c-white);
    }

    .showcase-action span::before { font-size: 30px; }

    /* The photo credit: the gold "ТИ" with the sentence set into its right
     * edge, at the bottom-left of the whole entry.
     *
     * The negative margins are the original's and they are what makes it read
     * as one lockup rather than a picture next to a caption — the words
     * overlap the mark by 17px and ride 10px above its baseline.
     *
     * Absolutely positioned against `.showcase`, so it sits at the foot of the
     * last passage. Below 760 the copy comes out of position and into the flow,
     * which is what `padding-bottom: 100px` down there is reserving room for:
     * that rule was ported with the rest of the section while the thing it
     * makes space for was not, so the entries have been ending in 100px of
     * nothing. */
    .showcase-credit {
        position: absolute;
        left: 20px;
        bottom: 20px;
        /* The 20px it keeps on the left, it keeps on the right. The block is
         * as wide as its two parts — 130px of mark and a 270px measure — and
         * below about 420 that runs past the window: at 380 the last word sat
         * flush against the edge with nothing after it. */
        right: 20px;
        z-index: 2;
        display: flex;
        align-items: center;
    }

    .showcase-credit p {
        max-width: 270px;
        margin: -10px 0 0 -17px;
        font-family: var(--font-display);
        /* 15, a size off the component's 16 — Georgi's, looking at it on a
         * phone, where three lines of 16 crowd the mark beside them. */
        font-size: 15px;
        font-weight: 700;
        line-height: 18px;
        text-transform: uppercase;
        color: var(--c-white);
    }

    /* `.laptop &` is a RANGE — 1024 to 1380 — not a max-width. This was
     * `max-width: 1023px`, which is the one span of screens where the class
     * never applied: the heading shrank on every phone and stayed 35px on the
     * laptops it was meant for. */
    @media (min-width: 1024px) and (max-width: 1380px) {
        .showcase-part h1 { font-size: 26px; }
    }

    /* `.mobile &` — 1023, not the 760 these were written at. The copy leaves
     * the right-hand column and the section gains its 100px of skirt a whole
     * class earlier than we had it. */
    /* Below 1023 the effect STAYS. Only the copy moves: it stops being a
     * narrow column centred on the right and becomes the full width, at the
     * foot of its own passage.
     *
     * This block used to undo the whole thing — the photograph went static at
     * its natural height, the passages lost their 110vh and the copy fell
     * into the flow under the picture. That is not what the component says
     * and not what the page does: measured on the old build at 419, the
     * photograph is 900 tall and sticky, each passage is 990, and the words
     * sit 20px off the bottom of each. Ours came out 1567 tall against its
     * 3070 — a different page, not a narrower one. */
    @media (max-width: 1023px) {
        :scope { padding-bottom: 100px; }
        .showcase-text {
            top: auto;
            bottom: 20px;
            left: 4%;
            right: auto;
            width: 92%;
            transform: none;
        }
        /* Including the first, which has no 60% anchor left to keep. */
        .showcase-part:first-child .showcase-text { top: auto; }
        /* The bars either side of a pull-quote's name thin with it. */
        .showcase-part blockquote h2 em { height: 7px; }
    }
}

/* ═══ sections/stories ═══ */
/* The second styling row — layouts/sections/stories.liquid.
 *
 * Ported from app/src/components/StylingSlider.vue: a 550px row of 420px
 * cards, the photograph filling each one, a dark gradient from the left, and
 * the title and summary set over it at the bottom.
 *
 * Scrolls with CSS rather than the Carousel component, for the same reasons as
 * the reel: it works before any script runs, and the keyboard and trackpad
 * keep behaving.
 */
@scope (.stories) {
    :scope {
        position: relative;
        margin-block-start: 55px;
        /* The same air under the row as over it. The component gives this row
         * `margin-top: 55px` and nothing below, and on a narrow screen the
         * block that follows brings only its own 20 — so the strip sat 55
         * from what is above it and 20 from what is below. Adjacent margins
         * collapse to the larger, so this is a floor rather than an addition:
         * above 1023, where the next block already brings 55, nothing moves. */
        margin-block-end: 55px;
        overflow: hidden;
        /* The line the arrows' glyphs sit on — the page's own column, the
         * same one the reel above uses, so the two rows' arrows stand over
         * each other rather than on two different lines. It is NOT the
         * strip's own 8px lead-in: that belongs to the cards, which are meant
         * to run off both edges. */
        --rail-inset: var(--gutter);
        /* The gap between cards AND the lead-in before the first one. One
         * value, because they are the same measurement: the row is meant to
         * read as a strip that continues past both edges, and a wider margin
         * at the start makes the first card look like the beginning of a
         * centred block instead. It used to be `var(--gutter)` — 48px on a
         * wide screen against an 8px gap — which is exactly that mistake. */
        --story-gap: 8px;
    }

    .stories-track {
        display: flex;
        gap: var(--story-gap);
        margin: 0;
        padding-inline: var(--story-gap);
        list-style: none;
        overflow-x: auto;
        overscroll-behavior-x: contain;
    }

    .story {
        flex: 0 0 auto;
        width: 420px;
    }

    .story-link {
        position: relative;
        display: block;
        height: 550px;
        overflow: hidden;
        text-decoration: none;
        color: var(--c-white);
    }

    .story-image {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    /* The gradient is what makes white text legible over an unknown
     * photograph. It runs from the LEFT rather than up from the bottom, which
     * is the original's choice and the reason the copy is left-aligned. */
    .story-link::before {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 1;
        background: linear-gradient(to right, rgb(0 0 0 / 0.45) 0%, rgb(0 0 0 / 0) 24%);
    }

    .story-body {
        position: absolute;
        left: 0;
        bottom: 0;
        z-index: 2;
        display: block;
        width: 100%;
        padding: 30px;
        box-sizing: border-box;
    }

    .story-title {
        display: block;
        font-family: var(--font-body);
        font-size: 25px;
        font-weight: 400;
        line-height: 1.2;
        text-transform: uppercase;
    }

    .story-summary {
        display: block;
        margin-block-start: 5px;
        font-family: var(--font-body);
        font-size: 16px;
        line-height: 1.3;
        /* Reserved in the original so cards of unequal copy still line up. */
        min-height: 80px;
    }

    /* The original hid the words below `.mobile` — a class a JS observer put on
     * the body — and left the photograph alone. Kept as a media query, and
     * with visibility rather than display so the words stay available to a
     * screen reader: a card whose only accessible content is an alt attribute
     * reads as a picture, not as an article. */
    /* 1023 and 640, the widths the observer's `.mobile` and `.phone` meant.
     * These were on 960 and 480, so the narrower card and the hidden copy both
     * arrived a breakpoint late. */
    @media (max-width: 1023px) {
        /* Tighter on a narrow screen: 25 above and below rather than 55.
         * The component gives one figure for every width; on a phone that
         * much air around a 330px strip reads as a hole in the page. */
        :scope { margin-block: 25px; }
        .story { width: 240px; }
        /* The component's 80px hit area here never rendered either — same tie,
         * same loser — so the strip keeps the 120px box that is on screen. */
        .story-body {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            overflow: hidden;
            clip-path: inset(50%);
            white-space: nowrap;
        }
    }

    @media (max-width: 640px) {
        :scope { height: auto; --rail-inset: 5%; }
        .story { width: 160px; }
        .story-link { height: 330px; }
    }

    /* The scrollbar stays until scripts/page/rail.js has added the arrows and
     * marked the section. Hiding it unconditionally is what made this row
     * unusable with a mouse: no scrollbar, no arrows, and a pointer has no
     * horizontal gesture. One affordance or the other, never neither. */
    :scope[data-arrows] .stories-track { scrollbar-width: none; }
    :scope[data-arrows] .stories-track::-webkit-scrollbar { display: none; }

    /* Drag-to-pan, for a mouse. `grab` is the standing invitation; `grabbing`
     * is the held state, set by the script only once the pointer has actually
     * moved, so a click on a card still reads as a click.
     *
     * The cursor appears only under [data-arrows] — the same attribute that
     * says there is anything to scroll. A grab cursor over a row that cannot
     * move is a promise the row does not keep. */
    :scope[data-arrows] .stories-track { cursor: grab; }
    /* `:scope[data-arrows]` repeated rather than just `.stories-track.is-dragging`,
     * which is what this said first and which never won. Inside @scope the
     * implicit `:scope` prefix contributes NO specificity, so the grab rule
     * above weighs 0-3-0 and the bare dragging rule 0-2-0 — the cursor stayed
     * `grab` all the way through a drag. check-scopes cannot catch this one:
     * `.is-dragging` is added by a script and never appears in the built HTML,
     * so pass 2 has no element to weigh the rules against. */
    :scope[data-arrows] .stories-track.is-dragging { cursor: grabbing; }

    /* A drag over a row of pictures would otherwise select text and, worse,
     * pick up the images and start a native file drag halfway through the
     * throw. Both are turned off while the hand is down, not permanently:
     * the titles stay selectable when nobody is dragging. */
    .stories-track.is-dragging, .stories-track.is-dragging * { user-select: none; }
    .stories-track.is-dragging a, .stories-track.is-dragging img { -webkit-user-drag: none; }

    /* The arrows are the shared component in components.css; this strip says
     * only where its glyph sits and how far it leans. 35px here against the
     * reel's 15 — StylingSlider's own figure. */
    :scope { --rail-lean: 35px; }

}

/* On a styling entry the row is the last thing before the footer, and it takes
 * the same measure above and below as it puts between its cards — the strip
 * reads as one object with an even margin around it rather than a band shoved
 * against the footer.
 *
 * `.showcase ~` for the same reason as in gift.css: `.showcase` is what makes a
 * page a styling entry, so this is true on all 61 and on nothing else. The home
 * page keeps the 55px it was ported with, where the row sits mid-page and has
 * the voucher block under it rather than the footer.
 *
 * The variable is declared on `.stories` itself, so it resolves here. */
.showcase ~ .stories { margin-block: var(--story-gap); }

/* The home page's strip is spaced the way its own cards are — asked for, and
 * the same bargain `.showcase ~ .stories` and `.enquiry + .stories` already
 * make above: the row reads as one object with an even margin around it, and
 * the measure of that margin is the gap the cards keep from each other.
 *
 * 55 above and 55 below is what it was ported with.
 *
 * Both sides of the top gap have to come down, not just this one: adjacent
 * margins collapse to the larger, so the CTA's own 50 would have held it open
 * by itself. See sections/cta.css.
 *
 * `.cta + .stories`, because the CTA section exists on exactly one page. And
 * from 1024 up only: below it the strip keeps the 25 it was given. */
@media (min-width: 1024px) {
    .cta + .stories { margin-block: var(--story-gap); }
}

/* Following the editorial sequence the row has no gap of its own —
 * `BlogSection.vue` says `& + .styling-slider { margin-top: 0 }`, and on
 * wedding-suits, made-to-measure and premium-textiles that is where it sits.
 * The 55px it carries is for the home page, where a `text` section precedes
 * it. */
.blocks + .stories { margin-block-start: 0; }

/* After the enquiry form — the voucher page — the strip sits as close to it as
 * its own cards sit to each other. `--story-gap` is declared on `.stories`, so
 * it resolves here; the same idiom as the styling entries' rule above. */
.enquiry + .stories { margin-block-start: var(--story-gap); }

/* The form KEEPS its trailing space here, and the 8px above is measured from
 * the end of that space rather than from the button.
 *
 * It used to give it up, so that the strip would sit as close to the form as
 * the cards sit to each other — the live page has the strip INSIDE the form,
 * where the padding falls after it instead of before. Reversed on Georgi's
 * call: with the padding gone the submit button ended flush against the
 * photographs and read as though the page had been cut off there.
 *
 * So the strip still hugs, but what it hugs is the panel, not the button. The
 * second rule that went with this one set `margin-bottom: 0` on the submit
 * block, which has only a `margin-top` — it had nothing to zero. */


/* Last thing on the page: the strip closes at its own gap rather than at the
 * 55px it otherwise keeps below itself.
 *
 * That 55 is the air between the strip and whatever follows it, and it is
 * written as a floor because adjacent margins collapse — see the note on the
 * rule itself. Nothing follows here: `<footer>` is the next element, and it is
 * a sibling of `<main>`, so there is no margin on the other side to collapse
 * with and the 55 stands alone as a gap between the cards and the footer's own
 * top edge. The strip then reads as a band set apart from the page's end
 * rather than as the page's end.
 *
 * `main > .stories:last-child` says the condition outright — the strip is the
 * last thing in the document's body — and needs no `:has()`, which the scope
 * check cannot analyse. It lives out here because @scope reaches downward
 * only: an ancestor is not something a rule inside the block can name.
 *
 * Four pages today: gift, made-to-measure, premium-textiles, wedding-suits. */
main > .stories:last-child { margin-block-end: var(--story-gap); }

/* ═══ sections/text ═══ */
/* The document's own prose — layouts/sections/text.liquid.
 * Ported from app/src/components/TwoColumns.vue. */
@scope (.text) {
    /* The same column as the banner below it: TwoColumns sits inside
     * `<Content>` — max-width 1280 with 50px padding — so its content box is
     * 1180px. This was 70rem with a gutter of its own, which made it both
     * narrower than the band under it and differently inset. */
    :scope {
        width: calc(100% - 100px);
        max-width: 1180px;
        margin-inline: auto;
        /* The wrapper's vertical padding, carried by the block itself.
         * `.content-wrapper` is `padding: 50px` with
         * `> * + * { margin-top: 50px }` between its children — and adjacent
         * margins COLLAPSE, so 50 here and 50 on the banner below give the
         * 50 between them the wrapper gives, while a page with no banner keeps
         * its own spacing. Replacing the section's old `padding: var(--gutter)`
         * with nothing is what took this away. */
        margin-block: 50px;
    }

    @media (min-width: 48rem) {
        :scope {
            columns: 2;
            /* 40px, and a rule between them — the rule was missing entirely.
             * `outset` is the component's own keyword; on a 1px border most
             * engines draw it as a plain line of the named colour. */
            column-gap: 40px;
            column-rule: 1px outset var(--c-line);
        }
        /* A paragraph must not be split across the column break. */
        /* No `break-inside: avoid`. TwoColumns does not set it, and a passage
         * of prose in two columns is MEANT to run from the foot of the first
         * into the head of the second — that is what makes it two columns
         * rather than two blocks. With it, a single paragraph could not split,
         * so it stayed whole in the left column and the right one stood empty:
         * every page here whose prose is one paragraph was half a layout. */
        p { margin-block-start: 0; }
    }

    p {
        font-family: var(--font-body);
        font-size: 16px;
        font-weight: 400;
        line-height: 1.3;
        color: var(--c-ink);
    }

    /* The last paragraph gives up its bottom margin — the same fault as in
     * blocks.css and gift.css. The component's reset leaves nothing with one,
     * so our base's 1em showed up as 16px of extra grey under the prose: the
     * panel came out 351 tall where the page gives 335. */
    p:last-of-type { margin-block-end: 0; }

    @media (max-width: 760px) {
        :scope { width: 90%; }
    }

    /* Prose on the grey panel. Two pages do it and they differ by one thing:
     * the contacts page keeps its two columns — `.contacts-summary` in
     * Contacts.vue only paints the ground — and the price pages drop to one,
     * because PriceMen.vue renders the prose straight into `<Content>` rather
     * than through TwoColumns at all.
     *
     * The panel is full-bleed and the prose is not, so the column comes from
     * padding rather than from a width: `<Content>` is max-width 1280 with
     * 50px of padding, which puts its content box at 1180 centred, and `max()`
     * collapses that to a plain 50px once the window is narrower. The 50px of
     * vertical space moves inside the grey for the same reason — as a margin
     * it would have left two white bands.
     *
     * The ink differs too, and both are the original's: #404040 on contacts,
     * #000 on the price pages, against the #303030 TwoColumns gives everywhere
     * else. Three near-blacks for three blocks of prose is almost certainly
     * three people rather than a decision, but they are not ours to even out. */
    :scope.text--panel,
    :scope.text--panel-plain {
        width: auto;
        max-width: none;
        margin: 0;
        padding: 50px max(50px, calc((100% - 1180px) / 2));
        background-color: var(--c-panel);
    }

    :scope.text--panel p { color: var(--c-text); }

    :scope.text--panel-plain {
        columns: auto;
        column-rule: none;
    }

    :scope.text--panel-plain p { color: var(--c-black); }

    /* The wrapper's own narrow padding — `45px 5% 20px`, against the 50 it
     * keeps on a wide screen. Ours held the 50 at every width, so at 442 the
     * prose had 342px of measure where the page gives it 398, and sat in from
     * both edges like a quotation. */
    @media (max-width: 760px) {
        :scope.text--panel,
        :scope.text--panel-plain { padding: 45px 5% 20px; }
    }
}

/* The interview's standfirst runs as ONE column.
 *
 * Two columns with a rule between them is the newspaper setting TwoColumns.vue
 * gives every page's prose, and it earns that where the passage is long enough
 * to fill both. Here it is a single paragraph introducing a person, so the
 * rule cut it in half and the halves read as two unrelated notes.
 *
 * Aimed by the header above it rather than by a variant field: `.article-header`
 * exists in one document on the site, so `+ .text` is the interview and nothing
 * else. Outside the `@scope` block above, because @scope reaches DOWNWARD only
 * — a sideways selector cannot live in it, and out here the root has to be
 * named again. */
.article-header + .text { columns: auto; }

/* ═══ tokens/base ═══ */
/* Design tokens, taken from what the components actually use.
 *
 * NOT ported from app/src/assets/default.less. That file is boilerplate from
 * another project and almost none of it describes this site: it sets Montserrat
 * on every element, white text on everything, semi-transparent white form
 * fields, and a green palette. The components override h1–h6 forty-eight times
 * between them, and Montserrat and Bebas are declared there and used by this
 * site exactly zero times.
 *
 * Intro was on that list and should not have been. It is the family the base
 * gives every h1, and every component overrides it — except CelebrityHeader,
 * whose own `font-family` line is commented out. So exactly one heading on the
 * whole site is set in it, and dropping the face left that heading in Open
 * Sans. `--font-intro` below, declared in fonts.css.
 *
 * Two things from it are deliberately dropped rather than carried over:
 *
 *   html { font-size: 60% }   made 1rem mean 9.6px, so every rem in the CSS
 *                             said something other than what it looked like.
 *                             The components are px throughout — 911 px values
 *                             against 15 rem — so nothing depended on it.
 *
 *   *:focus { outline: none } removed the focus ring for keyboard users with
 *                             nothing in its place. base.css sets a visible
 *                             one instead.
 *
 * The palette below is the one the components actually paint with, by
 * frequency. --gold is the brand accent and the only colour style.less itself
 * declared that this site uses. */
:root {
    --max: 1920px;

    --c-white: #fff;
    --c-black: #000;
    /* Body copy — the components' most common text colour after black. */
    --c-text: #404040;
    /* The brand accent. */
    --c-gold: #c39a6b;
    /* Panel grounds, lightest first. */
    --c-panel: #f1f1f1;
    --c-panel-2: #f5f5f5;
    --c-line: #dbdcde;
    /* Quieter text: captions, codes, labels. */
    --c-grey: #737373;
    --c-grey-light: #888;
    --c-ink: #303030;

    --font-body: 'PT Serif', Georgia, serif;
    --font-display: 'Open Sans', system-ui, sans-serif;
    /* One heading uses it — see the note in fonts.css. */
    --font-intro: 'Intro', 'Open Sans', system-ui, sans-serif;
    --font-condensed: 'Open Sans Condensed', 'Open Sans', system-ui, sans-serif;

    --gutter: clamp(1rem, 4vw, 3rem);
    --nav-height: 5rem;
}

/* ═══ tokens/fonts ═══ */
/* Typefaces — ported from app/src/assets/fonts.less.
 *
 * The url() is relative to '../assets/fonts/', not to the page: a url() in a
 * stylesheet resolves against the STYLESHEET, and this bundle is always at
 * {lang}/styles/site.css. So the prefix is a constant '../' at every page
 * depth, which is why it is not built from data.root.
 *
 * The files are shared into every language root by config/pipeline.js, so the
 * same path resolves under out/bg/ and under blackmarket.bg/ alike.
 *
 * Still .ttf, as the Vue build had them. woff2 is roughly a third of the bytes
 * for the same glyphs and every browser this site supports reads it — 1.8 MB
 * of fonts is worth converting, but it needs a tool this build does not carry
 * and it is a change to what ships, so it is its own commit.
 *
 * font-display is set here and was not set there: without it a slow font
 * blocks text from painting for up to three seconds. `swap` shows the
 * fallback immediately and swaps when the face arrives.
 */
/* The series title on the interview header, and nowhere else. default.less
 * sets `h1 { font-family: 'Intro' }` and every component overrides it — except
 * CelebrityHeader, whose own family line is commented out. So one heading on
 * one page is in this face, and without the file it was falling back to Open
 * Sans there. OTF as authored; see the note above about woff2. */
@font-face {
    font-family: 'Intro';
    font-display: swap;
    src: url('../assets/fonts/intro.otf') format('opentype');
}
@font-face {
    font-family: 'Bodoni';
    font-weight: 900;
    font-style: normal;
    font-display: swap;
    src: url('../assets/fonts/bod_cb.ttf') format('truetype');
}
@font-face {
    font-family: 'Open Sans';
    font-weight: 700;
    font-display: swap;
    src: url('../assets/fonts/opensans-bold.ttf') format('truetype');
}
@font-face {
    font-family: 'Open Sans';
    font-weight: 900;
    font-display: swap;
    src: url('../assets/fonts/opensans-extrabold.ttf') format('truetype');
}
@font-face {
    font-family: 'Open Sans';
    font-weight: 300;
    font-display: swap;
    src: url('../assets/fonts/opensans-light.ttf') format('truetype');
}
@font-face {
    font-family: 'Open Sans';
    font-weight: 400;
    font-display: swap;
    src: url('../assets/fonts/opensans-regular.ttf') format('truetype');
}
@font-face {
    font-family: 'Open Sans Condensed';
    font-weight: 700;
    font-display: swap;
    src: url('../assets/fonts/opensans-condbold.ttf') format('truetype');
}
@font-face {
    font-family: 'Open Sans Condensed';
    font-weight: 300;
    font-style: italic;
    font-display: swap;
    src: url('../assets/fonts/opensans-condlightitalic.ttf') format('truetype');
}
@font-face {
    font-family: 'Open Sans Condensed';
    font-weight: 300;
    font-display: swap;
    src: url('../assets/fonts/opensans-condlight.ttf') format('truetype');
}
@font-face {
    font-family: 'Playfair Display SC';
    font-weight: 900;
    font-display: swap;
    src: url('../assets/fonts/playfairdisplaysc-black.ttf') format('truetype');
}
@font-face {
    font-family: 'Playfair Display SC';
    font-weight: 900;
    font-style: italic;
    font-display: swap;
    src: url('../assets/fonts/playfairdisplaysc-blackitalic.ttf') format('truetype');
}
@font-face {
    font-family: 'Playfair Display SC';
    font-weight: 700;
    font-display: swap;
    src: url('../assets/fonts/playfairdisplaysc-bold.ttf') format('truetype');
}
@font-face {
    font-family: 'Playfair Display SC';
    font-weight: 700;
    font-style: italic;
    font-display: swap;
    src: url('../assets/fonts/playfairdisplaysc-bolditalic.ttf') format('truetype');
}
@font-face {
    font-family: 'Playfair Display SC';
    font-weight: 400;
    font-style: italic;
    font-display: swap;
    src: url('../assets/fonts/playfairdisplaysc-italic.ttf') format('truetype');
}
@font-face {
    font-family: 'Playfair Display SC';
    font-weight: 400;
    font-display: swap;
    src: url('../assets/fonts/playfairdisplaysc-regular.ttf') format('truetype');
}
@font-face {
    font-family: 'PT Serif';
    font-weight: 400;
    font-style: italic;
    font-display: swap;
    src: url('../assets/fonts/ptserif-italic.ttf') format('truetype');
}
@font-face {
    font-family: 'PT Serif';
    font-weight: 400;
    font-display: swap;
    src: url('../assets/fonts/ptserif-regular.ttf') format('truetype');
}
