/* =========================
   Global
   ========================= */

* {
    box-sizing: border-box;
}


:root {
    /* Also drives main's padding-top and
       .page-crane-bg's top offset below, so
       all three stay in sync — mobile
       override lives alongside .navbar's own
       height override.

       In rem (6.5rem = 104px at the default
       root size) rather than px, so the bar
       grows with a visitor's browser text-size
       setting instead of clipping its own
       links. */
    --navbar-height: 6.5rem;


    /* =====================================
       Crane void — where the hero sits
       =====================================

       The background crane has a clear
       rectangle framed by the jib (above), the
       mast (right) and the hoist cables (left).
       The hero photo and text are placed inside
       it, so these numbers are shared by both
       the crane and the hero rather than being
       guessed twice.

       Expressed as fractions of the SOURCE
       image, measured off images/crane-bg.png:
       the largest genuinely clear rectangle
       runs x 15.3%-59.9%, y 15.4%-100%. The
       values below sit just inside that, so the
       content never touches the structure.

       These translate to the screen because the
       image is painted at exactly 100% of the
       viewport width (see .page-crane-bg), so
       image-x% === viewport-x%. */

    /* Floor: the hoist cables run down at
       14.72% through this vertical band
       (measured off the source image), so this
       can't go much below 16% without the
       content touching them. */
    --void-left: 16%;

    /* Hard ceiling: the mast's left edge is at
       71.2% of the image through this whole
       vertical band (measured row by row), so
       anything past ~70% starts overlapping the
       tower. 69% keeps a visible margin off it. */
    --void-right: 69%;

    /* Vertical needs one extra step: the image
       is painted --crane-render-height tall
       (200vh), so a fraction OF THE IMAGE is
       double that fraction of the viewport —
       0.16 of the image is 32vh down the screen.
       Kept as a unitless fraction so the
       multiply below stays valid and there's
       only one number to change. */
    /* Note: the jib's underside sits at 0.1587
       across the hero's x-band (measured off
       the source image), so values below that
       tuck the heading slightly under the jib
       rather than clear of it. Fine while the
       crane is at low opacity — but that's the
       line to watch if it's ever made bolder. */
    --void-top-fraction: 0.145;

    --crane-render-height: 200vh;

    --void-top-on-screen:
        calc(
            var(--crane-render-height)
            * var(--void-top-fraction)
        );

    /*
     * One shared card elevation, used by every
     * card-ish surface (.service-card,
     * .review-card, .process-icon-step,
     * .blog-card, .value-card, .recent-work-
     * item, .hero-image …) so they all read as
     * the same material. Two layers rather than
     * one: a tight, barely-there contact shadow
     * plus a wide soft one, which is what makes
     * it read as a lift off the page instead of
     * a grey blur stuck to the edges. Slightly
     * blue-black rather than pure black, which
     * looks less muddy over the page's grey.
     */
    --card-shadow:
        0 1px 2px rgba(16, 24, 40, 0.04),
        0 4px 16px rgba(16, 24, 40, 0.07);

    --card-shadow-hover:
        0 2px 4px rgba(16, 24, 40, 0.05),
        0 12px 28px rgba(16, 24, 40, 0.12);


    /*
     * Two families, each doing one job — the
     * standard way to get typographic contrast
     * without the mismatched look of picking
     * two fonts that are trying to do the same
     * thing.
     *
     * Oswald is condensed and vertical, which
     * reads as industrial/signage — right for a
     * builder, and it earns its keep at large
     * uppercase sizes where the narrow letters
     * let a long heading stay on one line.
     * Deliberately NOT used below ~24px: a
     * condensed face at small sizes just reads
     * as cramped, so card titles (h3) stay on
     * Inter Semibold instead.
     *
     * Inter is a UI/text face designed for
     * screens (tall x-height, open apertures,
     * unambiguous 1/l/I), so it carries body
     * copy, nav, buttons and small headings.
     *
     * Both stacks fall back to the system UI
     * font rather than straight to Arial, so an
     * offline/blocked-CDN visitor still gets
     * something close in proportion rather than
     * a jarring metric shift.
     */
    --font-display:
        "Oswald",
        "Haettenschweiler",
        "Arial Narrow",
        system-ui,
        sans-serif;

    --font-body:
        "Inter",
        system-ui,
        -apple-system,
        "Segoe UI",
        Roboto,
        Arial,
        sans-serif;


    /*
     * Near-black rather than #000: pure black
     * on a light background is high enough
     * contrast to feel harsh and "vibrate" in
     * long paragraphs. This still clears WCAG
     * AA comfortably.
     */
    --color-text: #1b1f26;

    --color-text-muted: #5b6472;
}


html,
body {
    margin: 0;
    padding: 0;
}


/*
 * Without this, some mobile browsers
 * auto-boost text size for readability
 * on pages they treat as "desktop" —
 * which can kick in *after* the page has
 * already rendered at the correct size,
 * so text visibly jumps a moment after
 * load or after switching from a desktop
 * to a mobile viewport. The viewport meta
 * tag above is the main fix; this is the
 * explicit belt-and-suspenders version of
 * the same thing.
 */

html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;

    /* Smooths out anchor jumps like the
       Services page's "Read More" links
       from index.html (#kitchens etc.). */
    scroll-behavior: smooth;
}


body {
    font-family: var(--font-body);

    /*
     * 16px base with a 1.6 line-height — the
     * conventional readable default for body
     * copy. Anything much tighter than 1.5
     * makes multi-line paragraphs hard to
     * track; much looser and lines stop
     * reading as a block.
     */
    font-size: 16px;

    line-height: 1.6;

    color: var(--color-text);

    /*
     * Inter is a fairly light-stemmed face;
     * without this it renders noticeably
     * heavier/blurrier on macOS in particular.
     */
    -webkit-font-smoothing: antialiased;

    -moz-osx-font-smoothing: grayscale;

    /*
     * One page background for the whole site,
     * set here rather than per-section — the
     * sections themselves are transparent, so
     * this shows through on every page
     * (including ones like services.html and
     * about.html whose wrappers never set a
     * background of their own). Cards
     * (.service-card, .review-card, .faq-item,
     * .blog-card, …) stay white so they lift
     * off this rather than blending into it.
     *
     * background-attachment: fixed pins the
     * gradient to the viewport instead of the
     * document, so it reads as one consistent
     * light-from-above wash at any scroll
     * position. Without it the gradient would
     * be stretched over the full page height
     * (5000px+ on the home page), which spreads
     * it so thin it just looks like a flat
     * colour that inexplicably differs top vs
     * bottom.
     */
    background: linear-gradient(180deg, #e4e7ec 0%, #cbd0d9 100%);

    background-attachment: fixed;
}


/* =========================
   Type Scale
   ========================= */

/*
 * h1/h2 are the site's display tier — the big
 * uppercase section headers. Everything about
 * them is set here once so the individual
 * section rules further down only need to say
 * how *big* they are, not restate the family,
 * weight and spacing each time.
 */

h1,
h2 {

    font-family: var(--font-display);

    font-weight: 600;

    /*
     * Display sizes need a much tighter
     * line-height than body text — at 40px the
     * body's 1.6 would leave a visible gap
     * between the lines of a wrapped heading.
     */
    line-height: 1.1;

    /*
     * Positive tracking because these are set
     * uppercase. Capitals are drawn to sit
     * beside lowercase, so at all-caps their
     * default sidebearings read as too tight;
     * opening them up is what stops uppercase
     * headings looking cramped. (The opposite
     * rule applies to h3 below.)
     */
    letter-spacing: 0.02em;

    /*
     * Evens out the line lengths of a heading
     * that wraps, instead of leaving one
     * orphaned word on the last line. Ignored
     * by browsers that don't support it.
     */
    text-wrap: balance;
}


/*
 * h3 is the card/sub-heading tier — service
 * and blog card titles, process step names.
 * Kept on the body face: Oswald is condensed,
 * and condensed type at 18-20px reads as
 * cramped rather than characterful. Weight and
 * case carry the hierarchy here instead.
 */

h3,
h4 {

    font-family: var(--font-body);

    font-weight: 600;

    line-height: 1.3;

    /*
     * Slight negative tracking — the mirror of
     * the uppercase rule above. Inter (like most
     * text faces) is spaced for small sizes, so
     * at heading sizes it benefits from being
     * pulled very slightly tighter.
     */
    letter-spacing: -0.01em;

    text-wrap: balance;
}


/* =========================
   Decorative Background Crane
   ========================= */

/*
 * A real photo of a tower crane, treated as
 * a window exactly one screen wide and tall
 * (position: fixed, 100vw/100vh) looking into
 * an image rendered at twice that height
 * (background-size: 100% 200%) — so at rest
 * you only ever see 50% of the image's own
 * height at once. The inline script below
 * ties background-position-y (as a %) to
 * scroll progress (also a %, 0 at the top of
 * the page to 100 at the bottom): CSS defines
 * percentage background-position as
 * (container size − image size) × percentage,
 * which for a 100vh container and a 200vh
 * image is exactly (scroll progress)% of the
 * image's extra 100vh of overflow — e.g. 50%
 * scrolled through the page shows the image's
 * own 25%–75% band, 100% scrolled shows its
 * 50%–100% band. Decorative only (aria-hidden,
 * pointer-events:none), sitting above every
 * section's own opaque white background (the
 * only way it could show through them at all)
 * but faded enough to not compete with the
 * real content painted after it in the DOM.
 */

.page-crane-bg {

    position: fixed;

    /*
     * Starts below the fixed navbar (always
     * on screen, so this exclusion is
     * permanent) — without it, the navbar's
     * opaque bar covered whatever the crane
     * rendered in that strip, which looked
     * like the top of the image being cut
     * off.
     */
    top: var(--navbar-height);
    left: 0;

    /*
     * 100% (of the viewport, since this is
     * position:fixed) rather than 100vw — vw
     * includes the scrollbar's width, which
     * would make the crane a few px wider than
     * the document and knock its horizontal
     * percentages out of step with the hero's.
     * They have to agree for --void-* below to
     * mean the same thing to both.
     */
    width: 100%;

    height: calc(100vh - var(--navbar-height));

    /*
     * NEGATIVE, deliberately: this puts the
     * crane behind every piece of page content —
     * text included — so it can never obscure
     * anything no matter how opaque it gets.
     *
     * It's still visible because the page's
     * background lives on <body>, which
     * propagates to the root canvas and paints
     * below even negative layers, and because
     * the sections themselves are transparent
     * (see the Content Band rules) so the crane
     * shows straight through them.
     *
     * A z-index of 0 would NOT work here: in the
     * paint order a positioned element at 0 sits
     * ABOVE in-flow inline content, i.e. above
     * the text. Only a negative value drops it
     * beneath.
     */
    z-index: -1;

    /* .png, and it matters: the crane's
       background is transparent, so the page's
       own gradient shows through it. The old
       .jpg-named file was baked on opaque white,
       which sat as a white rectangle over the
       grey page. */
    background-image: url("../images/crane-bg.png");

    /*
     * Width 100% means the image exactly fills
     * this box horizontally, so image-x and
     * screen-x map 1:1 — "20% across the image"
     * is simply "20% across the viewport".
     * That's what makes --void-left/right below
     * work without any magic numbers.
     */
    background-size: 100% var(--crane-render-height);

    background-position: 50% 0%;

    background-repeat: no-repeat;

    /* Full opacity — it sits at a negative
       z-index (see above), so it can be full
       strength without ever competing with the
       content on top. */
    opacity: 1;

    /* Desaturated to grey: the source photo is a
       colour shot, and the brand palette reads
       better with the crane as a neutral
       backdrop rather than a coloured one. Only
       the paint is affected — the layout maths
       above (background-size/-position and the
       --void-* bands) is untouched by a filter. */
    filter: grayscale(1);

    pointer-events: none;
}


/* =========================
   Navigation Bar
   ========================= */

.navbar {

    position: fixed;

    top: 0;
    left: 0;

    width: 100%;

    height: var(--navbar-height);

    display: flex;

    align-items: center;

    justify-content: space-between;

    padding: 0 50px;

    /*
     * Near-black and translucent, with a blur
     * behind it — content scrolling under the
     * bar stays faintly readable as a soft wash
     * instead of vanishing at a hard edge, which
     * is what makes a fixed bar feel like it's
     * floating above the page rather than a lid
     * clamped on top of it. Browsers without
     * backdrop-filter just get a flat dark bar
     * and nothing looks broken.
     *
     * Everything inside the bar (logo, links,
     * active pill, hamburger) is inverted to
     * suit — see the rules below.
     */
    background: rgba(17, 20, 26, 0.9);

    backdrop-filter: blur(14px) saturate(140%);

    -webkit-backdrop-filter: blur(14px) saturate(140%);

    /* Light hairline instead of dark — a dark
       border on a dark bar is invisible. */
    border-bottom: 1px solid rgba(255, 255, 255, 0.10);

    box-shadow:
        0 1px 14px rgba(0, 0, 0, 0.22);

    z-index: 1000;
}


/* =========================
   Logo
   ========================= */

.logo {

    /* Display face here too — the wordmark is
       the one piece of small text that should
       read as brand rather than UI. */
    font-family: var(--font-display);

    font-size: 26px;

    font-weight: 600;

    letter-spacing: 0.08em;

    text-transform: uppercase;

    /* Set explicitly — it would otherwise
       inherit the page's near-black body colour
       and disappear into the dark bar. */
    color: white;
}


/* =========================
   Navigation Links
   ========================= */

.nav-links {

    display: flex;

    align-items: center;

    gap: 8px;
}


.nav-links a {

    display: inline-flex;

    align-items: center;

    /* Slightly off-white rather than pure white:
       full-strength white on a near-black bar
       glares a little at this size. */
    color: rgba(255, 255, 255, 0.88);

    text-decoration: none;

    font-size: 16px;

    letter-spacing: 0.01em;

    padding: 12px 20px;

    border-radius: 999px;

    transition:
        background-color 0.2s ease,
        color 0.2s ease;
}


.nav-links a:hover {

    /* Lightens what it sits on rather than
       darkening it, now the bar is dark. */
    background: rgba(255, 255, 255, 0.12);

    color: white;
}


/*
 * Marks whichever nav link matches the
 * current page — set by hand on each
 * page's own copy of the navbar (this
 * site has no templating/JS to derive it
 * automatically), the same way each page
 * already hand-edits its own nav-links.
 */

.nav-links a.is-active {

    font-weight: bold;

    /* Inverted against the dark bar — a black
       pill on a near-black bar would read as
       nothing at all. */
    background: white;

    color: #11141a;
}


/*
 * Hidden on desktop — .nav-links shows
 * inline there same as always. Only
 * switched on inside the mobile media
 * query below, where .nav-links itself
 * flips to a hidden dropdown that this
 * button (via js/nav.js) toggles open.
 */

.nav-toggle {

    display: none;

    flex-direction: column;

    justify-content: center;

    gap: 5px;

    width: 30px;

    height: 24px;

    padding: 0;

    background: none;

    border: none;

    cursor: pointer;
}


.nav-toggle-bar {

    width: 100%;

    height: 2px;

    /* White to show on the dark bar. */
    background: white;

    border-radius: 1px;

    transition: transform 0.2s ease, opacity 0.2s ease;
}


.nav-toggle:hover .nav-toggle-bar {

    opacity: 0.6;
}


/*
 * Same open/close morph as the FAQ +/×
 * icon elsewhere on the site — middle bar
 * fades out, top/bottom bars rotate into
 * an X, toggled by js/nav.js adding
 * .is-active to the button.
 */

.nav-toggle.is-active .nav-toggle-bar:nth-child(1) {

    transform: translateY(7px) rotate(45deg);
}


.nav-toggle.is-active .nav-toggle-bar:nth-child(2) {

    opacity: 0;
}


.nav-toggle.is-active .nav-toggle-bar:nth-child(3) {

    transform: translateY(-7px) rotate(-45deg);
}


/* =========================
   Main
   ========================= */

main {

    /* Just clears the fixed navbar. Any extra
       inset a page wants is that page's own
       business — .hero sets its own below. */
    padding-top: var(--navbar-height);
}


/* =========================
   Hero
   ========================= */

/* =========================
   Content Band
   ========================= */

/*
 * Every section on the home page shares one
 * column, aligned to the clear rectangle in the
 * background crane (the --void-* tokens at the
 * top of this file).
 *
 * Left padding puts the content's left edge on
 * --void-left; right padding is "everything
 * past --void-right" (100% − 69%). What's left
 * between the two IS the band, so there are no
 * width values to keep in sync — move a --void-
 * token and the whole page follows.
 *
 * Percentages resolve against each section's
 * own width, which is the document width — the
 * same reference the crane uses (see the
 * width:100% note on .page-crane-bg), so the
 * band lands on the crane's gap at any screen
 * size.
 */

.hero,
.trust-bar,
.services,
.process,
.recent-work,
.faq {

    padding-left: var(--void-left);

    padding-right: calc(100% - var(--void-right));
}


/*
 * .reviews and .cta are deliberately NOT in
 * that list — they share a wider column of
 * their own, running edge-to-edge on the left
 * and stopping at the band's right edge (the
 * crane's mast).
 *
 * .reviews needs it: the carousel picks how
 * many cards to show from the space between its
 * arrows (see updateVisibleCards in js/main.js),
 * and the band (~620px) only ever resolves to
 * 1 card.
 *
 * .cta follows it so the two sit on the same
 * centre line — both are centre-aligned text,
 * so matching their boxes matches what the eye
 * actually reads as the centre.
 */

.reviews,
.cta {

    padding-left: 0;

    padding-right: calc(100% - var(--void-right));
}


/*
 * These wrappers used to cap themselves
 * (1100px / 800px / 700px / 70%) and centre
 * inside the full page width. The band is the
 * cap now, so they simply fill it — leaving the
 * old caps in would draw a second, narrower
 * column floating inside the first.
 */

.trust-bar-inner,
.services-inner,
.process-inner,
.recent-work-inner,
.faq-inner,
.cta-inner {

    width: 100%;

    max-width: none;

    margin-left: 0;

    margin-right: 0;
}


/*
 * Vertical: main has already cleared the navbar,
 * so this is simply how far below it the crane's
 * void begins — putting the hero's first line on
 * --void-top-on-screen.
 */

.hero {

    padding-top: var(--void-top-on-screen);

    padding-bottom: 40px;

    text-align: center;
}


/*
 * Caps the hero's content width and
 * centers it — on a wide desktop screen
 * this is what leaves whitespace down
 * both sides; on mobile the section's
 * own (much smaller) side padding is
 * what's left, so content runs close to
 * full screen width instead.
 */

.hero-inner {

    max-width: 1100px;

    margin: 0 auto;
}


/*
 * Sits inside .hero-textbox directly above the
 * "Built On Trust" h2, rather than as its own
 * centred banner across the top of the hero —
 * the company name and its tagline read as one
 * unit that way, and the h1/h2 pairing gives
 * the column a proper heading hierarchy.
 */

.hero-textbox h1 {

    margin: 0 0 18px;

    font-size: 44px;

    line-height: 1.05;

    text-transform: uppercase;
}


/* =========================
   Hero Body
   ========================= */

/*
 * Image + text box side by side. Since
 * this row sits inside the centered,
 * width-capped .hero-inner rather than
 * spanning the full section, the image
 * ends up left of the page's true center
 * rather than dead-centered on its own.
 */

.hero-body {

    display: flex;

    align-items: center;

    gap: 50px;

    text-align: left;
}


.hero-image {

    /*
     * Pinned to company.jpg's real pixel
     * dimensions (289x408) rather than
     * left to derive from the browser's
     * own natural-size detection, plus
     * object-fit:contain as a safety net
     * — between them the box is always
     * exactly the image's own shape, so
     * there's nothing for it to crop.
     */
    flex: 1 1 45%;

    max-width: 460px;

    width: 100%;

    aspect-ratio: 289 / 408;

    object-fit: contain;

    border-radius: 8px;

    box-shadow:
        var(--card-shadow);
}


.hero-textbox {

    flex: 1 1 45%;
}


.hero-textbox h2 {

    margin: 0 0 16px;

    font-size: 26px;
}


.hero-textbox p {

    margin: 0 0 16px;

    font-size: 16px;

    line-height: 1.6;

    color: var(--color-text-muted);
}


/*
 * :last-of-type rather than :last-child
 * — the button after these paragraphs
 * (see .hero-cta below) is the true last
 * child now, so :last-child would never
 * match a <p> here at all.
 */

.hero-textbox p:last-of-type {

    margin-bottom: 0;
}


.hero-cta {

    margin-top: 16px;
}


/* =========================
   Services Section
   ========================= */

.services {

    padding-top: 35px;

    padding-bottom: 45px;

    text-align: center;
}


/*
 * Caps the section's content to roughly
 * 70% of the page width on desktop,
 * rather than spanning edge to edge like
 * the reviews carousel does — mobile
 * drops this back to full width (see the
 * mobile override below).
 */

.services-inner {

    margin: 0 auto;
}


.services-inner h2 {

    margin: 0 0 50px;

    font-size: 40px;

    text-transform: uppercase;
}


/*
 * MAX_PER_ROW is 3 for now — same idea as
 * the review carousel capping how many
 * cards show at once, just fixed instead
 * of scaling further. The minmax() below
 * is what enforces that cap: at the
 * container's full width, one column
 * works out to exactly a third of it
 * (the calc() side), so auto-fit never
 * places more than 3 — but that's
 * wrapped in max() against a 240px
 * floor, so on a narrower container
 * where a third would dip below 240px,
 * auto-fit drops to 2 columns, then 1,
 * instead of squeezing cards down
 * indefinitely.
 */

.services-grid {

    display: grid;

    grid-template-columns: repeat(auto-fit, minmax(max(240px, calc((100% - 60px) / 3)), 1fr));

    gap: 30px;

    text-align: left;
}


/* =========================
   Service Card
   ========================= */

/*
 * Same card look as .review-card (white
 * background, shadow, radius), so services
 * and reviews read as the same design
 * language.
 */

.service-card {

    display: flex;

    flex-direction: column;

    overflow: hidden;

    background: white;

    border-radius: 8px;

    box-shadow:
        var(--card-shadow);

    transition:
        transform 0.25s ease,
        box-shadow 0.25s ease;
}


/*
 * Shared hover lift for the site's clickable
 * cards — the card rises slightly and its
 * shadow deepens, so the whole tile reads as
 * one pressable thing. .service-card and
 * .blog-card also zoom their own image on
 * hover (see .service-image / .blog-card img);
 * these are separate transforms on separate
 * elements, so they compose rather than fight.
 */

.service-card:hover,
.blog-card:hover,
.value-card:hover,
.recent-work-item:hover {

    transform: translateY(-4px);

    box-shadow: var(--card-shadow-hover);
}


.service-image {

    width: 100%;

    aspect-ratio: 4 / 3;

    object-fit: cover;

    transition: transform 0.3s ease;
}


.service-card:hover .service-image {

    transform: scale(1.05);
}


.service-title {

    margin: 20px 20px 8px;

    font-size: 20px;
}


.service-description {

    margin: 0 20px 8px;

    font-size: 15px;

    line-height: 1.5;

    color: var(--color-text-muted);
}


/* =========================
   Service Link
   ========================= */

/*
 * margin-top:auto pushes this to the
 * bottom of the card regardless of how
 * long that card's description runs, so
 * every "Learn More" lines up at the same
 * height across a row — same trick the
 * review cards used for their author line.
 */

/*
 * margin-top: auto is what pins this row
 * to the bottom of the card regardless of
 * how long the description above runs —
 * .service-card is a column flexbox, so
 * this is the same trick .service-link
 * used to do by itself before "View
 * Portfolio" was added alongside it.
 */

.service-links {

    display: flex;

    flex-wrap: wrap;

    gap: 8px 16px;

    margin: auto 20px 20px;
}


.service-link {

    font-weight: bold;

    color: black;

    text-decoration: none;
}


.service-link:hover {

    text-decoration: underline;
}


/* =========================
   Why Choose Us (index.html)
   ========================= */

.trust-bar {

    padding-top: 30px;

    padding-bottom: 30px;

    text-align: center;
}


.trust-bar-inner {

    margin: 0 auto;

    display: flex;

    flex-wrap: wrap;

    justify-content: center;

    gap: 40px;
}


.trust-stat {

    min-width: 160px;
}


.trust-stat-number {

    margin: 0 0 4px;

    /* Big standalone figures are display type,
       not body type — same tier as the section
       headings. */
    font-family: var(--font-display);

    font-size: 40px;

    font-weight: 600;

    line-height: 1.1;
}


.trust-stat-label {

    margin: 0;

    font-size: 15px;

    color: #666666;
}


/* =========================
   How It Works (index.html)
   ========================= */

.process {

    padding-top: 20px;

    padding-bottom: 45px;

    text-align: center;
}


.process-inner {

    margin: 0 auto;
}


.process-inner h2 {

    margin: 0 0 12px;
}


.process-tagline {

    margin: 0 0 36px;

    font-size: 18px;

    color: var(--color-text-muted);
}


/*
 * 4 independent cards, not one fused box —
 * a small border-radius on all four corners
 * of each, like every other card style on
 * the site. The zig-zag is a light touch:
 * every card is the same fixed width,
 * centered as a group (align-items: center
 * on the column), and odd/even just nudge
 * off that shared center a little in
 * opposite directions via margin — nowhere
 * near their own width, so the section still
 * reads as one process rather than four
 * unrelated boxes.
 *
 * The icon/photo sits at the far edge of its
 * own card (justify-content: space-between)
 * purely from DOM order, no extra
 * positioning needed: odd steps put the
 * media last (content reads first, media
 * lands at the right edge); even steps put
 * the media first (media lands at the left
 * edge, content at the right).
 */

.process-icon-steps {

    display: flex;

    flex-direction: column;

    align-items: center;

    gap: 20px;

    margin: 0 auto;
}


.process-icon-step {

    position: relative;

    display: flex;

    align-items: center;

    justify-content: space-between;

    gap: 24px;

    /*
     * Sized as a percentage of the band rather
     * than a fixed max-width, so the zigzag's
     * travel stays proportional at any screen
     * size instead of shrinking to nothing on
     * narrow ones — the 25% left over is what
     * the alternating align-self below shifts
     * into.
     */
    width: 75%;

    padding: 24px 28px;

    background: white;

    border-radius: 10px;

    box-shadow:
        var(--card-shadow);
}


/*
 * The zigzag, kept inside the band rather than
 * spilling out of it: each step is 88% of the
 * band (above), and these alternate which edge
 * it sits against, so the leftover 12% becomes
 * the stagger. align-self is what does it —
 * .process-icon-steps centres its children, and
 * this overrides that per step. Margins wouldn't
 * work here, since the centring would still win.
 */

.process-icon-step:nth-child(odd) {

    align-self: flex-start;

    /* Held a touch off the band's outer edge
       rather than flush against it, so the
       stagger reads as deliberate rather than
       as the box running out of room. A
       percentage (of the band) keeps that inset
       proportional as the band resizes. */
    margin-left: 4%;
}


.process-icon-step:nth-child(even) {

    align-self: flex-end;

    margin-right: 4%;
}


.process-icon-step-text-left {

    text-align: left;
}


.process-icon-step-text-right {

    text-align: left;
}


/*
 * Shared size/shape for both variants
 * below, so an icon step and a photo step
 * read as the same kind of thing sitting at
 * the end of its bar, not two different
 * components.
 */

.process-step-media {

    flex-shrink: 0;

    width: 76px;

    height: 76px;

    border-radius: 12px;
}


.process-step-icon {

    display: flex;

    align-items: center;

    justify-content: center;

    background: black;

    color: white;
}


.process-step-icon svg {

    width: 38px;

    height: 38px;
}


/*
 * Each step's icon gets its own small
 * hover-triggered gesture, keyed off the
 * step card (not the icon itself) so
 * hovering anywhere on the card plays it —
 * a wave for "get in touch", a gentle
 * ring-wobble for "phone in a quote", and a
 * settling nod for the sign-off handshake.
 * "We get building" is handled separately
 * below: its house is drawn as 4 stacked
 * parts (foundation/walls/roof/door) that
 * reveal in sequence via staggered
 * animation-delay, rather than the icon
 * animating as a single whole.
 */

.process-icon-step:hover .process-step-icon--wave svg {

    animation: process-icon-wave 0.9s ease-in-out;

    transform-origin: 70% 100%;
}


.process-icon-step:hover .process-step-icon--ring svg {

    animation: process-icon-ring 0.6s ease-in-out;

    transform-origin: 50% 90%;
}


.process-step-emoji {

    display: inline-block;

    font-size: 34px;

    line-height: 1;
}


.process-icon-step:hover .process-step-icon--shake .process-step-emoji {

    animation: process-icon-shake 0.6s ease-in-out;

    transform-origin: 50% 50%;
}


@keyframes process-icon-wave {

    0%, 100% { transform: rotate(0deg); }

    20% { transform: rotate(-16deg); }

    40% { transform: rotate(14deg); }

    60% { transform: rotate(-10deg); }

    80% { transform: rotate(6deg); }
}


@keyframes process-icon-ring {

    0%, 100% { transform: rotate(0deg); }

    25% { transform: rotate(-7deg); }

    50% { transform: rotate(7deg); }

    75% { transform: rotate(-4deg); }
}


@keyframes process-icon-shake {

    0%, 100% { transform: translateY(0); }

    20% { transform: translateY(-4px); }

    40% { transform: translateY(0); }

    60% { transform: translateY(-4px); }

    80% { transform: translateY(0); }
}


/*
 * Each part of the house starts hidden a
 * touch below its resting spot; the base
 * rule keeps it fully visible at rest, and
 * the hover rule below restarts each part
 * from that hidden state with an increasing
 * delay per part, so the house appears to
 * build itself bottom-up: foundation, then
 * walls, then roof, then door.
 */

.build-part {

    opacity: 1;

    transform: translateY(0);
}


.process-icon-step:hover .process-step-icon--build .build-part {

    animation: process-icon-build-part 0.35s ease-out backwards;
}


.process-icon-step:hover .process-step-icon--build .build-part-1 {

    animation-delay: 0s;
}


.process-icon-step:hover .process-step-icon--build .build-part-2 {

    animation-delay: 0.15s;
}


.process-icon-step:hover .process-step-icon--build .build-part-3 {

    animation-delay: 0.3s;
}


.process-icon-step:hover .process-step-icon--build .build-part-4 {

    animation-delay: 0.45s;
}


@keyframes process-icon-build-part {

    0% { opacity: 0; transform: translateY(4px); }

    100% { opacity: 1; transform: translateY(0); }
}


.process-step-number {

    margin: 0 0 4px;

    font-size: 13px;

    font-weight: bold;

    letter-spacing: 0.05em;

    text-transform: uppercase;

    color: #888888;
}


.process-step-title {

    margin: 0 0 8px;

    font-size: 19px;
}


.process-step-description {

    margin: 0;

    font-size: 15px;

    line-height: 1.5;

    color: var(--color-text-muted);
}


/* =========================
   Recent Work (index.html)
   ========================= */

.recent-work {

    padding-top: 45px;

    padding-bottom: 45px;

    text-align: center;
}


.recent-work-inner {

    margin: 0 auto;
}


.recent-work-inner h2 {

    margin: 0 0 12px;
}


.recent-work-tagline {

    margin: 0 0 50px;

    font-size: 18px;

    color: var(--color-text-muted);
}


.recent-work-grid {

    display: grid;

    grid-template-columns: repeat(auto-fit, minmax(max(200px, calc((100% - 60px) / 4)), 1fr));

    gap: 20px;

    margin-bottom: 40px;
}


.recent-work-item {

    position: relative;

    display: block;

    aspect-ratio: 4 / 3;

    border-radius: 8px;

    overflow: hidden;

    box-shadow: var(--card-shadow);

    transition:
        transform 0.25s ease,
        box-shadow 0.25s ease;
}


.recent-work-item img {

    display: block;

    width: 100%;

    height: 100%;

    object-fit: cover;

    transition: transform 0.3s ease;
}


.recent-work-item:hover img {

    transform: scale(1.05);
}


.recent-work-caption {

    position: absolute;

    left: 0;
    right: 0;
    bottom: 0;

    padding: 10px 14px;

    background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));

    color: white;

    font-size: 14px;

    font-weight: bold;
}


/* =========================
   Services Page (services.html)
   ========================= */

.services-page {

    padding: 40px 50px 100px;

    text-align: center;
}


.services-page h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.services-page-tagline {

    margin: 0 0 60px;

    font-size: 18px;

    color: var(--color-text-muted);
}


.services-page-inner {

    max-width: 1100px;

    margin: 0 auto;
}


/*
 * One row per service, image on one side
 * and text on the other — same image +
 * text flex pattern as .hero-body, just
 * repeated and alternating sides via
 * .is-reversed on every other row rather
 * than a structural (nth-child) selector,
 * matching the site's existing is-* state
 * classes elsewhere.
 */

.service-row {

    display: flex;

    align-items: center;

    gap: 50px;

    text-align: left;

    /*
     * index.html's "Learn More" links land
     * on #kitchens etc. — without this the
     * fixed .navbar covers the top of
     * whichever row the browser scrolls to.
     *
     * Derived from --navbar-height rather
     * than repeated as a number, so it can't
     * fall behind when the bar's height
     * changes (it already had once), and so
     * the mobile bar needs no override of
     * its own. Only a fallback now: the
     * script at the bottom of services.html
     * centres the row instead, and this is
     * what a visitor without JS gets.
     */
    scroll-margin-top:
        calc(var(--navbar-height) + 20px);
}


.service-row:not(:last-child) {

    margin-bottom: 80px;
}


.service-row.is-reversed {

    flex-direction: row-reverse;
}


.service-row-image {

    flex: 1 1 45%;

    min-width: 0;
}


.service-row-image img {

    display: block;

    width: 100%;

    aspect-ratio: 4 / 3;

    object-fit: cover;

    border-radius: 8px;

    /* Same shadow as .service-card /
       .review-card / .portfolio-card so it
       reads as part of the same design
       system. */
    box-shadow: var(--card-shadow);
}


.service-row-content {

    flex: 1 1 45%;
}


.service-row-content h2 {

    margin: 0 0 16px;

    font-size: 26px;
}


.service-row-content p {

    margin: 0 0 16px;

    font-size: 16px;

    line-height: 1.6;

    color: var(--color-text-muted);
}


/* =========================
   FAQ Section
   ========================= */

.faq {

    padding-top: 20px;

    padding-bottom: 45px;

    text-align: center;
}


/*
 * Narrower than the hero/services
 * containers on purpose — FAQ answers
 * are body text, and a line that wide
 * gets hard to read.
 */

.faq-inner {

    margin: 0 auto;

    text-align: left;
}


.faq-inner h2 {

    margin: 0 0 50px;

    font-size: 40px;

    text-align: center;

    text-transform: uppercase;
}


.faq-list {

    display: flex;

    flex-direction: column;

    gap: 16px;
}


/* =========================
   FAQ Item
   ========================= */

.faq-item {

    padding: 20px 24px;

    background: white;

    border: 1px solid #e0e0e0;

    border-radius: 8px;

    /* The whole box toggles on click (see
       the FAQ Toggle script), not just the
       question row. */
    cursor: pointer;
}


/*
 * Staircase: every row is the SAME width, and
 * each one below sits further left than the one
 * above — the first starts on the band, the
 * last reaches the left edge of the screen.
 *
 * Because the width is pinned, a negative
 * margin-left slides the whole bar leftward
 * (rather than stretching it, which is what an
 * unpinned block would do), so the rows stay
 * identical and only their position changes.
 *
 * The step is in vw because the target is set
 * against the viewport, not anything inside the
 * layout. It's sized so the LAST row ends up
 * centred over the reviews section directly
 * below it:
 *
 *   reviews span 0 -> --void-right (69%), so
 *   their centre is at 34.5% of the screen;
 *   a row is --void-right - --void-left (53%)
 *   wide, so centring it there puts its left
 *   edge at 34.5% - 26.5% = 8%;
 *   the band starts at --void-left (16%), so
 *   the row has to travel 8% left over four
 *   steps — hence 2vw each.
 */

.faq-list {

    --faq-step: 2vw;
}


.faq-item {

    /* Pinned so the negative margins below move
       the bar instead of widening it. */
    width: 100%;
}


.faq-item:nth-child(1) {

    margin-left: 0;
}


.faq-item:nth-child(2) {

    margin-left: calc(var(--faq-step) * -1);
}


.faq-item:nth-child(3) {

    margin-left: calc(var(--faq-step) * -2);
}


.faq-item:nth-child(4) {

    margin-left: calc(var(--faq-step) * -3);
}


.faq-item:nth-child(5) {

    margin-left: calc(var(--faq-step) * -4);
}


/*
 * <details>/<summary> gives the expand/
 * collapse behavior for free, with no JS
 * — this just replaces the browser's
 * default marker with a "+" that rotates
 * into a "×" when open (see .faq-item
 * [open] below).
 */

.faq-question {

    display: flex;

    align-items: center;

    justify-content: space-between;

    gap: 20px;

    list-style: none;

    font-weight: bold;

    font-size: 18px;

    cursor: pointer;
}


.faq-question::-webkit-details-marker {

    display: none;
}


.faq-question::after {

    content: "+";

    flex-shrink: 0;

    font-size: 22px;

    font-weight: normal;

    transition: transform 0.2s ease;
}


.faq-item[open] .faq-question::after {

    transform: rotate(45deg);
}


.faq-answer {

    margin: 16px 0 0;

    font-size: 15px;

    line-height: 1.6;

    color: var(--color-text-muted);
}


/* =========================
   Final CTA
   ========================= */

.cta {

    padding-top: 45px;

    padding-bottom: 45px;

    text-align: center;
}


.cta-inner {

    max-width: 700px;

    margin: 0 auto;
}


.cta-inner h2 {

    margin: 0 0 16px;

    font-size: 36px;

    text-transform: uppercase;
}


.cta-text {

    margin: 0 0 30px;

    font-size: 18px;

    color: var(--color-text-muted);
}


/* =========================
   Reviews Section
   ========================= */

.reviews {

    padding-top: 20px;

    padding-bottom: 45px;

    text-align: center;
}


/* =========================
   Reviews Header
   ========================= */

.reviews-header h2 {

    margin: 0 0 20px;

    font-size: 40px;

    text-transform: uppercase;
}


/*
 * Only index.html's embedded reviews
 * section has this — reviews.html's own
 * .reviews-header has no <h2> above it
 * (the page's own <h1> already covers
 * that), so there's nothing here for it
 * to conflict with.
 */

/*
 * Compound selector (rather than just
 * .reviews-header-tagline alone) so this
 * reliably beats .reviews-header p below —
 * that rule also matches this element
 * (it's a <p> inside .reviews-header too)
 * and has equal-or-higher specificity
 * otherwise.
 */

.reviews-header .reviews-header-tagline {

    max-width: 600px;

    margin: 0 auto 30px;

    font-size: 18px;

    color: var(--color-text-muted);
}


.rating {

    display: flex;

    justify-content: center;

    align-items: center;

    gap: 12px;

    margin-bottom: 10px;
}


.stars {

    font-size: 40px;

    letter-spacing: 4px;
}


/* =========================
   Star Colors
   ========================= */

/*
 * Shared by the header's overall-rating
 * stars and each review card's own stars
 * (both built via renderStars in main.js)
 * — Google's own review widgets use this
 * same yellow-for-filled, grey-for-empty
 * styling.
 */

.star-filled {
    color: #fbbc04;
}

.star-empty {
    color: #dadce0;
}


.rating-score {

    font-family: var(--font-display);

    font-size: 34px;

    font-weight: 600;
}


.reviews-header p {

    margin: 0 0 50px;

    font-size: 19px;
}


/* =========================
   Reviews Carousel
   ========================= */

/*
 * Set this to control how far the arrows
 * sit from the screen edge. Everything
 * else (arrow position, mobile override)
 * reads from this one value.
 */
:root {
    --arrow-edge-gap: 30px;

    /*
     * A reference size, not a fixed one. JS
     * (updateVisibleCards in main.js) picks an
     * odd number of cards (up to MAX_CARDS)
     * based on how many cards this size roughly
     * fit in the real space between the two
     * arrows, then overwrites this variable so
     * the cards scale up or down to exactly
     * fill that space — never a partial card
     * poking out from under an arrow, and never
     * leftover empty space either.
     *
     * 250 rather than the original 400: the
     * reviews section now stops at the crane's
     * mast rather than running the full page
     * width, and at 400 that narrower space
     * rounded down to a single huge card. This
     * is what makes it land on 3.
     */
    --card-size: 250px;

    /* Gap between cards, shared by the
       flex gap and the JS fit-count math,
       so they can never drift out of
       sync with each other. */
    --card-gap: 25px;

    /* Same idea as --card-size/--card-gap
       above, but for the portfolio photo
       carousel — see updateVisibleCards()
       in portfolio.html. */
    --portfolio-card-size: 260px;

    --portfolio-card-gap: 20px;
}

.reviews-carousel {

    position: relative;

    /* Fills the content band rather than taking
       80% of it — the band is already the
       narrowing, and stacking a second 80% on
       top of it left the cards tiny. */
    width: 100%;

    margin: 0 auto 40px;
}


/* =========================
   Reviews Window
   ========================= */

.reviews-window {

    /*
     * Width is set inline by JS
     * (updateVisibleCards) to exactly fit
     * however many cards it decided on,
     * which on wide screens can exceed
     * 1200px once cards scale up past
     * 400px — no max-width here, since
     * that would silently clip the
     * computed width and desync the
     * visible window from the track's
     * real size. JS sets that width
     * assuming it's the CONTENT width (it
     * has no reason to think otherwise —
     * this rule is the only place that
     * cares), so this opts back out of the
     * site-wide border-box reset: with
     * border-box, the padding below would
     * eat into that content width instead
     * of adding onto it, and since the
     * cards can't shrink (flex: 0 0
     * var(--card-size)) the resulting
     * overflow got clipped by
     * overflow:hidden — invisibly on the
     * left where the track starts flush,
     * but visibly cutting into the last
     * card on the right where the excess
     * actually spills out.
     */
    box-sizing: content-box;

    margin: 0 auto;

    /*
     * overflow:hidden here exists to hide
     * the track's other LOOP_COPIES sitting
     * outside this window — but with zero
     * padding, its box exactly matched the
     * visible cards' own footprint, so
     * .review-card's box-shadow (which
     * paints outside the card's own box)
     * was clipped flush at every edge
     * instead of being visible the way it
     * is between cards, where the gap gives
     * it room. This padding gives the same
     * room on all four sides. Horizontally
     * it has to stay under --card-gap
     * (15px on mobile, the smallest value
     * it's set to) — that gap is genuine
     * empty track space just past each
     * visible card, so revealing a bit of
     * it is safe, but going past it would
     * start revealing a sliver of the next
     * (should-be-hidden) card.
     */
    padding: 14px 9px;

    overflow: hidden;
}


/* =========================
   Moving Track
   ========================= */

.reviews-track {

    display: flex;

    gap: var(--card-gap);

    transition:
        transform 0.7s ease;
}


/* =========================
   Review Cards
   ========================= */

.review-card {

    /* Always exactly --card-size square —
       see the :root comment above for how
       that size and the visible count are
       both derived in JS. */
    flex: 0 0 var(--card-size);

    aspect-ratio: 1 / 1;

    /*
     * Content (stars/text/author) doesn't
     * resize with the card, so on a small
     * square it could overflow the box —
     * clip rather than break the square.
     */
    overflow: hidden;

    padding: 30px;

    display: flex;

    flex-direction: column;

    background: white;

    text-align: left;

    border-radius: 8px;

    box-shadow:
        var(--card-shadow);

    /* Signals the card opens the full
       review on click/tap — see the
       click-vs-drag detection in
       js/main.js's drag system. */
    cursor: pointer;
}


/* =========================
   Review Header
   ========================= */

/*
 * Avatar on the left, name + stars
 * stacked in a column on the right — that
 * column is kept close to the avatar's
 * own height (see .review-meta below),
 * with the review text below all of it.
 */

.review-header {

    display: flex;

    align-items: center;

    gap: calc(var(--card-size) * 0.03);

    margin-bottom: 16px;
}


/* =========================
   Review Meta
   ========================= */

.review-meta {

    display: flex;

    flex-direction: column;

    /*
     * Tight, fixed gap rather than one
     * that scales with the card — name +
     * stars should read as one block that
     * lands at about the avatar's own
     * height, not stretch apart on a
     * large card.
     */
    gap: 1px;

    /* Nudged down slightly from dead-
       center against the avatar. */
    margin-top: 6px;
}


/* =========================
   Review Avatar
   ========================= */

.review-avatar {

    /*
     * Scales with the card — 48px at the
     * 400px reference size — same floor/
     * ceiling reasoning as .review-stars
     * below.
     */
    width: clamp(36px, calc(var(--card-size) * 0.12), 72px);

    aspect-ratio: 1 / 1;

    border-radius: 50%;

    flex-shrink: 0;

    object-fit: cover;
}


/* =========================
   Review Stars
   ========================= */

.review-stars {

    /*
     * Scales with the card (20px at the
     * 400px reference size — see --card-
     * size in :root) but never shrinks
     * past legible or grows past a size
     * that looks right on a large card.
     * The floor is close to the reference
     * size on purpose: a single-card mobile
     * layout reserves a lot of its narrow
     * width on the two arrows, so the card
     * itself often ends up smaller than
     * 400px even though it's the only card
     * on screen — without a floor this
     * close, that "one big card" view ends
     * up with smaller text than a wide
     * desktop view showing several cards.
     */
    font-size: clamp(18px, calc(var(--card-size) * 0.05), 26px);

    letter-spacing: 2px;
}


/* =========================
   Review Text
   ========================= */

.review-text {

    /* Scales with the card — 17px at the
       400px reference size, floor kept
       close to that so a small-card view
       (see .review-stars above) never
       reads smaller than a large-card one. */
    font-size: clamp(15px, calc(var(--card-size) * 0.0425), 20px);

    line-height: 1.6;

    margin: 0 0 20px;

    /* Clips a long review to 5 lines with
       a trailing "…" instead of letting it
       run past the card's edge (where
       .review-card's own overflow:hidden
       would otherwise just cut it off
       mid-line with no indication there's
       more) — click/tap the card to read
       the rest in #reviewLightbox. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 5;
    line-clamp: 5;
    overflow: hidden;

    /*
     * The card is a fixed square and a flex
     * column, so on a review that also carries
     * photos or an owner-response badge the
     * default flex-shrink squeezed THIS box to
     * a fractional line height (5.6 lines) —
     * slicing the last line horizontally
     * through the letters, which reads as a
     * rendering fault rather than a truncation.
     * Holding its height means the clamp's "…"
     * is what ends the text, always on a whole
     * line.
     */
    flex-shrink: 0;
}


/* =========================
   Review Photos
   ========================= */

/*
 * Small previews for the few demo reviews
 * that have photos attached (see
 * googleData in js/main.js) — left out of
 * the card entirely for every review that
 * doesn't. Sits below the (already
 * clamped) review text, so it never
 * pushes the card past its own fixed
 * square size; .review-card's overflow:
 * hidden clips it the same way it already
 * tolerates any other content overflow.
 */

.review-photos {

    display: flex;

    flex-wrap: wrap;

    gap: 8px;

    margin-top: auto;

    padding-top: 12px;
}


.review-photo-thumb {

    /* Scales with the card — same
       reasoning as .review-avatar above. */
    width: clamp(40px, calc(var(--card-size) * 0.15), 80px);

    aspect-ratio: 1 / 1;

    object-fit: cover;

    border-radius: 6px;

    cursor: pointer;

    transition: opacity 0.2s ease;
}


.review-photo-thumb:hover {

    opacity: 0.8;
}


/*
 * Just a one-line signal on the compact
 * card that the business replied — see
 * createReviewCard()'s ownerResponseBadgeHTML
 * comment for why the full reply text only
 * ever renders in the review lightbox, not
 * here. margin-top: auto pins it to the
 * bottom of the card the same way
 * .review-photos does.
 */

.review-owner-badge {

    margin-top: auto;

    padding-top: 12px;

    font-size: clamp(12px, calc(var(--card-size) * 0.032), 14px);

    font-style: italic;

    color: #666666;
}


/* =========================
   Review Author
   ========================= */

.review-author {

    margin: 0;

    font-weight: bold;

    /* Scales with the card — 16px at the
       400px reference size, floor kept
       close to that for the same reason
       as .review-stars above. */
    font-size: clamp(14px, calc(var(--card-size) * 0.04), 19px);
}


/* =========================
   Arrow Buttons
   ========================= */

.review-arrow {

    position: absolute;

    top: 50%;

    transform: translateY(-50%);

    width: 50px;

    height: 50px;

    flex-shrink: 0;

    border: none;

    border-radius: 50%;

    background: black;

    color: white;

    font-size: 24px;

    cursor: pointer;

    display: flex;

    justify-content: center;

    align-items: center;

    z-index: 10;

    transition:
        opacity 0.2s ease,
        transform 0.2s ease;
}


#previous-review {

    left: var(--arrow-edge-gap);
}


#next-review {

    right: var(--arrow-edge-gap);
}


.review-arrow:hover {

    opacity: 0.7;

    transform: translateY(-50%) scale(1.05);
}


.review-arrow:active {

    transform: translateY(-50%) scale(0.95);
}


/* =========================
   Buttons
   ========================= */

/*
 * Shared by the hero's "Get a Free
 * Quote" CTA and the "View all Google
 * reviews" link, so both read as the
 * same button style.
 */

.btn {

    display: inline-flex;

    align-items: center;

    justify-content: center;

    /* Roomier than the old 14px/25px so it
       reads as a primary action rather than a
       small utility link. */
    padding: 16px 34px;

    background: black;

    color: white;

    text-decoration: none;

    border: none;

    border-radius: 8px;

    font: inherit;

    /*
     * Larger and heavier than body copy, with a
     * touch of tracking — buttons are short
     * bursts of text that need to read as a
     * distinct, tappable label rather than a
     * run-on of the sentence next to them.
     */
    font-size: 17px;

    font-weight: 600;

    letter-spacing: 0.01em;

    /*
     * A real shadow rather than a flat block:
     * it lifts the button off the page's grey
     * so it's findable at a glance, which is
     * the main thing that made the old one easy
     * to miss. Tinted with the button's own
     * black rather than a neutral grey so the
     * glow reads as belonging to it.
     */
    box-shadow:
        0 1px 2px rgba(16, 24, 40, 0.10),
        0 6px 16px rgba(16, 24, 40, 0.22);

    cursor: pointer;

    /*
     * Opacity is deliberately NOT animated here
     * any more — fading a solid black button
     * toward the background is what made it
     * look weaker on hover, exactly when it
     * should feel more responsive.
     */
    transition:
        background-color 0.2s ease,
        box-shadow 0.2s ease,
        transform 0.2s ease;
}


.btn:hover {

    /* Lifts and deepens instead of fading. */
    background: #24272e;

    box-shadow:
        0 2px 4px rgba(16, 24, 40, 0.12),
        0 12px 26px rgba(16, 24, 40, 0.28);

    transform: translateY(-2px);
}


.btn:active {

    /* Presses back down — the shadow collapsing
       is what sells the click. */
    transform: translateY(0);

    box-shadow:
        0 1px 2px rgba(16, 24, 40, 0.12),
        0 3px 8px rgba(16, 24, 40, 0.20);
}


/*
 * Keyboard focus needs its own visible ring —
 * the hover styles above are mouse-only, and
 * without this a tab-through leaves no
 * indication of where you are. :focus-visible
 * rather than :focus so it doesn't fire on
 * mouse clicks.
 */

.btn:focus-visible {

    outline: 3px solid rgba(16, 24, 40, 0.45);

    outline-offset: 3px;
}


/* =========================
   Portfolio Section
   ========================= */

.portfolio {

    padding: 40px 50px 100px;

    text-align: center;
}


.portfolio h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.portfolio-tagline {

    margin: 0 0 30px;

    font-size: 18px;

    color: var(--color-text-muted);
}


/*
 * Only one photo per service exists yet
 * (see images/Services/), so switching
 * tabs swaps the single .portfolio-image
 * rather than paging through a gallery —
 * see the script at the bottom of
 * portfolio.html.
 */

/*
 * Grid, not flex-wrap — six tabs need an
 * exact 6-across row on desktop and an
 * exact 3x2 grid on mobile (see the
 * override in the mobile breakpoint),
 * rather than wrapping wherever there
 * happens to be room.
 */

.portfolio-tabs {

    display: grid;

    grid-template-columns: repeat(6, 1fr);

    gap: 12px;

    margin: 0 auto 30px;

    max-width: 900px;
}


.portfolio-tab {

    /* Grid items don't shrink below their
       content's width by default — without
       this, a long label like "Landscaping"
       forces its column wider than the
       track, pushing the row past the edge
       of the screen instead of resizing
       down to fit. */
    min-width: 0;

    padding: 10px 12px;

    border: 1px solid #cccccc;

    border-radius: 5px;

    background: white;

    font: inherit;

    font-size: 15px;

    font-weight: bold;

    color: black;

    cursor: pointer;

    transition:
        background 0.2s ease,
        color 0.2s ease;
}


.portfolio-tab:hover {

    border-color: black;
}


.portfolio-tab.is-active {

    border-color: black;

    background: black;

    color: white;
}


/*
 * Endless carousel of a service's photos
 * — same infinite-loop technique as the
 * reviews carousel (.reviews-carousel /
 * #reviews-track in js/main.js): the
 * arrows and window here reuse that same
 * --arrow-edge-gap variable, and the
 * track's --portfolio-card-size is
 * rewritten by portfolio.html's own script
 * exactly like --card-size is for
 * reviews.
 */

.portfolio-carousel {

    position: relative;

    /* .portfolio has 50px of left/right
       padding — cancel it out so the
       arrows' edge-gap is measured from
       the true viewport edge, same trick
       as .reviews-carousel. */
    width: calc(100% + 100px);

    margin: 0 -50px;
}


.portfolio-window {

    margin: 0 auto;

    overflow: hidden;
}


.portfolio-track {

    display: flex;

    gap: var(--portfolio-card-gap);

    transition:
        transform 0.7s ease;
}


.portfolio-card {

    /* Always exactly --portfolio-card-size
       — see updateVisibleCards() in
       portfolio.html for how that size and
       the visible count are both derived. */
    flex: 0 0 var(--portfolio-card-size);

    aspect-ratio: 4 / 3;

    border-radius: 8px;

    overflow: hidden;

    /* Same shadow as .review-card /
       .service-card so it reads as part of
       the same design system. */
    box-shadow: var(--card-shadow);
}


.portfolio-card img {

    display: block;

    width: 100%;

    height: 100%;

    object-fit: cover;

    transition: transform 0.3s ease;
}


.portfolio-card:hover img {

    transform: scale(1.05);
}


#portfolioPrev {

    left: var(--arrow-edge-gap);
}


#portfolioNext {

    right: var(--arrow-edge-gap);
}


/*
 * Hides the carousel entirely once
 * expanded (see setExpanded() in
 * portfolio.html) — the carousel and grid
 * are never both on screen at once, which
 * is what makes the carousel unusable
 * while expanded.
 */

.portfolio-carousel.is-hidden {

    display: none;
}


/*
 * The expanded "all photos" view — plain
 * grid, no loop copies, no dragging.
 * display:none by default and toggled via
 * .is-open (a class, not the [hidden]
 * attribute) so an author display rule can
 * never end up fighting the browser's own
 * [hidden] default.
 */

.portfolio-grid {

    display: none;

    /* 4 across on desktop — the mobile
       breakpoint below overrides this to
       2, matching the carousel's own
       getTargetCardCount() in
       portfolio.html. Columns are sized to
       --portfolio-card-size (the same
       variable the carousel just set,
       still holding its last computed
       value since updateVisibleCards()
       skips running while the grid is
       open) rather than a fraction of the
       grid's own width — that's what kept
       these photos the same size as the
       carousel's, instead of shrinking to
       whatever a 1fr column inside an 80%-
       wide, padding-shrunk container came
       out to. */
    grid-template-columns:
        repeat(4, var(--portfolio-card-size));

    justify-content: center;

    gap: var(--portfolio-card-gap);

    /* Full-bleed, same trick as
       .portfolio-carousel (cancels
       .portfolio's own 50px padding) —
       needs to be at least as wide as 4
       card-sized columns, and the carousel
       already reserves less than this for
       its arrows. */
    width: calc(100% + 100px);

    margin: 30px -50px 0;
}


.portfolio-grid.is-open {

    display: grid;
}


.portfolio-grid-item {

    /* Grid items don't shrink below their
       content's width by default — an img's
       intrinsic size is its natural pixel
       width, which is usually far wider
       than a grid column, so without this
       it would force the row wider than
       the page instead of resizing down. */
    min-width: 0;

    aspect-ratio: 4 / 3;

    border-radius: 8px;

    overflow: hidden;

    /* Same shadow as .review-card /
       .service-card so it reads as part of
       the same design system. */
    box-shadow: var(--card-shadow);
}


.portfolio-grid-item img {

    display: block;

    width: 100%;

    height: 100%;

    object-fit: cover;

    transition: transform 0.3s ease;
}


.portfolio-grid-item:hover img {

    transform: scale(1.05);
}


/*
 * Expand/collapse control, sitting below
 * the carousel (and the grid, once open) —
 * a labelled pill rather than a bare icon,
 * so it's clear what clicking it does
 * ("Show All" / "Hide") rather than
 * relying on the chevron direction alone.
 */

.portfolio-toggle {

    display: flex;

    align-items: center;

    gap: 8px;

    margin: 30px auto 0;

    padding: 10px 20px;

    border: 1px solid #cccccc;

    border-radius: 20px;

    background: white;

    font: inherit;

    font-size: 14px;

    font-weight: bold;

    color: black;

    cursor: pointer;

    transition:
        opacity 0.2s ease,
        border-color 0.2s ease;
}


.portfolio-toggle:hover {

    opacity: 0.7;

    border-color: black;
}


.portfolio-toggle-label {

    white-space: nowrap;
}


.portfolio-toggle-icon {

    width: 16px;

    height: 16px;

    flex-shrink: 0;

    color: black;

    transition: transform 0.3s ease;
}


/*
 * "Get a Free Quote" CTA below the toggle
 * — same 30px rhythm as the toggle's own
 * margin-top above, and centered the same
 * way (margin auto, .portfolio being
 * text-align: center).
 */

.portfolio-toggle + .btn {

    display: block;

    width: fit-content;

    margin: 20px auto 0;
}


/*
 * Flips the chevron to point up once
 * expanded, rather than swapping to a
 * second icon.
 */

.portfolio-toggle.is-expanded .portfolio-toggle-icon {

    transform: rotate(180deg);
}


/* =========================
   Contact Section
   ========================= */

/*
 * contact.html covers both "contact us"
 * and "get a quote" — the hero's CTA and
 * the navbar's Contact link both lead
 * here, rather than to two separate pages.
 */

.contact {

    padding: 40px 50px 100px;

    text-align: center;
}


.contact h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.contact-tagline {

    margin: 0 0 24px;

    font-size: 18px;

    color: var(--color-text-muted);
}


/* =========================
   Contact Form
   ========================= */

.contact-form {

    display: flex;

    flex-direction: column;

    gap: 20px;

    max-width: 600px;

    margin: 0 auto;

    text-align: left;
}


/*
 * Pairs two form-groups side by side
 * (Name + Address, then Phone + Email) —
 * each takes an even half via flex:1 on
 * the .form-group children below.
 */

.form-row {

    display: flex;

    gap: 20px;
}


.form-row .form-group {

    flex: 1;
}


.form-group {

    display: flex;

    flex-direction: column;

    gap: 6px;
}


.form-group label {

    font-weight: bold;

    font-size: 14px;
}


/*
 * Every text-like input shares the same
 * look; type="file" is deliberately left
 * out here since browsers won't let its
 * native appearance be styled this way.
 */

.form-group input[type="text"],
.form-group input[type="tel"],
.form-group input[type="email"],
.form-group textarea,
.form-group select {

    padding: 12px 14px;

    border: 1px solid #cccccc;

    border-radius: 5px;

    background: white;

    font-family: inherit;

    font-size: 15px;

    color: black;
}


.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {

    outline: 2px solid black;

    outline-offset: 1px;
}


.form-group input::placeholder,
.form-group textarea::placeholder {

    color: #999999;
}


/*
 * A <select> has no ::placeholder — this
 * is what greys out "Select your trade…"
 * the same way, since its disabled first
 * option (value="") is what makes the
 * select :invalid while required and
 * still on that option.
 */

.form-group select:invalid {

    color: #999999;
}


/*
 * No drag handle — height is fixed at
 * roughly 5 paragraphs of space to start,
 * then grown automatically as the user
 * types past that point (see the script
 * at the bottom of contact.html), rather
 * than left resizable or scrolling.
 */

.form-group textarea {

    min-height: 220px;

    resize: none;

    overflow: hidden;
}


/* =========================
   Consent Checkboxes
   ========================= */

/*
 * Two separate checkboxes rather than one
 * — the privacy one is `required` (no
 * legitimate-interest basis to reply
 * without it), the marketing one is
 * genuinely optional and unticked by
 * default, since UK PECR treats "reply to
 * my enquiry" and "send me future offers"
 * as two different consents that can't be
 * bundled into a single tickbox.
 */

.consent-group {

    display: flex;

    flex-direction: column;

    gap: 12px;
}


.consent-checkbox {

    position: relative;

    display: flex;

    padding-left: 28px;

    cursor: pointer;

    font-size: 14px;

    line-height: 1.5;

    color: var(--color-text-muted);
}


.consent-checkbox input[type="checkbox"] {

    position: absolute;

    left: 0;
    top: 2px;

    width: 18px;
    height: 18px;

    margin: 0;

    opacity: 0;

    cursor: pointer;
}


.consent-checkmark {

    position: absolute;

    left: 0;
    top: 2px;

    width: 18px;
    height: 18px;

    background: white;

    border: 1px solid #999999;

    border-radius: 3px;
}


.consent-checkbox input:checked ~ .consent-checkmark {

    background: black;

    border-color: black;
}


.consent-checkbox input:checked ~ .consent-checkmark::after {

    content: "";

    position: absolute;

    left: 6px;
    top: 2px;

    width: 4px;
    height: 9px;

    border: solid white;

    border-width: 0 2px 2px 0;

    transform: rotate(45deg);
}


.consent-checkbox input:focus-visible ~ .consent-checkmark {

    outline: 2px solid black;

    outline-offset: 2px;
}


.consent-checkbox a {

    color: black;
}


/* =========================
   CAPTCHA
   ========================= */

/*
 * Shared by contact.html and careers.html
 * — Google's real reCAPTCHA v2 checkbox
 * widget (loaded via the script tag next
 * to each page's other <script> includes).
 * The widget renders its own iframe UI, so
 * there's nothing to style here beyond
 * spacing/the two bits of page-owned text
 * around it.
 */

.captcha-error {

    margin: 8px 0 0;

    font-size: 14px;

    color: #c0392b;
}


/* Submit failures (network down, server
   rejected the enquiry). Same red as the
   captcha error so the form has one voice for
   "this didn't work", but its own element —
   the two can be shown for different reasons. */
.form-error {

    margin: 12px 0 0;

    font-size: 14px;

    color: #c0392b;
}


.captcha-caption {

    margin: 8px 0 0;

    font-size: 13px;

    color: #888888;
}


/* =========================
   Phone Field
   ========================= */

/*
 * Country-code chip + the actual phone
 * number input side by side. The chip
 * only ever shows the dial code itself
 * ("+44") so it stays as narrow as
 * possible and leaves the number input
 * nearly the whole row — the same width
 * the Name field gets in its own row.
 */

.phone-field {

    display: flex;

    gap: 8px;
}


/*
 * A native <select> sized and colored to
 * be invisible, layered exactly over the
 * small visible chip beside it — clicking
 * the chip really clicks the select
 * underneath, which opens with the full
 * "Country Name (+code)" list like any
 * other select. Doing it this way (rather
 * than a custom-built dropdown) keeps
 * native keyboard/mobile picker behavior.
 */

.phone-country {

    position: relative;

    flex: 0 0 auto;
}


.phone-country-display {

    display: flex;

    align-items: center;

    height: 100%;

    padding: 12px 10px;

    border: 1px solid #cccccc;

    border-radius: 5px;

    background: white;

    font-size: 15px;

    white-space: nowrap;
}


.phone-country-select {

    position: absolute;

    inset: 0;

    width: 100%;

    height: 100%;

    padding: 0;

    border: none;

    opacity: 0;

    cursor: pointer;
}


.phone-field input {

    flex: 1 1 auto;

    /*
     * min-width:0 stops a flex item from
     * refusing to shrink below its content
     * size — without it, a long typed
     * phone number could push the code
     * chip out of the row on a narrow
     * screen.
     */
    min-width: 0;
}


/* =========================
   File Attachments
   ========================= */

/*
 * The native file input is kept in the
 * DOM (still what actually holds the
 * chosen files and drives "required"-
 * style validation if that's ever added)
 * but visually hidden — a <label for=
 * "attachments"> styled like a button
 * opens the native file picker just as
 * well, and looks like the rest of the
 * site instead of the browser's default
 * file input chrome.
 */

.file-input-hidden {

    position: absolute;

    width: 1px;

    height: 1px;

    margin: -1px;

    padding: 0;

    overflow: hidden;

    clip: rect(0, 0, 0, 0);

    white-space: nowrap;

    border: 0;
}


.file-select-btn {

    align-self: flex-start;

    padding: 10px 20px;

    font-size: 14px;
}


/*
 * Replaces the browser's own "3 files"
 * summary with real thumbnails/names —
 * see the script at the bottom of
 * contact.html.
 */

.file-preview-list {

    display: flex;

    flex-wrap: wrap;

    /*
     * Wider than you'd expect for a 12px-
     * ish gap — the remove badge (see
     * .file-preview-remove below) pokes
     * outside each item's own box, so
     * neighboring items need extra room
     * or their badges start to overlap.
     */
    gap: 18px;
}


.file-preview-item {

    position: relative;

    display: flex;

    flex-direction: column;

    align-items: center;

    gap: 4px;

    width: 80px;
}


.file-preview-thumb {

    width: 70px;

    height: 70px;

    object-fit: cover;

    background: #f0f0f0;

    border: 1px solid #e0e0e0;

    border-radius: 6px;

    cursor: pointer;
}


/*
 * Small "x" badge in the corner of every
 * item (thumbnail or no-thumb chip alike,
 * since .file-preview-item itself is the
 * positioning context) that filters the
 * file out of attachedFiles — see the
 * script at the bottom of contact.html.
 */

.file-preview-remove {

    position: absolute;

    top: -6px;

    right: -6px;

    width: 20px;

    height: 20px;

    display: flex;

    align-items: center;

    justify-content: center;

    padding: 0;

    border: none;

    border-radius: 50%;

    background: black;

    color: white;

    font-size: 14px;

    line-height: 1;

    cursor: pointer;
}


.file-preview-remove:hover {

    opacity: 0.7;
}


.file-preview-name {

    max-width: 80px;

    overflow: hidden;

    text-overflow: ellipsis;

    white-space: nowrap;

    font-size: 11px;

    color: var(--color-text-muted);

    text-align: center;
}


/*
 * Files that don't get a thumbnail (not
 * an image or video) — a small labelled
 * chip with just the name, wider than
 * the 80px thumbnail column since
 * there's no square image constraining
 * it.
 */

.file-preview-item--no-thumb {

    flex-direction: row;

    width: auto;

    padding: 6px 10px;

    background: #f5f5f5;

    border: 1px solid #e0e0e0;

    border-radius: 6px;
}


.file-preview-item--no-thumb .file-preview-name {

    max-width: 160px;
}


/* =========================
   Success Popup
   ========================= */

/*
 * display:none by default, switched to
 * flex only via the .is-open class (see
 * the script at the bottom of contact.
 * html) — kept separate from the native
 * `hidden` attribute so this rule can't
 * end up fighting it for which one wins.
 */

.popup-overlay {

    display: none;

    position: fixed;

    inset: 0;

    align-items: center;

    justify-content: center;

    padding: 20px;

    background: rgba(0, 0, 0, 0.5);

    z-index: 2000;
}


.popup-overlay.is-open {

    display: flex;
}


.popup {

    max-width: 400px;

    padding: 40px;

    background: white;

    border-radius: 8px;

    text-align: center;

    box-shadow:
        var(--card-shadow);
}


.popup h2 {

    margin: 0 0 12px;

    font-size: 26px;
}


.popup p {

    margin: 0 0 24px;

    font-size: 15px;

    line-height: 1.5;

    color: var(--color-text-muted);
}


/* =========================
   Review Lightbox
   ========================= */

/*
 * Reuses .popup-overlay/.popup for the
 * overlay/card chrome (same #reviewLightbox
 * markup exists on both index.html and
 * reviews.html) — .review-lightbox just
 * widens it and switches it back to
 * left-aligned text, since a multi-
 * paragraph review reads better left-
 * aligned than .popup's own centered
 * default (built for a short confirmation
 * message).
 */

.review-lightbox {

    max-width: 500px;

    text-align: left;
}


.review-lightbox-header {

    display: flex;

    align-items: center;

    gap: 16px;

    margin-bottom: 20px;
}


.review-lightbox-avatar {

    width: 56px;

    aspect-ratio: 1 / 1;

    border-radius: 50%;

    object-fit: cover;

    flex-shrink: 0;
}


.review-lightbox-author {

    margin: 0;

    font-weight: bold;

    font-size: 16px;
}


.review-lightbox-stars {

    margin-top: 2px;

    font-size: 16px;
}


.review-lightbox-text {

    /* However long the review actually
       is — unlike .review-text in the
       card, nothing here is clamped —
       this scrolls instead of pushing the
       popup taller than the viewport. */
    max-height: 50vh;

    margin: 0 0 24px;

    overflow-y: auto;

    font-size: 16px;

    line-height: 1.6;

    color: var(--color-text-muted);
}


/*
 * Reuses .review-photo-thumb (same
 * hover/cursor/sizing as the card's own
 * photo row) — openReviewLightbox() in
 * main.js only fills this in and shows it
 * when the review actually has photos, so
 * it never leaves stray gap/margin behind
 * for reviews that don't.
 */
.review-lightbox-photos {

    display: flex;

    flex-wrap: wrap;

    gap: 8px;

    margin-bottom: 24px;
}


/*
 * Same idea as Google's own "Response from
 * the owner" box — set to display: none by
 * default and only switched on by
 * openReviewLightbox() in main.js when the
 * review actually has one (currently just
 * the under-3-star reviews).
 */
.review-lightbox-owner-response {

    display: none;

    margin: 0 0 24px;

    padding: 16px;

    background: #f5f5f5;

    border-radius: 6px;
}


.review-lightbox-owner-response-label {

    margin: 0 0 6px;

    font-weight: bold;

    font-size: 14px;
}


.review-lightbox-owner-response-text {

    margin: 0;

    font-size: 15px;

    line-height: 1.5;

    color: var(--color-text-muted);
}


/* =========================
   File Preview Lightbox
   ========================= */

/*
 * Same display:none / .is-open pattern as
 * .popup-overlay above, for the same
 * reason — a darker backdrop since this
 * one's showing a photo/video rather than
 * text, and a higher z-index purely so it
 * always wins if it's ever open at the
 * same time as the success popup.
 */

.lightbox-overlay {

    display: none;

    position: fixed;

    inset: 0;

    align-items: center;

    justify-content: center;

    padding: 40px;

    background: rgba(0, 0, 0, 0.85);

    z-index: 2100;
}


.lightbox-overlay.is-open {

    display: flex;
}


.lightbox-content img,
.lightbox-content video {

    display: block;

    max-width: 100%;

    max-height: 85vh;

    border-radius: 8px;
}


.lightbox-close {

    position: absolute;

    top: 24px;

    right: 24px;

    width: 40px;

    height: 40px;

    display: flex;

    align-items: center;

    justify-content: center;

    padding: 0;

    border: none;

    border-radius: 50%;

    background: white;

    color: black;

    font-size: 22px;

    line-height: 1;

    cursor: pointer;
}


.lightbox-close:hover {

    opacity: 0.8;
}


/*
 * Prev/Next — same circular white-on-dark
 * button treatment as the close button,
 * flanking the content on either side.
 * Disabled (nothing before the first /
 * after the last previewable file) rather
 * than hidden, same reasoning as the
 * reviews carousel arrows used to have
 * before that one became an endless loop —
 * this list of attachments is finite, so
 * there's a real start and end here.
 */

.lightbox-nav {

    position: absolute;

    top: 50%;

    transform: translateY(-50%);

    width: 44px;

    height: 44px;

    display: flex;

    align-items: center;

    justify-content: center;

    padding: 0;

    border: none;

    border-radius: 50%;

    background: white;

    color: black;

    font-size: 26px;

    line-height: 1;

    cursor: pointer;
}


.lightbox-nav:hover {

    opacity: 0.8;
}


.lightbox-nav:disabled {

    opacity: 0.3;

    cursor: default;
}


.lightbox-prev {

    left: 24px;
}


.lightbox-next {

    right: 24px;
}


/* =========================
   Extra Content
   ========================= */

.content {

    height: 1500px;

    padding: 80px 50px;
}


/* =========================
   Placeholder Page
   ========================= */

/*
 * Used by reviews.html — the nav link
 * exists but there's no real page built
 * yet, so this is a bare stand-in until
 * that content is written.
 */

.placeholder {

    padding: 40px 50px 100px;

    text-align: center;
}


.placeholder h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.placeholder-text {

    margin: 0;

    font-size: 18px;

    color: var(--color-text-muted);
}


/* =========================
   Careers Page (careers.html)
   ========================= */

.careers {

    padding: 40px 50px 100px;

    text-align: center;
}


.careers h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.careers-tagline {

    margin: 0 0 24px;

    font-size: 18px;

    color: var(--color-text-muted);
}


/*
 * Same flex-column layout as
 * .contact-form — .form-row/.form-group
 * and their input/textarea/select styling
 * are already generic (not scoped under
 * .contact-form), so this just needs its
 * own container sizing.
 */

.application-form {

    display: flex;

    flex-direction: column;

    gap: 20px;

    max-width: 600px;

    margin: 0 auto;

    text-align: left;
}


/* =========================
   Reviews Page (reviews.html)
   ========================= */

.reviews-page {

    padding: 40px 50px 100px;

    text-align: center;
}


.reviews-page h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.reviews-page-tagline {

    margin: 0 0 30px;

    font-size: 18px;

    color: var(--color-text-muted);
}


/*
 * Hides the carousel entirely once
 * expanded (see the toggle script in
 * reviews.html) — same idea as
 * .portfolio-carousel.is-hidden, so the
 * carousel and grid are never both on
 * screen at once.
 */

.reviews-carousel.is-hidden {

    display: none;
}


/*
 * Every review at once, in place of the
 * carousel — toggled the same way as
 * .portfolio-grid (a .portfolio-toggle
 * button, reused as-is since it isn't
 * scoped to .portfolio). Reuses .review-
 * card itself (avatar/stars/text all
 * already scale off --card-size) so a
 * card here looks identical to one in the
 * carousel above; columns are sized to
 * that same --card-size variable rather
 * than a fraction of the grid's own width
 * — the same fix as .portfolio-grid, so
 * these cards can't end up a different
 * size than the carousel's.
 */

.reviews-grid {

    display: none;

    grid-template-columns:
        repeat(auto-fit, var(--card-size));

    justify-content: center;

    gap: var(--card-gap);

    margin: 50px 0 0;
}


.reviews-grid.is-open {

    display: grid;
}


/* =========================
   Footer
   ========================= */

/*
 * Shared by every page (like .navbar, it
 * sits outside <main>) — dark rather than
 * one of the site's light backgrounds, so
 * it reads as a clear stop at the bottom
 * of the page rather than blending into
 * whichever section happens to be last.
 */

.site-footer {

    /* Opaque, so it hides the crane behind it
       as it scrolls up — no z-index needed now
       the crane sits at a negative layer. */

    padding: 60px 50px 30px;

    background: black;

    color: white;
}


.footer-inner {

    display: flex;

    justify-content: space-between;

    align-items: flex-start;

    gap: 40px;

    max-width: 1100px;

    margin: 0 auto;

    padding-bottom: 40px;

    border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}


.footer-logo {

    margin: 0 0 12px;

    font-size: 20px;

    font-weight: bold;
}


.footer-tagline {

    max-width: 320px;

    margin: 0;

    font-size: 14px;

    line-height: 1.6;

    color: rgba(255, 255, 255, 0.7);
}


.footer-contact {

    display: flex;

    flex-direction: column;

    gap: 10px;
}


/*
 * Two columns rather than one long stack —
 * eight links in a single column ran far taller
 * than the two blocks either side of it, which
 * made the footer look lopsided and gave the eye
 * a long list to scan.
 *
 * grid-auto-flow: column with four explicit rows
 * fills DOWN the first column then across, so
 * the order still reads top-to-bottom the way a
 * nav list should. (Default row flow would run
 * Home/About across the top instead, which reads
 * as pairs rather than a list.)
 */

.footer-links {

    display: grid;

    grid-template-rows: repeat(4, auto);

    grid-auto-flow: column;

    justify-content: start;

    gap: 10px 40px;
}


.footer-links a,
.footer-contact a {

    color: white;

    text-decoration: none;

    font-size: 15px;
}


.footer-contact a {

    display: flex;

    align-items: center;

    gap: 8px;
}


.footer-contact-icon {

    width: 16px;

    height: 16px;

    flex-shrink: 0;
}


.footer-links a:hover,
.footer-contact a:hover {

    opacity: 0.6;
}


.footer-heading {

    margin: 0 0 12px;

    font-size: 16px;

    font-weight: bold;
}


.footer-copyright {

    max-width: 1100px;

    margin: 20px auto 0;

    text-align: center;

    font-size: 13px;

    color: rgba(255, 255, 255, 0.5);
}


.footer-copyright a {

    color: rgba(255, 255, 255, 0.7);
}


.footer-copyright a:hover {

    color: white;
}


/* =========================
   Cookie Banner
   ========================= */

/*
 * Built and injected by js/cookies.js on
 * every page (like the navbar's hamburger
 * behavior in js/nav.js) rather than
 * hand-copied into each page's markup.
 * Same black/white palette as the rest of
 * the site's dark UI (the nav's active-pill
 * state, card shadows) so it reads as part
 * of the same design system rather than a
 * bolted-on third-party widget.
 */

.cookie-banner {

    position: fixed;

    left: 0;
    right: 0;
    bottom: 0;

    z-index: 2000;

    display: flex;

    flex-wrap: wrap;

    align-items: center;

    justify-content: space-between;

    gap: 20px;

    padding: 20px 50px;

    background: black;

    color: white;

    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.15);
}


.cookie-banner-text {

    flex: 1 1 320px;

    margin: 0;

    font-size: 14px;

    line-height: 1.5;

    color: rgba(255, 255, 255, 0.85);
}


.cookie-banner-text a {

    color: white;
}


.cookie-banner-actions {

    display: flex;

    flex-wrap: wrap;

    flex-shrink: 0;

    gap: 12px;
}


.cookie-banner-accept,
.cookie-banner-decline {

    padding: 12px 22px;

    border-radius: 5px;

    font: inherit;

    font-size: 15px;

    cursor: pointer;

    transition: opacity 0.2s ease;
}


.cookie-banner-accept {

    background: white;

    color: black;

    border: none;
}


.cookie-banner-decline {

    background: transparent;

    color: white;

    border: 1px solid rgba(255, 255, 255, 0.5);
}


.cookie-banner-accept:hover,
.cookie-banner-decline:hover {

    opacity: 0.8;
}


/* =========================
   Blog Page (blog.html)
   ========================= */

.blog-page {

    padding: 40px 50px 100px;

    text-align: center;
}


.blog-page h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.blog-page-tagline {

    margin: 0 0 30px;

    font-size: 18px;

    color: var(--color-text-muted);
}


/*
 * Fuzzy-matched against blogPosts in
 * js/blog.js as the visitor types — see
 * that file for the scoring itself. This
 * is just the input's own look, not tied
 * to .form-group like the contact/careers
 * forms since it stands alone here.
 */

.blog-search {

    max-width: 480px;

    margin: 0 auto 30px;
}


.blog-search-input {

    width: 100%;

    padding: 12px 20px;

    border: 1px solid #cccccc;

    border-radius: 24px;

    background: white;

    font-family: inherit;

    font-size: 15px;

    color: black;
}


.blog-search-input:focus {

    outline: 2px solid black;

    outline-offset: 1px;
}


.blog-search-input::placeholder {

    color: #999999;
}


.blog-search-hint {

    margin: -18px 0 30px;

    font-size: 14px;

    color: #666666;
}


/*
 * Same 3-column-cap trick as .services-
 * grid, and .blog-card mirrors .service-
 * card's white-card-on-grey-section look
 * — same design language as the services
 * and reviews cards, just with its own
 * class names since a "blog card" isn't
 * really a "service card".
 *
 * auto-fill rather than .services-grid's
 * auto-fit: the search in js/blog.js can
 * narrow this down to one or two results,
 * and auto-fit stretches whatever's left
 * to fill the row — auto-fill keeps the
 * column tracks reserved instead, so a
 * single search result stays card-sized
 * rather than growing to fill the width.
 */

.blog-grid {

    display: grid;

    grid-template-columns: repeat(auto-fill, minmax(max(240px, calc((100% - 60px) / 3)), 1fr));

    gap: 30px;

    text-align: left;
}


.blog-card {

    display: flex;

    flex-direction: column;

    overflow: hidden;

    background: white;

    border-radius: 8px;

    box-shadow:
        var(--card-shadow);

    transition:
        transform 0.25s ease,
        box-shadow 0.25s ease;
}


.blog-image {

    width: 100%;

    aspect-ratio: 4 / 3;

    object-fit: cover;
}


.blog-meta {

    margin: 20px 20px 0;

    font-size: 13px;

    text-transform: uppercase;

    letter-spacing: 0.03em;

    color: #888888;
}


.blog-title {

    margin: 8px 20px;

    font-size: 20px;
}


.blog-excerpt {

    margin: 0 20px 8px;

    font-size: 15px;

    line-height: 1.5;

    color: var(--color-text-muted);
}


.blog-link {

    /* Same margin-top: auto trick as
       .service-link — pins "Read More" to
       the bottom of the card regardless of
       how long the excerpt above runs. */
    margin: auto 20px 20px;

    font-weight: bold;

    color: black;

    text-decoration: none;
}


.blog-link:hover {

    text-decoration: underline;
}


/* =========================
   Blog Post Page (blog-post.html)
   ========================= */

.blog-post {

    padding: 40px 50px 100px;
}


.blog-post-inner {

    max-width: 800px;

    margin: 0 auto;

    text-align: left;
}


.blog-post-back {

    display: inline-block;

    margin-bottom: 24px;

    font-weight: bold;

    color: black;

    text-decoration: none;
}


.blog-post-back:hover {

    text-decoration: underline;
}


.blog-post-meta {

    margin: 0 0 8px;

    font-size: 13px;

    text-transform: uppercase;

    letter-spacing: 0.03em;

    color: #888888;
}


.blog-post-inner h1 {

    margin: 0 0 24px;

    font-size: 36px;
}


.blog-post-image {

    width: 100%;

    max-height: 420px;

    object-fit: cover;

    border-radius: 8px;

    margin-bottom: 24px;
}


.blog-post-body p {

    margin: 0 0 20px;

    font-size: 16px;

    line-height: 1.7;

    color: var(--color-text-muted);
}


.blog-post-body a {

    color: black;
}


/* =========================
   About Page (about.html)
   ========================= */

.about-page {

    padding: 40px 50px 100px;

    text-align: center;
}


.about-page h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.about-page-tagline {

    margin: 0 0 60px;

    font-size: 18px;

    color: var(--color-text-muted);
}


.about-inner {

    max-width: 1100px;

    margin: 0 auto;
}


/*
 * Same image + text row as .service-row on
 * services.html — kept as its own class
 * rather than reused directly so an "about
 * row" isn't literally a "service row",
 * same reasoning as .blog-card vs
 * .service-card.
 */

.about-row {

    display: flex;

    align-items: center;

    gap: 50px;

    margin-bottom: 80px;

    text-align: left;
}


.about-row-image {

    flex: 1 1 45%;

    min-width: 0;
}


.about-row-image img {

    /*
     * Pinned to company.jpg's real pixel
     * dimensions (289x408) rather than a
     * generic 4:3 crop — same reasoning as
     * .hero-image on index.html, which uses
     * this same portrait-shaped photo. A
     * forced 4:3 box with object-fit: cover
     * was cropping the top/bottom of the
     * image off since the source is taller
     * than it is wide.
     */
    display: block;

    width: 100%;

    aspect-ratio: 289 / 408;

    object-fit: contain;

    border-radius: 8px;

    box-shadow: var(--card-shadow);
}


.about-row-content {

    flex: 1 1 45%;
}


.about-row-content h2 {

    margin: 0 0 16px;

    font-size: 26px;
}


.about-row-content p {

    margin: 0 0 16px;

    font-size: 16px;

    line-height: 1.6;

    color: var(--color-text-muted);
}


/*
 * Three plain numbers/labels rather than
 * full cards — sits between the story row
 * and the values grid as a quick-scan
 * summary, not another set of boxes.
 */

.about-stats {

    display: flex;

    flex-wrap: wrap;

    justify-content: center;

    gap: 40px;

    margin-bottom: 80px;

    padding: 40px 0;

    border-top: 1px solid #e0e0e0;

    border-bottom: 1px solid #e0e0e0;
}


.about-stat {

    min-width: 160px;
}


.about-stat-number {

    margin: 0 0 4px;

    font-size: 36px;

    font-weight: bold;
}


.about-stat-label {

    margin: 0;

    font-size: 15px;

    color: #666666;
}


.about-inner h2.about-values-heading {

    margin: 0 0 30px;

    font-size: 28px;
}


/*
 * Same auto-fit/3-column-cap trick as
 * .services-grid — .value-card mirrors
 * .service-card's white-card-on-grey-
 * section look for the same reason
 * .about-row mirrors .service-row above.
 */

.values-grid {

    display: grid;

    grid-template-columns: repeat(auto-fit, minmax(max(240px, calc((100% - 60px) / 3)), 1fr));

    gap: 30px;

    margin-bottom: 80px;

    text-align: left;
}


.value-card {

    padding: 30px;

    background: white;

    border-radius: 8px;

    box-shadow: var(--card-shadow);

    transition:
        transform 0.25s ease,
        box-shadow 0.25s ease;
}


.value-icon {

    display: block;

    margin-bottom: 12px;

    font-size: 28px;
}


.value-title {

    margin: 0 0 8px;

    font-size: 19px;
}


.value-description {

    margin: 0;

    font-size: 15px;

    line-height: 1.5;

    color: var(--color-text-muted);
}


/* =========================
   Privacy Policy Page (privacy.html)
   ========================= */

.privacy-page {

    padding: 40px 50px 100px;

    text-align: center;
}


.privacy-page h1 {

    margin: 0 0 12px;

    font-size: 40px;

    text-transform: uppercase;
}


.privacy-page-tagline {

    margin: 0 0 40px;

    font-size: 15px;

    color: #888888;
}


.privacy-page-inner {

    max-width: 800px;

    margin: 0 auto;

    text-align: left;
}


.privacy-page-inner h2 {

    margin: 40px 0 12px;

    font-size: 22px;
}


.privacy-page-inner h2:first-child {

    margin-top: 0;
}


.privacy-page-inner p {

    margin: 0 0 16px;

    font-size: 16px;

    line-height: 1.6;

    color: var(--color-text-muted);
}


.privacy-page-inner ul {

    margin: 0 0 16px;

    padding-left: 20px;
}


.privacy-page-inner li {

    margin-bottom: 8px;

    font-size: 16px;

    line-height: 1.6;

    color: var(--color-text-muted);
}


.privacy-page-inner a {

    color: black;
}


/* =========================
   Mobile
   ========================= */

@media (max-width: 700px) {


    :root {
        --navbar-height: 3.75rem;
    }


    /* Decorative Background Crane */

    .page-crane-bg {

        display: none;
    }


    /* Navigation */

    .navbar {

        padding: 0 20px;
    }


    .logo {

        font-size: 20px;
    }


    .nav-toggle {

        display: flex;
    }


    /*
     * Off by default, switched to a full-
     * width dropdown panel by js/nav.js
     * adding .is-open — position: fixed
     * with top: 60px sits it right under
     * the mobile navbar's own height set
     * above.
     */

    .nav-links {

        display: none;

        position: fixed;

        top: 60px;
        left: 0;

        flex-direction: column;

        /* Base .nav-links sets align-items:
           center for desktop's row (to
           center links vertically in the
           bar) — reset to stretch here so
           this column's cross axis (link
           width) still fills full width. */
        align-items: stretch;

        width: 100%;

        gap: 0;

        padding: 10px 20px 20px;

        /* Opaque, unlike the desktop bar's
           translucent glass — this panel sits
           over page content, so it has to fully
           hide what's behind it to stay
           readable. Matches the bar's grey. */
        background: #11141a;

        box-shadow: 0 8px 20px rgba(16, 24, 40, 0.12);
    }


    .nav-links.is-open {

        display: flex;
    }


    .nav-links a {

        padding: 14px 16px;

        font-size: 16px;

        border-bottom: 1px solid rgba(255, 255, 255, 0.12);
    }


    .nav-links a:last-child {

        border-bottom: none;
    }


    /* Main */

    /* Hero */

    .hero {

        /* Small, fixed side padding —
           along with .hero-inner having
           no room left to cap, content
           runs close to full screen
           width instead of leaving
           whitespace down the sides. */
        padding: 40px 20px 50px;
    }


    .hero-textbox h1 {

        font-size: 32px;
    }


    .hero-body {

        /* Image above text box instead
           of side by side — there's no
           width left on a phone screen
           to put them side by side. */
        flex-direction: column;

        gap: 24px;
    }


    .hero-image {

        max-width: 100%;
    }


    /* Services */

    .services {

        padding: 30px 20px 45px;
    }


    .services-inner {

        /* No more 70% cap — content runs
           close to full screen width. */
        width: 100%;
    }


    .services-inner h2 {

        margin-bottom: 30px;

        font-size: 30px;
    }


    /* Why Choose Us */

    .trust-bar {

        padding: 25px 20px;
    }


    .trust-bar-inner {

        gap: 24px;
    }


    .trust-stat-number {

        font-size: 30px;
    }


    /* How It Works */

    .process {

        padding: 20px 20px 45px;
    }


    .process-inner h2 {

        font-size: 30px;
    }


    .process-tagline {

        margin-bottom: 30px;
    }


    .process-icon-steps {

        gap: 16px;
    }


    .process-icon-step {

        width: 90%;

        gap: 16px;

        padding: 20px 18px;
    }


    .process-icon-step:nth-child(odd) {

        margin-right: 18px;
    }


    .process-icon-step:nth-child(even) {

        margin-left: 18px;
    }


    .process-step-media {

        width: 58px;

        height: 58px;
    }


    .process-step-icon svg {

        width: 30px;

        height: 30px;
    }


    /* Recent Work */

    .recent-work {

        padding: 45px 20px;
    }


    .recent-work-grid {

        /* Desktop's auto-fit/minmax(200px)
           collapses to a single column once
           the row's narrower than ~400px,
           which is every mobile screen — a
           fixed 2-column grid keeps the 4
           items as 2x2 instead of a 1x4
           stack. */
        grid-template-columns: repeat(2, 1fr);
    }


    .recent-work-inner h2 {

        font-size: 30px;
    }


    .recent-work-tagline {

        margin-bottom: 30px;
    }


    /* Services Page */

    .services-page {

        padding: 45px 20px;
    }


    .services-page-tagline {

        margin-bottom: 30px;
    }


    .service-row,
    .service-row.is-reversed {

        /* No room to sit image and text
           side by side on a phone screen —
           same fallback as .hero-body. */
        flex-direction: column;

        gap: 20px;
    }


    .service-row:not(:last-child) {

        margin-bottom: 40px;
    }


    .service-row-content h2 {

        font-size: 22px;
    }


    /* FAQ */

    .faq {

        padding: 20px 20px 45px;
    }


    /* No staircase here — the rows already run
       the full screen width, so there's no
       offset left to slide into. */
    .faq-list {

        --faq-step: 0vw;
    }


    .faq-inner h2 {

        margin-bottom: 30px;

        font-size: 30px;
    }


    .faq-item {

        padding: 16px 18px;
    }


    /* Final CTA */

    .cta {

        padding: 45px 20px;
    }


    .cta-inner h2 {

        font-size: 28px;
    }


    /* Portfolio */

    .portfolio {

        padding: 40px 20px 100px;
    }


    .portfolio-tabs {

        /* 3x2 instead of a single row of 6
           — six tabs across is too tight on
           a phone screen. */
        grid-template-columns: repeat(3, 1fr);

        gap: 8px;
    }


    .portfolio-tab {

        padding: 8px 6px;

        font-size: 13px;
    }


    .portfolio-carousel {

        /* .portfolio padding drops to 20px
           here, same trick as
           .reviews-carousel's mobile
           override. */
        width: calc(100% + 40px);

        margin: 0 -20px;
    }


    .portfolio-grid {

        /* 2 across on mobile, matching
           getTargetCardCount() in
           portfolio.html. */
        grid-template-columns:
            repeat(2, var(--portfolio-card-size));

        /* .portfolio padding drops to 20px
           here, same trick as
           .portfolio-carousel's mobile
           override. */
        width: calc(100% + 40px);

        margin: 30px -20px 0;
    }


    /* Contact */

    .contact {

        padding: 20px 20px 60px;
    }


    .contact h1 {

        font-size: 32px;
    }


    .form-row {

        /* Stacks Name/Address and Phone/
           Email back into single columns
           — side by side is too tight on
           a phone-width screen. */
        flex-direction: column;

        gap: 20px;
    }


    /* Careers */

    .careers {

        padding: 20px 20px 60px;
    }


    .careers h1 {

        font-size: 32px;
    }


    /* Blog */

    .blog-page {

        padding: 45px 20px;
    }


    .blog-page h1 {

        font-size: 32px;
    }


    .blog-post {

        padding: 45px 20px;
    }


    .blog-post-inner h1 {

        font-size: 28px;
    }


    /* About */

    .about-page {

        padding: 45px 20px;
    }


    .about-row {

        /* No room to sit image and text
           side by side on a phone screen —
           same fallback as .service-row. */
        flex-direction: column;

        gap: 20px;

        margin-bottom: 40px;
    }


    .about-row-content h2 {

        font-size: 22px;
    }


    .about-stats {

        gap: 24px;

        margin-bottom: 40px;
    }


    .values-grid {

        margin-bottom: 40px;
    }


    /* Privacy Policy */

    .privacy-page {

        padding: 45px 20px;
    }


    .privacy-page h1 {

        font-size: 32px;
    }


    /* Reviews */

    .reviews {

        padding: 20px 20px 45px;
    }


    .reviews-page {

        padding: 45px 20px;
    }


    .reviews-page h1 {

        font-size: 32px;
    }


    .reviews-header h2 {

        font-size: 30px;
    }


    .reviews-header .reviews-header-tagline {

        font-size: 16px;

        margin-bottom: 24px;
    }


    .rating {

        gap: 8px;
    }


    .stars {

        font-size: 30px;
    }


    .rating-score {

        font-size: 24px;
    }


    /* "Based on N Google reviews" — desktop
       leaves 50px under this before the
       carousel, which is a lot of dead space
       on a narrow screen. */

    .reviews-header p {

        margin-bottom: 22px;
    }


    /* Carousel */

    :root {

        --arrow-edge-gap: 10px;

        --card-gap: 15px;

        --portfolio-card-gap: 12px;
    }


    .reviews-carousel {

        /* .reviews padding drops to 20px here */
        width: calc(100% + 40px);

        /* Tighter gap to the "View all Google
           reviews" button below than desktop's
           40px. */
        margin: 0 -20px 20px;
    }


    .review-card {

        padding: 25px;
    }


    /* Arrows */

    .review-arrow {

        width: 40px;

        height: 40px;

        font-size: 20px;
    }


    /* Footer */

    .site-footer {

        padding: 40px 20px 20px;
    }


    .footer-inner {

        flex-direction: column;

        align-items: center;

        gap: 24px;

        padding-bottom: 24px;

        text-align: center;
    }


    .footer-tagline {

        max-width: 100%;
    }


    .footer-contact {

        align-items: center;
    }


    /* The footer stacks and centres on mobile,
       so the two link columns centre as a pair
       rather than sitting flush left. */
    .footer-links {

        justify-content: center;

        text-align: center;
    }


    /* Cookie Banner */

    .cookie-banner {

        flex-direction: column;

        align-items: stretch;

        padding: 20px;
    }


    /*
     * .cookie-banner-text's flex-basis is
     * 320px on desktop, meant as a *width*
     * in the row layout above — flipping to
     * flex-direction: column here makes
     * that same 320px a *height* instead,
     * forcing the text block to 320px tall
     * regardless of its actual content.
     * Resetting the basis back to auto is
     * what actually fixes the oversized
     * banner on mobile.
     */

    .cookie-banner-text {

        flex-basis: auto;
    }


    .cookie-banner-accept,
    .cookie-banner-decline {

        flex: 1 1 0;
    }

}