/* ============================================================
   LIBERTY DIGITIZING — MAIN-SITE SHELL & MARKETING LAYER
   Consumes tokens from brand.css. Everything is namespaced
   (lb-*) so it never leaks into admin/designer/partner.
   Rendered by include/header.php + include/footer.php.
   ============================================================ */

:root {
    --lb-gold-light: #f4e4bc;
    --lb-serif: 'Playfair Display', Georgia, serif;
    --lb-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --lb-topbar-h: 76px;
}

/* ---------- Page canvas (marketing pages set <body class="lb-page">) ---------- */
body.lb-page {
    background:
        radial-gradient(ellipse 60% 40% at 85% -5%, rgba(212, 175, 55, .07), transparent),
        radial-gradient(ellipse 50% 35% at 0% 30%, rgba(212, 175, 55, .04), transparent),
        var(--brand-bg);
    color: var(--brand-text);
    font-family: var(--lb-sans);
    line-height: 1.65;
    overflow-x: hidden;
}
.lb-page img { max-width: 100%; }

/* ---------- Announcement strip ---------- */
.lb-announce {
    position: relative;
    z-index: 1002;
    overflow: hidden;
    background: linear-gradient(90deg, rgba(212, 175, 55, .16), rgba(212, 175, 55, .05) 40%, rgba(212, 175, 55, .16));
    border-bottom: 1px solid rgba(212, 175, 55, .25);
    font-size: .78rem;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.lb-announce.hidden { display: none; }
.lb-announce-track {
    display: flex;
    justify-content: center;
    gap: 3rem;
    padding: .45rem 3rem;
    white-space: nowrap;
    animation: lb-ticker 36s linear infinite;
    color: var(--lb-gold-light);
}
.lb-announce-item { flex: none; }
@keyframes lb-ticker {
    0% { transform: translateX(0); }
    100% { transform: translateX(-50%); }
}
.lb-announce-close {
    position: absolute;
    top: 50%;
    right: .9rem;
    transform: translateY(-50%);
    background: transparent;
    border: 0;
    color: var(--lb-gold-light);
    cursor: pointer;
    font-size: .85rem;
    padding: .3rem;
    line-height: 1;
    opacity: .8;
}
.lb-announce-close:hover { opacity: 1; }

/* ---------- Sticky translucent topbar ---------- */
.lb-topbar {
    position: sticky;
    top: 0;
    z-index: 1000;
    width: 100%;
    background: rgba(10, 10, 10, .72);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    border-bottom: 1px solid rgba(212, 175, 55, .18);
    transition: box-shadow .25s ease, background .25s ease;
}
.lb-topbar.scrolled,
.lb-topbar.announcement-hidden {
    background: rgba(10, 10, 10, .92);
    box-shadow: 0 12px 34px rgba(0, 0, 0, .45);
}
.lb-topbar-inner {
    max-width: 1280px;
    margin: 0 auto;
    padding: 0 1.5rem;
    height: var(--lb-topbar-h);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
}
.lb-logo { display: inline-flex; align-items: center; flex: none; }
.lb-logo img { height: 46px; width: auto; display: block; }

.lb-nav { display: flex; align-items: center; gap: .35rem; }
.lb-nav-link {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .55rem .8rem;
    border-radius: 8px;
    color: var(--brand-text);
    text-decoration: none;
    font-size: .86rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    transition: color .18s ease, background .18s ease;
    white-space: nowrap;
}
.lb-nav-link i { font-size: .62rem; opacity: .7; transition: transform .18s ease; }
.lb-nav-link::after {
    content: '';
    position: absolute;
    left: .8rem;
    right: .8rem;
    bottom: .28rem;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(90deg, var(--brand-gold), var(--lb-gold-light));
    transform: scaleX(0);
    transform-origin: left;
    transition: transform .22s ease;
}
.lb-nav-link:hover { color: var(--brand-gold); }
.lb-nav-link:hover::after { transform: scaleX(1); }

.lb-nav-drop { position: relative; }
.lb-nav-drop:hover > .lb-nav-link i,
.lb-nav-drop:focus-within > .lb-nav-link i { transform: rotate(180deg); }
.lb-drop-panel {
    position: absolute;
    top: calc(100% + 10px);
    left: 50%;
    transform: translate(-50%, 8px);
    min-width: 240px;
    background: rgba(17, 17, 17, .97);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border: 1px solid rgba(212, 175, 55, .22);
    border-radius: 14px;
    box-shadow: 0 26px 54px rgba(0, 0, 0, .55);
    padding: .6rem;
    opacity: 0;
    visibility: hidden;
    transition: opacity .2s ease, transform .2s ease, visibility .2s;
}
.lb-nav-drop:hover .lb-drop-panel,
.lb-nav-drop:focus-within .lb-drop-panel {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, 0);
}
.lb-drop-panel a {
    display: block;
    padding: .55rem .85rem;
    border-radius: 9px;
    color: var(--brand-text);
    text-decoration: none;
    font-size: .88rem;
    transition: background .15s ease, color .15s ease, padding-left .15s ease;
}
.lb-drop-panel a:hover {
    background: rgba(212, 175, 55, .1);
    color: var(--brand-gold);
    padding-left: 1.05rem;
}

/* ---------- Topbar actions ---------- */
.lb-actions { display: flex; align-items: center; gap: .6rem; flex: none; }
.lb-action-link {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .5rem .85rem;
    border-radius: 999px;
    font-size: .82rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--brand-text);
    border: 1px solid var(--brand-border-strong);
    white-space: nowrap;
    transition: border-color .18s ease, color .18s ease, background .18s ease;
}
.lb-action-link:hover { border-color: var(--brand-gold); color: var(--brand-gold); }
.lb-action-cta {
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    color: #0a0a0a !important;
    border-color: transparent;
    box-shadow: 0 6px 18px rgba(212, 175, 55, .25);
}
.lb-action-cta:hover { background: linear-gradient(145deg, var(--lb-gold-light), var(--brand-gold)); color: #0a0a0a !important; }

/* Cart indicator */
.lb-cart-wrap { position: relative; }
.lb-cart-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    border: 1px solid var(--brand-border-strong);
    background: rgba(255, 255, 255, .04);
    color: var(--brand-text);
    cursor: pointer;
    font-size: 1rem;
    transition: border-color .18s ease, color .18s ease;
}
.lb-cart-btn:hover { border-color: var(--brand-gold); color: var(--brand-gold); }
.lb-cart-badge {
    position: absolute;
    top: -5px;
    right: -5px;
    min-width: 19px;
    height: 19px;
    padding: 0 5px;
    border-radius: 999px;
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    color: #0a0a0a;
    font-size: .68rem;
    font-weight: 800;
    display: flex;
    align-items: center;
    justify-content: center;
}
#cartDropdown {
    display: none;
    position: absolute;
    top: calc(100% + 14px);
    right: 0;
    width: 340px;
    max-width: 88vw;
    background: rgba(17, 17, 17, .98);
    border: 1px solid rgba(212, 175, 55, .22);
    border-radius: 14px;
    box-shadow: 0 26px 54px rgba(0, 0, 0, .6);
    overflow: hidden;
    z-index: 1200;
}
#cartDropdown.active { display: block; }
.lb-cart-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .85rem 1rem;
    font-weight: 700;
    font-size: .85rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--brand-gold);
    border-bottom: 1px solid var(--brand-border);
    background: rgba(212, 175, 55, .06);
}
.lb-cart-empty { padding: 1.6rem 1rem; text-align: center; color: var(--brand-text-muted); font-size: .88rem; }
.lb-cart-items { max-height: 300px; overflow-y: auto; }
.lb-cart-item {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .75rem 1rem;
    border-bottom: 1px solid var(--brand-border);
}
.lb-cart-item img { width: 46px; height: 46px; object-fit: cover; border-radius: 8px; border: 1px solid var(--brand-border-strong); }
.lb-cart-item-name { margin: 0 0 .15rem; font-size: .84rem; font-weight: 600; color: var(--brand-text); }
.lb-cart-item-meta { font-size: .74rem; color: var(--brand-text-muted); }
.lb-cart-remove {
    margin-left: auto;
    background: transparent;
    border: 1px solid var(--brand-border-strong);
    color: var(--brand-text-muted);
    border-radius: 8px;
    width: 28px;
    height: 28px;
    cursor: pointer;
    font-size: .75rem;
    transition: all .15s ease;
}
.lb-cart-remove:hover { border-color: var(--brand-danger); color: var(--brand-danger); }
.lb-cart-foot {
    padding: .85rem 1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    font-size: .8rem;
    color: var(--brand-text-muted);
}
.lb-cart-foot-actions { padding: 0 1rem 1rem; display: flex; gap: .5rem; }

/* Hamburger */
.lb-burger {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 42px;
    height: 42px;
    padding: 0 .55rem;
    background: transparent;
    border: 1px solid var(--brand-border-strong);
    border-radius: 10px;
    cursor: pointer;
}
.hamburger-line {
    display: block;
    height: 2px;
    width: 100%;
    border-radius: 2px;
    background: var(--brand-gold);
    transition: transform .25s ease, opacity .25s ease;
}

/* ---------- Mobile drawer ---------- */
#mobileOverlay.lb-mobile-overlay {
    position: fixed;
    inset: 0;
    z-index: 1400;
    background: rgba(0, 0, 0, .6);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    opacity: 0;
    visibility: hidden;
    transition: opacity .25s ease, visibility .25s;
}
#mobileOverlay.lb-mobile-overlay.active { opacity: 1; visibility: visible; }
#mobileMenu.lb-mobile-menu {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1500;
    width: min(360px, 88vw);
    background: #101010;
    border-left: 1px solid rgba(212, 175, 55, .2);
    transform: translateX(102%);
    /* visibility, not just transform. Translated off-canvas, this drawer stayed
       visibility:visible, so its 36 links and buttons remained in the tab order
       and the accessibility tree while closed: tabbing through index.php at
       390px put focus inside the shut menu for 14 stops out of the first 60,
       with no focus ring anywhere on screen. visibility:hidden removes
       descendants from both, and is what #mobileOverlay directly above already
       does -- this rule just never picked up the pattern. Transitioning
       visibility alongside transform keeps the drawer painted for the whole
       close animation instead of blinking out at frame one. Chosen over the
       inert attribute deliberately: inert needs a recent browser, this does
       not, and production is older than dev. */
    visibility: hidden;
    transition: transform .3s cubic-bezier(.22, .8, .36, 1), visibility .3s;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
}
#mobileMenu.lb-mobile-menu.active { transform: translateX(0); visibility: visible; }
.lb-mobile-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.1rem 1.25rem;
    border-bottom: 1px solid var(--brand-border);
}
.lb-mobile-head img { height: 40px; }
.lb-mobile-close {
    background: transparent;
    border: 1px solid var(--brand-border-strong);
    border-radius: 10px;
    color: var(--brand-text);
    width: 38px;
    height: 38px;
    cursor: pointer;
    font-size: 1rem;
}
.lb-mobile-body { padding: 1rem 1.25rem 2rem; display: flex; flex-direction: column; gap: .25rem; }
.lb-mobile-link {
    display: block;
    padding: .7rem .5rem;
    color: var(--brand-text);
    text-decoration: none;
    font-weight: 600;
    font-size: .95rem;
    border-radius: 8px;
}
.lb-mobile-link:hover { color: var(--brand-gold); background: rgba(212, 175, 55, .06); }
.lb-mobile-group-btn {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: transparent;
    border: 0;
    color: var(--brand-text);
    font: inherit;
    font-weight: 600;
    font-size: .95rem;
    padding: .7rem .5rem;
    cursor: pointer;
    border-radius: 8px;
    text-align: left;
}
.lb-mobile-sub { display: none; padding: 0 0 .4rem .9rem; }
.lb-mobile-sub.active { display: block; }
.lb-mobile-sub a {
    display: block;
    padding: .45rem .5rem;
    color: var(--brand-text-muted);
    text-decoration: none;
    font-size: .88rem;
    border-left: 1px solid rgba(212, 175, 55, .25);
}
.lb-mobile-sub a:hover { color: var(--brand-gold); }
.lb-mobile-ctas { margin-top: 1.1rem; display: flex; flex-direction: column; gap: .6rem; }

/* Universal popup overlay (service / portfolio quick views) */
.lb-popup-overlay {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(5, 5, 5, .82);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    padding: 1.25rem;
}
.lb-popup-box {
    position: relative;
    width: 100%;
    max-width: 620px;
    max-height: 86vh;
    overflow-y: auto;
    background: var(--brand-surface);
    border: 1px solid rgba(212, 175, 55, .3);
    border-radius: 18px;
    box-shadow: 0 40px 80px rgba(0, 0, 0, .6);
    padding: 1.75rem;
}
.lb-popup-close {
    position: absolute;
    top: .9rem;
    right: .9rem;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid rgba(212, 175, 55, .4);
    background: rgba(212, 175, 55, .1);
    color: var(--brand-gold);
    cursor: pointer;
    font-size: .9rem;
    z-index: 2;
}
.lb-popup-box h3 { font-family: var(--lb-serif); color: var(--brand-gold); margin: 0 0 .75rem; font-size: 1.35rem; }
.lb-popup-box img { border-radius: 12px; margin-bottom: 1rem; }
.lb-popup-box p { color: var(--brand-text-muted); margin: 0 0 .6rem; }

/* Product modal (legacy hook kept for product landing pages) */
#productModal.lb-product-modal {
    position: fixed;
    inset: 0;
    z-index: 1900;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(5, 5, 5, .82);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    padding: 1.25rem;
}
#productModal.lb-product-modal.active { display: flex; }
#productModal .lb-product-dialog {
    position: relative;
    width: 100%;
    max-width: 560px;
    max-height: 88vh;
    overflow-y: auto;
    background: var(--brand-surface);
    border: 1px solid rgba(212, 175, 55, .3);
    border-radius: 18px;
    box-shadow: 0 40px 80px rgba(0, 0, 0, .6);
    padding: 1.75rem;
}
#productModal .lb-product-close {
    position: absolute;
    top: .9rem;
    right: .9rem;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid rgba(212, 175, 55, .4);
    background: rgba(212, 175, 55, .1);
    color: var(--brand-gold);
    cursor: pointer;
    font-size: .9rem;
}

/* ---------- Shared layout primitives ---------- */
.lb-container { width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 1.5rem; }
.lb-service-jump { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: .45rem; padding: .7rem 1rem; background: rgba(10,10,10,.92); border-bottom: 1px solid rgba(212,175,55,.2); backdrop-filter: blur(12px); }
.lb-service-jump span { color: var(--brand-text-muted); font-size: .72rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; margin-right: .25rem; }
.lb-service-jump a { color: var(--brand-text); border: 1px solid rgba(255,255,255,.12); border-radius: 999px; padding: .42rem .72rem; font-size: .78rem; text-decoration: none; transition: .2s ease; }
.lb-service-jump a:hover, .lb-service-jump a:focus-visible { color: #0a0a0a; background: var(--brand-gold); border-color: var(--brand-gold); }
.lb-service-jump a.is-cta { color: #0a0a0a; background: var(--brand-gold); border-color: var(--brand-gold); font-weight: 800; }
.lb-pricing-note { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: .8rem 1.2rem; margin: 1.5rem auto 0; max-width: 900px; padding: 1rem 1.2rem; }
.lb-pricing-note strong { color: var(--brand-gold); }
.lb-pricing-note span { color: var(--brand-text-muted); font-size: .88rem; }
.lb-section { padding: 5.5rem 0; position: relative; }
.lb-section--tight { padding: 3.5rem 0; }
.lb-section--raised { background: linear-gradient(180deg, rgba(255, 255, 255, .02), transparent); border-top: 1px solid var(--brand-border); border-bottom: 1px solid var(--brand-border); }

.lb-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: .55rem;
    font-size: .74rem;
    font-weight: 700;
    letter-spacing: .28em;
    text-transform: uppercase;
    color: var(--brand-gold);
    margin-bottom: 1rem;
}
.lb-eyebrow::before { content: ''; width: 34px; height: 1px; background: linear-gradient(90deg, transparent, var(--brand-gold)); }
.lb-eyebrow::after { content: ''; width: 34px; height: 1px; background: linear-gradient(90deg, var(--brand-gold), transparent); }
.lb-eyebrow--left::before { display: none; }

.lb-title {
    font-family: var(--lb-serif);
    font-size: clamp(1.9rem, 4vw, 2.9rem);
    font-weight: 700;
    line-height: 1.15;
    margin: 0 0 1rem;
    color: var(--brand-text);
}
.lb-sub { color: var(--brand-text-muted); max-width: 640px; margin: 0 auto; font-size: 1.02rem; }
.lb-gold { color: var(--brand-gold); }
.lb-center { text-align: center; }
.lb-head { margin-bottom: 3rem; }

/* Buttons */
.lb-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .55rem;
    padding: .85rem 1.7rem;
    border-radius: 999px;
    font-weight: 700;
    font-size: .92rem;
    letter-spacing: .03em;
    text-decoration: none;
    border: 1px solid transparent;
    cursor: pointer;
    transition: transform .18s ease, box-shadow .18s ease, background .18s ease, border-color .18s ease, color .18s ease;
}
.lb-btn:hover { transform: translateY(-2px); }
.lb-btn--gold {
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    color: #0a0a0a !important;
    box-shadow: 0 10px 26px rgba(212, 175, 55, .28);
}
.lb-btn--gold:hover { background: linear-gradient(145deg, var(--lb-gold-light), var(--brand-gold)); box-shadow: 0 14px 32px rgba(212, 175, 55, .38); }
.lb-btn--ghost {
    background: rgba(255, 255, 255, .04);
    border-color: rgba(212, 175, 55, .45);
    color: var(--brand-gold) !important;
}
.lb-btn--ghost:hover { background: rgba(212, 175, 55, .12); }
.lb-btn--sm { padding: .55rem 1.15rem; font-size: .82rem; }

/* Orphan 9. .lb-btn--block sits on ten buttons across cart / contact / faqs /
   index (x4) / pricing (x2) and had ZERO rules in site.css, style.css and
   brand.css -- the only definition anywhere in the repo is a page-local <style>
   block at product-template.php:268, so the modifier worked on exactly one page.
   Measured before, at 1440 and 390: 12 of the 15 rendered instances sat at
   intrinsic width. index.php "Get Quote" was 155.5px inside a 616px form
   (460.5px short); contact.php's same button 155.5px inside 1095.6px; pricing's
   "Get Custom Quote" 213.8px inside 616px. The three that DID measure full width
   (the index.php plan CTAs) get it from .lb-pricing-grid .pricing-card being a
   column flex container that stretches its items -- not from this class.

   display:flex, not block: .lb-btn centres an icon against a label with
   inline-flex, and blockifying preserves that centring while making the outer
   display block-level. width:100% is still needed on top -- a <button> sizes to
   fit-content even when block-level. box-sizing is stated explicitly because
   site.css has no global border-box reset and .lb-btn carries 1.7rem of side
   padding plus a 1px border; under content-box the button would overrun its form
   by 56.4px. The product-template.php duplicate is left in place: it declares
   the same width, so it is now redundant rather than wrong. */
.lb-btn--block {
    display: flex;
    width: 100%;
    box-sizing: border-box;
}

/* Page head band (sub-pages) */
.lb-pagehead {
    position: relative;
    padding: 4.2rem 0 3.6rem;
    background:
        radial-gradient(ellipse 55% 90% at 80% 0%, rgba(212, 175, 55, .13), transparent),
        linear-gradient(180deg, #101010, var(--brand-bg));
    border-bottom: 1px solid rgba(212, 175, 55, .15);
}
.lb-pagehead h1 {
    font-family: var(--lb-serif);
    font-size: clamp(2rem, 4.5vw, 3rem);
    margin: 0 0 .7rem;
    line-height: 1.12;
}
.lb-crumbs { display: flex; align-items: center; gap: .6rem; font-size: .84rem; color: var(--brand-text-muted); margin-top: 1.4rem; }
.lb-crumbs a { color: var(--brand-gold); text-decoration: none; }
.lb-crumbs a:hover { text-decoration: underline; }

/* Stats band */
.lb-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 1.1rem;
}
.lb-stat {
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: 16px;
    padding: 1.6rem 1.4rem;
    text-align: center;
    transition: transform .2s ease, border-color .2s ease;
}
.lb-stat:hover { transform: translateY(-4px); border-color: rgba(212, 175, 55, .4); }
.lb-stat-icon {
    width: 52px;
    height: 52px;
    margin: 0 auto .9rem;
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.25rem;
    color: var(--brand-gold);
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .25);
}
.lb-stat-number { font-size: 1.9rem; font-weight: 800; color: var(--brand-gold); font-variant-numeric: tabular-nums; line-height: 1.1; }
.lb-stat-label { font-size: .76rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--brand-text-muted); margin-top: .35rem; }

/* ---------- Hero slider (index) ---------- */
#heroSlider.lb-hero { position: relative; height: min(88vh, 760px); min-height: 560px; overflow: hidden; background: #000; }
.hero-slide {
    position: absolute;
    inset: 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity 1s ease, visibility 1s;
}
.hero-slide.active { opacity: 1; visibility: visible; z-index: 2; }
.hero-slide-bg {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    transform: scale(1.08);
    transition: transform 7s ease-out;
    /* Declared here on purpose. style.css:1837 (the older hero generation) also
       sets z-index:1 on this element; keeping it explicit in the file that owns
       the current hero design makes the three-layer stack readable in one place:
       photo 1 -> shade 2 -> .slide-content 3. */
    z-index: 1;
}
.hero-slide.active .hero-slide-bg { transform: scale(1); }
.lb-hero-shade {
    position: absolute;
    inset: 0;
    /* z-index is load-bearing, not decoration. Without it this element computes
       z-index:auto and paints UNDERNEATH the photo, because .hero-slide-bg
       carries z-index:1 from style.css:1837. Measured 2026-08-25: with the
       shade as authored, deleting its background changed nothing at all - the
       sampled backdrop at x=8% stayed rgb(118,89,68) byte for byte, i.e. the
       scrim was completely inert on every slide since it was written. Lifting
       it above the photo takes that same pixel to rgb(25,20,16). All 21 hero
       contrast failures measured across the 7 slides trace back to this line
       being absent. Keep it below .slide-content (3) so text stays on top. */
    z-index: 2;
    /* Reshaped 2026-08-25 from `.88 0%, .55 45%, .25 100%`. The text column
       lands at 1.9%-50.3% of the hero at 1280px, and because .slide-content is
       a centred 1280px box the column's right edge stays near 50% at any wider
       viewport - so hold the scrim across that span, then fall away hard. Net
       effect at the sample points: 30% goes .66 -> .88 and 52% goes .51 -> .74
       (under the type), while 76% goes .38 -> .34 and 100% goes .25 -> .12
       (over open photo). The photography is MORE visible than before where
       nothing is written on it; the asymmetry the original author chose gets
       stronger, not flattened. */
    background:
        linear-gradient(90deg,
            rgba(5, 5, 5, .92) 0%,
            rgba(5, 5, 5, .88) 30%,
            rgba(5, 5, 5, .74) 52%,
            rgba(5, 5, 5, .34) 76%,
            rgba(5, 5, 5, .12) 100%),
        linear-gradient(0deg, var(--brand-bg) 2%, transparent 30%);
}
.slide-content {
    position: relative;
    z-index: 3;
    height: 100%;
    max-width: 1280px;
    margin: 0 auto;
    padding: 0 1.5rem;
    display: flex;
    align-items: center;
}
/* The older hero generation still sets `.slide-text { max-width:800px;
   text-align:center; margin:0 auto }` at style.css:1859, and site.css only ever
   contested max-width - so the column was being centred. That fought five
   separate decisions in this generation, all of which specify a left column:
   this 620px measure, .hero-description's 520px measure, the 90deg scrim
   weighted .88 at the LEFT edge, .lb-page-hero's gold radial parked at 82% 8%
   in the right-hand negative space, and .slide-content's default flex-start.
   Measured at 1280px: centring put the column at 25.8%-74.2% of the viewport,
   under scrim alpha .69->.39, where the design offers .88->.53. site.css loads
   after style.css (header.php:117-119), so equal specificity is enough here -
   no !important needed. */
.slide-text { max-width: 620px; text-align: left; margin: 0; }
.slide-text.active-animate { animation: lb-rise .8s ease both; }
.hero-kicker {
    display: inline-flex;
    align-items: center;
    gap: .6rem;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .3em;
    text-transform: uppercase;
    color: var(--lb-gold-light);
    border: 1px solid rgba(212, 175, 55, .4);
    background: rgba(212, 175, 55, .08);
    border-radius: 999px;
    padding: .45rem 1.1rem;
    margin-bottom: 1.4rem;
}
.hero-title {
    font-family: var(--lb-serif);
    font-size: clamp(2.4rem, 5.6vw, 4.2rem);
    font-weight: 800;
    line-height: 1.08;
    margin: 0 0 1.2rem;
    text-wrap: balance;
}
.gradient-text,
.text-highlight {
    background: linear-gradient(100deg, var(--lb-gold-light) 0%, var(--brand-gold) 55%, var(--brand-gold-dark) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}
.hero-description { color: rgba(245, 245, 245, .82); font-size: 1.08rem; max-width: 520px; margin: 0 0 2rem; }
/* justify/align are declared because style.css:1888 sets `justify-content:center`
   and style.css:3408 adds `align-items:center` under its mobile breakpoint. With
   the column left-aligned again those centred the buttons against flush-left
   type - measured 132px of indent at 1280px. flex-start on both axes keeps the
   buttons flush with the headline in row layout and in the mobile column stack.
   Both buttons are .lb-btn with identical padding and font-size, so dropping
   the default `stretch` costs nothing in equal-height terms. */
.hero-actions { display: flex; flex-wrap: wrap; gap: .9rem; justify-content: flex-start; align-items: flex-start; }

/* `transform` here is a fix, not noise. style.css:1895 (older generation) centred
   this CONTAINER with `top:50%; transform:translateY(-50%)` and let flex
   space-between place the arrows. This generation instead positions each arrow
   individually inside a full-height container, so the old transform had nothing
   left to do - but nothing cancelled it either. Measured 2026-08-25 at 1280x800:
   computed transform was matrix(1,0,0,1,0,-352), box top -239px, and both arrow
   centres sat at y=113 - the hero's top edge, 352px above centre.
   The container is also capped to the same 1280px centred measure as
   .slide-content so the arrows land on the content grid rather than the raw
   viewport edge: at 1600px the text gutter is 24px in from x=160, and the arrow
   gutter is 24px in from x=1440, so the two are symmetric. Written as explicit
   top/bottom/left/right instead of `inset` to avoid the over-constrained
   left+right+width case. */
.slider-navigation {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    right: auto;
    width: min(100%, 1280px);
    transform: translateX(-50%);
    pointer-events: none;
    z-index: 4;
}
/* Both arrows are paired on the RIGHT, not straddling the hero. Two measured
   constraints force this, and they rule out every other position:
   1. Once .slide-text was restored to a left column, a chevron at
      `left:1.4rem; top:50%` landed on the headline - on slide 7 squarely over
      the "S" of "Solutions". Side arrows only ever appeared to work because the
      column was mis-centred into the middle of the hero. So `prev` cannot sit
      on the left at all.
   2. Pairing them bottom-right instead was the first attempt, and it collided
      with the reCAPTCHA badge: measured 2026-08-25, the badge is a fixed
      256x60 block pinned to the viewport bottom-right (left edge x=1210 at
      1280px, x=1530 at 1600px, top y=826 on a 900px-tall viewport) and it
      overlapped `.slider-next` at all five desktop widths tested. The badge is
      not movable, so the whole bottom-right corner is unusable.
   What is left is the hero's right-middle - the one region the 90deg scrim
   deliberately keeps bright (alpha .34 at 76%, .12 at 100%) and the 620px text
   column can never reach. At 800px, the narrowest width where the arrows still
   render, the column's max-width edge is x=644 and the nearer arrow starts at
   x=678. Below 768px style.css:3425 hides the container entirely. */
.slider-arrow {
    pointer-events: auto;
    position: absolute;
    top: 50%;
    bottom: auto;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    border-radius: 50%;
    /* .75, not the site-wide .4 gold rim used in 17 other places. Every one of
       those sits on a solid dark card where a .4 rim reads cleanly against a
       uniform backdrop; this is the only control in the file floating over
       unpredictable photography. Measured 2026-08-25 across all 7 slides: the
       disc's own fill cannot delineate the control at all (1.01-1.36:1 against
       the photo) and it never could - against slide 1's lum-74 bokeh even a
       fully opaque black disc caps at 2.28:1 - so the boundary is what has to
       carry it. At .4 the peak border pixel reached only 1.66-2.24:1; .75 puts
       it over 3:1 at both photographic extremes, which is the ratio WCAG 1.4.11
       asks of a control's boundary. The chevron itself was never the problem
       (9.57:1 worst case). */
    border: 1px solid rgba(212, 175, 55, .75);
    background: rgba(10, 10, 10, .5);
    /* The halo earns its place on exactly one slide. Six of the seven hero
       photographs are near-black in the right third (lum 7-36), where the .75
       gold rim alone measures 3.67-5.17:1 and needs no help. Slide 1 is bright
       bokeh (lum 77) and measured 2.83:1 - the one case where the rim washes
       out. A soft dark halo darkens only the pixels immediately around the disc,
       so it lifts that case without touching the six that are already black. */
    box-shadow: 0 4px 20px rgba(0, 0, 0, .55);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    color: var(--lb-gold-light);
    cursor: pointer;
    font-size: 1rem;
    transition: background .2s ease, color .2s ease, border-color .2s ease;
}
/* The hover transform must repeat translateY(-50%). style.css:1921 sets
   `.slider-arrow:hover { transform: scale(1.1) }`, and `transform` is not
   additive - on hover that would replace the centring outright and drop the
   arrow half its own height. Composing both keeps the author's intended
   scale-up and stays centred. */
.slider-arrow:hover {
    background: var(--brand-gold);
    color: #0a0a0a;
    transform: translateY(-50%) scale(1.08);
}
.slider-prev { left: auto; right: calc(1.5rem + 44px + .6rem); }
.slider-next { left: auto; right: 1.5rem; }
.slider-dots {
    position: absolute;
    bottom: 1.8rem;
    /* NOT `left: 50%; transform: translateX(-50%)`, which is what both this file
       and style.css:1927 used to say. An absolutely positioned shrink-to-fit box
       with `left: 50%` and `right: auto` gets an available width of only
       (containing block - 50%), i.e. half the hero. The seven dots need
       7*34 + 6*9.6 = 295.6px, and style.css:1927 also sets `flex-wrap: wrap`, so
       below a 592px viewport the rail silently wrapped into TWO rows: measured
       2026-08-25 the rail was exactly 195px at 390px, 207px at 414px, 240px at
       480px, with adjacent-dot pitch running backwards (-109px) across the row
       break and the two rows only 14px apart vertically. That is every phone -
       and it is exactly the range where style.css:3425 hides the arrows, so the
       one navigation control left visible was the broken one.
       Spanning left:0/right:0 and centring with justify-content removes the cap
       entirely; the rail then fits on one row from 296px up. transform must be
       reset explicitly or style.css:1927's translateX(-50%) drags the now
       full-width rail half a viewport to the left. flex-wrap is deliberately left
       inherited as `wrap` - it is a harmless graceful fallback below 296px. */
    left: 0;
    right: 0;
    transform: none;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: .6rem;
    z-index: 4;
    /* Spanning the full width means this container's box now covers the whole
       hero at the dot rail's height, including the bottom-right corner the fixed
       reCAPTCHA badge occupies. The box is invisible but it would still capture
       pointer events across that entire band and could swallow clicks on anything
       beneath it. Same treatment as .slider-navigation above: the container is
       inert and only the buttons take pointer events. */
    pointer-events: none;
}
.slider-dot {
    width: 34px;
    height: 4px;
    border-radius: 4px;
    border: 0;
    background: rgba(255, 255, 255, .22);
    cursor: pointer;
    transition: background .25s ease;
    padding: 0;
    /* containing block for the ::after hit area below; pointer-events restored
       because the rail container above is deliberately inert */
    position: relative;
    pointer-events: auto;
}
/* A 34x4 bar is not a touch target. WCAG 2.5.8 asks for 24x24 CSS px, or a >=24px
   offset to every adjacent target; measured 2026-08-25 the bar was 4px tall and,
   once wrapped, only 14px from the row above, so it failed both the size rule and
   the spacing exception. Grow the HIT AREA only, with an invisible centred
   ::after - clicks on a pseudo-element are attributed to its parent, so the target
   becomes 34x24 while the element's own layout box stays 34x4. That matters:
   changing the button's height would move the bar and shift the rail's measured
   28.8px offset from the hero bottom. `left/right: 0` keeps the hit area inside
   the dot's own column, so neighbours 9.6px apart never overlap. */
.slider-dot::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    height: 24px;
}
.slider-dot.active { background: linear-gradient(90deg, var(--brand-gold), var(--lb-gold-light)); }
/* No breakpoint rule for the arrows is added here, deliberately. Two mechanisms
   already hide them on small screens: style.css:3425 sets the whole
   `.slider-navigation` container to display:none under @media(max-width:768px)
   — site.css never declares `display` on that container, so it applies — and
   site.css:1460 separately hides `.slider-arrow` at 560px, which is now
   unreachable behind the 768px container rule (cleanup candidate; left alone).
   768px is the right threshold for the right-middle pair: the arrows need the
   open right-hand third, and below ~800px the 620px text column runs into it.
   Navigation is not lost where the arrows are hidden — the slider binds touch
   swipe with a 50px threshold (assets/js/script.js:645-732 setupTouchEvents +
   handleSwipe), ArrowLeft/Right (script.js:639-641), and the dots stay tappable.
   This was asserted here before it was measured, and when it finally was, on
   2026-08-25, both mobile fallbacks were broken: the dot rail wrapped into two
   rows on every viewport under 592px (see the .slider-dots note above) and a tap
   with no movement fired a phantom swipe because `endX` was never reset per
   gesture (fixed at script.js:667 and :687). Both are fixed and re-measured; the
   claim now holds. */

/* ---------- Page hero (sub-pages) — mirrors index .lb-hero geometry ---------- */
.lb-page-hero {
    position: relative;
    height: min(88vh, 760px);
    min-height: 560px;
    overflow: hidden;
    background: #000;
}
.lb-page-hero::before {
    content: '';
    position: absolute;
    inset: 0;
    background:
        radial-gradient(ellipse 55% 70% at 82% 8%, rgba(212, 175, 55, .14), transparent),
        linear-gradient(90deg,
            rgba(5, 5, 5, .92) 0%,
            rgba(5, 5, 5, .88) 30%,
            rgba(5, 5, 5, .74) 52%,
            rgba(5, 5, 5, .34) 76%,
            rgba(5, 5, 5, .12) 100%),
        linear-gradient(0deg, var(--brand-bg) 2%, transparent 30%);
}
.lb-page-hero::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: 1.8rem;
    transform: translateX(-50%);
    width: min(420px, 60vw);
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--brand-gold), transparent);
    opacity: .55;
}

@keyframes lb-rise {
    from { opacity: 0; transform: translateY(26px); }
    to { opacity: 1; transform: translateY(0); }
}

/* ---------- Cards: services / products / pricing / portfolio ---------- */
.lb-grid { display: grid; gap: 1.4rem; }
.lb-grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(440px, 100%), 1fr)); }
.lb-grid--3 { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.lb-grid--4 { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }

.lb-card {
    position: relative;
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: 18px;
    padding: 1.9rem 1.7rem;
    overflow: hidden;
    transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
}
.lb-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: linear-gradient(90deg, var(--brand-gold), transparent 70%);
    opacity: 0;
    transition: opacity .22s ease;
}
.lb-card:hover { transform: translateY(-6px); border-color: rgba(212, 175, 55, .38); box-shadow: 0 24px 48px rgba(0, 0, 0, .45); }
.lb-card:hover::before { opacity: 1; }
.lb-card-icon {
    width: 56px;
    height: 56px;
    border-radius: 15px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    color: var(--brand-gold);
    background: linear-gradient(145deg, rgba(212, 175, 55, .16), rgba(212, 175, 55, .05));
    border: 1px solid rgba(212, 175, 55, .3);
    margin-bottom: 1.2rem;
}
.lb-card h3 { font-family: var(--lb-serif); font-size: 1.28rem; margin: 0 0 .6rem; }
.lb-card p { color: var(--brand-text-muted); font-size: .94rem; margin: 0 0 1rem; }
.lb-ticks { list-style: none; margin: 0 0 1.4rem; padding: 0; display: grid; gap: .42rem; }
.lb-ticks li {
    position: relative;
    padding-left: 1.5rem;
    font-size: .89rem;
    color: var(--brand-text-muted);
}
.lb-ticks li::before {
    content: '\f00c';
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    position: absolute;
    left: 0;
    top: .12em;
    font-size: .72rem;
    color: var(--brand-gold);
}
.lb-meta-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .8rem 1rem;
    background: rgba(212, 175, 55, .06);
    border: 1px solid rgba(212, 175, 55, .2);
    border-radius: 12px;
    margin-bottom: 1.2rem;
    font-size: .85rem;
}
.lb-meta-price { color: var(--brand-gold); font-weight: 800; }
.lb-meta-time { color: var(--brand-text-muted); display: inline-flex; align-items: center; gap: .4rem; }

/* Product cards */
.lb-pcard {
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: 18px;
    overflow: hidden;
    transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
    display: flex;
    flex-direction: column;
}
.lb-pcard:hover { transform: translateY(-6px); border-color: rgba(212, 175, 55, .4); box-shadow: 0 24px 48px rgba(0, 0, 0, .45); }
.lb-pcard-media { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: #191919; }
.lb-pcard-media img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s ease; }
.lb-pcard:hover .lb-pcard-media img { transform: scale(1.06); }
.lb-pcat {
    position: absolute;
    top: .8rem;
    left: .8rem;
    padding: .28rem .7rem;
    border-radius: 999px;
    background: rgba(10, 10, 10, .72);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    border: 1px solid rgba(212, 175, 55, .35);
    color: var(--lb-gold-light);
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
}
.lb-pcard-body { padding: 1.2rem 1.3rem 1.4rem; display: flex; flex-direction: column; gap: .5rem; flex: 1; }
.lb-pcard-body h3 { font-family: var(--lb-serif); font-size: 1.12rem; margin: 0; }
.lb-pcard-body p { color: var(--brand-text-muted); font-size: .87rem; margin: 0; flex: 1; }
.lb-plink {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    color: var(--brand-gold);
    text-decoration: none;
    font-weight: 700;
    font-size: .86rem;
    letter-spacing: .04em;
    text-transform: uppercase;
}
.lb-plink i { transition: transform .2s ease; }
.lb-plink:hover i { transform: translateX(4px); }

/* Filters */
.lb-filters { display: flex; flex-wrap: wrap; gap: .55rem; justify-content: center; margin-bottom: 2.4rem; }
.lb-filters button {
    padding: .55rem 1.25rem;
    border-radius: 999px;
    background: transparent;
    border: 1px solid var(--brand-border-strong);
    color: var(--brand-text-muted);
    font-weight: 600;
    font-size: .85rem;
    cursor: pointer;
    transition: all .18s ease;
}
.lb-filters button:hover { color: var(--brand-gold); border-color: rgba(212, 175, 55, .45); }
.lb-filters button.active {
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    border-color: transparent;
    color: #0a0a0a;
}
.lb-hide { display: none !important; }

/* Pricing cards */
.lb-price-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: 20px;
    overflow: hidden;
    transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
}
.lb-price-card:hover { transform: translateY(-6px); border-color: rgba(212, 175, 55, .4); box-shadow: 0 26px 52px rgba(0, 0, 0, .45); }
.lb-price-head {
    padding: 1.6rem 1.7rem 1.2rem;
    background: linear-gradient(180deg, rgba(212, 175, 55, .1), transparent);
    border-bottom: 1px solid var(--brand-border);
}
.lb-price-head h3 { font-family: var(--lb-serif); font-size: 1.35rem; margin: 0 0 .2rem; }
.lb-price-head p { margin: 0; color: var(--brand-text-muted); font-size: .85rem; }
.lb-price-rows { padding: 1.1rem 1.7rem; display: grid; gap: .15rem; flex: 1; }
.lb-price-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: .62rem 0;
    border-bottom: 1px dashed rgba(255, 255, 255, .07);
    font-size: .92rem;
}
.lb-price-row:last-child { border-bottom: 0; }
.lb-price-row span:first-child { color: var(--brand-text-muted); }
.lb-price-val { color: var(--brand-gold); font-weight: 800; white-space: nowrap; }
.lb-price-foot { padding: 0 1.7rem 1.7rem; }

/* Portfolio strip & gallery */
.lb-strip { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(230px, 280px); gap: 1.2rem; overflow-x: auto; padding: .25rem .25rem 1.2rem; scroll-snap-type: x mandatory; }
.lb-strip::-webkit-scrollbar { height: 6px; }
.lb-strip::-webkit-scrollbar-thumb { background: rgba(212, 175, 55, .35); border-radius: 6px; }
.lb-strip-item { scroll-snap-align: start; }
.lb-gallery { columns: 3; column-gap: 1.2rem; }
@media (max-width: 980px) { .lb-gallery { columns: 2; } }
@media (max-width: 600px) { .lb-gallery { columns: 1; } }
.lb-shot {
    break-inside: avoid;
    margin: 0 0 1.2rem;
    position: relative;
    border-radius: 16px;
    overflow: hidden;
    border: 1px solid var(--brand-border);
    background: var(--brand-surface);
    cursor: pointer;
    transition: transform .22s ease, border-color .22s ease;
}
.lb-shot:hover { transform: translateY(-4px); border-color: rgba(212, 175, 55, .4); }
.lb-shot img { width: 100%; display: block; }
.lb-shot-cap {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 2.2rem .95rem .85rem;
    background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .88));
}
.lb-shot-cap h4 { margin: 0 0 .15rem; font-size: .95rem; color: var(--brand-text); }
.lb-shot-cap span { font-size: .74rem; letter-spacing: .12em; text-transform: uppercase; color: var(--brand-gold); }

/* Testimonials */
.lb-quote-card {
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: 18px;
    padding: 1.8rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    transition: transform .2s ease, border-color .2s ease;
}
.lb-quote-card:hover { transform: translateY(-4px); border-color: rgba(212, 175, 55, .35); }
.lb-quote-mark { color: var(--brand-gold); font-size: 1.6rem; font-family: var(--lb-serif); line-height: 1; }
.lb-quote-text { color: var(--brand-text); font-style: italic; margin: 0; flex: 1; }
.lb-quote-meta { display: flex; align-items: center; gap: .6rem; color: var(--brand-text-muted); font-size: .82rem; }
.lb-quote-meta i { color: var(--brand-gold); }

/* CTA bands */
.lb-cta {
    position: relative;
    overflow: hidden;
    border-radius: 24px;
    border: 1px solid rgba(212, 175, 55, .35);
    background:
        radial-gradient(ellipse 70% 130% at 15% -20%, rgba(212, 175, 55, .22), transparent),
        linear-gradient(145deg, #141414, #0c0c0c);
    padding: 3.2rem 2.4rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1.6rem;
}
.lb-cta h2 { font-family: var(--lb-serif); font-size: clamp(1.5rem, 3vw, 2.2rem); margin: 0 0 .5rem; }
.lb-cta p { margin: 0; color: var(--brand-text-muted); }

/* ---------- Forms ---------- */
.lb-form-card {
    background: var(--brand-surface);
    border: 1px solid rgba(212, 175, 55, .22);
    border-radius: 20px;
    padding: 2rem 1.8rem;
    box-shadow: var(--brand-shadow-sm);
}
.lb-form-card h3 { font-family: var(--lb-serif); font-size: 1.45rem; margin: 0 0 1.4rem; }
.lb-form-grid { display: grid; gap: .9rem; }
.lb-form-grid--2 { grid-template-columns: 1fr 1fr; }
@media (max-width: 640px) { .lb-form-grid--2 { grid-template-columns: 1fr; } }
.lb-input,
.lb-select,
.lb-textarea {
    width: 100%;
    padding: .85rem 1.05rem;
    background: var(--brand-bg-raised);
    border: 1px solid var(--brand-border-strong);
    border-radius: 11px;
    color: var(--brand-text);
    font-family: inherit;
    font-size: .93rem;
    transition: border-color .16s ease, box-shadow .16s ease;
}
.lb-input::placeholder,
.lb-textarea::placeholder { color: rgba(154, 154, 154, .75); }
.lb-input:focus,
.lb-select:focus,
.lb-textarea:focus {
    outline: none;
    border-color: var(--brand-gold);
    box-shadow: 0 0 0 3px rgba(212, 175, 55, .16);
}
.lb-select { appearance: none; -webkit-appearance: none; background-image: linear-gradient(45deg, transparent 49%, var(--brand-gold) 50%), linear-gradient(135deg, var(--brand-gold) 50%, transparent 51%); background-position: calc(100% - 19px) 55%, calc(100% - 14px) 55%; background-size: 5px 5px; background-repeat: no-repeat; padding-right: 2.4rem; }
.lb-select option { background: #161616; color: var(--brand-text); }
.lb-textarea { resize: vertical; min-height: 110px; }
.lb-file {
    width: 100%;
    padding: .6rem .8rem;
    background: var(--brand-bg-raised);
    border: 1px dashed var(--brand-border-strong);
    border-radius: 11px;
    color: var(--brand-text-muted);
    font-size: .85rem;
}
.lb-file:focus { outline: none; border-color: var(--brand-gold); }
.lb-form-note { font-size: .78rem; color: var(--brand-text-muted); margin: .3rem 0 0; }
.lb-form-note a { color: var(--brand-gold); }
.lb-alert { padding: .9rem 1.1rem; border-radius: 12px; margin-bottom: 1.2rem; font-weight: 600; font-size: .92rem; }
.lb-alert--ok { background: rgba(34, 197, 94, .12); border: 1px solid rgba(34, 197, 94, .4); color: #86efac; }
.lb-alert--err { background: rgba(239, 68, 68, .12); border: 1px solid rgba(239, 68, 68, .4); color: #fca5a5; }

/* Benefits list beside forms */
.lb-benefit { display: flex; gap: 1rem; padding: 1rem 0; border-bottom: 1px solid var(--brand-border); }
.lb-benefit:last-child { border-bottom: 0; }
.lb-benefit-icon {
    flex: none;
    width: 44px;
    height: 44px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--brand-gold);
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .25);
}
.lb-benefit h4 { margin: 0 0 .2rem; font-size: .98rem; }
.lb-benefit p { margin: 0; color: var(--brand-text-muted); font-size: .86rem; }

/* Info list (contact details) */
.lb-info-item { display: flex; gap: 1rem; padding: .95rem 0; border-bottom: 1px solid var(--brand-border); }
.lb-info-item:last-child { border-bottom: 0; }
.lb-info-icon {
    flex: none;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--brand-gold);
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .3);
}
.lb-info-item h4 { margin: 0 0 .15rem; font-size: .95rem; }
.lb-info-item p { margin: 0; color: var(--brand-text-muted); font-size: .9rem; word-break: break-word; }

/* Split layout: form + aside */
.lb-split { display: grid; grid-template-columns: 1.05fr .95fr; gap: 2.2rem; align-items: start; }
@media (max-width: 900px) { .lb-split { grid-template-columns: 1fr; } }

/* ---------- FAQ accordion ---------- */
.lb-faq { max-width: 860px; margin: 0 auto; display: grid; gap: .8rem; }
.faq-item {
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: 14px;
    overflow: hidden;
    transition: border-color .2s ease;
}
.faq-item.open { border-color: rgba(212, 175, 55, .4); }
.faq-question {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    background: transparent;
    border: 0;
    color: var(--brand-text);
    font: inherit;
    font-weight: 600;
    font-size: 1rem;
    text-align: left;
    padding: 1.15rem 1.3rem;
    cursor: pointer;
}
.faq-item.open .faq-question { color: var(--brand-gold); }
.faq-icon { color: var(--brand-gold); flex: none; transition: transform .25s ease; }
.faq-answer { max-height: 0; overflow: hidden; transition: max-height .35s ease; }
.faq-answer-content {
    padding: 0 1.3rem 1.3rem;
    color: var(--brand-text-muted);
    font-size: .93rem;
}

/* ---------- Legal / DB-driven prose ---------- */
.lb-prose { max-width: 860px; margin: 0 auto; color: var(--brand-text-muted); font-size: .98rem; }
.lb-prose h1, .lb-prose h2, .lb-prose h3, .lb-prose h4 {
    font-family: var(--lb-serif);
    color: var(--brand-text);
    line-height: 1.25;
    margin: 2rem 0 .8rem;
}
.lb-prose h2, .lb-prose h1 { color: var(--brand-gold); }
.lb-prose a { color: var(--brand-gold); }
.lb-prose ul, .lb-prose ol { padding-left: 1.4rem; display: grid; gap: .4rem; }
.lb-prose img { border-radius: 12px; border: 1px solid var(--brand-border); }
.lb-prose table { width: 100%; border-collapse: collapse; margin: 1.2rem 0; }
.lb-prose th, .lb-prose td { border: 1px solid var(--brand-border-strong); padding: .6rem .8rem; text-align: left; }

/* ---------- Footer ---------- */
.lb-footer {
    margin-top: 4.5rem;
    background: linear-gradient(180deg, #0d0d0d, #070707);
    border-top: 1px solid rgba(212, 175, 55, .22);
    position: relative;
}
.lb-footer::before {
    content: '';
    position: absolute;
    top: -1px;
    left: 50%;
    transform: translateX(-50%);
    width: min(420px, 60vw);
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--brand-gold), transparent);
}
.lb-footer-main {
    display: grid;
    grid-template-columns: 1.6fr 1fr 1fr 1fr 1.2fr;
    gap: 2.4rem;
    padding: 4rem 0 2.6rem;
}
@media (max-width: 1024px) { .lb-footer-main { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .lb-footer-main { grid-template-columns: 1fr; } }
.lb-footer-brand img { height: 52px; margin-bottom: 1.1rem; }
.lb-footer-brand p { color: var(--brand-text-muted); font-size: .9rem; max-width: 320px; margin: 0 0 1.2rem; }
.lb-socials { display: flex; gap: .6rem; }
.lb-socials a {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--brand-text-muted);
    border: 1px solid var(--brand-border-strong);
    text-decoration: none;
    transition: all .18s ease;
}
.lb-socials a:hover { color: #0a0a0a; background: var(--brand-gold); border-color: var(--brand-gold); }
.lb-footer h4 {
    color: var(--brand-gold);
    font-size: .8rem;
    font-weight: 700;
    letter-spacing: .18em;
    text-transform: uppercase;
    margin: 0 0 1.1rem;
}
.lb-footer-links { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.lb-footer-links a {
    color: var(--brand-text-muted);
    text-decoration: none;
    font-size: .9rem;
    transition: color .15s ease, padding-left .15s ease;
}
.lb-footer-links a:hover { color: var(--brand-gold); padding-left: 4px; }
.lb-footer-links--row { display: flex; flex-wrap: wrap; gap: .4rem 1.6rem; }
.lb-contact-line { display: flex; align-items: flex-start; gap: .7rem; color: var(--brand-text-muted); font-size: .9rem; margin-bottom: .75rem; }
.lb-contact-line i { color: var(--brand-gold); margin-top: .2rem; width: 16px; text-align: center; }
.lb-contact-line a { color: var(--brand-text-muted); text-decoration: none; }
.lb-contact-line a:hover { color: var(--brand-gold); }
.lb-footer-bottom {
    border-top: 1px solid var(--brand-border);
    padding: 1.4rem 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .8rem;
    font-size: .82rem;
    color: var(--brand-text-muted);
}
.lb-footer-bottom nav { display: flex; gap: 1.4rem; }
.lb-footer-bottom a { color: var(--brand-text-muted); text-decoration: none; }
.lb-footer-bottom a:hover { color: var(--brand-gold); }

/* Back-to-top
   Sits ABOVE the reCAPTCHA v3 badge rather than on top of it. Google fixes
   that badge at bottom:14px with a 60px-tall visible tab (top edge 74px from
   the bottom) hard-anchored bottom-right; it cannot be moved without breaking
   Google's terms. At the old bottom:1.6rem (25.6px) this button overlapped it
   at right 25.6-70px and, carrying z-index:1600 against the badge's auto,
   painted straight over it. Measured 2026-08-24 in Chromium at 1440x900.
   5.5rem = 88px clears the badge's 74px top edge with ~14px of air. */
#backToTop {
    position: fixed;
    bottom: 5.5rem;
    right: 1.6rem;
    z-index: 1600;
    width: 46px;
    height: 46px;
    border-radius: 50%;
    border: 1px solid rgba(212, 175, 55, .5);
    background: rgba(10, 10, 10, .8);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    color: var(--brand-gold);
    cursor: pointer;
    opacity: 0;
    visibility: hidden;
    transform: translateY(12px);
    transition: all .25s ease;
}
#backToTop.visible { opacity: 1; visibility: visible; transform: translateY(0); }
#backToTop:hover { background: var(--brand-gold); color: #0a0a0a; }

/* Scroll reveal (works with script.js IntersectionObserver) */
.animate-on-scroll {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity .7s ease, transform .7s ease;
}
.animate-on-scroll.animated { opacity: 1; transform: translateY(0); }
.slide-left { transform: translateX(-34px); }
.slide-right { transform: translateX(34px); }
.slide-left.animated,
.slide-right.animated { transform: translateX(0); }
@media (prefers-reduced-motion: reduce) {
    .animate-on-scroll, .slide-left, .slide-right { opacity: 1 !important; transform: none !important; transition: none !important; }
    .lb-announce-track, .hero-slide-bg { animation: none !important; transition: none !important; }
}

/* ---------- Responsive shell ---------- */
@media (max-width: 1120px) {
    .lb-nav { display: none; }
    .lb-burger { display: flex; }
    .lb-action-link--desktop { display: none; }
}
@media (max-width: 560px) {
    :root { --lb-topbar-h: 64px; }
    .lb-logo img { height: 38px; }
    .lb-section { padding: 3.6rem 0; }
    .slider-arrow { display: none; }
    .lb-announce-track { gap: 1.4rem; font-size: .68rem; }
    .lb-cta { padding: 2.2rem 1.5rem; }
}

/* ============================================================
   PHASE 2 + 3 LAYER — storefront conversion & premium polish
   Loaded after the base shell: overrides legacy style.css looks
   for product template pages (body.lb-tpl) and defines shell
   utilities that were used but never styled (.lb-table etc.).
   ============================================================ */

/* ---------- Missing shell utilities (pricing.php & friends) ---------- */
.lb-section--alt {
    background:
        radial-gradient(ellipse 60% 40% at 90% -10%, rgba(212, 175, 55, .05), transparent),
        linear-gradient(180deg, rgba(255, 255, 255, .02), transparent);
    border-top: 1px solid var(--brand-border);
    border-bottom: 1px solid var(--brand-border);
}
.lb-section-head { text-align: center; max-width: 720px; margin: 0 auto 2.6rem; }
.lb-section-head .lb-eyebrow { justify-content: center; }
.lb-h2 {
    font-family: var(--lb-serif);
    font-size: clamp(1.8rem, 3.6vw, 2.6rem);
    font-weight: 700;
    line-height: 1.15;
    margin: 0 0 .9rem;
}
.lb-h3 { font-family: var(--lb-serif); font-size: 1.45rem; margin: 0 0 .7rem; }
.lb-lede { color: var(--brand-text-muted); font-size: 1rem; margin: 0; }
.lb-muted { color: var(--brand-text-muted); }
.lb-hint { color: var(--brand-text-muted); font-size: .84rem; margin: 0; }
.lb-cols-2 { display: grid; grid-template-columns: 1fr; gap: 2.2rem; align-items: start; }
@media (min-width: 900px) { .lb-cols-2 { grid-template-columns: 1fr 1fr; } }

.lb-table-wrap {
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: var(--brand-radius-lg);
    overflow-x: auto;
    box-shadow: var(--brand-shadow-sm);
}
.lb-table { width: 100%; border-collapse: collapse; min-width: 420px; }
.lb-table th {
    text-align: left;
    padding: 1rem 1.25rem;
    color: var(--brand-gold);
    font-size: .74rem;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
    background: rgba(212, 175, 55, .08);
    border-bottom: 1px solid rgba(212, 175, 55, .24);
    white-space: nowrap;
}
.lb-table td {
    padding: 1rem 1.25rem;
    color: var(--brand-text);
    font-size: .95rem;
    border-top: 1px solid var(--brand-border);
}
.lb-table tbody tr { transition: background .12s ease, box-shadow .12s ease; }
.lb-table tbody tr:hover {
    background: rgba(212, 175, 55, .04);
    box-shadow: inset 3px 0 0 rgba(212, 175, 55, .55);
}

.lb-card--form { border-color: rgba(212, 175, 55, .22); }
.lb-card--gold {
    background:
        radial-gradient(ellipse 80% 60% at 20% -20%, rgba(212, 175, 55, .16), transparent),
        var(--brand-surface);
    border-color: rgba(212, 175, 55, .35);
}
.lb-card--gold h3 { color: var(--brand-gold); }
/* Alternating media/copy service rows (services.php).
   These three classes had NO rules in any of the three stylesheets and no
   page-local <style> block, while services.php used them 21 times across seven
   service blocks. With no grid the media and copy stacked, and with no width on
   the image each photo rendered at its intrinsic size - so the page was seven
   enormous pictures with a paragraph under each. Same orphaned-markup class of
   defect as the .pricing-* system, in the other direction: there the CSS existed
   and the markup had been deleted. */
.lb-service-row {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.75rem;
    align-items: center;
    padding: 2.25rem 0;
    border-bottom: 1px solid var(--brand-border);
}
.lb-service-row:last-child { border-bottom: 0; }
@media (min-width: 900px) {
    .lb-service-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 3rem; padding: 3rem 0; }
    /* --flip moves the image to the right on alternating rows. Source order stays
       media-then-copy, so the reading order on a phone and in the a11y tree is
       always picture-then-heading rather than alternating. */
    .lb-service-row--flip .lb-service-media { order: 2; }
    .lb-service-row--flip .lb-service-copy { order: 1; }
}
.lb-service-media {
    overflow: hidden;
    border: 1px solid var(--brand-border);
    border-radius: var(--brand-radius-lg);
    background: var(--brand-surface);
}
/* Fixed height with cover, not aspect-ratio: the source photos run from a wide
   3:1 patch shot to a near-square logo, and a rate sheet of ragged image heights
   reads as broken. */
.lb-service-media img { display: block; width: 100%; height: 240px; object-fit: cover; transition: transform .5s ease; }
@media (min-width: 900px) { .lb-service-media img { height: 330px; } }
.lb-service-row:hover .lb-service-media img { transform: scale(1.03); }
@media (prefers-reduced-motion: reduce) {
    .lb-service-media img { transition: none; }
    .lb-service-row:hover .lb-service-media img { transform: none; }
}
.lb-service-copy { min-width: 0; }
.lb-service-copy .service-icon { margin-bottom: .9rem; }
.lb-service-copy .lb-ticks { margin: 1rem 0 1.25rem; }

.lb-quote-grid { display: grid; grid-template-columns: 1fr; gap: 2rem; align-items: start; }
@media (min-width: 1024px) { .lb-quote-grid { grid-template-columns: 1.5fr 1fr; } }
.lb-quote-side { display: grid; gap: 1.4rem; }
.lb-field { margin-bottom: 1rem; min-width: 0; }
.lb-field > label {
    display: block;
    margin-bottom: .4rem;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--brand-text-muted);
}
.lb-btn--outline {
    background: transparent;
    border-color: rgba(212, 175, 55, .45);
    color: var(--brand-gold) !important;
}
.lb-btn--outline:hover { background: rgba(212, 175, 55, .1); }
.lb-btn--lg { padding: 1rem 2.2rem; font-size: 1rem; }
.lb-alert--gold {
    background: rgba(212, 175, 55, .08);
    border: 1px solid rgba(212, 175, 55, .35);
    color: var(--lb-gold-light);
}
.lb-alert--gold a { color: var(--brand-gold); }
.lb-alert--success { background: rgba(34, 197, 94, .12); border: 1px solid rgba(34, 197, 94, .4); color: #86efac; }
.lb-alert--error { background: rgba(239, 68, 68, .12); border: 1px solid rgba(239, 68, 68, .4); color: #fca5a5; }
.filter-buttons { display: flex; flex-wrap: wrap; gap: .55rem; justify-content: center; margin-bottom: 2.4rem; }
.product-actions { margin-top: auto; padding-top: 1rem; }
.modal-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 1.4rem; }
.product-modal-details h3 { font-family: var(--lb-serif); font-size: 1.4rem; margin: 0 0 .6rem; color: var(--brand-text); }
.product-modal-details p { color: var(--brand-text-muted); margin: 0 0 1.2rem; }

/* ---------- Phase 2: elevated product cards (product template pages) ---------- */
body.lb-tpl .products-grid { gap: 1.6rem; }
body.lb-tpl .products-section { background: transparent; }
body.lb-tpl .product-card {
    background: linear-gradient(180deg, #151515, var(--brand-surface));
    border: 1px solid var(--brand-border-strong);
    border-radius: 18px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
    transition: transform .28s cubic-bezier(.22, .8, .36, 1), box-shadow .28s ease, border-color .28s ease;
    will-change: transform;
}
body.lb-tpl .product-card::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    border-radius: 18px 18px 0 0;
    background: linear-gradient(90deg, var(--brand-gold), rgba(212, 175, 55, 0));
    opacity: 0;
    transform: none;
    transition: opacity .28s ease;
    z-index: 2;
}
body.lb-tpl .product-card:hover {
    transform: translateY(-8px);
    border-color: rgba(212, 175, 55, .45);
    box-shadow: 0 30px 60px rgba(0, 0, 0, .55), 0 6px 22px rgba(212, 175, 55, .10);
}
body.lb-tpl .product-card:hover::before { opacity: 1; transform: none; }
body.lb-tpl .product-image-wrapper {
    aspect-ratio: 4 / 3;
    overflow: hidden;
    background: #191919;
    border-bottom: 1px solid var(--brand-border);
}
body.lb-tpl .product-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .65s cubic-bezier(.22, .8, .36, 1);
}
body.lb-tpl .product-card:hover .product-image { transform: scale(1.08); }
body.lb-tpl .product-badge {
    position: absolute;
    top: .85rem;
    right: .85rem;
    z-index: 2;
    padding: .3rem .75rem;
    border-radius: 999px;
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    color: #0a0a0a;
    font-size: .68rem;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    box-shadow: 0 6px 16px rgba(212, 175, 55, .35);
}
body.lb-tpl .product-content { padding: 1.35rem 1.35rem 1.5rem; display: flex; flex-direction: column; flex: 1; }
body.lb-tpl .product-title {
    font-family: var(--lb-serif);
    font-size: 1.18rem;
    font-weight: 700;
    color: var(--brand-text);
    margin: 0 0 .5rem;
    letter-spacing: .01em;
}
body.lb-tpl .product-description {
    color: var(--brand-text-muted);
    font-size: .88rem;
    line-height: 1.55;
    margin: 0 0 .8rem;
    display: block;
    overflow: hidden;
    max-height: 3.1em;
    -webkit-mask-image: linear-gradient(180deg, #000 62%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 62%, transparent 100%);
}
body.lb-tpl .product-features-tag {
    color: var(--lb-gold-light);
    font-size: .74rem;
    line-height: 1.6;
    letter-spacing: .02em;
}
body.lb-tpl .product-price {
    font-family: var(--lb-serif);
    color: var(--brand-gold);
    font-size: 1.5rem;
    font-weight: 700;
    margin: .2rem 0 .4rem;
    font-variant-numeric: tabular-nums;
}
body.lb-tpl .product-price-currency { font-size: .85rem; vertical-align: super; margin-right: 1px; }
body.lb-tpl .product-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .55rem;
    width: 100%;
    padding: .8rem 1.4rem;
    border: none;
    border-radius: 999px;
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    color: #0a0a0a !important;
    font-weight: 700;
    font-size: .88rem;
    letter-spacing: .03em;
    cursor: pointer;
    overflow: hidden;
    position: relative;
    transition: transform .2s ease, box-shadow .2s ease, background .2s ease;
    box-shadow: 0 8px 22px rgba(212, 175, 55, .22);
}
body.lb-tpl .product-btn::before {
    content: '';
    position: absolute;
    top: 0; left: -80%;
    width: 50%; height: 100%;
    background: linear-gradient(105deg, transparent, rgba(255, 255, 255, .35), transparent);
    transform: skewX(-20deg);
    transition: left .55s ease;
}
body.lb-tpl .product-btn:hover { transform: translateY(-2px); box-shadow: 0 14px 30px rgba(212, 175, 55, .34); }
body.lb-tpl .product-btn:hover::before { left: 130%; }
body.lb-tpl .filter-btn.active,
body.lb-tpl .filter-btn:hover {
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    color: #0a0a0a;
}
body.lb-tpl .filter-container {
    background: rgba(22, 22, 22, .85);
    border: 1px solid var(--brand-border);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}
body.lb-tpl .stat-number { color: var(--brand-gold); }

/* ---------- Phase 2: premium product modal ---------- */
#productModal.lb-product-modal { z-index: 2100; }
#productModal.lb-product-modal.active {
    animation: lb-overlay-in .25s ease both;
}
#productModal .lb-product-dialog { scrollbar-width: thin; scrollbar-color: rgba(212, 175, 55, .5) transparent; }
#productModal.lb-product-modal.active .lb-product-dialog {
    animation: lb-modal-pop .42s cubic-bezier(.21, .98, .4, 1.08) both;
}
#productModal.lb-product-modal.closing { animation: lb-overlay-out .2s ease both; }
#productModal.lb-product-modal.closing .lb-product-dialog { animation: lb-modal-drop .2s ease both; }
@keyframes lb-overlay-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes lb-overlay-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes lb-modal-pop {
    from { opacity: 0; transform: translateY(34px) scale(.94); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes lb-modal-drop {
    from { opacity: 1; transform: translateY(0) scale(1); }
    to { opacity: 0; transform: translateY(26px) scale(.96); }
}
#productModal .lb-product-close {
    transition: background .2s ease, color .2s ease, transform .25s ease;
}
#productModal .lb-product-close:hover {
    background: var(--brand-gold);
    color: #0a0a0a;
    transform: rotate(90deg);
}
body.lb-tpl .product-modal-content { gap: 1.6rem; }
body.lb-tpl .product-modal-details h3,
body.lb-tpl .product-details h3 {
    font-family: var(--lb-serif);
    font-size: 1.45rem;
    margin: 0 0 .6rem;
    color: var(--brand-text);
}
body.lb-tpl .product-modal-details p,
body.lb-tpl .product-details p { color: var(--brand-text-muted); margin: 0 0 1.2rem; }
body.lb-tpl .product-modal-image img,
body.lb-tpl .product-image img {
    border-radius: 14px;
    border: 1px solid rgba(212, 175, 55, .25);
}
body.lb-tpl .size-option {
    position: relative;
    background: rgba(255, 255, 255, .03);
    border: 1px solid var(--brand-border-strong);
    cursor: pointer;
}
body.lb-tpl .size-option input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    margin: 0;
    cursor: pointer;
}
body.lb-tpl .size-option:hover { border-color: rgba(212, 175, 55, .55); color: var(--brand-gold); }
body.lb-tpl .size-option:has(input:checked) {
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    border-color: transparent;
    color: #0a0a0a;
    box-shadow: 0 6px 16px rgba(212, 175, 55, .3);
}
body.lb-tpl .quantity-btn {
    width: 38px;
    height: 38px;
    border-radius: 10px;
    background: rgba(255, 255, 255, .05);
    border: 1px solid var(--brand-border-strong);
}
body.lb-tpl .quantity-btn:hover {
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    color: #0a0a0a;
}
#productQuantity { background: rgba(10, 10, 10, .8); border-color: var(--brand-border-strong); }

/* ---------- Phase 3: premium feature grids ("Why choose our ...") ---------- */
body.lb-tpl .section-header { text-align: center; max-width: 720px; margin: 0 auto 2.6rem; }
body.lb-tpl .services-grid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.4rem; }
body.lb-tpl .service-card {
    background: linear-gradient(180deg, #151515, var(--brand-surface));
    border: 1px solid var(--brand-border-strong);
    border-radius: 18px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, .3);
    padding: 1.9rem 1.5rem;
    transition: transform .28s cubic-bezier(.22, .8, .36, 1), box-shadow .28s ease, border-color .28s ease;
}
body.lb-tpl .service-card::before {
    top: 0; left: 0; right: 0;
    height: 3px;
    background: linear-gradient(90deg, var(--brand-gold), rgba(212, 175, 55, 0));
    opacity: 0;
    transform: none;
    transition: opacity .28s ease;
}
body.lb-tpl .service-card:hover::before { opacity: 1; transform: none; }
body.lb-tpl .service-card:hover {
    transform: translateY(-8px);
    border-color: rgba(212, 175, 55, .45);
    box-shadow: 0 28px 56px rgba(0, 0, 0, .5), 0 6px 20px rgba(212, 175, 55, .1);
}
body.lb-tpl .service-icon,
body.lb-tpl .tpl-feature-icon {
    width: 58px;
    height: 58px;
    border-radius: 16px;
    background: linear-gradient(145deg, rgba(212, 175, 55, .18), rgba(212, 175, 55, .05));
    border: 1px solid rgba(212, 175, 55, .32);
    color: var(--brand-gold);
    font-size: 1.35rem;
    transition: transform .28s ease, background .28s ease;
}
body.lb-tpl .service-card:hover .service-icon,
body.lb-tpl .service-card:hover .tpl-feature-icon {
    transform: translateY(-3px);
    background: linear-gradient(145deg, var(--brand-gold), var(--brand-gold-dark));
    color: #0a0a0a;
}
body.lb-tpl .service-title,
body.lb-tpl .service-card h4 {
    font-family: var(--lb-serif);
    color: var(--brand-text);
    font-size: 1.12rem;
}
body.lb-tpl .service-description,
body.lb-tpl .service-card p { color: var(--brand-text-muted); opacity: 1; }
body.lb-tpl .about-title { font-family: var(--lb-serif); color: var(--brand-gold); font-size: clamp(1.5rem, 2.6vw, 2rem); }
body.lb-tpl .about-feature i {
    width: 44px;
    height: 44px;
    border-radius: 13px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    color: var(--brand-gold);
    background: linear-gradient(145deg, rgba(212, 175, 55, .16), rgba(212, 175, 55, .05));
    border: 1px solid rgba(212, 175, 55, .3);
}
body.lb-tpl .about-card {
    background: linear-gradient(180deg, #151515, var(--brand-surface));
    border: 1px solid var(--brand-border-strong);
    border-radius: 16px;
    box-shadow: 0 10px 26px rgba(0, 0, 0, .3);
    padding: 1.6rem 1.3rem;
    transition: transform .26s ease, border-color .26s ease, box-shadow .26s ease;
}
body.lb-tpl .about-card:hover {
    transform: translateY(-6px);
    border-color: rgba(212, 175, 55, .45);
    box-shadow: 0 24px 48px rgba(0, 0, 0, .45);
}
body.lb-tpl .about-card h4 { font-family: var(--lb-serif); color: var(--brand-text); }
body.lb-tpl .about-card p { color: var(--brand-text-muted); opacity: 1; }
body.lb-tpl .about-card-icon {
    width: 52px;
    height: 52px;
    margin: 0 auto .9rem;
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.2rem;
    color: var(--brand-gold);
    background: linear-gradient(145deg, rgba(212, 175, 55, .18), rgba(212, 175, 55, .05));
    border: 1px solid rgba(212, 175, 55, .32);
}
body.lb-tpl .quote-form-wrapper .form-card {
    background: var(--brand-surface);
    border: 1px solid rgba(212, 175, 55, .25);
    border-radius: 20px;
    padding: 2rem;
    box-shadow: var(--brand-shadow-sm);
}
body.lb-tpl .quote-benefits { color: var(--brand-text); }

/* ============================================================================
   FORM CONTROLS for the lb-* system                          (session 5, UI)
   ----------------------------------------------------------------------------
   Why this block exists: .lb-field styled its <label> and nothing else, so
   every input / select / textarea inside one fell back to browser defaults.
   Measured 2026-08-24: 101 unstyled controls across 8 storefront pages
   (index, contact, cart, faqs, pricing, services, aboutus, portfolio) - white
   boxes, Courier textareas, native select arrows, on a black-and-gold page.

   .lb-field--full appears throughout the markup but had no rule, and the base
   .lb-form-grid carried no grid-template-columns. A "span all columns" class
   only makes sense on a multi-column grid, so the columns were designed and
   then lost. Both are restored below.

   Two deliberate choices:
     1. Fields sit DARKER than the card they rest on, so they read as wells
        carved into the surface. Making inputs lighter than their container is
        the reflexive dark-theme move and it reads cheap next to gold.
     2. Focus draws a satin-stitch seam along the bottom edge rather than a
        glow ring. This is an embroidery shop - the stitch is the house mark.
        It is the only ornament here; everything else stays quiet.

   Compatibility (production PHP/Apache are older than dev, so CSS is too):
   plain CSS, multiple backgrounds, repeating-linear-gradient, custom props.
   No :has(), no :is(), no nesting, no container queries. accent-color and
   ::file-selector-button degrade to the native control when unsupported.
   Scoped to .lb-field so the .brand-input generation is untouched.
   ============================================================================ */

:root {
    --lb-well: #0c0c0c;
    --lb-well-hover: #101010;
    --lb-thread: #e8c85a;
    --lb-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'%3E%3Cpath d='M1 1l4.5 4.5L10 1' fill='none' stroke='%23d4af37' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --lb-stitch: repeating-linear-gradient(90deg, var(--lb-thread) 0, var(--lb-thread) 5px, transparent 5px, transparent 10px);
}

/* The grid the markup was written for. */
.lb-form-grid { grid-template-columns: 1fr; gap: 1rem 1.1rem; }
@media (min-width: 640px) { .lb-form-grid { grid-template-columns: 1fr 1fr; } }
.lb-field--full { grid-column: 1 / -1; }
.lb-form-grid .lb-field { margin-bottom: 0; }

/* ---- base control ------------------------------------------------------- */
.lb-field input:not([type="file"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"]),
.lb-field select,
.lb-field textarea {
    width: 100%;
    box-sizing: border-box;
    padding: .8rem .95rem;
    background-color: var(--lb-well);
    background-image: none;
    border: 1px solid var(--brand-border-strong);
    border-radius: var(--brand-radius);
    color: var(--brand-text);
    font-family: var(--lb-sans);
    font-size: .95rem;
    line-height: 1.4;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .55);
    transition: border-color .16s ease, background-color .16s ease,
                box-shadow .16s ease;
}

.lb-field textarea {
    min-height: 7.5rem;
    resize: vertical;
    display: block;
}

.lb-field select {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    padding-right: 2.6rem;
    background-image: var(--lb-chevron);
    background-repeat: no-repeat;
    background-position: right 1rem center;
    background-size: 11px 7px;
    cursor: pointer;
}
/* Native dropdown list renders with the OS default on Windows without this. */
.lb-field select option { background: var(--brand-bg-raised); color: var(--brand-text); }

.lb-field ::-webkit-input-placeholder { color: #6d6a63; }
.lb-field ::-moz-placeholder { color: #6d6a63; opacity: 1; }
.lb-field :-ms-input-placeholder { color: #6d6a63; }
.lb-field ::placeholder { color: #6d6a63; }

/* An unselected select should LOOK unselected.
   -------------------------------------------------------------------------
   A <select> has no ::placeholder. Its empty first option renders as ordinary
   option text, so measured at 1440 every empty select on the site painted
   rgb(245,245,245) -- byte-identical to a select holding a real answer, while
   the text input beside it greyed its placeholder to #6d6a63. Eighteen selects
   across nine pages all read as already answered. Same grey as the inputs, so
   "nothing entered yet" looks the same in every field type.

   Two hooks on one rule, deliberately:

   :invalid   - a select's only possible constraint is `required`, so for the 11
                required selects :invalid is exactly equivalent to "empty". Pure
                CSS, no JS dependency, and it clears itself the instant a real
                option is chosen. Supported since Chrome 10 / Firefox 4 / IE10,
                and this site already relies on `required` and type=email, so it
                is inside the production floor. :has() would have covered
                optional selects too, but it is outside that floor.
   .lb-empty  - the 7 optional selects (quote_backing, patch_type, service_type,
                country1) can never be :invalid, so script.js stamps this class
                on any empty select. Progressive enhancement: with JS off those
                selects keep today's appearance rather than breaking.

   Restricted to `select` on the .quote-form generation -- `.form-input:invalid`
   unqualified would also match a text input mid-typing and grey real characters
   the customer had entered. */
.lb-field select:invalid,
.lb-field select.lb-empty,
.quote-form select.form-input:invalid,
.quote-form select.form-input.lb-empty,
.brand-select:invalid,
.brand-select.lb-empty { color: #6d6a63; }
/* The open dropdown list is a separate surface and its options are real
   choices, so they stay full strength regardless of the closed control. */
.lb-field select:invalid option,
.lb-field select.lb-empty option,
.quote-form select.form-input:invalid option,
.quote-form select.form-input.lb-empty option,
.brand-select:invalid option,
.brand-select.lb-empty option { color: var(--brand-text); }

/* ---- hover -------------------------------------------------------------- */
.lb-field input:not([type="file"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"]):hover,
.lb-field select:hover,
.lb-field textarea:hover {
    background-color: var(--lb-well-hover);
    border-color: rgba(212, 175, 55, .32);
}

/* ---- focus: the stitch seam -------------------------------------------- */
.lb-field input:not([type="file"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"]):focus,
.lb-field textarea:focus {
    outline: none;
    background-color: var(--lb-well-hover);
    border-color: rgba(212, 175, 55, .55);
    background-image: var(--lb-stitch);
    background-repeat: no-repeat;
    /* Inset horizontally so the seam stops inside the corner radius, the way
       stitching stops short of a hem edge rather than running off it. */
    background-position: 11px 100%;
    background-size: calc(100% - 22px) 2px;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .55), 0 0 0 3px rgba(212, 175, 55, .13);
}
/* Select keeps its chevron, so both layers are declared together. */
.lb-field select:focus {
    outline: none;
    background-color: var(--lb-well-hover);
    border-color: rgba(212, 175, 55, .55);
    background-image: var(--lb-chevron), var(--lb-stitch);
    background-repeat: no-repeat, no-repeat;
    background-position: right 1rem center, 11px 100%;
    background-size: 11px 7px, calc(100% - 22px) 2px;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .55), 0 0 0 3px rgba(212, 175, 55, .13);
}

/* Chrome paints autofilled fields near-white and ignores background-color.
   The inset shadow is the only reliable override; without it the homepage
   login fields flash white on a black card. */
.lb-field input:-webkit-autofill,
.lb-field input:-webkit-autofill:hover,
.lb-field input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--brand-text);
    -webkit-box-shadow: inset 0 0 0 100px var(--lb-well-hover), inset 0 1px 2px rgba(0, 0, 0, .55);
    caret-color: var(--brand-text);
    transition: background-color 5000s ease-in-out 0s;
}

/* ---- file input --------------------------------------------------------- */
.lb-field input[type="file"] {
    width: 100%;
    box-sizing: border-box;
    padding: .6rem .7rem;
    background: var(--lb-well);
    border: 1px dashed rgba(212, 175, 55, .3);
    border-radius: var(--brand-radius);
    color: var(--brand-text-muted);
    font-family: var(--lb-sans);
    font-size: .88rem;
    cursor: pointer;
    transition: border-color .16s ease, background-color .16s ease;
}
.lb-field input[type="file"]:hover {
    background: var(--lb-well-hover);
    border-color: rgba(212, 175, 55, .55);
}
.lb-field input[type="file"]:focus {
    outline: none;
    border-color: var(--brand-gold);
    box-shadow: 0 0 0 3px rgba(212, 175, 55, .13);
}
.lb-field input[type="file"]::file-selector-button {
    margin-right: .85rem;
    padding: .5rem 1rem;
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .35);
    border-radius: 7px;
    color: var(--brand-gold);
    font-family: var(--lb-sans);
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .16s ease;
}
.lb-field input[type="file"]::file-selector-button:hover { background: rgba(212, 175, 55, .2); }
.lb-field input[type="file"]::-webkit-file-upload-button {
    margin-right: .85rem;
    padding: .5rem 1rem;
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .35);
    border-radius: 7px;
    color: var(--brand-gold);
    font-family: var(--lb-sans);
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    cursor: pointer;
}

/* ---- checkbox / radio --------------------------------------------------- */
.lb-field input[type="checkbox"],
.lb-field input[type="radio"] {
    accent-color: var(--brand-gold);
    width: 1.05rem;
    height: 1.05rem;
    vertical-align: -2px;
    cursor: pointer;
}

/* ---- supporting text --------------------------------------------------- */
.lb-field .lb-hint { margin-top: .45rem; display: block; }
.lb-form-foot {
    margin: .95rem 0 0;
    color: var(--brand-text-muted);
    font-size: .9rem;
}
/* .lb-faq p a joins this list because the FAQ answers are <details><summary> +
   <p>, not .lb-card, so the one prose link in there (faqs.php:57, "pricing
   page") was rendering as an unstyled anchor: rgb(0, 0, 238) underline, the
   browser default, on a near-black page. It is the only genuine prose link on
   the storefront still doing that - the other one, all-products.php's "Request
   a custom quote", already computes a gold underline. */
.lb-form-foot a,
.lb-card p a,
.lb-faq p a {
    color: var(--brand-gold);
    font-weight: 600;
    text-decoration: none;
    border-bottom: 1px solid rgba(212, 175, 55, .35);
    transition: border-color .16s ease, color .16s ease;
}
.lb-form-foot a:hover,
.lb-card p a:hover,
.lb-faq p a:hover { color: var(--lb-thread); border-bottom-color: var(--lb-thread); }

/* Disabled controls should read as unavailable, not merely dim. */
.lb-field input:disabled,
.lb-field select:disabled,
.lb-field textarea:disabled {
    opacity: .5;
    cursor: not-allowed;
    background-color: #080808;
}

@media (prefers-reduced-motion: reduce) {
    .lb-field input,
    .lb-field select,
    .lb-field textarea { transition: none; }
}

/* ============================================================================
   FORM CONTROLS, part 2: the brand-* generation                (session 5, UI)
   ----------------------------------------------------------------------------
   The storefront carries two form-styling generations:
     .lb-field ...    - homepage, contact, cart, faqs, pricing, services,
                        aboutus, portfolio          (styled in the block above)
     .brand-input /
     .brand-select /
     .brand-textarea  - the four order forms, login, signup, forgot

   brand.css styles the text inputs but never resets select appearance, never
   touches file inputs, radios or checkboxes, and has no autofill guard. So
   after the block above landed, sendQuote.php still showed four white native
   "Choose File" buttons and OS select arrows while index.php looked finished.
   Measured 2026-08-24: 16 native file inputs and 9 native selects across the
   four order forms.

   This block deliberately reuses the SAME custom properties as the .lb-field
   block (--lb-well, --lb-chevron, --lb-stitch). Sharing the tokens is what
   makes the two generations render identically rather than merely similarly -
   change a token once and both move together. site.css loads after brand.css,
   so these win on order without needing !important.
   ============================================================================ */

/* ---- select: kill the OS arrow, draw ours ------------------------------- */
.brand-select {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    padding-right: 2.6rem;
    background-image: var(--lb-chevron);
    background-repeat: no-repeat;
    background-position: right 1rem center;
    background-size: 11px 7px;
    cursor: pointer;
}
.brand-select option { background: var(--brand-bg-raised); color: var(--brand-text); }

/* ---- the stitch seam, matching .lb-field ------------------------------- */
.brand-input:focus,
.brand-textarea:focus {
    background-image: var(--lb-stitch);
    background-repeat: no-repeat;
    background-position: 11px 100%;
    background-size: calc(100% - 22px) 2px;
}
.brand-select:focus {
    background-image: var(--lb-chevron), var(--lb-stitch);
    background-repeat: no-repeat, no-repeat;
    background-position: right 1rem center, 11px 100%;
    background-size: 11px 7px, calc(100% - 22px) 2px;
}

/* ---- file inputs -------------------------------------------------------- */
.brand-input[type="file"],
.file-input[type="file"] {
    width: 100%;
    box-sizing: border-box;
    padding: .6rem .7rem;
    background: var(--lb-well);
    border: 1px dashed rgba(212, 175, 55, .3);
    border-radius: var(--brand-radius);
    color: var(--brand-text-muted);
    font-family: var(--brand-font);
    font-size: .88rem;
    cursor: pointer;
    box-shadow: none;
}
.brand-input[type="file"]:hover,
.file-input[type="file"]:hover {
    background: var(--lb-well-hover);
    border-color: rgba(212, 175, 55, .55);
}
.brand-input[type="file"]:focus,
.file-input[type="file"]:focus {
    outline: none;
    background-image: none;
    border-color: var(--brand-gold);
    box-shadow: 0 0 0 3px rgba(212, 175, 55, .13);
}
.brand-input[type="file"]::file-selector-button,
.file-input[type="file"]::file-selector-button {
    margin-right: .85rem;
    padding: .5rem 1rem;
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .35);
    border-radius: 7px;
    color: var(--brand-gold);
    font-family: var(--brand-font);
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .16s ease;
}
.brand-input[type="file"]::file-selector-button:hover,
.file-input[type="file"]::file-selector-button:hover { background: rgba(212, 175, 55, .2); }
.brand-input[type="file"]::-webkit-file-upload-button,
.file-input[type="file"]::-webkit-file-upload-button {
    margin-right: .85rem;
    padding: .5rem 1rem;
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .35);
    border-radius: 7px;
    color: var(--brand-gold);
    font-family: var(--brand-font);
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    cursor: pointer;
}

/* ---- radios / checkboxes on the order forms ---------------------------- */
/* These are bare <input type="radio" class="mr-2"> with no brand class, so
   they are scoped by the page's body class instead. body.acct-body is listed
   because the four send* order forms moved onto the customer-panel shell and
   now carry that class; without it their radios fell back to the native blue
   accent. The account pages' own controls were relying on nothing at all. */
body.brand-page input[type="radio"],
body.brand-page input[type="checkbox"],
body.acct-body input[type="radio"],
body.acct-body input[type="checkbox"],
body.lb-page input[type="radio"],
body.lb-page input[type="checkbox"] {
    accent-color: var(--brand-gold);
    width: 1.05rem;
    height: 1.05rem;
    vertical-align: -2px;
    cursor: pointer;
}

/* ---- autofill guard, matching .lb-field -------------------------------- */
.brand-input:-webkit-autofill,
.brand-input:-webkit-autofill:hover,
.brand-input:-webkit-autofill:focus,
.brand-textarea:-webkit-autofill {
    -webkit-text-fill-color: var(--brand-text);
    -webkit-box-shadow: inset 0 0 0 100px var(--brand-bg-raised);
    caret-color: var(--brand-text);
    transition: background-color 5000s ease-in-out 0s;
}

/* ==========================================================================
   Patch quote form -- the THIRD form generation (product-template.php)
   --------------------------------------------------------------------------
   The storefront has three parallel form vocabularies, not two. This one is
   `.form-row` / `.form-input` / `.form-select` from style.css:2265-2345, and it
   serves the patch quote form on all five patch pages via
   product-template.php. Its rows were bare grid cells with no field wrapper
   and no labels at all, so a row with an odd child count left a visible hole
   and every control leaned on its placeholder for a name.

   EVERY RULE HERE IS `.quote-form`-SCOPED, deliberately. `.form-row` has seven
   live consumers -- account/profile.php, account/editOrder.php, sendOrder.php,
   sendPatchOrder.php, sendQuote.php, sendVectorOrder.php and this template --
   so an unscoped `.form-row` or `.form-field` change would silently restyle
   six other pages that were never measured. Scoping also lets these rules win
   on specificity without `!important`.

   Living in site.css rather than style.css is what makes `--brand-*` tokens
   available; style.css predates the brand token layer.
   ========================================================================== */
.quote-form .form-field { min-width: 0; }

/* Same job as .lb-field--full on the modern forms, same name shape, so the two
   generations stay learnable as one system. Name needs it because it sits alone
   on its row: measured at 1440, a lone child in the 2-track row left the second
   260px track empty, and an empty track next to a filled one reads as a field
   that failed to render rather than as spacing. */
.quote-form .form-field--full { grid-column: 1 / -1; }

/* Mirrors .lb-field > label at site.css:1578-1586 declaration for declaration.
   The point is that a customer moving between the pricing quote form and a
   patch quote form should not be able to tell they are looking at two
   different stylesheets. */
.quote-form .form-field > label {
    display: block;
    margin-bottom: .4rem;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--brand-text-muted);
}

/* Width x Height x Quantity is one logical group -- a patch spec -- so it gets
   a 3-track row instead of being split across a 2-track row plus an orphan.
   Measured: at the form's 435.4px inner width three tracks give ~122px each,
   which holds a 62px uppercase label above a number input without clipping
   either. Below 640px style.css:2266 already collapses .form-row to 1fr and
   this inherits that, so the trio stacks on mobile like every other row. */
@media (min-width: 640px) {
    .quote-form .form-row--trio { grid-template-columns: repeat(3, 1fr); }
}

/* The select wrapper is what carries the chevron background-image, so it has to
   fill its field cell now that a label sits above it rather than beside it. */
.quote-form .form-field .form-select { display: block; }

/* ==========================================================================
   Contact channel cards (contact.php, index.php, services.php, aboutus.php)
   --------------------------------------------------------------------------
   These four cards were markup without a stylesheet. Measured 2026-08-24 in
   Chromium at 1440x900:

     - getComputedStyle(.contact-item).color returned rgb(0, 0, 238). That is
       the browser's default unvisited-link colour, #0000EE, rendering blue and
       underlined on a #0a0a0a page. The email and phone cards are <a> elements
       and nothing had ever set a colour on them.
     - .lb-contact-cards had no rule at all, so display computed to "block" and
       the four cards stacked full width at 1152px each - a 4-row list where a
       2-up grid belongs.
     - Each card's inner <div> held <strong>label</strong><span>value</span> as
       inline siblings with no separator, so they rendered welded together:
       "Email Ussupport@libertydigitizing.com", "Working HoursMon-Sat",
       "CoverageServing clients in 28+ countries worldwide".

   SESSION 6 - the fix above was scoped `.lb-contact-cards .contact-item`, and
   only contact.php has that wrapper. The other three pages emit the identical
   markup shape (<a class="contact-item"><i/><div><strong/><span/></div></a>)
   and got none of it, so all three still rendered #0000EE blue with the label
   welded to the value. Confirmed live at 375px: index.php painted 11 elements
   at rgb(0,0,238), and the 1440px sweep read `a.contact-item 392x26` with text
   "Emailsupport@libertydi" on both services.php and aboutus.php.

   The component rules are therefore now UNSCOPED - one component, four pages,
   one rule set. Only the two genuinely presentational bits stay conditional,
   and they key off `.lb-card` in the markup rather than off an ancestor:

     contact.php   <a class="contact-item lb-card">   standalone card  -> padding + lift
     index.php     <a class="contact-item lb-card">   standalone card  -> padding + lift
     services.php  <a class="contact-item">           row INSIDE a card -> layout only
     aboutus.php   <a class="contact-item">           row INSIDE a card -> layout only

   That distinction matters: on services.php and aboutus.php these links are
   rows within a "Direct Lines" .lb-card, so 1.5rem of card padding and a
   translateY lift on hover would be a card lifting inside a card.

   The one .contact-item rule that did exist (style.css:3000) targets
   .contact-icon and .contact-details children. Zero pages emit that markup, so
   that block is dead except for its own `.contact-item` flex declaration, which
   these rules restate. Left in place rather than deleted; site.css loads after
   style.css, so the values here win.

   The icon tile deliberately reuses .lb-benefit-icon's exact values (44px, 12px
   radius, gold glyph on rgba(212,175,55,.1) with a .25 border) rather than
   introducing a second icon treatment - the site already had this vocabulary.

   Not changed: the 176px between the alert and the footer is 88px of
   .lb-section padding-bottom plus the footer's own top padding, which is the
   shared section rhythm on every page, not a defect here. The 760px hero is
   .lb-page-hero, shared by 11 pages.

   Compatibility: plain CSS only - no :has(), :is(), nesting or container
   queries, so this parses on the older production stack.
   ========================================================================== */

.lb-contact-cards {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.1rem;
}
@media (min-width: 760px) {
    .lb-contact-cards { grid-template-columns: 1fr 1fr; }
}

/* index.php's side column had no rule of any kind, so its three cards touched
   with 0px between them. Single column on purpose: it sits beside the form at
   desktop and is far too narrow for the 2-up above. */
.lb-contact-side {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.1rem;
    align-content: start;
}

.contact-item {
    display: flex;
    align-items: flex-start;
    gap: 1.1rem;
    color: var(--brand-text);
    text-decoration: none;
}

/* Standalone card presentation. Denser than .lb-card's 1.9rem/1.7rem: these are
   compact two-line info rows, not full content cards. */
.contact-item.lb-card {
    padding: 1.5rem 1.6rem;
}

/* Bare rows inside someone else's card still need vertical rhythm between
   siblings; the card supplies the surface, not the spacing. */
.contact-item + .contact-item {
    margin-top: .35rem;
}
.contact-item.lb-card + .contact-item.lb-card {
    margin-top: 0;   /* the grid gap already owns this */
}

/* The bare <i> carries the tile. */
.contact-item > i {
    flex: none;
    width: 44px;
    height: 44px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    color: var(--brand-gold);
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .25);
    transition: background-color .22s ease, border-color .22s ease;
}

/* Stack label over value so they stop welding together. min-width:0 lets the
   long email wrap instead of forcing the grid column wider. */
.contact-item > div {
    display: flex;
    flex-direction: column;
    gap: .3rem;
    min-width: 0;
}
.contact-item strong {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--brand-text-muted);
}
.contact-item span {
    color: var(--brand-text);
    line-height: 1.5;
    /* support@libertydigitizing.com has no break opportunity of its own. */
    overflow-wrap: anywhere;
}

/* Only the email and phone cards are links; the hours and coverage cards are
   plain <div>s and must not pretend to be clickable. */
a.contact-item:hover span,
a.contact-item:focus-visible span { color: var(--brand-gold); }
a.contact-item:hover > i,
a.contact-item:focus-visible > i {
    background: rgba(212, 175, 55, .18);
    border-color: rgba(212, 175, 55, .45);
}
a.contact-item:focus-visible {
    outline: 2px solid var(--brand-gold);
    outline-offset: 3px;
}
/* The lift belongs to the standalone card only - a row lifting inside its own
   parent card reads as a rendering fault. */
a.contact-item.lb-card:hover,
a.contact-item.lb-card:focus-visible {
    border-color: rgba(212, 175, 55, .45);
    transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
    a.contact-item.lb-card:hover,
    a.contact-item.lb-card:focus-visible { transform: none; }
}

/* ==========================================================================
   Sports uniforms grid (index.php #sports-products)
   --------------------------------------------------------------------------
   `.sports-products-grid` had NO rule anywhere in the stylesheet stack. Its
   sibling `.products-grid` does (style.css:2414), which is why the Product
   Collection section laid out as a proper card grid while this one did not:
   with no rule the container fell back to display:block, and its seven
   .product-card children each stretched to the full 1152px container width.

   Measured in Chromium at 1440x900, index.php, before this rule:
                          #productsGrid        #sportsProductsGrid
     display              grid                 block
     gridTemplateColumns  264px x4             none
     gap                  32px                 normal
     cards / rows         9 / 3                7 / 7
     card size            264 x 593            1152 x 270
   Same section width, same card markup, opposite layout - the sports cards
   were letterboxing their product images across the full container.

   Columns and breakpoints mirror .products-grid exactly (4 / 2 / 1 at 1024px
   and 640px) so the two product sections behave identically rather than merely
   similarly. Seven cards fill 4+3; the section above fills 4+4+1, so a ragged
   final row is already the established look here.

   Note index.php is <body class="lb-page"> - NOT lb-tpl - so the 61
   `body.lb-tpl` product overrides in this file do not apply to it. The gap is
   therefore matched to style.css's var(--spacing-xl) (32px), not to the
   1.6rem that body.lb-tpl uses on all-products.php.

   Compatibility: plain CSS, no :has()/:is()/nesting/container queries.
   ========================================================================== */

.sports-products-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--spacing-xl, 2rem);
}
@media (max-width: 1024px) {
    .sports-products-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
    .sports-products-grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   index.php card media box - ragged row alignment
   --------------------------------------------------------------------------
   `.product-image-wrapper` is given no height, and `.product-image` is
   `width:100%; height:100%; object-fit:cover` (style.css:2470-2478). A
   percentage height against an auto-height parent has nothing to resolve
   against, so each image fell back to its own natural aspect ratio and every
   card's media box came out a different height. The cards themselves are equal
   height (grid stretch + height:100%) and the buttons align (.product-content
   uses justify-content:space-between), so the symptom was specifically the
   TITLES starting at different Y across a row.

   Measured in Chromium at 1440x900, index.php, before this rule - media box
   height per card, and the resulting title-top spread within each row:
     #products   row 1  208 208 208 208            spread   0px  (all square)
     #products   row 2  mixed naturals             spread  70px
     #sports     row 1  215 215 234 266            spread  69px
     #sports     row 2  215 267 218                spread  52px
   Row 1 of #products only looked right by luck: those four photos happen to
   be square. Any non-square photo broke the row.

   This is NOT a new treatment. `body.lb-tpl .product-image-wrapper` (line
   1414) already fixes exactly this with a box on a #191919 plate with a
   hairline bottom rule, and `.lb-pcard-media` (line 840) uses the identical
   values. index.php simply never received it, because it is
   <body class="lb-page"> with no lb-tpl.

   RATIO: 1/1, not the 4/3 those two rules use. 4/3 is a landscape box and
   these are photographs of hanging garments, which are portrait. Measured the
   four candidates on the seven sports photos by computing, per image, how much
   of the source object-fit:cover discards:

     ratio / fit      section  worst crop  avg crop  worst letterbox
     4 / 3   cover     1454px     44%        33%          0%
     1 / 1   cover     1557px     25%        11%          0%
     4 / 5   cover     1661px     20%        11%          0%
     1 / 1   contain   1557px      0%         0%         25%

   4/3 beheaded the American Football jersey and cut the hem off Basketball
   (44% of that source gone). 1/1 and 4/5 tie on average crop; 1/1 is 104px
   shorter per section and a square plate suits a catalog whose sources are
   mixed square and portrait, so 1/1 wins. `contain` was rejected: zero crop,
   but up to 25% of the box becomes empty plate, which reads as a broken image
   on a dark card.

   Scoped `body.lb-page:not(.lb-tpl)` on purpose: all-products.php is
   `lb-page lb-tpl` and product-template.php renders `lb-page lb-tpl`, and both
   already have the lb-tpl rule at their own 4/3. The :not() keeps this off
   them entirely instead of relying on source order between two rules of equal
   specificity. Verified across 19 product pages: index 208x156 -> 208x208,
   all-products still 212x159, the 17 stubs still 267x200.

   Compatibility: aspect-ratio and :not() only. Both are older than the
   `:has()` this stylesheet already ships at line 1582, so this adds no new
   floor. object-fit was already relied on by the pre-existing image rule.
   ========================================================================== */

body.lb-page:not(.lb-tpl) .product-image-wrapper {
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: #191919;
    border-bottom: 1px solid var(--brand-border);
}
/* The base rule leaves the img inline, which leaves a descender gap under it
   inside a fixed-ratio box. lb-tpl sets display:block for the same reason. */
body.lb-page:not(.lb-tpl) .product-image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}


/* ==========================================================================
   TWO CARD SYSTEMS STACKED ON ONE ELEMENT
   index.php (16 cards) and all-products.php (17 cards) both write
       class="product-card lb-card animate-on-scroll"
   which stacks two complete, independent card systems on the same element:

     .lb-card       (site.css:760)   a generic PROSE card. padding 1.9rem
                                     1.7rem, own border + 18px radius +
                                     background, plus .lb-card h3 / .lb-card p
                                     typography rules. Built to wrap text.
     .product-card  (style.css:2432) a STRUCTURAL card. flex column,
                                     overflow:hidden, height:100%, and expects
                                     .product-image-wrapper + .product-content
                                     children, where .product-content is what
                                     supplies the inner padding.

   .lb-card's prose padding therefore wraps the entire product structure. Three
   measured consequences, all visible:

     1. The media box is pushed 28px off the card edge, so a hard-cornered
        photo floats inside an 18px-rounded card. Measured media border-radius
        0px against card border-radius 18px. On this catalog that is loud:
        50 of 55 product photos across index / all-products / tshirts /
        basketball were shot on white (edge luminance 226-255 of 255), so each
        one reads as a bright slab with square corners inside a rounded dark
        card.
     2. The text column is inset twice - once by .lb-card (27.2px) and again by
        .product-content (24px) - leaving only 160px of the 264px card for
        text. That is why titles wrap to three lines and why the description
        top varies by up to 67px across a single row.
     3. all-products.php diverged from its own 17 stub siblings for no reason:
        it measured a 212x159 media box where basketball.php, tshirts.php and
        the rest all measure 267x200. Same renderer family, same lb-tpl rules -
        the only difference was this stray .lb-card padding.

   Zeroing the padding when both classes are present resolves all three at
   once, and does it by adopting the pattern the codebase already uses:
   body.lb-tpl .product-card (site.css:1388) declares NO padding, precisely so
   its media box sits flush and .product-content owns the inset.

   Why nothing else is needed here:
     - No border-radius on the media box. Flush + the card's existing
       overflow:hidden + its 18px radius clips the photo's top corners to the
       card automatically.
     - .lb-card::before (the 3px gold hover bar, site.css:769) now paints across
       the top edge of the flush photo. That is deliberate house behaviour, not
       a collision - body.lb-tpl .product-card::before does exactly the same
       thing on the lb-tpl pages, and the card's overflow:hidden clips the bar
       to the rounded corners.

   Specificity: .product-card.lb-card is (0,2,0) and beats .lb-card's (0,1,0),
   so it needs no !important. It cannot affect the many legitimate .lb-card
   prose cards elsewhere on the site, because it only matches elements carrying
   BOTH classes - which is only these two pages' product cards.

   Known 1px nit, deliberately left alone: the media box keeps its
   border-bottom, so with aspect-ratio resolving against the border box the
   image is 1px shorter than the box and a 1px line of plate shows above the
   divider. body.lb-tpl .product-image-wrapper has the identical 1px. Matching
   the existing pattern is worth more than a pixel; if it is ever fixed, fix
   both together.
   ========================================================================== */

.product-card.lb-card {
    padding: 0;
}

/* Reserve two lines for the product title so the description below it starts at
   the same Y across a row. Measured: with the padding fix in place, titles on
   index.php split exactly 8 one-line / 8 two-line at 1152px and up, which left
   the description top ragged by 34px - precisely one line - in three of four
   rows. Reserving the taller of the two closes it.

   2.8em is two lines exactly, not an eyeball: min-height in em resolves against
   the element's own font-size, so 2 x the heading's line-height ratio holds at
   both title sizes (index 20.48px, all-products 18.88px) without hardcoding a
   pixel value. The ratio is the 1.4 set on .lb-card h3 below, so this value is
   coupled to it - change one and the other must follow, or rows go ragged
   again. It was 3.3em while the ratio was the inherited 1.65.

   Two lines, not three: at 1040px - the narrowest 4-column width - two index
   titles do wrap to three lines. min-height is a floor, not a clamp, so those
   still render in full; they simply do not align at that one width. Reserving
   three lines instead would add a permanent empty line under the eight
   single-line titles at every width, which is the worse trade. Nothing is ever
   truncated. */
.product-card.lb-card .product-title {
    min-height: 2.8em;
}

/* Display headings need display leading. .lb-card h3 was inheriting the body's
   line-height of 1.65, which on a 20.48px Playfair Display title is 33.8px - so
   wrapped titles read as two loosely stacked lines rather than one heading.
   57 headings across 10 storefront pages inherit this (40 at 20.48px, 17 at
   18.88px); 33 of them wrap to two lines, so a third of them showed the defect.

   1.4 rather than the tighter value it looks like it wants, because the limit
   was measured, not guessed. Chromium Range rects give each line's font-metric
   box as 27px tall at this size, and the gap between consecutive boxes goes
   negative below 1.4 (harness lhtest.js, index.php):

     ratio   leading   gap between line boxes
     -----   -------   --------------------------------
     1.65    33.8px    +6.8px  (current: too loose)
     1.40    28.7px    +1.7px  <- chosen: tightest safe
     1.30    26.6px    -0.4px
     1.22    25.0px    -2.0px
     1.15    23.6px    -3.5px

   Those metric boxes are wider than the actual ink, so 1.3 would not visibly
   collide on most strings - but these 57 headings carry arbitrary product copy,
   and an ascender meeting a descender is only a matter of which words appear.
   1.4 is provably clear for any string, and still cuts the leading 15%.

   Row alignment is preserved: descTop and button-top spread both stay at 0px
   across all four index rows, because the title reservation above moved with
   this ratio. */
.lb-card h3 {
    line-height: 1.4;
}

/* Hold two product columns down to 501px instead of collapsing to one at 640px.

   The single-column phone layout was measured, not assumed, and it is bad: at
   640px each card is 592x806 with a 590px-tall photo taking 73% of it, so the
   viewport shows barely one product. Two columns at that width cuts index.php
   from a 25.9k document to 18.6k, and hotel-apparels.php from 29.8k to 14.1k
   (53% shorter), because the media box is square so its height tracks the card
   width.

   Where the break belongs was found in two passes, and the first pass was
   wrong. Candidate testing on index.php (harness mobbreak.js) pointed at 480px:

     candidate      520px          480px          430px
     ------------   ------------   ------------   -----------------------------
     break-at-560   1col 26,071    1col 25,682    1col
     break-at-480   2col 18,715    1col 25,682    1col
     break-at-420   2col           2col, 3-line   2col, 3-line + all 16 View
                                   titles         More buttons wrap to 2 lines

   But that pass injected `gap:1rem` while the real gutter on lb-tpl pages is
   1.6rem, and it stepped 520 -> 480 without testing between. Shipping 480 and
   re-measuring for real (breakverify.js, 6 pages) showed wrapped buttons at
   481px on five of them. The floor is exact (btnfloor.js):

     viewport   card    button box   label needs   result
     --------   -----   ----------   -----------   ---------------------------
     490px      205     155          153          1 line  (index, "View More")
     481px      201     151          153          2 lines
     490px      208     163          159          1 line  (stubs, "Add to Quote")
     481px      204     159          159          2 lines - equal is not enough

   So two columns are safe down to 490px and break at 481px. The rule stops at
   501px rather than 491px to leave ~9px of headroom: the label widths above are
   measured with Inter loaded, and a fallback face mid-load measures wider. The
   cost of the extra margin is that the 481-500px band renders single-column,
   which is 19px of viewport width almost nothing reports.

   Accepted, not a defect: at 520px two index and tshirts titles wrap to three
   lines. min-height is a floor rather than a clamp, so they render in full and
   only lose row alignment - the same trade already accepted at 1040px on
   desktop, and far cheaper than +7,000px of scrolling.

   Authored here rather than editing style.css because site.css is the last of
   the three sheets include/header.php links (:117-119), so an equal-specificity
   rule here beats style.css's `@media (max-width:640px) .products-grid {1fr}`
   without !important. The 500px block below restates 1fr because this file's
   640px block would otherwise carry two columns all the way down. */
@media (max-width: 640px) {
    .products-grid,
    .sports-products-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 500px) {
    .products-grid,
    .sports-products-grid {
        grid-template-columns: 1fr;
    }
}

/* Reset the browser's anchor underline on the four link-as-control components
   that never got one. This sheet and style.css already do this per component -
   .nav-item, .cart-btn, .footer-links a, .lb-crumbs a, .lb-mobile-link,
   .lb-card p a and a dozen more all carry their own `text-decoration: none` -
   but there is no base `a` rule anywhere in style.css, brand.css or site.css,
   so any component added later inherits the UA default. These four were missed.

   Measured across 12 storefront pages (harness decoaudit.js / decorest.js):

     component        count   decoration colour     what it looked like
     --------------   -----   -------------------   ---------------------------
     .product-btn      33     rgb(10,10,10)         black underline inside a
                                                    filled gold CTA
     .service-card      7     rgb(0,0,238)          browser-default blue, on a
                                                    whole card wrapped in <a>
     .service-btn       4     rgb(0,0,238)          browser-default blue
     .contact-item      7     rgb(0,0,238)          browser-default blue

   rgb(0, 0, 238) is the UA :link colour, which also proves those three set no
   `color` of their own - the decoration inherits currentColor, so a default
   blue underline means nothing had overridden it.

   Deliberately NOT included:
   - `a { text-decoration: none }`. A blanket reset would also strip the two
     genuine inline prose links, where the underline is the affordance rather
     than a bug. Those are handled properly above by the .lb-faq p a addition,
     which keeps an underline and makes it gold.
   - .lb-logo. It computes `underline rgb(0,0,238)` on all 12 pages and looks
     like a fifth instance of this bug, but it wraps a bare <img> with no text
     node of its own, so no underline is ever painted. Left alone rather than
     "fixed", since there is nothing to fix. */
.product-btn,
.service-btn,
.service-card,
.contact-item {
    text-decoration: none;
}

/* ==========================================================================
   Scroll-reveal vs hover: one `transition` shorthand, two jobs   (session 5)
   --------------------------------------------------------------------------
   `.animate-on-scroll` (line 1250) declares

       transition: opacity .7s ease, transform .7s ease;

   at specificity (0,1,0), later in the cascade than both `.product-card`'s
   `transition: all` (style.css:2437) and `.lb-card`'s own list (line 767). A
   `transition` shorthand is all-or-nothing: the winning declaration REPLACES
   the list rather than merging with it. So on every element carrying
   .animate-on-scroll, the scroll-reveal transition took over the component's
   hover transition, and any hover property outside opacity/transform had
   nothing to animate it.

   Measured across 9 storefront pages (harness aostrans.js): 24 of 28
   hover-capable .animate-on-scroll components had at least one un-eased
   property. Every one of them snapped the same two:

     component                        pages  snapped on hover
     ------------------------------   -----  --------------------------------
     .lb-card                           4    border-color, box-shadow
     .lb-card.lb-card--form             4    border-color, box-shadow
     .lb-card.lb-card--gold             3    border-color, box-shadow
     .product-card.lb-card              2    border-color, box-shadow
     .service-card.lb-card              2    border-color, box-shadow
     .contact-item.lb-card              2    border-color, box-shadow
     .lb-stat                           2    border-color
     .sports-product-card               1    border-color, box-shadow
     .pricing-card.lb-card              1    border-color, box-shadow
     .pricing-card--featured            1    border-color, box-shadow
     .testimonial-card.lb-card          1    border-color, box-shadow
     .stat-item                         1    border-color, box-shadow
     .portfolio-card                    1    border-color, box-shadow

   The only 4 that passed were the two `body.lb-tpl`-scoped components
   (.product-card at 1388, .service-card at 1604), which re-declare the list at
   (0,2,1) and so outrank .animate-on-scroll.

   Those 4 had the MIRROR defect instead: their list is `transform, box-shadow,
   border-color` with NO `opacity`, so their scroll-reveal FADE never ran.
   Measured frame by frame (harness reveal.js) on the reveal:

     page               opacity over 14 frames        verdict
     ----------------   ---------------------------   ---------------------
     index.php          0 .012 .031 ... .485 .532     fades correctly
     all-products.php   1 1 1 1 1 1 1 1 1 1 1 1 1 1   SNAPS 0->1, no fade
     basketball.php     1 1 1 1 1 1 1 1 1 1 1 1 1 1   SNAPS 0->1, no fade

   translateY animated on all three; only the fade was lost. That is all 19
   template pages revealing without a fade - 42 cards on hotel-apparels alone.

   One list fixes both directions. Three selectors: (0,1,0) for the general
   case, and two at (0,3,1) to unambiguously outrank the (0,2,1) lb-tpl rules
   rather than relying on a source-order tie-break.

   Durations, and the one real trade-off:

   - `transform` is used by BOTH jobs - the reveal slides translateY(28px)->0,
     the hover lifts 0->translateY(-8px/-15px). A shorthand can only give it one
     duration, so one of the two has to give. It was .7s, which meant the
     homepage's hover lift took 700ms; hover feedback past roughly 400ms reads
     as lag rather than response. The reveal is a one-time 28px slide and
     survives being tightened, so transform goes to .34s and the reveal slide
     tightens from 700ms to 340ms. That is a deliberate, measured trade, not an
     oversight: the sluggish hover was the worse of the two.
   - `.34s` also sits next to the .28s the lb-tpl cards already used, so the
     whole storefront now shares one hover tempo instead of two.
   - The easing is the curve already in use at 1393 and 1425, not a new one.
   - `opacity` stays slower than the slide at .5s, so the fade finishes ~160ms
     after the element settles - one gesture with a soft tail, rather than the
     700/340 split that would read as two separate events.

   Considered and rejected: giving the hover lift the independent `translate`
   property (Transforms Level 2) so it would not share a duration with the
   reveal's `transform`. It works, and it would preserve the 700ms reveal, but
   it means editing the hover rule of a dozen components across two stylesheets
   and it degrades silently to no lift at all on an older browser. Not worth it
   to save 360ms on a one-time animation.

   Reduced motion is unaffected: line 1261 sets `transition: none !important`
   on .animate-on-scroll, and !important beats every declaration here. */
.animate-on-scroll,
body.lb-tpl .product-card.animate-on-scroll,
body.lb-tpl .service-card.animate-on-scroll {
    transition: opacity .5s ease,
                transform .34s cubic-bezier(.22, .8, .36, 1),
                box-shadow .34s ease,
                border-color .34s ease;
}

/* ==========================================================================
   One card system: extend the Phase 2 elevation to the whole .lb-card family
   --------------------------------------------------------------------------
   `body.lb-tpl .product-card` (line 1388, headed "Phase 2: elevated product
   cards") gives a card a subtle top-lit gradient, the stronger border token,
   and a resting drop shadow. It reaches 26 of the storefront's 88 cards. The
   other 62 fall through to `.lb-card` (line 760), which is flat
   `var(--brand-surface)` with `var(--brand-border)` and no shadow at all.

   Audited across 11 pages (harness cardfam.js) - 88 elements, 12 variants:

     variant                              n   pages  gradient  resting shadow
     ----------------------------------   --   -----  --------  --------------
     product-card (lb-tpl pages)          26     2      YES          YES
     (bare .lb-card)                      11     8      no           no
     service-card                         11     2      no           no
     lb-card--form                         8     6      no           no
     product-card.sports-product-card      7     1      no           no
     contact-item                          7     2      no           no
     testimonial-card                      5     1      no           no
     lb-timeline-card                      5     1      no           no
     lb-card--gold                         4     4      own          no
     pricing-card                          2     1      no           no
     pricing-card--featured                1     1      no           no
     empty-cart                            1     1      no           no

   The line that settles it: `product-card.sports-product-card` on index.php is
   flat while `product-card` on the template pages is elevated. That is the SAME
   component rendering two different ways depending on which page it is on -
   objectively a divergence, not a design decision. 13.4 already recorded the
   rule for this situation: making two surfaces differ from each other is worse
   than either choice alone.

   So the values below are not new. They are copied verbatim from the Phase 2
   block at 1388-1412 so that a product card is now identical on both page
   families, and every other card joins the same system. Nothing here was
   invented; the only judgement made was which of the two existing treatments to
   unify on, and Phase 2 is both the more recent and the more deliberate.

   Three declarations, three different guards, for three different reasons:

   - `background` excludes `.lb-card--gold`, which sets its own radial gold
     wash at 1344. Note the gradient itself is nearly imperceptible - #151515 to
     #161616 is one step per channel. It is included for exact parity with the
     Phase 2 block rather than for visible effect; the border and the shadow are
     what actually change how these cards read.
   - `border-color` additionally excludes `.lb-card--form`, whose gold .22
     border (line 1343) is deliberate. Both variants are (0,1,0) and sit at 1343
     and 1344, so a bare `.lb-card` appended here would outrank them on source
     order and quietly turn eight form panels and four gold cards white-bordered.
     `:not()` contributes its argument's specificity, so these land at (0,2,0)
     and (0,3,0) - which also means they outrank `body.lb-tpl .product-card`
     (0,2,1) on class count. Harmless, because the value is the same one.
   - `box-shadow` needs no guard: nothing in the family sets one.

   The `.lb-card:hover` at 780 already lifted every one of these cards; this
   only brings its three values into line with Phase 2 (-6px to -8px, gold .38
   to .45, and the second gold-tinted shadow layer that Phase 2 added).

   Reduced motion: line 1261 kills the scroll-reveal transition but not a hover
   transform, so these cards jumped rather than eased under `reduce` - true
   before this change too, at -6px. Guarded below, following the pattern already
   used for `.contact-item` at 2190. The lb-tpl selectors are repeated there
   because (0,3,1) outranks `.lb-card:hover` inside the media query as well. */
.lb-card:not(.lb-card--gold) {
    background: linear-gradient(180deg, #151515, var(--brand-surface));
}
.lb-card:not(.lb-card--gold):not(.lb-card--form) {
    border-color: var(--brand-border-strong);
}
.lb-card {
    box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}
.lb-card:hover {
    transform: translateY(-8px);
    border-color: rgba(212, 175, 55, .45);
    box-shadow: 0 30px 60px rgba(0, 0, 0, .55), 0 6px 22px rgba(212, 175, 55, .10);
}
@media (prefers-reduced-motion: reduce) {
    .lb-card:hover,
    body.lb-tpl .product-card:hover,
    body.lb-tpl .service-card:hover { transform: none; }
}

/* =========================================================================
   Storefront sweep, 2026-08-25. Every rule below replaces a MEASURED defect.
   Appended (not edited in place) so the older generations stay readable; this
   sheet is linked last in include/header.php so it wins at equal specificity.
   ========================================================================= */

/* ---------- 1. portfolio.php had no grid at all ----------
   portfolio.php:91 emits `class="lb-portfolio-grid"`. That class had ZERO
   rules in site.css, style.css, or any inline block - verified by grep across
   the whole tree. style.css:2923 defines `.portfolio-grid` (no `lb-` prefix),
   so the markup and the stylesheet never met. The div fell back to `display:
   block` and all 39 cards stacked at full container width.

   Compounding it, style.css:2944 `.portfolio-image` sets `width:100%` and
   `object-fit:cover` but NO height and NO aspect-ratio - so `cover` had no box
   to cover and every image rendered at its natural aspect scaled to 1152px.
   Measured before: document height 28,842px at 1440 wide.

   `contain` rather than `cover`: these are artwork proofs (logos, digitized
   designs, vector conversions). Cropping a logo to fill a tile destroys the
   thing being shown. Square tiles + contain + a dark mat shows each piece
   whole and turns mixed aspect ratios into one even wall. */
.lb-portfolio-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(268px, 100%), 1fr));
    gap: 1.4rem;
}
.lb-portfolio-grid .portfolio-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.lb-portfolio-grid .portfolio-image {
    width: 100%;
    aspect-ratio: 1 / 1;
    height: auto;
    object-fit: contain;
    padding: .85rem;
    background:
        radial-gradient(ellipse 70% 70% at 50% 0%, rgba(212, 175, 55, .05), transparent),
        #121212;
    border-bottom: 1px solid var(--brand-border);
    display: block;
}
.lb-portfolio-grid .portfolio-content {
    padding: .95rem 1.05rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: .2rem;
    flex: 1;
}
.lb-portfolio-grid .portfolio-title {
    font-family: var(--lb-serif);
    font-size: 1rem;
    line-height: 1.3;
    margin: 0;
}
.lb-portfolio-grid .portfolio-description {
    font-size: .8rem;
    line-height: 1.5;
    margin: 0;
}
@media (max-width: 600px) {
    .lb-portfolio-grid { grid-template-columns: repeat(2, 1fr); gap: .8rem; }
    .lb-portfolio-grid .portfolio-image { padding: .5rem; }
    .lb-portfolio-grid .portfolio-content { padding: .7rem .75rem .8rem; }
    .lb-portfolio-grid .portfolio-title { font-size: .88rem; }
    .lb-portfolio-grid .portfolio-description { font-size: .72rem; }
}

/* ---------- 2. sub-page hero ate the whole first screen ----------
   site.css:904 set `height: min(88vh, 760px); min-height: 560px` on
   .lb-page-hero, "mirroring index .lb-hero geometry". Mirroring was the
   mistake: index's hero is a 7-slide carousel and earns 88vh, while a sub-page
   hero holds a kicker, one headline and one line of copy. Measured 760px tall
   at 1440, 768 AND 390 - identical at every width, because `min-height:560px`
   floors it. On a 390x844 phone that leaves ~84px of content above the fold on
   all 30 sub-pages, so every page opened onto a near-empty screen.

   Now a band that scales: tall enough to hold the art direction, short enough
   that content is visible on arrival. 1440 -> ~430px, 390 -> ~300px. */
.lb-page-hero {
    height: auto;
    min-height: 0;
    padding: clamp(4.5rem, 9vh, 7rem) 0 clamp(3.2rem, 6vh, 4.5rem);
    display: flex;
    align-items: center;
}
.lb-page-hero > .slide-content {
    position: relative;
    height: auto;
    min-height: 0;
    display: block;
    width: 100%;
}
/* the hero's gold hairline sat at `bottom: 1.8rem` against a 760px box; keep it
   proportionate now that the box is ~430px */
.lb-page-hero::after { bottom: 1.15rem; }

/* ---------- 3. product copy was faded out through its own second line ----------
   site.css:1638 clamped .product-description to `max-height: 3.1em` - exactly
   two lines at `line-height: 1.55` - then applied
   `mask-image: linear-gradient(180deg, #000 62%, transparent)`.
   62% of 3.1em is 1.92em, which lands 24% INTO line two, so line two was
   faded across its entire glyph height. Measured on tshirts.php: "High-quality
   premium white t-shirt with superior fabric. Perfect" ended mid-sentence in a
   smear. A reader cannot tell a masked line from a rendering fault.

   line-clamp truncates on a word boundary and appends a real ellipsis, so the
   cut reads as an editorial decision instead of a bug. Firefox 68+, Chrome and
   Safari all support -webkit-line-clamp; where it is unsupported the
   `overflow:hidden` + max-height fallback still bounds the box. */
body.lb-tpl .product-description {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    max-height: 3.1em;
    overflow: hidden;
    -webkit-mask-image: none;
    mask-image: none;
}

/* Card height agreed within a row but not between pages: measured 401px on
   hoodies, 432 on tshirts, 497 on hockey, 513 on index, 564 on patches - a
   163px spread on the same `.products-grid`, caused by titles wrapping to one
   line on some pages and two on others. Reserving two lines makes the grid
   rhythm hold when a visitor moves between category pages. */
body.lb-tpl .product-title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    min-height: 2.6em;
}

/* Same class, two gaps: `body.lb-tpl .products-grid` (site.css:1572) set
   1.6rem = 25.6px, while index.php is not an lb-tpl page so it inherited
   style.css's `--spacing-xl` = 32px. Measured 26 vs 32 on the same
   `.products-grid`. One value, so the column rhythm survives navigation. */
body.lb-tpl .products-grid,
body.lb-page .products-grid,
body.lb-page .sports-products-grid { gap: 1.75rem; }
@media (max-width: 600px) {
    body.lb-tpl .products-grid,
    body.lb-page .products-grid,
    body.lb-page .sports-products-grid { gap: 1rem; }
}

/* ---------- 1b. the portfolio wall was half white holes ----------
   26 of the 39 portfolio sources are artwork scanned or exported on a white
   sheet (measured earlier across the image set: 77% of product/portfolio
   photography is white-plate, 26 files at a flat lum=255, sd=0). Dropped onto
   a near-black tile with `object-fit: contain`, each one read as a blank white
   card - rows 3 to 6 of the gallery were white rectangles.

   Rather than crop the plate away (which would cut the artwork these tiles
   exist to show) every piece is now matted on the same warm paper. A dark
   gallery wall hung with paper mats is the treatment a print shop actually
   uses: the white-sheet exports blend into the mat and stop reading as holes,
   and the dark-background illustrations read as prints laid on paper. One
   treatment for all 39, so nothing depends on knowing an image's background. */
.lb-portfolio-grid .portfolio-image {
    background: linear-gradient(160deg, #f7f4ee 0%, #ebe6dc 100%);
    padding: 1rem;
    border-bottom: 1px solid rgba(212, 175, 55, .22);
    box-shadow: inset 0 -14px 26px -18px rgba(0, 0, 0, .45);
}
.lb-portfolio-grid .portfolio-card {
    background: var(--brand-bg-raised, #141414);
    border: 1px solid var(--brand-border);
    border-radius: 14px;
    transition: transform .4s cubic-bezier(.22, .8, .36, 1),
                border-color .3s ease, box-shadow .4s ease;
}
.lb-portfolio-grid .portfolio-card:hover {
    transform: translateY(-6px);
    border-color: rgba(212, 175, 55, .5);
    box-shadow: 0 26px 52px rgba(0, 0, 0, .55), 0 4px 18px rgba(212, 175, 55, .12);
}
/* style.css:2950 scales .portfolio-image 1.1x on hover. Inside a matted tile
   that pushes the artwork under the mat's own padding, so the zoom is dropped
   in favour of lifting the whole card. */
.lb-portfolio-grid .portfolio-card:hover .portfolio-image { transform: none; }
@media (max-width: 600px) {
    .lb-portfolio-grid .portfolio-image { padding: .6rem; }
}

/* ==========================================================================
   Service card footer line, and the two pricing card systems
   --------------------------------------------------------------------------
   SESSION 6. Three separate "markup shipped without a stylesheet" faults,
   all found by reading computed styles on the live pages in Chromium.

   FAULT 1 -- .service-price had ZERO rules anywhere in the codebase. It is a
   <span> inside <a class="service-card">, so it inherited the browser's own
   unvisited-link colour and painted rgb(0, 0, 238) -- raw #0000EE -- seven
   times on index.php, on a near-black page.

   Why nothing caught it earlier: .service-title and .service-description DO
   have unscoped colour rules (style.css:2671 and :2678), so they render
   correctly and the card looks finished. Only the price line was left to
   inherit. Both places a fix would naturally have landed also miss it --
   every .service-card rule in site.css:1802-1848 is `body.lb-tpl`-scoped
   while index.php is `body class="lb-page"`, and the documented link-colour
   fix at site.css:2776 set text-decoration on .service-card but never color.

   The seven values are mixed: "From $5/design" and "From $10/artwork" are
   real prices, but "Factory-direct pricing", "Custom quotes" and "Monthly
   plans" are not. So this is not styled as a price -- it is a rate hint, one
   line saying what the commercial shape of the service is. Treated as a card
   footer band above a hairline, which also gives seven cards with 1-2 line
   descriptions one shared bottom edge instead of a ragged one.

   FAULT 2 -- index.php's three tier cards use .lb-pricing-grid, .plan-name,
   .plan-price, .currency, .period, .plan-features, .popular-badge and
   .pricing-card--featured. All eight had ZERO rules. Measured consequences:
     - .lb-pricing-grid computed display:block, so the three cards stacked
       full width at every viewport instead of sitting 3-up.
     - .plan-features never got list-style:none, so every feature row drew
       BOTH a native <li> bullet AND its own <i class="fas fa-check"> glyph.
       Two markers per line, on twelve lines.
     - .popular-badge rendered as bare body text reading "Most Popular"
       above the plan name, with nothing making it a badge.
     - .plan-price / .currency / .period had no size or baseline, so
       "$5/ design" ran together at body size where a price should dominate.

   FAULT 3 -- .pricing-btn (style.css:2902) was authored for a <button>
   driven by openServicePopup(). That function does not exist anywhere in the
   codebase any more, so pricing.php uses anchors. An anchor needs three
   declarations a button gets for free.

   Two cascade facts this block has to respect, both verified by reading the
   rules rather than assumed:

     a) .lb-card sets `overflow: hidden` (site.css:964). A badge positioned to
        straddle the card's top edge would have its top half clipped. So
        .popular-badge is a tab that hangs INSIDE the top edge -- which needs
        no override, and reads as attached to the card rather than floating
        over it. All three cards in the grid get the same extra top padding so
        the tab has clearance AND the three price blocks stay on one line;
        padding only the featured card would drop its price ~10px below its
        neighbours.

     b) `.lb-card:not(.lb-card--gold):not(.lb-card--form)` (site.css:2940) sets
        border-color at specificity (0,3,0). A plain `.pricing-card--featured`
        is (0,1,0) and would silently lose, so the featured rule is written
        `.lb-pricing-grid .pricing-card--featured.lb-card` -- also (0,3,0),
        winning on source order because it is later in the file.

   Compatibility: plain CSS only. No :has(), :is(), nesting or container
   queries, so this parses on the older production stack.
   ========================================================================== */

/* --- FAULT 1: service card footer ---------------------------------------- */

/* Unscoped on purpose: index.php is body.lb-page and the whole
   `body.lb-tpl .service-card` block above never reaches it. Setting colour on
   the anchor itself stops any future child inheriting UA blue the way
   .service-price did. */
.service-card {
    color: var(--brand-text);
    /* column + margin-top:auto on the footer is what equalises card bottoms */
    display: flex;
    flex-direction: column;
}
.service-card .service-description { flex: 1 1 auto; }

.service-price {
    display: block;
    margin-top: auto;
    padding-top: .9rem;
    border-top: 1px solid rgba(212, 175, 55, .16);
    color: var(--brand-gold);
    font-size: .82rem;
    font-weight: 600;
    letter-spacing: .01em;
}
a.service-card:hover .service-price,
a.service-card:focus-visible .service-price {
    border-top-color: rgba(212, 175, 55, .4);
}

/* --- FAULT 2: index.php tier cards --------------------------------------- */

.lb-pricing-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.4rem;
    align-items: stretch;
}
@media (min-width: 720px) {
    .lb-pricing-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
    .lb-pricing-grid { grid-template-columns: repeat(3, 1fr); }
}

/* Flex column so the CTA pins to the bottom edge of every card regardless of
   feature-list length. padding-top clears the "Most Popular" tab -- applied to
   all three so the price line stays level across the row. (0,2,0) beats
   .lb-card's own padding at (0,1,0). */
.lb-pricing-grid .pricing-card {
    display: flex;
    flex-direction: column;
    padding-top: 2.6rem;
}

/* "See Full Price List" sat at exactly 0px below the card row (measured, both
   widths): .lb-center carries no top margin and the grid contributes none.
   Scoped to the adjacent sibling so .lb-center keeps its zero default
   everywhere else it is used. Adjacent-sibling is CSS2 -- safe on the old
   production PHP/browser floor, unlike :has(). */
.lb-pricing-grid + .lb-center {
    margin-top: 2.2rem;
}

.popular-badge {
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    padding: .3rem .95rem;
    border-radius: 0 0 11px 11px;
    background: linear-gradient(135deg, #d4af37, #b8941f);
    color: #0a0a0a;
    font-size: .64rem;
    font-weight: 800;
    letter-spacing: .14em;
    text-transform: uppercase;
    white-space: nowrap;
}

.plan-name {
    font-size: .73rem;
    font-weight: 700;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--brand-text-muted);
    text-align: center;
}
.plan-price {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: .08rem;
    margin: .5rem 0 1.35rem;
    font-family: var(--lb-serif);
    font-size: 3rem;
    line-height: 1;
    font-weight: 700;
    color: var(--brand-text);
}
/* The numeral is a bare text node between the two spans, so it becomes its own
   flex item -- gap applies between all three.
   The $ needs its own right margin rather than a wider container gap, because
   that gap also sets the numeral -> .period distance, which measures a healthy
   4.1px and must not move. Sized from ink, not from the em box: Playfair's $ has
   a NEGATIVE right sidebearing (ink overhangs its advance by .4px) and the
   digits carry only 1px of left bearing, so the .08rem gap left just 1.9px of
   real air beside a 48px numeral and read as touching. .2rem takes it to ~5px. */
.plan-price .currency {
    font-size: 1.3rem;
    font-weight: 600;
    color: var(--brand-gold);
    /* lift the $ to the numeral's cap height instead of its baseline */
    align-self: flex-start;
    margin-top: .34rem;
    margin-right: .2rem;
}
.plan-price .period {
    font-family: var(--lb-sans);
    font-size: .8rem;
    font-weight: 500;
    letter-spacing: .01em;
    color: var(--brand-text-muted);
    margin-left: .18rem;
}

/* The double-marker fix: drop the native bullet, keep the check glyph. */
.plan-features {
    list-style: none;
    margin: 0 0 1.5rem;
    padding: 0;
    display: grid;
    gap: .62rem;
    flex: 1 1 auto;
}
.plan-features li {
    display: flex;
    align-items: flex-start;
    gap: .6rem;
    font-size: .88rem;
    line-height: 1.45;
    color: var(--brand-text-muted);
}
.plan-features li i {
    flex: none;
    margin-top: .3em;
    font-size: .7rem;
    color: var(--brand-gold);
}

/* (0,3,0), later in file than site.css:2940 -- see cascade note (b) above. */
.lb-pricing-grid .pricing-card--featured.lb-card {
    border-color: rgba(212, 175, 55, .42);
    box-shadow: 0 18px 46px rgba(0, 0, 0, .5), 0 4px 18px rgba(212, 175, 55, .08);
}

/* --- FAULT 3: pricing.php rate sheet ------------------------------------- */

/* Four cards, so 2-up before 4-up. .pricing-grid's own
   repeat(auto-fit, minmax(300px, 1fr)) resolves to 3 columns inside a 1152px
   container and strands the fourth card alone on a second row. */
.lb-pricing-4 { grid-template-columns: 1fr; }
@media (min-width: 720px) {
    .lb-pricing-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1180px) {
    .lb-pricing-4 { grid-template-columns: repeat(4, 1fr); }
}

/* These four cards deliberately do NOT carry .lb-card. Adding it would drag in
   `.lb-card h3` and `.lb-card p` (site.css:993-994), which outrank
   .pricing-title and .pricing-subtitle at (0,1,1) vs (0,1,0) and would quietly
   restyle the card headers. Instead the design-system surface is restated here
   at (0,2,0) so these cards match every other card on the site -- same
   gradient, radius, border and hover as .lb-card -- without that interference.
   .pricing-card's own gradient is 135deg/20px radius, which is the only card on
   the site that would have looked different. */
.lb-pricing-4 .pricing-card {
    display: flex;
    flex-direction: column;
    background: linear-gradient(180deg, #151515, var(--brand-surface));
    border-color: var(--brand-border-strong);
    border-radius: 18px;
    padding: 1.9rem 1.7rem;
    box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}
.lb-pricing-4 .pricing-card:hover {
    transform: translateY(-8px);
    border-color: rgba(212, 175, 55, .45);
    box-shadow: 0 30px 60px rgba(0, 0, 0, .55), 0 6px 22px rgba(212, 175, 55, .10);
}
/* Rate rows take the slack so the four CTAs sit on one line. The services have
   six rows each today, but the labels wrap differently at 2-up. */
.lb-pricing-4 .pricing-body {
    flex: 1 1 auto;
    margin-bottom: 1.5rem;
}
.lb-pricing-4 .pricing-header { margin-bottom: 1.2rem; }
.lb-pricing-4 .pricing-title { font-size: 1.22rem; }
.lb-pricing-4 .pricing-service { font-size: .9rem; }
/* Anchor the price to the label's FIRST baseline, not the row's centre.
   "New Website Development" needs 185.5px against 150.3px available -- 35px
   short, so it wraps to two lines and no type-size tweak can prevent it (14.4px
   would have to drop to ~11.7px). With align-items:center the price then floats
   midway between the two lines and reads as a centring accident; on the first
   baseline it reads as belonging to the item. Also correct for the single-line
   rows, where label and price differ in size and centring misaligns baselines.
   ACCEPTED IMPERFECTION: card 4's rows still sit up to 18.6px lower than cards
   1-3 (measured). Forcing cross-card row alignment needs subgrid or a fixed
   min-height on every row -- the first is outside the production browser floor,
   the second pads all 24 rows to two lines. The four CTAs do stay aligned
   (measured identical), which is the alignment the eye actually tracks. */
.lb-pricing-4 .pricing-item { align-items: baseline; }

/* The three things an <a> does not inherit from a rule written for <button>. */
a.pricing-btn {
    display: block;
    text-align: center;
    text-decoration: none;
}

/* The two digitizing footnotes used .lb-center plus an inline max-width:560px.
   .lb-ticks is a grid whose li is padding-left:1.5rem with an absolutely
   positioned glyph at left:0, so centring the TEXT stranded every tick at the
   far left of a 560px box while the words sat in the middle. Centre the block
   and let each row stay left-aligned against its own tick. */
.lb-ticks--inline {
    justify-content: center;
    max-width: 34rem;
    margin-left: auto;
    margin-right: auto;
    /* .lb-ticks ships `margin: 0 0 1.4rem` -- bottom only -- so the footnotes
       started at exactly 0px below the rate-card row (measured, both widths).
       2.2rem sits just under the 41.6px this section already uses between its
       head and the grid, so the footnotes read as trailing the cards rather
       than as a new block. */
    margin-top: 2.2rem;
}
.lb-ticks--inline li { text-align: left; }

/* ==========================================================================
   services.php: the process steps and the turnaround table
   --------------------------------------------------------------------------
   SESSION 6, continued. Two more orphaned-markup cases on the same page, both
   confirmed by grepping every stylesheet for the class names:

     .lb-steps / .lb-step / .lb-step-num   -- zero rules
     .lb-times                             -- zero rules

   What that produced on the page:
     - "Four Steps To Done" rendered as four stacked blocks of bare text: an
       unstyled "1", then a default <h4>, then a default <p>. Nothing marked it
       as a sequence and nothing set it in a row.
     - "Turnaround Times" is <li><span>label</span><strong>value</strong></li>.
       With no rule, the span and strong are inline siblings with no separation,
       so they welded into "Digitizing2-12 hrs" and "Vector art4-24 hrs" -- and
       the <ul> still drew its native bullet.

   The numbering is kept because this content genuinely is a sequence: send ->
   quote -> approve -> receive, in that order, and the order is information the
   reader needs. The connector hairline between badges is what makes the
   sequence literal rather than decorative, so it only appears in the 4-up
   layout where the badges actually form a row; stacked, the numbers carry it
   alone and a connector would cut through the copy.

   The steps deliberately are NOT cards. This section sits between two
   card-heavy sections, and a third row of bordered boxes flattens the page's
   rhythm -- the badge and the hairline are enough structure.

   Plain CSS only: no :has(), :is(), nesting or container queries.
   ========================================================================== */

.lb-steps {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.6rem;
}
@media (min-width: 640px) {
    .lb-steps { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
    .lb-steps { grid-template-columns: repeat(4, 1fr); }
}

.lb-step {
    position: relative;
    min-width: 0;
}

/* 42px circle echoing the .contact-item icon tile (same gold wash and border),
   but round rather than 12px-radius so it reads as a node in a sequence
   instead of an icon chip. */
.lb-step-num {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: rgba(212, 175, 55, .1);
    border: 1px solid rgba(212, 175, 55, .3);
    font-family: var(--lb-serif);
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1;
    color: var(--brand-gold);
}

.lb-step h4 {
    margin: 1rem 0 .4rem;
    font-family: var(--lb-serif);
    font-size: 1.08rem;
    font-weight: 600;
    color: var(--brand-text);
}
.lb-step p {
    margin: 0;
    font-size: .9rem;
    line-height: 1.6;
    color: var(--brand-text-muted);
}

/* Connector: from the right edge of this badge across the grid gap to the next
   badge. `right: -1.6rem` is the gap set above -- if that gap changes, this
   changes with it. Only in the 4-up layout, and never after the last step.
   The ramp ends at .14, not .06: over 240px of rail, .06 alpha on #0A0A0A is
   effectively invisible, so the last third dropped out and the timeline read as
   three segments trailing off rather than four linked nodes. It still fades
   left-to-right -- the section reads in that direction -- just never to nothing. */
@media (min-width: 1024px) {
    .lb-step::after {
        content: '';
        position: absolute;
        top: 21px;
        left: 54px;
        right: -1.6rem;
        height: 1px;
        background: linear-gradient(90deg, rgba(212, 175, 55, .32), rgba(212, 175, 55, .14));
    }
    .lb-step:last-child::after { display: none; }
}

/* --- Turnaround table ----------------------------------------------------- */

/* Same label -> value shape as .pricing-item, so it is set the same way: kill
   the native bullet, push the pair to opposite edges, hairline between rows. */
.lb-times {
    list-style: none;
    margin: 0;
    padding: 0;
}
.lb-times li {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: .62rem 0;
    border-bottom: 1px solid var(--brand-border);
}
.lb-times li:last-child {
    border-bottom: none;
    padding-bottom: 0;
}
.lb-times li span {
    color: var(--brand-text-muted);
    font-size: .92rem;
}
.lb-times li strong {
    color: var(--brand-gold);
    font-weight: 600;
    font-size: .92rem;
    /* "2-12 hrs" must never break across lines */
    white-space: nowrap;
}

/* --- Logo anchor ---------------------------------------------------------- */

/* .lb-logo wraps only an <img>, so nothing blue is visible while that image
   loads -- but the anchor still computes color: rgb(0, 0, 238), which is what
   the alt text would paint if the file ever 404s. Same class of fault as
   .service-price, one declaration to close. */
.lb-logo { color: var(--brand-text); }

/* ==========================================================================
   index.php: the About section
   --------------------------------------------------------------------------
   FIFTH instance of the orphaned-markup pattern, and the largest so far. Every
   class in this section had ZERO rules in site.css, style.css and brand.css:
   .lb-about-grid, .lb-about-media, .lb-about-copy, .lb-about-badge and
   .lb-ticks--spaced. Measured before this block:

     .lb-about-grid   display:block, grid-template-columns:none
                      -> media and copy STACKED at 1440 as well as 390
     .lb-about-media  position:static  -> nothing for the badge to anchor to
     img              display:inline, 720x720 unconstrained, border-radius:0
     .lb-about-badge  position:static, background:transparent, width:1152px
     strong "23+"     font-size:16px, display:inline

   which is precisely the "23+ Years of Craft renders as unstyled tiny text
   beneath the image" defect from the annotated screenshots: there was no badge,
   just two inline text nodes in a full-width block below a full-bleed image.

   NOT A DEFECT, recorded so the next probe does not chase it: measuring this
   image without scrolling reports naturalWidth 0 / renderedW 0, because it
   carries loading="lazy" and the section is far below the fold. The file is
   fine -- HTTP 200, 113KB, 720x720 once in view. Scroll before measuring any
   lazy image.

   The badge is a dark plaque with a gold hairline, not a filled gold sticker:
   .popular-badge already owns the filled-gold treatment on this same page, and
   a tenure credential should not compete with a sale flag. It sits INSIDE the
   image rather than hanging off the corner -- outside reads slightly richer but
   at -1.5rem it lands exactly on .lb-container's 1.5rem padding edge, so any
   narrower container would push it out of the viewport. Inside is safe at every
   width and still overlaps the photograph, which is the point.
   ========================================================================== */
.lb-about-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 2.5rem;
    align-items: center;
}
/* 960, not the 1024 used by .lb-steps: a square photo plus this much copy has
   room to sit side by side well before 1024, and stacking it longer than it
   needs to be leaves a 720px image alone on the line. */
@media (min-width: 960px) {
    .lb-about-grid {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
        gap: 3.5rem;
    }
}

.lb-about-media {
    position: relative;   /* badge anchor */
    min-width: 0;
}
.lb-about-media img {
    display: block;       /* was inline: killed the baseline gap under the photo */
    width: 100%;
    height: auto;
    border-radius: 18px;
    border: 1px solid var(--brand-border-strong);
    box-shadow: 0 18px 46px rgba(0, 0, 0, .5);
}

.lb-about-badge {
    position: absolute;
    bottom: 1.1rem;
    left: 1.1rem;
    padding: .85rem 1.25rem;
    border-radius: 14px;
    border: 1px solid rgba(212, 175, 55, .38);
    background: rgba(10, 10, 10, .82);
    box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
    text-align: left;
}
.lb-about-badge strong {
    display: block;
    font-family: var(--lb-serif);
    font-size: 2.4rem;
    line-height: 1;
    font-weight: 700;
    color: var(--brand-gold);
}
.lb-about-badge span {
    display: block;
    margin-top: .2rem;
    font-size: .68rem;
    font-weight: 600;
    letter-spacing: .15em;
    text-transform: uppercase;
    color: var(--brand-text-muted);
}

.lb-about-copy { min-width: 0; }

/* These list items carry a bolded lead-in plus a clause, not the one-liners
   .lb-ticks was sized for, so they need more room between rows and a brighter
   lead-in to separate label from explanation. */
.lb-ticks--spaced { gap: .85rem; }
/* .lb-ticks ships `margin: 0 0 1.4rem` -- bottom only -- so the first tick butts
   straight against the lede above while rows 2-3 are spaced, and the list reads
   as mis-set. Same omission as .lb-ticks--inline on pricing.php. */
.lb-ticks--spaced { margin-top: 1.5rem; }
.lb-ticks--spaced li { font-size: .92rem; line-height: 1.6; }
.lb-ticks--spaced li strong { color: var(--brand-text); font-weight: 600; }


/* ==========================================================================
   index.php: the quote-form card head, the portfolio strip, the testimonials
   --------------------------------------------------------------------------
   Three separate user-reported defects, all measured in Chromium at 1440x900
   and 390x844 before and after. Two of them are the orphaned-markup pattern
   again (instances 7 and 8): markup written against class names that have
   ZERO rules in site.css, style.css AND brand.css.

   1. .lb-card-head / .lb-badge  -- index.php:417-420 -- NO RULES ANYWHERE.
      Measured before, identical at both widths:
          .lb-card-head   display:block, margin-bottom:0, no border
          .lb-badge       display:inline, 16px, rgb(245,245,245),
                          background transparent, border none, radius 0, pad 0
          badge on the same line as the h3?    NO
          badge bottom -> "FULL NAME *" label   3.4px
          card-head bottom -> <form> top        0px
      So "Response in ~30 min" was not a badge at all -- it was a sentence of
      body copy on its own line, 3.4px above the first field label. That is
      exactly the reported "heading jammed into the form" collision.

   2. .lb-strip-item img -- index.php:891-901. Here the CONTAINER is styled
      (site.css:1130, grid-auto-columns minmax(230px,280px)) but the item
      carries only `scroll-snap-align` and the img carries nothing:
          img  display:inline, object-fit:fill, aspect-ratio:auto, radius 0
          10 natural sizes from 1000x440 to 317x509
          8 DISTINCT rendered heights at 1440: 101.2 114.4 187.3 242.9 245
                                               305.8 369.3 390.8
      A 280px-wide column of images whose heights differ by 3.9x is the
      reported "ragged portfolio grid". Not orphaned -- incomplete.

   3. .lb-testimonials / .stars -- index.php:916-926 -- NO RULES ANYWHERE.
          .lb-testimonials  display:block, grid-template-columns:none
          card width         1152px (full container, single column at 1440)
          card heights       142 / 142 / 168.3 / 142 / 221.1  -- ragged
          .stars             rgb(245,245,245) 16px  -- WHITE, not gold
          figcaption         16px -- same size as the quote body, no hierarchy

   THE INVERSE HALF, for the record: site.css:1162-1176 holds a complete and
   unused testimonial stylesheet -- .lb-quote-card / .lb-quote-mark /
   .lb-quote-text / .lb-quote-meta -- with ZERO markup consumers anywhere in
   the repo. Same disjoint-halves shape as the .pricing-* block. Left in place
   as a documented cleanup candidate, not deleted; the intent recorded in it
   (a flex column whose text takes flex:1 so cards in a row end level) is
   recovered below on the classes the markup actually uses.
   ========================================================================== */

/* ---- 1. card head ---- */
.lb-card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .7rem 1rem;
    /* The head owns the separation, so zero the h3's own .6rem below. 1.4rem
       plus the rule reads as a header band rather than a first paragraph. */
    margin-bottom: 1.4rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--brand-border);
}
.lb-card-head h3 { margin: 0; }

/* A status pill, not a label: "Response in ~30 min" is a live promise, so it
   gets the one small piece of motion on this card -- a slow gold pulse on the
   dot. Held to the dot alone (nothing else here animates) and switched off
   under prefers-reduced-motion. */
.lb-badge {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .32rem .72rem;
    border-radius: 999px;
    border: 1px solid rgba(212, 175, 55, .32);
    background: rgba(212, 175, 55, .08);
    color: var(--brand-gold);
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    white-space: nowrap;
    line-height: 1;
}
.lb-badge::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--brand-gold);
    flex: 0 0 auto;
    animation: lbBadgePulse 2.4s ease-in-out infinite;
}
@keyframes lbBadgePulse {
    0%, 100% { opacity: 1;  box-shadow: 0 0 0 0 rgba(212, 175, 55, .45); }
    50%      { opacity: .5; box-shadow: 0 0 0 4px rgba(212, 175, 55, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .lb-badge::before { animation: none; }
}

/* ---- 2. portfolio strip tiles ---- */
/* Uniform HEIGHT, variable width -- the correct invariant for a horizontal rail.
   Fixing the width instead (the first attempt) forced every tile to one aspect
   ratio, and seven of the ten sources are 2.27-ratio composite showcases that a
   square crop guts. A rail scrolls sideways, so tiles may differ in width; what
   must not differ is the height, or the row goes ragged. Contact-sheeted all ten
   sources before choosing: banner1/2/4/5/6/7/8 are wide multi-piece showcases
   (several with BEFORE/AFTER pairs whose meaning depends on both halves staying
   in frame), Car-1 and the eagle are single pieces, FWK6E9 is a portrait crest. */
.lb-strip { display: flex; align-items: stretch; }
.lb-strip-item {
    margin: 0;
    flex: 0 0 auto;
    border-radius: 16px;
    overflow: hidden;
    border: 1px solid var(--brand-border);
    background: var(--brand-surface);
    transition: transform .22s ease, border-color .22s ease;
}
.lb-strip-item:hover { transform: translateY(-4px); border-color: rgba(212, 175, 55, .4); }
/* NO aspect-ratio and NO cover on the common path: at a fixed height with width
   auto, every tile keeps its own ratio and nothing is cropped. `contain` on a
   plate was tried and rejected on evidence first -- these ten sources have no
   common plate colour, so no mat blends with them. Sampled corner luminance:
       255 249 (pure white)  232/52 (split)  192 188 (grey)  198 (light grey)
       137 103 (mid grey)    23 12 2 (near black)
   with mean luminance 64-169. Letterboxing on any fixed plate leaves a visible
   rectangle inside most tiles and reads as a broken image, not a gallery mat.
   Sizing to height sidesteps the question: there is no leftover box to fill.
   max-width is a guard for future uploads only, not for anything in the folder
   today -- the widest current source is 2.27 ratio, 500px at this height. A
   genuine panorama would otherwise produce a single 2000px tile that swamps the
   rail; past the clamp `cover` crops its sides rather than squashing it. */
.lb-strip-item img {
    display: block;
    height: 240px;
    width: auto;
    max-width: 520px;
    object-fit: cover;
    object-position: center;
}
/* Shorter at phone width so a wide showcase still fits a swipe: 2.27 x 170 is
   386px against a 342px viewport, close enough to read as one tile. At 240px it
   would be 545px and each swipe would land mid-image. */
@media (max-width: 640px) {
    .lb-strip-item img { height: 170px; max-width: 400px; }
}

/* ---- 3. testimonials ---- */
/* flex-wrap, not grid. Five cards never divide evenly into 2 or 3 columns, so a
   grid always leaves a hole -- and it leaves it on the right, which reads as a
   card that failed to load. Wrapping flex with justify-content:center puts the
   short last row under the middle of the row above, which reads as deliberate.
   The row-levelling grid would have given comes free: align-items defaults to
   stretch, and stretch equalises within each flex LINE, which is exactly the
   scope wanted (cards level with their own row, not with a taller row above).
   Basis is calc'd rather than a fixed px min so every card in every row is the
   same width -- flex-basis:300px + grow would have made the 2-card row wider
   than the 3-card row. */
.lb-testimonials {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1.4rem;
}
.lb-testimonials .testimonial-card { flex: 0 1 100%; }
/* 700 then 1080. Two columns arrive early because these quotes are short (the
   longest is 6 lines at 342px); three wait until 1080 so a card never drops
   below ~330px, where a 5-star row plus a rule-separated caption starts to feel
   cramped. Five cards land 3 + 2 centred. */
@media (min-width: 700px) {
    .lb-testimonials .testimonial-card { flex: 0 1 calc((100% - 1.4rem) / 2); }
}
@media (min-width: 1080px) {
    .lb-testimonials .testimonial-card { flex: 0 1 calc((100% - 2 * 1.4rem) / 3); }
}

/* flex column + flex:1 on the quote is what levels the cards: measured heights
   were 142/142/168.3/142/221.1 as plain blocks. This is the one idea worth
   recovering from the dead .lb-quote-card block above. */
.testimonial-card { display: flex; flex-direction: column; }
.testimonial-card .stars {
    display: flex;
    gap: .16rem;
    margin-bottom: .9rem;
    font-size: .82rem;
    color: var(--brand-gold);   /* was rgb(245,245,245) -- five white stars */
}
.testimonial-card blockquote {
    flex: 1;
    margin: 0;
    font-size: .95rem;
    line-height: 1.7;
    color: var(--brand-text);
}
/* The caption was 16px -- the same size as the quote it captions, so the date
   read as a second sentence. Demoted to 11.5px uppercase behind a hairline: it
   is metadata, and metadata should not compete with the quote.
   NOTE: the dates themselves are real rows in the `testimonial` table
   (2013-2018) and are NOT touched here -- what is displayed is the owner's
   call, not a UI repair. */
.testimonial-card figcaption {
    margin-top: 1.1rem;
    padding-top: .85rem;
    border-top: 1px solid var(--brand-border);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--brand-text-muted);
}
