/* ==========================================================================
   components.css — shared UI: buttons, cards, footer, lightbox.
   Used by home, service, and location pages alike. Header/nav live in
   css/nav.css; forms in css/form.css.
   ========================================================================== */


/* --------------------------------------------------------------------------
   Buttons. Three roles only: emergency, primary, quiet.
   -------------------------------------------------------------------------- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 48px;
    /* em, not rem: a text container's padding tracks its own font-size, so the
       larger emergency face and the mobile size-down carry their padding with
       them (see agency/development.md). 0.7/1.88em == 12/32px at the 17px base. */
    padding: 0.7em 1.88em;
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 700;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--dur) var(--ease-out),
                border-color var(--dur) var(--ease-out),
                color var(--dur) var(--ease-out),
                scale var(--dur-press) var(--ease-out);
}

/* Press feedback. `scale` as its own property, not `transform`, so it composes
   with elements that already translate (the floating call button). */
.btn:active,
.call-fab:active,
.gallery-grid button:active,
.social a:active,
.service-list a:active {
    scale: 0.97;
}

.btn svg {
    flex: none;
}

/* The button's `gap` separates the icon from the label. A label split into a
   bare text node plus a span becomes two flex items, and the gap replaces the
   word space between them. Keep each label inside a single element. */

/* Emergency: the single loudest thing on the page. */
.btn--emergency {
    background: var(--emergency);
    color: var(--ink-on-dark);
    font-size: var(--text-lg);
    padding-inline: 2.29em;   /* 48px at the 21px emergency face */
}

.btn--emergency:hover {
    background: var(--emergency-hover);
    color: var(--ink-on-dark);
}

/* At 21px with 3rem of inline padding the phone number has under 110px to sit
   in on a 320px screen, and breaks across four lines. The number must never
   wrap: it is the thing being read. Shrink the padding and the type instead. */
@media (max-width: 480px) {
    .btn--emergency {
        padding-inline: 0.7em;
        font-size: var(--text-base);
    }
}

.btn--primary {
    background: var(--blue-fill);
    color: var(--ink-on-dark);
}

.btn--primary:hover {
    background: var(--blue-fill-hover);
    color: var(--ink-on-dark);
}

.btn--outline {
    background: transparent;
    border-color: var(--border-strong);
    color: var(--ink);
}

.btn--outline:hover {
    border-color: var(--ink);
    background: var(--surface-raised);
    color: var(--ink);
}

.section--ink .btn--outline {
    border-color: var(--ink-border-strong);
    color: var(--ink-on-dark);
}

.section--ink .btn--outline:hover {
    background: var(--ink-raised);
    border-color: var(--on-ink-muted);
    color: var(--ink-on-dark);
}

.btn--block {
    width: 100%;
}

/* Floating call button: the thumb-zone path for a caller mid-scroll. It stands
   down over the footer, where it would otherwise cover the footer's own phone
   link and make it unclickable. */
.call-fab[data-hidden] {
    opacity: 0;
    transform: translateY(120%);
    pointer-events: none;
}

.call-fab {
    transition: opacity var(--dur) var(--ease-out),
                transform var(--dur-slow) var(--ease-out),
                background var(--dur) var(--ease-out),
                scale var(--dur-press) var(--ease-out);
    position: fixed;
    right: var(--gutter);
    bottom: max(var(--space-5), env(safe-area-inset-bottom));
    z-index: var(--z-fab);
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 56px;
    padding: 0.7em 1.41em;
    background: var(--emergency);
    color: var(--ink-on-dark);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-lg);
    font-family: var(--font-display);
    font-weight: 700;
    text-decoration: none;
}

.call-fab:hover {
    background: var(--emergency-hover);
    color: var(--ink-on-dark);
}

@media (prefers-reduced-motion: reduce) {
    .call-fab[data-hidden] {
        transform: none;
    }
}

@media (max-width: 720px) {
    .call-fab {
        left: var(--gutter);
        right: var(--gutter);
        justify-content: center;
    }
}

/* --------------------------------------------------------------------------
   Cards
   -------------------------------------------------------------------------- */
/* The card is an em component: its font-size is the single dial, and its
   padding, gap and icon are all em, so resizing the card is one declaration
   and the internal rhythm stays proportional. 1.88/0.71em == 32/12px here. */
.card {
    position: relative;   /* containing block for the stretched hit area */
    display: flex;
    flex-direction: column;
    font-size: var(--text-base);
    gap: 0.71em;
    padding: 1.88em;
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    transition: border-color var(--dur) var(--ease-out),
                box-shadow var(--dur) var(--ease-out),
                translate var(--dur) var(--ease-out),
                scale var(--dur-press) var(--ease-out);
}

/* On a narrow card the even em padding eats the content column. Pull the
   inline padding to a flat 1.6rem below 640px so the text has room to breathe. */
@media (max-width: 640px) {
    .card {
        padding-inline: 1.6rem;
    }
}

/* Each card holds exactly one link, so the whole card can be the target.
   Pressing anywhere in the card drives the link's :active state. */
.card:has(.card__link:active) {
    scale: 0.995;
}

/* The card had no resting-to-hover state at all: it looked like paper, not
   like something you could act on. Gated, because a tap fires :hover. */
@media (hover: hover) and (pointer: fine) {
    /* A nod, not a shout. The card was changing border colour, shadow, lift and
       button fill at once. `::after` is the stretched hit area now, so it must
       never translate. */
    .card:hover {
        border-color: var(--border-strong);
        box-shadow: var(--shadow);
        translate: 0 -2px;
    }
}

.card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.59em;      /* 44px; scales with the card */
    height: 2.59em;
    border-radius: var(--radius);
    background: var(--blue-wash);
    color: var(--blue-text);
}

.card__icon svg {
    width: 1.29em;      /* 22px */
    height: 1.29em;
}

.card p {
    color: var(--ink-body);
}

/* This was plain blue text with a glyph: nothing said "control". It is a
   tonal button now. Subordinate to the page's primary orange CTA by weight
   and colour, but unmistakably pressable. */
.card__link {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 0.47em;
    margin-top: auto;
    padding: 0.47em 1.41em;
    background: var(--blue-wash);
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--blue-text);          /* 5.93:1 on --blue-wash */
    font-family: var(--font-display);
    font-weight: 700;
    text-decoration: none;
    transition: background var(--dur) var(--ease-out),
                border-color var(--dur) var(--ease-out),
                color var(--dur) var(--ease-out);
}

/* The stretched hit area. `inset: 0` on the card, not on the button. */
.card__link::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

.card__link svg {
    flex: none;
    transition: translate var(--dur) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
    .card:hover .card__link {
        border-color: var(--blue-fill);
    }

    .card:hover .card__link svg {
        translate: 3px 0;
    }
}

/* --------------------------------------------------------------------------
   Checklist. Ticks are inline SVG, never a glyph from an unloaded icon font.
   -------------------------------------------------------------------------- */
/* `min(240px, 100%)` keeps auto-fit from demanding a 240px track in a column
   narrower than that, which is what forces a horizontal scrollbar on phones. */
.checklist {
    margin-top: var(--space-6);
    display: grid;
    gap: var(--space-3);
    grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
    list-style: none;
}

.checklist li {
    display: grid;
    grid-template-columns: 2.2rem 1fr;
    gap: var(--space-3);
    align-items: start;
}

.checklist svg {
    margin-top: 0.3em;
    color: var(--blue-text);
}

.section--ink .checklist svg {
    color: var(--blue-on-ink);
}

/* Form styles (.form-card, .field, inputs, select, validation, .form-status)
   live in css/form.css. */

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.site-footer {
    padding-block: var(--section-y) var(--space-6);
    background: var(--surface-ink);
    color: var(--on-ink-muted);
}

/* Footer column labels are NOT content headings: they must not compete with the
   page's own h2s in the document outline. Rendered as styled paragraphs inside
   <nav> landmarks, so screen readers still get named sections without polluting
   the heading hierarchy. */
.site-footer .footer__heading {
    margin: 0 0 var(--space-4);
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--ink-on-dark);
}

/* The logo is dark blue; invert it to white so it reads on the dark footer. */
.site-footer .logo img { filter: brightness(0) invert(1); }

/* Brand, services, company. The 25-city list is NOT a column here; it gets its
   own full-width band below, or it renders as a 25-item vertical scroll. */
.footer-grid {
    display: grid;
    gap: var(--space-7);
    grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}

.footer-grid ul {
    list-style: none;
    display: grid;
    gap: var(--space-2);
}

.site-footer a {
    color: var(--on-ink-soft);
    text-decoration: none;
}

.site-footer a:hover {
    color: var(--ink-on-dark);
    text-decoration: underline;
}

/* Full-width band. auto-fill (not auto-fit) so the columns keep their width and
   the cities read as a tidy directory instead of stretching to fill the row. */
.footer-areas {
    margin-top: var(--space-7);
    padding-top: var(--space-6);
    border-top: 1px solid var(--ink-raised);
}

.footer-areas ul {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
    gap: var(--space-2) var(--space-5);
    list-style: none;
}

.footer-areas a {
    display: block;
    padding-block: var(--space-1);
}

.footer-bottom {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    align-items: center;
    justify-content: space-between;
    margin-top: var(--space-7);
    padding-top: var(--space-5);
    border-top: 1px solid var(--ink-raised);
    font-size: var(--text-sm);
}

/* Paid-Google landings: strip the footer's browse paths so the page keeps the
   visitor on the one service they clicked an ad for. utilities.js sets the flag
   in the head; see there for how it is detected and carried across pages. */
html[data-source="gads"] .footer-areas,
html[data-source="gads"] .footer-item--content {
    display: none;
}

/* `ul.social`, not `.social`: `.footer-grid ul` above sets display:grid and
   would otherwise win on specificity and stack these into a column. */
ul.social {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
    margin-top: var(--space-5);
    list-style: none;
}

.social a {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 36px;
    padding: 0 var(--space-4);
    border: 1px solid var(--ink-border);
    border-radius: var(--radius-pill);
    color: var(--on-ink-soft);
    font-size: var(--text-sm);
    font-weight: 600;
}

.social a:hover {
    background: var(--ink-raised);
    border-color: var(--ink-border-strong);
    color: var(--ink-on-dark);
}

/* --------------------------------------------------------------------------
   Lightbox
   -------------------------------------------------------------------------- */
/* `display` must stay tied to [open], or it overrides the UA rule that keeps a
   closed dialog hidden and the gallery renders inline on the page. */
.lightbox:not([open]) {
    display: none;
}

.lightbox[open] {
    display: grid;
    opacity: 1;
    scale: 1;
    transition-duration: var(--dur);
}

/* A modal is not anchored to a trigger, so it scales from its centre. */
.lightbox {
    opacity: 0;
    scale: 0.96;
    /* Exit is faster than enter. */
    transition: opacity var(--dur-press) var(--ease-out),
                scale var(--dur-press) var(--ease-out),
                overlay var(--dur-press) allow-discrete,
                display var(--dur-press) allow-discrete;
}

@starting-style {
    .lightbox[open] {
        opacity: 0;
        scale: 0.96;
    }
}

.lightbox {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    place-items: center;
    max-width: 100vw;
    max-height: 100vh;
    width: 100%;
    height: 100%;
    padding: var(--space-5);
    background: rgb(var(--scrim-rgb) / 0.92);
    border: 0;
}

.lightbox::backdrop {
    background: rgb(var(--scrim-rgb) / 0.92);
}

.lightbox img {
    max-width: min(110rem, 92vw);
    max-height: 82vh;
    width: auto;
    border-radius: var(--radius);
}

.lightbox__btn {
    position: absolute;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    background: rgba(255, 255, 255, 0.12);
    color: var(--ink-on-dark);
    border: 1px solid rgba(255, 255, 255, 0.24);
    border-radius: 50%;
    font-size: var(--text-xl);
    line-height: 1;
    cursor: pointer;
}

.lightbox__btn:hover {
    background: rgba(255, 255, 255, 0.24);
}

.lightbox__close { top: var(--space-5); right: var(--space-5); }
.lightbox__prev { left: var(--space-5); }
.lightbox__next { right: var(--space-5); }

.lightbox__count {
    position: absolute;
    bottom: var(--space-5);
    left: 50%;
    translate: -50% 0;
    color: var(--on-ink-soft);
    font-size: var(--text-sm);
}

/* --------------------------------------------------------------------------
   Reveal on scroll. The hidden start state applies only under
   [data-reveal-ready], which JS sets after confirming observer support.
   Without that attribute the content is visible. Keep it that way.
   -------------------------------------------------------------------------- */
[data-reveal-ready] [data-reveal] {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity var(--dur-slow) var(--ease-out),
                transform var(--dur-slow) var(--ease-out);
}

[data-reveal-ready] [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
}

/* --------------------------------------------------------------------------
   Stagger. A group whose children all arrive together reads as a switch being
   thrown. Arriving in sequence reads as a hand dealing cards. The container
   itself does not fade, or the group would cross-fade under its own children.

   Stagger is decorative: it never gates interaction, and the last child is in
   place inside 400ms.
   -------------------------------------------------------------------------- */
[data-reveal-ready] [data-reveal][data-stagger] {
    opacity: 1;
    transform: none;
}

[data-reveal-ready] [data-stagger] > * {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity var(--dur-slow) var(--ease-out),
                transform var(--dur-slow) var(--ease-out);
}

[data-reveal-ready] [data-stagger].is-revealed > * {
    opacity: 1;
    transform: none;
}

[data-reveal-ready] [data-stagger].is-revealed > *:nth-child(1) { transition-delay: 0ms; }
[data-reveal-ready] [data-stagger].is-revealed > *:nth-child(2) { transition-delay: calc(var(--stagger) * 1); }
[data-reveal-ready] [data-stagger].is-revealed > *:nth-child(3) { transition-delay: calc(var(--stagger) * 2); }
[data-reveal-ready] [data-stagger].is-revealed > *:nth-child(4) { transition-delay: calc(var(--stagger) * 3); }
[data-reveal-ready] [data-stagger].is-revealed > *:nth-child(5) { transition-delay: calc(var(--stagger) * 4); }
[data-reveal-ready] [data-stagger].is-revealed > *:nth-child(n+6) { transition-delay: calc(var(--stagger) * 5); }

@media (prefers-reduced-motion: reduce) {
    [data-reveal-ready] [data-reveal] {
        opacity: 1;
        transform: none;
        transition: none;
    }
}

/* --------------------------------------------------------------------------
   Map embed. A responsive map iframe (Google Maps etc.): fills its container
   and never collapses below a usable height.
   -------------------------------------------------------------------------- */
.map-embed {
    display: block;
    width: 100%;
    height: 100%;
    min-height: 40rem;
    border: 0;
}
