/* ============================================================================
   GLEENERGY SOLAR SOLUTIONS — Design System
   ----------------------------------------------------------------------------
   Layer order:
     1. Design tokens
     2. Base / typography
     3. Layout primitives
     4. Components (buttons, cards, forms, nav, footer …)
     5. Page sections
     6. Vendor overrides (Owl Carousel, Lightbox, Isotope)
     7. Motion & accessibility
   ========================================================================== */

/* ==========================================================================
   1. DESIGN TOKENS
   ========================================================================== */

:root {
    /* ---- Brand palette ---- */
    --brand-violet:        #502e8a;   /* Primary Violet */
    --brand-purple:        #6f2c8f;   /* Purple */
    --brand-gold:          #faa31e;   /* Golden Yellow */
    --brand-plum:          #2a0d3c;   /* Dark Purple */

    /* ---- Semantic colour roles ---- */
    --primary:             var(--brand-violet);
    --primary-dark:        #3d2269;
    --primary-darker:      #2d1850;
    --primary-light:       #6b45a8;
    --primary-soft:        #f2edf9;   /* tinted surface */
    --primary-softer:      #f8f5fc;

    --secondary:           var(--brand-purple);
    --secondary-dark:      #56216e;
    /* Pressed state for purple buttons, matching how --primary-darker relates
       to --primary. */
    --secondary-darker:    #401953;
    --secondary-light:     #8b4aab;

    --accent:              var(--brand-gold);
    --accent-dark:         #d9860b;
    --accent-light:        #ffc061;
    --accent-soft:         #fff6e6;

    --dark:                var(--brand-plum);
    --dark-2:              #3a1451;
    --dark-3:              #4a1d66;

    --background:          #ffffff;
    --surface:             #ffffff;
    --surface-2:           #f7f6fa;
    --surface-3:           #efedf4;
    --surface-inverse:     var(--dark);

    --text:                #1d1226;
    --text-secondary:      #4a4055;
    --muted:               #6d6478;
    --text-inverse:        #ffffff;
    --text-inverse-muted:  rgba(255, 255, 255, .74);

    --border:              #e3e0e9;
    --border-strong:       #cdc7d6;
    --border-inverse:      rgba(255, 255, 255, .18);

    --success:             #1f7a4d;
    --success-soft:        #e8f5ee;
    --danger:              #b3261e;
    --danger-soft:         #fdecea;

    /* ---- Typography scale ---- */
    --font-sans: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI",
                 Roboto, "Helvetica Neue", Arial, sans-serif;

    --fw-regular:  400;
    --fw-medium:   500;
    --fw-semibold: 600;
    --fw-bold:     700;
    --fw-extra:    800;

    /* Fluid type — scales between 360px and 1400px viewports.
       Deliberately restrained: the earlier scale topped out at a 72px hero and
       40px section headings, which read as loud rather than confident. Pulling
       the display sizes back ~20% and letting body copy hold at 16px gives the
       page a quieter, more corporate voice without losing hierarchy — the
       ratio between steps is unchanged, only the ceiling moved. */
    /* Hero headline only (.hero-title). Capped at 3rem so the three-line
       headline plus subtitle and buttons clear the hero's 558px floor —
       at 3.5rem the buttons landed exactly on the clip edge. */
    --fs-display: clamp(2rem, 1.45rem + 2.4vw, 3rem);
    --fs-h1:      clamp(1.875rem, 1.45rem + 1.9vw, 2.75rem);
    --fs-h2:      clamp(1.5rem, 1.28rem + 1vw, 2.125rem);
    --fs-h3:      clamp(1.25rem, 1.15rem + 0.45vw, 1.5rem);
    --fs-h4:      clamp(1.0625rem, 1.02rem + 0.2vw, 1.1875rem);
    --fs-h5:      1rem;
    --fs-body:    1rem;
    --fs-body-lg: clamp(1rem, 0.97rem + 0.15vw, 1.125rem);
    --fs-small:   0.875rem;
    --fs-caption: 0.8125rem;
    --fs-label:   0.75rem;

    --lh-tight:   1.12;
    --lh-snug:    1.28;
    --lh-normal:  1.65;

    --ls-tight:   -0.022em;
    --ls-heading: -0.014em;
    --ls-label:    0.08em;

    /* ---- Spacing ---- */
    --space-1:  0.25rem;
    --space-2:  0.5rem;
    --space-3:  0.75rem;
    --space-4:  1rem;
    --space-5:  1.5rem;
    --space-6:  2rem;
    --space-7:  2.5rem;
    --space-8:  3rem;
    --space-9:  4rem;
    --space-10: 5rem;

    --section-y:       clamp(3.5rem, 2.2rem + 5vw, 6.5rem);
    --section-y-tight: clamp(2.5rem, 1.8rem + 3vw, 4.5rem);

    /* ---- Radii ---- */
    --radius-sm:   6px;
    --radius:      10px;
    --radius-md:   14px;
    --radius-lg:   20px;
    --radius-xl:   28px;
    --radius-pill: 999px;

    /* ---- Elevation ---- */
    --shadow-xs: 0 1px 2px rgba(29, 18, 38, .06);
    --shadow-sm: 0 2px 8px rgba(29, 18, 38, .06),
                 0 1px 2px rgba(29, 18, 38, .04);
    --shadow:    0 6px 20px rgba(29, 18, 38, .08),
                 0 2px 6px rgba(29, 18, 38, .04);
    --shadow-md: 0 14px 34px rgba(42, 13, 60, .12),
                 0 4px 10px rgba(42, 13, 60, .06);
    --shadow-lg: 0 26px 60px rgba(42, 13, 60, .18),
                 0 8px 18px rgba(42, 13, 60, .08);
    /* Glow under a hovered primary button — tinted to match it (#6f2c8f). */
    --shadow-brand: 0 16px 40px rgba(111, 44, 143, .28);
    --shadow-gold:  0 14px 32px rgba(250, 163, 30, .32);

    /* ---- Gradients ---- */
    --gradient-brand: linear-gradient(135deg, var(--brand-violet) 0%, var(--brand-purple) 100%);
    --gradient-deep:  linear-gradient(150deg, var(--brand-plum) 0%, #3f1659 48%, var(--brand-violet) 100%);
    --gradient-gold:  linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
    --gradient-veil:  linear-gradient(90deg,
                        rgba(42, 13, 60, .93) 0%,
                        rgba(42, 13, 60, .78) 42%,
                        rgba(42, 13, 60, .30) 74%,
                        rgba(42, 13, 60, .12) 100%);

    /* ---- Motion ---- */
    --ease:      cubic-bezier(.4, 0, .2, 1);
    --ease-out:  cubic-bezier(.16, 1, .3, 1);
    --dur-fast:  .16s;
    --dur:       .26s;
    --dur-slow:  .45s;

    /* ---- Layout ---- */
    --container-max: 1240px;
    --header-h:      84px;

    /* Vertical room reserved at the bottom-right for the Pancake chat
       launcher, so our own floating controls never land on top of it. */
    --chat-clearance: clamp(6.5rem, 9vw, 7.5rem);

    /* ---- Bootstrap bridge ----
       Bootstrap 5.3 reads these, so .btn-primary, .text-primary,
       .bg-dark etc. pick up the brand palette without arbitrary hex. */
    --bs-primary:            #502e8a;
    --bs-primary-rgb:        80, 46, 138;
    --bs-secondary:          #6f2c8f;
    --bs-secondary-rgb:      111, 44, 143;
    --bs-dark:               #2a0d3c;
    --bs-dark-rgb:           42, 13, 60;
    --bs-body-font-family:   var(--font-sans);
    --bs-body-font-size:     1rem;
    --bs-body-font-weight:   400;
    --bs-body-line-height:   1.65;
    --bs-body-color:         #1d1226;
    --bs-body-bg:            #ffffff;
    --bs-border-color:       #e3e0e9;
    --bs-link-color:         #502e8a;
    --bs-link-hover-color:   #3d2269;
    --bs-border-radius:      10px;
    --bs-heading-color:      #1d1226;
}


/* ==========================================================================
   2. BASE / TYPOGRAPHY
   ========================================================================== */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
    /* Offset anchor targets so the sticky header never covers them */
    scroll-padding-top: calc(var(--header-h) + 1rem);
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    padding: 0;
    font-family: var(--font-sans);
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: var(--lh-normal);
    color: var(--text);
    background-color: var(--background);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Nothing may cause a sideways scrollbar on mobile */
    overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    margin-top: 0;
    margin-bottom: var(--space-4);
    font-family: var(--font-sans);
    color: var(--text);
    text-wrap: balance;
}

h1, .h1 {
    font-size: var(--fs-h1);
    font-weight: var(--fw-extra);
    line-height: var(--lh-tight);
    letter-spacing: var(--ls-tight);
}

h2, .h2 {
    font-size: var(--fs-h2);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-heading);
}

h3, .h3 {
    font-size: var(--fs-h3);
    font-weight: var(--fw-bold);
    line-height: 1.32;
    letter-spacing: var(--ls-heading);
}

h4, .h4 {
    font-size: var(--fs-h4);
    font-weight: var(--fw-semibold);
    line-height: 1.38;
}

h5, .h5 {
    font-size: var(--fs-h5);
    font-weight: var(--fw-semibold);
    line-height: 1.45;
}

h6, .h6 {
    font-size: var(--fs-small);
    font-weight: var(--fw-semibold);
    line-height: 1.5;
}

p {
    margin-top: 0;
    margin-bottom: var(--space-4);
    color: var(--text-secondary);
    text-wrap: pretty;
}

.lead,
.text-lead {
    font-size: var(--fs-body-lg);
    font-weight: var(--fw-regular);
    line-height: 1.6;
    color: var(--text-secondary);
}

a {
    color: var(--primary);
    text-decoration: none;
    transition: color var(--dur) var(--ease);
}

a:hover {
    color: var(--primary-dark);
}

img {
    max-width: 100%;
    height: auto;
}

hr {
    border: 0;
    border-top: 1px solid var(--border);
    opacity: 1;
}

::selection {
    background: var(--primary);
    color: #fff;
}

/* Weight utilities (kept — the markup uses them) */
.fw-medium { font-weight: var(--fw-medium) !important; }
.fw-semibold { font-weight: var(--fw-semibold) !important; }
.fw-bold   { font-weight: var(--fw-bold) !important; }
.fw-black  { font-weight: var(--fw-extra) !important; }

/* Eyebrow / kicker label above section headings */
.section-label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
    font-size: var(--fs-label);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--primary);
}

.section-label::before {
    content: "";
    width: 26px;
    height: 2px;
    background: var(--accent);
    border-radius: 2px;
}

.section-label.is-inverse {
    color: var(--accent);
}

.section-label.is-centered {
    justify-content: center;
}

.section-heading {
    max-width: 46rem;
    margin-inline: auto;
    margin-bottom: var(--space-8);
    text-align: center;
}

.section-heading > p:last-child {
    margin-bottom: 0;
}

.section-title {
    font-size: var(--fs-h2);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
    letter-spacing: var(--ls-heading);
    margin-bottom: var(--space-4);
    color: var(--text);
}

/* Text colour helpers scoped to the brand */
.text-brand        { color: var(--primary) !important; }
.text-brand-dark   { color: var(--dark) !important; }
.text-accent       { color: var(--accent-dark) !important; }
.text-muted-brand  { color: var(--muted) !important; }

.text-gradient {
    background: linear-gradient(120deg, var(--accent-light) 0%, var(--accent) 55%, #ffd48a 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.bg-surface   { background-color: var(--surface-2) !important; }
.bg-surface-3 { background-color: var(--surface-3) !important; }
.bg-brand     { background: var(--gradient-brand) !important; }
.bg-deep      { background: var(--gradient-deep) !important; }

/* Screen-reader-only text (Bootstrap 4 name still used in the markup) */
.sr-only {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Skip link */
.skip-link {
    position: absolute;
    top: -100%;
    left: var(--space-4);
    z-index: 2000;
    padding: var(--space-3) var(--space-5);
    background: var(--primary);
    color: #fff;
    font-weight: var(--fw-semibold);
    border-radius: 0 0 var(--radius) var(--radius);
    transition: top var(--dur) var(--ease);
}

.skip-link:focus {
    top: 0;
    color: #fff;
}


/* ==========================================================================
   3. LAYOUT PRIMITIVES
   ========================================================================== */

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

/* Anchor offset for the single-page navigation.
   html{scroll-padding-top} covers native anchor jumps and CSS smooth
   scrolling; scroll-margin-top on the targets themselves also covers
   programmatic scrollIntoView() calls, which ignore scroll-padding. */
section[id],
main[id],
[id="team"] {
    scroll-margin-top: calc(var(--header-h) + 1rem);
}

@media (max-width: 991.98px) {
    section[id],
    main[id],
    [id="team"] {
        /* Collapsed header is shorter on mobile */
        scroll-margin-top: 5rem;
    }
}

.section-tight {
    padding-block: var(--section-y-tight);
}

.section-dark {
    background: var(--gradient-deep);
    color: var(--text-inverse);
}

.section-dark h1,
.section-dark h2,
.section-dark h3,
.section-dark h4,
.section-dark h5 {
    color: var(--text-inverse);
}

.section-dark p {
    color: var(--text-inverse-muted);
}

.section-soft {
    background: var(--surface-2);
}

/* Bootstrap pairs a 1.5rem gutter with 0.75rem of container padding, so a
   .row's -0.75rem side margins land flush inside it. Several rows here use
   g-lg-5 (3rem), which makes those margins -1.5rem and pushes the row 0.75rem
   past the container on each side — enough to raise a horizontal scrollbar.
   Match the container padding to the larger gutter from lg up. */
@media (min-width: 992px) {
    .container,
    .container-lg,
    .container-md,
    .container-sm,
    .container-xl,
    .container-xxl {
        padding-left: var(--space-5);
        padding-right: var(--space-5);
    }
}

@media (min-width: 1400px) {
    .container,
    .container-lg,
    .container-md,
    .container-sm,
    .container-xl,
    .container-xxl {
        max-width: var(--container-max);
    }
}


/* ==========================================================================
   4. COMPONENTS
   ========================================================================== */

/* ---- 4.1 Focus ring (applies to every interactive element) ---- */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

.section-dark :where(a, button):focus-visible,
.footer :where(a, button):focus-visible,
.offcanvas :where(a, button, input, select, textarea):focus-visible {
    outline-color: var(--accent);
}


/* ---- 4.2 Buttons ---- */
.btn {
    --btn-bg: transparent;
    --btn-color: var(--text);
    --btn-border: transparent;

    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 0.8125rem 1.75rem;
    font-family: var(--font-sans);
    font-size: var(--fs-body);
    font-weight: var(--fw-semibold);
    line-height: 1.25;
    letter-spacing: 0;
    text-align: center;
    border: 1.5px solid var(--btn-border);
    border-radius: var(--radius);
    background: var(--btn-bg);
    color: var(--btn-color);
    cursor: pointer;
    transition: background var(--dur) var(--ease),
                color var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform var(--dur-fast) var(--ease);
}

.btn:hover {
    transform: translateY(-2px);
}

.btn:active {
    transform: translateY(0);
}

.btn i {
    transition: transform var(--dur) var(--ease);
}

.btn:hover i.fa-arrow-right {
    transform: translateX(3px);
}

/* Primary — brand violet, gold on hover */
.btn.btn-primary,
/* Primary buttons are brand PURPLE (--secondary, #6f2c8f), not the violet
   --primary. Only the buttons moved — the 35 other --primary consumers
   (headings, labels, icons, links) stay violet. */
.btn-primary {
    --btn-bg: var(--secondary);
    --btn-color: #fff;
    --btn-border: var(--secondary);
    box-shadow: var(--shadow-sm);
}

.btn.btn-primary:hover,
.btn.btn-primary:focus,
.btn-primary:hover,
.btn-primary:focus {
    --btn-bg: var(--secondary-dark);
    --btn-border: var(--secondary-dark);
    --btn-color: #fff;
    box-shadow: var(--shadow-brand);
}

.btn.btn-primary:active {
    --btn-bg: var(--secondary-darker);
    --btn-border: var(--secondary-darker);
}

/* Accent — the conversion button. Used sparingly. */
.btn-accent {
    --btn-bg: var(--accent);
    --btn-color: var(--dark);
    --btn-border: var(--accent);
    font-weight: var(--fw-bold);
    box-shadow: var(--shadow-sm);
}

.btn-accent:hover,
.btn-accent:focus {
    --btn-bg: var(--accent-light);
    --btn-border: var(--accent-light);
    --btn-color: var(--dark);
    box-shadow: var(--shadow-gold);
}

/* Secondary — outlined */
.btn-outline-brand {
    --btn-bg: transparent;
    --btn-color: var(--primary);
    --btn-border: var(--border-strong);
}

.btn-outline-brand:hover,
.btn-outline-brand:focus {
    --btn-bg: var(--primary-soft);
    --btn-color: var(--primary-dark);
    --btn-border: var(--primary);
}

/* Outlined on dark backgrounds */
.btn-outline-light-brand {
    --btn-bg: transparent;
    --btn-color: #fff;
    --btn-border: rgba(255, 255, 255, .45);
    backdrop-filter: blur(2px);
}

.btn-outline-light-brand:hover,
.btn-outline-light-brand:focus {
    --btn-bg: rgba(255, 255, 255, .12);
    --btn-color: #fff;
    --btn-border: #fff;
}

.btn-lg {
    padding: 1rem 2.125rem;
    font-size: var(--fs-body-lg);
}

.btn-sm {
    padding: 0.5rem 1.125rem;
    font-size: var(--fs-small);
}

.btn.disabled,
.btn:disabled {
    opacity: .6;
    pointer-events: none;
    transform: none;
}

/* Square icon buttons */
.btn-square    { width: 40px; height: 40px; }
.btn-sm-square { width: 34px; height: 34px; }
.btn-lg-square { width: 50px; height: 50px; }

.btn-square,
.btn-sm-square,
.btn-lg-square {
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: var(--fw-regular);
    flex-shrink: 0;
}

/* Text link with arrow.
   min-height keeps the tap target at the WCAG 2.5.8 minimum without
   changing how the link looks — the extra area is padding, not ink. */
.link-arrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 44px;
    padding-block: var(--space-2);
    font-size: var(--fs-small);
    font-weight: var(--fw-semibold);
    color: var(--primary);
}

.link-arrow i {
    transition: transform var(--dur) var(--ease);
}

.link-arrow:hover {
    color: var(--primary-dark);
}

.link-arrow:hover i {
    transform: translateX(4px);
}


/* ---- 4.3 Cards ---- */
.card {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-xs);
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}

.card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
    border-color: var(--border-strong);
}

/* Statistic tile */
.stat-card {
    height: 100%;
    padding: var(--space-6) var(--space-4);
    text-align: center;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-xs);
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}

.stat-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
    border-color: color-mix(in srgb, var(--primary) 26%, var(--border));
}

.icon-container {
    width: 58px;
    height: 58px;
    margin: 0 auto var(--space-4);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--primary-soft);
    color: var(--primary);
    border-radius: var(--radius-md);
    font-size: 24px;
    transition: background var(--dur) var(--ease),
                color var(--dur) var(--ease);
}

.stat-card:hover .icon-container,
.card:hover .icon-container {
    background: var(--gradient-brand);
    color: #fff;
}

.highlight-number {
    /* Kept a step below the section headings so the numbers read as data,
       not as a second layer of headline. */
    font-size: clamp(1.625rem, 1.4rem + 0.9vw, 2.125rem);
    font-weight: var(--fw-extra);
    line-height: 1.1;
    letter-spacing: var(--ls-tight);
    color: var(--primary);
    font-variant-numeric: tabular-nums;
}

.highlight-number .unit {
    color: var(--accent-dark);
}

.highlight-title {
    margin-top: var(--space-2);
    margin-bottom: 0;
    font-size: var(--fs-small);
    font-weight: var(--fw-medium);
    color: var(--muted);
}


/* ---- 4.4 Forms ----
   Scoped deliberately. The old stylesheet styled bare `form`, `input`,
   `select`, `textarea` and `button` globally, which leaked into every
   Bootstrap component on the site. */

.form-label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: var(--fs-small);
    font-weight: var(--fw-semibold);
    color: var(--text);
}

.form-label .required {
    color: var(--danger);
    margin-left: 2px;
}

.form-control,
.form-select {
    width: 100%;
    padding: 0.75rem 1rem;
    font-family: var(--font-sans);
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: 1.5;
    color: var(--text);
    background-color: var(--surface);
    border: 1.5px solid var(--border-strong);
    border-radius: var(--radius);
    transition: border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
    /* 16px minimum stops iOS Safari zooming on focus */
    font-size: max(16px, var(--fs-body));
}

.form-control::placeholder {
    color: var(--muted);
    opacity: 1;
}

.form-control:focus,
.form-select:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 4px rgba(80, 46, 138, .14);
    outline: none;
}

.form-control:disabled,
.form-control[readonly] {
    background-color: var(--surface-3);
    color: var(--text-secondary);
    cursor: default;
}

textarea.form-control {
    min-height: 120px;
    resize: vertical;
}

.form-text,
.form-hint {
    display: block;
    margin-top: var(--space-2);
    font-size: var(--fs-caption);
    color: var(--muted);
}

/* Native validation feedback, only after the user has interacted */
.needs-validation.was-validated .form-control:invalid,
.needs-validation.was-validated .form-select:invalid {
    border-color: var(--danger);
}

.needs-validation.was-validated .form-control:valid,
.needs-validation.was-validated .form-select:valid {
    border-color: var(--success);
}

.invalid-feedback {
    display: none;
    margin-top: var(--space-2);
    font-size: var(--fs-caption);
    font-weight: var(--fw-medium);
    color: var(--danger);
}

.needs-validation.was-validated :invalid ~ .invalid-feedback {
    display: block;
}

/* Honeypot — hidden from humans, visible to naive bots */
.hp-field {
    position: absolute !important;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* Alert banners for form success / failure states */
.form-alert {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    margin-bottom: var(--space-5);
    border-radius: var(--radius);
    border: 1px solid transparent;
    font-size: var(--fs-small);
    font-weight: var(--fw-medium);
}

.form-alert i {
    margin-top: 2px;
    flex-shrink: 0;
}

.form-alert-error {
    background: var(--danger-soft);
    border-color: rgba(179, 38, 30, .28);
    color: #7d1a15;
}

/* In-place submission confirmation.
   Replaces the old standalone thank-you page: the form is hidden and this
   panel takes its place, so the visitor never leaves the page. */
.submission-success {
    padding: clamp(1.5rem, 4vw, 2.5rem);
    text-align: center;
    background: var(--success-soft);
    border: 1px solid rgba(31, 122, 77, .3);
    border-radius: var(--radius-md);
}

.submission-success:focus {
    outline: none;
}

.submission-success-icon {
    width: 68px;
    height: 68px;
    margin: 0 auto var(--space-5);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 28px;
    color: #fff;
    background: var(--success);
    border-radius: var(--radius-pill);
}

.submission-success h3 {
    margin-bottom: var(--space-3);
    color: #145636;
}

.submission-success p {
    color: #1d5c3d;
}

.submission-next {
    max-width: 30rem;
    margin: var(--space-5) auto;
    padding-left: var(--space-5);
    text-align: left;
    color: #1d5c3d;
    font-size: var(--fs-small);
}

.submission-next li {
    margin-bottom: var(--space-2);
}

/* Button busy state */
.btn .btn-spinner {
    display: none;
    width: 1em;
    height: 1em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: gle-spin .7s linear infinite;
}

.btn.is-loading {
    pointer-events: none;
    opacity: .8;
}

.btn.is-loading .btn-spinner {
    display: inline-block;
}

.btn.is-loading .btn-label {
    opacity: .85;
}

@keyframes gle-spin {
    to { transform: rotate(360deg); }
}


/* ---- 4.5 Navbar ---- */
.navbar {
    background: var(--surface);
    box-shadow: 0 1px 0 var(--border);
    transition: box-shadow var(--dur) var(--ease),
                background var(--dur) var(--ease);
}

.navbar.sticky-top {
    top: 0;
    z-index: 1030;
}

.navbar.is-scrolled {
    box-shadow: var(--shadow);
    background: rgba(255, 255, 255, .96);
    backdrop-filter: saturate(180%) blur(12px);
}

.navbar .navbar-brand {
    display: flex;
    align-items: center;
    padding-block: var(--space-3);
    min-height: var(--header-h);
}

.navbar .navbar-brand img {
    width: auto;
    height: 46px;
    max-height: 46px;
}

.navbar .navbar-nav .nav-link {
    display: flex;
    align-items: center;
    position: relative;
    padding: var(--space-3) 0;
    margin-right: var(--space-6);
    color: var(--text);
    font-size: var(--fs-small);
    font-weight: var(--fw-semibold);
    letter-spacing: .01em;
    text-transform: uppercase;
    transition: color var(--dur) var(--ease);
}

.navbar .navbar-nav .nav-link::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 12px;
    width: 0;
    height: 2px;
    border-radius: 2px;
    background: var(--accent);
    transition: width var(--dur) var(--ease);
}

.navbar .navbar-nav .nav-link:hover,
.navbar .navbar-nav .nav-link:focus,
.navbar .navbar-nav .nav-link.active {
    color: var(--primary);
}

.navbar .navbar-nav .nav-link:hover::after,
.navbar .navbar-nav .nav-link.active::after {
    width: 100%;
}

.navbar-toggler {
    padding: var(--space-2) var(--space-3);
    border: 1.5px solid var(--border-strong);
    border-radius: var(--radius-sm);
}

.navbar-toggler:focus {
    box-shadow: none;
}

/* Header call-to-action */
.navbar .navbar-cta {
    align-self: stretch;
    display: inline-flex;
    align-items: center;
    padding-inline: var(--space-6);
    border-radius: 0;
    min-height: var(--header-h);
}

/* Header phone link */
.navbar-phone {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--fs-small);
    font-weight: var(--fw-semibold);
    color: var(--dark);
    white-space: nowrap;
}

.navbar-phone i {
    color: var(--accent-dark);
}

.navbar-phone:hover {
    color: var(--primary);
}

/* Six nav items plus the phone number and the CTA button are a tight fit in
   the 992–1199 band: at the default 2rem item spacing the row needs ~1053px
   against ~977px available, which pushes the CTA off-screen. Tighten the
   spacing in that band only — from 1200px up there is room for the default.
   The phone overrides need !important to beat Bootstrap's ms-lg-4/me-lg-4,
   which carry it themselves. */
@media (min-width: 992px) and (max-width: 1199.98px) {
    .navbar .navbar-nav .nav-link {
        margin-right: var(--space-4);
    }

    .navbar-phone {
        margin-left: var(--space-3) !important;
        margin-right: var(--space-3) !important;
    }
}

@media (max-width: 991.98px) {
    .navbar .navbar-collapse {
        border-top: 1px solid var(--border);
        padding: var(--space-4) 0 var(--space-5);
        max-height: calc(100vh - var(--header-h));
        overflow-y: auto;
    }

    .navbar .navbar-nav .nav-link {
        margin-right: 0;
        padding: var(--space-3) 0;
        /* comfortable touch target */
        min-height: 46px;
    }

    .navbar .navbar-nav .nav-link::after {
        bottom: 6px;
    }

    .navbar .navbar-cta {
        width: 100%;
        min-height: 52px;
        margin-top: var(--space-4);
        border-radius: var(--radius);
        padding-inline: var(--space-5);
    }

    .navbar .navbar-brand img {
        height: 38px;
        max-height: 38px;
    }
}


/* ---- 4.6 Off-canvas quote panel ---- */
.offcanvas {
    background: var(--gradient-deep);
    color: var(--text-inverse);
    max-width: 100%;
}

.offcanvas-header {
    border-bottom: 1px solid var(--border-inverse);
    padding: var(--space-5);
}

.offcanvas-title,
.offcanvas-header h5 {
    color: var(--text-inverse);
    font-weight: var(--fw-bold);
    margin: 0;
}

.offcanvas-body {
    padding: var(--space-5);
}

.offcanvas .btn-close {
    /* Bootstrap ships this at 32px; enlarge to a comfortable touch target. */
    width: 44px;
    height: 44px;
    padding: 0;
    background-size: 16px;
    background-position: center;
    filter: invert(1) grayscale(100%) brightness(200%);
    opacity: .85;
}

.offcanvas .btn-close:hover {
    opacity: 1;
}

.offcanvas .form-label {
    color: var(--text-inverse);
}

.offcanvas .form-text,
.offcanvas .form-hint {
    color: var(--text-inverse-muted);
}

.offcanvas .form-control,
.offcanvas .form-select {
    background-color: rgba(255, 255, 255, .96);
    border-color: transparent;
    color: var(--text);
}

.offcanvas .form-control:focus,
.offcanvas .form-select:focus {
    background-color: #fff;
    border-color: var(--accent);
    box-shadow: 0 0 0 4px rgba(250, 163, 30, .28);
}

.offcanvas .form-alert-error {
    background: rgba(255, 255, 255, .95);
}

.quote-panel-intro {
    font-size: var(--fs-small);
    color: var(--text-inverse-muted);
    margin-bottom: var(--space-5);
}

.quote-panel-logo {
    max-height: 44px;
    width: auto;
}


/* ---- 4.7 Footer ---- */
.footer {
    background: var(--gradient-deep);
    color: var(--text-inverse-muted);
}

/* The column headings are <h2 class="h5"> — h2 for the document outline,
   .h5 for the size. Matching only the h5/h4 ELEMENTS missed them entirely, so
   they inherited the footer's muted body colour and lost their bottom margin,
   leaving the headings dimmer than the text under them. Match the classes too. */
.footer h2,
.footer h4,
.footer h5,
.footer .h4,
.footer .h5 {
    color: var(--text-inverse);
    font-size: var(--fs-h5);
    font-weight: var(--fw-semibold);
    margin-bottom: var(--space-5);
}

.footer p {
    color: var(--text-inverse-muted);
}

.footer a {
    color: var(--text-inverse-muted);
}

.footer a:hover {
    color: #fff;
}

.footer .footer-logo {
    max-width: 230px;
    height: auto;
    margin-bottom: var(--space-4);
}

.footer .footer-contact-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
    color: var(--text-inverse-muted);
}

.footer .footer-contact-item i {
    margin-top: 5px;
    color: var(--accent);
    flex-shrink: 0;
    width: 16px;
    text-align: center;
}

/* Each contact method carries a caption ("Sales inquiries", "After-sales
   hotline"), folded in from the closing CTA band so the two hotlines are
   distinguishable. The span stacks the link over its caption. */
.footer .footer-contact-item > span {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.footer .footer-contact-item small {
    font-size: var(--fs-caption);
    /* One step below --text-inverse-muted; no token exists at this level, so
       the value is literal rather than a var() with a phantom name. */
    color: rgba(255, 255, 255, .58);
}

/* tel:/mailto: links are the things people actually tap, so keep the hit area
   at the 44px touch minimum used elsewhere. The height is padding, pulled back
   by a negative margin so the caption still sits tight beneath the number. */
.footer .footer-contact-item a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding-block: var(--space-1);
    margin-block: calc(var(--space-1) * -1);
    word-break: break-word;
}

.footer .btn.btn-link {
    display: flex;
    align-items: center;

    /* The base .btn rule centres its flex content, which on a full-width
       footer link centred every label and left both edges of the list
       ragged. Footer navigation must hang off a single left margin, aligned
       with its heading. */
    justify-content: flex-start;

    /* Bootstrap's .btn-link ships underlined. Permanent underlines on a
       footer list read as dated and add visual noise to five stacked items;
       reserve the underline for hover so the affordance is still there. */
    text-decoration: none;

    gap: var(--space-2);
    min-height: 40px;
    margin-bottom: 0;
    padding: var(--space-2) 0;
    text-align: left;
    color: var(--text-inverse-muted);
    font-weight: var(--fw-regular);
    text-transform: none;
    border: 0;
    transition: color var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}

/* Chevron marker.
   Held out of the text flow so every label starts flush with the "Quick
   links" heading — in flow it pushed the labels 16px right and broke the
   column's left edge. It fades in on hover as the row slides, which keeps
   the affordance without a permanent row of decorative glyphs.

   To bring the chevrons back permanently: delete the `opacity: 0` here and
   the `opacity: 1` in the hover rule below. */
.footer .btn.btn-link::before {
    content: "\f105";
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    color: var(--accent);
    position: absolute;
    left: calc(var(--space-4) * -1);
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
}

.footer .btn.btn-link {
    position: relative;
}

.footer .btn.btn-link:hover,
.footer .btn.btn-link:focus-visible {
    color: #fff;
    text-decoration: underline;
    text-underline-offset: 3px;
    transform: translateX(4px);
    box-shadow: none;
}

.footer .btn.btn-link:hover::before,
.footer .btn.btn-link:focus-visible::before {
    opacity: 1;
}

/* Keep the list hugging its heading rather than stretching the full column.
   A footer link whose clickable box spans 373px of empty space invites
   mis-clicks; sizing to content keeps the target on the words. */
.footer nav {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.footer .btn.btn-social {
    width: 40px;
    height: 40px;
    margin-right: var(--space-2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    background: rgba(255, 255, 255, .08);
    border: 1px solid var(--border-inverse);
    border-radius: var(--radius-pill);
    transition: background var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}

.footer .btn.btn-social:hover {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--dark);
    transform: translateY(-2px);
}

.footer .copyright {
    padding: var(--space-5) 0;
    margin-top: var(--space-6);
    border-top: 1px solid var(--border-inverse);
    font-size: var(--fs-small);
    color: var(--text-inverse-muted);
}

.footer .copyright a {
    color: #fff;
    font-weight: var(--fw-medium);
}

.footer .copyright a:hover {
    color: var(--accent);
}


/* ---- 4.8 Back to top ---- */
/* Sits above the Pancake chat bubble, which is fixed to the bottom-right.
   --chat-clearance is the room reserved for that bubble; raise it here if
   Pancake ever ships a larger launcher. */
.back-to-top {
    position: fixed;
    right: clamp(1rem, 3vw, 2.5rem);
    bottom: var(--chat-clearance);
    z-index: 1020;
    display: none;
    width: 46px;
    height: 46px;
    align-items: center;
    justify-content: center;
    /* Matches the primary buttons (#6f2c8f). */
    background: var(--secondary);
    color: #fff;
    border: 0;
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-md);
    transition: background var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}

.back-to-top:hover {
    background: var(--accent);
    color: var(--dark);
    transform: translateY(-3px);
}


/* ---- 4.9 Spinner ---- */
#spinner {
    opacity: 0;
    visibility: hidden;
    transition: opacity .4s ease-out, visibility 0s linear .4s;
    z-index: 99999;
}

#spinner.show {
    transition: opacity .4s ease-out, visibility 0s linear 0s;
    visibility: visible;
    opacity: 1;
}

.spinner-border.text-primary {
    color: var(--primary) !important;
}


/* ==========================================================================
   5. PAGE SECTIONS
   ========================================================================== */

/* ---- 5.1 Hero ---- */
.hero {
    position: relative;
    isolation: isolate;
    background: var(--gradient-deep);
    color: var(--text-inverse);
    overflow: hidden;
}

.hero-inner {
    position: relative;
    /* Was clamp(4rem, 2.5rem + 8vw, 8.5rem) — 136px top AND bottom at desktop,
       272px of padding inside a 558px hero. That, more than the type size, is
       what pushed the buttons onto the clip edge. */
    padding-block: clamp(3rem, 2rem + 4vw, 5rem);
}

.hero-title {
    font-size: var(--fs-display);
    font-weight: var(--fw-extra);
    line-height: var(--lh-tight);
    letter-spacing: var(--ls-tight);
    color: #fff;
    margin-bottom: var(--space-5);
    text-align: left;
}

.hero-subtitle {
    max-width: 40rem;
    font-size: var(--fs-body-lg);
    line-height: 1.6;
    color: rgba(255, 255, 255, .88);
    margin-bottom: var(--space-7);
}

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

/* The buttons are currently the last element in all three slides, so that
   bottom margin was 48px of dead space below the fold. The rule above is kept
   for when something is added beneath them; this zeroes it until then. */
.hero-actions:last-child {
    margin-bottom: 0;
}

@media (max-width: 575.98px) {
    .hero-actions .btn {
        width: 100%;
    }
}


/* ---- 5.2 Interior page header ---- */
.page-header {
    position: relative;
    isolation: isolate;
    padding-block: clamp(3rem, 2rem + 5vw, 6rem);
    background: var(--gradient-deep);
    color: var(--text-inverse);
    overflow: hidden;
}

.page-header::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background-image: linear-gradient(90deg,
                        rgba(42, 13, 60, .92) 0%,
                        rgba(42, 13, 60, .74) 55%,
                        rgba(42, 13, 60, .55) 100%),
                      var(--page-header-image, none);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

.page-header h1,
.page-header .h1 {
    color: #fff;
    margin-bottom: var(--space-3);
}

.page-header p {
    color: rgba(255, 255, 255, .82);
    max-width: 44rem;
    margin-bottom: 0;
}

/* Page-specific header artwork */
.legal-header       { --page-header-image: url("../img/nav/about_1.png"); }

.breadcrumb {
    margin-bottom: 0;
    padding: 0;
    background: transparent;
    font-size: var(--fs-small);
}

.page-header .breadcrumb-item,
.page-header .breadcrumb-item a {
    color: rgba(255, 255, 255, .72);
}

.page-header .breadcrumb-item a:hover {
    color: #fff;
}

.page-header .breadcrumb-item.active {
    color: var(--accent);
}

.page-header .breadcrumb-item + .breadcrumb-item::before {
    color: rgba(255, 255, 255, .45);
}


/* ---- 5.3 Service cards ---- */
.service-item {
    height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-xs);
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}

.service-item:hover {
    transform: translateY(-6px);
    box-shadow: var(--shadow-md);
    border-color: color-mix(in srgb, var(--primary) 24%, var(--border));
}

.service-media {
    position: relative;
    aspect-ratio: 3 / 2;
    overflow: hidden;
    background: var(--surface-3);
}

.service-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--dur-slow) var(--ease-out);
}

.service-item:hover .service-media img {
    transform: scale(1.05);
}

.service-body {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    padding: 0 var(--space-5) var(--space-5);
}

.service-icon {
    position: relative;
    margin: -34px 0 var(--space-4);
    width: 68px;
    height: 68px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 28px;
    color: var(--primary);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    transition: background var(--dur) var(--ease),
                color var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}

.service-item:hover .service-icon {
    color: #fff;
    background: var(--gradient-brand);
    border-color: transparent;
}

.service-body h3,
.service-body h4 {
    margin-bottom: var(--space-3);
}

.service-body p {
    flex: 1 1 auto;
    font-size: var(--fs-small);
    margin-bottom: var(--space-4);
}


/* ---- 5.4 Portfolio / projects ---- */
#portfolio-flters {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2);
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-7);
}

#portfolio-flters li {
    display: inline-flex;
    align-items: center;
    padding: var(--space-2) var(--space-5);
    font-size: var(--fs-small);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: color var(--dur) var(--ease),
                background var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}

#portfolio-flters li:hover {
    color: var(--primary);
    border-color: var(--primary);
}

#portfolio-flters li.active {
    color: #fff;
    background: var(--primary);
    border-color: var(--primary);
}

.portfolio-item {
    margin-bottom: var(--space-2);
}

.portfolio-img {
    position: relative;
    display: block;
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--surface-3);
}

.portfolio-img img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--dur-slow) var(--ease-out);
}

/* Scrim revealed on hover.
   The old rules used position:relative on ::before/::after, so the
   pseudo-elements were laid out in flow and pushed the image sideways
   instead of covering it. */
.portfolio-img::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top,
                rgba(42, 13, 60, .85) 0%,
                rgba(42, 13, 60, .35) 55%,
                rgba(42, 13, 60, 0) 100%);
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
}

.portfolio-item:hover .portfolio-img img,
.portfolio-item:focus-within .portfolio-img img {
    transform: scale(1.06);
}

.portfolio-item:hover .portfolio-img::after,
.portfolio-item:focus-within .portfolio-img::after {
    opacity: 1;
}

.portfolio-btn {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    opacity: 0;
    transform: translateY(8px);
    transition: opacity var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}

.portfolio-item:hover .portfolio-btn,
.portfolio-item:focus-within .portfolio-btn {
    opacity: 1;
    transform: translateY(0);
}

.portfolio-btn .btn {
    color: #fff;
    border-color: rgba(255, 255, 255, .6);
    background: rgba(255, 255, 255, .12);
    backdrop-filter: blur(3px);
}

.portfolio-btn .btn:hover {
    color: var(--dark);
    background: var(--accent);
    border-color: var(--accent);
}

/* Project name / scope of works / location, each on its own line with a bold
   label, per the brochure. The label is a span inside the element rather than
   a separate <dt>, so the project name stays a real <h3> and the eighteen
   cards remain navigable by heading. */
.portfolio-meta {
    padding-top: var(--space-4);
}

.portfolio-meta .project-name {
    font-size: var(--fs-small);
    font-weight: var(--fw-regular);
    line-height: 1.5;
    margin-bottom: var(--space-2);
    color: var(--muted);
}

.portfolio-meta .project-fact {
    font-size: var(--fs-small);
    line-height: 1.5;
    margin-bottom: var(--space-2);
    color: var(--muted);
}

.portfolio-meta .project-fact:last-child {
    margin-bottom: 0;
}

.portfolio-meta .meta-label {
    font-weight: var(--fw-bold);
    color: var(--text);
}

.portfolio-meta h5 {
    font-size: var(--fs-h5);
    font-weight: var(--fw-semibold);
    line-height: 1.45;
    margin-bottom: 0;
    color: var(--text);
}

/* Keyboard focus must be visible even though the controls fade in */
.portfolio-btn .btn:focus-visible {
    opacity: 1;
}


/* ---- 5.5 Savings calculator ---- */
.calculator-panel {
    padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}

/* The two tiers sit side by side as plain columns — no card chrome. Each
   figure is a label above a filled, read-only-looking field, which is what
   the reference layout uses. */
.calculator-result {
    height: 100%;
}

.calculator-tier {
    font-size: clamp(1.35rem, 1.15rem + .9vw, 1.75rem);
    font-weight: var(--fw-semibold);
    line-height: 1.2;
    color: var(--text);
    margin-bottom: var(--space-4);
}

.calculator-figure {
    margin-bottom: var(--space-4);
}

.calculator-figure dt {
    font-size: var(--fs-small);
    font-weight: var(--fw-medium);
    color: var(--text);
    margin-bottom: var(--space-2);
}

/* Styled as a disabled input rather than plain text: it reads as a value the
   tool produced, not something the visitor typed. */
.calculator-figure dd {
    margin: 0;
    padding: var(--space-3) var(--space-4);
    font-size: 1.0625rem;
    font-weight: var(--fw-medium);
    line-height: 1.4;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    background: var(--surface-3);
    border: 1px solid transparent;
    border-radius: var(--radius-sm, 8px);
}

.calculator-figure.is-highlight dd {
    color: var(--primary);
    font-weight: var(--fw-semibold);
}

/* The Calculate button uses .btn-primary, i.e. --primary / #502e8a, so it is
   single-sourced from the brand palette rather than carrying its own hex. */

/* Footnotes sit directly under the two result columns, so only the notes
   block carries the rule — not each paragraph inside it. */
.calculator-notes {
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
}

/* Sits under its own tier's savings figure, so it hangs off the column rather
   than the shared notes block. */
.calculator-result .calculator-footnote {
    margin: var(--space-2) 0 0;
    font-size: var(--fs-caption);
    line-height: 1.45;
    color: var(--muted);
}

.calculator-tip {
    margin-bottom: var(--space-4);
    font-size: var(--fs-caption);
    line-height: 1.5;
    color: var(--muted);
}

.calculator-disclaimer {
    margin-bottom: var(--space-3);
    font-size: var(--fs-caption);
    line-height: 1.5;
    color: var(--muted);
}

.calculator-figure sup {
    /* Keep the asterisk from stretching the label's line box. */
    line-height: 0;
    font-size: .75em;
}

.calculator-empty {
    color: var(--muted);
    font-size: var(--fs-small);
}


/* ---- 5.5a Inverter gallery ----------------------------------------------
   Three groups of installation photos under one section heading. Each group
   gets its own rule-underlined title, matching the reference layout. */
.inverter-group + .inverter-group {
    margin-top: var(--space-8);
}

.inverter-group-title {
    display: inline-block;
    margin-bottom: var(--space-5);
    padding-bottom: var(--space-3);
    font-size: var(--fs-h4);
    font-weight: var(--fw-bold);
    color: var(--primary);
    /* The underline runs past the text, as in the reference. inline-block keeps
       it tied to the title's width; the extra reach comes from the padding. */
    border-bottom: 3px solid var(--primary);
    padding-right: var(--space-8);
}

/* Fixed ratio so the grid stays even whatever each photo's native shape is —
   these are phone snaps and their aspect ratios vary. Portrait, because
   wall-mounted inverters are taller than they are wide. */
.inverter-shot {
    display: block;
    position: relative;
    aspect-ratio: 3 / 4;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--surface-3);
    box-shadow: var(--shadow-xs);
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}

.inverter-shot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

/* Two landscape shots have their subject off-centre, and a centred 3:4 crop
   sliced both units in half with dead wall in the middle. Bias the crop toward
   the right-hand unit so one inverter is framed whole; the lightbox still shows
   the full frame. Applied per photo, not globally — the other landscape shots
   centre correctly. */
.inverter-shot img.focus-right {
    object-position: 85% center;
}

.inverter-shot:hover,
.inverter-shot:focus-visible {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
}

@media (prefers-reduced-motion: reduce) {
    .inverter-shot:hover,
    .inverter-shot:focus-visible {
        transform: none;
    }
}


/* ---- 5.5b Process steps -------------------------------------------------
   Five steps in a row divided by hairlines, per the brochure. Same divider
   approach as .reasons-grid, but five columns instead of three and circular
   icon badges instead of rounded squares.

   Five across only fits from 1200px up; below that the row count changes, so
   each breakpoint resets the nth-child rules the smaller one set. Without the
   reset the additive media queries stripe the wrong cells. */
.process-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: 1fr;
}

.process-step {
    padding: var(--space-6) var(--space-4);
    text-align: center;
}

/* 1-up: stacked, so dividers run horizontally. */
.process-step + .process-step {
    border-top: 1px solid var(--border);
}

.process-icon {
    width: 66px;
    height: 66px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--space-4);
    font-size: 25px;
    color: #fff;
    background: var(--gradient-brand);
    /* Circular, unlike the rounded squares in .reason-icon. */
    border-radius: 50%;
    box-shadow: var(--shadow-sm);
}

.process-title {
    margin-bottom: var(--space-2);
    font-size: var(--fs-small);
    font-weight: var(--fw-extra);
    letter-spacing: .04em;
    /* Uppercased in CSS rather than in the markup so screen readers still get
       normal-cased text instead of spelling it out letter by letter. */
    text-transform: uppercase;
    color: var(--primary);
}

.process-text {
    margin-bottom: 0;
    color: var(--muted);
    /* One notch below --fs-small; no token exists at this size and the five
       columns are too narrow to carry --fs-small comfortably. */
    font-size: .8125rem;
    line-height: 1.6;
}

@media (min-width: 576px) {
    .process-steps {
        grid-template-columns: repeat(2, 1fr);
    }

    .process-step + .process-step {
        border-top: 0;
    }

    .process-step:nth-child(2n) {
        border-left: 1px solid var(--border);
    }

    .process-step:nth-child(n + 3) {
        border-top: 1px solid var(--border);
    }

    /* Five items into two columns leaves the last one stranded in the left
       half. Span it so its centred content reads as a proper final row. */
    .process-step:nth-child(5) {
        grid-column: 1 / -1;
    }
}

@media (min-width: 768px) {
    .process-steps {
        grid-template-columns: repeat(3, 1fr);
    }

    .process-step:nth-child(5) {
        grid-column: auto;
    }

    .process-step:nth-child(2n),
    .process-step:nth-child(n + 3) {
        border-left: 0;
        border-top: 0;
    }

    .process-step:not(:nth-child(3n + 1)) {
        border-left: 1px solid var(--border);
    }

    .process-step:nth-child(n + 4) {
        border-top: 1px solid var(--border);
    }
}

/* Five across — the brochure layout. Only from 1200px, below which the
   columns get too narrow for the body copy. */
@media (min-width: 1200px) {
    .process-steps {
        grid-template-columns: repeat(5, 1fr);
    }

    .process-step:nth-child(n + 4),
    .process-step:not(:nth-child(3n + 1)) {
        border-left: 0;
        border-top: 0;
    }

    .process-step + .process-step {
        border-left: 1px solid var(--border);
    }
}


/* The reCAPTCHA iframe is a fixed 304px wide and will push a narrow column
   sideways. Scale it down rather than let it cause horizontal overflow. */
@media (max-width: 400px) {
    .g-recaptcha {
        transform: scale(0.85);
        transform-origin: 0 0;
        height: 66px;
    }
}



/* ---- 5.7 Testimonials ---- */
/* One card per testimonial. The figure and the blockquote both used to carry
   background + border + shadow, which drew a card inside a card. The frame now
   belongs to the figure alone; the quote is plain text within it. */
.testimonial-item {
    height: 100%;
    padding: var(--space-6);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-xs);

    /* Column layout so the author block can be pinned to the bottom — see
       figcaption below. Without it, quotes of different lengths push the names
       to three different heights across the row. */
    display: flex;
    flex-direction: column;

    transition: opacity var(--dur) var(--ease),
                transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}

.testimonial-text {
    position: relative;
    margin-bottom: var(--space-5);
    /* Take up the slack so the caption is pushed down. */
    flex: 1 1 auto;
}

/* Pinned to the bottom of the card, so every author sits on one baseline
   regardless of how long the quote above it runs. */
.testimonial-item figcaption {
    margin-top: auto;
}

.testimonial-text::before {
    content: "\201C";
    display: block;
    font-size: 3.5rem;
    line-height: .8;
    font-weight: var(--fw-extra);
    color: var(--accent);
    margin-bottom: var(--space-2);
}

.testimonial-img img {
    width: 64px;
    height: 64px;
    object-fit: cover;
    border-radius: var(--radius-pill);
}

/* Equal-height cards.
   Owl sizes each .owl-item to its own content, so a long quote made one card
   taller and dropped its author 53px below the others. Making the stage a flex
   row stretches every item to the tallest; the inline width Owl writes on each
   item is preserved by flex: 0 0 auto, so its measurements still hold. */
.testimonial-carousel .owl-stage {
    display: flex;
    align-items: stretch;
}

.testimonial-carousel .owl-item {
    flex: 0 0 auto;
    display: flex;
    height: auto;
}

.testimonial-carousel .owl-item > * {
    width: 100%;
}

/* Centre-focus emphasis now applies to the whole card rather than the inner
   quote, since the quote no longer has a frame of its own. */
.testimonial-carousel .owl-item .testimonial-item {
    opacity: .68;
    transform: scale(.94);
}

.testimonial-carousel .owl-item.center .testimonial-item {
    opacity: 1;
    transform: scale(1);
    box-shadow: var(--shadow-md);
}

.testimonial-carousel .owl-nav {
    display: flex;
    justify-content: center;
    gap: var(--space-3);
    margin-top: var(--space-5);
}

.testimonial-carousel .owl-nav .owl-prev,
.testimonial-carousel .owl-nav .owl-next {
    width: 44px;
    height: 44px;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    color: var(--primary);
    background: var(--surface) !important;
    border: 1px solid var(--border-strong) !important;
    border-radius: var(--radius-pill) !important;
    transition: all var(--dur) var(--ease);
}

.testimonial-carousel .owl-nav .owl-prev:hover,
.testimonial-carousel .owl-nav .owl-next:hover {
    color: #fff;
    background: var(--primary) !important;
    border-color: var(--primary) !important;
}


/* ---- 5.8 About / mission & vision ---- */
.about-us,
.mission-vision {
    padding-block: var(--section-y);
    background-color: var(--surface-2);
}

.about-title {
    font-size: var(--fs-h2);
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-heading);
    margin-bottom: var(--space-5);
    color: var(--text);
}

.about-text {
    font-size: var(--fs-body-lg);
    line-height: 1.7;
    color: var(--text-secondary);
}

.about-image img {
    width: 100%;
    height: auto;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}

.icon-box {
    width: 62px;
    height: 62px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--space-4);
    font-size: 26px;
    color: var(--primary);
    background: var(--primary-soft);
    border-radius: var(--radius-md);
}


/* ---- 5.8a Why Gleenergy -------------------------------------------------
   A 3x2 grid separated by hairlines instead of cards, per the brochure.
   The dividers are borders on the cells rather than a background trick, so
   they survive reflow. Each breakpoint resets the rules the smaller one set:
   the media queries are additive, so nth-child logic from the 2-up layout
   would otherwise leak into the 3-up layout and stripe the wrong cells. */
.reasons-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: 1fr;
}

.reason {
    padding: var(--space-6) var(--space-5);
    text-align: center;
}

/* 1-up: stacked, so dividers run horizontally. */
.reason + .reason {
    border-top: 1px solid var(--border);
}

.reason-icon {
    width: 56px;
    height: 56px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--space-4);
    font-size: 22px;
    color: #fff;
    background: var(--gradient-brand);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}

.reason-title {
    margin-bottom: var(--space-2);
    font-size: var(--fs-h5);
    color: var(--primary);
}

.reason-text {
    margin-bottom: 0;
    color: var(--muted);
    font-size: var(--fs-small);
}

@media (min-width: 576px) {
    .reasons-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    .reason + .reason {
        border-top: 0;
    }

    .reason:nth-child(2n) {
        border-left: 1px solid var(--border);
    }

    .reason:nth-child(n + 3) {
        border-top: 1px solid var(--border);
    }
}

@media (min-width: 992px) {
    .reasons-grid {
        grid-template-columns: repeat(3, 1fr);
    }

    /* Undo the 2-up striping before applying the 3-up striping. */
    .reason:nth-child(2n),
    .reason:nth-child(n + 3) {
        border-left: 0;
        border-top: 0;
    }

    .reason:not(:nth-child(3n + 1)) {
        border-left: 1px solid var(--border);
    }

    .reason:nth-child(n + 4) {
        border-top: 1px solid var(--border);
    }
}

/* ---- 5.8b Core values ---- */
.core-values-heading {
    margin: var(--space-8) 0 var(--space-5);
    text-align: center;
    font-size: var(--fs-h3);
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-heading);
}

.core-values {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/* Paired with the values rather than sitting above them, so the block reads
   as one unit the way the printed brochure does. object-fit lets the 4:3
   source fill a taller frame that matches the height of five stacked rows
   without distorting. */
.core-values-image {
    width: 100%;
    height: 100%;
    /* No max-height on desktop: the column is a stretched flex item, so
       height:100% resolves to the row height and the photo ends level with
       the last value. Capping it would reintroduce the mismatch. */
    object-fit: cover;
    object-position: center;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}

@media (max-width: 991.98px) {
    /* Stacked: the column no longer has a height to fill, so give the photo
       a fixed band rather than letting a 4:3 source run 280px+ tall. */
    .core-values-image {
        height: 240px;
    }
}

/* Icon beside the text rather than above it: these are one-line definitions,
   so a horizontal row keeps each value compact and lets five of them sit in
   a 3 + 2 grid without the block dominating the About section. */
.value-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    height: 100%;
    padding: var(--space-5);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-xs);
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}

.value-item:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
    border-color: color-mix(in srgb, var(--primary) 26%, var(--border));
}

.value-icon {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    color: var(--primary);
    background: var(--primary-soft);
    border-radius: var(--radius);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

.value-item:hover .value-icon {
    background: var(--gradient-brand);
    color: #fff;
}

.value-body {
    display: flex;
    flex-direction: column;
    font-size: var(--fs-small);
    color: var(--text-secondary);
    line-height: 1.55;
}

.value-body strong {
    margin-bottom: var(--space-1);
    font-size: var(--fs-h5);
    font-weight: var(--fw-semibold);
    color: var(--text);
}

@media (prefers-reduced-motion: reduce) {
    .value-item:hover { transform: none; }
}


/* ---- 5.9 Partner / client logos ---- */
.partner-logo {
    max-width: 100%;
    height: auto;
    filter: grayscale(100%);
    opacity: .62;
    transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease);
}

.partner-logo:hover {
    filter: grayscale(0%);
    opacity: 1;
}

.logo-strip {
    padding-block: var(--section-y-tight);
    background: var(--surface-2);
    border-block: 1px solid var(--border);
}

.logo-strip-track {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: clamp(1rem, 1.6vw, 1.75rem) clamp(1rem, 1.8vw, 2rem);
}

/* ---- Continuous logo marquee ----
   The nine brands used to wrap onto three stacked rows on a phone. Scrolling
   them instead keeps a single tidy line at every width and shows every brand
   without the visitor doing anything.

   The list is rendered twice and the track translates exactly -50%. At that
   point copy 2 sits precisely where copy 1 started, so restarting the
   animation is invisible — no jump, no gap. Duration is set from the number
   of logos so the speed stays constant if brands are added or removed. */
.logo-marquee {
    position: relative;
    overflow: hidden;
    /* Fade the ends so logos enter and leave instead of being chopped off. */
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

/* The animation only runs once main.js has appended the duplicate list.
   Before that (and with JS off) the strip renders as the normal static
   wrapped row — translating -50% with a single copy would drag it half
   off-screen and leave a gap. */
.logo-marquee-track {
    display: flex;
    justify-content: center;
    width: 100%;
}

.logo-marquee.is-ready .logo-marquee-track {
    width: max-content;
    justify-content: flex-start;
    animation: logo-marquee var(--marquee-duration, 45s) linear infinite;
}

/* Static fallback: let the single list wrap normally until it is cloned. */
.logo-marquee:not(.is-ready) .logo-marquee-group {
    flex-wrap: wrap;
    justify-content: center;
}

.logo-marquee-group {
    /* Override the wrapping strip: inside the marquee it is one flat line. */
    flex-wrap: nowrap;
    flex: 0 0 auto;
    justify-content: flex-start;
    /* Matches the track gap so the join between the two copies keeps rhythm. */
    padding-inline: calc(clamp(1rem, 1.8vw, 2rem) / 2);
}

.logo-marquee-group .logo-strip-item {
    flex: 0 0 auto;
}

/* Trim the band to the logos.
   Because each file carries a different amount of transparent padding, the
   canvas that has to grow largest to yield the target artwork height (Enphase,
   32% fill -> a 100px canvas for 32px of artwork) would otherwise dictate the
   row height and leave the strip looking airy. Capping the row keeps the band
   proportionate; the overflow being clipped is transparent padding only, and
   every logo's artwork is centred in its canvas with room to spare.

   Once the files are re-exported to a uniform 60% fill (HANDOVER.md §0c) the
   canvases equalise and this cap stops doing anything. */
.logo-marquee.is-ready .logo-marquee-group {
    height: calc(var(--logo-height, 32px) * 2.2);
    align-items: center;
    overflow: hidden;
}

@keyframes logo-marquee {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
}

/* Let people stop it to read a brand, by pointer or by keyboard. */
.logo-marquee:hover .logo-marquee-track,
.logo-marquee:focus-within .logo-marquee-track {
    animation-play-state: paused;
}

/* Reduced motion: no movement at all. Fall back to the static wrapped row,
   and drop the duplicate set so nothing is shown twice. */
@media (prefers-reduced-motion: reduce) {
    .logo-marquee {
        -webkit-mask-image: none;
                mask-image: none;
    }
    .logo-marquee-track {
        animation: none;
        width: 100%;
        justify-content: center;
    }
    .logo-marquee-group {
        flex-wrap: wrap;
        justify-content: center;
    }
    .logo-marquee-group[aria-hidden="true"] {
        display: none;
    }
}

.logo-strip-item {
    display: flex;
    align-items: center;
    justify-content: center;

    /* Default lives on the PARENT so it reaches the image by inheritance.
       Putting it on `.logo-strip-item img` instead would give it specificity
       (0,1,1), which outranks the single-class per-logo overrides below and
       silently pins every logo to the same value. A directly-set property
       always beats an inherited one, so this ordering just works. */
    --logo-fill: 0.5;

    /* Same inheritance trick as --logo-fill above: the default must live on
       the parent so the single-class per-logo overrides can beat it. */
    --logo-nudge: 0;

    /* Artwork height — the single knob for logo scale.
       These were previously 15–20px only because nine logos had to fit one
       static row inside the container. The marquee scrolls, so that ceiling
       no longer applies and they can be read at a comfortable size. */
    --logo-height: clamp(22px, 2.2vw, 32px);
}

/* Optical normalisation.
   ---------------------------------------------------------------------
   Every supplier file is an 800x400 canvas, but the artwork inside each
   one occupies a wildly different share of that canvas — measured from
   the actual pixels: Canadian Solar fills 73% of the height, Enphase
   only 32%. Sizing by canvas height (the old `height: 42px`) therefore
   rendered the artwork anywhere between 13px and 31px tall: a 2.3x
   spread, which is what made the row look ragged.

   --logo-fill is that measured fraction. Dividing the target height by
   it cancels the file's built-in padding, so every LOGO ends up the same
   optical height no matter how its canvas was prepared.

   These per-logo values become unnecessary once the files are re-exported
   to a consistent spec (HANDOVER.md §0c) — at that point every value is
   the same and the whole block collapses back to one rule. */
.logo-strip-item img {
    height: calc(var(--logo-height) / var(--logo-fill));
    width: auto;
    max-width: 100%;
    object-fit: contain;

    /* Full colour, matching the supplied reference. */
    filter: none;
    opacity: 1;

    /* Some supplier files ship an opaque white plate instead of alpha
       (HUAWEI does today). `multiply` makes white render as the section
       background, so the plate disappears without editing the asset.
       Harmless on the files that already have proper transparency. */
    mix-blend-mode: multiply;

    /* Several files have their artwork off-centre inside the canvas — SUNWAYS
       carries 180px of padding above and 66px below. Centring the image
       therefore does NOT centre the mark, and it sits low on the row. This
       nudge re-centres the artwork; it is a fraction of the canvas, so it
       scales with --logo-height automatically. */
    transform: translateY(var(--logo-lift));
    --logo-lift: calc(var(--logo-nudge) * -1 * var(--logo-height) / var(--logo-fill));

    transition: transform var(--dur) var(--ease);
}

.logo-strip-item img:hover {
    /* Must repeat the lift — a bare scale() would drop it and make the logo
       jump vertically on hover. */
    transform: translateY(var(--logo-lift)) scale(1.06);
}

/* Per-file corrections, both MEASURED from the artwork's pixel bounds rather
   than estimated (scratchpad/measure-logos.ps1).

     --logo-fill  artwork height / canvas height. Drives scale, so every mark
                  renders at the same optical height.
     --logo-nudge (topPadding - bottomPadding) / (2 x canvas height). Corrects
                  artwork that is not centred in its own canvas. 0 = centred.

   All thirteen are listed even where the value equals a default, so a missing
   brand is obvious. A brand with no entry silently falls back to fill 0.5,
   which is what left lvtopsun ~80% oversized and dyness ~45% undersized. */
.logo-jinko      { --logo-fill: 0.642; --logo-nudge: -0.011; }
.logo-solis      { --logo-fill: 0.625; --logo-nudge:  0;     }
.logo-longi      { --logo-fill: 0.410; --logo-nudge:  0.025; }
.logo-trina      { --logo-fill: 0.425; --logo-nudge:  0;     }
.logo-deye       { --logo-fill: 0.690; --logo-nudge:  0.025; }
.logo-sunways    { --logo-fill: 0.385; --logo-nudge:  0.143; }
.logo-luxpower   { --logo-fill: 0.335; --logo-nudge:  0.040; }
.logo-thinkpower { --logo-fill: 0.388; --logo-nudge:  0.026; }
.logo-enphase    { --logo-fill: 0.325; --logo-nudge:  0.100; }
.logo-huawei     { --logo-fill: 0.390; --logo-nudge:  0;     }
.logo-genix      { --logo-fill: 0.338; --logo-nudge:  0;     }
.logo-lvtopsun   { --logo-fill: 0.892; --logo-nudge:  0;     }
.logo-dyness     { --logo-fill: 0.272; --logo-nudge: -0.031; }

@media (prefers-reduced-motion: reduce) {
    /* Drop the hover zoom but KEEP the centring lift — `transform: none` here
       would shunt every off-centre logo back down the row. */
    .logo-strip-item img:hover { transform: translateY(var(--logo-lift)); }
}

.page-partners,
.page-clients {
    background: var(--surface);
}


/* ---- 5.10 Media / video ---- */
.video-thumbnail {
    position: relative;
    cursor: pointer;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.video-thumbnail img {
    width: 100%;
    height: auto;
    display: block;
}

.video-thumbnail .play-icon {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 72px;
    height: 72px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    color: var(--dark);
    background-color: var(--accent);
    border-radius: 50%;
    box-shadow: var(--shadow-gold);
    transition: transform var(--dur) var(--ease),
                background-color var(--dur) var(--ease);
}

.video-thumbnail:hover .play-icon {
    transform: translate(-50%, -50%) scale(1.08);
    background-color: var(--accent-light);
}

.circle-image {
    border-radius: 50%;
    object-fit: cover;
    width: 100%;
    aspect-ratio: 1 / 1;
}

.rect-image {
    width: 100%;
    height: auto;
    border-radius: var(--radius-md);
}

/* Calculator photo: fixed 4:3, NOT tied to the panel's height.
   It previously used height:100% inside a stretched column so both sides sat
   flush. That could not hold: the panel is ~290px empty and ~1031px once
   results render, so the photo resized 2.4x mid-interaction every time someone
   pressed Calculate. A stable image beats a flush bottom edge — the two can
   only both be true if the panel's height never changes.
   4:3 matches the source (1800x1350), so nothing is cropped or wasted. */
@media (min-width: 992px) {
    #calculator .rect-image {
        aspect-ratio: 4 / 3;
        height: auto;
        object-fit: cover;
        object-position: center;
    }
}

@media (max-width: 991.98px) {
    /* Stacked: no column height to fill, so give the photo a fixed band
       rather than letting a 4:3 source run the full width tall. */
    #calculator .rect-image {
        height: 260px;
        object-fit: cover;
    }
}

/* Embedded maps and video only — not a global iframe rule */
.map,
.map-embed {
    border-radius: var(--radius-md);
    overflow: hidden;
    border: 1px solid var(--border);
    line-height: 0;
}

.map iframe,
.map-embed iframe {
    display: block;
    width: 100%;
    height: clamp(280px, 40vw, 420px);
    border: 0;
}


/* ---- 5.11 Contact ---- */
/* Height is opt-in via Bootstrap's .h-100, NOT baked in here.
   These cards appear in two different contexts: a three-across grid where
   equal heights look right, and as a single aside next to the contact form.
   A blanket height:100% served the first and wrecked the second — the aside
   stretched to match the form column (946px for 111px of content) and
   overflowed the section. Add .h-100 in the markup where equal heights are
   actually wanted. */
.contact-info-card {
    padding: var(--space-6);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-xs);
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}

.contact-info-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
}

.contact-info-card .icon-box {
    margin-bottom: var(--space-4);
}

.contact-info-card h3 {
    font-size: var(--fs-h5);
    margin-bottom: var(--space-2);
}

.contact-info-card a {
    font-weight: var(--fw-medium);
    word-break: break-word;
}

.contact-info p {
    margin: var(--space-1) 0;
}




/* ---- 5.14 Image viewer -------------------------------------------------
   Replaces lightbox.min.css. Dark surface so the photograph carries the
   frame, with the caption in the header and the counter in the footer. */
.gallery-modal .modal-content {
    background: #14081d;
    border: 0;
    border-radius: var(--radius-md);
    color: #fff;
    overflow: hidden;
}

.gallery-modal .modal-header,
.gallery-modal .modal-footer {
    border-color: rgba(255, 255, 255, .12);
    padding: var(--space-4) var(--space-5);
}

.gallery-modal .modal-title {
    font-size: var(--fs-small);
    font-weight: var(--fw-medium);
    line-height: 1.4;
    color: rgba(255, 255, 255, .92);
    margin: 0;
    padding-right: var(--space-4);
}

/* Bootstrap's close button is a dark SVG; invert it on this surface. */
.gallery-modal .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
    opacity: .8;
}

.gallery-modal .btn-close:hover {
    opacity: 1;
}

.gallery-modal .modal-body {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: #000;
}

.gallery-modal .modal-body img {
    display: block;
    max-width: 100%;
    /* Leaves room for the header and footer inside the viewport. */
    max-height: 72vh;
    width: auto;
    height: auto;
    object-fit: contain;
}

.gallery-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    color: #fff;
    background: rgba(0, 0, 0, .45);
    border: 1px solid rgba(255, 255, 255, .25);
    border-radius: 50%;
    cursor: pointer;
    transition: background var(--dur) var(--ease);
}

.gallery-nav:hover,
.gallery-nav:focus-visible {
    background: var(--secondary);
    border-color: var(--secondary);
}

.gallery-prev { left: var(--space-4); }
.gallery-next { right: var(--space-4); }

.gallery-count {
    margin: 0;
    font-size: var(--fs-caption);
    color: rgba(255, 255, 255, .6);
}

/* An empty footer would still draw its top border. */
.gallery-modal .modal-footer:has(.gallery-count:empty) {
    display: none;
}

@media (max-width: 575.98px) {
    .gallery-modal .modal-body img { max-height: 60vh; }
    .gallery-nav { width: 38px; height: 38px; }
    .gallery-prev { left: var(--space-2); }
    .gallery-next { right: var(--space-2); }
}


/* ---- 5.15 Chat widget spacing ----
   The third-party bubble sits bottom-right; keep our own floating
   controls clear of it. */
/* The Pancake loader injects its launcher into <body>, not into this
   wrapper, so this rule does not control the widget itself — it only keeps
   the (empty) script container out of the layout. Pancake's own stacking is
   set by the vendor and is not overridden anywhere in this stylesheet. */
.chat-widget {
    position: relative;
    z-index: 1040;
}

/* Third-party chat launchers commonly stack themselves near the top of the
   z-index range. When the visitor has deliberately opened the quote panel,
   that panel must win — otherwise the bubble can sit over the form fields
   and the submit button. Bootstrap's defaults (1045/1040) are too low to
   guarantee that against a vendor widget.

   NOTE: verify on the live domain. Pancake's actual z-index cannot be read
   from a local preview, and if it ever exceeds this value these two numbers
   are the only thing that needs changing. */
.offcanvas,
.offcanvas.show {
    z-index: 2147482000;
}

.offcanvas-backdrop {
    z-index: 2147481999;
}


/* ==========================================================================
   6. VENDOR OVERRIDES
   ========================================================================== */

/* ---- Owl Carousel — hero ----
   Slide height. object-fit:cover scales the photo to fill the WIDTH, so
   whatever exceeds this height is cropped away, centred. Artwork should be
   prepared against the crop windows documented in HANDOVER.md §0b — keep the
   subject inside the middle band and away from the left third, which the
   text panel and its gradient sit over. */
.header-carousel .owl-carousel-item {
    position: relative;
    min-height: clamp(460px, 62vh, 720px);
}

.header-carousel .owl-carousel-item > img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.owl-carousel-inner {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    background: var(--gradient-veil);
}

.header-carousel .owl-dots {
    position: absolute;
    right: clamp(1rem, 3vw, 2rem);
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    z-index: 3;
}

.header-carousel .owl-dots .owl-dot {
    position: relative;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-pill);
    overflow: hidden;
    border: 2px solid rgba(255, 255, 255, .55);
    opacity: .7;
    transition: width var(--dur) var(--ease),
                height var(--dur) var(--ease),
                opacity var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}

.header-carousel .owl-dots .owl-dot.active {
    width: 56px;
    height: 56px;
    opacity: 1;
    border-color: var(--accent);
}

.header-carousel .owl-dots .owl-dot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

@media (max-width: 767.98px) {
    .header-carousel .owl-dots {
        top: auto;
        bottom: var(--space-4);
        left: 50%;
        right: auto;
        transform: translateX(-50%);
        flex-direction: row;
    }

    .header-carousel .owl-dots .owl-dot {
        width: 34px;
        height: 34px;
    }

    .header-carousel .owl-dots .owl-dot.active {
        width: 40px;
        height: 40px;
    }
}

/* ---- Owl Carousel — image sizing ----
   Owl ships `.owl-carousel .owl-item img { width: 100% }`, specificity (0,2,1).
   Anything that needs to disagree must beat that, and matching it is not
   enough — equal specificity is settled by source order, which is brittle.
   Both rules below are (0,3,1) so they win outright, wherever they sit.

   A blanket `.owl-carousel .owl-item img { width: auto }` used to live here,
   left over from a logo carousel that is now a plain flex strip. Because it
   tied with the hero rule on specificity and came later in the file, it won —
   and the hero photo was sized from its height and its 16:9 aspect ratio
   instead of filling the slide, leaving a band of empty purple on the right
   at any viewport wider than about 1060px. */

/* Hero slides: the photo is the background, so it must cover the slide. */
.header-carousel .owl-item .owl-carousel-item > img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    max-width: none;   /* the global img{max-width:100%} would cap it otherwise */
}

/* Testimonial avatars: fixed circles, never full-bleed. */
.testimonial-carousel .owl-item .testimonial-img img {
    width: 64px;
    height: 64px;
    max-width: none;
    object-fit: cover;
    border-radius: var(--radius-pill);
}

/* ---- Bootstrap carousel used for the product logo slider ---- */
.carousel-item img {
    height: 120px;
    object-fit: contain;
    margin: 0 auto;
}

/* ---- Lightbox ---- */
.lb-data .lb-caption,
.lb-data .lb-number {
    font-family: var(--font-sans);
}

/* ---- Isotope ---- */
.portfolio-container {
    /* Isotope absolutely positions children; keep it from collapsing */
    min-height: 1px;
}


/* ==========================================================================
   7. MOTION & ACCESSIBILITY
   ========================================================================== */

/* Scroll-reveal (progressive: without JS everything stays visible) */
.reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity var(--dur-slow) var(--ease-out),
                transform var(--dur-slow) var(--ease-out);
    will-change: opacity, transform;
}

.reveal.is-visible {
    opacity: 1;
    transform: none;
}

.no-js .reveal,
html:not(.js) .reveal {
    opacity: 1;
    transform: none;
}

/* Honour the user's motion preference across our styles AND the
   animate.css / WOW.js classes the markup still uses. */
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    *,
    *::before,
    *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }

    .reveal {
        opacity: 1 !important;
        transform: none !important;
    }

    .animated,
    .wow {
        animation: none !important;
        visibility: visible !important;
    }

    .btn:hover,
    .card:hover,
    .service-item:hover,
    .stat-card:hover,
    .contact-info-card:hover {
        transform: none;
    }
}

/* Windows high-contrast / forced colours */
@media (forced-colors: active) {
    .btn,
    .card,
    .service-item,
    .stat-card {
        border: 1px solid ButtonText;
    }
}

/* Print */
@media print {
    .navbar,
    .offcanvas,
    .back-to-top,
    .chat-widget,
    .footer,
    #spinner {
        display: none !important;
    }

    body {
        color: #000;
        background: #fff;
    }

    a[href^="http"]::after {
        content: " (" attr(href) ")";
        font-size: .8em;
    }
}
