/* ===========================================
   publicPage.css - Public Page
   Base styles: page layout, container,
   buttons, and shared modal base classes.
   Section-specific styles are in pagePartials/
   =========================================== */

:root {
    --page-accent: #2563eb;
    --page-btn-bg: #ffffff;
    --page-btn-text: #1a1a2e;
    --page-btn-border: #e2e8f0;
    --page-btn-hover: #f0f0f0;
    --page-btn-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    --page-btn-hover-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
    --page-text-primary: #1f2937;
    --page-text-secondary: #6b7280;
    --page-text-muted: #9ca3af;
    --page-bg: #EDEEF1;
    --page-radius: 12px;
    --page-transition: all 0.2s ease;
    /* Height-responsive top pad, shared with the banner/hero bleed in
       pagePartials/header.css so the cover's negative margin always cancels
       exactly this value (a fixed -24px cut the cover on short phones). */
    --page-top-pad: clamp(16px, 3vh, 24px);
}

/* Owner-written text can never widen the page or a card: `anywhere` (unlike
   break-word) also counts as a min-content break, so centred flex items (name,
   bio, labels, titles) shrink and wrap instead of overflowing. nowrap surfaces
   (tab pills, contact values, step labels) have no soft-wrap opportunity and are
   unaffected. */
body {
    overflow-wrap: anywhere;
}

/* Keep the document scrollbar's gutter reserved while a modal's overflow:hidden
   lock removes the bar, so the page behind the backdrop does not re-centre by
   half a scrollbar on open/close (classic-scrollbar platforms only; overlay
   scrollbars are unaffected). Plain `stable`, not both-edges: both-edges would
   reserve a dead strip on the left of every page. */
html {
    scrollbar-gutter: stable;
}

/* Lock background scroll while any modal/overlay is open.
   Pure CSS via :has() — every overlay signals "open" with the .open class,
   so no JS coordination is needed (and there is no open/close timing to get wrong). */
html:has(.page-modal-overlay.open),
html:has(.booking-modal.open),
html:has(.cfm-modal.open),
html:has(.portfolio-detail-overlay.open),
html:has(#aiChatModalOverlay.open),
html:has(.page-gallery-lightbox.open),
html:has(.tiktok-video-modal.open) {
    overflow: hidden !important;
    /* Belt to the overflow:hidden braces — stops the document from rubber-banding
       behind the overlay. The real iOS lock is the JS position:fixed <body> pin in
       Views/partials/publicPage.ejs, because iOS Safari ignores overflow:hidden here. */
    overscroll-behavior: none;
}

/* Prevent secondary scrollbars on overlay elements */
.page-modal-overlay,
.booking-modal,
.cfm-modal,
#aiChatModalOverlay,
.page-gallery-lightbox,
.tiktok-video-modal,
.portfolio-detail-overlay {
    overflow: hidden !important;
}

/* Contain scroll-chaining: reaching the end of a modal's own scroll area must not
   rubber-band or scroll the page behind it (also disables pull-to-refresh inside
   modals). Covers the Tier-1 body and every bespoke modal's scroll region. */
.page-modal-body,
#aiChatMessagesContainer,
.cfm-body,
.booking-modal-body {
    overscroll-behavior: contain;
}

/* Classic scrollbars used to pop in and out of these bodies between steps, views
   and filters, shifting every field and grid by the bar's width. Reserve the
   gutter permanently (no effect on overlay-scrollbar platforms). */
.page-modal-body,
.cfm-body,
.booking-modal-body {
    scrollbar-gutter: stable;
}


/* Page wrapper */
.page-wrapper {
    position: relative;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.page-container {
    width: 100%;
    max-width: 680px;
    margin: 0 auto;
    padding: 0 16px;
    /* Height-responsive top pad (preserving the notch safe-area) so vertical
       spacing compresses on short viewports rather than forcing a scroll. */
    padding-top: calc(var(--page-top-pad) + env(safe-area-inset-top));
    padding-bottom: env(safe-area-inset-bottom);
    flex: 1;
    display: flex;
    flex-direction: column;
}

.public-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
}


.public-page-sections {
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 100%;
    /* No bottom padding: the footer's own top padding + margin-top:auto already
       separate the last button from the footer, so a rigid bottom pad here would
       only add non-collapsing height. Top pad is height-responsive.
       With margin-top below, this is the last rung of the header's optical ladder
       (~32px) — the group boundary between the header and the buttons. It must stay
       the widest gap on the page; see the rhythm block in pagePartials/header.css.
       That is also why the floor is 16px and not lower: this is the ONLY gap in the
       header that compresses on short viewports (every rung above it is fixed), so a
       smaller floor lets it collapse toward the 16px location->icons gap and the
       header stops reading as a separate group — at a 12px floor it bottomed out at
       1.25x. 16px keeps it >= 1.5x at every height, and matches .page-container's
       clamp above. The floor only binds below ~533px of viewport height, so this
       costs nothing on any viewport that currently fits without scrolling. */
    padding-top: clamp(16px, 3vh, 24px);
    padding-bottom: 0;
    margin-top: 8px;
}

.public-page-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 16px 24px;
    background: var(--page-btn-bg);
    border: var(--page-btn-border-width, 1px) solid var(--page-btn-border);
    border-radius: var(--page-radius);
    font-size: 15px;
    font-weight: 500;
    color: var(--page-btn-text);
    cursor: pointer;
    transition: var(--page-transition);
    box-shadow: var(--page-btn-shadow);
    font-family: var(--page-font, inherit);
    /* Fixed line box: with the UA `normal` a label containing emoji or Arabic
       makes that one button 1-2.5px taller than its neighbours. */
    line-height: 1.2;
}

/* Guarded: on a touch device :hover sticks after the tap, leaving the button
   lifted and darkened until something else is touched. :active stays outside —
   that is the press feedback touch actually wants. */
@media (hover: hover) {
    .public-page-btn:hover {
        background: var(--page-btn-hover);
        box-shadow: var(--page-btn-hover-shadow);
        transform: translateY(-1px);
    }
}

.public-page-btn:active {
    transform: translateY(0);
    box-shadow: var(--page-btn-shadow);
}

/* ===========================================
   Shared Modal Base Styles
   Mobile: Full-screen takeover
   Desktop: Centered modal with backdrop blur
   =========================================== */

/* Closed but not yet display:none (the 200ms teardown): it must not keep
   hit-testing over the page and swallow the next tap. */
.page-modal-overlay:not(.open) {
    pointer-events: none;
}

.page-modal-overlay {
    position: fixed;
    inset: 0;
    height: 100%;
    height: 100dvh;
    z-index: 9999;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: stretch;
    justify-content: center;
    opacity: 0;
    transition: opacity 0.2s ease;
}

.page-modal-overlay.open {
    opacity: 1;
}

.page-modal {
    background: #ffffff;
    width: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* Pin the mobile full-screen modal to the overlay's height instead of
       relying on align-items:stretch. On iOS Safari a stretch-only child with
       no definite height (and a compositing transform) can resolve shorter than
       the overlay, leaving the dark backdrop showing above it. Overridden by the
       desktop centered-card rules below (height:auto; max-height:85vh). */
    height: 100%;
    height: 100dvh;
    transform: translateY(20px);
    transition: transform 0.2s ease;
}

.page-modal-overlay.open .page-modal {
    transform: translateY(0);
}

/* Mobile: drop the slide-up transform entirely so .page-modal is not promoted
   to its own compositing layer (which on iOS Safari can paint a stale, offset
   layer for heavy/late-reflowing modals -> dark band above the modal). Desktop
   keeps the entrance animation via the rule above. Mirrors bookingModal.css. */
@media screen and (max-width: 767px) {
    .page-modal,
    .page-modal-overlay.open .page-modal {
        transform: none;
    }

    /* No fade on phones: the white iOS safe-area strips (html.modal-chrome-light)
       flip instantly, so a 200ms sheet fade over a dark page shows a mismatched
       frame on every open and close. */
    .page-modal-overlay {
        transition: none;
    }
}

/* iOS safe-area fix. iOS paints the regions behind the status bar (top) and the browser
   toolbar (bottom) with the PAGE's background color — not with any in-page overlay. While
   a full-screen modal is open, force the page surfaces white so those strips match the
   white modal; the modal covers the content area, so this only shows in the safe-area
   strips. Toggled on <html> by the observer (mobile only) with a :has() fallback for the
   section modals; both keep desktop's dimmed-backdrop centered card untouched.
   No transition here on purpose: the strips are not covered by the modal, so any fade is
   watched directly and trails the (hidden) page revert — it must flip instantly both ways. */
html.modal-chrome-light,
html.modal-chrome-light body,
html.modal-chrome-light .page-wrapper {
    background: #ffffff !important;
}

@media screen and (max-width: 767px) {
    html:has(.page-modal-overlay.open),
    html:has(.page-modal-overlay.open) body,
    html:has(.page-modal-overlay.open) .page-wrapper {
        background: #ffffff !important;
    }
}

.page-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Keep the title/close below the status bar; extra space is white (modal bg) so it
       stays invisible whether or not the modal itself reaches the physical top. */
    padding: calc(16px + env(safe-area-inset-top)) 20px 16px;
    border-bottom: 1px solid #e5e7eb;
    flex-shrink: 0;
    gap: 12px;
}

.page-modal-header h3 {
    font-size: 18px;
    font-weight: 600;
    color: var(--page-text-primary);
}

.page-modal-close {
    width: 36px;
    height: 36px;
    border: none;
    background: none;
    cursor: pointer;
    color: #6b7280;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, color 0.15s;
    flex-shrink: 0;
}

.page-modal-close:hover {
    background: #f3f4f6;
    color: #374151;
}

/* Focus lands here every time a modal opens (syncModalFocus, partials/publicPage.ejs).
   Inset offset: the button sits flush against the header edge. Not the accent — a
   pale one leaves no visible ring at all (faqsModal.css). */
.page-modal-close:focus-visible {
    outline: 2px solid var(--page-text-primary, #1f2937);
    outline-offset: -2px;
}

.page-modal-body {
    padding: 24px 20px;
    overflow-y: auto;
    flex: 1;
    /* Allow this flex:1 child to shrink below its content min-size so it scrolls
       internally instead of overgrowing the now height-pinned modal (WebKit). */
    min-height: 0;
}

.page-modal-empty {
    text-align: center;
    color: var(--page-text-muted);
    font-size: 14px;
    padding: 40px 0;
}

/* ===========================================
   Public Page Share Button
   =========================================== */

.public-page-share-btn {
    position: absolute;
    top: calc(16px + env(safe-area-inset-top));
    right: calc(16px + env(safe-area-inset-right));
    z-index: 100;
    width: 44px;
    height: 44px;
    border-radius: 9999px;
    background: #ffffff;
    border: 1px solid #e2e8f0;
    color: #1a1a2e;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
    .public-page-share-btn:hover {
        background: #f0f0f0;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
        transform: scale(1.05);
    }
}

.public-page-share-btn:active {
    transform: scale(0.95);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}

.public-page-share-btn svg {
    flex-shrink: 0;
}

.public-page-share-btn.shared {
    background: var(--page-accent, #2563eb);
    color: var(--page-accent-text, #ffffff);
    border-color: transparent;
}

/* ===========================================
   Desktop Styles (768px+)
   =========================================== */

/* 680px is the line where the banner/hero card stops bleeding and gains its
   rounded corners (pagePartials/header.css). The container gutter and the share
   button move at the SAME line so the button never straddles the card's corner:
   card top = 40 (pad) + 24 (card margin) = 64px vs button bottom = 16 + 44 = 60px. */
@media screen and (min-width: 680px) {
    .page-container {
        padding-top: 40px;
        padding-left: 24px;
        padding-right: 24px;
    }

    .public-page-share-btn {
        top: 16px;
        right: calc(24px + env(safe-area-inset-right));
    }
}

@media screen and (min-width: 768px) {
    .public-page-sections {
        padding-top: 24px;
    }

    .public-page-btn {
        padding: 18px 28px;
        font-size: 16px;
        border-radius: 14px;
    }

    /* Desktop modal: centered with blur */
    .page-modal-overlay {
        align-items: center;
        padding: 24px;
        backdrop-filter: blur(4px);
        -webkit-backdrop-filter: blur(4px);
    }

    .page-modal {
        max-width: 560px;
        height: auto;
        max-height: 85vh;
        /* Landscape phones are >=768px wide and land here: cap to the visible
           height so the card is never sheared top and bottom. Desktop unchanged. */
        max-height: min(85vh, calc(100dvh - 48px));
        border-radius: 16px;
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
    }

    .page-modal-header {
        padding: 20px 24px;
    }

    .page-modal-body {
        padding: 28px 24px;
    }
}

/* ===========================================
   Large Desktop (1024px+)
   =========================================== */

@media screen and (min-width: 1024px) {
    .page-container {
        padding-top: 48px;
    }

    .public-page-share-btn {
        top: 24px;
    }
}

/* ===========================================
   Phone override that must FOLLOW the base rules above (same specificity, source
   order decides): the home-indicator inset on every Tier-1 body (the legal modal
   used to carry its own copy). The 44px close target lives in modalShell.css
   instead — it applies to BOTH tiers and must also cover a landscape phone,
   which is exactly the condition that file already owns.
   =========================================== */
@media screen and (max-width: 767px) {
    .page-modal-body {
        padding-bottom: calc(24px + env(safe-area-inset-bottom));
    }

    /* The gallery modal stays .open underneath its lightbox / TikTok player, so
       the light-chrome rules above would paint the iOS safe-area strips white
       under a 92%-black overlay. Higher specificity than the light rules. */
    html:has(.page-gallery-lightbox.open),
    html:has(.page-gallery-lightbox.open) body,
    html:has(.page-gallery-lightbox.open) .page-wrapper,
    html:has(.tiktok-video-modal.open),
    html:has(.tiktok-video-modal.open) body,
    html:has(.tiktok-video-modal.open) .page-wrapper {
        background: #111111 !important;
    }
}

/* Background Patterns are rendered inline via server-side EJS or JS live preview */
