/* ============================================================
   Beauty / salon / spa vertical — Hidook Site Builder.
   Powder calm, gallery-first, programare always in chrome + hero.
   Opacity/transform motion only. Local photos only.
   ============================================================ */

:root {
    --color-primary:       #5f7350;
    --color-primary-light: #8fa37a;
    --color-primary-dark:  #2a3340;
    --color-cream:         #e8eef2;

    --ink: #2a3340;
    --soft: rgba(42, 51, 64, 0.78);
    /* WCAG AA: at 0.48 this was 2.5-2.7:1 against the light section backgrounds
       it's used on (paper/snow/blush) — well under the 4.5:1 normal-text floor.
       0.74 clears 4.5:1 against all three with margin (kicker labels, footer
       text, chip/price/schedule meta all use this on light backgrounds only —
       dark-section kickers use a separate literal rgba(255,255,255,…) already). */
    --mute: rgba(42, 51, 64, 0.74);
    --paper: var(--color-cream, #e8eef2);
    --void: #1c2228;
    --snow: #fbfcfd;
    --cta: var(--color-primary, #6b7f5a);
    --accent: var(--color-primary, #6b7f5a);
    --link: #4a5c6a;
    --blush: #e8ddd6;
    --font: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    --display: 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
    --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
    --max: 980px;
    --r: 8px;
    --r-pill: 980px;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* WAVE8-03: .pf-mobile-nav (the off-canvas hamburger drawer, below) is
   position:fixed + inset:0, hidden by transform:translateX(100%) rather than
   display:none (needed so its slide-in transition can animate). A
   position:fixed box shifted a full viewport-width to the right is, by
   spec, taken out of normal flow and shouldn't inflate the document's own
   scrollable area — but this codebase's 200%-zoom test approximation
   (document.documentElement.style.zoom, not a real browser zoom) measurably
   counts it anyway, and it's the last remaining source of horizontal
   overflow on this template. overflow-x:hidden on <html> is the standard,
   low-risk fix for exactly this "off-canvas drawer causes horizontal
   scroll" class of issue — it only clips the X axis (no effect on the
   normal vertical scroll or the .pf-chrome sticky header, verified against
   the existing lightbox/mobile-nav/sticky-header oracles). */
html { scroll-behavior: smooth; overflow-x: hidden; }
/* overflow-x:hidden must be repeated on body too: html's overflow-x alone
   governs the VIEWPORT's own scrollbar, but body remains its own box that
   can still be dragged/scrolled sideways within that viewport if it (not
   html) is what's actually wider than the viewport content-box — confirmed
   by testing scrollTo()/wheel after setting only html's rule. */
body.pf-page {
    font-family: var(--font);
    background: var(--void);
    color: var(--snow);
    font-size: 17px; line-height: 1.5; letter-spacing: 0.01em;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }

/* Sticky powder-on-dark chrome with programare CTA */
.pf-chrome {
    position: sticky; top: 0; z-index: 40;
    background: rgba(28, 34, 40, 0.88);
    backdrop-filter: saturate(140%) blur(16px);
    -webkit-backdrop-filter: saturate(140%) blur(16px);
    border-bottom: 1px solid rgba(255,255,255,0.06);
}
.pf-chrome__inner {
    max-width: 1120px; margin: 0 auto; min-height: 56px; padding: 0.5rem 22px;
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1rem;
}
.pf-chrome__mark, .pf-chrome__logo {
    font-family: var(--display); font-weight: 500; font-size: 1rem; letter-spacing: 0.02em;
    /* A long business name or CTA label at very narrow widths (200% zoom on a
       390px phone is ~195px) wraps onto its own line instead of forcing the
       chrome row past the viewport (flex-wrap above), rather than being cut off. */
    min-width: 0;
}
/* Standard logo box shared across all 5 templates (max-height + width:auto +
   object-fit:contain, no fixed aspect-ratio) — a square upload no longer
   renders at a different height per template (was 22px here vs up to 460px
   elsewhere for the identical file). */
.pf-chrome__logo { max-height: 40px; width: auto; object-fit: contain; }
/* Standalone nav targets, 44px. These rendered 17px tall — text with no
   padding — which is under even the 24x24 of WCAG 2.5.8 (Level AA), and a
   masthead link is not "inline in a sentence", so no exception applies. This
   is the primary navigation of a paying customer's site. Same floor the CTAs
   use; see bot/test/waveB-touch-targets.test.js. */
.pf-chrome__nav a { display: inline-flex; align-items: center; min-height: 44px; }
.pf-chrome__nav {
    display: none; gap: 1.35rem; font-size: 11px; font-weight: 600;
    letter-spacing: 0.12em; text-transform: uppercase; color: rgba(255,255,255,0.72);
}
@media (min-width: 720px) { .pf-chrome__nav { display: flex; } }
.pf-chrome__nav a:hover { color: #fff; }

/* Mobile menu toggle — hidden on desktop, shown as a hamburger below 720px */
.pf-chrome__toggle {
    display: none; order: 3; flex: 0 0 auto;
    width: 44px; height: 44px; margin: -11px 0;
    align-items: center; justify-content: center; flex-direction: column; gap: 5px;
    border: 0; background: transparent; color: #fff; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.pf-chrome__toggle-bar {
    display: block; width: 20px; height: 2px; background: currentColor; border-radius: 2px;
    transition: transform 200ms var(--ease-out), opacity 150ms var(--ease-out);
}
.pf-chrome__toggle[aria-expanded="true"] .pf-chrome__toggle-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.pf-chrome__toggle[aria-expanded="true"] .pf-chrome__toggle-bar:nth-child(2) { opacity: 0; }
.pf-chrome__toggle[aria-expanded="true"] .pf-chrome__toggle-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Mobile nav drawer — a sibling of .pf-chrome in the markup (see template.html comment)
   so its position:fixed is relative to the viewport, not clipped by an ancestor's
   backdrop-filter containing block. */
.pf-mobile-nav {
    display: none;
}
@media (max-width: 719.98px) {
    .pf-chrome__mark, .pf-chrome__logo { order: 1; }
    .pf-chrome__cta { order: 2; }
    .pf-chrome__toggle { display: inline-flex; }
}
@media (max-width: 719.98px) {

    .pf-mobile-nav {
        display: flex; position: fixed; inset: 0; z-index: 45;
        flex-direction: column; align-items: flex-start; justify-content: center;
        gap: 1.85rem; padding: 6rem 2rem 3rem;
        background: rgba(28, 34, 40, 0.98);
        backdrop-filter: saturate(140%) blur(20px); -webkit-backdrop-filter: saturate(140%) blur(20px);
        transform: translateX(100%); visibility: hidden;
        transition: transform 240ms var(--ease-out), visibility 0s linear 240ms;
    }
    .pf-mobile-nav.is-open {
        transform: translateX(0); visibility: visible;
        transition: transform 240ms var(--ease-out), visibility 0s linear 0s;
    }
    .pf-mobile-nav a { font-family: var(--display); font-size: 1.4rem; letter-spacing: 0; text-transform: none; font-weight: 500; color: rgba(255,255,255,0.92); }
}
.pf-chrome__cta {
    flex: 0 1 auto; max-width: 100%; box-sizing: border-box;
    display: inline-flex; align-items: center; justify-content: center;
    text-align: center;
    min-height: 44px;
    /* No white-space:nowrap: a long custom CTA label (business-editable) must be
       able to wrap onto a second line at very narrow widths (200% zoom on a
       390px phone is ~195px) instead of forcing the pill past the viewport. */
    background: var(--cta); color: #fff; border-radius: var(--r-pill);
    padding: 10px 18px; font-size: 12px; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase;
    transition: transform 140ms var(--ease-out);
}
.pf-chrome__cta:active { transform: scale(0.97); }
/* Below ~420px the masthead cannot hold all three: the wordmark is 98px, the
   booking button 204px (the seed label is "Programează o vizită") and the menu
   toggle 44px, which with gaps and gutters needs about 414px. The row wrapped,
   the header grew to 91px, and the order shuffled between widths — at 390 the
   toggle dropped under the wordmark, at 360 the wordmark sat alone above the
   other two. Drop the masthead button here: the same call to action is the
   first thing in the hero directly below it, and the menu drawer carries it
   too, so nothing becomes unreachable. */
@media (max-width: 419.98px) {
    /* Not display:none, though that was the first thing I reached for. On a
       salon's site the masthead booking button is the highest-value control on
       the page, and wave5-portfolio-a11y checks it is a real target — rightly.
       Make it fit instead: the seed label ("Programează o vizită") is unusually
       long, so cap the width and let an over-long one ellipsise rather than
       force the row to wrap. */
    .pf-chrome__cta {
        font-size: 10px;
        letter-spacing: 0.06em;
        padding: 10px 12px;
        max-width: 38vw;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .pf-chrome__mark { max-width: 26vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

.pf-hero {
    position: relative;
    width: 100%;
    min-height: 88vh;
    display: block;
    background: var(--void);
}
.pf-hero__frame {
    position: relative;
    width: 100%;
    min-height: 88vh;
    display: flex;
    align-items: flex-end;
}
.pf-hero__bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    background-size: cover !important;
    background-position: center !important;
    background-repeat: no-repeat !important;
}
/* Legibility veil.
   The hero wordmark and tagline are white text laid directly on whatever
   photograph the salon uploads, and this gradient is the only thing between
   them. It used to run 0.08 to 0.45 — an atmospheric wash, not a contrast
   guarantee. Measured on rendered pixels behind the headline: 1.94:1 at the
   10th percentile on the photo this template ships with, 1.40:1 on a light
   one, against the 3:1 WCAG AA floor for text at this size. The template was
   scored 6/10 with that in plain sight.
   Weighted to reach full strength by the upper third — where the copy block
   actually sits — rather than only at the very bottom of the frame. It stays
   a full-frame wash on purpose: a scrim sized to the copy block reads as a
   dark rectangle pasted on the photo, which is worse than the problem. */
.pf-hero__veil {
    position: absolute; inset: 0;
    background: linear-gradient(180deg,
        rgba(24, 28, 33, 0.14) 0%,
        rgba(24, 28, 33, 0.52) 18%,
        rgba(24, 28, 33, 0.80) 38%,
        rgba(24, 28, 33, 0.88) 100%);
}
.pf-hero__copy {
    position: relative; z-index: 1; padding: 0 22px 4rem; max-width: var(--max);
    /* Flex items default to min-width:auto (locked to their content's
       min-content width) — at very narrow widths (200% zoom on a 390px phone
       is ~195px) that held the wordmark/tagline to their longest unbreakable
       word and pushed this past the viewport. min-width:0 lets it shrink and
       actually wrap. */
    min-width: 0;
}
.pf-hero__logo { height: 40px; margin-bottom: 1rem; }
/* Hero type hierarchy.
   The salon name and the tagline used to share one rule — same family, same
   54px, same weight, stacked with no gap — so "Atelier Ivoire" and "Păr,
   îngrijirea tenului și manichiură — cu răbdare și precizie" read as one
   run-on sentence rather than as a name and a promise. The name is the <h1>
   and keeps the display scale; the tagline becomes the lede it actually is. */
.pf-hero__word {
    font-family: var(--display); font-size: clamp(2.4rem, 6.8vw, 3.6rem);
    font-weight: 500; line-height: 1.06; letter-spacing: -0.02em;
    margin: 0;
}
.pf-hero__tag, .hero-tagline {
    font-family: var(--body, inherit);
    font-size: clamp(1.05rem, 1.6vw, 1.3rem);
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: 0;
    max-width: 36rem;
    margin-top: 1.1rem;
    color: rgba(255, 255, 255, 0.94);
}
/* Touch-target floor: 44px.
   Measured across all five templates at 1440 and 390, every primary call to
   action on a generated site — the buttons that make a small business's phone
   ring — rendered between 34px and 42px tall. That clears WCAG 2.5.8 (Level
   AA, 24x24) and falls short of 2.5.5 (Level AAA, 44x44) and of the figure
   both the iOS and Android guidelines give for a thumb. Several of these rules
   already carried a min-height, set just under the mark.
   Locked by bot/test/waveB-touch-targets.test.js, which measures the rendered
   box rather than the declaration. */
.pf-hero__cta, .hero-cta {
    display: inline-flex; align-items: center; min-height: 44px;
    margin-top: 1.35rem; padding: 10px 20px;
    background: var(--cta); color: #fff; border: 0; border-radius: var(--r-pill);
    font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
    transition: transform 140ms var(--ease-out);
}
.pf-hero__cta:active { transform: scale(0.97); }
.pf-hint, .scroll-hint {
    width: 100%; border: 0; background: transparent; color: rgba(255,255,255,0.48);
    padding: 0 22px 1.5rem; font-size: 11px; letter-spacing: 0.12em;
    text-transform: uppercase; cursor: pointer; text-align: left;
}
.scroll-hint__line {
    display: block; width: 1px; height: 24px; margin-top: 0.4rem;
    background: rgba(255,255,255,0.32);
}

.pf-main { background: var(--paper); color: var(--ink); }
.pf-shell { max-width: var(--max); margin: 0 auto; padding: 0 22px; }
.pf-gal { padding: clamp(3rem, 8vw, 5.5rem) 0; }
.pf-kicker {
    font-size: 11px; font-weight: 600; letter-spacing: 0.14em;
    text-transform: uppercase; color: var(--mute); margin-bottom: 0.5rem;
}
.pf-kicker--center { text-align: center; }
.pf-display {
    font-family: var(--display); font-size: clamp(1.85rem, 3.5vw, 2.45rem);
    font-weight: 500; line-height: 1.15; letter-spacing: -0.01em; margin-bottom: 1.5rem;
}
.pf-display--center { text-align: center; }
.pf-copy { color: var(--soft); max-width: 36rem; }
.pf-copy--sm { font-size: 0.92rem; margin-top: 0.5rem; }

.pf-series { margin: 2.5rem 0 3.5rem; }
.pf-series__t { font-family: var(--display); font-size: 1.35rem; font-weight: 500; }
/* Measured uncapped at 936px/93 chars-per-line on a 1440 viewport — same 36rem cap as .pf-copy. */
.pf-series__b { color: var(--soft); margin: 0.35rem 0 1rem; max-width: 36rem; }
/* The photo wall is deliberately full-bleed while its heading stays in the
   980px text column — the contrast between the two is the section's whole
   idea. But it ran to x=0 and x=1440 with a narrow gap between the photos, so
   the outer edges were the only seams on the page that did not match, and the
   rounded corners of the first and last photo in each row were sliced off by
   the viewport. It read as a grid that had escaped its layout rather than one
   placed edge to edge on purpose. Same gutter outside as inside.
   The gap itself is widened here (photos end up a little smaller as the same
   row width is now split with more space between them): on a client-chosen
   section colour the photos read as a single fused mass at a tight gap —
   more breathing room keeps them reading as separate framed shots. */
/* SUITE C (feedback 2026-09-13, punct 3): supersedes the WAVE10 fix below.
   WAVE10 solved "a partial last row leaves an empty gap" by STRETCHING the
   leftover photo(s) to fill the row (`grid-column: span 6` + `aspect-ratio:
   21/9` on a lone remainder). That is exactly the defect the owner reported
   with a screenshot: adding a 4th/5th photo to a 3-per-row grid made that
   photo render as a huge full-width banner under the row of three. Uniform
   sizing and "stretch to fill" are mutually exclusive — this fix keeps
   uniform sizing and drops the stretch entirely, so
   bot/test/wave10-portfolio-gallery-grid.test.js's "no empty last-row gap"
   assertion no longer applies (it is rewritten in this same commit to check
   the new invariant: never stretched, and any row that would otherwise
   leave a gap becomes a scrollable carousel instead — see that file's
   updated header comment).
   Every photo, at any count, renders at the same size: `.collage-deck` is a
   single-row flex track (no wrapping) sized to fit N photos per screen
   width; a category with more photos than fit becomes a horizontal
   scroll-snap carousel (collage.js wires the prev/next buttons + swipe is
   native `overflow-x` touch scrolling) instead of wrapping to a second row
   or stretching a leftover photo. */
.collage-stage { padding-inline: 20px; position: relative; }
.collage-deck {
    display: flex;
    gap: 20px;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: 20px;
    -webkit-overflow-scrolling: touch;
    /* Buttons + swipe are the carousel's affordances; a second, native
       scrollbar underneath the photos read as visual clutter in review. */
    scrollbar-width: none;
    /* Flex items paint like inline content (CSS Flexbox §z-index), which
       sits ABOVE a lower positioned sibling in stacking order regardless of
       DOM order — confirmed live: without this, `.collage-nav` (z-index:5,
       a sibling of this deck, not a child) rendered visually on top but a
       photo <img> still received the click. Giving the deck its own
       explicit stacking context (position + a z-index below the buttons')
       contains that flex-item painting quirk inside the deck itself, so the
       stage-level comparison becomes a plain "z-index 1 vs 5" and the nav
       buttons are reliably clickable, not just visually on top. */
    position: relative;
    z-index: 1;
}
.collage-deck::-webkit-scrollbar { display: none; }
@media (prefers-reduced-motion: reduce) {
    .collage-deck { scroll-behavior: auto; }
}
/* Two per screen on narrow widths, three from 834px up — same breakpoint
   WAVE10 used for its 3-per-row desktop grid. A category at or under the
   breakpoint's count renders as a plain uniform row (nothing to scroll,
   `overflow-x` auto is a no-op); above it, .collage-nav (below) engages. */
.pf-frame, .collage-photo {
    flex: 0 0 calc((100% - 20px) / 2);
    scroll-snap-align: start;
}
@media (min-width: 834px) {
    .pf-frame, .collage-photo { flex: 0 0 calc((100% - 40px) / 3); }
}
/* Real <button>s, absolutely positioned over the stage's own side padding so
   they never add horizontal page scroll. collage.js shows/hides + enables/
   disables them based on the deck's actual measured overflow (never a fixed
   photo-count guess), and sets their Romanian aria-label from the same
   `data-lb-prev`/`data-lb-next` values the lightbox buttons already use. */
.collage-nav {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: 44px; height: 44px; border: 0; border-radius: 50%;
    display: grid; place-items: center;
    background: rgba(251, 252, 253, 0.92); color: var(--ink);
    font-size: 1.5rem; line-height: 1; cursor: pointer;
    box-shadow: 0 4px 14px rgba(28, 34, 40, 0.22);
    z-index: 5;
    transition: background 160ms var(--ease-out), opacity 160ms var(--ease-out);
    -webkit-tap-highlight-color: transparent;
}
.collage-nav:hover { background: var(--snow); }
.collage-nav:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.collage-nav:disabled { opacity: 0.35; cursor: default; }
.collage-nav[hidden] { display: none; }
.collage-nav--prev { left: 8px; }
.collage-nav--next { right: 8px; }
.pf-frame, .collage-photo { margin: 0; border-radius: var(--r); overflow: hidden; }
/* WAVE10: `height: auto` is required, not decorative — build.js's CLS-safety
   pass (injectResponsiveImages) writes real width/height HTML ATTRIBUTES onto
   every <img> from the source file's own intrinsic pixel size. Those attributes
   map to a `height` (not `aspect-ratio`) presentational hint, so — despite
   being lower-priority than any author rule — they fully satisfy the box's
   `height` property (making it non-auto) and this rule's `aspect-ratio: 4/5`
   is then never consulted at all (that only kicks in when height computes to
   auto). Confirmed live: this category's own photos ship at mixed native
   sizes (960x960 vs 960x643), and without `height: auto` here the two shapes
   rendered at two different pixel heights side by side in the same row — a
   real, visible defect, not merely an untidy intent. Same fix applied to
   .pf-person__pic img below, which shares the pattern. */
.pf-frame img, .collage-photo img { width: 100%; height: auto; aspect-ratio: 4/5; object-fit: cover; }

.pf-about {
    background: var(--void); color: var(--snow);
    padding: clamp(3rem, 8vw, 5.5rem) 0;
}
.pf-about .pf-kicker { color: rgba(255,255,255,0.48); }
.pf-about .pf-display { color: var(--snow); }
.pf-about .pf-copy { color: rgba(255,255,255,0.72); }
.pf-about__grid {
    display: grid; gap: 2rem;
}
@media (min-width: 834px) {
    .pf-about__grid { grid-template-columns: 1.4fr 0.6fr; align-items: end; }
}
.pf-about__rule {
    display: flex; align-items: center; gap: 0.75rem;
    font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase;
    color: rgba(255,255,255,0.4);
}
.pf-about__slash {
    flex: 1; height: 1px; background: rgba(255,255,255,0.18); max-width: 6rem;
}

.pf-svc, .pf-sched, .pf-team { padding: clamp(3rem, 7vw, 4.5rem) 0; }
/* Online-booking intro (kicker/heading/copy/privacy line) sits on the same
   client-repaintable --paper as the price list above, so it gets the same
   panel treatment for the same reason — see the comment on .pf-price. */
.pf-svc__intro {
    max-width: 40rem; margin: 0 auto 1.75rem; padding: 1.5rem 1.75rem;
    background: color-mix(in srgb, var(--snow) 92%, transparent);
    border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
    border-radius: var(--r);
}
/* rgb(89,96,106) is a fixed 78%-ink/22%-white mix (--ink and white are both
   constants, so this is written as a plain colour rather than a color-mix()
   the panel's own background above still needs, since it never has to react
   to --paper — only to the panel's own fixed backdrop). */
.pf-svc__intro .pf-kicker { color: rgb(89, 96, 106); }
.pf-svc__intro .pf-copy { color: rgb(89, 96, 106); }
/* PLAN-FEEDBACK-2026-09-13 Suite E: editor-only hint (see appointment.
   showActivationHint in build.js, never present on a published site). Sits
   directly in .pf-shell — the dark --void page background, NOT the light
   .pf-svc__intro panel above — so it reuses the same rgba(255,255,255,0.72)
   "soft light copy on dark section" convention as .pf-appt .pf-copy /
   .pf-about .pf-copy below, already proven: composited over --void
   (#1c2228) that is ~8.9:1, comfortably clearing the 4.5:1 WCAG AA floor
   for normal text. */
.pf-appt-hint { margin-top: 0.9rem; max-width: 30rem; font-size: 0.85rem; line-height: 1.5; color: rgba(255,255,255,0.72); }
.pf-chips {
    display: grid; grid-template-columns: 1fr; gap: 10px; margin-top: 0.5rem;
}
@media (min-width: 640px) { .pf-chips { grid-template-columns: repeat(2, 1fr); } }
.pf-chip {
    display: flex; flex-wrap: wrap; align-items: center; gap: 0.65rem;
    background: var(--snow); border-radius: var(--r); padding: 16px 18px;
    border: 1px solid rgba(42, 51, 64, 0.06);
}
.pf-chip__icon { width: 22px; height: 22px; color: var(--cta); display: inline-flex; }
.pf-chip__icon svg { width: 22px; height: 22px; }
.pf-chip__label { font-weight: 600; flex: 1; }
.pf-chip__price { font-size: 0.9rem; color: var(--mute); }

/* The section this list sits in is painted with --paper (theme.cream), a
   colour the client can repaint to anything from the colour picker — not
   only the near-white paper/snow/blush this list shipped with. A saturated
   choice can put this text and its dividers well under WCAG AA even at full
   solid ink, because for a fair range of background lightness neither a
   dark nor a light foreground alone clears 4.5:1/3:1 against it. A panel
   mixed toward --snow (not toward --paper) keeps its own colour light no
   matter what --paper turns into — even a fully black section background
   only pulls it to about 0.77 relative luminance — so the ink-based colours
   below, computed against that worst case, hold their ratio on any section
   colour. Same translucent-panel technique as the reusable one used
   elsewhere in this codebase for text over an unpredictable background. */
.pf-price {
    margin-top: 2rem; max-width: 32rem; margin-left: auto; margin-right: auto;
    padding: 0.25rem 1.5rem;
    background: color-mix(in srgb, var(--snow) 92%, transparent);
    border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
    border-radius: var(--r);
}
.pf-price__row {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.5rem;
    padding: 0.65rem 0; border-bottom: 1px solid rgb(110, 116, 125);
}
.pf-price__row:last-child { border-bottom: 0; }
.pf-price__name { font-weight: 500; }
.pf-price__dots { flex: 1; border-bottom: 1px dotted rgb(110, 116, 125); min-width: 1.5rem; transform: translateY(-4px); }
.pf-price__val { color: rgb(89, 96, 106); white-space: nowrap; }

.pf-sched__list { max-width: 28rem; margin: 0 auto; }
.pf-sched__row {
    display: flex; justify-content: space-between; gap: 1rem;
    padding: 0.75rem 0; border-bottom: 1px solid rgba(42, 51, 64, 0.1);
}
.pf-sched__day { font-weight: 600; }
.pf-sched__hours { color: var(--soft); }

.pf-team__grid {
    display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-top: 0.5rem;
}
@media (min-width: 640px) { .pf-team__grid { grid-template-columns: repeat(3, 1fr); } }
.pf-person {
    background: var(--snow); border-radius: var(--r); padding: 1rem;
    border: 1px solid rgba(42, 51, 64, 0.06);
}
.pf-person__pic { border-radius: var(--r); overflow: hidden; margin-bottom: 0.85rem; }
.pf-person__pic img { width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; }
.pf-person__name { font-family: var(--display); font-size: 1.15rem; font-weight: 500; }
.pf-person__role { font-size: 0.85rem; color: var(--mute); margin-top: 0.2rem; }

.pf-ig { padding: clamp(3rem, 7vw, 4.5rem) 0; background: var(--blush); }
.pf-ig__head {
    display: flex; justify-content: space-between; align-items: flex-end;
    gap: 1rem; margin-bottom: 1.5rem;
}
.pf-ig__handle { color: var(--mute); margin-top: 0.25rem; }
.pf-follow {
    display: inline-flex; align-items: center; min-height: 44px; padding: 4px 0;
    font-size: 12px; font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--link);
}
.pf-ig__embed { margin-bottom: 1.25rem; }
.instagram-embed-iframe {
    width: 100%; min-height: 480px; border: 0; border-radius: var(--r); background: var(--snow);
}

/* Instagram teaser (editor-preview-only "example" section) — see
   build.js renderHtml()'s cfg.instagram.showTeaser and this template's
   <!-- @if instagram.showTeaser --> block. Never renders on a published
   site (see bot/test/suite10-instagram-teaser-editmode.test.js).
   builder/edit-overlay.js toggles .is-revealed and un-hides the veil on
   interaction; the grid and the veil are stacked in the same grid cell so
   the veil always reads centred over the photos, whatever their count. */
.hb-ig-teaser {
    display: grid; grid-template-columns: 1fr; row-gap: 1rem;
    max-width: var(--max); margin: 0 auto; padding: clamp(2.5rem, 6vw, 4rem) 22px;
}
.hb-ig-teaser__badge {
    grid-row: 1; grid-column: 1; justify-self: start;
    font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--link); background: var(--snow); border: 1px solid rgba(42, 51, 64, 0.14);
    border-radius: var(--r-pill); padding: 0.4rem 0.9rem;
}
.hb-ig-teaser__grid {
    grid-row: 2; grid-column: 1; list-style: none;
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem;
    transition: filter 0.3s var(--ease-out);
}
.hb-ig-teaser__grid li { aspect-ratio: 1; border-radius: var(--r); overflow: hidden; background: var(--paper); }
.hb-ig-teaser__grid img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hb-ig-teaser.is-revealed .hb-ig-teaser__grid { filter: blur(6px); }
/* filter: blur() makes the grid a stacking context, which paints above the
   veil even though the veil comes later in the DOM — so once revealed, the
   blurred tiles swallowed clicks meant for the connect button. Caught by
   suite10-instagram-teaser-connect: the CTA was visible, enabled and stable,
   and still unclickable. Lift the veil out of that fight explicitly. */
.hb-ig-teaser__veil {
    position: relative; z-index: 1;
    grid-row: 2; grid-column: 1;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 1rem; text-align: center; padding: 1.5rem;
        /* The scrim has to guarantee the text on it, not merely tint whatever photo
       happens to be underneath. At 0.5–0.55 the lead text measured anywhere from
       3.4:1 to 4.5:1 and moved between runs, because a blurred bright tile can
       show through. 0.86 makes the reading independent of the photo, which is
       the only version of this that is actually testable. */
    background: rgba(28, 34, 40, 0.86); border-radius: var(--r);
}
.hb-ig-teaser__veil[hidden] { display: none; }
.hb-ig-teaser__lead { color: var(--snow); font-weight: 600; max-width: 26ch; }
/* --cta resolves to the owner's own --color-primary, which put near-white text
   at 3.47:1 on Portocaliu and 4.47:1 on Roz — under the 4.5:1 bar. This button
   is builder chrome on a scrim, not the site's brand, so it does not read the
   palette at all. The other templates carrying this section were corrected the
   same way. */
.hb-ig-teaser__cta {
    display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
    padding: 0.65rem 1.5rem; border: 0; border-radius: var(--r-pill);
    background: #fbfcfd; color: #14171a; font-weight: 600; font-size: 13px; cursor: pointer;
}
@media (prefers-reduced-motion: reduce) {
    .hb-ig-teaser__grid { transition: none; }
}

.pf-appt {
    padding: clamp(3rem, 8vw, 5rem) 0;
    background: var(--void); color: var(--snow);
}
.pf-appt .pf-kicker { color: rgba(255,255,255,0.48); }
.pf-appt .pf-display { color: var(--snow); }
.pf-appt .pf-copy { color: rgba(255,255,255,0.72); }
.pf-appt__grid {
    display: grid; gap: 2rem;
}
.pf-appt__grid > * {
    /* Grid items default to min-width:auto (locked to content's min-content
       width) same as flex items — without this, a long WhatsApp CTA label
       could hold this column wider than the viewport at very narrow widths. */
    min-width: 0;
}
@media (min-width: 834px) {
    .pf-appt__grid { grid-template-columns: 1.2fr 0.8fr; align-items: start; }
}
.pf-appt__list { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 1.25rem; }
.pf-row, .contact-item {
    display: inline-flex; align-items: center; min-height: 44px;
    padding: 9px 15px; border-radius: var(--r-pill);
    border: 1px solid rgba(255,255,255,0.35); color: #fff; font-size: 14px;
    /* WAVE8-03: an address or Instagram handle can be one long unbreakable
       token (no spaces). .pf-appt__list already wraps chips onto new lines
       (flex-wrap:wrap), but a flex item's default min-width still refuses
       to shrink a SINGLE chip below its own unbreakable content width, so
       one long token could still push this chip — and the page — wider
       than the viewport instead of wrapping internally. Same fix shape as
       .pr-contact__row in templates/professionals/styles.css. */
    max-width: 100%;
    overflow-wrap: break-word;
    word-break: break-word;
}
.pf-appt__panel {
    background: rgba(255,255,255,0.06); border-radius: var(--r);
    padding: 1.5rem; border: 1px solid rgba(255,255,255,0.08);
}
.pf-appt__wa {
    display: inline-flex; width: 100%; justify-content: center;
    align-items: center; min-height: 44px;
    background: var(--cta); color: #fff; border-radius: var(--r-pill);
    padding: 12px 16px; font-size: 12px; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 1.25rem;
}
.pf-appt__hr {
    display: flex; justify-content: space-between; gap: 1rem;
    padding: 0.4rem 0; font-size: 0.92rem; color: rgba(255,255,255,0.72);
}
.pf-appt__hr strong { color: #fff; font-weight: 600; }

.pf-foot {
    background: var(--paper); color: var(--mute); font-size: 12px;
    border-top: 1px solid rgba(42, 51, 64, 0.08);
}
.pf-foot__inner {
    display: flex; flex-wrap: wrap; gap: 1.25rem; align-items: center;
    justify-content: space-between; padding: 2rem 22px 3rem;
}
.pf-foot__logo { height: 24px; }
.pf-foot__soc { display: flex; gap: 0.5rem; }
/* Real glyphs, not the "IG"/"FB" text abbreviations this shipped with — those
   read as near-invisible clutter rather than recognisable network marks,
   especially once the footer's --paper background is a client-chosen colour.
   The dark circle is a fixed --ink tone rather than anything paper-derived,
   so the white glyph inside always has a solid, predictable background to
   sit on regardless of the section colour behind it. */
.pf-foot__soc a {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px; border-radius: 50%;
    background: var(--ink);
}
.pf-soc-icon {
    width: 18px; height: 18px; background-repeat: no-repeat;
    background-position: center; background-size: contain;
}
.pf-soc-icon--ig {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z'/%3E%3C/svg%3E");
}
.pf-soc-icon--fb {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z'/%3E%3C/svg%3E");
}

.whatsapp-float {
    position: fixed; right: 18px; bottom: 18px; width: 48px; height: 48px; border-radius: 50%;
    background: #25D366; color: #fff; display: grid; place-items: center;
    /* z-index: 55, not the shared 30 every other template uses for this same
       button — collage.js (above) assigns each .collage-photo an inline
       z-index up to 50 (its own "which photo overlaps which" stacking, unrelated
       to page chrome), and this button must stay clickable even when the
       gallery grid is scrolled to a position where a photo's screen box
       happens to cover this fixed corner. Found via the editor's own list-add
       scroll fix: preserving/landing on scroll position (instead of always
       resetting to the top on every re-render) made that overlap reachable
       for the first time — the z-index gap was latent before that. */
    z-index: 55;
    box-shadow: 0 8px 20px rgba(37, 211, 102, 0.35);
}
.whatsapp-float .wa-icon { display: block; }
.hb-built-by {
    margin: 0.55rem 0 0;
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--mute, #6b6b6b);
}
.hb-built-by a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.wa-qr {
    /* Same reasoning as .whatsapp-float above: above collage.js's max inline
       z-index (50), with a safety margin, instead of the shared 50 every
       other template uses for this same modal. */
    position: fixed; inset: 0; z-index: 60;
    display: grid; place-items: center;
}
.wa-qr[hidden] { display: none !important; }
.wa-qr__backdrop { position: absolute; inset: 0; background: rgba(18, 22, 31, 0.64); backdrop-filter: blur(4px); }
.wa-qr__card {
    position: relative;
    background: var(--snow); color: var(--ink); border: 1px solid rgba(255, 255, 255, 0.72); border-radius: 18px;
    padding: 2rem; width: min(92vw, 380px); text-align: center; box-shadow: 0 28px 72px rgba(18, 22, 31, 0.3);
}
.wa-qr__close { position: absolute; right: 12px; top: 10px; width: 36px; height: 36px; border: 0; border-radius: 50%; background: rgba(42, 51, 64, 0.07); font-size: 1.5rem; line-height: 1; cursor: pointer; }
.wa-qr__title { margin: 0 2rem 1rem; font-size: 1.45rem; line-height: 1.2; }
.wa-qr__code { display: grid; place-items: center; min-height: 260px; padding: 10px; background: #fff; border: 1px solid rgba(42, 51, 64, 0.1); border-radius: 18px; }
.wa-qr__code img { display: block; max-width: 100%; height: auto; }
.wa-qr__code svg { display: block; max-width: 100%; height: auto; }
.wa-qr__open { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; margin-top: 1rem; padding: 0.7rem 1.15rem; background: #25D366; color: #fff; border-radius: 999px; font-weight: 700; text-align: center; box-shadow: 0 8px 20px rgba(37, 211, 102, 0.24); }

/* Gallery lightbox — full-screen viewer opened by collage.js on photo click/tap */
.lightbox {
    position: fixed; inset: 0; z-index: 100;
    display: grid; place-items: center; padding: 4vmin;
    background: rgba(28, 34, 40, 0.92);
    backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    animation: pfLbFade 200ms var(--ease-out);
}
.lightbox[hidden] { display: none; }
@keyframes pfLbFade { from { opacity: 0; } to { opacity: 1; } }
.lightbox-img {
    max-width: min(92vw, 1100px); max-height: 86vh;
    object-fit: contain; border-radius: var(--r);
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
}
.lightbox-close, .lightbox-nav {
    position: absolute; display: grid; place-items: center;
    width: 44px; height: 44px; border: 0; border-radius: 50%;
    background: rgba(255, 255, 255, 0.12); color: var(--snow);
    font-size: 1.6rem; line-height: 1; cursor: pointer;
    transition: background 160ms var(--ease-out), transform 160ms var(--ease-out);
    -webkit-tap-highlight-color: transparent;
}
.lightbox-close:hover, .lightbox-nav:hover { background: rgba(255, 255, 255, 0.24); }
.lightbox-close:focus-visible, .lightbox-nav:focus-visible { outline: 2px solid var(--snow); outline-offset: 2px; }
.lightbox-close { top: 18px; right: 18px; }
.lightbox-nav { top: 50%; transform: translateY(-50%); font-size: 1.9rem; }
.lightbox-nav:hover { transform: translateY(-50%) scale(1.06); }
.lightbox-prev { left: 18px; }
.lightbox-next { right: 18px; }
@media (max-width: 719.98px) {
    .lightbox { padding: 0; }
    .lightbox-img { max-width: 100vw; max-height: 78vh; border-radius: 0; }
    .lightbox-close { top: 10px; right: 10px; }
    .lightbox-prev { left: 8px; }
    .lightbox-next { right: 8px; }
}

.fade-in-section { animation: pfIn 420ms var(--ease-out) both; }
@keyframes pfIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .fade-in-section, .hero-content { animation: none !important; opacity: 1 !important; transform: none !important; }
}
