/* ==========================================================================
   JLBS Construction - site styles

   Layered on top of stock Bootstrap 5.3 via CSS custom properties, so there is
   no Sass build step and no Node toolchain to maintain.

   Brand colours are sampled directly from the JLBS logo supplied by the client:
   orange #F27E00 and near-black #151616.
   ========================================================================== */

:root {
    /* Brand */
    --jlbs-ink: #151616;
    --jlbs-slate: #2b2d2f;
    --jlbs-slate-soft: #4a4d50;
    --jlbs-accent: #f27e00;
    --jlbs-accent-hover: #c96700;

    /* The accent used as *text*. #f27e00 and #c96700 are both below 4.5:1 on white, so neither
       can carry copy - but --jlbs-accent-hover is still correct as a button hover *background*,
       where the requirement is contrast against the button label rather than against the page. */
    --jlbs-accent-text: #a85700;    /* 5.21:1 on white, 4.81:1 on --jlbs-surface */
    --jlbs-accent-on-dark: #ffab5e; /* 9.70:1 on --jlbs-ink, 6.15:1 on the hero scrim */

    /* Neutrals */
    --jlbs-surface: #f4f6f8;
    --jlbs-border: #dde3e9;
    --jlbs-muted: #5b6b7b;

    /* Bootstrap overrides */
    --bs-body-color: #2b3742;
    --bs-body-font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --bs-body-font-size: 1.0625rem;
    --bs-body-line-height: 1.65;
    /* Bootstrap 5.3's `a` rule reads the -rgb forms:
           color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1))
       Setting only --bs-link-color does nothing at all - which is why every unstyled anchor on
       this site rendered Bootstrap's default #0d6efd until this was fixed. Keep the two forms in
       step, and keep both -rgb values, or links silently revert to blue. */
    --bs-link-color: var(--jlbs-accent-text);
    --bs-link-color-rgb: 168, 87, 0;
    --bs-link-hover-color: var(--jlbs-ink);
    --bs-link-hover-color-rgb: 21, 22, 22;
    --bs-border-radius: 0.5rem;
    --bs-emphasis-color: var(--jlbs-ink);
}

body {
    -webkit-font-smoothing: antialiased;
    /* Clears the fixed mobile call bar so it never covers the end of the page. */
    padding-bottom: 4.25rem;
}

/* body's padding keeps *content* clear of the call bar, but says nothing about where the browser
   stops when it scrolls an element into view on focus - which is typically flush with the viewport
   bottom, underneath the bar. SC 2.4.11 Focus Not Obscured. Same breakpoint as the bar itself. */
@media (max-width: 991.98px) {
    html {
        scroll-padding-bottom: 5rem;
    }
}

@media (min-width: 992px) {
    body {
        padding-bottom: 0;
    }
}

h1, h2, h3, h4 {
    color: var(--jlbs-ink);
    font-weight: 700;
    letter-spacing: -0.015em;
    line-height: 1.2;
}

h1 { font-size: clamp(2rem, 1.4rem + 2.6vw, 3.25rem); }
h2 { font-size: clamp(1.5rem, 1.2rem + 1.3vw, 2.25rem); }
h3 { font-size: clamp(1.15rem, 1.05rem + 0.5vw, 1.4rem); }

.lead {
    font-size: clamp(1.075rem, 1rem + 0.4vw, 1.3rem);
    color: var(--jlbs-muted);
}

/* --- Accessibility ------------------------------------------------------- */

:focus-visible {
    outline: 3px solid var(--jlbs-accent);
    outline-offset: 2px;
    border-radius: 2px;
}

.skip-link {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 1080;
    padding: 0.6rem 1rem;
    background: var(--jlbs-ink);
    color: #fff;
    border-radius: var(--bs-border-radius);
}

.icon {
    width: 1.15em;
    height: 1.15em;
    vertical-align: -0.18em;
    flex: none;
}

/* --- Buttons ------------------------------------------------------------- */

.btn {
    --bs-btn-font-weight: 600;
    --bs-btn-padding-x: 1.35rem;
    --bs-btn-padding-y: 0.65rem;
}

/* Centre each label optically rather than on the font's line box, which in Segoe UI (system-ui
   on Windows) reserves more space above the glyphs than below. text-box trims to the active
   font's own metrics, so it corrects whichever system font is in use rather than nudging for
   one, and the padding adds back exactly what the trim removed, so buttons keep their height.
   Browsers without text-box (Firefox, for now) keep the line-box rendering.

   Labels centre on the capital height: the band from the top of the capitals to the baseline,
   which is what the eye reads as the label, descenders aside. Word labels were centred on the
   x-height for a while instead (1.3.0), which left their capitals and ascenders about 2px high
   at 16px, and it looked high. On the cap height every label, words or digits, measures within
   half a pixel of centre. */
@supports (text-box: trim-both cap alphabetic) {
    .btn {
        text-box: trim-both cap alphabetic;
        padding-block: calc(var(--bs-btn-padding-y) + (1lh - 1cap) / 2);
    }
}

/* A button sitting in a row of text links - the header's quote button - is read against those
   links, not against its own edges. Centring it optically put its text 3px above the 17px nav
   labels beside it; the untrimmed line box is the closest match, at 1px. Opt out entirely. */
.btn.btn--inline {
    text-box: normal;
    padding-block: var(--bs-btn-padding-y);
}

/* Centres an icon on the capitals beside it, not on the line box. 0.575em is half the icon's
   1.15em height. Browsers without the cap unit ignore this and keep the -0.18em from .icon. */
.btn .icon {
    vertical-align: calc(0.5cap - 0.575em);
}

.btn-accent {
    --bs-btn-color: var(--jlbs-ink);
    --bs-btn-bg: var(--jlbs-accent);
    --bs-btn-border-color: var(--jlbs-accent);
    --bs-btn-hover-color: var(--jlbs-ink);
    --bs-btn-hover-bg: var(--jlbs-accent-hover);
    --bs-btn-hover-border-color: var(--jlbs-accent-hover);
    --bs-btn-active-bg: var(--jlbs-accent-hover);
    --bs-btn-active-border-color: var(--jlbs-accent-hover);
    --bs-btn-active-color: var(--jlbs-ink);
}

/* A submit button while its form is sending (set by site.js). Deliberately not Bootstrap's
   .disabled: .btn-accent defines no disabled colours, so that would blank the button out
   mid-send. The spinner and label carry the state; this only stops it looking clickable. */
.btn[aria-disabled="true"] {
    cursor: progress;
}

.btn-ink {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--jlbs-ink);
    --bs-btn-border-color: var(--jlbs-ink);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--jlbs-slate);
    --bs-btn-hover-border-color: var(--jlbs-slate);
    --bs-btn-active-bg: var(--jlbs-slate);
    --bs-btn-active-border-color: var(--jlbs-slate);
    --bs-btn-active-color: #fff;
}

.btn-outline-ink {
    --bs-btn-color: var(--jlbs-ink);
    --bs-btn-border-color: rgba(21, 22, 22, 0.35);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--jlbs-ink);
    --bs-btn-hover-border-color: var(--jlbs-ink);
    --bs-btn-active-bg: var(--jlbs-ink);
    --bs-btn-active-border-color: var(--jlbs-ink);
    --bs-btn-active-color: #fff;
}

.btn-on-dark {
    --bs-btn-color: #fff;
    --bs-btn-border-color: rgba(255, 255, 255, 0.5);
    --bs-btn-hover-color: var(--jlbs-ink);
    --bs-btn-hover-bg: #fff;
    --bs-btn-hover-border-color: #fff;
    --bs-btn-active-color: var(--jlbs-ink);
    --bs-btn-active-bg: #fff;
    --bs-btn-active-border-color: #fff;
}

/* --- Header -------------------------------------------------------------- */

.site-header__strip {
    background: var(--jlbs-ink);
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.875rem;
    padding: 0.45rem 0;
}

.site-header__area {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.site-header__phone {
    color: #fff;
    font-weight: 600;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.site-header__phone:hover,
.site-header__phone:focus {
    color: var(--jlbs-accent);
}

.site-header .navbar {
    background: #fff;
    border-bottom: 1px solid var(--jlbs-border);
    padding-block: 0.75rem;
}

.navbar-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
}

.navbar-brand__mark {
    background: var(--jlbs-accent);
    color: var(--jlbs-ink);
    font-weight: 800;
    letter-spacing: 0.06em;
    font-size: 1rem;
    padding: 0.45rem 0.6rem;
    border-radius: 0.35rem;
}

.navbar-brand__text {
    display: flex;
    flex-direction: column;
    line-height: 1.15;
}

.navbar-brand__text strong {
    color: var(--jlbs-ink);
    font-size: 1.075rem;
}

.navbar-brand__text small {
    color: var(--jlbs-muted);
    font-size: 0.75rem;
}

.navbar .nav-link {
    color: var(--jlbs-slate);
    font-weight: 600;
    padding-inline: 0.9rem;
}

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

.navbar .nav-link.active {
    color: var(--jlbs-ink);
    box-shadow: inset 0 -3px 0 var(--jlbs-accent);
}

@media (max-width: 991.98px) {
    .navbar .nav-link.active {
        box-shadow: inset 3px 0 0 var(--jlbs-accent);
    }

    .navbar-nav .btn {
        margin-top: 0.75rem;
    }
}

/* --- Sections ------------------------------------------------------------ */

.section {
    padding-block: clamp(2.75rem, 2rem + 3vw, 5rem);
}

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

.section--ink {
    background: var(--jlbs-ink);
    color: rgba(255, 255, 255, 0.82);
}

.section--ink h1,
.section--ink h2,
.section--ink h3 {
    color: #fff;
}

.section__eyebrow {
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--jlbs-accent-text);
    margin-bottom: 0.5rem;
}

.section--ink .section__eyebrow {
    color: var(--jlbs-accent-on-dark);
}

/* The hero is not a .section--ink - it is its own component - so it never picked up the rule
   above and rendered the light-background accent on a dark scrim. --jlbs-accent is not enough
   here either: measured against the worst pixel under the glyphs it reaches only ~4.25:1. */
.hero .section__eyebrow {
    color: var(--jlbs-accent-on-dark);
}

/* --- Hero ---------------------------------------------------------------- */

/* A real JLBS project. The gradient underneath is the paint-time background and the
   fallback if the image fails, so text is never left on bare white. */
.hero {
    position: relative;
    background: linear-gradient(160deg, var(--jlbs-slate) 0%, var(--jlbs-ink) 70%);
    color: rgba(255, 255, 255, 0.9);
    overflow: hidden;
}

.hero__media {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* The photograph is decorative here - the h1 carries the meaning - but it must not
       wash out the copy, hence the scrim below. */
    opacity: 0.78;
}

.hero::after {
    content: "";
    position: absolute;
    inset: 0;
    /* Scrim: keeps the headline above 4.5:1 contrast wherever the photo happens to be light. */
    background: linear-gradient(
        100deg,
        rgba(21, 22, 22, 0.88) 0%,
        rgba(21, 22, 22, 0.62) 48%,
        rgba(21, 22, 22, 0.30) 100%
    );
    pointer-events: none;
}

.hero > .container {
    position: relative;
    z-index: 1;
    padding-block: clamp(3rem, 2rem + 6vw, 6.5rem);
}

.hero h1 {
    color: #fff;
    max-width: 18ch;
}

.hero__lead {
    color: rgba(255, 255, 255, 0.82);
    max-width: 52ch;
}

.hero__points {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    padding: 0;
    margin: 0;
    list-style: none;
}

.hero__points li {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-weight: 600;
    color: #fff;
}

.hero__points .icon {
    color: var(--jlbs-accent);
}

/* --- Cards --------------------------------------------------------------- */

.tile {
    background: #fff;
    border: 1px solid var(--jlbs-border);
    border-radius: var(--bs-border-radius);
    padding: 1.75rem;
    transition: border-color 0.15s ease, transform 0.15s ease;
}

a.tile {
    display: block;
    text-decoration: none;
    color: inherit;
}

a.tile:hover,
a.tile:focus-visible {
    border-color: var(--jlbs-accent);
    transform: translateY(-2px);
}

.tile__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: 0.6rem;
    background: rgba(242, 126, 0, 0.13);
    color: var(--jlbs-slate);
    margin-bottom: 1rem;
}

.tile__icon .icon {
    width: 1.6rem;
    height: 1.6rem;
}

.tile__more {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-weight: 600;
    color: var(--jlbs-slate);
}

.checklist {
    list-style: none;
    padding: 0;
    margin: 0;
}

.checklist li {
    display: flex;
    gap: 0.65rem;
    align-items: flex-start;
    padding-block: 0.35rem;
}

.checklist .icon {
    color: var(--jlbs-accent-text);
    margin-top: 0.35em;
}

/* --- Service detail blocks ----------------------------------------------- */

.service-block + .service-block {
    margin-top: clamp(2.5rem, 2rem + 2vw, 4rem);
    padding-top: clamp(2.5rem, 2rem + 2vw, 4rem);
    border-top: 1px solid var(--jlbs-border);
}

/* --- Projects ------------------------------------------------------------ */

.project-card {
    border: 1px solid var(--jlbs-border);
    border-radius: var(--bs-border-radius);
    overflow: hidden;
    background: #fff;
    height: 100%;
    display: flex;
    flex-direction: column;
}

.project-card__media {
    position: relative;
    margin: 0;
    background: var(--jlbs-surface);
}

.project-card__media img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 2;
    object-fit: cover;
}

.project-card__body {
    padding: 1.25rem 1.5rem 1.5rem;
    flex: 1 1 auto;
}

.project-card__meta {
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 700;
    color: var(--jlbs-accent-text);
}

/* Impossible to miss in review, so sample data cannot quietly reach production. */
.placeholder-flag {
    position: absolute;
    inset-inline: 0;
    top: 0;
    background: #b42318;
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 0.35rem 0.75rem;
    text-align: center;
}

/* --- Calls to action ----------------------------------------------------- */

.cta-band {
    background: var(--jlbs-ink);
    color: rgba(255, 255, 255, 0.82);
}

.cta-band h2 {
    color: #fff;
}

.cta-band__phone {
    color: var(--jlbs-accent);
    font-weight: 700;
    font-size: 1.5rem;
    text-decoration: none;
    white-space: nowrap;
}

.cta-band__phone:hover,
.cta-band__phone:focus {
    color: #fff;
}

/* --- Forms --------------------------------------------------------------- */

.form-panel {
    background: #fff;
    border: 1px solid var(--jlbs-border);
    border-radius: var(--bs-border-radius);
    padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
}

.form-label {
    font-weight: 600;
    color: var(--jlbs-ink);
}

/* Added by RequiredMarkTagHelper. The accent text colour already used for small labels, so it
   reads as a marker rather than part of the label, and passes contrast on white. */
.required-mark {
    margin-left: 0.15em;
    color: var(--jlbs-accent-text);
    font-weight: 700;
}

.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    border-color: var(--jlbs-accent);
    box-shadow: 0 0 0 0.2rem rgba(242, 126, 0, 0.25);
}

.form-check-input:checked {
    background-color: var(--jlbs-slate);
    border-color: var(--jlbs-slate);
}

.field-validation-error {
    display: block;
    margin-top: 0.35rem;
    font-size: 0.9rem;
    color: #b42318;
}

.input-validation-error {
    border-color: #b42318;
}

.validation-summary-errors {
    background: #fdf2f1;
    border: 1px solid #f3c6c2;
    border-radius: var(--bs-border-radius);
    padding: 1rem 1.25rem;
    color: #8a2019;
}

.validation-summary-errors ul {
    margin: 0;
    padding-left: 1.1rem;
}

.validation-summary-valid {
    display: none;
}

/* The honeypot must be unreachable by people and by assistive technology, but still
   present in the DOM and fillable by a bot. display:none is skipped by some bots, so
   it is moved off-screen instead. */
.hp-field {
    position: absolute !important;
    left: -9999px !important;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* --- Footer -------------------------------------------------------------- */

.site-footer {
    background: var(--jlbs-ink);
    color: rgba(255, 255, 255, 0.72);
    padding-block: 3rem 2rem;
    font-size: 0.95rem;
}

.site-footer__brand {
    font-size: 1.25rem;
    font-weight: 700;
    color: #fff;
    margin-bottom: 0.5rem;
}

.site-footer__strapline {
    max-width: 38ch;
    margin: 0;
}

.site-footer__heading {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--jlbs-accent);
    margin-bottom: 0.85rem;
}

.site-footer__list li {
    padding-block: 0.2rem;
}

/* Bootstrap's muted text utilities assume a light background and fail contrast here. */
.site-footer__muted {
    color: rgba(255, 255, 255, 0.72);
}

.site-footer a {
    color: rgba(255, 255, 255, 0.85);
    text-decoration: none;
}

.site-footer a:hover,
.site-footer a:focus {
    color: var(--jlbs-accent);
    text-decoration: underline;
}

.site-footer__rule {
    border-color: rgba(255, 255, 255, 0.15);
    margin-block: 2rem 1.25rem;
    opacity: 1;
}

.site-footer__legal {
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.6);
}

/* --- Mobile call bar ----------------------------------------------------- */

.mobile-call-bar {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 1030;
    display: flex;
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.18);
}

.mobile-call-bar__item {
    flex: 1 1 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    padding: 0.9rem 0.5rem;
    font-weight: 700;
    text-decoration: none;
    font-size: 0.95rem;
}

.mobile-call-bar__item--call {
    background: var(--jlbs-accent);
    color: var(--jlbs-ink);
}

.mobile-call-bar__item--quote {
    background: var(--jlbs-ink);
    color: #fff;
}

/* --- Breadcrumbs --------------------------------------------------------- */

.breadcrumb {
    --bs-breadcrumb-divider-color: var(--jlbs-muted);
    --bs-breadcrumb-item-active-color: var(--jlbs-muted);
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.breadcrumb a {
    text-decoration: none;
}

/* --- Accordion ----------------------------------------------------------- */

.accordion {
    --bs-accordion-btn-focus-box-shadow: 0 0 0 0.2rem rgba(242, 126, 0, 0.25);
    --bs-accordion-active-bg: rgba(242, 126, 0, 0.1);
    --bs-accordion-active-color: var(--jlbs-ink);
    --bs-accordion-border-color: var(--jlbs-border);
}

.accordion-button {
    font-weight: 600;
}

/* --- Prose --------------------------------------------------------------- */

.prose p {
    max-width: 68ch;
}

.prose > :last-child {
    margin-bottom: 0;
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        transition-duration: 0.001ms !important;
    }
}

/* --- Brand mark ----------------------------------------------------------- */

.navbar-brand__logo {
    height: 46px;
    width: auto;
    display: block;
}

@media (max-width: 575.98px) {
    .navbar-brand__logo { height: 38px; }
}

.site-footer__logo {
    height: 54px;
    width: auto;
    margin-bottom: 1rem;
}

/* --- Accreditation badges ------------------------------------------------- */

.accreditations {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
    align-items: center;
    list-style: none;
    padding: 0;
    margin: 0;
}

.accreditation {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.accreditation img {
    /* Held small on purpose: the supplied artwork is low resolution. Replace with official
       badge files from the NAPIT and Gas Safe member portals before scaling this up. */
    height: 46px;
    width: auto;
    background: #fff;
    border-radius: 4px;
    padding: 2px;
}

.accreditation__text {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
    font-size: 0.8rem;
}

.accreditation__text strong { color: #fff; }
.accreditation__text span { color: rgba(255, 255, 255, 0.65); }

/* --- Before / after comparison -------------------------------------------- */

.compare {
    position: relative;
    overflow: hidden;
    border-radius: var(--bs-border-radius);
    touch-action: none;
    user-select: none;
    cursor: ew-resize;
    aspect-ratio: 4 / 3;
    background: var(--jlbs-surface);
}

@media (min-width: 768px) {
    .compare { aspect-ratio: 16 / 9; }
}

.compare__img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Clipped rather than resized: the "before" layer stays exactly the same size as the
   "after" layer underneath, so the two never drift out of register as the handle moves.
   A percentage width on the wrapper would squash the image instead of revealing it. */
.compare__before {
    position: absolute;
    inset: 0;
    clip-path: inset(0 50% 0 0);
}

.compare__handle {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 3px;
    background: #fff;
    transform: translateX(-1.5px);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
    pointer-events: none;
}

.compare__handle::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 2.75rem;
    height: 2.75rem;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23151616' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 6-5 6 5 6'/%3E%3Cpath d='m15 6 5 6-5 6'/%3E%3C/svg%3E") center / 1.4rem no-repeat;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}

/* The range input is the accessible control: invisible, but focusable and keyboard-operable. */
.compare__range {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: ew-resize;
}

.compare__range:focus-visible {
    opacity: 1;
    outline: 3px solid var(--jlbs-accent);
    outline-offset: -3px;
}

.compare__tag {
    position: absolute;
    bottom: 1rem;
    padding: 0.3rem 0.75rem;
    background: rgba(21, 22, 22, 0.75);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    border-radius: 999px;
    pointer-events: none;
}

.compare__tag--before { left: 1rem; }
.compare__tag--after { right: 1rem; }

/* --- Gallery -------------------------------------------------------------- */

.gallery-filter {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 1.75rem;
}

.gallery-filter .btn.active {
    background: var(--jlbs-ink);
    border-color: var(--jlbs-ink);
    color: #fff;
}

.project-card__link {
    display: block;
    overflow: hidden;
    background: var(--jlbs-surface);
}

.project-card__link img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    transition: transform 0.25s ease;
}

.project-card__link:hover img,
.project-card__link:focus-visible img {
    transform: scale(1.04);
}

.project-card__body {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.85rem 1rem 1rem;
}

.project-card__title {
    font-weight: 600;
    color: var(--jlbs-ink);
    font-size: 0.975rem;
    line-height: 1.3;
}

/* The category label on /projects links to its service page. Same look as the plain label, so the
   grid reads the same; the underline on hover and focus is what says it goes somewhere. Shrunk to
   its text so the click target is the word, not the whole card width. */
a.project-card__meta {
    align-self: flex-start;
    text-decoration: none;
}

a.project-card__meta:hover,
a.project-card__meta:focus-visible {
    text-decoration: underline;
}

/* --- Lightbox ------------------------------------------------------------- */

/* The controls sit off the photograph, never on it: the dialog's padding reserves room for them.
   Phones (below 768px) have no spare width beside a 96vw photo, so there the close button gets a
   strip above it and previous/next go below it, either side of the caption, where a thumb
   reaches them anyway. From 768px up, both sides get a 3.25rem column (a 2.75rem button and a
   0.5rem gap), and the close button sits diagonally off the photograph's top-right corner. border-box sizing means the
   padding comes out of the photograph's width, so it can never grow into the columns. */
.lightbox {
    /* The space the photograph may fill. site.js sizes the image from these and the photo's own
       dimensions before it has loaded, so the dialog never collapses while it downloads. */
    --lb-max-w: min(96vw, 1400px);
    --lb-max-h: min(82vh, calc(94vh - 7.5rem));
    border: 0;
    padding: 3.25rem 0 0;
    max-width: min(96vw, 1400px);
    max-height: 94vh;
    background: transparent;
    overflow: visible;
}

.lightbox::backdrop {
    background: rgba(10, 11, 11, 0.9);
}

/* Sized up front from the photograph's ratio and natural width (set by site.js from the card's
   data-width/height): as wide as fits, without exceeding the height limit or upscaling. Without
   those values it falls back to the old auto sizing. object-fit stops a wrong ratio in
   projects.json from distorting the photo; it would only letterbox it. */
.lightbox__img {
    display: block;
    width: min(var(--lb-max-w), calc(var(--lb-max-h) * var(--lb-ratio)), var(--lb-natural-w));
    height: auto;
    max-width: 100%;
    max-height: var(--lb-max-h);
    aspect-ratio: var(--lb-ratio, auto);
    object-fit: contain;
    border-radius: var(--bs-border-radius);
    background: var(--jlbs-ink);
    transition: opacity 0.15s ease;
}

/* While a photograph downloads - on opening, and on every step - the previous one is hidden, so
   the old photo never sits under the new caption. The frame keeps the new photo's shape in the
   site's ink colour, and a spinner appears only if loading takes longer than 0.3s, so a fast
   connection sees nothing but the photo. */
/* Hidden instantly - no fade out - so there is no moment where the old photo and the new caption
   are both on screen. Only the reveal fades. */
.lightbox--loading .lightbox__img {
    opacity: 0;
    transition: none;
}

.lightbox--loading .lightbox__frame::after {
    content: "";
    position: absolute;
    inset: 0;
    width: 2.5rem;
    height: 2.5rem;
    margin: auto;
    border: 3px solid rgba(255, 255, 255, 0.25);
    border-top-color: #fff;
    border-radius: 50%;
    opacity: 0;
    animation: lightbox-spin 0.8s linear infinite, lightbox-reveal 0s linear 0.3s forwards;
}

@keyframes lightbox-spin {
    to { transform: rotate(1turn); }
}

@keyframes lightbox-reveal {
    to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .lightbox--loading .lightbox__frame::after {
        animation: lightbox-reveal 0s linear 0.3s forwards;
    }
}

/* Same height as the step buttons, so on phones they sit level with the text between them. */
.lightbox__caption {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    margin: 0.75rem 0 0;
    padding-inline: 3.5rem;
    color: #fff;
    text-align: center;
    font-size: 0.95rem;
}

/* Centres an SVG rather than a text glyph. The "×" character that was here measured 3.5px below
   the button's centre in a 40px button: its ink sits on the font's math axis, so no font-size or
   line-height value moves it, and flex centring the line box does not either. */
.lightbox__close {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: #fff;
    color: var(--jlbs-ink);
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
}

.lightbox__close .icon,
.lightbox__step .icon {
    width: 1.25rem;
    height: 1.25rem;
    /* .icon's baseline nudge is for icons sitting beside text; inside a flex box it would
       reintroduce exactly the offset this change removes. */
    vertical-align: baseline;
}

.lightbox__frame {
    position: relative;
    border-radius: var(--bs-border-radius);
    background: var(--jlbs-ink);
}

/* Previous and next. Same white disc as the close button. Positioned against the photograph's
   frame, so on phones they drop into the caption row whatever the photograph's height. */
.lightbox__step {
    position: absolute;
    top: calc(100% + 0.75rem);
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: #fff;
    color: var(--jlbs-ink);
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
}

.lightbox__step--prev {
    left: 0;
}

.lightbox__step--next {
    right: 0;
}

/* One photograph showing (a category with a single project): nothing to step to. */
.lightbox__step[hidden] {
    display: none;
}

@media (min-width: 768px) {
    /* 2.5rem on top holds the close button diagonally off the photograph's corner (below).
       The 0.25rem underneath roughly balances it against the caption, so the photograph stays
       close to the middle of the screen. */
    .lightbox {
        --lb-max-w: calc(min(96vw, 1400px) - 6.5rem);
        /* The calc only bites on short screens, such as a phone on its side. */
        --lb-max-h: min(82vh, calc(94vh - 6.5rem));
        padding: 2.5rem 3.25rem 0.25rem;
    }

    .lightbox__caption {
        display: block;
        min-height: 0;
        margin-top: 0.85rem;
        padding-inline: 0;
    }

    /* Diagonally off the photograph's top-right corner, with its box flush to the lines of the
       photograph's side and top edge. Its nearest point is then about 8px from the corner, the
       same as the arrows' gap to the photograph. It is judged against the corner, and centring
       it over the next button's column instead left it about 23px away and looking detached. */
    .lightbox__close {
        top: 0;
        right: 0.75rem;
    }

    .lightbox__step {
        top: 50%;
        transform: translateY(-50%);
    }

    .lightbox__step--prev {
        left: -3.25rem;
    }

    .lightbox__step--next {
        right: -3.25rem;
    }
}
