/* ─── Shared base ─────────────────────────────────────────── */
/* clip, not hidden: hidden turns html/body into scroll containers, which
   silently disables every position:sticky descendant (the card deck needs
   it). clip crops overflow without creating a scroll container. */
html, body { max-width: 100%; overflow-x: clip !important; }
body { font-family: 'Inter', system-ui, sans-serif; }

/* ─── Resizable navbar (vanilla port of Aceternity <Navbar>) ───
   Full-width and transparent at the top of the page; past 100px of scroll the
   bar narrows into a floating translucent pill with blur + shadow. The React
   version drives this with a Framer spring; here it's a CSS transition. */
:root {
    --nav-shadow: 0 0 24px rgba(34,42,53,.06), 0 1px 1px rgba(0,0,0,.05),
                  0 0 0 1px rgba(34,42,53,.04), 0 0 4px rgba(34,42,53,.08),
                  0 16px 68px rgba(47,48,55,.05), 0 1px 0 rgba(255,255,255,.1) inset;
}

.nav-body,
.mobile-nav {
    transition: max-width .45s cubic-bezier(.16,1,.3,1),
                transform  .45s cubic-bezier(.16,1,.3,1),
                background-color .3s ease, box-shadow .3s ease,
                border-radius .3s ease, padding .3s ease;
    background-color: transparent;
}

/* Scrolled state — desktop pill */
.nav-scrolled .nav-body {
    max-width: 64rem;
    transform: translateY(12px);
    /* Near-solid: the pill floats over the dark finale at the end of the
       page, and at .8 it picked up the backdrop and read as grey. */
    background-color: rgba(255,255,255,.95);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    box-shadow: var(--nav-shadow);
}
/* The phone number folds away when the bar narrows, so the links keep room. */
.nav-scrolled .nav-phone { opacity: 0; width: 0; margin: 0; pointer-events: none; overflow: hidden; }
.nav-phone { transition: opacity .25s ease, width .35s ease; }

/* Scrolled state — mobile bar */
.nav-scrolled .mobile-nav {
    max-width: calc(100vw - 2rem);
    transform: translateY(10px);
    padding-left: 12px; padding-right: 12px;
    border-radius: 1rem;
    background-color: rgba(255,255,255,.95);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    box-shadow: var(--nav-shadow);
}

/* The navbar is fixed, so anchor targets must clear it when jumped to. */
section[id], [id]:target { scroll-margin-top: 84px; }

/* Nav item hover pill (CSS stand-in for the shared layoutId pill) */
.nav-link { position: relative; border-radius: 9999px; transition: background-color .2s ease, color .2s ease; }
.nav-link:hover { background-color: #f1f5f9; }

/* Mobile dropdown panel — .menu-open is mirrored from #mobileMenu's .hidden by JS */
#mobileMenu { box-shadow: var(--nav-shadow); }
#menuClose { display: none; }
.mobile-nav.menu-open #menuOpen  { display: none; }
.mobile-nav.menu-open #menuClose { display: inline-flex; }

@media (prefers-reduced-motion: reduce) {
    .nav-body, .mobile-nav, .nav-phone { transition: none; }
}

/* ─── Infinite moving cards (vanilla port of Aceternity <InfiniteMovingCards>) ───
   The list is duplicated by JS, so translating it -50% (minus half the 1rem gap)
   lands exactly on the copy and loops seamlessly. Animation only starts once the
   clones exist, which is what data-animated gates. */
.scroller {
    -webkit-mask-image: linear-gradient(to right, transparent, white 20%, white 80%, transparent);
            mask-image: linear-gradient(to right, transparent, white 20%, white 80%, transparent);
}
.scroller[data-animated="true"] .scroller__inner {
    animation: scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite;
}
.scroller[data-animated="true"] .scroller__inner:hover { animation-play-state: paused; }
@keyframes scroll { to { transform: translate(calc(-50% - 0.5rem)); } }
@media (prefers-reduced-motion: reduce) {
    .scroller[data-animated="true"] .scroller__inner { animation: none; }
}

/* ─── ShinyText (vanilla port of React Bits <ShinyText>) ───
   A gradient clipped to the glyphs, swept across by animating
   background-position. The React version drives that single value with
   requestAnimationFrame; a @keyframes does the same for free. The 150% → -50%
   range mirrors its `useTransform(progress, p => 150 - p * 2)`.
   Per-instance colour/speed via the custom properties below. */
.shiny-text {
    display: inline-block;
    background-image: linear-gradient(
        var(--shiny-spread, 120deg),
        var(--shiny-color, #b5b5b5) 0%,
        var(--shiny-color, #b5b5b5) 35%,
        var(--shiny-shine, #fff) 50%,
        var(--shiny-color, #b5b5b5) 65%,
        var(--shiny-color, #b5b5b5) 100%
    );
    background-size: 200% auto;
    background-position: 150% center;
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: shinyText var(--shiny-speed, 3s) linear infinite;
}
@keyframes shinyText {
    from { background-position: 150% center; }
    to   { background-position: -50% center; }
}
@media (prefers-reduced-motion: reduce) {
    /* Hold the base colour rather than sweeping. */
    .shiny-text { animation: none; background-position: 50% center; }
}

/* ─── Who We Serve – audience split panel ─────────────────
   Two background halves meeting on a diagonal, with a soft blurred seam. The
   halves are clipped, but the TEXT lives in a separate grid layer above them —
   otherwise the clip-path would cut through the copy near the divider.
   Desktop only: below md the halves stack and each cell paints its own bg. */
.audience-split__half { position: absolute; inset: 0; }
.audience-split__half--left  { clip-path: polygon(0 0, 57% 0, 43% 100%, 0 100%); }
.audience-split__half--right { clip-path: polygon(57% 0, 100% 0, 100% 100%, 43% 100%); }

/* The photo is not flatly desaturated. A grey base carries a full-colour copy on
   top, revealed by a vertical gradient mask: colour returns toward the bottom
   and fades out under the heading, so the copy stays legible. `--gray` keeps a
   little saturation rather than going to a dead 100% grey. */
.audience-split__img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
}
.audience-split__img--gray  { filter: grayscale(.85); }
.audience-split__img--color {
    -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.75) 35%, rgba(0,0,0,.25) 62%, transparent 85%);
            mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.75) 35%, rgba(0,0,0,.25) 62%, transparent 85%);
}
.audience-split__seam {
    position: absolute; z-index: 1; pointer-events: none;
    top: -12%; bottom: -12%; left: 50%;
    width: 260px;
    transform: translateX(-50%) rotate(8deg);
    background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.30) 45%, rgba(255,255,255,0));
    filter: blur(26px);
}

/* WhatsApp float */
#wa-float { box-shadow: 0 4px 20px rgba(37,211,102,.5); }

/* Brand reference (from Solaraya profile):
   Solar Orange #F7941E · Flame Red-Orange #F74A00 · Charcoal #36363A */

/* Hover lifts — desktop only */
.lift { transition: transform .25s, box-shadow .25s; }
@media (hover: hover) {
    .lift:hover { transform: translateY(-4px); box-shadow: 0 12px 32px rgba(0,0,0,.10); }
}

/* ─── Landing – selection cards ──────────────────────────── */
/* Address autocomplete rows (Google Places suggestions) */
.addr-row { transition: background-color .15s ease; }
.addr-row:hover, .addr-row.addr-active { background-color: #FFF4E6; }
.addr-row:focus-visible { outline: 2px solid #F7941E; outline-offset: -2px; }

.sel-card { cursor: pointer; transition: border-color .2s, background .2s, color .2s; }
.sel-card.active { border-color: #F7941E !important; background: #FFF4E6; color: #B45309; }
.sel-card.active .sel-icon { color: #F7941E; }
.sel-card.active .sel-label { color: #B45309; font-weight: 700; }

.bill-pill { cursor: pointer; transition: all .2s; }
.bill-pill.active { background: #F7941E; color: white; border-color: #F7941E; }

.state-btn { cursor: pointer; transition: all .15s; }
.state-btn.active { background: #F7941E; color: white; border-color: #F7941E; }

/* Landing – step connector line */
.step-line::after {
    content: '';
    position: absolute;
    top: 28px; left: calc(50% + 28px);
    width: calc(100% - 56px);
    height: 2px;
    background: linear-gradient(90deg, #FCE0BD, #FCE0BD);
    border-top: 2px dashed #F7941E;
}
@media (max-width: 1023px) { .step-line::after { display: none; } }

/* Landing – range slider */
input[type=range] { accent-color: #F7941E; }

/* Landing – savings bar animation */
.savings-bar { transition: width 1s ease; }

/* Landing – featured package card */
.pkg-featured { background: linear-gradient(145deg, #C2410C 0%, #F7941E 100%); }

/* Landing – form field focus */
.field:focus { outline: none; border-color: #F7941E; box-shadow: 0 0 0 3px rgba(247,148,30,.18); }

/* Landing – 6-phase chevron bar */
.phase-bar { display: flex; align-items: stretch; }
.phase-step {
    flex: 1; min-width: 0;
    height: 60px;
    background: #F7941E;
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-weight: 800; font-size: .78rem;
    letter-spacing: .02em; text-align: center; padding: 0 6px;
    position: relative;
    clip-path: polygon(22px 0%, calc(100% - 22px) 0%, 100% 50%, calc(100% - 22px) 100%, 0% 100%, 22px 50%);
    margin-left: -20px;
}
.phase-step:first-child {
    clip-path: polygon(0% 0%, calc(100% - 22px) 0%, 100% 50%, calc(100% - 22px) 100%, 0% 100%);
    margin-left: 0; border-radius: 10px 0 0 10px; padding-left: 12px;
}
.phase-step:nth-child(2n) { background: #D97706; }
.phase-step:nth-child(1){z-index:6} .phase-step:nth-child(2){z-index:5}
.phase-step:nth-child(3){z-index:4} .phase-step:nth-child(4){z-index:3}
.phase-step:nth-child(5){z-index:2} .phase-step:nth-child(6){z-index:1}

/* ─── Landing – Who We Serve spotlight cards ─────────────── */
/* Vanilla port of React Bits <SpotlightCard>: a radial glow tracks the
   cursor. Relies on the card's existing Tailwind bg/border/radius +
   overflow-hidden; only adds the glow layer. */
.card-spotlight {
    position: relative;
    --mouse-x: 50%;
    --mouse-y: 50%;
    --spotlight-color: rgba(247, 148, 30, .15);
}
.card-spotlight::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at var(--mouse-x) var(--mouse-y), var(--spotlight-color), transparent 80%);
    opacity: 0;
    transition: opacity .5s ease;
    pointer-events: none;
    z-index: 0;
}
.card-spotlight:hover::before,
.card-spotlight:focus-within::before { opacity: 1; }
/* Keep card content above the glow layer */
.card-spotlight > * { position: relative; z-index: 1; }

/* ─── CTA star-border (React Bits port) ──────────────────── */
/* Non-invasive wrapper: keeps the wrapped button's own styling and adds an
   animated star-gradient that sweeps the top & bottom edges. Two radial
   blobs (::before top, ::after bottom) travel behind the opaque button; the
   thin 2px vertical padding is the strip where the sweep shows. */
.star-border {
    position: relative;
    display: inline-block;
    padding: 2px 0;                 /* thickness — sweep peeks top & bottom */
    border-radius: var(--star-radius, 0.9rem);
    overflow: hidden;
    isolation: isolate;
    --star-color: #FFE7C2;
    --star-speed: 6s;
}
.star-border--block { display: block; }
.star-border > * { position: relative; z-index: 1; }
.star-border--block > * { width: 100%; }

.star-border::before,
.star-border::after {
    content: '';
    position: absolute;
    width: 300%;
    height: 50%;
    border-radius: 50%;
    opacity: .7;
    z-index: 0;
    pointer-events: none;
    background: radial-gradient(circle, var(--star-color), transparent 10%);
}
.star-border::after  { bottom: -12px; right: -250%; animation: star-movement-bottom var(--star-speed) linear infinite alternate; }
.star-border::before { top: -12px;    left:  -250%; animation: star-movement-top    var(--star-speed) linear infinite alternate; }

@keyframes star-movement-bottom {
    0%   { transform: translate(0%, 0%);    opacity: 1; }
    100% { transform: translate(-100%, 0%); opacity: 0; }
}
@keyframes star-movement-top {
    0%   { transform: translate(0%, 0%);   opacity: 1; }
    100% { transform: translate(100%, 0%); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .star-border::before, .star-border::after { animation: none; opacity: 0; }
}

/* ─── Portfolio – filter pills ───────────────────────────── */
.filter-pill { cursor: pointer; transition: all .2s; }
.filter-pill.active { background: #F7941E; color: #fff; border-color: #F7941E; }

/* Portfolio – project card image placeholder */
.proj-img {
    width: 100%; aspect-ratio: 16/10;
    display: flex; align-items: center; justify-content: center;
    position: relative; overflow: hidden;
}

/* Portfolio – lightbox */
#lightbox { transition: opacity .25s; }

/* ── 3D tilt + glare cards (vanilla port of React TiltCard) ── */
/* Transform is written directly per-rAF while hovering, so it is NOT transitioned
   here (that would interpolate every frame and lag). box-shadow/border-color still
   ease. On leave, .tilt-return eases the flatten back. will-change is toggled by JS
   only during a hover, so idle cards are never promoted to their own layer. */
.tilt-card { position: relative; transform: perspective(1000px); transform-style: preserve-3d;
    transition: box-shadow .3s ease, border-color .3s ease; }
.tilt-card.tilt-return { transition: transform .5s cubic-bezier(0.16, 1, 0.3, 1), box-shadow .3s ease, border-color .3s ease; }
.tilt-card > .tilt-glare {
    position: absolute; inset: 0; border-radius: inherit; z-index: 4;
    pointer-events: none; opacity: 0; transition: opacity .3s ease;
}


/* ─── Card deck (sticky stacking sections, pulserobot-style) ───
   The pin is native CSS sticky — the browser holds the card, no JS runs per
   frame, so it cannot lag behind the scroll. Later siblings paint over
   pinned earlier ones by DOM order. The only scripted motion is a small
   scale/dim on the covered card, driven by motion.dev scroll(): compositor
   ScrollTimeline where supported, cheap transform-only fallback elsewhere.
   Desktop-only; phones and tablets read the sections in normal flow. */
@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
    .card-deck { --pin: 84px; }
    .deck-card {
        /* !important: two cards also carry Tailwind's .relative (their absolute
           children need it on mobile), and the CDN injects its styles after
           this sheet — without this the cascade quietly un-sticks them. */
        position: sticky !important;
        top: var(--pin);
        z-index: 1;               /* one stacking context per card, DOM order wins */
        border-radius: 2rem;
        overflow: hidden;
        box-shadow: 0 -14px 44px rgba(20, 24, 40, .14);
        margin-bottom: 5rem;
        transform-origin: top center;
    }
    .deck-card:last-child {
        margin-bottom: 0;
        /* The finale (CTA + footer). It pins at the very top edge so it
           takes the whole viewport as it consumes the process card, and
           its bottom is the end of the page — square corners there. */
        top: 0;
        border-radius: 2rem 2rem 0 0;
        /* Dark panel arriving over a light card — needs a heavier lip. */
        box-shadow: 0 -24px 70px rgba(15, 20, 40, .38);
    }

}

/* Short desktop viewports (13" laptops): trim the two tallest cards so a
   pinned card always fits between the pin line and the fold. */
@media (min-width: 1024px) and (max-height: 860px) and (prefers-reduced-motion: no-preference) {
    .audience-cell { min-height: 68vh !important; }
    .phase-col     { min-height: 150px !important; }
}


