/* Layer order declared for future organization — deliberately NOT used
   to wrap the existing ~600 lines below yet. @layer priority is fixed
   by this declaration order alone, independent of selector specificity
   or source position: moving this file's many already-order-dependent
   overrides (.is-editor-preview's hero resets, the WCAG focus-visible
   floor meant to be beaten by more specific :focus rules, .field-error's
   !important, several max-width media-query fixups) into named layers
   without re-verifying every one of those interactions individually
   would risk silently breaking things that were fixed carefully, one
   real bug at a time, over many separate passes on this file. New rules
   can start adopting @layer now; migrating the rest is a dedicated,
   separately-verified pass, not a mechanical find-and-wrap. */
@layer reset, theme, layout, components, utilities, motion;

*{box-sizing:border-box;margin:0;padding:0;}
/* WCAG 2.4.7 (visible focus) baseline for every interactive element —
   :focus-visible, not :focus, so a mouse click doesn't show it, only
   real keyboard navigation does. Placed before every more specific
   :focus rule below (.block-contact input:focus, .editable-field:focus,
   etc.) so those can still override it deliberately where they already
   have their own visible focus treatment; this is just the floor for
   everything else. */
a:focus-visible,button:focus-visible,input:focus-visible,textarea:focus-visible,
select:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
/* Skip-navigation link (WCAG 2.4.1) — off-screen until it receives
   keyboard focus (the very first Tab stop on the page, see site_builder's
   body markup), then pinned to the top-left so a keyboard/screen-reader
   user can jump straight to <main> without tabbing through the entire
   nav on every single page. */
.skip-link{position:absolute;top:-3rem;left:0.5rem;z-index:1000;
  background:var(--accent);color:#fff;padding:0.6rem 1.1rem;border-radius:6px;
  text-decoration:none;font-weight:600;transition:top 0.15s ease;}
.skip-link:focus{top:0.5rem;}
html{scroll-behavior:smooth;}
body{font-family:var(--font-body);background:var(--paper);color:var(--ink);
  line-height:1.65;-webkit-font-smoothing:antialiased;}
h1,h2,h3{font-family:var(--font-display);letter-spacing:-0.01em;}
.page-nav{display:flex;justify-content:space-between;align-items:center;
  padding:1.1rem 2rem;border-bottom:1px solid var(--line);position:sticky;top:0;
  backdrop-filter:blur(10px);
  background-color:color-mix(in srgb, var(--paper) 88%, transparent);
  z-index:10;flex-wrap:wrap;gap:0.5rem;}
.page-nav-links{display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap;}
.page-nav a{color:var(--ink);text-decoration:none;font-size:0.88rem;
  font-weight:500;transition:color .15s;}
.page-nav a:hover{color:var(--accent);}
.page-nav .brand{font-family:var(--font-display);font-weight:700;font-size:1.2rem;}
/* Hamburger toggle (see nav-toggle.js) — hidden on desktop by default,
   shown only under the mobile breakpoint below, where 4-5 nav links
   stopped fitting a sticky header cleanly even with flex-wrap. */
.nav-toggle{display:none;background:none;border:none;cursor:pointer;
  flex-direction:column;gap:4px;padding:0.5rem;z-index:11;}
.nav-toggle span{display:block;width:24px;height:2px;background:var(--ink);
  border-radius:1px;transition:transform .2s,opacity .2s;}
.nav-toggle.is-open span:nth-child(1){transform:translateY(6px) rotate(45deg);}
.nav-toggle.is-open span:nth-child(2){opacity:0;}
.nav-toggle.is-open span:nth-child(3){transform:translateY(-6px) rotate(-45deg);}

/* Sections alternate their ground so a page reads as composed bands
   rather than one flat sheet with headings on it.

   This selector used to be `body>section`, and matched nothing on any
   site ever generated: every block is rendered inside
   `<div class="page-content"><main id="main-content">` (see
   render_site's shell), so no section has ever been a child of <body>.
   Every Standard and Pro site has been one uniform colour from the hero
   to the footer since the rule was written.

   Counted among sections only, so the hero is always odd and keeps its
   own gradient, and the <footer> keeps its dark band. No
   :not(.block-cta-band) exclusion is needed — that band's dark
   treatment lives on an inner .cta-band-card div (see render_cta_band),
   not on the <section>. */
main>section.block:nth-of-type(even){background:var(--surface);}
/* Pro wraps every block in its own reveal div (see site_builder's
   is_pro_theme branch), so on a Pro site the section is a grandchild of
   <main> and the rule above cannot see it. */
main>.animate-on-scroll:nth-of-type(even)>section.block{background:var(--surface);}

/* Fluid, not a fixed narrow column — and NOT plain max-width+margin:auto
   either, since that would shrink the <section> element itself, taking
   any section background (the alternating even-section tint below, the
   footer's dark band) down to that same narrow width instead of the
   full-bleed color band it's supposed to be. padding-inline instead:
   the section box stays 100% wide (background reaches both edges), and
   its CONTENT is inset by whichever is bigger — a normal fluid gutter,
   or enough to keep content capped at --content-max (1600px, only
   relevant on genuinely ultra-wide monitors) once the viewport exceeds
   that. Vertical padding keeps each theme's existing space_scale
   multiplier (see THEME_PRESETS) but adds a vw component so it actually
   grows with viewport, not just root font-size. */
.block{width:100%;
  padding-block:calc((3rem + 2vw) * var(--space-scale));
  padding-inline:max(clamp(1rem,5vw,3rem), calc((100% - var(--content-max)) / 2));}
.block h2{font-size:var(--text-xl);margin-bottom:0.6rem;font-weight:700;}
.section-intro{color:var(--muted);margin-bottom:2rem;max-width:640px;}
/* .page-title: the real <h1> for inner pages (Menu/Gallery/About/
   Contact) that used to render as <h2> with no <h1> anywhere on the
   page — see render_site's is_page_title branching. */
.page-title{font-size:var(--text-2xl);font-weight:700;margin-bottom:0.3rem;color:var(--ink);}
.page-subtitle{font-size:1.1rem;color:var(--muted);margin-bottom:2rem;}

.block-hero{text-align:center;min-height:100vh;min-height:100dvh;display:flex;
  flex-direction:column;justify-content:center;width:100%;
  padding-block:calc((3rem + 2vw) * var(--space-scale));
  padding-inline:max(clamp(1rem,5vw,3rem), calc((100% - var(--content-max)) / 2));
  background:linear-gradient(160deg, var(--paper) 0%, var(--surface) 100%);
  position:relative;overflow:hidden;}
.block-hero::before{content:"";position:absolute;top:-30%;right:-10%;width:60%;height:160%;
  background:radial-gradient(circle, color-mix(in srgb, var(--accent2) 18%, transparent) 0%, transparent 70%);
  pointer-events:none;}
.block-hero h1{font-size:var(--text-3xl);font-weight:700;margin-bottom:1.2rem;max-width:820px;
  margin-left:auto;margin-right:auto;position:relative;line-height:1.1;letter-spacing:-0.02em;}
.subheadline{color:var(--muted);max-width:600px;margin:0 auto 1.85rem;font-size:1.1rem;position:relative;}
.rating-note{display:inline-block;color:var(--accent);font-weight:600;font-size:0.85rem;
  margin-bottom:1rem;padding:0.3rem 0.9rem;border:1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius:999px;position:relative;}
.cta-group{display:flex;gap:1rem;justify-content:center;flex-wrap:wrap;position:relative;}
.btn-primary,.btn-secondary{display:inline-block;padding:0.85rem 1.75rem;border-radius:var(--radius);
  text-decoration:none;font-weight:600;font-size:0.95rem;transition:transform .15s,box-shadow .15s;
  cursor:pointer;}
.btn-primary{background:var(--accent);color:#fff;
  box-shadow:0 4px 14px rgba(var(--shadow-rgb),0.28);}
.btn-secondary{background:transparent;color:var(--accent);border:1.5px solid var(--accent);}
.btn-secondary:hover{background:color-mix(in srgb, var(--accent) 8%, transparent);}

.hero-stats{display:flex;gap:2.5rem;justify-content:center;flex-wrap:wrap;
  margin-top:2.5rem;position:relative;}
.hero-stat{display:flex;flex-direction:column;align-items:center;}
.hero-stat-number{font-family:var(--font-display);font-size:1.8rem;font-weight:700;color:var(--ink);}
.hero-stat-label{font-size:0.78rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;}

.block-hero-split{display:grid;grid-template-columns:1fr 1fr;gap:3.5rem;align-items:center;
  align-content:center;text-align:left;min-height:100vh;min-height:100dvh;width:100%;
  padding-block:calc((3rem + 2vw) * var(--space-scale));
  padding-inline:max(clamp(1rem,5vw,3rem), calc((100% - var(--content-max)) / 2));}
  /* Restates the same padding-inline inset as .block (this section also
     carries the .block-hero class, whose own padding-block/inline this
     overrides via normal cascade order) — matching the SAME
     --content-max variable every other section uses is what keeps the
     page's content column from wobbling left/right as you scroll,
     instead of each block type silently picking its own number. */
.block-hero-split .hero-text{position:relative;}
.block-hero-split .subheadline{margin-left:0;margin-right:0;}
.block-hero-split .cta-group,.block-hero-split .hero-stats{justify-content:flex-start;}
.block-hero-split h1{margin-left:0;margin-right:0;}
.hero-image{position:relative;}
.hero-image img{width:100%;height:460px;object-fit:cover;border-radius:calc(var(--radius) + 8px);
  box-shadow:0 20px 50px rgba(var(--shadow-rgb),0.25);}

.hero-photo-control{position:absolute;top:0.85rem;right:0.85rem;z-index:5;display:flex;
  align-items:center;gap:0.4rem;cursor:pointer;background:rgba(0,0,0,0.55);color:#fff;
  border:1.5px solid rgba(255,255,255,0.8);border-radius:999px;padding:0.55rem;
  font-size:1rem;line-height:1;backdrop-filter:blur(4px);transition:transform .15s,background .15s;}
.hero-photo-control:hover{transform:scale(1.08);background:rgba(0,0,0,0.75);}
.hero-photo-control-empty{position:static;margin:1.5rem auto 0;padding:0.6rem 1.1rem;
  font-size:0.85rem;font-weight:600;background:color-mix(in srgb, var(--accent) 10%, transparent);
  color:var(--accent);border:1.5px dashed color-mix(in srgb, var(--accent) 45%, transparent);}
.hero-photo-control-empty:hover{background:color-mix(in srgb, var(--accent) 16%, transparent);}

/* max-width:1400px (not none) is deliberate, even on an inset "framed"
   hero: object-fit:cover's crop severity on a portrait-shaped uploaded
   photo scales directly with how much WIDER than tall this section is
   — on an unbounded-width ultra-wide monitor that ratio (and the
   resulting crop) becomes extreme (a portrait photo reduced to a thin,
   heavily zoomed-in sliver — the "giant lips" bug). Capping width
   bounds the worst case regardless of viewport size. */
/* height:clamp (not min-height:100vh) on purpose — a true full-screen
   hero read as overwhelming on a real page: the photo dominated the
   entire first viewport with almost no visual cue that content
   continued below, and a heavy 0.1->0.75 scrim darkened most of the
   photo just to keep text legible. clamp(520px,75vh,780px) keeps the
   hero substantial without claiming the whole screen — small screens
   get a sane floor, normal screens see ~75% (enough of the next
   section peeking in to invite scrolling), huge monitors get a ceiling
   so the photo never stretches absurdly tall. min-height:auto here is
   load-bearing, not decorative: this section ALSO carries the plain
   .block-hero class (min-height:100vh/100dvh, further up), which is a
   separate property from height and otherwise wins as a floor — height
   alone here silently did nothing, the hero still rendered at a full
   100vh/dvh regardless of the clamp (verified: min-height always wins
   over a smaller height per spec). See is-editor-preview's override
   below — height:auto there for the same reason min-height was
   overridden (100vh style units are relative to the auto-resizing
   editor iframe's OWN height, which would otherwise feed back into
   itself the same way min-height:100vh used to). */
.block-hero-fullbleed{position:relative;min-height:auto;height:clamp(520px,75vh,780px);display:flex;align-items:flex-end;
  padding:calc((3rem + 2vw) * var(--space-scale)) clamp(1rem,5vw,3rem);text-align:left;
  margin:1.5rem auto;width:calc(100% - 3rem);max-width:1400px;border-radius:calc(var(--radius) + 12px);
  overflow:hidden;box-shadow:0 20px 60px rgba(var(--shadow-rgb),0.15);}
.hero-fullbleed-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:center 30%;z-index:0;}
.hero-fullbleed-scrim{position:absolute;inset:0;z-index:1;
  background:linear-gradient(180deg, rgba(0,0,0,0.25) 0%, rgba(0,0,0,0.15) 40%, rgba(0,0,0,0.45) 100%);}
.block-hero-fullbleed .hero-text{position:relative;z-index:2;max-width:700px;}
.block-hero-fullbleed h1,.block-hero-fullbleed .subheadline,.block-hero-fullbleed .rating-note{color:#fff;}
.block-hero-fullbleed h1{margin-left:0;text-shadow:0 2px 20px rgba(0,0,0,0.35), 0 1px 3px rgba(0,0,0,0.4);}
.block-hero-fullbleed .subheadline{margin-left:0;color:rgba(255,255,255,0.9);text-shadow:0 1px 10px rgba(0,0,0,0.3);}
.block-hero-fullbleed .cta-group,.block-hero-fullbleed .hero-stats{justify-content:flex-start;}
.block-hero-fullbleed .hero-stat-number{color:#fff;}
.block-hero-fullbleed .hero-stat-label{color:rgba(255,255,255,0.7);}
.block-hero-fullbleed .btn-secondary{border-color:#fff;color:#fff;}
.block-hero-fullbleed .btn-secondary:hover{background:rgba(255,255,255,0.15);}

/* Cards begin where their heading begins. Centring a short row put
   three cards in the middle of a band under a heading pinned to the
   left edge, and put two grids on the same page on two different axes —
   the single commonest reason a generated page reads as "everything
   centered". The catalog grid had already been corrected on its own;
   this is the same correction for every grid. */
.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,290px));gap:1.5rem;
  justify-content:start;}

/* ── Sparse sections compose, they do not sit in a grid with holes ──
   A grid sized for three or four things puts one card in the middle of
   an otherwise empty row, under a heading pinned to the left edge, and
   the page reads as unfinished rather than as a business with one
   offering. A business with one service is the ordinary case, not the
   degenerate one, so it gets a composition of its own: the card takes a
   real measure, its type grows, and it begins where its heading begins.
   Two things fill the width as a pair. Three or more keep the grid. */
.card-grid:has(> :last-child:nth-child(1)){grid-template-columns:minmax(0,min(100%,42rem));
  justify-content:start;}
.card-grid:has(> :last-child:nth-child(2)){grid-template-columns:repeat(2,minmax(0,min(100%,29rem)));
  justify-content:start;}
.card-grid:has(> :last-child:nth-child(1)) .card{padding:var(--space-6, 2rem);}
.card-grid:has(> :last-child:nth-child(1)) .card h3{font-size:1.45rem;}
/* And the section around it closes the row. A single card left-aligned
   under a heading still leaves half the band empty; putting the heading
   and its intro in a column beside the card makes one composition out
   of the two, which is what a designer would have drawn for a business
   with one thing to offer. */
@media(min-width:960px){
  section.block:has(>.card-grid>:last-child:nth-child(1)){
    display:grid;grid-template-columns:minmax(0,4fr) minmax(0,7fr);
    column-gap:clamp(2rem,5vw,5rem);align-items:start;}
  section.block:has(>.card-grid>:last-child:nth-child(1))>h2{
    grid-column:1;align-self:start;}
  section.block:has(>.card-grid>:last-child:nth-child(1))>.section-intro{
    grid-column:1;margin-bottom:0;}
  section.block:has(>.card-grid>:last-child:nth-child(1))>.card-grid{
    grid-column:2;grid-row:1 / -1;}
}

/* A lone card with a photograph reads as a poster, not a tile: the
   picture takes the left half and the words sit beside it. */
@media(min-width:760px){
  .card-grid:has(> :last-child:nth-child(1)) .card:has(.card-photo){
    display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:2rem;align-items:center;}
  .card-grid:has(> :last-child:nth-child(1)) .card:has(.card-photo) .card-photo{
    margin:0;height:100%;}
  .card-grid:has(> :last-child:nth-child(1)) .card:has(.card-photo) .card-photo img{
    height:100%;min-height:14rem;object-fit:cover;}
}
/* Nested — same selectors/specificity as before (.card:hover, .card h3,
   .card p, .card-icon svg), just grouped so the component reads as one
   unit instead of four separate flat rules. */
.card{
  display:flex;flex-direction:column;background:var(--paper);border:1px solid var(--line);
  border-radius:var(--radius);padding:var(--space-5);box-shadow:0 2px 10px rgba(var(--shadow-rgb),0.06);
  transition:transform .15s,box-shadow .15s;
  & h3{margin-bottom:0.5rem;font-size:1.1rem;font-weight:700;}
  & p{color:var(--muted);font-size:0.92rem;flex:1;}
}
.card-icon{
  width:2rem;height:2rem;color:var(--accent);margin-bottom:0.75rem;display:block;
  & svg{width:100%;height:100%;}
}

.illustrative-warning{background:#fff3e0;border:1px solid #ffcc80;border-radius:var(--radius);
  padding:0.75rem 1rem;margin-bottom:1.5rem;font-size:0.88rem;color:#5c3d00;}

.pricing-card{display:flex;flex-direction:column;background:var(--paper);border:1px solid var(--line);
  border-radius:var(--radius);padding:1.75rem;position:relative;
  box-shadow:0 2px 10px rgba(var(--shadow-rgb),0.06);transition:transform .15s,box-shadow .15s;}
.pricing-card.featured{border-color:var(--accent);border-width:2px;}
.pricing-card .badge{position:absolute;top:-0.8rem;right:1.25rem;background:var(--accent);
  color:#fff;font-size:0.7rem;font-weight:600;padding:0.3rem 0.75rem;border-radius:999px;}
.pricing-card h3{margin-bottom:0.3rem;font-size:1.1rem;font-weight:700;}
.pricing-card .price{font-family:var(--font-display);font-size:1.9rem;font-weight:700;
  margin:0.5rem 0;color:var(--accent);}
.pricing-card ul{list-style:none;padding-left:0;flex:1;}
.pricing-card li{padding:0.3rem 0;color:var(--muted);font-size:0.92rem;}

.menu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(270px,350px));gap:2.25rem;
  justify-content:center;}
.menu-category{transition:transform .15s,box-shadow .15s;border-radius:var(--radius);}
.menu-category h3{font-size:1.15rem;font-weight:700;margin-bottom:0.75rem;
  padding-bottom:0.5rem;border-bottom:2px solid var(--accent);display:inline-block;}
.menu-item{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;
  padding:0.65rem 0;border-bottom:1px solid var(--line);}
.menu-item-name{font-weight:600;}
.menu-item-price{color:var(--accent);font-weight:600;font-size:0.92rem;}
.menu-item-desc{width:100%;color:var(--muted);font-size:0.85rem;margin-top:0.2rem;}

.before-after-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,290px));
  gap:1.5rem;justify-content:center;}
.before-after-card{background:var(--paper);border-radius:var(--radius);overflow:hidden;
  box-shadow:0 2px 10px rgba(var(--shadow-rgb),0.06);}
.before-after-images{display:grid;grid-template-columns:1fr 1fr;gap:2px;}
.before-after-images img{width:100%;height:180px;object-fit:cover;}
.ba-side{position:relative;}
/* Masonry via CSS columns, not a uniform equal-height grid: each photo
   keeps its natural aspect ratio, so the gallery's row heights vary the
   way a real curated photo wall does instead of every tile being an
   identical cropped rectangle. break-inside:avoid on the wrapping <a>
   (not the <img>) is what stops a browser from splitting one photo
   across two columns. */
.photo-grid{column-count:3;column-gap:1.25rem;}
.photo-grid a,.photo-grid>.photo-grid-item{display:block;break-inside:avoid;margin-bottom:1.25rem;}
/* Edit mode wraps each thumbnail in .photo-grid-item (position:relative,
   so the replace-photo camera overlay has something to anchor to) — the
   <a> inside it drops the margin/break rules above so they aren't
   doubled up on both the wrapper and its child. */
.photo-grid-item{position:relative;}
.photo-grid-item a{margin-bottom:0;}
.photo-grid img{width:100%;height:auto;display:block;border-radius:var(--radius);
  box-shadow:0 2px 10px rgba(var(--shadow-rgb),0.08);transition:transform .2s;
  background:var(--surface);}
.photo-grid img:hover{transform:scale(1.02);}
.photo-replace-control{position:absolute;top:0.5rem;right:0.5rem;z-index:5;cursor:pointer;
  display:flex;align-items:center;justify-content:center;width:1.9rem;height:1.9rem;
  background:rgba(0,0,0,0.55);color:#fff;border:1.5px solid rgba(255,255,255,0.8);
  border-radius:999px;font-size:0.85rem;backdrop-filter:blur(4px);transition:transform .15s,background .15s;}
.photo-replace-control:hover{transform:scale(1.08);background:rgba(0,0,0,0.75);}
@media (max-width:900px){.photo-grid{column-count:2;}}
@media (max-width:560px){.photo-grid{column-count:1;}}
/* The "+" upload tile lives OUTSIDE .photo-grid now (see
   render_photo_gallery) — mixed into the masonry columns it was a
   fixed-180px item among variable-height real photos, and CSS multi-
   column layout balances by column HEIGHT, not DOM order, so it kept
   landing clipped/stranded in whichever column filled up first. */
.photo-grid-upload-row{margin-top:1.25rem;}
.photo-grid-upload-row .photo-upload-tile{width:180px;}
.ba-label{display:block;font-size:0.72rem;text-transform:uppercase;letter-spacing:0.05em;
  color:var(--muted);padding:0.4rem 0.5rem 0;}
.ba-caption{padding:0.6rem 0.9rem 0.9rem;color:var(--muted);font-size:0.85rem;}

/* Draggable before/after reveal slider (ba-slider.js) — for a COMPLETE
   pair only; an incomplete pair still uses .before-after-images/.ba-side
   above (nothing to compare with one photo). */
.ba-slider{position:relative;width:100%;height:220px;overflow:hidden;border-radius:var(--radius);cursor:ew-resize;}
.ba-slider img{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;display:block;}
.ba-after{z-index:1;}
.ba-before-wrapper{position:absolute;top:0;left:0;height:100%;overflow:hidden;z-index:2;}
.ba-before{width:100%;height:100%;object-fit:cover;}
.ba-handle{position:absolute;top:0;bottom:0;width:4px;background:#fff;z-index:3;cursor:ew-resize;
  left:50%;transform:translateX(-50%);box-shadow:0 0 10px rgba(0,0,0,0.3);}
.ba-handle-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:36px;height:36px;
  background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-size:0.75rem;color:#333;box-shadow:0 2px 8px rgba(0,0,0,0.3);}
/* Both replace-photo controls are direct children of .ba-slider so their
   position stays fixed regardless of drag position (see
   render_before_after_gallery) — .photo-replace-control's own
   z-index:5 already sits above .ba-handle's z-index:3. Only the
   "before" one needs repositioning so it doesn't sit on top of the
   "after" one's default top-right corner. */
.ba-slider .photo-replace-control[data-role="before"]{left:0.5rem;right:auto;}

.lightbox-trigger{display:block;cursor:zoom-in;}
/* lightbox.js drives open/close on public pages via inline style.display
   (see _lightbox_overlay) so Back closes the lightbox via popstate
   instead of just untargeting a URL hash. .lightbox:target stays as the
   fallback for edit mode (no lightbox.js there — is-editor-preview
   guards it out) and for a JS-disabled visitor; the trigger's real
   href="#{id}" still works as plain hash navigation in both cases.
   .lightbox-close fills the WHOLE overlay (not just a tight box around
   the image), so clicking anywhere — backdrop or photo — closes it. */
/* Nested — same selectors/specificity/order as before (.lightbox:target,
   .lightbox-close, .lightbox-close-icon, .lightbox img, .lightbox-nav*),
   just grouped under the overlay they all belong to. Prev/next (injected
   by lightbox.js, only for a group with 2+ photos) are siblings of
   .lightbox-close in the DOM, so their position:absolute layers them on
   top of its full-bleed click-anywhere-closes area without disturbing
   that element's own flex layout. */
.lightbox{
  display:none;position:fixed;inset:0;z-index:200;background:rgba(0,0,0,0.92);
  align-items:center;justify-content:center;padding:2rem;
  &:target{display:flex;}
  & img{max-width:min(90vw,1100px);max-height:85vh;object-fit:contain;
    border-radius:var(--radius);box-shadow:0 20px 60px rgba(0,0,0,0.5);}
  & .lightbox-close{position:relative;display:flex;align-items:center;justify-content:center;
    width:100%;height:100%;padding:3rem 1.5rem;cursor:zoom-out;
    &:hover .lightbox-close-icon{opacity:1;}
  }
  & .lightbox-close-icon{position:absolute;top:1.5rem;right:1.5rem;color:#fff;font-size:2rem;
    line-height:1;opacity:0.7;transition:opacity .2s;}
  & .lightbox-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:5;
    display:flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;
    background:rgba(0,0,0,0.45);color:#fff;border:none;border-radius:50%;font-size:1.1rem;
    cursor:pointer;transition:background .15s;
    &:hover{background:rgba(0,0,0,0.7);}
  }
  & .lightbox-nav-prev{left:1rem;}
  & .lightbox-nav-next{right:1rem;}
}

.eyebrow{font-family:var(--font-body);font-size:0.78rem;font-weight:700;text-transform:uppercase;
  letter-spacing:0.1em;color:var(--accent);margin-bottom:0.75rem;}
.block-about{display:grid;grid-template-columns:1.1fr 0.9fr;gap:3.5rem;align-items:center;}
  /* max-width intentionally left unset — inherits .block's 1000px, same
     reasoning as block-hero-split above (was 1100px, one more bespoke
     number contributing to the wobbling-edge bug). */
.block-about .about-text h2{margin-bottom:1rem;}
.block-about .about-text p:not(.eyebrow){color:var(--muted);font-size:1.02rem;line-height:1.75;}
.about-panel{position:relative;aspect-ratio:4/5;border-radius:calc(var(--radius) + 8px);overflow:hidden;
  background:linear-gradient(150deg, var(--accent) 0%, var(--accent2) 100%);
  box-shadow:0 20px 50px rgba(var(--shadow-rgb),0.2);}
.about-panel-image{width:100%;height:100%;object-fit:cover;display:block;background:var(--surface);}

.block-highlights .highlights-header{display:flex;align-items:baseline;justify-content:space-between;
  gap:1.5rem;flex-wrap:wrap;margin-bottom:2rem;}
.block-highlights .highlights-header h2{margin-bottom:0;}
.block-highlights .highlights-header .btn-secondary{flex-shrink:0;}

.testimonial-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.5rem;}
.testimonial-card{background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  padding:1.75rem;box-shadow:0 2px 10px rgba(var(--shadow-rgb),0.06);position:relative;
  transition:transform .15s,box-shadow .15s;}
.testimonial-card .quote-mark{position:absolute;top:0.75rem;right:1.25rem;font-family:var(--font-display);
  font-size:3rem;color:color-mix(in srgb, var(--accent) 25%, transparent);line-height:1;}
.testimonial-card p:not(.testimonial-author){color:var(--ink);font-size:0.95rem;line-height:1.65;
  margin-bottom:1rem;position:relative;}
.testimonial-author{color:var(--muted);font-size:0.85rem;font-weight:600;}
.testimonial-rating{display:flex;gap:0.15rem;margin-bottom:0.5rem;position:relative;}
.testimonial-rating .star{color:#f59e0b;font-size:1rem;}
.testimonial-rating .star-empty{color:var(--line);}

.faq-list{display:flex;flex-direction:column;gap:0.75rem;max-width:800px;}
.faq-item{background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  padding:1.1rem 1.4rem;}
.faq-item summary{cursor:pointer;font-weight:600;color:var(--ink);list-style:none;
  display:flex;align-items:center;justify-content:space-between;gap:1rem;}
.faq-item summary::-webkit-details-marker{display:none;}
.faq-item summary::after{content:"+";font-size:1.3rem;color:var(--accent);flex-shrink:0;
  transition:transform .15s;}
.faq-item[open] summary::after{transform:rotate(45deg);}
.faq-item p{color:var(--muted);font-size:0.95rem;line-height:1.65;margin-top:0.75rem;}

.block-cta-band{text-align:center;}
.cta-band-card{background:var(--ink);border-radius:calc(var(--radius) + 4px);
  padding:3.5rem 2rem;max-width:820px;margin:0 auto;}
.cta-band-card h2{color:var(--paper);margin-bottom:0.6rem;}
.cta-band-card p{color:color-mix(in srgb, var(--paper) 75%, transparent);margin-bottom:1.75rem;
  max-width:520px;margin-left:auto;margin-right:auto;}

.block-contact .contact-layout{display:grid;grid-template-columns:1fr 1.1fr;gap:2.5rem;align-items:start;
  margin-top:1.5rem;}
@media (max-width:760px){.block-contact .contact-layout{grid-template-columns:1fr;}}
.contact-info-row{display:flex;align-items:flex-start;gap:1rem;margin-bottom:1.5rem;}
.contact-info-icon{display:flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;
  border-radius:50%;background:color-mix(in srgb, var(--accent) 12%, transparent);color:var(--accent);
  flex-shrink:0;}
.contact-info-icon svg{width:1.25rem;height:1.25rem;}
.contact-info-label{display:block;font-size:0.85rem;font-weight:700;color:var(--ink);margin-bottom:0.15rem;}
.contact-info-row .editable-field,.contact-info-row p{color:var(--muted);font-size:0.95rem;margin:0;}
.contact-form-card{background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  padding:1.75rem;}
.block-contact form{display:flex;flex-direction:column;gap:0.9rem;}
.block-contact label{display:flex;flex-direction:column;font-size:0.85rem;gap:0.3rem;
  font-weight:500;color:var(--muted);}
.block-contact textarea,.block-contact input{border:1px solid var(--line);border-radius:var(--radius);
  padding:0.65rem 0.8rem;font-family:var(--font-body);font-size:0.95rem;resize:vertical;background:var(--surface);}
.block-contact textarea:focus,.block-contact input:focus{outline:2px solid var(--accent);outline-offset:2px;border-color:var(--accent);}
.block-contact button{margin-top:0.25rem;align-self:flex-start;}
.lead-form-status,.form-status{display:none;margin-top:1rem;font-size:0.9rem;color:var(--accent);}
.lead-form-status.is-visible,.form-status.is-visible{display:block;}
.lead-form-status.is-error,.form-status.is-error{color:#b91c1c;}
.field-error{border-color:#dc2626 !important;}
.field-error-msg{color:#dc2626;font-size:0.8rem;margin-top:0.25rem;display:none;}
.field-error-msg.is-visible{display:block;}

.booking-widget-placeholder{background:var(--surface);border:1.5px dashed var(--accent);
  border-radius:var(--radius);padding:2rem;text-align:center;color:var(--muted);font-size:0.95rem;}
.booking-widget-card{background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  padding:1.75rem;max-width:520px;margin:0 auto;}
.booking-step{display:flex;flex-direction:column;gap:0.9rem;}
.booking-step label,.booking-details-form label{display:flex;flex-direction:column;font-size:0.85rem;
  gap:0.3rem;font-weight:500;color:var(--muted);}
.booking-step select,.booking-step input,.booking-details-form input{border:1px solid var(--line);
  border-radius:var(--radius);padding:0.6rem 0.75rem;font-family:var(--font-body);font-size:0.95rem;
  background:var(--surface);}
.booking-slots{display:flex;flex-wrap:wrap;gap:0.5rem;}
.booking-slot-btn{border:1.5px solid var(--line);background:var(--paper);border-radius:var(--radius);
  padding:0.5rem 0.9rem;font-size:0.85rem;cursor:pointer;transition:border-color .15s,background .15s;}
.booking-slot-btn:hover{border-color:var(--accent);}
.booking-slot-btn.is-selected{border-color:var(--accent);background:color-mix(in srgb, var(--accent) 10%, transparent);
  color:var(--accent);font-weight:600;}
.booking-slots-message{color:var(--muted);font-size:0.85rem;}
.booking-details-form{display:flex;flex-direction:column;gap:0.9rem;margin-top:1rem;}
.booking-consent{border:0;margin:0;padding:0;display:flex;flex-direction:column;gap:0.5rem;min-width:0;}
.booking-consent legend{font-size:0.85rem;font-weight:500;color:var(--muted);padding:0;margin-bottom:0.35rem;}
.booking-details-form .booking-consent-option{flex-direction:row;align-items:flex-start;gap:0.55rem;font-weight:400;color:var(--ink,inherit);line-height:1.4;}
.booking-consent-option input{width:1.1rem;height:1.1rem;margin:0.1rem 0 0;flex:none;padding:0;accent-color:var(--accent);}
.booking-consent-note{margin:0;font-size:0.78rem;color:var(--muted);}
.booking-status{margin-top:1rem;font-size:0.9rem;color:var(--accent);}
/* Nothing is reserved for times until the visitor asks for them: the
   message and the slots appear with "Loading…", the answer, or the
   no-availability line booking-widget.js writes. */
.booking-slots-message:empty,.booking-slots:empty{display:none;}
.booking-status:empty{margin:0;}

.block-footer{text-align:center;padding:2.5rem 2rem;color:var(--muted);font-size:0.85rem;
  border-top:1px solid var(--line);background:var(--ink);}
.block-footer .footer-business,.block-footer .footer-tagline,.block-footer .footer-disclaimer,.block-footer .footer-legal{
  color:color-mix(in srgb, var(--paper) 70%, transparent);}
.block-footer .footer-legal a{color:inherit;text-decoration:underline;font-size:0.8rem;}
.footer-business{font-family:var(--font-display);font-weight:700;font-size:1.1rem;
  color:var(--paper) !important;}
.footer-social{display:flex;justify-content:center;gap:1.25rem;margin:0.9rem 0;font-size:0.8rem;
  flex-wrap:wrap;}
.footer-social a{color:color-mix(in srgb, var(--paper) 80%, transparent);text-decoration:underline;}
.footer-social a:hover{color:var(--accent);}
.footer-social-item{color:color-mix(in srgb, var(--paper) 70%, transparent);}
.footer-social-item .editable-field{color:color-mix(in srgb, var(--paper) 90%, transparent) !important;}
.footer-contact{display:flex;justify-content:center;align-items:center;flex-wrap:wrap;gap:0.5rem;
  margin:0.9rem 0;font-size:0.8rem;color:color-mix(in srgb, var(--paper) 80%, transparent);}
.footer-contact a{color:color-mix(in srgb, var(--paper) 90%, transparent);text-decoration:underline;}
.footer-contact a:hover{color:var(--accent);}
.theme-toggle{margin-top:1rem;background:none;cursor:pointer;
  border:1px solid color-mix(in srgb, var(--paper) 30%, transparent);border-radius:999px;
  padding:0.4rem 0.7rem;font-size:1rem;line-height:1;}
.theme-toggle-icon-dark{display:none;}
:root[data-theme="dark"] .theme-toggle-icon-light{display:none;}
:root[data-theme="dark"] .theme-toggle-icon-dark{display:inline;}
.footer-newsletter{margin:1rem 0;}
.footer-newsletter-label{color:color-mix(in srgb, var(--paper) 80%, transparent);
  font-size:0.8rem;margin-bottom:0.5rem;}
.footer-newsletter-form{display:flex;justify-content:center;gap:0.5rem;flex-wrap:wrap;}
.footer-newsletter-form input{padding:0.6rem 0.9rem;border-radius:var(--radius);
  border:1px solid color-mix(in srgb, var(--paper) 30%, transparent);
  background:color-mix(in srgb, var(--paper) 10%, transparent);color:var(--paper);
  font-size:0.85rem;min-width:200px;}
.footer-newsletter-form input::placeholder{color:color-mix(in srgb, var(--paper) 60%, transparent);}
.btn-small{padding:0.6rem 1.1rem;font-size:0.85rem;}

.editable-field{outline:1px dashed transparent;border-radius:3px;cursor:text;transition:outline-color .15s;
  display:inline-block;min-width:1.5em;min-height:1.2em;}
.editable-field:hover{outline-color:var(--accent);}
.editable-field:focus{outline:2px solid var(--accent);background:rgba(var(--shadow-rgb),0.05);}
.editable-field:empty{outline-color:color-mix(in srgb, var(--accent) 40%, transparent);}
.editable-field:empty::before{content:attr(data-placeholder);color:var(--muted);opacity:0.65;}
.rich-toolbar{position:fixed;z-index:1000;display:flex;gap:2px;background:#18181b;
  border-radius:6px;padding:3px;box-shadow:0 8px 20px rgba(0,0,0,0.35);}
.rich-toolbar button{width:26px;height:26px;border:none;border-radius:4px;background:none;
  color:#e4e4e7;font-family:Georgia,serif;font-size:14px;cursor:pointer;line-height:1;}
.rich-toolbar button:hover{background:#3f3f46;}
.rich-toolbar button.is-active{background:#4f46e5;color:#fff;}
.rich-toolbar button:first-child{font-weight:700;}
.rich-toolbar button:last-child{font-style:italic;}
.edit-toggle{display:inline-block;margin:0.5rem 0;padding:0.35rem 0.8rem;border-radius:var(--radius);
  border:1px dashed var(--muted);background:none;font-size:0.8rem;cursor:pointer;color:var(--muted);}
.edit-toggle.is-on{border-color:var(--accent);color:var(--accent);border-style:solid;}
.edit-add-btn{display:inline-block;margin-top:0.6rem;padding:0.35rem 0.8rem;border-radius:var(--radius);
  border:1px dashed var(--accent);background:none;font-size:0.8rem;cursor:pointer;color:var(--accent);}
.edit-remove-btn{border:none;background:none;color:#b23b3b;cursor:pointer;font-size:1rem;
  line-height:1;margin-left:0.5rem;padding:0 0.25rem;}
.menu-category h3{display:flex;align-items:center;justify-content:space-between;}
.photo-upload-tile{display:flex;align-items:center;justify-content:center;height:180px;
  border:2px dashed var(--accent);border-radius:var(--radius);cursor:pointer;
  color:var(--accent);font-size:2rem;font-weight:300;transition:background .15s;}
.photo-upload-tile:hover{background:color-mix(in srgb, var(--accent) 6%, transparent);}
.before-after-add .before-after-images{grid-template-columns:1fr 1fr;}
.before-after-add .photo-upload-tile{height:180px;font-size:1.5rem;}

/* One photo per services item / menu category (see render_services'
   render_item and render_menu's render_category) — same
   .photo-replace-control/.photo-upload-tile overlay pattern as the
   gallery/hero photos, just in a smaller card-sized box. */
.card-photo,.menu-category-photo{position:relative;border-radius:var(--radius);overflow:hidden;
  margin-bottom:0.75rem;}
.card-photo img,.menu-category-photo img{width:100%;height:110px;object-fit:cover;display:block;}
.card-photo .photo-upload-tile,.menu-category-photo .photo-upload-tile{height:110px;font-size:1.5rem;}

/* A single restrained entrance moment on load — opacity+transform only
   (never width/height, which triggers layout thrashing), capped at the
   first three sections since anything further down is already in its
   settled state by the time a visitor scrolls there. Respects
   prefers-reduced-motion by only existing inside the no-preference
   query at all — the deliberate choice to have ONE quiet moment rather
   than scattering motion everywhere is what keeps this from reading as
   an AI-generated flourish. */
@media(prefers-reduced-motion:no-preference){
  body>section:nth-of-type(-n+3){animation:section-in .7s cubic-bezier(.16,1,.3,1) both;}
  body>section:nth-of-type(2){animation-delay:.08s;}
  body>section:nth-of-type(3){animation-delay:.16s;}
}
@keyframes section-in{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:translateY(0);}}

/* Skeleton shimmer while an image loads — background paints BEHIND the
   <img>'s own content, so once the browser has something real to show
   there, the shimmer is simply painted over and invisible; no JS-driven
   "loaded" class needed to hide it explicitly. (A stop-the-animation
   rule keyed on img[src]:not([src=""]) would fire the instant the tag
   exists in markup — every real photo already has a non-empty src from
   first paint — which would kill the shimmer immediately instead of
   once the image actually finishes loading, so that part's skipped.)
   Wrapped in prefers-reduced-motion:no-preference, same as section-in
   above — reduced-motion still gets the plain --surface background,
   just without the animated sweep. */
@media(prefers-reduced-motion:no-preference){
  .hero-image img,.hero-fullbleed-bg,.about-panel-image,.photo-grid img,.before-after-images img{
    background:linear-gradient(90deg,var(--surface) 25%,var(--line) 50%,var(--surface) 75%);
    background-size:200% 100%;animation:skeleton-shimmer 1.5s ease-in-out infinite;}
}
@keyframes skeleton-shimmer{0%{background-position:200% 0;}100%{background-position:-200% 0;}}

@media(max-width:900px){
  .block-hero-split,.block-about{grid-template-columns:1fr;text-align:center;}
  .block-hero-split{text-align:left;}
  .block-hero-split .hero-image{order:-1;}
  .hero-image img{height:280px;}
  .about-panel{aspect-ratio:16/9;order:-1;}
}

@media(max-width:640px){
  /* Section padding and hero heading size are fluid via clamp()/vw now
     (see .block/.block-hero* above) — no fixed-px overrides needed here
     anymore, they'd just fight the clamp values. Hero min-height stays
     100dvh at every width on purpose (spec: hero is always full-screen). */
  .page-nav-links{gap:1rem;}
  .page-nav a{font-size:0.82rem;}
  .hero-stats{gap:1.5rem;}

  /* Collapse the nav links behind the hamburger toggle instead of
     letting them flex-wrap into a second, cramped row. */
  .nav-toggle{display:flex;}
  .page-nav{position:relative;flex-wrap:nowrap;}
  .page-nav-links{display:none;position:absolute;top:100%;left:0;right:0;
    background:color-mix(in srgb, var(--paper) 95%, transparent);
    backdrop-filter:blur(10px);border-bottom:1px solid var(--line);
    flex-direction:column;padding:1rem 2rem;z-index:9;}
  .page-nav-links.is-open{display:flex;}
}

@media(min-width:1400px){
  /* One more masonry column once there's genuinely room for it — kept a
     flat 3 above so a 50-photo gallery on an ultra-wide monitor doesn't
     blow each thumbnail up to ~500px wide. */
  .photo-grid{column-count:4;}
}
.page-nav a.active{color:var(--accent);font-weight:700;}

/* Hero sections size themselves using vh/dvh-relative units (.block-hero
   and .block-hero-split's min-height:100vh; .block-hero-fullbleed's
   height:clamp(...,75vh,...)) so the hero reads as substantial on a
   real visitor's actual screen — correct on the public site, but a
   genuine bug inside the dashboard's edit-mode iframe: the parent
   auto-sizes that iframe to match this document's scrollHeight (see
   editor-bridge.js's reportHeight), and vh/dvh are relative to the
   iframe's OWN current height — growing the iframe grows what they
   mean, which grows scrollHeight again. is-editor-preview (set on
   <body> only when edit_mode=True, see render_site) removes that
   circular dependency at the source instead of just bounding it
   client-side: every hero variant sizes to its own content/padding in
   the editor, same as any other block. The real public site is
   untouched. .block-hero-fullbleed needs BOTH height and min-height
   reset — it sets height directly, not just a min-height floor. */
.is-editor-preview .block-hero,
.is-editor-preview .block-hero-split{min-height:auto;}
.is-editor-preview .block-hero-fullbleed{height:auto;min-height:auto;}

/* Mobile-only sticky call button (see render_site's fab_html) — hidden
   by default, shown only under the phone-sized breakpoint where a
   thumb-reachable call action matters most and a nav link doesn't cut
   it. body padding-bottom keeps it from covering page content. */
.mobile-fab{display:none;}
.fab-call{position:fixed;bottom:1rem;right:1rem;left:1rem;z-index:100;display:flex;
  align-items:center;justify-content:center;gap:0.5rem;padding:0.85rem 1rem;
  background:var(--accent);color:var(--paper);border-radius:999px;font-weight:700;
  font-size:0.95rem;text-decoration:none;box-shadow:0 4px 16px rgba(var(--shadow-rgb),0.25);
  cursor:pointer;}
@media (max-width:640px){
  .mobile-fab{display:block;}
  body{padding-bottom:4.5rem;}
}

/* Floating "chat with us" icon(s) — see render_site's chat_widget_html.
   Own z-index below .fab-call's 100 so a mobile visitor can still reach
   the Book/Call button above it rather than the two stacking illegibly. */
.chat-widget-stack{position:fixed;bottom:1rem;right:1rem;z-index:90;display:flex;
  flex-direction:column;gap:0.6rem;align-items:flex-end;}
.chat-widget-icon{display:flex;align-items:center;justify-content:center;width:3rem;height:3rem;
  border-radius:50%;color:#fff;box-shadow:0 4px 16px rgba(var(--shadow-rgb),0.3);
  transition:transform 0.15s ease;}
.chat-widget-icon:hover{transform:scale(1.08);}
.chat-widget-icon svg{width:1.6rem;height:1.6rem;}
.chat-widget-whatsapp{background:#25D366;}
.chat-widget-telegram{background:#229ED9;}
@media (max-width:640px){
  .chat-widget-stack{bottom:5.5rem;}
}

/* Regenerate-in-progress overlay — injected/removed by editor-bridge.js
   (never present on the public site). Its own .block ancestor gets
   position:relative set inline by that same script only when it isn't
   already positioned, so this covers exactly that one section. */
.regenerate-overlay{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;
  align-items:center;justify-content:center;text-align:center;padding:1rem;
  background:rgba(255,255,255,0.92);border-radius:var(--radius);backdrop-filter:blur(4px);}
.regenerate-overlay p{margin:0.75rem 0 0;color:var(--ink);font-size:0.95rem;font-weight:500;}
.regenerate-spinner{width:32px;height:32px;border:3px solid var(--line);border-top-color:var(--accent);
  border-radius:50%;animation:regenerate-spin 0.8s linear infinite;}
@keyframes regenerate-spin{to{transform:rotate(360deg);}}
.regenerate-error-text{color:#b91c1c;font-weight:600;}
.regenerate-retry{margin-top:0.75rem;padding:0.4rem 1rem;background:var(--accent);color:var(--paper);
  border:none;border-radius:var(--radius);font-size:0.85rem;font-weight:600;cursor:pointer;}

/* Photo-generate overlay — smaller/lighter than .regenerate-overlay
   since it sits over a single photo tile, not a whole section. Injected
   by editor-bridge.js on the tile itself (an "add" placeholder) or its
   parent (a "replace" camera icon sitting ON an existing photo — see
   photoOverlayTarget). */
.photo-generate-overlay,.photo-generate-error{position:absolute;inset:0;z-index:20;display:flex;
  flex-direction:column;align-items:center;justify-content:center;gap:0.5rem;text-align:center;
  background:rgba(255,255,255,0.9);border-radius:var(--radius);font-size:0.85rem;color:var(--muted);}
.photo-generate-error{color:#b91c1c;font-weight:600;}
.photo-generate-spinner{width:24px;height:24px;border:2px solid var(--line);border-top-color:var(--accent);
  border-radius:50%;animation:regenerate-spin 0.8s linear infinite;}

/* Staggered card entrance — same reduced-motion guard and "just one
   quiet moment" philosophy as section-in above, extended to grid items
   specifically. .pricing-card/.menu-category/.before-after-card are
   written bare (not scoped to a parent grid class) since nth-child only
   cares about position among siblings, not which grid wraps them —
   .pricing-card in particular shares the same .card-grid wrapper as
   .card (services), so scoping it to ".card-grid .pricing-card" would
   have been redundant anyway. */
@media(prefers-reduced-motion:no-preference){
  .card-grid .card,.testimonial-grid .testimonial-card,.pricing-card,.menu-category,.before-after-card{
    opacity:0;transform:translateY(12px);animation:card-in .5s cubic-bezier(.16,1,.3,1) forwards;}
  .card-grid .card:nth-child(1),.testimonial-grid .testimonial-card:nth-child(1),
  .pricing-card:nth-child(1),.menu-category:nth-child(1),.before-after-card:nth-child(1){animation-delay:.05s;}
  .card-grid .card:nth-child(2),.testimonial-grid .testimonial-card:nth-child(2),
  .pricing-card:nth-child(2),.menu-category:nth-child(2),.before-after-card:nth-child(2){animation-delay:.1s;}
  .card-grid .card:nth-child(3),.testimonial-grid .testimonial-card:nth-child(3),
  .pricing-card:nth-child(3),.menu-category:nth-child(3),.before-after-card:nth-child(3){animation-delay:.15s;}
  .card-grid .card:nth-child(4),.pricing-card:nth-child(4),
  .menu-category:nth-child(4),.before-after-card:nth-child(4){animation-delay:.2s;}
}
@keyframes card-in{to{opacity:1;transform:translateY(0);}}

/* Hover feedback — magnetic buttons, card lift, photo tint — all gated
   behind reduced-motion like the entrance animation above, per the same
   "user didn't ask for movement" reasoning, even though hover-triggered
   feedback (as opposed to autoplaying motion) is a judgment call; keeping
   every new hover effect in one guard is simpler than deciding per-effect. */
@media(prefers-reduced-motion:no-preference){
  .btn-primary:hover,.btn-secondary:hover{
    transform:translateY(-2px);box-shadow:0 8px 20px color-mix(in srgb, var(--accent) 25%, transparent);}
  .card:hover,.testimonial-card:hover,.pricing-card:hover,.menu-category:hover{
    transform:translateY(-4px);box-shadow:0 8px 24px color-mix(in srgb, var(--ink) 8%, transparent);}
  .photo-grid a::after{
    content:"";position:absolute;inset:0;border-radius:inherit;
    background:color-mix(in srgb, var(--accent) 15%, transparent);
    opacity:0;transition:opacity .2s;pointer-events:none;}
  .photo-grid a:hover::after{opacity:1;}
}

/* Press states — a quick, snappy scale-down on click/tap, distinct from
   the slower hover transitions elsewhere. */
.btn-primary:active,.btn-secondary:active,.booking-slot-btn:active,.fab-call:active{
  transform:scale(0.97);transition:transform .05s;}

/* Contained hover zoom — overflow:hidden on the WRAPPER (not the image
   itself) is what keeps a scaled-up image from visually spilling past
   its own rounded corners into surrounding content. .about-panel
   already has its own overflow:hidden + a deliberately larger radius
   (calc(var(--radius) + 8px)) than the base --radius, so it's left out
   here rather than overwritten back down to the smaller value. */
.hero-image,.photo-grid a{overflow:hidden;border-radius:var(--radius);}
.photo-grid a{position:relative;}
.hero-image img:hover,.about-panel-image:hover,.photo-grid img:hover{
  transform:scale(1.04);transition:transform .4s cubic-bezier(.16,1,.3,1);}

/* Nav link underline — grows from 0 on hover, stays full-width for the
   current page (works alongside, not instead of, the existing
   .page-nav a.active color/weight rule below). */
.page-nav a{position:relative;}
.page-nav a::after{content:'';position:absolute;bottom:-2px;left:0;width:0;height:2px;
  background:var(--accent);transition:width .25s ease;}
.page-nav a:hover::after,.page-nav a.active::after{width:100%;}

/* First real inhabitant of the @layer stack declared at the top of this
   file — safe here since .page-content/.page-transition-overlay are
   novel class names (see static/js/page-transitions.js) with nothing
   unlayered elsewhere to lose a specificity fight against. */
@layer motion{
  .page-transition-overlay{position:fixed;inset:0;background:var(--paper);z-index:9999;
    opacity:0;pointer-events:none;transition:opacity .2s ease;}
  .page-transition-overlay.is-active{opacity:1;pointer-events:auto;}
  .page-content{opacity:1;transition:opacity .2s ease;}
  .page-content.is-exiting{opacity:0;}
}

/* RTL support (see <html dir="rtl">, set by render_site from the site's
   resolved language — app.core.i18n.is_rtl). Deliberately NOT the usual
   "flex-direction: row-reverse" checklist: verified in a real browser
   that this file's actual layouts (.block-hero-split is a 2-column CSS
   grid, .footer-newsletter-form/.page-nav-links are default-row flex)
   ALREADY visually reverse under dir="rtl" with zero extra CSS — that's
   how direction/grid and flex row order work natively. Adding a manual
   row-reverse or order:-1 on top of that would double-reverse them back
   to looking like the LTR layout, which is why those aren't here. The
   rules below are for genuinely PHYSICAL absolute-position offsets
   (left/right, which direction does NOT affect on their own) that do
   need a manual flip. */
[dir="rtl"] .lightbox-close-icon{right:auto;left:1.5rem;}
[dir="rtl"] .lightbox-nav-prev{left:auto;right:1rem;}
[dir="rtl"] .lightbox-nav-next{right:auto;left:1rem;}

.cookie-banner{position:fixed;bottom:0;left:0;right:0;background:var(--surface);
  border-top:1px solid var(--line);padding:1rem 2rem;z-index:1000;
  transform:translateY(100%);transition:transform .3s ease;}
.cookie-banner.is-visible{transform:translateY(0);}
.cookie-banner-content{max-width:1200px;margin:0 auto;display:flex;align-items:center;
  gap:1.5rem;flex-wrap:wrap;justify-content:space-between;}
.cookie-banner-actions{display:flex;gap:0.75rem;align-items:center;flex-wrap:wrap;}
.cookie-banner-link{font-size:var(--text-sm);color:var(--accent);}
@media(max-width:640px){.cookie-banner-content{flex-direction:column;text-align:center;gap:0.6rem;}
  /* On a phone this sits over the page's own controls. It stays
     compliant and readable, and stops being a third of the screen. */
  .cookie-banner{padding:0.75rem 1rem;}
  .cookie-banner p{font-size:0.82rem;line-height:1.4;margin:0;}
  .cookie-banner-actions{gap:0.5rem;}}
/* Same compensation idea as .mobile-fab's body{padding-bottom:4.5rem}
   above — keeps the fixed, full-width banner from sitting on top of
   footer content (e.g. the theme-toggle button) on a page short enough
   that the footer is already near the viewport bottom. Toggled by
   cookie-consent.js alongside showing/hiding the banner itself. */
body.has-cookie-banner{padding-bottom:5rem;}

/* ─────────────────────────────────────────────────────────────────────
   Selling: the catalog grid, one item's page, and the receipt.
   Built on the card and button the rest of the site already uses — a
   salon that sells one bottle of shampoo should not suddenly have a
   supermarket on page two.
   ───────────────────────────────────────────────────────────────────── */
.catalog-card{text-decoration:none;color:inherit;}
.catalog-card h3{color:var(--ink);}
.catalog-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(var(--shadow-rgb),0.12);}
/* Cards begin where their heading begins. The shared grid centres a
   short row, which left a single sellable thing marooned in the middle
   of the page under a heading at the left edge. */
.catalog-grid{justify-content:start;}
.catalog-card-body{flex:1;display:flex;flex-direction:column;}
/* The two facts a decision is made on, on one line at the foot of the
   card, above a hairline: a price that reads as money and an action
   that reads as a control rather than as a link in a list. */
.catalog-foot{display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;justify-content:space-between;
  margin-top:1rem;padding-top:0.85rem;border-top:1px solid var(--line);}
.catalog-meta{display:flex;flex-wrap:wrap;gap:0.6rem;align-items:baseline;margin:0;flex:none;}
.catalog-price{font-weight:700;font-size:1.2rem;color:var(--ink);font-variant-numeric:tabular-nums;}
.catalog-duration{color:var(--muted);font-size:0.85rem;}
.catalog-action{font-weight:600;color:var(--accent);font-size:0.9rem;
  border:1px solid color-mix(in srgb, var(--accent) 35%, transparent);border-radius:999px;
  padding:0.35rem 0.85rem;white-space:nowrap;transition:background .15s,color .15s;}
.catalog-card:hover .catalog-action{background:var(--accent);color:#fff;}

/* A service or menu line that can actually be bought. The card keeps the
   shape it already had; the price and the one thing to do are added at
   its foot, so the section reads as one list, not two. */
.card-sellable .offer-action,.menu-item-sellable .offer-action{display:flex;flex-wrap:wrap;gap:0.6rem;
  align-items:baseline;margin:0.7rem 0 0;}
.offer-price{font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums;}
.offer-link{font-weight:600;color:var(--accent);font-size:0.92rem;text-decoration:none;}
.offer-link::after{content:" →";}
.offer-link:hover{text-decoration:underline;}
.menu-item-sellable .offer-action{margin-top:0.35rem;}

/* The editor asked to be shown a thing. A ring, briefly, on the site's
   own accent — nothing that survives publishing (the attribute it hangs
   off is only rendered in edit mode) and nothing that repaints the
   design the owner is trying to judge. */
.kd-item-focus{position:relative;border-radius:var(--radius);
  box-shadow:0 0 0 2px var(--accent),0 0 0 8px rgba(var(--shadow-rgb),0.10);
  transition:box-shadow .25s ease;}
[data-kd-item]{cursor:pointer;}

/* One item, and one decision.

   The page carries five things: whose it is, what it is, what it costs,
   what it is for, and the one control. With a photograph they share the
   width; without one the type does the work alone, in a single measured
   column — a page that left half the screen open beside a name and a
   price read as one that had failed to load. */
.block-item{padding-top:2.5rem;
  /* Room to scroll the Buy control clear of the cookie banner, which is
     fixed to the bottom of the first visit and would otherwise sit on
     top of the one thing this page exists for. */
  padding-bottom:8rem;}
.item-layout{display:grid;gap:1.75rem;align-items:start;}
@media(min-width:760px){.item-layout{grid-template-columns:minmax(0,5fr) minmax(0,6fr);gap:3.5rem;}}
@media(min-width:760px){.item-layout-solo{grid-template-columns:minmax(0,62ch);justify-content:center;}}
/* The picture is a column of the composition, not the thing that sets
   the page's height. A tall photograph used to run nine hundred pixels
   down the screen while the column beside it — the name, the price and
   the only control — ended in the first third, and the rest of the page
   was the picture and nothing else. A fixed ratio makes the two columns
   agree, whatever shape the owner uploaded. */
.item-photo img{width:100%;height:auto;border-radius:var(--radius);display:block;}
@media(min-width:760px){
  .item-photo img{aspect-ratio:4/3;object-fit:cover;max-height:min(68vh,34rem);}
  /* Reading the words does not mean losing sight of the thing. */
  .item-layout>.item-body{position:sticky;top:5.5rem;}
}
/* On a phone the photograph must not be the whole first screen: the
   name and the price belong above the fold with it, not under it. */
@media(max-width:759px){.item-photo img{max-height:34vh;object-fit:cover;}}
/* Back to where this was found. A detail page reached from a card and
   ending in a footer is a cul-de-sac; the collection it belongs to is
   the one link worth having, above the name where a breadcrumb goes. */
.item-back{display:inline-flex;align-items:center;gap:0.4rem;margin:0 0 1.1rem;
  font-size:0.9rem;font-weight:600;color:var(--muted);text-decoration:none;}
.item-back::before{content:"\2190";}
.item-back:hover{color:var(--accent);}
.item-seller{font-size:0.8rem;letter-spacing:0.07em;text-transform:uppercase;color:var(--muted);
  margin:0 0 0.5rem;}
.item-name{font-size:clamp(1.75rem,4.5vw,2.6rem);line-height:1.1;margin:0 0 0.6rem;letter-spacing:-0.01em;}
.item-price{display:flex;gap:0.9rem;align-items:baseline;flex-wrap:wrap;font-size:1.6rem;font-weight:700;
  font-variant-numeric:tabular-nums;margin:0 0 1.1rem;}
.item-duration{font-size:0.95rem;font-weight:500;color:var(--muted);}
.item-description{color:var(--muted);margin-bottom:1.75rem;max-width:58ch;white-space:pre-line;}
.item-notice{border:1px solid var(--line);border-radius:var(--radius);padding:0.7rem 0.9rem;
  margin-bottom:1.25rem;font-size:0.9rem;color:var(--muted);}
/* The transaction, as one object rather than three loose lines — and
   as part of the page rather than a grey slab dropped onto it. A filled
   panel in the middle of an otherwise unboxed page reads as a widget
   from somewhere else; a rule above and below sets the same thing apart
   without borrowing another surface. */
.item-buy{display:flex;flex-direction:column;gap:0.85rem;align-items:stretch;
  border-block:1px solid var(--line);padding-block:1.4rem;max-width:26rem;}
.item-buy .btn-primary,.item-buy button[type=submit]{width:100%;text-align:center;}
.item-buy button[type=submit]{font:inherit;font-weight:600;cursor:pointer;border:0;
  padding:0.9rem 1.75rem;border-radius:var(--radius);background:var(--accent);color:#fff;
  box-shadow:0 4px 14px rgba(var(--shadow-rgb),0.28);}
.item-buy button[aria-busy=true]{opacity:0.6;cursor:progress;}
.item-buy-ask .btn-primary{background:transparent;color:var(--accent);
  border:1px solid var(--accent);box-shadow:none;}
.item-buy-ask .btn-primary:hover{background:var(--accent);color:#fff;}
.item-secure{margin:0;font-size:0.85rem;color:var(--muted);}
.item-quantity{display:flex;align-items:center;gap:0.6rem;font-size:0.92rem;color:var(--muted);}
.item-quantity input{width:5rem;padding:0.55rem 0.6rem;border:1px solid var(--line);
  border-radius:8px;background:var(--paper);color:var(--ink);font:inherit;
  font-variant-numeric:tabular-nums;}

/* The receipt. One confirmed fact, and a way back. */
.block-receipt{padding:3rem 0;}
.receipt-card{max-width:34rem;margin:0 auto;text-align:center;border:1px solid var(--line);
  border-radius:var(--radius);padding:2.5rem 1.75rem;background:var(--paper);}
.receipt-tick{display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;
  border-radius:50%;background:#16a34a;color:#fff;font-size:1.4rem;margin-bottom:1rem;}
.receipt-card h1{font-size:1.6rem;margin:0 0 0.75rem;}
.receipt-item{font-size:1.05rem;margin:0 0 0.5rem;font-variant-numeric:tabular-nums;}
.receipt-next{color:var(--muted);margin:0 0 1.5rem;}

/* ── The service ledger ───────────────────────────────────────────────
   Past a handful of services a card grid stops being a comparison and
   becomes a wall: three services and thirty-five rendered identically,
   and a card had nowhere to put a price or a length even when the owner
   had set both. A ledger row is name … meta, with the description
   under it — the shape the reference corpus showed holding from three
   items to forty, built from hairlines and type rather than boxes.
   Which shape a list takes is decided once, in app/rendering/volume.py.  */
.service-ledger{display:flex;flex-direction:column;gap:var(--space-10,3rem);}
.service-rows{display:flex;flex-direction:column;}
.service-group-title{font-family:var(--font-display);font-size:var(--text-lg);
  margin:0 0 var(--space-4,1rem);padding-bottom:var(--space-2,0.5rem);
  border-bottom:1px solid var(--line);letter-spacing:0.01em;}
.service-row{display:grid;grid-template-columns:auto 1fr auto;
  grid-template-areas:"name leader meta" "desc desc desc";
  align-items:baseline;gap:0 var(--space-3,0.75rem);
  padding:var(--space-4,1rem) 0;border-bottom:1px solid var(--line);}
.service-row:last-child{border-bottom:0;}
.service-row-name{grid-area:name;font-weight:600;font-size:1.05rem;}
/* The leader is the thing that makes a row read as a ledger rather than
   as two columns that happen to share a line. Decorative by definition,
   so it never reaches a screen reader. */
.service-row-leader{grid-area:leader;border-bottom:1px dotted var(--line);
  transform:translateY(-0.25em);min-width:1.5rem;}
.service-row-meta{grid-area:meta;color:var(--muted);font-size:0.95rem;
  font-variant-numeric:tabular-nums;white-space:nowrap;}
.service-row-desc{grid-area:desc;color:var(--muted);font-size:0.92rem;
  margin:var(--space-2,0.5rem) 0 0;max-width:60ch;}
.service-row-name .offer-link,.service-row-name .sell-link{margin-left:var(--space-3,0.75rem);
  font-size:0.85rem;font-weight:500;}
.service-index{margin:0 0 var(--space-8,2.5rem);}
.service-index ul{display:flex;flex-wrap:wrap;gap:var(--space-2,0.5rem) var(--space-5,1.5rem);
  list-style:none;margin:0;padding:0 0 var(--space-4,1rem);border-bottom:1px solid var(--line);}
.service-index a{color:var(--muted);text-decoration:none;font-size:0.9rem;
  letter-spacing:0.02em;text-transform:uppercase;}
.service-index a:hover,.service-index a:focus{color:var(--accent);}
/* Mobile is a recomposition, not a stack: the row keeps being a row and
   reflows INSIDE itself — name on one line, the price and the length
   under it — instead of turning into a card. */
@media(max-width:560px){
  .service-row{grid-template-columns:1fr;grid-template-areas:"name" "meta" "desc";}
  .service-row-leader{display:none;}
  .service-row-meta{white-space:normal;margin-top:0.15rem;}
}
