/* ============================================================================
   FRONT PAGE
   Public landing page for the clinic (resources/views/layouts/frontpage.blade.php).

   The page is built around one idea: a patient arriving here wants to know
   whether the clinic is open, where it is, and how to get into their account.
   Those three answers live in the hero status rail; everything else on the page
   is deliberately quiet so the rail is the thing you see first.

   ONE IMAGE, THREE WASHES. The clinic photograph is painted ONCE, on a fixed
   layer behind the whole document (body::before), so it holds still while the
   page scrolls over it. No section paints its own copy - a section that did
   would restart the picture at its own top edge and break the illusion.

   Every section is therefore transparent apart from a translucent blue wash,
   and the washes are chained: each one ends on the colour the next one starts
   from (A -> B, B -> C, C -> B, B -> A). Scrolling the page reads as one
   continuous gradient rather than a stack of bands.

   Two consequences worth knowing before editing:
     - Wash alpha is load-bearing. Text contrast is measured against the wash
       composited over a WHITE photo pixel, the worst case. Lowering an alpha
       to show more of the picture takes the text down with it.
     - There is no light ground any more, so there is one card, one tile and
       one link colour - all of them light-on-dark.

   The auth modals are NOT skinned here - they keep their own frosted navy in
   css/FrontPageCSS/auth.css, which the standalone reactivate-account page
   shares. Do not move rules between the two files.
   ============================================================================ */

/* ===========================
   TOKENS
   =========================== */
:root {
    /* Ink - the clinic navy, carried over from the previous design. */
    --fp-ink: #0B2239;
    --fp-ink-deep: #04101E;
    --fp-ink-lift: rgba(255, 255, 255, 0.07);
    --fp-ink-lift-strong: rgba(255, 255, 255, 0.12);

    /* The three section washes, deepest to lightest. All three are blue - C
       leans towards steel rather than going grey, so the run stays in one
       family instead of fading out at the light end.

       The alpha values are the contrast budget. Measured against a white photo
       pixel, C at 0.88 still gives 5.3:1 for secondary text and 4.7:1 for the
       cyan links; at 0.86 the links drop under 4.5:1. Do not lower them. */
    --fp-wash-a: rgba(5, 20, 38, 0.93);
    --fp-wash-b: rgba(10, 36, 66, 0.95);
    --fp-wash-c: rgba(17, 54, 88, 0.88);

    --fp-teal: #0E7490;
    --fp-teal-deep: #0A5A70;
    --fp-teal-lift: #22D3EE;

    /* Status colours, picked to sit on the washes rather than on white. */
    --fp-open-lift: #4ADE80;
    --fp-soon-lift: #FBBF24;

    /* Text roles. There is a single ground now, so these are global. */
    --fp-text: #F3F8FC;
    --fp-text-soft: #B9CDDE;
    --fp-focus: #22D3EE;
    --fp-link: #22D3EE;
    --fp-hairline: rgba(255, 255, 255, 0.14);

    /* Space scale (4pt rhythm). */
    --fp-1: 4px;
    --fp-2: 8px;
    --fp-3: 12px;
    --fp-4: 16px;
    --fp-5: 24px;
    --fp-6: 32px;
    --fp-7: 48px;
    --fp-8: 64px;

    --fp-radius-sm: 8px;
    --fp-radius: 14px;
    --fp-radius-lg: 22px;

    --fp-shell: 1180px;
    --fp-gutter: clamp(20px, 5vw, 56px);
    --fp-section-y: clamp(64px, 9vw, 112px);

    /* Motion tokens. The three easings are CSS equivalents of the GSAP curves
       the reveal timings are modelled on, so the page moves the way those
       presets do without shipping the library (this app self-hosts every
       asset by hand - see the offline-asset note in the repo). */
    --fp-ease: cubic-bezier(0.22, 1, 0.36, 1);
    --fp-ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
    --fp-ease-expo: cubic-bezier(0.16, 1, 0.30, 1);
    --fp-ease-back: cubic-bezier(0.34, 1.40, 0.64, 1);

    /* Scaled up roughly 1.5x from the dataset's 400-600ms scroll-reveal band.
       The presets are tuned for GSAP's snappier default feel; at this size the
       page reads as deliberate rather than brisk. Past about 1.1s a reveal
       stops feeling considered and starts feeling like lag, so --fp-dur-slow
       is the ceiling - raise these together or the exits and the stagger fall
       out of proportion with the entrances. */
    --fp-dur-fast: 0.50s;
    --fp-dur: 0.80s;
    --fp-dur-slow: 1.05s;
    --fp-stagger: 110ms;

    --fp-nav-h: 62px;
}

/* Percentages down the DOCUMENT at which the wash changes colour. They are
   tuned to land near the section joins at desktop widths, but nothing depends
   on hitting them exactly - see the note on body::after. */
:root {
    --fp-stop-hero: 17%;
    --fp-stop-about: 40%;
    --fp-stop-services: 60%;
    --fp-stop-location: 81%;
    --fp-stop-contact: 94%;
}

/* ===========================
   RESET & BASE
   =========================== */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
    /* Anchor scrolling stops under the sticky navbar instead of behind it. */
    scroll-padding-top: calc(var(--fp-nav-h) + 12px);
}

body {
    position: relative;
    font-family: 'Poppins', system-ui, -apple-system, 'Segoe UI', sans-serif;
    font-size: 1rem;
    line-height: 1.7;
    color: var(--fp-text);
    /* Shows only until the photograph paints, and behind it wherever a very
       tall viewport out-runs the image. */
    background: var(--fp-ink-deep);
    /* clip, not hidden: overflow-x:hidden makes <body> a scroll container,
       which pins position:sticky to the body scrollport instead of the
       viewport and drops the navbar out of the top of the page. clip contains
       the same stray horizontal overflow without creating that container. */
    overflow-x: clip;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* THE PHOTOGRAPH. One fixed layer for the entire document, behind every
   section, so it holds still while the page scrolls over it and the washes
   above slide across a picture that never moves.

   position:fixed rather than background-attachment:fixed on purpose: iOS
   Safari ignores the attachment and repaints the image per-section at the
   wrong scale, and on Android it costs a full-image repaint every scroll
   frame. A fixed layer is handed to the compositor once. */
body::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    /* Sized to the LARGEST viewport, not the current one. A phone browser grows
       and shrinks its visible area as the URL bar hides and returns; with
       inset:0 the layer would be re-sized with it and the photograph would
       visibly rescale on every scroll direction change. 100lvh is fixed. */
    height: 100vh;
    height: 100lvh;
    z-index: -2;
    background:
        var(--frontpage-background-image, url('../../images/clinic-bg.jpg'))
        center center / cover no-repeat;
}

/* THE WASH, as ONE layer spanning the whole document rather than a gradient
   per section.

   Per-section washes cannot be made seamless. Even with both sides set to the
   identical colour, two adjacent translucent boxes each get their edge
   antialiased where the join lands on a fractional device pixel, so that row
   receives partial alpha from each instead of full alpha from one - and a pale
   hairline is drawn across the photograph. It is invisible at DPR 1 and
   obvious at the 1.25 and 1.5 scalings Windows ships by default.

   One element cannot have a seam with itself. The colour stops below are
   positioned as percentages of document height, so they drift a little as
   content grows - that is fine and by design: the run is continuous either
   way, and there is no boundary left to show. */
body::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image:
        /* THE REVEAL. Extra cover over the hero's left, where the type sits, so
           the wash underneath can stay light enough to show the photograph on
           the right. It is a RADIAL and it lives on this element rather than on
           .fp-hero, for two related reasons:

           - A radial falls off in every direction at once. It has no edge to
             cut and no knee to fade, so it cannot draw a band the way the old
             masked diagonal overlay did.
           - Being on the same element as the wash puts both in the same
             coordinate space. The previous attempt faded the overlay in
             percentages of the HERO while the wash rose in percentages of the
             DOCUMENT; the two ramps never met, and the gap between them was
             the pale stripe through the buttons and the status rail.

           Its geometry is in document percentages, so the ellipse tracks the
           hero's share of the page as content grows. It does not track it
           exactly - it does not need to, because by the time the wash has
           reached wash-a the residual here is under 0.02 alpha, which is
           nothing. */
        radial-gradient(ellipse 95% 17% at -2% 6%,
            rgba(5, 20, 38, 0.80) 26%,
            rgba(5, 20, 38, 0.58) 68%,
            rgba(5, 20, 38, 0.34) 90%,
            rgba(5, 20, 38, 0) 100%),
        /* THE WASH. */
        linear-gradient(180deg,
            rgba(5, 20, 38, 0.56) 0%,
            rgba(5, 20, 38, 0.62) 10%,
            var(--fp-wash-a) var(--fp-stop-hero),
            var(--fp-wash-b) var(--fp-stop-about),
            var(--fp-wash-c) var(--fp-stop-services),
            var(--fp-wash-b) var(--fp-stop-location),
            var(--fp-wash-a) var(--fp-stop-contact),
            var(--fp-ink-deep) 100%);
}

/* The two layers are budgeted together. Composited, they put ~0.80 over the
   hero copy - the floor at which --fp-text-soft clears 4.5:1 against a white
   photo pixel - while leaving the top right at 0.56 so the picture reads.
   Raising the wash without lowering the radial darkens the reveal away;
   lowering the radial without raising the wash fails the body copy. */

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

/* Bootstrap's reboot sets ul{padding-left:2rem}, and an element selector beats
   the universal reset above no matter which stylesheet loads last - so without
   this every list on the page sits indented 32px from the card it is in. */
.fp-nav-list,
.fp-hours,
.fp-contact-list {
    padding-left: 0;
}

a {
    color: inherit;
}

/* One focus treatment for the whole page. Each ground supplies its own
   --fp-focus, so the ring stays visible on navy and on paper alike.
   The outline follows each element's own border-radius, so nothing here sets
   one - doing so would square off the pill buttons the moment they take focus.

   .fp-nav-toggle is listed by class rather than left to the element selector:
   Bootstrap's .navbar-toggler:focus{outline:0} out-specifies a bare
   button:focus-visible and would leave the menu button with no ring at all. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
.fp-nav-toggle:focus-visible {
    outline: 3px solid var(--fp-focus);
    outline-offset: 3px;
}

.fp-skip {
    position: absolute;
    left: 16px;
    top: -100px;
    z-index: 2000;
    padding: 12px 20px;
    border-radius: var(--fp-radius-sm);
    background: #FFFFFF;
    color: var(--fp-ink);
    font-weight: 600;
    text-decoration: none;
    box-shadow: 0 10px 30px rgba(6, 21, 36, 0.35);
    transition: top 0.2s var(--fp-ease);
}

.fp-skip:focus {
    top: 12px;
}

.fp-shell {
    width: 100%;
    max-width: var(--fp-shell);
    margin-inline: auto;
    padding-inline: var(--fp-gutter);
}

.fp-section {
    padding-block: var(--fp-section-y);
    position: relative;
}

/* Visually hidden but read aloud - used for the headings that give each
   landmark a name without printing a second title on screen. */
.fp-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ===========================
   TYPE SYSTEM
   Poppins is the only self-hosted family (public/fonts/poppins), so the
   personality has to come from the weight range rather than a second face.
   All six weights 300-800 are downloaded; the page uses 300/400/500/600/700.
   =========================== */
.fp-title {
    font-size: clamp(1.75rem, 3.2vw, 2.6rem);
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: var(--fp-text);
    text-wrap: balance;
}

.fp-lede {
    margin-top: var(--fp-3);
    font-size: 1.0625rem;
    font-weight: 400;
    color: var(--fp-text-soft);
    max-width: 56ch;
}

.fp-card-title {
    font-size: 1.0625rem;
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: -0.01em;
    color: var(--fp-text);
}

.fp-card-body {
    margin-top: var(--fp-2);
    font-size: 0.9375rem;
    line-height: 1.65;
    color: var(--fp-text-soft);
}

.fp-card-action {
    margin-top: var(--fp-4);
}

/* ===========================
   MOTION

   Two systems. The HERO plays once on load as an orchestrated sequence; every
   section below it is driven by scroll and reverses when it leaves, so the
   page animates going down AND coming back up.

   The easings are CSS equivalents of the standard GSAP curves, so the timing
   reads the way the presets these are modelled on do:
     --fp-ease-out   power2.out      arriving, the default
     --fp-ease-expo  expo.out        long travel: the hero title wipe, the navbar
     --fp-ease-back  back.out(1.4)   a small overshoot, for cards landing

   EVERYTHING HERE IS GATED ON html.fp-js, which a one-line script sets before
   the body paints. Without it a browser that fails to run the page script
   would be left with a document full of opacity:0 - the animation system must
   never be able to hide the content it is animating.
   =========================== */
/* Prefixed with html.fp-js so it out-specifies component rules that set their
   own `transition` shorthand - .fp-card's hover transition is a single class
   and would otherwise win, dropping the reveal to that rule's 0.25s. */
html.fp-js [data-reveal] {
    opacity: 0;
    transition-property: opacity, transform, clip-path;
    transition-duration: var(--fp-dur);
    transition-timing-function: var(--fp-ease-out);
    transition-delay: calc(var(--i, 0) * var(--fp-stagger));
}

html.fp-js [data-reveal].is-in {
    opacity: 1;
    transform: none;
}

/* Leaving is faster than arriving and never staggered. A reversed exit that
   replays the entrance timing feels like the page is fighting the scroll. */
html.fp-js [data-reveal]:not(.is-in) {
    transition-duration: var(--fp-dur-fast);
    transition-delay: 0s;
}

/* --fp-dir is flipped to -1 by the observer when an element enters from the
   TOP of the viewport, i.e. while scrolling up, so it always travels towards
   its resting place rather than away from it. */
html.fp-js [data-reveal="rise"] {
    transform: translate3d(0, calc(var(--fp-dir, 1) * 28px), 0);
}

html.fp-js [data-reveal="fade"] {
    transform: none;
}

html.fp-js [data-reveal="pop"] {
    transform: translate3d(0, calc(var(--fp-dir, 1) * 20px), 0) scale(0.94);
    transition-timing-function: var(--fp-ease-back);
}

html.fp-js [data-reveal="left"] {
    transform: translate3d(-30px, 0, 0);
}

html.fp-js [data-reveal="right"] {
    transform: translate3d(34px, 0, 0) scale(0.98);
    transition-duration: var(--fp-dur-slow);
}

/* ---------------------------
   PAGE LOAD
   --------------------------- */
html.fp-js .fp-nav {
    transform: translate3d(0, -100%, 0);
    transition: transform 0.9s var(--fp-ease-expo);
}

html.fp-js.fp-ready .fp-nav {
    transform: none;
}

/* The clinic name wipes up word by word behind its own baseline rather than
   just fading - the lockup is the one place on the page worth a real entrance.
   The negative bottom inset keeps descenders from being clipped at rest. */
html.fp-js .fp-hero-title span {
    opacity: 0;
    clip-path: inset(105% 0 -0.2em 0);
    transform: translate3d(0, 0.24em, 0);
    transition:
        opacity 0.55s linear,
        clip-path var(--fp-dur-slow) var(--fp-ease-expo),
        transform var(--fp-dur-slow) var(--fp-ease-expo);
}

html.fp-js .fp-hero.is-loaded .fp-hero-title span {
    opacity: 1;
    clip-path: inset(-0.06em 0 -0.2em 0);
    transform: none;
}

/* These MUST stay under html.fp-js. The rule above sets `transition` as a
   shorthand, which resets transition-delay to 0, and it carries an extra class
   from the html.fp-js prefix - unprefixed, these lose the cascade and all the
   words wipe at once instead of one after another. */
html.fp-js .fp-hero-title span:nth-child(1) { transition-delay: 200ms; }
html.fp-js .fp-hero-title span:nth-child(2) { transition-delay: 340ms; }
html.fp-js .fp-hero-title span:nth-child(3) { transition-delay: 480ms; }
html.fp-js .fp-hero-title span:nth-child(n+4) { transition-delay: 620ms; }

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

    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        transition-delay: 0s !important;
        scroll-behavior: auto !important;
    }

    html.fp-js [data-reveal],
    html.fp-js [data-reveal].is-in,
    html.fp-js .fp-hero-title span {
        opacity: 1;
        transform: none;
        clip-path: none;
    }

    html.fp-js .fp-nav {
        transform: none;
    }
}

/* ===========================
   NAVBAR
   =========================== */
.fp-nav {
    position: sticky;
    top: 0;
    z-index: 1000;
    min-height: var(--fp-nav-h);
    padding: 0;
    background: rgba(6, 21, 36, 0.72);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    backdrop-filter: blur(14px) saturate(140%);
    --fp-focus: #22D3EE;
}

/* Older Android browsers ignore backdrop-filter and would leave the bar
   translucent over the page text. Same fallback the auth panel uses. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    .fp-nav {
        background: rgba(6, 21, 36, 0.94);
    }
}

.fp-nav > .fp-shell {
    display: flex;
    align-items: center;
    gap: var(--fp-4);
    min-height: var(--fp-nav-h);
}

/* The hero already prints the logo and clinic name at full size, so the bar
   brand would be a duplicate above the fold. It fades in once the hero is
   scrolled past, where it becomes the only thing naming the page. */
.fp-nav-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    margin-right: auto;
    text-decoration: none;
    color: #F3F8FC;
    opacity: 0;
    transform: translateY(-6px);
    pointer-events: none;
    transition: opacity 0.35s var(--fp-ease), transform 0.35s var(--fp-ease);
}

.fp-nav.is-scrolled .fp-nav-brand {
    opacity: 1;
    transform: none;
    pointer-events: auto;
}

.fp-nav-brand img {
    height: 30px;
    width: auto;
}

.fp-nav-brand span {
    font-size: 0.9375rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    white-space: nowrap;
}

.fp-nav-list {
    display: flex;
    align-items: center;
    gap: var(--fp-1);
    list-style: none;
    margin: 0;
}

.fp-nav-link {
    display: block;
    padding: 10px 14px;
    border-radius: var(--fp-radius-sm);
    font-size: 0.9375rem;
    font-weight: 500;
    color: rgba(243, 248, 252, 0.82);
    text-decoration: none;
    transition: color 0.2s var(--fp-ease), background-color 0.2s var(--fp-ease);
}

.fp-nav-link:hover {
    color: #FFFFFF;
    background: rgba(255, 255, 255, 0.08);
}

/* Current section is marked with weight and a rule, not colour alone. */
.fp-nav-link[aria-current="true"] {
    color: #FFFFFF;
    font-weight: 600;
    box-shadow: inset 0 -2px 0 var(--fp-teal-lift);
}

.fp-nav-cta {
    margin-left: var(--fp-2);
}

/* Bootstrap's .navbar-expand-md owns the show/hide of this panel and of the
   toggler below (display:flex!important / display:none above 768px), which is
   why the mobile rules further down break at 767.98px and not at some tidier
   number - a mismatch would leave a band of widths with neither a menu nor a
   toggle. */
.fp-nav-panel {
    align-items: center;
    justify-content: flex-end;
    gap: var(--fp-2);
    flex-grow: 0;
}

.fp-nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-left: auto;
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: var(--fp-radius-sm);
    background: rgba(255, 255, 255, 0.06);
    color: #F3F8FC;
    font-size: 1.1rem;
    cursor: pointer;
}

/* ===========================
   BUTTONS
   =========================== */
.fp-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--fp-2);
    min-height: 46px;
    padding: 12px 26px;
    border: 1px solid transparent;
    border-radius: 999px;
    font-family: inherit;
    font-size: 0.9375rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    text-decoration: none;
    cursor: pointer;
    transition:
        background-color 0.22s var(--fp-ease),
        border-color 0.22s var(--fp-ease),
        color 0.22s var(--fp-ease),
        box-shadow 0.22s var(--fp-ease),
        transform 0.22s var(--fp-ease);
}

.fp-btn-primary {
    background: var(--fp-teal);
    border-color: var(--fp-teal);
    color: #FFFFFF;
    box-shadow: 0 10px 26px -12px rgba(14, 116, 144, 0.9);
}

.fp-btn-primary:hover {
    background: var(--fp-teal-deep);
    border-color: var(--fp-teal-deep);
    color: #FFFFFF;
    transform: translateY(-1px);
}

.fp-btn-ghost {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.38);
    color: #FFFFFF;
}

.fp-btn-ghost:hover {
    background: rgba(255, 255, 255, 0.16);
    border-color: #FFFFFF;
    color: #FFFFFF;
    transform: translateY(-1px);
}

.fp-btn-sm {
    min-height: 40px;
    padding: 8px 18px;
    font-size: 0.875rem;
}

.fp-btn-quiet {
    background: transparent;
    border-color: rgba(255, 255, 255, 0.28);
    color: var(--fp-text);
}

.fp-btn-quiet:hover {
    border-color: var(--fp-teal-lift);
    color: var(--fp-teal-lift);
    background: rgba(255, 255, 255, 0.06);
}

/* ===========================
   SECTION 1: HERO
   =========================== */
/* NO background and NO overlay of its own. The photograph comes from
   body::before and the wash from body::after, both of which run the length of
   the document.

   The hero used to add a diagonal overlay on top - dark where the type sat,
   clearing to the right to show the subject - masked so it faded out before
   the hero's bottom edge. It looked good in isolation and was wrong: that
   fade-out is a ramp measured in percentages of the HERO, while the wash
   underneath ramps up in percentages of the DOCUMENT. The two never line up,
   so between where the overlay had faded and where the wash had risen there
   was a pale horizontal band straight through the buttons and the status rail.

   Two ramps in two coordinate spaces cannot be reconciled by tuning them.
   There is one wash now, in one direction, on one element. Nothing is left
   that can band. */
.fp-hero {
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: min(820px, calc(100svh - var(--fp-nav-h)));
}

/* The lockup takes the space the rail does not, so the rail always finishes
   flush with the bottom of the first screen instead of being pushed under the
   fold by a long clinic name or description. */
.fp-hero-body {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    padding-block: clamp(20px, 3vw, 44px);
}

.fp-hero-inner {
    max-width: 680px;
}

/* The logo stands on the photograph directly - no tile, no border. The drop
   shadow is not decoration: the mark is dark red on a photograph whose
   brightness the clinic controls from Settings, and without it the logo
   disappears against a dark upload. It is a shadow, not a surface. */
.fp-hero-mark {
    display: flex;
    width: fit-content;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--fp-4);
    filter: drop-shadow(0 2px 10px rgba(3, 12, 22, 0.55));
}

.fp-hero-mark img {
    height: 76px;
    width: auto;
}

/* The clinic name is printed one word per line, and the last word carries the
   weight. Settings prints the name in capitals; the contrast between the light
   leading words and the heavy final one is what makes the lockup read as a
   mark rather than as a run of shouted text. */
.fp-hero-title {
    font-size: clamp(2.5rem, 6.4vw, 4rem);
    line-height: 0.98;
    letter-spacing: -0.03em;
    margin-bottom: var(--fp-4);
}

.fp-hero-title span {
    display: block;
    font-weight: 300;
    color: #E4EEF6;
}

.fp-hero-title span:last-child {
    font-weight: 700;
    color: #FFFFFF;
}

.fp-hero-text {
    font-size: 1.0625rem;
    line-height: 1.7;
    color: var(--fp-text-soft);
    max-width: 52ch;
    margin-bottom: var(--fp-5);
}

.fp-hero-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--fp-3);
    margin-bottom: var(--fp-3);
}

.fp-hero-note {
    font-size: 0.875rem;
    color: var(--fp-text-soft);
}

/* On a phone the hero text runs the full width of the picture rather than
   sitting in a column down one side, so it needs cover everywhere and the run
   starts deeper. Same single layer, same single direction. */
@media (max-width: 767.98px) {
    body::after {
        background-image: linear-gradient(180deg,
            rgba(5, 20, 38, 0.86) 0%,
            rgba(5, 20, 38, 0.89) 10%,
            var(--fp-wash-a) var(--fp-stop-hero),
            var(--fp-wash-b) var(--fp-stop-about),
            var(--fp-wash-c) var(--fp-stop-services),
            var(--fp-wash-b) var(--fp-stop-location),
            var(--fp-wash-a) var(--fp-stop-contact),
            var(--fp-ink-deep) 100%);
    }
}

/* Short laptop screens (1366x768 and the like) are the common case here, and
   at full size the lockup pushes the status rail half under the fold. Trimming
   the lockup rather than the rail keeps the three answers on the first screen,
   which is the whole point of the hero. */
@media (min-width: 768px) and (max-height: 820px) {
    .fp-hero-mark img {
        height: 52px;
    }

    .fp-hero-title {
        font-size: clamp(2.2rem, 5vw, 3.25rem);
    }

    .fp-hero-text {
        font-size: 1rem;
        margin-bottom: var(--fp-4);
    }

    .fp-hero-body {
        padding-block: clamp(16px, 2.5vw, 32px);
    }
}

/* ---------------------------
   THE STATUS RAIL
   The one loud element on the page. It answers the three questions a patient
   arrives with - are you open, where are you, what is your number - from the
   clinic's own Settings data, so nothing on it is decorative.
   --------------------------- */
/* No panel, no fill, no dividers. The rail carries nothing but three icons and
   their text, sitting straight on the hero wash - boxed and tinted it read as
   a footer bolted to the bottom of the first screen rather than as part of it.
   The bottom of the hero wash is the deepest colour on the page, which is what
   lets the rail hold contrast without a surface of its own. */
.fp-hero-rail {
    flex: 0 0 auto;
    padding-bottom: clamp(20px, 3vw, 36px);
}

.fp-rail {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--fp-5);
}

.fp-rail-item {
    display: flex;
    align-items: flex-start;
    gap: var(--fp-3);
    min-height: 44px;
    text-decoration: none;
    color: inherit;
}

a.fp-rail-item:hover .fp-rail-value,
a.fp-rail-item:hover .fp-rail-sub {
    color: var(--fp-teal-lift);
}

.fp-rail-item > i {
    font-size: 0.9375rem;
    color: var(--fp-teal-lift);
    margin-top: 5px;
    flex-shrink: 0;
}

.fp-rail-label {
    display: block;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #9FBACF;
    margin-bottom: 2px;
}

.fp-rail-value {
    display: block;
    font-size: 0.9375rem;
    font-weight: 600;
    line-height: 1.45;
    color: #FFFFFF;
    overflow-wrap: anywhere;
}

.fp-rail-sub {
    display: block;
    font-size: 0.8125rem;
    font-weight: 400;
    color: #B9CDDE;
    font-variant-numeric: tabular-nums;
}

/* Status dot. The state is carried by the word next to it as well as the
   colour, so the dot is reinforcement rather than the only signal. */
.fp-status {
    display: inline-flex;
    align-items: center;
    gap: var(--fp-2);
}

.fp-status-dot {
    position: relative;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

.fp-status-open {
    color: var(--fp-open-lift);
}

.fp-status-closing_soon {
    color: var(--fp-soon-lift);
}

.fp-status-closed,
.fp-status-unknown {
    color: #93AAC0;
}

.fp-status-open .fp-status-dot::after {
    content: "";
    position: absolute;
    inset: -5px;
    border-radius: 50%;
    border: 1px solid currentColor;
    opacity: 0.55;
    animation: fp-pulse 2.6s var(--fp-ease) infinite;
}

@keyframes fp-pulse {
    0% { transform: scale(0.7); opacity: 0.6; }
    70% { transform: scale(1.35); opacity: 0; }
    100% { transform: scale(1.35); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .fp-status-open .fp-status-dot::after {
        animation: none;
        opacity: 0.4;
        transform: scale(1);
    }
}

.fp-rail-value .fp-status-word {
    color: currentColor;
}

/* ===========================
   CARDS
   One card language for values, services and contacts. Hairline border, small
   accent tile for the icon, restrained hover - the loud element on this page
   is the status rail, and everything below it stays quiet on purpose.
   =========================== */
/* Translucent, never opaque: a solid card would punch a hole in the photograph
   the section is standing on. The lift is a white tint over whatever wash is
   behind it, so a card reads as the same object in all four sections while
   still taking that section's colour. */
.fp-card {
    display: flex;
    flex-direction: column;
    padding: var(--fp-5);
    border-radius: var(--fp-radius);
    background: var(--fp-ink-lift);
    border: 1px solid var(--fp-hairline);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    /* The hover lift uses `translate`, NOT `transform`. Cards are also reveal
       targets, and the reveal owns `transform` for the better part of a second
       as the card comes in. Sharing one property means the two fight: the
       card's 0.25s hover transition wins the cascade and the reveal snaps in
       at a quarter of its intended duration. `translate` is an independent
       property that composes with `transform`, so each side animates its own
       thing and neither can clobber the other. */
    transition:
        background-color 0.25s var(--fp-ease),
        border-color 0.25s var(--fp-ease),
        translate 0.25s var(--fp-ease);
}

.fp-card:hover {
    background: var(--fp-ink-lift-strong);
    border-color: rgba(255, 255, 255, 0.26);
    translate: 0 -2px;
}

.fp-tile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    margin-bottom: var(--fp-4);
    border-radius: 12px;
    background: rgba(34, 211, 238, 0.14);
    color: var(--fp-teal-lift);
    font-size: 1.0625rem;
    flex-shrink: 0;
}

/* ===========================
   SECTION 2: ABOUT
   =========================== */
/* Heading and subtitle centred, body copy centred as a block but ranged left -
   centred paragraphs lose the left edge the eye returns to on every line. */
.fp-about-intro {
    max-width: 720px;
    margin-inline: auto;
    text-align: center;
}

.fp-about-intro .fp-lede {
    margin-inline: auto;
}

.fp-about-body {
    max-width: 68ch;
    margin: clamp(28px, 4vw, 44px) auto 0;
}

.fp-about-body p {
    font-size: 1rem;
    line-height: 1.8;
    color: var(--fp-text-soft);
}

.fp-about-body p + p {
    margin-top: var(--fp-4);
}

/* The first paragraph carries the weight of a lede - it is the one sentence
   most readers will finish. */
.fp-about-body p:first-child {
    font-size: 1.0625rem;
    color: var(--fp-text);
}

.fp-values {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--fp-4);
    margin-top: clamp(40px, 5vw, 64px);
}

/* ===========================
   SECTION 3: SERVICES
   =========================== */
.fp-services-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--fp-4);
    margin-top: clamp(32px, 4vw, 52px);
}

/* ===========================
   SECTION 4: LOCATION
   =========================== */
.fp-location-grid {
    display: grid;
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
    gap: clamp(28px, 4vw, 56px);
    align-items: start;
    margin-top: clamp(32px, 4vw, 48px);
}

.fp-location-stack {
    display: flex;
    flex-direction: column;
    gap: var(--fp-4);
}

.fp-address {
    font-size: 1rem;
    line-height: 1.65;
    color: var(--fp-text);
}

/* Hours are printed as a two-column list rather than a run of <br>-joined
   prose, so the day range and the times line up down the block. Tabular
   figures keep the times from shifting column to column. */
.fp-hours {
    list-style: none;
    margin: var(--fp-3) 0 0;
    display: flex;
    flex-direction: column;
    gap: var(--fp-1);
}

.fp-hours li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--fp-3);
    padding: 9px 0;
    border-bottom: 1px solid var(--fp-hairline);
    font-size: 0.9375rem;
}

.fp-hours li:last-child {
    border-bottom: 0;
}

.fp-hours .fp-hours-day {
    color: var(--fp-text);
    font-weight: 500;
}

.fp-hours .fp-hours-time {
    color: var(--fp-text-soft);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.fp-map {
    margin: 0;
    border-radius: var(--fp-radius-lg);
    overflow: hidden;
    border: 1px solid var(--fp-hairline);
    background: var(--fp-ink-lift);
}

.fp-map img {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
}

.fp-map figcaption {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--fp-3);
    padding: var(--fp-3) var(--fp-4);
    font-size: 0.8125rem;
    color: var(--fp-text-soft);
    border-top: 1px solid var(--fp-hairline);
}

.fp-map figcaption a {
    display: inline-block;
    padding-block: 6px;
    color: var(--fp-link);
    font-weight: 500;
    text-decoration: none;
    white-space: nowrap;
}

.fp-map figcaption a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* ===========================
   SECTION 5: CONTACT
   =========================== */
.fp-contact-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--fp-4);
    margin-top: clamp(32px, 4vw, 52px);
}

.fp-contact-list {
    list-style: none;
    margin: var(--fp-3) 0 0;
    display: flex;
    flex-direction: column;
    gap: var(--fp-2);
}

.fp-contact-list li {
    font-size: 0.9375rem;
    line-height: 1.5;
}

.fp-contact-list a {
    display: inline-block;
    padding-block: 5px;
    color: var(--fp-link);
    text-decoration: none;
    font-weight: 500;
    overflow-wrap: anywhere;
    border-radius: 4px;
}

.fp-contact-list a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.fp-contact-list .fp-social {
    display: flex;
    align-items: center;
    gap: var(--fp-2);
    color: var(--fp-text-soft);
}

.fp-contact-list .fp-social i {
    color: var(--fp-teal-lift);
    width: 18px;
    text-align: center;
    flex-shrink: 0;
}

.fp-contact-empty {
    font-size: 0.9375rem;
    color: var(--fp-text-soft);
    margin-top: var(--fp-3);
}

/* ===========================
   FOOTER
   =========================== */
/* No background: the last stops of body::after close the run out on solid ink
   here, so the photograph fades away under the footer rather than being cut
   off at a hard edge. */
.fp-footer {
    color: #B9CDDE;
    padding-block: clamp(40px, 5vw, 64px) var(--fp-5);
}

.fp-footer-top {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--fp-6);
    padding-bottom: var(--fp-5);
    border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}

.fp-footer-brand {
    display: flex;
    align-items: center;
    gap: var(--fp-3);
    max-width: 40ch;
}

.fp-footer-brand img {
    height: 44px;
    width: auto;
}

.fp-footer-brand strong {
    display: block;
    color: #F3F8FC;
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -0.01em;
}

/* Wraps the name and the address line under it. The size set here is the
   address's; the name overrides it from the rule above. */
.fp-footer-brand span {
    display: block;
    font-size: 0.8125rem;
    line-height: 1.5;
}

/* The open/closed answer, in the slot the section links used to hold. Ranged
   right on a wide footer so it sits opposite the brand rather than drifting
   mid-row. */
.fp-footer-status {
    text-align: right;
}

.fp-footer-label {
    display: block;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #8FA8BF;
    margin-bottom: 5px;
}

/* The dot sits inside this, so it has to be a flex row like the rail's copy -
   .fp-status only sets the colour, never the layout.

   Deliberately sets NO colour: the dot draws with currentColor and the state
   classes (.fp-status-open / -closing_soon / -closed) are what supply it. A
   colour here is the same specificity and comes later in the file, so it wins
   and the footer loses the green/amber/grey entirely. The colour is only
   hard-set on the anchor below, which is the one variant that carries no
   state class. */
.fp-footer-status-value {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--fp-2);
    font-size: 0.9375rem;
    font-weight: 600;
    line-height: 1.4;
    text-decoration: none;
}

a.fp-footer-status-value {
    color: #FFFFFF;
}

a.fp-footer-status-value:hover {
    color: var(--fp-teal-lift);
    text-decoration: underline;
    text-underline-offset: 4px;
}

.fp-footer-status-detail {
    display: block;
    margin-top: 2px;
    font-size: 0.8125rem;
    color: #B9CDDE;
    font-variant-numeric: tabular-nums;
}

.fp-footer-base {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--fp-3);
    padding-top: var(--fp-5);
    font-size: 0.8125rem;
}

.fp-footer-base a {
    padding-block: 4px;
    color: #B9CDDE;
    text-decoration: none;
    border-radius: 4px;
    overflow-wrap: anywhere;
}

.fp-footer-base a:hover {
    color: var(--fp-teal-lift);
    text-decoration: underline;
    text-underline-offset: 4px;
}

/* ===========================
   RESPONSIVE
   Two breakpoints do the work: 900px folds the two-column sections into one,
   680px switches the navbar to its dropdown and the rail to a stack.
   =========================== */
@media (max-width: 1024px) {
    .fp-values {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .fp-services-grid,
    .fp-contact-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 900px) {
    .fp-location-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .fp-rail {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--fp-4);
    }
}

/* Navbar only - tied to Bootstrap's navbar-expand-md breakpoint. */
@media (max-width: 767.98px) {
    .fp-nav-brand {
        opacity: 1;
        transform: none;
        pointer-events: auto;
        margin-right: 0;
    }

    /* Bootstrap's collapse drives this panel, so the rules below only restyle
       it - .collapse/.show keep their own display handling. */
    .fp-nav-panel {
        position: absolute;
        top: calc(100% + 8px);
        right: var(--fp-gutter);
        left: var(--fp-gutter);
        padding: var(--fp-3);
        border-radius: var(--fp-radius);
        background: rgba(6, 21, 36, 0.96);
        border: 1px solid rgba(255, 255, 255, 0.14);
        box-shadow: 0 24px 50px rgba(3, 10, 22, 0.45);
    }

    .fp-nav-list {
        flex-direction: column;
        align-items: stretch;
        gap: 2px;
        width: 100%;
    }

    .fp-nav-link {
        padding: 12px 14px;
    }

    .fp-nav-cta {
        margin: var(--fp-2) 0 0;
        width: 100%;
    }
}

@media (max-width: 680px) {
    .fp-values,
    .fp-services-grid,
    .fp-contact-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .fp-hero {
        min-height: 0;
    }

    .fp-hero-mark img {
        height: 56px;
    }

    /* Full width and stacked. Side by side at this width the longer label
       wraps and the two buttons end up different heights. */
    .fp-hero-actions .fp-btn {
        flex: 1 1 100%;
    }

    .fp-footer-top {
        flex-direction: column;
        gap: var(--fp-5);
    }

    /* Stacked, there is nothing on the other side of the row to range against. */
    .fp-footer-status {
        text-align: left;
    }

    .fp-footer-status-value {
        justify-content: flex-start;
    }
}

/* 360px phones. Type stays at readable sizes here - the previous design
   dropped card body copy to 0.7rem (11px) at this width, under the 12px floor
   and well under the 16px the form inputs need to avoid iOS zoom. */
@media (max-width: 400px) {
    .fp-card {
        padding: var(--fp-4);
    }

    .fp-rail-item {
        padding: var(--fp-3) var(--fp-4);
    }

    .fp-card-body,
    .fp-contact-list li,
    .fp-hours li {
        font-size: 0.875rem;
    }
}

/* ===========================
   LEGACY UTILITIES
   Still referenced by the auth modal markup in FrontPageViews/.
   =========================== */
.input-wrapper {
    position: relative;
}

.input-wrapper .error {
    font-size: 0.75rem;
    margin-top: 3px;
    display: block;
}

.input-invalid {
    border-color: #dc3545 !important;
}

/* ===========================
   SCROLLBAR
   =========================== */
::-webkit-scrollbar {
    width: 10px;
}

::-webkit-scrollbar-track {
    background: #0A1C2E;
}

::-webkit-scrollbar-thumb {
    background: #2E4C68;
    border-radius: 5px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--fp-teal-lift);
}
