/* =========================================================
   Static site template
   styles.css
   ========================================================= */

/* ---- Design tokens ---------------------------------------------------- */
:root {
    --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    /* Headings: bold/industrial condensed face. */
    --font-serif: "Oswald", "Arial Narrow", system-ui, sans-serif;
    /* Brand wordmark only: kept as the original serif. */
    --font-brand: "Dancing Script", "Instrument Serif", Georgia, "Times New Roman", serif;

    /* Single content column width. Blocks fill it; the page gutter keeps them
       off the screen edges on small viewports. */
    --content-width: 56rem;

    /* Uniform vertical rhythm between consecutive blocks in a section. */
    --block-gap: 1.5rem;

    /* GLOBAL SIZE DIAL. Everything below is in rem / rem-based clamp(), so it
       all scales off the root font-size. Change this one value to make the
       whole site (text, headings, gutters, gaps, content widths) bigger or
       smaller proportionally. 100% = browser default (usually 16px);
       106.25% ≈ 17px; 112.5% ≈ 18px. Applied on `html` below. */
    --root-scale: 106.25%;

    --gutter: clamp(1.25rem, 4vw, 2.5rem);
    --radius: 6px;
    --radius-sm: 6px;

    --transition: 220ms cubic-bezier(0.4, 0, 0.2, 1);

    /* Interaction tokens — theme-independent (same in light + dark).
       Keyboard focus ring (only shown after real keyboard nav) and the
       subtle "press" shrink shared by every button-like control. */
    --focus-ring: 2px solid var(--accent);
    --focus-offset: 3px;

    --unicode-arrow: "\2192"; /* Unicode for → */

    /* Theme-independent tokens — identical in light + dark, so defined once
       here instead of duplicated in both [data-theme] blocks.

       --brand-* : per-platform social/contact icon tints. renderTheme() can
                   override these from SITE.theme.socialColors.
       --on-image-* / --dot-* / --carousel-chip-* : controls that sit ON TOP of
                   photos (lightbox, carousel dots/counter), so they stay
                   dark-on-light in BOTH themes for legibility over any image. */
    --panel-blur: 8px; /* frosted-glass radius; kept modest to ease scroll repaint cost */

    --brand-instagram: #e1306c;
    --brand-youtube: #ff0000;
    --brand-phone: #2e9e4f;
    --brand-email: #3b5bdb;

    --on-image-text: #fff;
    --on-image-btn-bg: rgba(0, 0, 0, 0.45);
    --on-image-btn-bg-hover: rgba(0, 0, 0, 0.7);
    --on-image-hairline: rgba(255, 255, 255, 0.25);
    --on-image-veil: rgba(0, 0, 0, 0.88);

    --dot-veil-bg: rgba(0, 0, 0, 0.35);
    --dot-ring: rgba(255, 255, 255, 0.75);
    --dot-fill-hover: rgba(255, 255, 255, 0.35);

    --carousel-chip-text: #f4f5f8;
}

/* Dark (default) — only tokens that DIFFER from light live here. Shared,
   theme-independent tokens (brand/on-image/dot/chip/panel-blur) are in :root. */
[data-theme="dark"] {
    /* — Backgrounds — */
    --bg-base: #0e0f13; /* page background */
    --bg-panel: rgba(22, 24, 30, 0.62); /* cards / link rows (translucent) */
    --bg-panel-solid: #16181e; /* mobile drawer, buttons (opaque) */

    /* — Text — */
    --text: #ecedf1; /* headings + body */
    --text-soft: #d1d1d1; /* secondary / lead text */
    --text-faint: #ffffff; /* footer / captions */

    /* — Menu / nav — */
    --menu-text: #a6a9b4; /* idle nav link */
    --menu-text-active: #ecedf1; /* hovered / active nav link */
    --menu-hover-bg: var(--accent-soft); /* hover pill behind link */

    /* — Accents & lines — */
    --accent: #C9B7FA;
    /* Derived from --accent so changing the brand color updates the soft
       accent (hover pills, focus glow) automatically — one value, many uses. */
    --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
    --border: rgba(255, 255, 255, 0.10);

    /* — Background-image overlay & header — */
    --overlay-top: rgba(14, 15, 19, 0.79);
    --overlay-bottom: rgba(14, 15, 19, 0.98);
    --header-bg: rgba(14, 15, 19, 0.82);

    /* — Monochrome icon/logo inversion —
       Card icons and the optional header logo are black PNGs on transparent
       (same style as the skull badge). Black art is invisible on the dark
       theme, so dark inverts it to white; light leaves it untouched. */
    --mono-icon-filter: invert(1);
}

/* Light — only tokens that DIFFER from dark; shared tokens are in :root. */
[data-theme="light"] {
    /* — Backgrounds — */
    --bg-base: #ffffff; /* page background */
    /* Cards/rows are more opaque in light mode so text sits on a near-solid
       surface instead of letting the background photo bleed through. */
    --bg-panel: rgba(248, 248, 250, 0.90); /* cards / link rows */
    --bg-panel-solid: #f1f1f4; /* mobile drawer, buttons (opaque) */

    /* — Text — */
    --text: #15161a; /* headings + body */
    --text-soft: #131415; /* secondary / lead text */
    --text-faint: #212123; /* footer / captions */

    /* — Menu / nav — */
    --menu-text: #4b4d55; /* idle nav link */
    --menu-text-active: #15161a; /* hovered / active nav link */
    --menu-hover-bg: var(--accent-soft); /* hover pill behind link */

    /* — Accents & lines — */
    --accent: #8C1C6C;
    /* Derived from --accent. Light mode uses a slightly lower 10% tint. */
    --accent-soft: color-mix(in srgb, var(--accent) 10%, transparent);
    --border: rgba(43, 43, 43, 0.49);

    /* — Background-image overlay & header —
       Heavier than dark: pushes the background photo back so cards/text read
       cleanly in light mode while still showing the image. */
    --overlay-top: rgba(255, 255, 255, 0.47);
    --overlay-bottom: rgba(255, 255, 255, 0.62);
    --header-bg: rgba(255, 255, 255, 0.90);

    /* — Monochrome icon/logo inversion —
       Black art already reads on the white theme — no filter. */
    --mono-icon-filter: none;
}

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

/* No text I-beam anywhere — mouse stays a normal arrow over text.
   Interactive elements still get the pointer hand below. */
* {
    cursor: default;
}

a, button, [role="tab"], label, summary,
input[type="checkbox"], input[type="radio"],
.menu-toggle, .theme-toggle {
    cursor: pointer;
}

/* No blinking text caret on non-editable content. Clicking text focuses the
   <main> container (it's focusable so the skip-link can target it), which would
   otherwise show an editing caret. Real form fields keep a normal caret + cursor. */
* {
    caret-color: transparent;
}

input, textarea, [contenteditable] {
    caret-color: auto;
    cursor: text;
}

html {
    /* Root font-size = the global size dial (--root-scale in :root). All rem
       units resolve against this, so changing the token rescales the whole
       site. Must live here on `html`, not body — rem is anchored to the root. */
    font-size: var(--root-scale);

    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;

    /* Reserve vertical scrollbar space even on tabs that do not need scrolling.
       Without this, switching from a short tab to a tall tab makes the browser
       add the right scrollbar, shrink the layout viewport, and visually squish
       / shift the centered content by the scrollbar width. */
    scrollbar-gutter: stable;
}

html, body {
    overscroll-behavior-y: none;
}

html.site-booting body {
    opacity: 0;
}

html.site-ready body {
    opacity: 1;
    transition: opacity 100ms ease;
}

/* Fallback for older browsers that do not support scrollbar-gutter.
   Keep this on the root scroll container, not body, so the page layout width
   stays consistent between short and long tabs. */
@supports not (scrollbar-gutter: stable) {
    html {
        overflow-y: scroll;
    }
}

body {
    margin: 0;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    font-family: var(--font-sans);
    font-size: clamp(0.94rem, 0.9rem + 0.15vw, 1rem);
    line-height: 1.58;
    color: var(--text);
    background: var(--bg-base);
    overflow-x: hidden;
    /* Don't let late-loading content (background image, map iframe) pin the
       viewport to a nearby element and drag the scroll position on refresh. */
    overflow-anchor: none;
    transition: background-color var(--transition), color var(--transition);
}

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

    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        transition-duration: 0.001ms !important;
    }
}

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

a {
    color: inherit;
    text-decoration: none;
}

/* Kill fake mobile/touch focus rings.
   Focus styling appears only after real keyboard navigation. */
:focus {
    outline: none;
}

body.keyboard-nav :focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
    border-radius: 4px;
}

body:not(.keyboard-nav) :focus-visible {
    outline: none;
}

/* Extra protection for mobile Firefox / touch browsers. */
@media (hover: none), (pointer: coarse) {
    a:focus,
    button:focus,
    [role="tab"]:focus,
    a:focus-visible,
    button:focus-visible,
    [role="tab"]:focus-visible {
        outline: none;
    }
}

/* Remove the grey/blue tap-highlight rectangle mobile browsers draw on tap. */
a, button, [role="tab"], .menu-toggle, .theme-toggle {
    -webkit-tap-highlight-color: transparent;
}

/* ---- Background ------------------------------------------------------- */
.bg,
.bg-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: 100vh; /* fallback */
    height: 100lvh; /* LARGE viewport height: the tallest state (URL bar hidden), and it does NOT change as the bar slides — so no resize on scroll */
    z-index: -2;
    pointer-events: none;
}

/* Two stacked background layers so we can crossfade between images without a
   flash. JS toggles .is-active to fade one in while the other fades out, and
   each active layer runs a slow Ken Burns drift/zoom. */
.bg {
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    opacity: 0;
    transition: opacity 1200ms ease;
    /* Continuous, seamless drift — zoom in and back out forever, no snap.
       Runs on both layers always; opacity (.is-active) decides which shows. */
    /* Ken Burns drift — toggle by commenting line below. */
    animation: bg-drift 15s ease-in-out infinite alternate;
}

.bg.is-active {
    opacity: 1;
}

@keyframes bg-drift {
    from {
        transform: scale(1.02) translate(0, 0);
    }
    to {
        transform: scale(1.12) translate(-1.5%, -1.5%);
    }
}

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

.bg-overlay {
    z-index: -1;
    background: linear-gradient(to bottom, var(--overlay-top), var(--overlay-bottom));
    backdrop-filter: saturate(112%);
    transition: background var(--transition);
}

/* ---- Skip link -------------------------------------------------------- */
.skip-link {
    position: absolute;
    left: 50%;
    top: -3rem;
    transform: translateX(-50%);
    background: var(--accent);
    color: #fff;
    padding: 0.5rem 1rem;
    border-radius: var(--radius-sm);
    z-index: 100;
    transition: top var(--transition);
}

.skip-link:focus {
    top: 0.75rem;
}

/* ---- Header ----------------------------------------------------------- */
.site-header {
    position: sticky;
    top: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.9rem var(--gutter);
    background: var(--header-bg);
    -webkit-backdrop-filter: blur(var(--panel-blur));
    backdrop-filter: blur(var(--panel-blur));
    border-bottom: 1px solid var(--border);
}

.brand {
    font-family: var(--font-brand);
    font-size: 1.6rem;
    letter-spacing: 0.01em;
    font-style: normal;
    margin-right: auto;
    transition: opacity var(--transition);
}

.brand:hover {
    opacity: 0.7;
}

/* Brand as a PNG logo — set by renderNav() when the spec has `brandImage`.
   The image is height-bound to roughly the text brand's visual size so the
   header layout and header-fit math stay unchanged; width follows the asset's
   aspect ratio. Monochrome black logos get the same dark-theme inversion as
   card icons via --mono-icon-filter. */
.brand--image {
    display: flex;
    align-items: center;
}

.brand__img {
    display: block;
    height: 2.1rem;
    width: auto;
    max-width: 8rem;
    object-fit: contain;
    filter: var(--mono-icon-filter);
    user-select: none;
    -webkit-user-select: none;
}

.nav-desktop {
    display: flex;
    gap: 0.35rem;
    flex: none;
}

.nav-desktop a {
    position: relative;
    padding: 0.45rem 0.85rem;
    font-size: 0.95rem;
    color: var(--menu-text);
    border-radius: var(--radius-sm);
    transition: color var(--transition), background var(--transition);
}

.nav-desktop a:hover {
    color: var(--menu-text-active);
    background: var(--menu-hover-bg);
}

.nav-desktop a.is-active {
    color: var(--menu-text-active);
}

.nav-desktop a.is-active::after {
    content: "";
    position: absolute;
    left: 0.85rem;
    right: 0.85rem;
    bottom: 0.15rem;
    height: 2px;
    background: var(--accent);
    border-radius: 2px;
}

.header-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: none;
}

/* Theme toggle */
.theme-toggle,
.menu-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border: 1px solid var(--border);
    background: var(--bg-panel);
    backdrop-filter: blur(var(--panel-blur));
    color: var(--text);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: border-color var(--transition), background var(--transition), transform var(--transition);
}

.theme-toggle:hover,
.menu-toggle:hover {
    border-color: var(--accent);
}

.theme-toggle:active,
.menu-toggle:active {
    border-color: var(--accent);
}

/* Theme toggle icon — one SVG that morphs between a crescent moon (dark mode,
   default) and a rayed sun (light mode). The "bite" is a mask circle that
   slides off-shape in light mode; the rays fade + scale in. Pure CSS, driven
   by [data-theme="light"] on <body>. The global prefers-reduced-motion rule
   collapses these transitions to an instant swap. */
.theme-toggle__icon {
    width: 1.25rem;
    height: 1.25rem;
    color: var(--text);
    overflow: visible;
}

.theme-toggle__cut {
    /* Slides between biting into the core (crescent) and fully clear (disc). */
    transition: transform var(--transition);
    transform: translateX(0);
}

.theme-toggle__rays {
    opacity: 0;
    transform: scale(0.4);
    transform-origin: center;
    transition: opacity var(--transition), transform var(--transition);
}

/* Light mode → sun: the mask bite slides fully clear (disc), rays bloom in.
   The core radius is intentionally NOT animated: animating the SVG `r`
   property isn't a valid CSS length without a unit and isn't supported in
   Firefox, and the bite-slide + rays carry the morph on their own. */
[data-theme="light"] .theme-toggle__cut {
    transform: translateX(20px);
}

[data-theme="light"] .theme-toggle__rays {
    opacity: 1;
    transform: scale(1);
}

/* Mobile menu toggle (hidden on desktop) */
.menu-toggle {
    display: none;
    flex-direction: column;
    gap: 4px;
}

.menu-toggle__bar {
    width: 1.1rem;
    height: 2px;
    background: var(--text);
    border-radius: 2px;
    transition: transform var(--transition), opacity var(--transition);
}

.menu-toggle[aria-expanded="true"] .menu-toggle__bar:nth-child(1) {
    transform: translateY(6px) rotate(45deg);
}

.menu-toggle[aria-expanded="true"] .menu-toggle__bar:nth-child(2) {
    opacity: 0;
}

.menu-toggle[aria-expanded="true"] .menu-toggle__bar:nth-child(3) {
    transform: translateY(-6px) rotate(-45deg);
}

/* ---- Brand + socials (header) ----------------------------------------- */
/* The brand anchor is wrapped in .brand-wrap by JS so social icons can sit
   right beside it. The wrapper takes over the brand's auto-margin so the
   header layout (nav + actions pushed right) is unchanged. */
.brand-wrap {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-right: auto;
    /* Hold natural width (don't shrink or clip): the socials keep their real
       size so they genuinely approach the nav as the window narrows. JS
       (initHeaderFit) watches that gap and switches to the hamburger before
       they touch, hiding both the nav and these socials. */
    flex: none;
}

.brand-wrap .brand {
    margin-right: 0;
    flex: none;
}

/* ---- Socials ---------------------------------------------------------- */
.socials {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.socials__link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.55rem;
    border-radius: var(--radius-sm);
    font-size: 0.85rem;
    color: var(--menu-text);
    transition: color var(--transition), background var(--transition);
}

.socials__link:hover {
    color: var(--menu-text-active);
    background: var(--menu-hover-bg);
}

.socials__label {
    line-height: 1;
}

/* Icon takes its brand color; the label stays in the readable menu color.
   The svg uses fill="currentColor", so we set color only on the svg here. */
.socials__link svg {
    display: block;
    flex: none;
}

.socials__link--instagram svg {
    color: var(--brand-instagram);
}

.socials__link--youtube svg {
    color: var(--brand-youtube);
}

.socials__link--phone svg {
    color: var(--brand-phone);
}

.socials__link--email svg {
    color: var(--brand-email);
}

/* ---- Mobile nav ------------------------------------------------------- */
.nav-mobile {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 50;
    width: min(78vw, 20rem);
    height: 100vh;
    height: 100dvh;
    padding: 5rem 1.5rem 2rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    /* Frosted, like the header — translucent panel + blur instead of solid. */
    background: var(--header-bg);
    -webkit-backdrop-filter: blur(var(--panel-blur));
    backdrop-filter: blur(var(--panel-blur));
    border-left: 1px solid var(--border);
    transform: translateX(105%);
    transition: transform var(--transition);
    overflow-y: auto;
}

/* Close button pinned top-right inside the drawer. */
.nav-mobile__close {
    position: absolute;
    top: 1rem;
    right: 1rem;
    width: 2.5rem;
    height: 2.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-panel);
    -webkit-backdrop-filter: blur(var(--panel-blur));
    backdrop-filter: blur(var(--panel-blur));
    color: var(--text);
    font-size: 1.4rem;
    line-height: 1;
    transition: border-color var(--transition), transform var(--transition);
}

.nav-mobile__close:hover {
    border-color: var(--accent);
}

.nav-mobile__close:active {
    border-color: var(--accent);
}

.nav-mobile.is-open {
    transform: translateX(0);
}

.nav-mobile a {
    padding: 0.9rem 1rem;
    font-size: 1.1rem;
    color: var(--menu-text);
    border-radius: var(--radius-sm);
    transition: color var(--transition), background var(--transition);
}

.nav-mobile a:hover,
.nav-mobile a.is-active {
    color: var(--menu-text-active);
    background: var(--menu-hover-bg);
}

/* Social links sit at the bottom of the drawer, stacked one per row, styled
   exactly like the header version (colored icon + visible label). The generic
   ".nav-mobile a" rule above sets the row padding/size; these just lay them out
   vertically and restore the inline icon+label gap. */
.nav-mobile .socials {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.25rem;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}

.nav-mobile .socials .socials__link {
    gap: 0.6rem;
    font-size: 1.1rem;
}

.nav-mobile__scrim {
    position: fixed;
    inset: 0;
    z-index: 45;
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    transition: opacity var(--transition);
    /* When visible, swallow scroll/touch so the page behind can't be scrolled. */
    touch-action: none;
    overscroll-behavior: contain;
}

.nav-mobile {
    /* ...all your existing properties... */
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* ---- Content ---------------------------------------------------------- */
/* The content column is the single site width; blocks fill it and the gutter
   keeps them off the edges on small screens. */
.content {
    width: 100%;
    max-width: var(--content-width);
    margin: 0 auto;
    padding: clamp(2rem, 6vw, 3rem) var(--gutter) 3rem;
    outline: none;
    flex: 1 0 auto;
}

.section {
    margin-bottom: clamp(3rem, 8vw, 5rem);
}

.section:last-child {
    margin-bottom: 0;
}

/* Tab panels: hidden ones are removed; active one fades in gently */
.content > .section[hidden] {
    display: none;
}

.section--active {
    animation: panel-in 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

@keyframes panel-in {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .section--active {
        animation: none;
    }
}

/* ---- Block layout: uniform spacing ----------------------------------- */
/* Every rendered block is wrapped by JS in a .block element. This is the
   single source of truth for the vertical rhythm between blocks. Adding a new
   block type needs no new spacing CSS — it inherits .block + .block. Blocks
   fill the content column; control a block's own width inside its builder/CSS
   if it ever needs to be narrower. */
.block {
    width: 100%;
    margin-inline: auto;
}

/* Uniform gap between consecutive blocks. Replaces the old per-pair adjacency
   rules (.section__text + .card-grid, etc.) with one rule that covers every
   combination, present and future. */
.block + .block {
    margin-top: var(--block-gap);
}

/* A section title sits above the first block; give the first block air below
   the title without needing the block to know about it. */
.section__title + .block {
    margin-top: 0;
}

/* Shared block heading + intro line. Six block types (table, faq, slideshow,
   gallery, hours, review) open with an optional serif name and a prose blurb;
   these were six identical rule pairs. Builders now emit `block__name` /
   `block__blurb` alongside the type-specific class (e.g. "block__name
   table__name"), so the common look lives here once and a new block's heading
   is styled for free. Override per type by adding rules to the specific class. */
.block__name {
    font-family: var(--font-serif);
    font-weight: 500;
    font-size: 1.25rem;
    margin: 0 0 0.6rem;
    color: var(--text);
}

.block__blurb {
    margin: 0 0 0.75rem;
}

/* Intro / hero */
.intro__eyebrow {
    font-size: 0.85rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin: 0 0 0.75rem;
}

.intro__title {
    font-family: var(--font-serif);
    font-weight: 600;
    font-size: clamp(2.4rem, 7vw, 3.6rem);
    line-height: 1.05;
    margin: 0 0 1rem;
    letter-spacing: -0.01em;
    text-transform: uppercase;
}

.intro__title em {
    font-style: normal;
    color: var(--accent);
}

.intro__lead {
    font-size: clamp(1.1rem, 1rem + 0.6vw, 1.3rem);
    color: var(--text-soft);
    margin: 0;
}

/* Section headings */
.section__title {
    font-family: var(--font-serif);
    font-weight: 600;
    font-size: clamp(1.6rem, 4vw, 2rem);
    margin: 0 0 1.25rem;
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.01em;
}

/* Generic prose */
.prose p {
    color: var(--text-soft);
    margin: 0 0 1rem;
}

.prose p:last-child {
    margin-bottom: 0;
}

/* Card grid (services / projects) */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
    gap: 1rem;
}

.card {
    padding: 1.35rem 1.4rem;
    background: var(--bg-panel);
    backdrop-filter: blur(var(--panel-blur));
    border: 2px solid var(--border);
    border-radius: var(--radius);
    /* transform uses a short ease-OUT (fast start, gentle settle) so the 3px
       lift feels crisp, not draggy; border-color keeps the shared curve. The
       lift is composited (see will-change on a.card__link) so moving the
       blurred panel doesn't repaint the backdrop-filter every frame — that
       repaint was the stutter. */
    transition: transform 140ms cubic-bezier(0.2, 0.7, 0.3, 1), border-color var(--transition);
    display: flex;
    flex-direction: column;
}

/* Only clickable cards react to the cursor. A plain (non-linked) .card stays
   put — reacting like a button would be a false affordance (user hovers, it
   lifts and glows, click does nothing). The lift/accent is reserved for
   a.card__link, which is what buildCards adds only when the card is a link. */
a.card__link:hover {
    transform: translateY(-3px);
    border-color: var(--accent);
}

/* Card icon — set by buildCards() when an item has an `icon`: a monochrome
   black-on-transparent PNG (skull-badge style), sat to the LEFT of the text
   and vertically centered against it (.card--icon switches the card from the
   default column to a row). The --mono-icon-filter token inverts it to white
   in dark theme and leaves it alone in light, so one asset works on both
   themes. */
.card--icon {
    flex-direction: row;
    align-items: center;
    gap: 1.1rem;
}

.card__icon {
    display: block;
    width: 4rem;
    height: 4rem;
    flex: none;
}

.card__icon img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: var(--mono-icon-filter);
    user-select: none;
    -webkit-user-select: none;
}

/* The text column inside an icon card. Mirrors the plain card's column flow
   so .card__meta's margin-top:auto (stick to bottom) keeps working; min-width
   lets long titles wrap instead of pushing the icon out of the card. */
.card__body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

.card h3 {
    margin: 0 0 0.4rem;
    font-size: 1.1rem;
    font-weight: 600;
}

.card p {
    margin: 0;
    color: var(--text-soft);
    font-size: 0.95rem;
}

a.card__link {
    display: flex;
    cursor: pointer;
    position: relative;
    will-change: transform;
}

/* Persistent "go" cue on clickable cards, so the affordance survives on touch
   (no hover on mobile) — not just on desktop hover. The arrow rides on the
   accent meta line and nudges right on hover. */
a.card__link .card__meta::after {
    /* content: var(--unicode-arrow); */ /* arrow cue intentionally disabled */
    margin-left: 0.4rem;
    display: inline-block;
    font-size: 2.5em;
    line-height: 0.5;
    vertical-align: -0.09em;
    transition: transform var(--transition);
}

a.card__link:hover .card__meta::after {
    transform: translateX(3px);
}

/* Fallback for clickable cards with no meta line: a corner arrow on the card
   itself, so the cue is guaranteed regardless of content. (The card is
   position:relative above to anchor this.) */
a.card__link:not(:has(.card__meta))::after {
    /* content: var(--unicode-arrow); */
    position: absolute;
    top: 1.1rem;
    right: 1.2rem;
    font-size: 1.2rem;
    line-height: 1;
    color: var(--accent);
    transition: transform var(--transition);
}

a.card__link:not(:has(.card__meta)):hover::after {
    transform: translateX(3px);
}

.card__meta {
    display: inline-block;
    margin-top: auto;
    padding-top: 0.85rem;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--accent);
}

/* Link list (where to find me / contact) */
.link-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0.4rem;
}

.link-list a {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.85rem 1.1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-panel);
    backdrop-filter: blur(var(--panel-blur));
    color: var(--text);
    transition: border-color var(--transition), transform var(--transition);
}

.link-list a:hover {
    border-color: var(--accent);
    transform: translateX(3px);
}

/* Kind + value stack in a column next to the icon */
.link-list__text {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

/* Small muted "what is this" line ("Telefón", "Instagram", …) */
.link-list__kind {
    font-size: 0.72rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-faint);
}

/* Label hugs the icon on the left */
.link-list .label {
    font-weight: 500;
    overflow-wrap: anywhere;
}

/* Persistent clickability cue — same arrow language as redirect cards, so
   contact rows never need a "these are clickable" sentence in the copy. */
.link-list__arrow {
    margin-left: auto;
    flex: none;
    display: inline-flex;
    align-items: center;
    color: var(--accent);
}

.link-list__arrow::after {
    /* content: var(--unicode-arrow); */
    font-size: 1.5rem;
    line-height: 1;
}

/* When a handle is present it takes the auto-push; the arrow just trails it. */
.link-list a:has(.handle) .link-list__arrow {
    margin-left: 0.75rem;
}

/* layout: "grid" — rows become side-by-side tiles (auto-stacks when narrow). */
.link-list--grid {
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 0.6rem;
}

.link-list--grid a {
    padding: 1.05rem 1.15rem;
}

.link-list--grid a:hover {
    transform: translateY(-2px);
}

/* Handle pushed to the right end. margin-left:auto does the "space-between"
   job without forcing a wrap. */
.link-list .handle {
    margin-left: auto;
    color: var(--text-faint);
    font-size: 0.9rem;
    text-align: right;
    overflow-wrap: anywhere;
}

/* Below this width, EVERY button stacks the handle under the label, so they
   all stay consistent (not one wrapped, others not). Tune the 26rem to taste. */
@media (max-width: 26rem) {
    .link-list a {
        flex-wrap: wrap;
    }

    .link-list .handle {
        margin-left: 0; /* drop to its own line, align under label */
        flex-basis: 100%;
        text-align: left;
        padding-left: calc(2rem + 0.6rem); /* indent under the label, past the icon */
        order: 10; /* wrap below; the arrow stays up on the value line */
    }

    /* With the handle on its own line, the arrow takes the auto-push again. */
    .link-list a:has(.handle) .link-list__arrow {
        margin-left: auto;
    }
}

.link-list__icon {
    display: inline-flex;
    align-items: center;
    flex: none;
    margin-right: 0.75rem;
    color: var(--text-soft);
}

/* Per-platform brand colors for contact-row icons. Brand colors, not theme
   tokens — intentionally identical in light and dark mode, and kept on hover. */
.link-list--phone .link-list__icon {
    color: var(--brand-phone);
}

.link-list--email .link-list__icon {
    color: var(--brand-email);
}

.link-list--instagram .link-list__icon {
    color: var(--brand-instagram);
}

/* ---- Table (price list / structured rows) ----------------------------- */
/* A structured table rendered by buildTable() in script.js from a `table`
   block: an optional name + blurb, then `headings` (the columns) and `rows`
   (arrays of cells in heading order). Colors come from the same theme tokens
   as cards and the link list, so it re-themes automatically. The .table-scroll
   wrapper lets a wide table scroll sideways on narrow screens instead of
   squashing columns or forcing the whole page wider.

   On mobile (<= 640px) the table is restructured into stacked row-cards — see
   the stacking block inside the responsive section near the bottom. The base
   rules here are the DESKTOP table; do not put display:block / card styling
   here or it leaks onto desktop. */

/* Scroll container: holds the panel frame and clips the corners. On desktop a
   very wide table can scroll sideways here; on mobile this is neutralized and
   the rows stack instead (see responsive section). */
.table-scroll {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow-x: auto;
    background: var(--bg-panel);
    backdrop-filter: blur(var(--panel-blur));
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.8rem;
}

.data-table th,
.data-table td {
    padding: 0.85rem 1.1rem;
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--border);
}

/* Header row: serif heading style, consistent with section/slideshow names. */
.data-table thead th {
    font-family: var(--font-serif);
    font-weight: 500;
    font-size: 1.05rem;
    color: var(--text);
    white-space: nowrap;
}

/* Body cells default to the softer body text color, like card/prose copy. */
.data-table tbody td {
    color: var(--text-soft);
}

/* Last column reads as a price/value: right-aligned, accented, won't wrap. */
.data-table__value {
    text-align: left;
    white-space: wrap;
}

.data-table td.data-table__value {
    color: var(--accent);
    font-weight: 600;
}

/* Gentle row highlight on hover, matching the link-list/card feel. */
.data-table tbody tr {
    transition: background var(--transition);
}

.data-table tbody tr:hover {
    /* background: var(--menu-hover-bg); */
}

/* ---- FAQ / accordion -------------------------------------------------- */
/* Rendered by buildFaq() in engine.js from a `faq` block: optional name +
   blurb, then `items` of { q, a }. Each row is a native <button> (the
   question) toggling a `.faq__a` panel (the answer). Uses the same panel
   tokens as cards/links/table so it re-themes in light/dark for free. The
   +/- icon is drawn with pseudo-elements (no icon font / SVG asset). */

.faq__list {
    display: grid;
    gap: 0.5rem;
}

.faq__item {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-panel);
    -webkit-backdrop-filter: blur(var(--panel-blur));
    backdrop-filter: blur(var(--panel-blur));
    overflow: hidden;
}

/* The question button fills its row, sits flat (the .faq__item frames it). */
.faq__q {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.95rem 1.1rem;
    border: 0;
    background: transparent;
    cursor: pointer;
    text-align: left;
    font: inherit;
    font-weight: 600;
    color: var(--text);
    transition: background var(--transition), color var(--transition);
}

.faq__q:hover {
    background: var(--menu-hover-bg);
    color: var(--accent);
}

.faq__q-text {
    flex: 1;
}

/* +/- toggle drawn from two bars; the vertical one hides when expanded. */
.faq__icon {
    position: relative;
    flex: none;
    width: 14px;
    height: 14px;
}

.faq__icon::before,
.faq__icon::after {
    content: "";
    position: absolute;
    background: var(--accent);
    transition: transform var(--transition), opacity var(--transition);
}

.faq__icon::before { /* horizontal bar */
    top: 50%;
    left: 0;
    width: 100%;
    height: 2px;
    transform: translateY(-50%);
}

.faq__icon::after { /* vertical bar */
    left: 50%;
    top: 0;
    width: 2px;
    height: 100%;
    transform: translateX(-50%);
}

/* Expanded: rotate the set and collapse the vertical bar into a minus. */
.faq__q[aria-expanded="true"] .faq__icon::after {
    opacity: 0;
    transform: translateX(-50%) rotate(90deg);
}

/* The answer panel. `hidden` (set by JS) collapses it; when shown it gets a
   thin divider from the question and the standard prose treatment inside. */
.faq__a {
    padding: 0 1.1rem 1rem;
    border-top: 1px solid var(--border);
    color: var(--text-soft);
}

.faq__a .prose {
    margin-top: 0.85rem;
}

/* ---- Map: embed mode (live Google iframe) ----------------------------- */
.map-embed {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--bg-panel);
    aspect-ratio: 21 / 9; /* wider, shorter — more of a rectangle */
}

.map-embed iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
    /* gently match the site mood without breaking legibility */
    filter: saturate(0.95);
    pointer-events: none; /* view-only on all browsers; open via the button */
}

/* ---- Map: static mode (no iframe, clean themed card) ------------------ */
/* Rendered when a `map` block sets mode: "static". A lightweight panel that
   matches cards/table — location name, optional address, and the Open-in-Maps
   button — instead of the noisier live iframe. */
.map-card {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-panel);
    backdrop-filter: blur(var(--panel-blur));
    padding: 1.35rem 1.4rem;
}

.map-card__name {
    margin: 0;
    font-family: var(--font-serif);
    font-weight: 500;
    font-size: 1.25rem;
    line-height: 1.2;
    color: var(--text);
}

.map-card__address {
    margin: 0.4rem 0 0;
    color: var(--text-soft);
    font-size: 0.95rem;
}

/* The Open-in-Maps button row is shared by both map modes. */
.map-open-row {
    margin: 0.75rem 0 0;
}

/* In embed mode the row sits just under the framed map. */
.map-embed + .map-open-row {
    margin-top: 0.75rem;
}

.map-open-row a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.6rem;
    padding: 0.65rem 1rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--bg-panel);
    color: var(--text);
    font: inherit;
    line-height: 1;
    transition: border-color var(--transition), transform var(--transition);
}

.map-open-row a:hover {
    border-color: var(--accent);
}

.map-open-row a:active {
    border-color: var(--accent);
}

@media (max-width: 640px) {
    .map-embed {
        aspect-ratio: 4 / 5;
    }
}

/* ---- Carousel / slideshow --------------------------------------------- */
/* Reusable image slideshow rendered by buildCarousel() in script.js. Used by
   a `slideshow` block. One slide = a plain framed image (.carousel--single, no
   controls); two or more = manual carousel with prev/next buttons, dots, and an
   "n / total" chip. Colors come from the theme tokens above so it re-themes and
   matches cards/map automatically. */

.carousel {
    position: relative;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--bg-panel);
    backdrop-filter: blur(var(--panel-blur));

    /* One canonical IMAGE shape. The chin (caption) is no longer a hardcoded
       height: slides are grid-stacked in the viewport below, so the frame is
       image (fixed ratio) + the tallest caption in this carousel. Still no
       per-slide jumping — the container is sized by the tallest slide, once,
       and never resizes as slides change. */
    --carousel-ratio: 16 / 10;
}

.carousel__viewport {
    /* Grid-stacking replaces the old absolute/inset-0 stack. Every slide
       occupies the same 1/1 cell, so they overlap for the crossfade AND the
       container auto-sizes to the tallest slide. This is what lets captions
       be content-sized (no fixed --carousel-caption-height) without the
       slideshow resizing between slides. */
    display: grid;
    background: var(--bg-panel-solid);
    overflow: hidden;
}

/* All slides stay in the stack; opacity shows/hides. A slide is a 2-row grid:
   row 1 = image at the canonical ratio, row 2 = chin, which flexes to fill
   whatever the tallest caption claimed — so chins match across slides. */
.carousel__slide {
    grid-area: 1 / 1;
    min-width: 0;
    margin: 0;

    display: grid;
    grid-template-rows: auto 1fr;

    /* Keep all slides in the stack.
       Opacity handles showing/hiding.
       This is what allows real overlap. */
    opacity: 0;

    transition: opacity 500ms ease-in-out;

    pointer-events: none;
}

.carousel__slide.is-active,
.carousel--single .carousel__slide {
    opacity: 1;
    pointer-events: auto;
    visibility: visible;
}

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

/* Expand button wraps the image so it's a real, focusable control that opens
   the lightbox. The canonical ratio now lives HERE, not on the viewport —
   the image area is the fixed part of the frame, the chin is the flexible
   part. zoom-in cursor hints it's clickable. */
.carousel__expand {
    grid-row: 1;
    width: 100%;
    aspect-ratio: var(--carousel-ratio);
    min-height: 0;
    padding: 0;
    margin: 0;
    border: 0;
    background: none;
    display: block;
    overflow: hidden;
    cursor: zoom-in;
}

.carousel__expand:focus-visible {
    outline: var(--focus-ring);
    outline-offset: -2px;
}

.carousel__img {
    width: 100%;
    height: 100%;
    min-height: 0;
    object-fit: cover;
    display: block;
}

/* If a slide image 404s, hide the broken-image glyph; the panel background
   shows through instead (silent fail, like the page background). */
.carousel__img.is-broken {
    visibility: hidden;
}

/* Number counter hidden: dots are the indicator. Keeping both is visual noise
   and increases the chance of overlapping real image content. */
.carousel__counter {
    display: none;
}

/* Caption / chin.
   Uses normal site theme tokens, not hardcoded photo-overlay colors.

   Content-sized: the line-clamps below are the ceiling, not a fixed pixel
   height. The old fixed height (clamp + vw) was smaller than 1 title line +
   2 text lines on phones, and justify-content: center clipped the overflow
   on BOTH edges — that was the sliced-title / mid-word-cut bug. */
.carousel__caption {
    z-index: 2;

    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.15rem;

    min-height: 3rem; /* small floor so a one-liner chin doesn't look pinched */

    padding: 0.7rem 0.85rem;
    overflow: hidden;

    background: var(--bg-panel);
    color: var(--text);
    border-top: 1px solid var(--border);

    -webkit-backdrop-filter: blur(var(--panel-blur));
    backdrop-filter: blur(var(--panel-blur));
}

.carousel__title {
    font-family: var(--font-serif);
    font-size: 1.15rem;
    line-height: 1.2;
    color: var(--text);
}

.carousel__text {
    font-size: 0.95rem;
    line-height: 1.35;
    color: var(--text-soft);
}

.carousel__subtext {
    font-size: 0.85rem;
    line-height: 1.35;
    color: var(--text-faint);
}

/* Line-clamps are now the ONLY cap on caption size. Overflow past the clamp
   ends on a line boundary with an ellipsis — never a sliced glyph. */
.carousel__title,
.carousel__text,
.carousel__subtext {
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
}

.carousel__title {
    -webkit-line-clamp: 1;
    line-clamp: 1;
}

.carousel__text,
.carousel__subtext {
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

/* Prev / next buttons, vertically centered on each edge. */
.carousel__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.4rem;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: var(--bg-panel-solid);
    color: var(--text);
    font-size: 1rem;
    line-height: 1;
    transition: border-color var(--transition), background var(--transition), transform var(--transition);
}

.carousel__nav--prev {
    left: 0.6rem;
}

.carousel__nav--next {
    right: 0.6rem;
}

.carousel__nav:hover {
    border-color: var(--accent);
    background: var(--menu-hover-bg);
}

.carousel__nav:active {
    border-color: var(--accent);
}

/* Dots indicator, floating inside the image without sitting on captions. */
.carousel__dots {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    z-index: 4;

    display: flex;
    justify-content: center;
    align-items: center;
    gap: 0.45rem;

    padding: 0.35rem 0.5rem;
    border-radius: 999px;

    background: var(--dot-veil-bg);
    border: 1px solid rgba(255, 255, 255, 0.18);

    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}

.carousel__dot {
    width: 0.55rem;
    height: 0.55rem;
    padding: 0;
    border: 1px solid var(--dot-ring);
    border-radius: 50%;
    background: transparent;
    transition: background var(--transition), transform var(--transition);
}

.carousel__dot:hover {
    background: var(--dot-fill-hover);
}

.carousel__dot.is-active {
    background: var(--on-image-text);
    transform: scale(1.15);
}

@media (max-width: 640px) {
    .carousel {
        --carousel-ratio: 4 / 3;
        /* --carousel-caption-height removed — the chin is content-sized now */
    }

    .carousel__nav {
        width: 2.1rem;
        height: 2.1rem;
    }
}

/* ---- Gallery (image grid) --------------------------------------------- */
/* Rendered by buildGallery() in engine.js. Sibling to the carousel: same theme
   tokens, same shared lightbox (getLightbox), but a scannable grid of tiles
   instead of one paged frame. A `gallery` block shows all images at once;
   clicking any tile opens the lightbox at that image. A `photo` block is a
   one-image gallery. */

.gallery__grid {
    display: grid;
    /* Auto-fit tiles; min tile ~13rem, never overflowing a narrow screen. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
    gap: 0.75rem;
}

/* Optional hard column hints (data-columns="2|3|4"); still collapse on narrow
   viewports via the media query below. */
.gallery__grid[data-columns="2"] {
    grid-template-columns: repeat(2, 1fr);
}

.gallery__grid[data-columns="3"] {
    grid-template-columns: repeat(3, 1fr);
}

.gallery__grid[data-columns="4"] {
    grid-template-columns: repeat(4, 1fr);
}

/* Each tile is a button so it's focusable/keyboard-openable. Strip button
   chrome; the image fills it. zoom-in cursor signals it opens the lightbox —
   same affordance language as .carousel__expand. */
.gallery__item {
    display: flex;
    flex-direction: column;
    padding: 0;
    margin: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--bg-panel);
    -webkit-backdrop-filter: blur(var(--panel-blur));
    backdrop-filter: blur(var(--panel-blur));
    cursor: zoom-in;
    transition: transform var(--transition), border-color var(--transition);
}

.gallery__item:hover {
    transform: translateY(-3px);
    border-color: var(--accent);
}

.gallery__item:active {
    border-color: var(--accent);
}

.gallery__item:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
}

/* Square tile. object-fit: cover crops uniformly so a ragged set of photo
   dimensions still reads as a tidy grid (same trick as .carousel__img). */
.gallery__img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
    min-height: 0;
}

/* Broken image: hide the glyph, let the panel show through (silent fail,
   matches .carousel__img.is-broken). */
.gallery__img.is-broken {
    visibility: hidden;
}

/* Pager: prev / "page / total" / next. Sits centered under the grid; only
   rendered when the set spans more than one page (engine.js). Uses the same
   token language as the rest of the UI. */
.gallery__pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    margin-top: 0.9rem;
}

.gallery__pager-status {
    font-variant-numeric: tabular-nums;
    font-size: 0.95rem;
    color: var(--text-soft);
    min-width: 3.5rem;
    text-align: center;
}

.gallery__pager-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-panel);
    color: var(--text);
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    transition: transform var(--transition), border-color var(--transition);
}

.gallery__pager-btn:hover:not(:disabled) {
    transform: translateY(-2px);
    border-color: var(--accent);
}

.gallery__pager-btn:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
}

.gallery__pager-btn:disabled {
    opacity: 0.4;
    cursor: default;
}

/* Narrow screens: relax forced column counts to two so tiles stay tappable. */
@media (max-width: 640px) {
    .gallery__grid[data-columns="3"],
    .gallery__grid[data-columns="4"] {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* ----------------------------------------------------------------------
   Opening hours block. A definition list rendered as label/value rows;
   today's row is highlighted. Mirrors the panel/border token language used
   by the table and gallery blocks.
---------------------------------------------------------------------- */

.hours__list {
    margin: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--bg-panel);
    -webkit-backdrop-filter: blur(var(--panel-blur));
    backdrop-filter: blur(var(--panel-blur));
}

.hours__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.6rem 0.85rem;
    border-top: 1px solid var(--border);
}

.hours__row:first-child {
    border-top: 0;
}

.hours__day {
    margin: 0;
    color: var(--text-soft);
}

.hours__value {
    margin: 0;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

/* Today's row: tinted background, accent edge, and a bolder day label so the
   current day reads at a glance without needing a separate badge. */
.hours__row.is-today {
    background: var(--accent-soft);
    box-shadow: inset 3px 0 0 var(--accent);
}

.hours__row.is-today .hours__day {
    color: var(--text);
    font-weight: 600;
}

/* ---- Review block ----------------------------------------------------- */
/* Rendered by buildReview() in engine.js from a `review` block. Outbound
   "leave a review" CTAs (Google, Facebook, …). No backend — each is a link to
   the platform's own review page. Surfaces match cards/links so it re-themes
   for free in light/dark and on the 404 page. */

.review__list {
    display: grid;
    gap: 0.6rem;
}

/* A CTA row: icon + label + trailing arrow, matching .link-list a treatment. */
.review__cta {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.85rem 1.1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-panel);
    -webkit-backdrop-filter: blur(var(--panel-blur));
    backdrop-filter: blur(var(--panel-blur));
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.18s ease, background 0.18s ease, transform 0.18s ease;
}

.review__cta:hover {
    border-color: var(--accent);
    background: var(--accent-soft);
    transform: translateY(-1px);
}

.review__cta:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
}

.review__cta-icon {
    display: inline-flex;
    flex: 0 0 auto;
    line-height: 0;
}

.review__cta-label {
    flex: 1 1 auto;
    font-weight: 500;
}

/* Trailing arrow, mirroring .link-list__arrow. */
.review__cta-arrow {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    background: currentColor;
    opacity: 0.55;
    -webkit-mask: no-repeat center / contain url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg>');
    mask: no-repeat center / contain url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg>');
    transition: transform 0.18s ease, opacity 0.18s ease;
}

.review__cta:hover .review__cta-arrow {
    opacity: 1;
    transform: translateX(2px);
}

/* That prevents Firefox/Android from hijacking random taps and opening Maps.
Users can intentionally open it via the explicit "Open in Maps" link. */
@media (hover: none), (pointer: coarse) {
    .map-embed iframe {
        pointer-events: none;
    }

    .map-open-row {
        text-align: center;
    }

    .brand:hover {
        opacity: 1;
    }

    .nav-desktop a:hover,
    .nav-mobile a:hover {
        color: var(--menu-text);
        background: transparent;
    }

    .nav-desktop a.is-active,
    .nav-mobile a.is-active {
        color: var(--menu-text-active);
    }

    .nav-mobile a.is-active {
        background: var(--menu-hover-bg);
    }

    .theme-toggle:hover,
    .menu-toggle:hover,
    .map-open-row a:hover {
        border-color: var(--border);
    }

    a.card__link:hover {
        transform: none;
        border-color: var(--accent);
    }

    a.card__link:hover .card__meta::after,
    a.card__link:not(:has(.card__meta)):hover::after {
        transform: none;
    }

    .link-list a:hover {
        transform: none;
        border-color: var(--border);
    }

    /* Review CTA: drop the sticky lift/tint + arrow nudge on touch. */
    .review__cta:hover {
        transform: none;
        border-color: var(--border);
        background: var(--bg-panel);
    }

    .review__cta:hover .review__cta-arrow {
        transform: none;
        opacity: 0.55;
    }

    /* Table: drop the sticky row highlight on touch (matches the rest). */
    .data-table tbody tr:hover {
        background: transparent;
    }

    /* FAQ: drop the sticky question hover on touch (the press still works). */
    .faq__q:hover {
        background: transparent;
        color: var(--text);
    }

    .site-footer a:hover {
        color: var(--text-soft);
    }

    /* Socials: neutralize sticky hover on touch (matches the rest of the site) */
    .socials__link:hover {
        color: var(--menu-text);
        background: transparent;
    }

    /* Carousel: keep buttons usable but drop sticky hover styling on touch. */
    .carousel__nav:hover {
        border-color: var(--border);
        background: var(--bg-panel-solid);
    }

    .carousel__dot:hover {
        background: transparent;
    }

    .carousel__dot.is-active:hover {
        background: var(--on-image-text);
    }

    /* Gallery: drop the sticky hover lift on touch (matches carousel/cards). */
    .gallery__item:hover {
        transform: none;
        border-color: var(--border);
    }
}

/* ---- Footer ----------------------------------------------------------- */
.site-footer {
    width: 100%;
    max-width: none;
    flex-shrink: 0;
    padding: 0.75rem clamp(1.5rem, 5vw, 4rem);
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
    justify-content: space-between;
    align-items: center;
    color: var(--text-soft);
    font-size: 0.78rem;
    border-top: 1px solid var(--border);
}

.site-footer a {
    color: var(--text-soft);
}

.site-footer a:hover {
    color: var(--accent);
}

/* Visitor count sits right after the © line. Empty until the count loads
   (or stays empty if analytics is off / unreachable), and the ::before dot
   only shows once there's text, so there's no stray leading dot. When it
   links to the public dashboard it's a plain footer link and inherits the
   shared `.site-footer a` style — same as the credit link — so both match.
   The faint color applies only to the non-link span variant. */
span.footer__count {
    color: var(--text-soft);
}

.footer__count:not(:empty)::before {
    content: "·";
    margin: 0 0.5rem 0 0;
    color: var(--text-soft);
    opacity: 0.6;
}

/* ---- Responsive ------------------------------------------------------- */

/* Mobile nav mode, triggered two ways that behave identically:
   (1) genuinely small screens via the media query below;
   (2) the .force-mobile-nav class, added by JS (initHeaderFit) the instant the
       desktop nav and the header socials would touch. In that mode the desktop
       nav AND the header socials are both hidden and the hamburger takes over —
       the socials still live in the mobile drawer, so nothing is lost.
   CSS can't @media on a class, so the two triggers are kept as parallel rules. */
.site-header.force-mobile-nav .nav-desktop {
    display: none;
}

.site-header.force-mobile-nav .brand-wrap .socials {
    display: none;
}

.site-header.force-mobile-nav .menu-toggle {
    display: inline-flex;
}

@media (max-width: 640px) {
    .nav-desktop {
        display: none;
    }

    /* On small screens the header socials are hidden too; they appear in the
       drawer instead. */
    .brand-wrap .socials {
        display: none;
    }

    .menu-toggle {
        display: inline-flex;
    }

    .link-list a {
        padding: 1rem 1.1rem;
    }

    /* ---- Stack data tables on mobile ---------------------------------- */
    /* Long-text tables (e.g. Podmienky servisu) are unreadable as a
       side-scrolling 2-column grid on a narrow screen. Instead of scrolling
       horizontally, turn each row into a self-contained card with cells
       stacked vertically. Works off source order, no JS change needed.

       This is the ONLY place the card/stacking treatment lives — the base
       .data-table rules above stay a normal desktop table. */
    /* Neutralize the scroll frame: the cards carry their own borders now. */
    .table-scroll {
        overflow-x: visible;
        border: none;
        background: transparent;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        min-width: 0;
    }

    /* Collapse the table structure to blocks so rows/cells stack. min-width:0
       lets them shrink below their intrinsic content width — this is what stops
       a long sentence (e.g. the skladovanie row) from forcing a sideways scroll. */
    .data-table,
    .data-table thead,
    .data-table tbody,
    .data-table tr,
    .data-table td {
        display: block;
        width: 100%;
        min-width: 0;
    }

    /* Visually hide the header row — each stacked row is self-explanatory in
       source order (e.g. condition then detail, or úkon/popis/cena). Kept in
       the DOM for screen readers. */
    .data-table thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* Each row becomes a card matching the rest of the site's panels. */
    .data-table tbody tr {
        border: 1px solid var(--border);
        background: var(--bg-panel);
        backdrop-filter: blur(var(--panel-blur));
        -webkit-backdrop-filter: blur(var(--panel-blur));
        padding: 0.5rem 1.25rem;
    }

    .data-table tbody tr:last-child {
        margin-bottom: 0;
    }

    /* Cells stack inside the card; a light divider sits between them, none
       after the last. overflow-wrap/word-break guarantee even an unbroken
       string can't push the card wider than the screen. */
    .data-table tbody td {
        padding: 0.55rem 0;
        border-bottom: 1px solid var(--border);
        white-space: normal;
        overflow-wrap: anywhere;
        word-break: break-word;
    }

    .data-table tbody tr td:last-child {
        border-bottom: none;
    }

    /* Price/value cell: drop the right-align + nowrap so it reads as a normal
       value inside the stacked row. Keeps the accent color + weight. */
    .data-table td.data-table__value {
        text-align: left;
        white-space: normal;
    }

    /* No sticky hover-fill on the stacked cards. */
    .data-table tbody tr:hover {
        background: var(--bg-panel);
    }
}

/* Mobile drawer is fixed/overlayed.
   Do NOT lock vertical body scroll here. On some mobile browsers / scrollbar
   setups, changing body overflow while the drawer is open changes the viewport
   calculation and makes the centered content look pushed or narrower.

   Keep the same horizontal overflow behavior as the normal body state so opening
   the menu does not change layout width. */
body.menu-open {
    overflow-x: hidden;
}

/* ---- Lightbox (expanded image view) ----------------------------------- */
/* A single shared overlay (created once in JS) reused by every carousel.
   Shows the full image letterboxed (object-fit: contain) so each image keeps
   its own aspect ratio inside one uniform frame — nothing is cropped. */
.lightbox {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: clamp(1rem, 4vw, 3rem);
    background: var(--on-image-veil);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    opacity: 0;
    transition: opacity 200ms ease;
    cursor: zoom-out;
}

.lightbox.is-open {
    opacity: 1;
}

/* The image: capped to the viewport, letterboxed, never cropped or upscaled
   past its natural size beyond the frame. All expanded images share this same
   bounding frame, so they read as a consistent set. */
.lightbox__img {
    max-width: 100%;
    max-height: 82vh;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: var(--radius);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
    cursor: default;
}

.lightbox__caption {
    margin: 0;
    max-width: min(100%, 48rem);
    text-align: center;
    color: var(--carousel-chip-text);
    font-size: 0.95rem;
    line-height: 1.4;
    cursor: default;
}

.lightbox__close {
    position: absolute;
    top: clamp(0.75rem, 3vw, 1.5rem);
    right: clamp(0.75rem, 3vw, 1.5rem);
    width: 2.75rem;
    height: 2.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--on-image-hairline);
    border-radius: 50%;
    background: var(--on-image-btn-bg);
    color: var(--on-image-text);
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
    transition: border-color var(--transition), background var(--transition), transform var(--transition);
}

.lightbox__close:hover {
    border-color: var(--on-image-text);
    background: var(--on-image-btn-bg-hover);
}

.lightbox__close:active {
    border-color: var(--on-image-text);
}

.lightbox__close:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
}

/* Prev / next arrows in the expanded view, vertically centered on each edge.
   Hidden by JS when the carousel has only one image. */
.lightbox__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 3rem;
    height: 3rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--on-image-hairline);
    border-radius: 50%;
    background: var(--on-image-btn-bg);
    color: var(--on-image-text);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    transition: border-color var(--transition), background var(--transition), transform var(--transition);
}

.lightbox__nav--prev {
    left: clamp(0.5rem, 3vw, 1.5rem);
}

.lightbox__nav--next {
    right: clamp(0.5rem, 3vw, 1.5rem);
}

.lightbox__nav:hover {
    border-color: var(--on-image-text);
    background: var(--on-image-btn-bg-hover);
}

.lightbox__nav:active {
    border-color: var(--on-image-text);
}

.lightbox__nav:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
}

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

/* Lock page scroll behind the open lightbox. */
body.lightbox-open {
    overflow: hidden;
}

/* While fading out (is-open removed but not yet hidden), let the cursor and
   clicks fall through immediately instead of being held by the zoom-out
   overlay during the 200ms fade. */
.lightbox:not(.is-open) {
    pointer-events: none;
}

/* ----------------------------------------------------------------------
   ERROR STATE — shown when site-spec.json can't be loaded/parsed and there
   is no inline content to fall back to. Append to styles.css. Uses the same
   theme tokens as the rest of the site, so it re-themes for free.
---------------------------------------------------------------------- */

.error-state {
    /* Roomy, centered card so the message reads as intentional, not broken. */
    padding: 2.5rem 1.75rem;
    border: 1px solid var(--border, rgba(127, 127, 127, 0.25));
    border-radius: 14px;
    background: var(--bg-panel);
    text-align: center;
}

.error-state__title {
    margin: 0 0 0.75rem;
    font-size: clamp(1.4rem, 4vw, 2rem);
    line-height: 1.2;
}

.error-state__lead {
    margin: 0 auto 1.5rem;
    max-width: 38rem;
    color: var(--text-soft);
    line-height: 1.6;
}

/* Reuse the header .socials layout but center it here. */
.error-state .socials {
    justify-content: center;
    margin: 0 auto 1.5rem;
}

.error-state__details {
    margin-top: 1.5rem;
    text-align: left;
    max-width: 38rem;
    margin-left: auto;
    margin-right: auto;
    font-size: 0.875rem;
    color: var(--text-soft);
}

.error-state__details summary {
    cursor: pointer;
    user-select: none;
    opacity: 0.8;
}

.error-state__details summary:hover {
    opacity: 1;
}

.error-state__details p {
    margin: 0.5rem 0 0;
    padding: 0.75rem;
    border-radius: 8px;
    background: var(--bg-base, rgba(127, 127, 127, 0.1));
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    word-break: break-word;
}

/*
  Built from m-remis/static-web-template
  https://github.com/m-remis/static-web-template
*/