/* ---- Site chrome: theme toggle, social links, brand ---- */
.theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: 1px solid var(--ap-border);
    background: transparent;
    color: var(--ap-accent-text);
    border-radius: var(--ap-radius-button);
    transition: border-color 0.15s ease, color 0.15s ease;
}

.theme-toggle:hover {
    border-color: var(--ap-accent);
    color: var(--ap-accent-text-hover);
}

/* The footer's social icons share the theme-toggle recipe on purpose: border,
   accent icon and radius all come from the theme, so the row stays theme-agnostic. */
.social-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid var(--ap-border);
    color: var(--ap-accent-text);
    border-radius: var(--ap-radius-button);
    font-size: 0.85rem;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.social-link:hover {
    border-color: var(--ap-accent);
    color: var(--ap-accent-text-hover);
}

.nav-brand-text {
    font-family: var(--ap-font-display);
    font-weight: 800;
    font-size: 0.8rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

.nav-mono {
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

.section {
    padding: var(--ap-space-section) 0;
}

/* ---- Per-block settings (the .blk wrapper BlockRenderer emits) ---- */
/* The wrapper owns a block's vertical padding: no block template carries a
   padding utility, so these presets are the only thing that pads a block.
   Normal is one block space per edge, half a section space, so two adjacent
   blocks still add up to the theme's section rhythm. */
.blk--top-none .section {
    padding-top: 0;
}

.blk--top-compact .section {
    padding-top: calc(var(--ap-space-block) / 2);
}

.blk--top-normal .section {
    padding-top: var(--ap-space-block);
}

.blk--top-large .section {
    padding-top: calc(var(--ap-space-block) * 2);
}

.blk--bottom-none .section {
    padding-bottom: 0;
}

.blk--bottom-compact .section {
    padding-bottom: calc(var(--ap-space-block) / 2);
}

.blk--bottom-normal .section {
    padding-bottom: var(--ap-space-block);
}

.blk--bottom-large .section {
    padding-bottom: calc(var(--ap-space-block) * 2);
}

/* Device visibility. The 992px break matches the col-lg-* splits the block
   templates use, so "mobile" here means what the layout already treats as
   mobile. */
@media (max-width: 991.98px) {
    .blk--hide-mobile {
        display: none;
    }
}

@media (min-width: 992px) {
    .blk--hide-desktop {
        display: none;
    }
}

/* Split-variant column order. .split-row marks the row whose two columns the
   options swap; a row nested inside one of those columns never carries it, and
   neither does a block with no split variant. A reversal holds at every width,
   so "comme sur ordinateur" keeps its promise, and the mobile option flips it
   back. */
.blk--reversed .split-row > :first-child {
    order: 2;
}

@media (max-width: 991.98px) {
    .blk--m-inverted .split-row > :first-child {
        order: 2;
    }

    .blk--reversed.blk--m-inverted .split-row > :first-child {
        order: 0;
    }
}

@media (min-width: 992px) {
    .divider-start-lg {
        border-left: 1px solid var(--ap-border);
    }

    /* The rule belongs between the two columns, so it changes side with them.
       The gutter around it is the row's own, never a padding of this rule. */
    .blk--reversed .divider-start-lg {
        border-left: 0;
        border-right: 1px solid var(--ap-border);
    }
}

/* ---- Base typography and surfaces ---- */
.display-title {
    font-family: var(--ap-font-display);
    font-weight: 900;
    letter-spacing: -0.03em;
    text-transform: uppercase;
    line-height: 0.98;
    overflow-wrap: break-word;
    margin-top: 12px;
}

.lead-quote {
    max-width: 520px;
    font-family: var(--ap-font-mono);
    color: var(--ap-faint);
}

.section-head {
    display: flex;
    align-items: baseline;
    gap: 1.25rem;
    margin-bottom: 2.25rem;
}

/* Unscoped: badges/strip and stats/panel place the eyebrow outside a
   .section-head, and it must look the same everywhere. */
.label {
    font-family: var(--ap-font-mono);
    font-size: 0.8rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--ap-faint);
    white-space: nowrap;
}

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

.divider-accent {
    border-top: 2px solid var(--ap-accent);
}

.panel {
    position: relative;
    overflow: hidden;
    background-color: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

.panel > *,
.cta-band > .container-fluid {
    position: relative;
    z-index: 1;
}

/* ---- Steps ---- */
.stepper .step-node {
    position: relative;
    z-index: 1;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 3.5rem;
    height: 3.5rem;
    border: 1px solid var(--ap-accent);
    border-radius: 50%;
    background: var(--ap-bg);
    color: var(--ap-accent-text);
    font-size: 1.25rem;
}

.stepper-horizontal {
    display: flex;
}

.stepper-horizontal .step {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 0 1rem;
}

.stepper-horizontal .step:not(:last-child)::after {
    content: '';
    position: absolute;
    top: 1.75rem;
    left: 50%;
    width: 100%;
    height: 1px;
    background: var(--ap-border);
    z-index: 0;
}

.stepper-horizontal .step-content {
    margin-top: 1.25rem;
}

@media (max-width: 767.98px) {
    .stepper-horizontal {
        flex-direction: column;
        gap: 2.25rem;
    }

    .stepper-horizontal .step {
        align-items: flex-start;
        text-align: left;
        padding: 0;
    }

    .stepper-horizontal .step:not(:last-child)::after {
        display: none;
    }

    .stepper-horizontal .step-content {
        margin-top: 1rem;
    }
}

.meta-line {
    font-family: var(--ap-font-mono);
    font-size: 0.625rem;
    line-height: 1.9;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

/* ---- Image frames ---- */
.image-frame {
    position: relative;
    width: 100%;
    border: 1px solid var(--ap-border);
    background-color: var(--ap-panel);
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a8781' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2'/%3E%3Ccircle cx='8.5' cy='8.5' r='1.8'/%3E%3Cpath d='m21 15-4.5-4.5a2 2 0 0 0-2.8 0L5 19'/%3E%3C/svg%3E"),
        repeating-linear-gradient(45deg, var(--ap-border) 0, var(--ap-border) 1px, transparent 1px, transparent 14px);
    background-repeat: no-repeat, repeat;
    background-position: center center, 0 0;
    background-size: 52px 52px, auto;
}

/* Every frame states its crop once, and states it on the frame rather than on
   the image, so an unfilled slot is the same box as a filled one and a row of
   them keeps one baseline whatever the tenant has uploaded so far. A block that
   wants the file's own proportions puts aspect-ratio back to auto on the image
   it owns (gallery/masonry, hero/split). */
.frame-hero {
    aspect-ratio: 3 / 2;
}

.frame-square {
    aspect-ratio: 1 / 1;
}

.frame-wide {
    aspect-ratio: 3 / 2;
}

.frame-cinema {
    aspect-ratio: 21 / 9;
}

.frame-portrait {
    aspect-ratio: 4 / 5;
}

.site-image {
    width: 100%;
    object-fit: cover;
    border: 1px solid var(--ap-border);
}

/* ---- Audience cards and bullet lists ---- */
.audience-card {
    height: 100%;
    border: 1px solid var(--ap-border);
    border-top: 2px solid var(--ap-accent);
    padding: 1.75rem 1.5rem;
    transition: border-color 0.15s ease;
}

.audience-card:hover {
    border-color: var(--ap-accent);
}

.audience-icon {
    font-size: 1.6rem;
    color: var(--ap-accent);
}

.feature-item {
    display: grid;
    grid-template-columns: 1.5rem 1fr;
    gap: 0.875rem;
    padding: 0.875rem 0;
    border-top: 1px solid var(--ap-border);
    align-items: baseline;
}

.feature-item .check {
    color: var(--ap-accent);
}

/* ---- Call-to-action band ---- */
.cta-band {
    position: relative;
    overflow: hidden;
    background-color: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

/* The same field panel and split set in the body face, kept to a measure the
   full-width band would otherwise let run edge to edge. */
.cta-band__body {
    max-width: 56ch;
    color: inherit;
}

.cta-band a.btn {
    --bs-btn-bg: var(--ap-accent-contrast);
    --bs-btn-border-color: var(--ap-accent-contrast);
    --bs-btn-color: var(--ap-accent);
    --bs-btn-hover-bg: transparent;
    --bs-btn-hover-border-color: var(--ap-accent-contrast);
    --bs-btn-hover-color: var(--ap-accent-contrast);
    --bs-btn-active-bg: transparent;
    --bs-btn-active-border-color: var(--ap-accent-contrast);
    --bs-btn-active-color: var(--ap-accent-contrast);

    border-radius: var(--ap-radius-button);
}

.cta-band .eyebrow,
.cta-ink .eyebrow,
.cta-note {
    color: inherit;
}

.cta-note {
    font-family: var(--ap-font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    opacity: 0.8;
}

/* Every accent token is measured against --ap-bg, so the swapped body pair is
   the only combination an inverted surface promises (ADR-0005). */
/* The inverted surface, shared by the two blocks that close a page on an
   invitation. The accent tokens are measured against --ap-bg and carry no
   guarantee on an ink fill (ADR-0005), so everything on it inherits. */
.cta-ink,
.newsletter-band {
    border-radius: var(--ap-radius);
    background-color: var(--ap-text);
    color: var(--ap-bg);
}

.cta-ink__body {
    max-width: 52ch;
    opacity: 0.85;
}

.cta-ink .btn-accent,
.newsletter-band .btn-accent {
    --bs-btn-bg: var(--ap-bg);
    --bs-btn-border-color: var(--ap-bg);
    --bs-btn-color: var(--ap-text);
    --bs-btn-hover-bg: transparent;
    --bs-btn-hover-border-color: var(--ap-bg);
    --bs-btn-hover-color: var(--ap-bg);
    --bs-btn-active-bg: transparent;
    --bs-btn-active-border-color: var(--ap-bg);
    --bs-btn-active-color: var(--ap-bg);
}

/* ---- Blog ---- */
/* Layout goes through the Bootstrap utilities in the template; these classes
   carry nothing but the tokens and the hover. .border already resolves
   --bs-border-color onto --ap-border (theme.css). */
.article-card {
    color: var(--ap-text);
    transition: border-color 0.15s ease;
}

.article-card:hover {
    border-color: var(--ap-accent);
    color: var(--ap-text);
}

.article-card .image-frame,
.article-card .site-image {
    border: 0;
    border-bottom: 1px solid var(--ap-border);
}

/* The list variant is an index, so each row carries what a reader decides on:
   when it was written, what it is about, and the first sentence of it. */
.article-list__row {
    border-top: 1px solid var(--ap-border);
}

.article-list__row:last-child {
    border-bottom: 1px solid var(--ap-border);
}

.article-list__link {
    display: grid;
    gap: 0.4rem;
    padding: 1.5rem 0;
    color: var(--ap-text);
    text-decoration: none;
    transition: color 0.15s ease;
}

@media (min-width: 768px) {
    .article-list__link {
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: baseline;
        column-gap: 2rem;
    }

    .article-list__meta,
    .article-list__title,
    .article-list__excerpt {
        grid-column: 1;
    }

    .article-list__more {
        grid-column: 2;
        grid-row: 2;
        text-align: right;
        white-space: nowrap;
    }
}

.article-list__link:hover {
    color: var(--ap-accent-text);
}

.article-list__excerpt {
    max-width: 68ch;
    color: var(--ap-muted);
}

.article-list__more {
    color: var(--ap-accent-text);
}

.article-card-meta {
    padding-top: 0.75rem;
    font-family: var(--ap-font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.measure-article {
    max-width: 760px;
}

.article-body {
    font-size: 1.0625rem;
    line-height: 1.75;
    color: var(--ap-muted);
}

.article-body p {
    margin-bottom: 1.25rem;
}

/* ---- Public forms ---- */
.public-form .form-label {
    font-weight: 600;
    color: var(--ap-text);
}

/* The one mark every form shares: the asterisk hangs on the `required` class
   bootstrap_5_layout puts on the label, an optional field carries nothing. Its alt text
   is empty: a screen reader acts on the control's required attribute, and the legend
   (components/_required_legend.html.twig) carries the meaning for everyone else. */
.required-legend {
    color: var(--ap-muted);
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.required-mark,
.form-label.required::after {
    color: var(--ap-accent-text);
    font-weight: 700;
}

.form-label.required::after {
    content: ' *' / '';
}

.public-form .form-control,
.public-form .form-select {
    background-color: var(--ap-bg);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    color: var(--ap-text);
    font-family: var(--ap-font-body);
    padding: 0.65rem 0.9rem;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.public-form .form-control::placeholder {
    color: var(--ap-faint);
}

.public-form .form-control:focus,
.public-form .form-select:focus {
    border-color: var(--ap-accent);
    box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--ap-accent) 25%, transparent);
}

/* Radio choices as selectable cards: bootstrap_5's btn-check markup
   (visually hidden input + adjacent label), pure-CSS states. The
   public site ships no framework JS (ADR-0007). */
.public-form .choice-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
    gap: var(--ap-space-tight);
}

.public-form .choice-cards label {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    min-height: 4.25rem;
    padding: 1rem;
    background-color: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    font-weight: 600;
    color: var(--ap-text);
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.public-form .choice-cards label:hover {
    border-color: var(--ap-accent-strong);
}

.public-form .choice-cards .btn-check:checked + label {
    background-color: var(--ap-accent);
    border-color: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

.public-form .choice-cards .btn-check:focus-visible + label {
    outline: 2px solid var(--ap-accent);
    outline-offset: 2px;
}

.public-form .choice-cards .btn-check.is-invalid + label {
    border-color: var(--bs-danger);
}

/* Multi-choice select: a <details> disclosure styled as a closed .form-select.
   The panel overlays, so opening it never reflows the form. */
.public-form .choice-dropdown {
    position: relative;
}

/* display:block, not flex: it drops the disclosure triangle and it is what
   makes text-overflow work. The extra right padding keeps a long selection's
   ellipsis clear of the chevron the theme places 12px from the edge. */
.public-form .choice-dropdown-summary {
    display: block;
    cursor: pointer;
    list-style: none;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    padding-right: 2.25rem;
}

.public-form .choice-dropdown-summary::-webkit-details-marker {
    display: none;
}

.public-form .choice-dropdown-summary.is-empty {
    color: var(--ap-faint);
}

.public-form .choice-dropdown-summary:focus-visible {
    outline: 2px solid var(--ap-accent);
    outline-offset: 2px;
}

.public-form .choice-dropdown-panel {
    position: absolute;
    z-index: 3;
    inset-inline: 0;
    top: calc(100% + 0.25rem);
    max-height: 15rem;
    overflow-y: auto;
    padding: 0.75rem 1rem;
    background-color: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
}

.public-form .choice-dropdown-panel .form-check {
    margin-bottom: 0.25rem;
}

.public-form .choice-dropdown-panel .form-check-label {
    cursor: pointer;
}

/* Consent checkbox: accent fill on Bootstrap's own glyph. Its input
   is appearance:none, so accent-color would be a no-op. */
.public-form .form-check-input:checked {
    background-color: var(--ap-accent);
    border-color: var(--ap-accent);
}

.public-form .form-check-input:focus {
    border-color: var(--ap-accent);
    box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--ap-accent) 25%, transparent);
}

.public-form .form-check .form-check-label {
    color: var(--ap-muted);
}

.confirm-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    background: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

/* Multi-step forms: one fieldset per step. The thread and the nav are hidden until
   app/form_steps.js reveals them, so without the script the fieldsets read as sections. */
.form-steps__panel {
    min-width: 0;
    padding: 0;
    margin: 0;
    border: 0;
}

/* The rule between two sections; a page the script hid draws none under itself. */
.form-steps__panel:not([hidden]) + .form-steps__panel {
    margin-top: 2rem;
    padding-top: 2rem;
    border-top: 1px solid var(--ap-border);
}

.form-steps__legend {
    float: none;
    width: 100%;
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    margin-bottom: 1rem;
    font-family: var(--ap-font-display);
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--ap-text);
}

.form-steps__legend:focus {
    outline: none;
}

.form-steps__index {
    font-family: var(--ap-font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    color: var(--ap-accent-text);
}

.form-steps__intro {
    margin-bottom: 1.25rem;
}

.form-steps__thread {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-bottom: 1.5rem;
}

.form-steps__rank {
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ap-muted);
}

.form-steps__bars {
    display: flex;
    gap: 0.35rem;
}

.form-steps__bars > span {
    flex: 1;
    height: 3px;
    border-radius: 3px;
    background: var(--ap-border);
    transition: background-color 0.2s ease;
}

.form-steps__bars > .is-done {
    background: var(--ap-accent);
}

.form-steps__nav {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-top: 1.5rem;
}

/* Suivant and Envoyer keep the right edge whether or not Précédent is on screen. */
.form-steps__nav > [data-form-steps-next],
.form-steps__nav > [data-form-steps-submit] {
    margin-left: auto;
}

/* The page app/form_steps.js brings on screen slides in; the panel it hides is display:none. */
@media (prefers-reduced-motion: no-preference) {
    .form-steps__panel:not([hidden]) {
        animation: form-steps-in 0.25s ease-out;
    }

    @keyframes form-steps-in {
        from {
            opacity: 0;
            transform: translateY(0.5rem);
        }
    }
}

/* ---- FAQ ---- */
.faq details {
    border-top: 1px solid var(--ap-border);
}

.faq details:last-of-type {
    border-bottom: 1px solid var(--ap-border);
}

.faq summary {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    padding: 1.5rem 0;
    cursor: pointer;
    list-style: none;
    font-family: var(--ap-font-display);
    font-weight: 700;
    font-size: 1.15rem;
}

.faq summary:hover {
    color: var(--ap-accent-text);
}

.faq summary::-webkit-details-marker {
    display: none;
}

.faq summary .sign {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 1.75rem;
    height: 1.75rem;
    margin-left: auto;
    border: 1px solid var(--ap-border);
    border-radius: 50%;
    font-family: var(--ap-font-mono);
    color: var(--ap-accent);
    transition: transform 0.15s ease, border-color 0.15s ease;
}

.faq summary:hover .sign {
    border-color: var(--ap-accent);
}

.faq details[open] summary .sign {
    transform: rotate(45deg);
}

.faq .answer {
    padding: 0 0 1.5rem;
    color: var(--ap-muted);
}

/* ─────────────────────────────────────────────────────────────
   UI polish
   ───────────────────────────────────────────────────────────── */

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

/* Header with a subtle shadow once the page is scrolled; sticky is the
   tenant's choice (.site-nav--sticky). */
.site-nav {
    z-index: 1030;
    background-color: var(--ap-bg);
    transition: box-shadow 0.2s ease;
}

.site-nav--sticky {
    position: sticky;
    top: 0;
}

.site-nav.is-scrolled {
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
}

/* The upload keeps its own ratio: only the height is capped, so a wide mark and
   a square one both leave the navbar the same height. */
.navbar-logo {
    max-height: 60px;
}

/* Navbar variants: layout re-composition only, same markup tree, same
   tokens, no variant-specific colors. Below lg every variant keeps the stock
   Bootstrap collapse. */
@media (min-width: 992px) {
    /* Centered: brand row on top, links row below, toggle in the corner. */
    .site-nav--centered .container-fluid {
        position: relative;
        flex-direction: column;
        gap: 0.25rem;
    }

    .site-nav--centered .navbar-brand {
        justify-content: center;
        margin-right: 0;
    }

    .site-nav--centered .nav-brand-text {
        font-size: 1.35rem;
        letter-spacing: 0.02em;
        text-transform: none;
    }

    .site-nav--centered .navbar-collapse {
        justify-content: center;
    }

    .site-nav--centered .nav-item--toggle {
        position: absolute;
        top: 0.75rem;
        right: 3rem;
    }

    /* The two lists read as one row everywhere but in the split variant, which
       spends the gap on its own side balance instead. */
    .site-nav:not(.site-nav--split) .nav-group--lead:has(.nav-item) {
        margin-inline-end: 1.5rem;
    }

    /* Split: links on both sides of the centered brand. The collapse dissolves
       (display: contents) so both lists flex directly in the container, where equal
       side widths hold the brand centered whatever each side carries. */
    .site-nav--split .navbar-collapse {
        /* stylelint-disable-next-line declaration-no-important -- matches Bootstrap's own on .navbar-expand-lg */
        display: contents !important;
    }

    .site-nav--split .container-fluid {
        column-gap: 1.5rem;
    }

    .site-nav--split .nav-group {
        flex: 1 1 0;
    }

    .site-nav--split .nav-group--lead {
        order: -1;
    }

    .site-nav--split .nav-group--trail {
        order: 1;
        justify-content: flex-end;
    }

    .site-nav--split .navbar-brand {
        order: 0;
        margin-inline: 0;
    }

    .site-nav--split .nav-brand-text {
        font-size: 1.35rem;
        letter-spacing: 0.02em;
        text-transform: none;
    }
}

/* Transparent navbar over a media hero. Fixed white and translucent literals,
   never theme tokens: a scrim over a photograph is theme-invariant (the
   .hero-screen precedent). Desktop only: a transparent collapse is unreadable. */
@media (min-width: 992px) {
    .site-nav--overlay {
        position: absolute;
        inset-inline: 0;
        top: 0;
    }

    .site-nav--overlay.site-nav--sticky {
        position: fixed;
    }

    .site-nav--overlay:not(.is-past-hero) {
        --bs-border-color: transparent;

        background-color: transparent;
    }

    .site-nav--overlay:not(.is-past-hero).is-scrolled {
        box-shadow: none;
    }

    .site-nav--overlay:not(.is-past-hero)::before {
        content: "";
        position: absolute;
        inset: 0 0 auto;
        height: 34svh;
        background: linear-gradient(180deg, rgb(10 10 12 / 55%), transparent);
        pointer-events: none;
        z-index: -1;
    }

    .site-nav--overlay:not(.is-past-hero) .nav-link {
        color: rgb(255 255 255 / 72%);
    }

    .site-nav--overlay:not(.is-past-hero) .nav-link.is-active {
        color: var(--bs-white);
        border-bottom-color: rgb(255 255 255 / 70%);
    }

    .site-nav--overlay:not(.is-past-hero) .navbar-brand,
    .site-nav--overlay:not(.is-past-hero) .nav-brand-text {
        color: var(--bs-white);
    }

    .site-nav--overlay:not(.is-past-hero) .theme-toggle {
        color: var(--bs-white);
        border-color: rgb(255 255 255 / 35%);
    }

    .site-nav--overlay:not(.is-past-hero) .btn-accent {
        background: rgb(255 255 255 / 14%);
        border-color: rgb(255 255 255 / 55%);
        color: var(--bs-white);
        backdrop-filter: blur(6px);
    }
}

.footer-logo {
    max-height: 58px;
    max-width: fit-content;
}

/* Footer variants. */
.site-footer--statement {
    padding-top: 4rem;
}

.footer-statement {
    font-size: clamp(1.8rem, 4vw, 2.6rem);
    max-width: 20ch;
}

.nav-link.is-active {
    color: var(--ap-accent-text);
    border-bottom: 1px solid var(--ap-accent);
}

.arrow {
    display: inline-block;
    transition: transform 0.15s ease;
}

.btn:hover .arrow,
a:hover > .arrow {
    transform: translateX(3px);
}

.article-card,
.audience-card {
    transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.article-card:hover,
.audience-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
}

.reading-progress {
    position: fixed;
    top: 0;
    left: 0;
    height: 3px;
    width: 0;
    background: var(--ap-accent);
    z-index: 1040;
    transition: width 0.1s linear;
}

@media (prefers-reduced-motion: reduce) {
    .article-card:hover,
    .audience-card:hover {
        transform: none;
    }

    .arrow,
    .btn:hover .arrow,
    a:hover > .arrow {
        transition: none;
        transform: none;
    }

    .reading-progress,
    .resa-availability {
        transition: none;
    }
}

/* ---- Hero ---- */
.hero-statement-aside {
    border-left: 2px solid var(--ap-accent);
    padding-left: 1.25rem;
    text-wrap: pretty;
}

/* Type-led opener: with no image to carry it, the headline takes the full
   measure and the prose steps back into a reading column under it. */
.hero-typo {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.75rem;
}

.hero-typo__eyebrow::before {
    content: "";
    display: block;
    width: 3rem;
    height: 2px;
    margin: 0 auto 1.1rem;
    background: var(--ap-accent);
}

.hero-typo__headline {
    font-size: clamp(2.6rem, 7.5vw, 5.5rem);
    text-wrap: balance;
}

.hero-typo__reading {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    max-width: 60ch;
}

/* No hairline: the opening photograph reads as a plate, not as a framed
   thumbnail. It takes its crop from .frame-hero like every other slot. */
.site-image.hero-split__image {
    border: 0;
}

/* Full-screen media hero: the media owns the viewport and the text sits on it
   directly, no panel between the two. Fixed white and translucent literals,
   never theme tokens: a scrim over a photograph is theme-invariant
   (ADR-0005). */
.hero-screen {
    position: relative;
    display: flex;
    align-items: center;
    min-height: 100svh;
    overflow: hidden;
}

.hero-screen--flat {
    background: var(--ap-panel);
    color: var(--ap-text);
}

.hero-screen__image {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Darkest at the two edges, lightest across the middle, so the navbar and the
   scroll cue stay readable without a band cutting the photograph in two. It
   stops short of full transparency because the tenant's photo can be bright
   exactly where the headline lands. */
.hero-screen:not(.hero-screen--flat)::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(
        180deg,
        rgb(10 10 12 / 62%) 0%,
        rgb(10 10 12 / 24%) 32%,
        rgb(10 10 12 / 28%) 62%,
        rgb(10 10 12 / 58%) 100%
    );
    pointer-events: none;
}

/* Scroll cue. Decorative, so a pseudo-element rather than markup a screen
   reader announces, and scoped to the overlay case: that is the only one where
   the hero starts at the top of the viewport, so anywhere else the cue would
   sit below the fold it is pointing at. */
@media (min-width: 992px) {
    body.has-nav-overlay .site-nav + .blk .hero-screen:not(.hero-screen--flat)::after {
        content: "↓";
        position: absolute;
        bottom: 1.75rem;
        left: 50%;
        z-index: 2;
        transform: translateX(-50%);
        color: rgb(255 255 255 / 65%);
        font-size: 1.1rem;
        line-height: 1;
        pointer-events: none;
    }
}

.hero-screen__body {
    position: relative;
    z-index: 2;
    width: 100%;
    padding: 6rem 0 5rem;
}

.hero-screen:not(.hero-screen--flat) .hero-screen__body {
    color: var(--bs-white);
}

.hero-screen:not(.hero-screen--flat) .eyebrow,
.hero-screen:not(.hero-screen--flat) .lead-quote {
    color: inherit;
}

/* The quote rule reads as a stray mark beside a centered column. */
.hero-screen .lead-quote {
    padding-left: 0;
    border-left: none;
}

/* Glass buttons: the accent fill would be the one colored thing on the photo,
   and its contrast is guaranteed against the theme, not against the media. */
.hero-screen:not(.hero-screen--flat) .btn-accent,
.hero-screen:not(.hero-screen--flat) .btn-outline-accent {
    --bs-btn-bg: rgb(255 255 255 / 14%);
    --bs-btn-border-color: rgb(255 255 255 / 55%);
    --bs-btn-color: var(--bs-white);
    --bs-btn-hover-bg: rgb(255 255 255 / 26%);
    --bs-btn-hover-border-color: var(--bs-white);
    --bs-btn-hover-color: var(--bs-white);
    --bs-btn-active-bg: rgb(255 255 255 / 26%);
    --bs-btn-active-border-color: var(--bs-white);
    --bs-btn-active-color: var(--bs-white);

    backdrop-filter: blur(6px);
}

/* ---- Galleries ---- */
.gallery-masonry {
    columns: 2;
    column-gap: 1rem;
}

@media (min-width: 992px) {
    .gallery-masonry {
        columns: 3;
    }
}

/* The gap under a caption is what separates two images, so it has to beat the
   gap above it by enough that the caption reads as belonging to the one it
   sits under rather than to the one below. */
.gallery-masonry__item {
    break-inside: avoid;
    margin: 0 0 2.25rem;
}

.gallery-masonry__caption {
    display: block;
    margin-top: 0.4rem;
}

.gallery-masonry__item .site-image,
.gallery-masonry__item .image-frame {
    width: 100%;
    height: auto;
    min-height: 0;
    aspect-ratio: auto;
}

.gallery-masonry__item .image-frame {
    min-height: 220px;
}

/* Editor only, and after the rule above so these win: an unfilled slot has no
   image to take its proportions from, so every placeholder would render at one
   height and the mosaic would read as a grid. The template cycles the three
   shapes a mixed gallery produces. */
.gallery-masonry__item--tall .image-frame {
    min-height: 0;
    aspect-ratio: 3 / 4;
}

.gallery-masonry__item--square .image-frame {
    min-height: 0;
    aspect-ratio: 1 / 1;
}

.gallery-masonry__item--wide .image-frame {
    min-height: 0;
    aspect-ratio: 4 / 3;
}

/* An explicit width is load-bearing: the slides size with width: 100%, which
   contributes nothing to a shrink-to-fit box, so an auto-width carousel
   collapses to its indicator dots and never reaches the max-width. */
.gallery-carousel {
    width: 100%;
    max-width: 1080px;
    margin-inline: auto;
}

/* Shared by the gallery block and the boutique gallery: the arrows are
   positioned against the stage, so the thumbnails stay outside it. */
.carousel-stage {
    position: relative;
}

/* Bootstrap's carousel restyled to the theme tokens: its stock controls and
   indicators are white and vanish on the light theme. Never set a plain
   "margin" on the item: .carousel-item needs Bootstrap's margin-right: -100%. */
.gallery-carousel__item {
    margin-top: 0;
    margin-bottom: 0;
}

.carousel-control-prev:has(.carousel-chip),
.carousel-control-next:has(.carousel-chip) {
    width: 5rem;
    opacity: 1;
}

.carousel-chip {
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    background: var(--ap-panel);
    color: var(--ap-text);
    transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.carousel-control-prev:hover .carousel-chip,
.carousel-control-next:hover .carousel-chip,
.carousel-control-prev:focus-visible .carousel-chip,
.carousel-control-next:focus-visible .carousel-chip {
    border-color: var(--ap-accent);
    background: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

@media (max-width: 575.98px) {
    .carousel-control-prev:has(.carousel-chip),
    .carousel-control-next:has(.carousel-chip) {
        width: 3.5rem;
    }

    .carousel-chip {
        width: 2.25rem;
        height: 2.25rem;
    }
}

.gallery-carousel .carousel-indicators {
    position: static;
    margin: 1rem 0 0;
}

.gallery-carousel .carousel-indicators [data-bs-target] {
    background-color: var(--ap-text);
    opacity: 0.3;
}

.gallery-carousel .carousel-indicators .active {
    background-color: var(--ap-accent);
    opacity: 1;
}

.gallery-carousel__item .image-frame,
.gallery-carousel__item .site-image {
    aspect-ratio: 16 / 9;
    width: 100%;
    height: auto;
    min-height: 0;
    object-fit: cover;
    border-radius: var(--ap-radius);
}

/* A 16/9 slide on a phone is a 190px letterbox; the taller crop keeps the
   subject readable at the widths a single-image variant is built for. */
@media (max-width: 575.98px) {
    .gallery-carousel__item .image-frame,
    .gallery-carousel__item .site-image {
        aspect-ratio: 4 / 3;
    }
}

/* ---- Pricing ---- */
.pricing-tier {
    padding: 1.75rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    background: var(--ap-panel);
}

.pricing-tier--on {
    border-color: var(--ap-accent);
    box-shadow: 0 0 0 1px var(--ap-accent);
}

.pricing-tier__price {
    font-family: var(--ap-font-display);
    font-size: 1.75rem;
    line-height: 1.1;
}

.pricing-tier__list {
    list-style: none;
    margin: 0 0 1.25rem;
    padding: 0;
}

.pricing-tier__list li {
    position: relative;
    padding-left: 1.35rem;
    margin-bottom: 0.4rem;
}

.pricing-tier__list li::before {
    content: "✓";
    position: absolute;
    left: 0;
    color: var(--ap-accent);
}

.pricing-table__row {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    padding: 1.5rem 0;
    border-top: 1px solid var(--ap-border);
}

@media (min-width: 768px) {
    .pricing-table__row {
        grid-template-columns: 1fr 1.4fr auto;
        align-items: start;
        gap: 2rem;
    }
}

.pricing-table__row--on {
    border-top-color: var(--ap-accent);
}

.pricing-table__price {
    text-align: left;
}

@media (min-width: 768px) {
    .pricing-table__price {
        text-align: right;
        min-width: 10rem;
    }
}

/* Sticky cells drop their borders under border-collapse, so the matrix keeps
   separate borders and paints them cell by cell. */
/* Positioned so the cells' .visually-hidden spans resolve against this box:
   without it they escape the scroller and widen the page on a phone. */
.price-matrix {
    position: relative;
    overflow-x: auto;
    margin-inline: -0.25rem;
    padding-inline: 0.25rem;
}

.price-matrix__table {
    width: 100%;
    min-width: 34rem;
    border-collapse: separate;
    border-spacing: 0;
}

.price-matrix__corner {
    background: var(--ap-bg);
}

.price-matrix__criterion,
.price-matrix__corner {
    position: sticky;
    left: 0;
    z-index: 1;
    min-width: 11rem;
}

.price-matrix__criterion {
    background: var(--ap-bg);
    font-weight: 500;
    text-align: left;
    padding: 0.85rem 1rem 0.85rem 0;
    border-top: 1px solid var(--ap-border);
}

.price-matrix__head {
    padding: 0 1rem 1rem;
    text-align: center;
    vertical-align: bottom;
}

.price-matrix__name {
    display: block;
    font-weight: 600;
}

.price-matrix__price {
    display: block;
    font-family: var(--ap-font-display);
    font-size: 1.5rem;
    line-height: 1.1;
}

.price-matrix__cell {
    padding: 0.85rem 1rem;
    text-align: center;
    vertical-align: middle;
    border-top: 1px solid var(--ap-border);
}

.price-matrix__head--on,
.price-matrix__cell--on {
    background: rgba(var(--ap-accent-rgb), 0.07);
}

.price-matrix__head--on {
    border-top: 2px solid var(--ap-accent);
    border-radius: var(--ap-radius) var(--ap-radius) 0 0;
    padding-top: 1rem;
}

/* Whichever of tbody or tfoot ends the table closes the highlighted column. */
.price-matrix__table > *:last-child > tr:last-child .price-matrix__cell--on {
    border-radius: 0 0 var(--ap-radius) var(--ap-radius);
}

.price-matrix__mark {
    font-size: 1.1rem;
    line-height: 1;
}

.price-matrix__mark--yes {
    color: var(--ap-accent-text);
}

.price-matrix__mark--no {
    color: var(--ap-muted);
}

.price-matrix-card {
    padding: 1.75rem;
    border-radius: var(--ap-radius);
}

.price-matrix-card--on {
    border-color: var(--ap-accent);
    box-shadow: 0 0 0 1px var(--ap-accent);
}

.price-matrix-card__list {
    margin: 0 0 1.25rem;
}

.price-matrix-card__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.5rem 0;
    border-top: 1px solid var(--ap-border);
}

.price-matrix-card__row:first-child {
    border-top: 0;
}

.price-matrix-card__criterion {
    color: var(--ap-muted);
    font-weight: 400;
}

.price-matrix-card__value {
    margin: 0;
    font-weight: 500;
    text-align: right;
}

/* ---- Key figures ---- */
.stat__value {
    font-family: var(--ap-font-display);
    font-size: clamp(1.9rem, 4vw, 2.75rem);
    line-height: 1;
    color: var(--ap-text);
}

/* Every accent token is measured against --ap-bg, never against --ap-text, so
   nothing accent-colored is guaranteed legible here (ADR-0005). Inverting the
   body pair is the only combination the palette promises on an ink fill. */
.stats-band {
    background-color: var(--ap-text);
    color: var(--ap-bg);
}

.stats-band .stat__value {
    color: inherit;
    font-size: clamp(2.1rem, 5vw, 3.25rem);
}

.stats-band .label {
    color: color-mix(in srgb, var(--ap-bg) 62%, var(--ap-text));
}

.stats-band__caption {
    max-width: 22ch;
    margin-inline: auto;
    font-size: 0.9rem;
    line-height: 1.5;
    color: color-mix(in srgb, var(--ap-bg) 78%, var(--ap-text));
}

/* ---- Logo band ---- */
.logo-strip {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2.5rem;
}

.logo-strip__item img {
    max-height: 88px;
    max-width: 300px;
    width: auto;
    filter: grayscale(1);
    opacity: 0.7;
    transition: filter 0.15s ease, opacity 0.15s ease;
}

.logo-strip__item img:hover {
    filter: none;
    opacity: 1;
}

/* An unfilled slot keeps its place in the strip: .image-frame has no
   intrinsic height and the flex item shrinks to fit, so the box is sized
   here to match the 88px logo cap. */
.logo-strip__item--empty {
    width: 160px;
}

.logo-strip__item--empty .image-frame {
    height: 88px;
    background-size: 36px 36px, auto;
}

/* ---- Testimonials ---- */
/* One surface per avis: the quote first because it is the content, the face a
   fixed round crop at the foot of it rather than a column that stretches to
   whatever the quote's height turns out to be. */
.testimonial-card {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    margin: 0;
    padding: 1.75rem;
    background: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

.testimonial-card__quote {
    flex: 1;
    margin: 0;
    font-family: var(--ap-font-display);
    font-size: 1.15rem;
    line-height: 1.45;
    text-wrap: pretty;
}

.testimonial-card__who {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--ap-border);
}

.testimonial-card__portrait {
    flex: none;
    width: 3.25rem;
}

/* The alternating rows carry the testimony at display scale: the photograph is
   support, so the quote is the loudest thing in the row. */
.testimonial-lead {
    margin: 0;
    font-family: var(--ap-font-display);
    font-size: clamp(1.25rem, 2.2vw, 1.75rem);
    line-height: 1.35;
    text-wrap: pretty;
}

.testimonial-row + .testimonial-row {
    margin-top: 3rem;
}

/* ---- Rich text ---- */
.rich-text > :last-child {
    margin-bottom: 0;
}

.rich-text blockquote {
    margin: 1.5rem 0;
    padding-left: 1.25rem;
    border-left: 2px solid var(--ap-accent);
    font-family: var(--ap-font-display);
}

/* The title column is short and the text column is long, so the rule reads as
   the seam between them rather than as a box around either. */
@media (min-width: 992px) {
    .rich-text__title {
        position: sticky;
        top: 5rem;
    }
}

/* ---- Stats ---- */
/* A rule between the cells, so a short last label reads as the end of a column
   rather than as a hole on the right of the section. The padding is what keeps
   the figures off it: the first cell keeps its edge, aligned with the heading. */
@media (min-width: 768px) {
    .stat-row__cell + .stat-row__cell {
        border-left: 1px solid var(--ap-border);
        padding-left: 2rem;
    }

    .stat-row__cell:not(:last-child) {
        padding-right: 2rem;
    }
}

/* ---- Features: the numbered variants ---- */
/* The rank is the list's own index in all three, which is why no variant of this
   block stores a number the order could contradict. */
.step-rank {
    font-family: var(--ap-font-display);
    font-size: 2.4rem;
    font-weight: 600;
    line-height: 0.9;
    color: var(--ap-muted);
}

.step-title {
    margin: 0;
    font-family: var(--ap-font-display);
    font-size: 1.1rem;
    font-weight: 600;
}

.step-body {
    margin: 0;
    color: var(--ap-muted);
}

/* One rule per row and one under the last, so the run reads as a sequence rather
   than as three boxes. */
.step-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border-bottom: 1px solid var(--ap-border);
}

.step-list__row {
    display: grid;
    gap: 0.5rem;
    padding: 1.6rem 0;
    border-top: 1px solid var(--ap-border);
}

@media (min-width: 768px) {
    .step-list__row {
        grid-template-columns: 6rem 1fr 1fr;
        gap: 2rem;
        align-items: start;
        padding: 1.9rem 0;
    }
}

/* The same rule, turned: it caps each column instead of separating two rows. */
.step-columns {
    list-style: none;
    margin-bottom: 0;
    padding-left: 0;
}

.step-columns__cell {
    border-top: 1px solid var(--ap-border);
    padding-top: 1.25rem;
}

/* A filet from one rank to the next, stopped after the last: a chronology reads
   as continuous, and a line running past the end would promise a step that is
   not there. */
.step-rail {
    list-style: none;
    margin: 0;
    padding: 0;
}

.step-rail__row {
    position: relative;
    display: grid;
    grid-template-columns: 3rem 1fr;
    gap: 1.5rem;
    padding-bottom: 2.5rem;
}

.step-rail__row:last-child {
    padding-bottom: 0;
}

.step-rail__row::before {
    content: '';
    position: absolute;
    top: 3.4rem;
    bottom: 0;
    left: calc(1.5rem - 0.5px);
    width: 1px;
    background: var(--ap-border);
}

.step-rail__row:last-child::before {
    content: none;
}

.step-rail__rank {
    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    border: 1px solid var(--ap-border);
    border-radius: 50%;
    background: var(--ap-bg);
    font-family: var(--ap-font-mono);
    font-size: 0.8rem;
}

.step-rail__text {
    padding-top: 0.4rem;
}

/* ---- Divider ---- */
/* A block that says nothing, on purpose. The rule takes the theme's border tone
   and never the accent, which is kept for the things that do mean something, and
   the size is the air the rule sits in rather than the block's own margins. */
.divider-rule {
    display: flex;
    align-items: center;
    gap: 1.5rem;
}

.divider-rule--small {
    padding: 2.5rem 0;
}

.divider-rule--medium {
    padding: 4rem 0;
}

.divider-rule--large {
    padding: 6rem 0;
}

/* The accent variant stacks its mark over its word, so the two centre on each
   other instead of sitting on one baseline. */
.divider-rule--centered {
    flex-direction: column;
    justify-content: center;
    gap: 0.9rem;
}

.divider-rule__mark {
    width: 3.5rem;
}

.divider-rule--editing {
    outline: 1px dashed var(--ap-border);
}

/* ---- Video ---- */
/* Click-to-load, the same contract as the map: the poster and the button are all
   a page view loads. The 16:9 lives on the frame rather than on a shared crop,
   because what fills it is an iframe as often as an image. */
.video-stacked__body {
    max-width: 60ch;
}

.video-canvas {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    background: var(--ap-panel);
}

.video-canvas .site-image,
.video-canvas .image-frame,
.video-canvas iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    object-fit: cover;
}

.video-canvas__play {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    color: var(--ap-text);
    text-decoration: none;
}

/* White on a scrim rather than a token: an accent has no contrast guarantee over
   a photograph, and this one is over whichever photograph the tenant picked. */
.video-canvas--poster .video-canvas__play {
    color: #fff;
    background: linear-gradient(180deg, rgba(10, 10, 12, 0.2), rgba(10, 10, 12, 0.55));
}

.video-canvas__button {
    display: grid;
    place-items: center;
    width: 4.5rem;
    height: 4.5rem;
    border: 1px solid currentcolor;
    border-radius: 50%;
}

.video-canvas__play:hover .video-canvas__button {
    background: rgba(var(--ap-accent-rgb), 0.16);
}

.video-canvas__cue {
    font-family: var(--ap-font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

/* The bleed variant is one frame per page, so it takes a cinema crop and carries
   its title inside. The text takes no pointer events: the whole frame is the
   play control, and a title on top of it must not swallow the click. */
.video-canvas--bleed {
    aspect-ratio: 21 / 9;
}

.video-bleed__text {
    position: absolute;
    right: 1.5rem;
    bottom: 1.5rem;
    left: 1.5rem;
    color: #fff;
    pointer-events: none;
}

@media (max-width: 767.98px) {
    .video-canvas--bleed {
        aspect-ratio: 4 / 3;
    }
}

/* ---- Badge band ---- */
/* A band of guarantees, not a row of fields: no box around each item, one rule
   over and under the band, and a hairline between the cells, so the items read
   as one line rather than as a row of labelled inputs. */
.badge-strip {
    list-style: none;
    display: grid;
    gap: 1.25rem;
    margin: 0;
    padding: 1.75rem 0;
    border-top: 1px solid var(--ap-border);
    border-bottom: 1px solid var(--ap-border);
}

@media (min-width: 768px) {
    .badge-strip {
        grid-auto-flow: column;
        grid-auto-columns: 1fr;
        gap: 0;
    }

    .badge-strip__item {
        padding-inline: 2rem;
    }

    .badge-strip__item:first-child {
        padding-inline-start: 0;
    }

    .badge-strip__item:last-child {
        padding-inline-end: 0;
    }

    .badge-strip__item + .badge-strip__item {
        border-left: 1px solid var(--ap-border);
    }
}

.badge-strip__item {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
}

.badge-strip__icon {
    flex: none;
    margin-top: 0.2rem;
    font-size: 1.05rem;
    color: var(--ap-accent);
}

.badge-strip__body {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}

.badge-strip__title {
    font-family: var(--ap-font-display);
    font-size: 1rem;
    font-weight: 700;
    color: var(--ap-text);
}

.badge-strip__detail {
    font-size: 0.85rem;
    line-height: 1.45;
    color: var(--ap-muted);
}

/* ---- Hours, map and contact ---- */
/* The hours status is a state, so it keeps the pill. The contact note is a
   sentence about the channels under it, and a bordered pill above a list of
   links reads as the first button in the section. */
.contact-note {
    display: block;
    margin-bottom: 1.5rem;
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ap-muted);
}

.hours-status {
    display: inline-block;
    margin-bottom: 1.5rem;
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--ap-accent);
    border-radius: var(--ap-radius);
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ap-accent-text);
}

.hours-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.hours-row {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.8rem 0;
    border-bottom: 1px solid var(--ap-border);
}

.hours-row__day {
    font-family: var(--ap-font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

.hours-row--closed .hours-row__day,
.hours-row--closed .hours-row__value {
    color: var(--ap-faint);
}

/* The dotted run that ties a label to its value. Baseline alignment keeps it on
   the first line, so a value carrying a unit under it does not drag it down.
   Margins stay at zero: the synthesized baseline is then the dotted edge. */
.dot-leader {
    flex: 1;
    align-self: baseline;
    border-bottom: 1px dotted var(--ap-border);
}

/* Shared click-to-load map surface: hours/panel and every map variant draw the
   same frame, so the RGPD-sensitive placeholder has one implementation. */
.map-canvas {
    display: block;
    height: 16rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    background-image:
        repeating-linear-gradient(0deg, var(--ap-border) 0, var(--ap-border) 1px, transparent 1px, transparent 2.75rem),
        repeating-linear-gradient(90deg, var(--ap-border) 0, var(--ap-border) 1px, transparent 1px, transparent 2.75rem);
}

.map-canvas--flat {
    height: 7rem;
}

.map-canvas--tall {
    height: 20rem;
}

.map-canvas--bleed {
    height: 20rem;
}

.map-canvas--embed {
    position: relative;
    overflow: hidden;
}

.map-canvas--embed.is-loaded {
    background-image: none;
}

.map-canvas__load {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    height: 100%;
    padding: 1rem;
    border: 0;
    background: transparent;
    color: var(--ap-text);
    text-align: center;
}

.map-canvas__load:hover {
    background: rgba(var(--ap-accent-rgb), 0.08);
}

.map-canvas__cue {
    font-family: var(--ap-font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.map-canvas__notice {
    max-width: 24rem;
    color: var(--ap-muted);
    font-size: 0.75rem;
}

.map-canvas--embed iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

.map-access {
    display: grid;
    gap: 1.25rem;
}

.map-access__item dt {
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.map-access__item dd {
    margin: 0.35rem 0 0;
    color: var(--ap-muted);
}

.map-bleed__panel {
    margin-top: -2rem;
    margin-right: 1rem;
    margin-left: 1rem;
}

@media (min-width: 768px) {
    .map-access {
        grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    }
}

@media (min-width: 992px) {
    .map-canvas--tall {
        height: 100%;
        min-height: 24rem;
    }

    .map-canvas--bleed {
        height: 32rem;
    }

    .map-bleed {
        position: relative;
    }

    .map-bleed__panel {
        position: absolute;
        bottom: 2rem;
        left: 2rem;
        max-width: 26rem;
        margin: 0;
    }
}

.hours-address,
.map-address {
    font-weight: 600;
}

.hours-contact {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    margin-top: 1rem;
    font-family: var(--ap-font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.contact-channels {
    list-style: none;
    margin: 0;
    padding: 0;
}

.contact-channel {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    height: 100%;
    padding: 1.25rem;
    background: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    color: var(--ap-text);
    text-decoration: none;
    transition: border-color 0.2s ease, transform 0.2s ease;
}

.contact-channel:hover,
.contact-channel:focus-visible {
    border-color: var(--ap-accent);
    color: var(--ap-text);
    transform: translateY(-2px);
}

.contact-channel__kind {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.contact-channel__label {
    font-family: var(--ap-font-display);
    font-size: 1.15rem;
    line-height: 1.3;
}

.contact-channel__detail {
    font-size: 0.9rem;
    color: var(--ap-muted);
}

/* The bar leaves the flow only on phones, where it is the block's whole point. */
.contact-bar {
    list-style: none;
    display: flex;
    gap: 0.75rem;
    margin: 0;
    padding: 0;
}

/* min-width:0 beats the flex-item default that would stop the label ellipsizing. */
.contact-bar__item {
    flex: 1 1 0;
    min-width: 0;
}

.contact-bar__link {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.85rem 1rem;
    background: var(--ap-accent);
    border: 1px solid var(--ap-accent);
    border-radius: var(--ap-radius);
    color: var(--ap-accent-contrast);
    font-family: var(--ap-font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-decoration: none;
    min-width: 0;
}

.contact-bar__kind {
    display: none;
}

.contact-bar__link:hover,
.contact-bar__link:focus-visible {
    background: var(--ap-accent-strong);
    border-color: var(--ap-accent-strong);
    color: var(--ap-accent-contrast);
}

.contact-bar__item:nth-child(n + 2) .contact-bar__link {
    background: var(--ap-panel);
    border-color: var(--ap-border);
    color: var(--ap-text);
}

.contact-bar__item:nth-child(n + 2) .contact-bar__link:hover,
.contact-bar__item:nth-child(n + 2) .contact-bar__link:focus-visible {
    border-color: var(--ap-accent);
    color: var(--ap-text);
}

@media (max-width: 767.98px) {
    .contact-bar {
        position: fixed;
        z-index: 1030;
        right: 0;
        bottom: 0;
        left: 0;
        gap: 0.5rem;
        padding: 0.6rem 0.75rem calc(0.6rem + env(safe-area-inset-bottom));
        background: var(--ap-bg);
        border-top: 1px solid var(--ap-border);
    }

    .contact-bar__link {
        gap: 0.4rem;
        padding: 0.85rem 0.5rem;
        font-size: 0.7rem;
        letter-spacing: 0.06em;
    }

    /* The number is unreadable at a third of a phone width, so the canal names it. */
    .contact-bar__label {
        display: none;
    }

    .contact-bar__kind {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
}

/* ---- Opening status ---- */
.opening-now {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.opening-now__eyebrow {
    font-family: var(--ap-font-mono);
    font-size: 0.65rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ap-muted);
}

/* The visitor came for this line, so it outweighs the sentence naming the next
   change rather than sitting under it as a caption. */
.opening-state {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    gap: 0.6rem;
    margin: 0;
    padding: 0.6rem 1.1rem;
    border: 1px solid var(--ap-border);
    border-radius: 100px;
    font-family: var(--ap-font-mono);
    font-size: 0.85rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ap-text);
}

.opening-state__dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: currentColor;
}

/* An accent fill is only guaranteed to read against --ap-accent-contrast (ADR-0005). */
.opening-state--open {
    border-color: var(--ap-accent);
    background: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

.opening-state--closing-soon {
    border-color: var(--ap-accent);
    color: var(--ap-accent-text);
}

.opening-now__headline {
    margin: 0;
    font-family: var(--ap-font-display);
    font-size: clamp(1.35rem, 4vw, 1.75rem);
    line-height: 1.15;
}

.opening-closure {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin: 0;
    padding: 0.9rem 1rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    background: var(--ap-panel);
    font-size: 0.9rem;
}

.opening-closure__eyebrow {
    font-family: var(--ap-font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ap-muted);
}

.opening-week {
    list-style: none;
    margin: 0;
    padding: 0;
}

.opening-week__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--ap-border);
}

.opening-week__row:last-child {
    border-bottom: 0;
}

.opening-week__day {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.opening-week__hours {
    font-family: var(--ap-font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    text-align: right;
}

.opening-week__row--closed .opening-week__day,
.opening-week__row--closed .opening-week__hours {
    color: var(--ap-faint);
}

/* The swapped body pair is the only combination an inverted surface promises. */
.opening-week__row--today {
    margin: 0 -0.9rem;
    padding: 0.75rem 0.9rem;
    border-bottom-color: transparent;
    border-radius: var(--ap-radius);
    background: var(--ap-text);
    color: var(--ap-bg);
    font-weight: 600;
}

.opening-week__row--today.opening-week__row--closed .opening-week__day,
.opening-week__row--today.opening-week__row--closed .opening-week__hours {
    color: color-mix(in srgb, var(--ap-bg) 78%, var(--ap-text));
}

.opening-week__marker {
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: currentColor;
}

/* ---- Services and price cards ---- */
.price-list__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--ap-border);
    margin-bottom: 0.5rem;
}

.price-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.price-list__item {
    padding: 1rem 0;
    border-bottom: 1px solid var(--ap-border);
}

.price-list__line {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
}

.price-list__name {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    font-weight: 600;
}

.price-list__badge {
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    font-family: var(--ap-font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ap-accent-text);
}

.price-list__price {
    flex: none;
    text-align: right;
    font-family: var(--ap-font-mono);
    color: var(--ap-accent-text);
}

.price-list__description {
    max-width: 48ch;
    margin-top: 0.25rem;
    font-size: 0.925rem;
}

.price-list__facts {
    margin: 1.5rem 0;
    padding-top: 1.25rem;
    border-top: 1px solid var(--ap-border);
}

.price-list__fact {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.4rem 0;
}

.price-card {
    background: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    overflow: hidden;
}

.price-card__media {
    position: relative;
}

.price-card__media .image-frame,
.price-card__media .site-image {
    border-width: 0 0 1px;
}

.price-card__flag {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    padding: 0.2rem 0.55rem;
    background: var(--ap-accent);
    color: var(--ap-accent-contrast);
    font-family: var(--ap-font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.price-card__body {
    padding: 1.1rem 1.25rem 1.25rem;
}

.price-card__price {
    padding-top: 0.75rem;
    border-top: 1px solid var(--ap-border);
    font-family: var(--ap-font-mono);
    color: var(--ap-accent-text);
}

/* ---- Team ---- */
.team-member__bio {
    max-width: 28ch;
    margin-inline: auto;
}

/* The name sits under a rectangular crop rather than beside a round one, so it
   reads as a caption for the picture above it. */
.team-card__name {
    font-family: var(--ap-font-display);
    font-size: 1.05rem;
    font-weight: 600;
}

/* One line per person: the portrait is a thumbnail beside the text rather than
   the head of a card, so a page « à propos » holds a whole team without a grid. */
.team-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.team-list__row {
    display: grid;
    grid-template-columns: 5rem 1fr;
    gap: 1.5rem;
    padding: 1.6rem 0;
    border-top: 1px solid var(--ap-border);
}

.team-list__row:last-child {
    border-bottom: 1px solid var(--ap-border);
}

@media (min-width: 768px) {
    .team-list__row {
        grid-template-columns: 7rem 1fr;
        gap: 2rem;
    }
}

.team-member__portrait {
    width: min(11rem, 60%);
    margin: 0 auto 1.25rem;
}

.frame-round,
.team-member__portrait .site-image {
    aspect-ratio: 1 / 1;
    min-height: 0;
    border-radius: 50%;
}

/* ---- Showcase ---- */
/* Stacked réalisations read as one list, so each carries its own bottom rule. */
.showcase-item {
    padding-bottom: 2.5rem;
    border-bottom: 1px solid var(--ap-border);
}

/* The crop follows the column the frame lands in, so the plank keeps its weight
   whatever the count: the span is what changes, in the template. */
.showcase-frames .image-frame,
.showcase-frames .site-image {
    aspect-ratio: 4 / 3;
    height: auto;
    min-height: 0;
    object-fit: cover;
}

/* A third of the plank is a narrow column, so the triptych stands its frames up:
   square on its own line, and at lg, where the story sits beside them, as tall as
   the story itself. The floor and the cap are in cqw so they follow the frame's
   own width: never flatter than square, never more upright than 4:5. */
@media (min-width: 768px) {
    .showcase-frames--alone {
        justify-content: flex-end;
    }

    .showcase-frames--triptych .image-frame,
    .showcase-frames--triptych .site-image {
        aspect-ratio: 1 / 1;
    }
}

@media (min-width: 992px) {
    .blk--reversed .showcase-frames--alone {
        justify-content: flex-start;
    }

    .showcase-frames--triptych {
        height: 100%;
    }

    .showcase-frames--triptych > * {
        container-type: inline-size;
    }

    .showcase-frames--triptych .image-frame,
    .showcase-frames--triptych .site-image {
        aspect-ratio: auto;
        height: 100%;
        min-height: 100cqw;
        max-height: 125cqw;
    }
}

/* The paragraph sits beside a portrait, so it keeps a reading measure of its
   own: half a wide band is far past the line length prose is read at. */
.content-split__body {
    max-width: 52ch;
}

/* ---- Content stack ---- */
/* The image is contained, never cropped: capping it at the reading column is
   what lets this block promise three fields and no layout choice. */
.content-stack__media {
    margin-inline: auto;
    max-width: 560px;
}

/* The same cap as the centered variant, pulled to the left edge: switching
   variant changes the alignment, never the size of the picture. */
.content-stack--left .content-stack__media {
    margin-inline: 0;
}

.content-stack--medallion .content-stack__media {
    max-width: 11rem;
}

/* An empty slot has no image to give it a height; the medallion takes its own
   from .frame-round, so it must not inherit this one. */
.content-stack__media .image-frame {
    min-height: 220px;
}

.content-stack--medallion .content-stack__media .image-frame {
    min-height: 0;
}

/* The holding page shown for a tenant whose site is not public yet. */
.holding-page {
    max-width: 640px;
    margin: 0 auto;
    padding-top: 10vh;
}

/* ---- Announcement ---- */
/* The band bleeds full width and the note stays in the measure, so only the
   framing differs: the row inside is one shared partial. */
.announce__row {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem 1.25rem;
}

.announce__tag {
    flex: none;
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.announce__message {
    flex: 1 1 auto;
    margin: 0;
    text-wrap: pretty;
}

.announce__cta {
    flex: none;
    font-family: var(--ap-font-mono);
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-decoration: underline;
    text-underline-offset: 0.25em;
}

/* Editor-only: an expired annonce keeps its place in the builder so the tenant
   can see why it left the site. */
.announce__expired {
    flex: none;
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    opacity: 0.75;
}

.announce--band.announce--accent {
    background-color: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

.announce--band.announce--neutral {
    background-color: var(--ap-text);
    color: var(--ap-bg);
}

.announce--band .announce__cta {
    color: inherit;
}

.announce--note {
    padding: 1.25rem;
    background-color: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

.announce--note.announce--accent {
    border-left: 3px solid var(--ap-accent);
}

.announce--note .announce__tag {
    color: var(--ap-faint);
}

.announce--note .announce__cta {
    color: var(--ap-accent-text);
}

@media (min-width: 768px) {
    .announce__row {
        flex-direction: row;
        align-items: baseline;
    }
}

/* ---- Before / after ---- */
/* Both frames stay on one row at every width: a comparison read one image at a
   time is not a comparison. A fixed ratio on both sides is what makes the two
   photos comparable when their crops differ. */
/* minmax(0, …) rather than 1fr: a grid track sized to a replaced element takes
   its minimum from the file's intrinsic width, so a 2000px photo would push the
   row past the viewport. */
.before-after__pair {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0.5rem;
}

.before-after__side {
    position: relative;
    margin: 0;
}

.before-after__side .image-frame,
.before-after__side .site-image {
    aspect-ratio: 4 / 3;
    min-height: 0;
}

.before-after__tag {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    padding: 0.2rem 0.55rem;
    background: var(--ap-panel);
    color: var(--ap-text);
    border: 1px solid var(--ap-border);
    font-family: var(--ap-font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.before-after__tag--after {
    background: var(--ap-accent);
    border-color: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

.before-after__case + .before-after__case {
    margin-top: 2.5rem;
    padding-top: 2.5rem;
    border-top: 1px solid var(--ap-border);
}

.before-after__facts {
    display: grid;
    gap: 1rem;
    margin-top: 2rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--ap-border);
}

.before-after__slider {
    --ba-split: 50%;
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
}

@media (max-width: 575.98px) {
    .before-after__slider {
        aspect-ratio: 4 / 3;
    }
}

.before-after__slider .image-frame,
.before-after__slider .site-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

.before-after__reveal {
    position: absolute;
    inset: 0;
    clip-path: inset(0 calc(100% - var(--ba-split)) 0 0);
}

.before-after__slider .before-after__tag--after {
    left: auto;
    right: 0.6rem;
}

.before-after__handle,
.before-after__range {
    display: none;
}

.before-after__slider.is-interactive .before-after__handle {
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--ba-split);
    width: 2px;
    margin-left: -1px;
    background: var(--ap-bg);
}

.before-after__knob {
    position: absolute;
    top: 50%;
    left: 50%;
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    transform: translate(-50%, -50%);
    border: 2px solid var(--ap-bg);
    border-radius: 50%;
    background: var(--ap-accent);
    color: var(--ap-accent-contrast);
    font-family: var(--ap-font-mono);
}

.before-after__slider.is-interactive .before-after__range {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    appearance: none;
    background: transparent;
    opacity: 0;
    cursor: ew-resize;
}

/* The range itself is transparent, so the ring goes on the knob. Light border
   then dark outline, because neither token is guaranteed against a photograph. */
.before-after__slider:has(.before-after__range:focus-visible) .before-after__knob {
    outline: 2px solid var(--ap-text);
    outline-offset: 2px;
}

/* Arrows would need framework JS on a public page (ADR-0007), so the scroll is
   the control and a peeking next card is the affordance. */
.before-after__series {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 88%;
    gap: 1.5rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    padding-bottom: 1rem;

    /* A drawn track, because the platform hides the overlay one until a scroll
       starts: the peeking card says there is more, this says how much more. */
    scrollbar-width: thin;
    scrollbar-color: var(--ap-border) transparent;
}

.before-after__card {
    min-width: 0;
    scroll-snap-align: start;
}

@media (min-width: 576px) {
    .before-after__facts {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (min-width: 768px) {
    .before-after__series {
        grid-auto-columns: 58%;
    }
}

@media (min-width: 992px) {
    .before-after__series {
        grid-auto-columns: 42%;
    }
}

/* The prestations are what the block is selling, so they read as a list on a
   surface rather than as loose rows under a centered title. */
.resa-services {
    list-style: none;
    margin: 0;
    padding: 0.5rem 1.25rem;
    background: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

.resa-services__row {
    display: flex;
    justify-content: space-between;
    gap: 1.5rem;
    padding: 0.85rem 0;
}

.resa-services__row + .resa-services__row {
    border-top: 1px solid var(--ap-border);
}

.resa-services__name {
    font-weight: 500;
}

.resa-services__meta {
    color: var(--ap-muted);
    white-space: nowrap;
}

/* ---- Booking: the swapped panel ---- */
/* Swapped in place by app/booking.js, so the dim is the only sign a request is in flight.
   The attribute never appears with the script off, and focus lands here only
   programmatically: the panel is tabindex="-1", never in the tab order. */
.resa-availability {
    transition: opacity 0.15s ease;
}

.resa-availability[aria-busy='true'] {
    opacity: 0.55;
}

.resa-availability:focus {
    outline: none;
}

.booking-form .form-label {
    font-weight: 600;
    color: var(--ap-text);
}

/* ---- Module pages: the shared header ---- */
/* Back link, title, standfirst and meta, then a neutral rule: one skeleton for a form's
   page and for the booking ones, painted with the existing tokens. */
.module-head {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
    padding-bottom: 1.75rem;
    margin-bottom: 2.5rem;
    border-bottom: 1px solid var(--ap-border);
}

.module-head__back {
    font-family: var(--ap-font-mono);
    font-size: 0.72rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ap-muted);
}

.module-head__title {
    margin: 0;
}

.module-head__intro {
    max-width: 58ch;
    margin: 0;
    color: var(--ap-muted);
    text-wrap: pretty;
}

.module-head__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0.25rem 0 0;
}

.module-head__tag {
    padding: 0.4rem 0.75rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius-button);
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.module-body {
    max-width: 45rem;
    margin-inline: auto;
}

/* ---- Booking: the step thread ---- */
/* The list carries the meaning; under 768 px it leaves the flow and gives way to the rank
   plus four rules, four labels not fitting on one phone line. */
.resa-steps {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 0;
    margin-bottom: 2.25rem;
    border-block: 1px solid var(--ap-border);
}

.resa-steps__list {
    display: flex;
    flex: 1;
    align-items: center;
    gap: 0.9rem;
    padding: 0;
    margin: 0;
    list-style: none;
}

.resa-steps__step {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    color: var(--ap-faint);
}

.resa-steps__step + .resa-steps__step {
    flex: 1;
}

/* The rule joining two steps, laid before the one that follows. */
.resa-steps__step + .resa-steps__step::before {
    content: '';
    flex: 1;
    min-width: 1.5rem;
    height: 1px;
    background: var(--ap-border);
}

.resa-steps__step > a,
.resa-steps__step > span {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}

.resa-steps__num {
    display: grid;
    place-items: center;
    width: 1.7rem;
    height: 1.7rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius-button);
    font-family: var(--ap-font-mono);
    font-size: 0.75rem;
}

.resa-steps__label {
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

.resa-steps__step--done {
    color: var(--ap-muted);
}

.resa-steps__step--done .resa-steps__num {
    border-color: var(--ap-accent);
    color: var(--ap-accent-text);
}

.resa-steps__step--current {
    color: var(--ap-text);
}

.resa-steps__step--current .resa-steps__num {
    background: var(--ap-accent);
    border-color: var(--ap-accent);
    color: var(--ap-accent-contrast);
    font-weight: 600;
}

.resa-steps__step--current .resa-steps__label {
    font-weight: 700;
}

.resa-steps__rank,
.resa-steps__bars {
    display: none;
}

@media (max-width: 767.98px) {
    .resa-steps__list {
        display: none;
    }

    .resa-steps__rank {
        display: block;
        margin: 0;
        font-family: var(--ap-font-mono);
        font-size: 0.7rem;
        letter-spacing: 0.14em;
        text-transform: uppercase;
    }

    .resa-steps__bars {
        display: flex;
        gap: 0.3rem;
        margin-left: auto;
    }

    .resa-steps__bars > span {
        width: 1.25rem;
        height: 3px;
        background: var(--ap-border);
    }

    .resa-steps__bars > .is-done {
        background: var(--ap-accent);
    }
}

/* ---- Booking: the service list ---- */
/* A list, not a card grid: it holds at one service as at twenty, and lines the duration
   and the price up in a column. */
.resa-group + .resa-group {
    margin-top: 2.75rem;
}

.resa-group__label {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin: 0 0 0.5rem;
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.resa-group__label::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--ap-border);
}

.resa-offer {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 2rem;
    padding: 1.5rem 1.25rem;
    border-bottom: 1px solid var(--ap-border);
    color: inherit;
    text-decoration: none;
}

.resa-offer:hover,
.resa-offer:focus-visible {
    background: var(--ap-panel);
    text-decoration: none;
}

.resa-offer__main {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    min-width: 0;
}

.resa-offer__name {
    margin: 0;
    font-size: 1.15rem;
    font-weight: 700;
}

.resa-offer__desc {
    margin: 0;
    color: var(--ap-muted);
    text-wrap: pretty;
}

.resa-offer__dates {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0.25rem 0 0;
}

.resa-offer__date {
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius-button);
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    color: var(--ap-accent-text);
}

.resa-offer__date--full,
.resa-offer__date--more {
    color: var(--ap-faint);
}

.resa-offer__meta {
    margin: 0;
    font-family: var(--ap-font-mono);
    font-size: 0.82rem;
    color: var(--ap-faint);
    white-space: nowrap;
}

.resa-offer__cta {
    padding: 0.7rem 1.1rem;
    border: 1px solid var(--ap-accent);
    border-radius: var(--ap-radius-button);
    font-family: var(--ap-font-mono);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ap-accent-text);
    white-space: nowrap;
}

.resa-offer:hover .resa-offer__cta,
.resa-offer:focus-visible .resa-offer__cta {
    background: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

@media (max-width: 767.98px) {
    .resa-offer {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.85rem;
    }

    .resa-offer__cta {
        justify-self: start;
    }
}

/* ---- Booking: the day rail ---- */
/* The rail replaces the paginated week: every day carries its slot count, closed or full
   days stay visible, and the chosen day's hours follow underneath. Seven columns while
   they fit, a sideways scroll below that: a column narrower than its own number is
   unreadable (ADR-0007, zero JS). */
.resa-board__calendar {
    min-width: 0;
}

.resa-board--split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(16rem, 21rem);
    gap: 2.5rem;
    align-items: start;
}

@media (max-width: 991.98px) {
    .resa-board--split {
        grid-template-columns: minmax(0, 1fr);
    }
}

.resa-days__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1rem;
}

.resa-days__month {
    font-family: var(--ap-font-display);
    font-size: 1.05rem;
    font-weight: 700;
}

.resa-days__arrows {
    display: flex;
    gap: 0.5rem;
    margin-left: auto;
}

.resa-days {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(5.5rem, 1fr);
    gap: 0.6rem;
    padding-bottom: 0.35rem;
    overflow-x: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--ap-border) transparent;
}

.resa-day {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    height: 100%;
    padding: 0.8rem 0.4rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    background: var(--ap-panel);
    color: inherit;
    text-decoration: none;
}

a.resa-day:hover,
a.resa-day:focus-visible {
    border-color: var(--ap-accent);
    text-decoration: none;
}

.resa-day--current {
    background: var(--ap-accent);
    border-color: var(--ap-accent);
    color: var(--ap-accent-contrast);
}

.resa-day--off {
    background: transparent;
    opacity: 0.55;
}

.resa-day__weekday {
    font-family: var(--ap-font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.resa-day__number {
    font-family: var(--ap-font-display);
    font-size: 1.4rem;
    font-weight: 700;
    line-height: 1;
}

.resa-day__count {
    font-family: var(--ap-font-mono);
    font-size: 0.6rem;
    text-align: center;
    color: var(--ap-accent-text);
}

.resa-day--off .resa-day__count {
    color: var(--ap-faint);
}

.resa-day--current .resa-day__weekday,
.resa-day--current .resa-day__count {
    color: inherit;
}

.resa-slots__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 1rem;
    padding-top: 1.5rem;
    margin-top: 1.5rem;
    border-top: 1px solid var(--ap-border);
}

.resa-slots__day {
    font-family: var(--ap-font-display);
    font-size: 1.1rem;
    font-weight: 700;
}

.resa-slots__count {
    font-family: var(--ap-font-mono);
    font-size: 0.72rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.resa-slots {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-top: 1rem;
}

.resa-slot {
    min-width: 5.5rem;
    font-family: var(--ap-font-mono);
}

.resa-note {
    margin-top: 1.5rem;
}

/* Party size and resource are links: they change the URL the rail reloads, so they work
   with JavaScript off and can be swapped along with it. */
.resa-panel {
    display: flex;
    flex-direction: column;
    background: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

.resa-panel__title {
    padding: 1.1rem 1.35rem;
    margin: 0;
    border-bottom: 1px solid var(--ap-border);
    font-family: var(--ap-font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.resa-panel__group {
    padding: 1.15rem 1.35rem;
}

.resa-panel__group + .resa-panel__group {
    border-top: 1px solid var(--ap-border);
}

.resa-panel__label {
    margin: 0 0 0.6rem;
    font-size: 0.9rem;
    font-weight: 600;
}

.resa-choices {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.resa-choice {
    display: grid;
    place-items: center;
    flex: 1 1 2.5rem;
    min-height: 2.75rem;
    padding: 0 0.85rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius-button);
    font-family: var(--ap-font-mono);
    font-size: 0.85rem;
    color: inherit;
    text-decoration: none;
}

.resa-choices--wide .resa-choice {
    flex: 0 1 auto;
    font-family: var(--ap-font-body);
    font-size: 0.88rem;
}

a.resa-choice:hover,
a.resa-choice:focus-visible {
    border-color: var(--ap-accent);
    color: var(--ap-accent-text);
    text-decoration: none;
}

.resa-choice--on {
    background: var(--ap-accent);
    border-color: var(--ap-accent);
    color: var(--ap-accent-contrast);
    font-weight: 600;
}

.resa-choice--off {
    border-style: dashed;
    color: var(--ap-faint);
}

/* Session mode has no aside to hang the party size on, so the same panel goes above the
   list; full width would stretch ten choices into a ribbon, hence the reset flex. */
.resa-panel--bar {
    margin-bottom: 2rem;
}

.resa-panel--bar .resa-choice {
    flex: 0 1 auto;
    min-width: 2.75rem;
}

/* ---- Booking: the dated sessions ---- */
/* La jauge remplit les places qui restent : elle dit d'un coup d'oeil ce que la phrase
   « 2 places restantes » demande de lire. */
.resa-session {
    display: grid;
    grid-template-columns: 4.5rem minmax(0, 1fr) 12rem auto;
    align-items: center;
    gap: 1.75rem;
    padding: 1.35rem 0;
    border-bottom: 1px solid var(--ap-border);
}

.resa-session--full {
    opacity: 0.6;
}

.resa-session__date {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.1rem;
    padding: 0.6rem 0;
    background: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

.resa-session__day {
    font-family: var(--ap-font-display);
    font-size: 1.4rem;
    font-weight: 700;
    line-height: 1;
}

.resa-session__month {
    font-family: var(--ap-font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.resa-session__main {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}

.resa-session__title {
    font-size: 1.05rem;
    font-weight: 700;
}

.resa-session__hours {
    font-family: var(--ap-font-mono);
    font-size: 0.8rem;
    color: var(--ap-faint);
}

.resa-session__seats {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.resa-session__left {
    font-family: var(--ap-font-mono);
    font-size: 0.78rem;
    letter-spacing: 0.01em;
    color: var(--ap-text);
}

.resa-session__cta {
    white-space: nowrap;
}

.resa-gauge {
    height: 4px;
    background: var(--ap-border);
    border-radius: 4px;
    overflow: hidden;
}

.resa-gauge__fill {
    display: block;
    height: 100%;
    width: var(--resa-gauge, 0%);
    border-radius: inherit;
    background: var(--ap-accent);
}

@media (max-width: 767.98px) {
    .resa-session {
        grid-template-columns: 4.5rem minmax(0, 1fr);
        gap: 1rem 1.25rem;
    }

    .resa-session__seats,
    .resa-session__cta {
        grid-column: 2;
    }
}

/* ---- Booking: the recap and the ticket ---- */
/* One « Modifier » per row, in place of the « Changer de créneau » link lost at the top of the page. */
.resa-recap {
    background: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

.resa-recap__title {
    padding: 1.1rem 1.35rem;
    margin: 0;
    border-bottom: 1px solid var(--ap-border);
    font-family: var(--ap-font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ap-faint);
}

.resa-recap__row {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.1rem 1.35rem;
    border-bottom: 1px solid var(--ap-border);
}

.resa-recap__main {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}

.resa-recap__name {
    font-weight: 700;
}

.resa-recap__note {
    font-family: var(--ap-font-mono);
    font-size: 0.78rem;
    color: var(--ap-faint);
}

.resa-recap__edit {
    margin-left: auto;
    font-family: var(--ap-font-mono);
    font-size: 0.66rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ap-accent-text);
    white-space: nowrap;
}

.resa-recap__foot {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1.1rem 1.35rem;
}

.resa-recap__line,
.resa-recap__total {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
}

.resa-recap__line {
    font-size: 0.9rem;
    color: var(--ap-muted);
}

.resa-recap__total {
    padding-top: 0.75rem;
    border-top: 1px solid var(--ap-border);
    font-weight: 700;
}

.resa-ticket {
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

.resa-ticket__head {
    display: flex;
    align-items: center;
    gap: 1.15rem;
    padding: 1.15rem 1.35rem;
}

.resa-ticket__date {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    align-items: center;
    gap: 0.1rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--ap-accent);
    border-radius: var(--ap-radius);
}

.resa-ticket__day {
    font-family: var(--ap-font-display);
    font-size: 1.35rem;
    font-weight: 700;
    line-height: 1;
}

.resa-ticket__month {
    font-family: var(--ap-font-mono);
    font-size: 0.58rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ap-accent-text);
}

.resa-ticket__main {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}

.resa-ticket__name {
    font-weight: 700;
}

.resa-ticket__hours {
    font-family: var(--ap-font-mono);
    font-size: 0.78rem;
    color: var(--ap-faint);
}

.resa-ticket__foot {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.9rem 1.35rem;
    border-top: 1px solid var(--ap-border);
    font-size: 0.9rem;
    color: var(--ap-muted);
}

/* Six boxes, one real input each, so the code is posted as six values and the screen
   still holds with JavaScript off; app/booking_code.js only walks the caret. */
.resa-code {
    display: flex;
    flex: 1 1 auto;
    gap: 0.5rem;
    max-width: 20rem;
}

/* Six fixed boxes overflow the panel below 400px, so they share the row instead. */
.resa-code__box {
    flex: 1 1 0;
    min-width: 0;
    padding-inline: 0;
    font-family: var(--ap-font-mono);
    font-size: 1.35rem;
    text-align: center;
}

/* ---- Public flash banner ---- */
/* Pinned to the bottom of the viewport: in the flow it would sit between the navbar
   and the first block and break the .site-nav + .blk adjacency the full-screen hero
   is promoted by. The container takes no pointer events, only the alert does. */
.site-flash {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 1040;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 0 1rem 1.25rem;
    pointer-events: none;
}

.site-flash .alert {
    width: min(100%, 34rem);
    pointer-events: auto;
    box-shadow: 0 6px 24px rgb(0 0 0 / 18%);
}

/* ---- Newsletter: the page section ---- */
/* No Bootstrap .input-group: its radii come from a selector too specific to
   override without !important, and the field welded to the button falls to a
   few characters on a phone. The flex-basis wraps the button to the next line
   before the address becomes unreadable. */
.newsletter-blk__group {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
}

.newsletter-blk__group > .form-control {
    flex: 1 1 14rem;
    border-radius: var(--ap-radius);
}

.newsletter-blk__text {
    max-width: 46ch;
    color: var(--ap-muted);
}

/* Tied to the field above it rather than hung off the bottom of the section. */
.newsletter-blk__note {
    margin-top: 0.6rem;
    font-size: 0.8rem;
    color: var(--ap-faint);
}

/* Nothing on the ink surface keeps a token measured against --ap-bg. */
.newsletter-band .label,
.newsletter-band .newsletter-blk__text,
.newsletter-band .newsletter-blk__note {
    color: inherit;
    opacity: 0.78;
}

/* The quiet variant: an accent edge so the panel reads as a section rather
   than a grey box, and a measure so the field does not span it end to end. */
.newsletter-blk--stacked {
    border-top: 2px solid var(--ap-accent);
}

.newsletter-blk--stacked .newsletter-blk__text {
    margin-inline: auto;
}

.newsletter-blk__stack {
    width: min(30rem, 100%);
    margin-inline: auto;
}

.newsletter-blk--stacked .newsletter-blk__group {
    justify-content: center;
}

.newsletter-blk--stacked .newsletter-blk__note {
    text-align: center;
}

/* ---- Newsletter: the footer column ---- */
/* The field is welded to the button, so only the outer corners are shaped: Bootstrap
   already squares the joint, from a selector specific enough to hold against this one.
   They follow the button token, not --ap-radius, because the pair reads as one control. */
.newsletter .input-group > .form-control {
    border-start-start-radius: var(--ap-radius-button);
    border-end-start-radius: var(--ap-radius-button);
}

/* ---------- Products ---------- */

.products-head {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
    margin-bottom: 2rem;
}

.products-head__title {
    min-width: 0;
}

.products-head__cta {
    flex: none;
}

/* Arrows would need framework JS on a public page (ADR-0007), so the scroll is
   the control and the card cut by the right edge is the affordance: the rail
   bleeds past the container padding it sits in. */
.products-rail,
.rayons-rail {
    display: grid;
    grid-auto-flow: column;
    gap: 1.5rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    margin-right: -1.5rem;
    padding-right: 1.5rem;
    padding-bottom: 1rem;
}

.products-rail {
    grid-auto-columns: 62%;
}

/* A rayon card is the same object at every width, a square photo over one line
   of text, so its track is a constant: sized as a share of the viewport it
   shrank to a thumbnail on a phone. Only the peek stays proportional. */
.rayons-rail {
    grid-auto-columns: min(20rem, 85%);
}

.products-rail__card,
.rayons-rail__card {
    min-width: 0;
    scroll-snap-align: start;
}

.product-card__blurb {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* On the card's p-4 padding, so it overlaps the image corner. */
.product-card__badge {
    position: absolute;
    top: 2rem;
    left: 2rem;
    z-index: 1;
    padding: 0.2rem 0.65rem;
    border: 1px solid var(--ap-border);
    border-radius: 999px;
    background: var(--ap-panel);
    color: var(--ap-accent-text);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.shop-compare-at {
    color: var(--ap-faint);
    font-size: 0.75em;
    font-weight: 400;
}

.products-selection__cta {
    display: flex;
    justify-content: center;
    margin-top: 2.5rem;
    padding-top: 2rem;
    border-top: 1px solid var(--ap-border);
}

@media (min-width: 576px) {
    .products-rail {
        grid-auto-columns: 38%;
    }
}

@media (min-width: 768px) {
    .products-head {
        flex-direction: row;
        align-items: flex-end;
        justify-content: space-between;
        gap: 1.5rem;
    }

    .products-rail {
        grid-auto-columns: 28%;
    }
}

@media (min-width: 992px) {
    .products-rail,
    .rayons-rail {
        margin-right: -3rem;
        padding-right: 3rem;
    }

    .products-rail {
        grid-auto-columns: 22%;
    }
}

/* Legal page: anchors styled as tabs. :target picks the visible
   section, so the links deep-link, survive JavaScript being off and stay
   indexable. Without :has() support every section shows, which is the safe
   way to fail on a page whose purpose is being readable. */
.legal-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    border-bottom: 1px solid var(--ap-border);
    margin-bottom: var(--ap-space-block);
}

.legal-tabs a {
    padding: 0.75rem 0;
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    color: var(--ap-muted);
    text-decoration: none;
}

.legal-tabs a:hover,
.legal-tabs a:focus-visible {
    color: var(--ap-text);
}

.legal-section + .legal-section {
    margin-top: var(--ap-space-block);
}

.legal-block {
    color: var(--ap-muted);
}

.legal-table th[scope="row"] {
    white-space: nowrap;
}

@supports selector(:has(*)) {
    /* The fragment swaps the section in place, so it must not also scroll: a
       margin taller than the viewport puts the scroll target above the document
       top, where it clamps to zero. Only inside @supports, because the fallback
       stacks every section and there jumping to one is the right behavior. */
    .legal-section {
        scroll-margin-top: 100vh;
    }

    body:has(.legal-section:target) .legal-section {
        display: none;
    }

    body:has(.legal-section:target) .legal-section:target {
        display: block;
        margin-top: 0;
    }

    body:has(#mentions:target) .legal-tabs a[href="#mentions"],
    body:has(#donnees:target) .legal-tabs a[href="#donnees"],
    body:has(#cgv:target) .legal-tabs a[href="#cgv"],
    body:not(:has(.legal-section:target)) .legal-tabs a:first-child {
        color: var(--ap-text);
        border-bottom-color: var(--ap-accent);
    }

    body:not(:has(.legal-section:target)) .legal-section:not(:first-of-type) {
        display: none;
    }
}

/* ---------- Medallion hero ---------- */

/* The rule crosses the column and the medallion sits on it, so the image is
   held by the section instead of floating above the text. */
.hero-medallion {
    position: relative;
    display: flex;
    justify-content: center;
    align-self: stretch;
}

.hero-medallion::before {
    content: "";
    position: absolute;
    top: 50%;
    right: 0;
    left: 0;
    border-top: 1px solid var(--ap-border);
}

.hero-medallion__media {
    position: relative;
    width: min(420px, 78vw);
}

.hero-medallion__image {
    box-shadow: 0 1.25rem 2rem -0.75rem color-mix(in srgb, var(--ap-text) 30%, transparent);
}

/* ---------- Price bands ---------- */

/* The 1px gap on a border-colored grid draws the cell rules at any column count. */
.price-bands {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1px;
    overflow: hidden;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    background: var(--ap-border);
}

.price-bands__cell {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1.75rem 1.5rem;
    background: var(--ap-panel);
    color: var(--ap-text);
    text-decoration: none;
    transition: background-color 0.15s ease;
}

a.price-bands__cell:hover,
a.price-bands__cell:focus-visible {
    background: var(--ap-bg);
    color: var(--ap-text);
}

.price-bands__range {
    font-family: var(--ap-font-display);
    font-size: 1.9rem;
    line-height: 1;
    color: var(--ap-accent-text);
}

.price-bands__title {
    margin-top: 0.25rem;
    font-weight: 700;
}

.price-bands__body {
    font-size: 0.9rem;
}

.price-bands__count {
    margin-top: auto;
    padding-top: 0.75rem;
}

@media (min-width: 768px) {
    .price-bands {
        grid-template-columns: repeat(2, 1fr);
    }

    /* An odd last cell spans the row, so the grid never shows an empty slot. */
    .price-bands__cell:last-child:nth-child(odd) {
        grid-column: 1 / -1;
    }
}

@media (min-width: 992px) {
    .price-bands--3 {
        grid-template-columns: repeat(3, 1fr);
    }

    .price-bands--4 {
        grid-template-columns: repeat(4, 1fr);
    }

    .price-bands--3 .price-bands__cell:last-child:nth-child(odd),
    .price-bands--4 .price-bands__cell:last-child:nth-child(odd) {
        grid-column: auto;
    }
}

/* ---------- Glossary ---------- */

.glossary-term {
    display: grid;
    grid-template-columns: 6rem 1fr;
    gap: 1.25rem;
    align-items: start;
    padding: 1.35rem 1.5rem;
    background: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
}

/* Sized for the eight-character sigle the form allows; wrapping is the last resort. */
.glossary-term__code {
    font-family: var(--ap-font-display);
    font-size: 1.35rem;
    line-height: 1.1;
    color: var(--ap-accent-text);
    overflow-wrap: anywhere;
}

.glossary-point {
    padding-top: 1rem;
    border-top: 1px solid var(--ap-border);
}

/* ---------- Rayon chips ---------- */

/* Under 992px the chips have the whole width and no column to wrap into: eight
   rayons stack into four rows and push the framed rayon off the screen. One
   scrolling line instead, bleeding past the container padding like the rails. */
.rayon-chips {
    display: flex;
    gap: 0.6rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    margin-right: -1.5rem;
    padding-right: 1.5rem;
    padding-bottom: 0.75rem;
}

.rayon-chips > li {
    flex: none;
}

.rayon-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 1.1rem;
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius-button);
    background: var(--ap-panel);
    color: var(--ap-muted);
    font-size: 0.85rem;
    text-decoration: none;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.rayon-chip:hover,
.rayon-chip:focus-visible {
    border-color: var(--ap-accent);
    color: var(--ap-accent-text);
}

.rayon-chip.active {
    border-color: var(--ap-accent);
    background: var(--ap-accent);
    color: var(--ap-accent-contrast);
    font-weight: 600;
}

.rayon-chip__count {
    font-family: var(--ap-font-mono);
    font-size: 0.65rem;
    letter-spacing: 0.08em;
    opacity: 0.8;
}

.rayon-featured__facts {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem 1.75rem;
    margin: 1.4rem 0 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--ap-border);
}

.rayon-featured__value {
    display: block;
    margin-bottom: 0.35rem;
    font-family: var(--ap-font-display);
    font-size: 1.5rem;
    line-height: 1;
}

/* The chips have their own column beside the framed rayon: they wrap into it
   rather than hide a tab behind a scroll gesture. */
@media (min-width: 992px) {
    .rayon-chips {
        flex-wrap: wrap;
        overflow-x: visible;
        margin-right: 0;
        padding-right: 0;
        padding-bottom: 0;
    }
}

/* Address suggestions. The list is built by address_autocomplete.js
   and never exists without it, so no rule here has a no-JavaScript counterpart. */
.address-autocomplete {
    position: relative;
}

.address-suggestions {
    position: absolute;
    z-index: 3;
    inset-inline: 0;
    top: calc(100% + 0.25rem);
    max-height: 15rem;
    overflow-y: auto;
    margin: 0;
    padding: 0.25rem;
    list-style: none;
    background-color: var(--ap-panel);
    border: 1px solid var(--ap-border);
    border-radius: var(--ap-radius);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
}

.address-suggestion {
    padding: 0.5rem 0.75rem;
    border-radius: var(--ap-radius);
    cursor: pointer;
    color: var(--ap-text);
}

.address-suggestion:hover,
.address-suggestion.is-active {
    background-color: color-mix(in srgb, var(--ap-accent) 14%, transparent);
}

/* The address register's advisory notice: it warns, it never refuses,
   so it reads as a remark rather than as a field error. */
.address-notice {
    margin-top: 0.25rem;
    color: var(--ap-muted);
}

.address-notice::before {
    content: '⚠ ';
}
