/* ─── Shared base ─────────────────────────────────────────── */
html, body { max-width: 100%; overflow-x: hidden; }
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);
    background-color: rgba(255,255,255,.8);
    -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,.8);
    -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; }
}

/* ─── ScrollStack (vanilla port of React Bits <ScrollStack>) ───
   Cards pin at `stackPosition` and scale down as later cards stack on top.
   Differences from the shipped CSS, which assumes the component's default
   nested-scroller mode:
     - no `height:100%; overflow-y:auto` on the wrapper — we drive off window
       scroll, so the page scrolls normally (a nested scroller would fight the
       fixed navbar and the anchor links)
     - no fixed `height: 20rem` on cards — our sections are tall and uneven
     - the 50rem bottom padding becomes a spacer giving the pin room to play out
   `will-change` is toggled by JS only while the stack is on screen, so four
   full-height sections aren't permanently promoted to GPU layers. */
.scroll-stack { position: relative; }
.scroll-stack-inner { padding-bottom: 28rem; }
.scroll-stack-card {
    transform-origin: top center;
    backface-visibility: hidden;
    border-radius: 2rem;
    overflow: hidden;
    box-shadow: 0 0 30px rgba(0,0,0,.10);
    position: relative;
}
.scroll-stack-end { width: 100%; height: 1px; }

/* Short desktop viewports (e.g. 13" laptops): the two tallest stacked sections
   are taller than the viewport, so while pinned their lower half falls below the
   fold and can't be scrolled to. Trim them here so each card fits. Taller
   viewports (external monitors) keep the full sizes and are untouched. */
@media (min-width: 1024px) and (max-height: 860px) {
    /* !important: overrides Tailwind's md:min-h-[680px] (loads after this file)
       and the inline min-height:200px on the phase columns. */
    .audience-cell { min-height: 70vh !important; }
    .phase-col     { min-height: 150px !important; }
}

/* Desktop-only and motion-safe; elsewhere the sections stack normally in flow. */
@media (max-width: 1023px), (prefers-reduced-motion: reduce) {
    .scroll-stack-inner { padding-bottom: 0; }
    .scroll-stack-card {
        border-radius: 0;
        box-shadow: none;
        transform: none !important;
        margin-bottom: 0 !important;
    }
}

/* ─── 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 ──────────────────────────── */
.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 – Get Started Particles background ──────────── */
/* React Bits <Particles> (ogl/WebGL) layer behind the consultation form.
   Non-interactive to pointer events so the form stays fully clickable. */
.particles-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
}
.particles-bg canvas {
    display: block;
    width: 100%;
    height: 100%;
}

/* ─── 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;
}
