
:root {
    --wrap: 1460px;
}



/* Anas Print House global page loader */
.aph-page-loader {
    position: fixed;
    inset: 0;
    z-index: 99999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fff;
    opacity: 1;
    visibility: visible;
    transition: opacity 250ms ease, visibility 250ms ease;
}

.aph-loader-content {
    position: relative;
    width: 144px;
    height: 144px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.aph-loader-logo {
    width: 94px;
    height: 94px;
    object-fit: contain;
    position: relative;
    z-index: 1;
}

.aph-loader-spinner {
    position: absolute;
    inset: 0;
    border: 3px solid #eee9df;
    border-top-color: #b58a45;
    border-right-color: #b58a45;
    border-radius: 50%;
    animation: aph-loader-rotate 0.85s linear infinite;
}

.aph-loader-hidden {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

.aph-loader-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

@keyframes aph-loader-rotate {
    to {
        transform: rotate(360deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    .aph-loader-spinner {
        animation: none;
    }

    .aph-page-loader {
        transition: none;
    }
}



.art img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .7s var(--ease-out);
}

.card:hover .art img,
.proj:hover .art img {
    transform: scale(1.07);
}

/* A card that opens its page wherever it is clicked: the title link's own
   ::after is stretched over the whole .card (position: relative in site.css).
   .card::before/::after are taken by the hover tint and gold bar, hence the
   link's. The carousel still swallows the click after a real drag. */
.card--link .card-title a::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
}

/* A second action on the card (a product's Get a Quote) must sit above the
   stretched link, or it would open the product page instead. */
.card--link .btn {
    position: relative;
    z-index: 2;
}

.card--link .card-title a:focus-visible::after {
    outline: 2px solid var(--gold);
    outline-offset: -2px;
    border-radius: inherit;
}

/* The product page's main gallery frame. site.css sizes it from the SVG
   illustration inside it; a photo needs its own frame, so it is given the same
   4:3 shape the illustrations use and cropped to fill. */
.gallery-main > img {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    display: block;
}

/* ---------- Brand mark ----------
   The client's final logo (roof over the AP monogram, img/logo-mark.svg) is
   4:3, where the design's placeholder mark was nearly 2:1. site.css sizes the
   mark by height, so at its heights the new one reads too small; these are the
   same selectors with heights that keep the monogram at about the old size. */
.brand-mark {
    height: 62px;
}

.drawer-top .brand-mark {
    height: 54px;
}

.brand--light .brand-mark {
    height: 76px;
}

@media (max-width: 560px) {
    .brand-mark {
        height: 54px;
    }
}

/* ---------- Quotation form ----------
   The quote form grew from the design's short enquiry form into a real
   quotation: sectioned steps, a dimensions row, add-on tick cards and a success
   panel. Everything here reuses the design's own tokens and input styles — only
   the new arrangements needed rules. */

.quote-grid {
    /* Catalogue left, form right. The form is the wider column: it carries two
       fields per row, the catalogue only needs to show tiles. */
    grid-template-columns: .9fr 1.3fr;
    gap: clamp(30px, 4.5vw, 56px);
    align-items: start;
}

.quote-side {
    display: grid;
    gap: var(--s4);
    align-content: start;
    min-width: 0;
}

.quote-main {
    min-width: 0;
}

@media (max-width: 900px) {
    .quote-grid {
        grid-template-columns: 1fr;
    }
}

.quote-sec + .quote-sec {
    margin-top: var(--s6);
    padding-top: var(--s5);
    border-top: 1px solid var(--rule);
}

/* Numbered step head: a short line about what the block wants, rather than a
   full "Step N of 6" eyebrow over every field group. */
.quote-sec-head {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    margin-bottom: var(--s4);
}

.quote-sec-n {
    flex: none;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--g-brand);
    color: #fff;
    font-size: .82rem;
    font-weight: 800;
}

.quote-sec-title {
    font-size: 1.18rem;
    margin: 0;
}

.quote-sec-note {
    margin: 2px 0 0;
    font-size: .82rem;
    color: var(--ink-50);
}

/* The help affordance next to "Your specification". */
.quote-tip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    margin-left: 6px;
    vertical-align: middle;
    border: 1px solid var(--rule-2);
    border-radius: 50%;
    background: transparent;
    color: var(--ink-50);
    font-size: .72rem;
    font-weight: 750;
    cursor: help;
}

.quote-tip:hover,
.quote-tip:focus-visible {
    border-color: var(--brand);
    color: var(--brand);
}

/* Length / width / depth / unit on one row, stacking two-up on a phone. */
.dim-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--s2);
}

@media (max-width: 560px) {
    .dim-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* Add-on tick cards. A hidden card must not reserve grid space, hence the
   [hidden] rule — display:grid would otherwise override it. */
.chk-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: var(--s2);
}

.chk-card {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 12px 14px;
    border: 1px solid var(--rule-2);
    border-radius: var(--r);
    background: var(--white);
    font-size: .84rem;
    font-weight: 650;
    color: var(--ink);
    cursor: pointer;
    transition: border-color .2s, background .2s, color .2s;
}

.chk-card:hover {
    border-color: var(--brand);
}

.chk-card:has(input:checked) {
    border-color: var(--brand);
    background: var(--brand-tint);
    color: var(--brand);
}

.chk-card input,
.chk-line input {
    accent-color: var(--brand);
    flex: none;
}

.chk-card[hidden] {
    display: none;
}

.chk-line {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: .86rem;
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
}

.quote-date {
    margin-top: var(--s3);
    max-width: 260px;
}

/* The text box that opens under a dropdown answered "Other". It sits inside
   the dropdown's own .field, so it carries its own error state. */
.quote-other {
    margin-top: var(--s2);
}

.quote-other.has-err .input {
    border-color: var(--err);
}

.quote-other.has-err .err-msg {
    display: block;
}

.quote-assure {
    margin-top: 16px;
    text-align: center;
}

/* The banner naming the product the visitor arrived from. It heads the
   catalogue column, which spaces its own children, so it carries no margin. */
.quote-context {
    display: grid;
    grid-template-columns: 132px 1fr;
    gap: var(--s4);
    align-items: center;
    padding: var(--s4);
    border: 1px solid var(--rule);
    border-radius: var(--r-lg);
    background: var(--white);
}

.quote-context h2 {
    font-size: 1.2rem;
    margin: 2px 0 6px;
}

.quote-context-art {
    border-radius: var(--r);
    overflow: hidden;
}

.quote-context-art img,
.quote-context-art svg {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    display: block;
}

@media (max-width: 560px) {
    .quote-context {
        grid-template-columns: 1fr;
    }
}

/* ---------- Success panel ---------- */

.quote-done {
    text-align: center;
}

.quote-done-icon svg {
    width: 52px;
    height: 52px;
    color: var(--brand);
}

.quote-done h2 {
    margin: var(--s3) 0 var(--s2);
}

.quote-ref {
    display: inline-block;
    margin: var(--s4) 0;
    padding: 14px 28px;
    border: 1px dashed var(--rule-2);
    border-radius: var(--r);
    background: var(--brand-tint);
}

.quote-ref-label {
    display: block;
    font-size: .74rem;
    font-weight: 750;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-50);
    margin-bottom: 4px;
}

.quote-ref strong {
    font-size: 1.3rem;
    letter-spacing: .04em;
    color: var(--brand);
}

.quote-done-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    justify-content: center;
    margin-top: var(--s4);
}

/* ---------- Hero banner: height ----------
   The banner filled a laptop screen on its own and pushed the services rail
   below the fold. Less room above and below the grid; the bottom padding still
   has to clear the trust bar, which floats up over the hero by up to 72px. */
.hero {
    padding-block: clamp(28px, 3vw, 52px) clamp(86px, 7vw, 110px);
}

/* ---------- Hero banner: product showcase ----------
   The hero visual is a carousel of products instead of the design's three
   floating illustrated panes (which are still the fallback for a catalogue with
   nothing published). .hero-stage sizes itself from those panes, so the
   showcase has to release that sizing and let the slide set the height. */

.hero-showcase {
    aspect-ratio: auto;
    min-height: 0;
    /* REQUIRED. .hero-stage is a grid item, so its automatic minimum size is
       the min-content width of what is inside it — and a carousel track's
       min-content is the sum of every slide. carousel.js sets each slide's
       width from the viewport it measures, so without this the track widens
       the column, the column widens the track, and --slide-w runs away to
       millions of pixels. The other rails never hit this: they sit in plain
       block boxes, whose width comes from the containing block, not content. */
    min-width: 0;
}

/* The showcase is one white product card with a thumbnail rail under it —
   the product spotlight the large print and packaging stores lead with. The
   rail replaces the dots; the arrows sit on the card's sides and show on hover.

   --neutral is overridden inside the picture on purpose: the SVG illustrations
   paint their own background with it, so re-pointing it at the backdrop colour
   puts an illustration and a photograph on the same studio surface. */
.hero-showcase {
    --hero-backdrop: #F2F3F8;
}

.hero-slide {
    display: flex;
    flex-direction: column;
    border-radius: var(--r-xl);
    overflow: hidden;
    background: var(--white);
    box-shadow: 0 34px 70px -30px rgba(3, 4, 20, .7), 0 0 0 1px rgba(255, 255, 255, .1);
}

.hero-slide-media {
    position: relative;
    display: block;
    flex: none;
}

.hero-slide .art,
.hero-thumb .art,
.hero-float .art {
    --neutral: var(--hero-backdrop);
    display: block;
    background: radial-gradient(90% 80% at 50% 42%, #fff 0%, var(--hero-backdrop) 70%);
}

.hero-slide .art {
    aspect-ratio: 16 / 10;
}

/* A product is shown whole here, never cropped to fill: contain, with room
   around it. multiply drops a studio shot's white background into the
   backdrop, so the product sits on the surface instead of in a white box. */
.hero-slide .art img,
.hero-thumb .art img,
.hero-float .art img {
    object-fit: contain;
    mix-blend-mode: multiply;
}

.hero-slide .art img,
.hero-slide .art svg {
    padding: 6% 10% 8%;
}

.hero-slide-tag {
    position: absolute;
    top: 16px;
    left: 16px;
    z-index: 2;
    padding: 6px 12px;
    border-radius: var(--r-pill);
    background: rgba(255, 255, 255, .92);
    box-shadow: var(--sh-1);
    font-size: .68rem;
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--brand);
}

.hero-slide-body {
    flex: 1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s3) var(--s4);
    padding: clamp(16px, 1.8vw, 22px) clamp(18px, 2.2vw, 26px);
    border-top: 1px solid var(--rule);
}

.hero-slide-text {
    flex: 1 1 200px;
    min-width: 0;
}

.hero-slide-title {
    margin: 0;
    font-size: clamp(1.08rem, 1.5vw, 1.3rem);
    line-height: 1.25;
    color: var(--brand);
}

.hero-slide-title a {
    color: inherit;
    text-decoration: none;
}

.hero-slide-title a:hover {
    color: var(--gold-deep);
}

/* One line, always: every slide is the same height, so the card does not
   jump as the carousel turns. */
.hero-slide-desc {
    margin: 4px 0 0;
    font-size: .86rem;
    color: var(--ink-50);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.hero-slide-actions {
    display: flex;
    align-items: center;
    gap: var(--s4);
    flex: none;
}

/* The slide that has just come into view settles in: the product eases down
   from a touch larger while the text rises. carousel.js flips aria-hidden on
   every turn, which is what starts it. */
.hero-showcase .carousel-slide[aria-hidden="false"] .art > * {
    animation: hero-settle .9s var(--ease-out) both;
}

.hero-showcase .carousel-slide[aria-hidden="false"] .hero-slide-body > * {
    animation: hero-rise .6s var(--ease-out) both;
}

.hero-showcase .carousel-slide[aria-hidden="false"] .hero-slide-actions {
    animation-delay: .08s;
}

@keyframes hero-settle {
    from { transform: scale(1.06); opacity: .4; }
    to { transform: none; opacity: 1; }
}

@keyframes hero-rise {
    from { transform: translateY(10px); opacity: 0; }
    to { transform: none; opacity: 1; }
}

/* The frame holds the product card and the two mini cards on its corners.
   Its padding is the room the mini cards stand in, so they overlap the main
   card only at the corners. The mini cards are absolute: in flow they would
   feed the grid item's min-content width and blow up --slide-w (see the note
   on .hero-showcase). */
.hero-frame {
    position: relative;
    padding: 7% 7% 0 9%;
}

.hero-float {
    position: absolute;
    z-index: 4;
    width: 28%;
    padding: 8px;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, .97);
    box-shadow: 0 26px 50px -20px rgba(3, 4, 20, .7), 0 0 0 1px rgba(255, 255, 255, .5);
    cursor: pointer;
    transition: box-shadow .35s var(--ease);
}

.hero-float:hover {
    box-shadow: 0 30px 60px -18px rgba(3, 4, 20, .8), 0 0 0 2px var(--gold-lift);
}

.hero-float .art {
    aspect-ratio: 4 / 3;
    border-radius: var(--r);
}

.hero-float .art img {
    padding: 8%;
}

.hero-float-cap {
    display: block;
    padding: 10px 6px 4px;
}

.hero-float-cap small {
    display: block;
    font-size: .6rem;
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--gold-deep);
}

.hero-float-cap b {
    display: block;
    margin-top: 2px;
    font-size: .84rem;
    line-height: 1.3;
    color: var(--brand);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* site.js fades the contents out, swaps in the next product, fades back. */
.hero-float .art > *,
.hero-float-cap {
    transition: opacity .25s var(--ease);
}

.hero-float.is-swapping .art > *,
.hero-float.is-swapping .hero-float-cap {
    opacity: 0;
}

.hero-float--tr {
    top: 0;
    right: 0;
    animation: floaty-2 9s ease-in-out infinite;
}

/* Sits on the picture, clear of the white text panel under it. */
.hero-float--bl {
    left: 0;
    bottom: 30%;
    animation: floaty 7s ease-in-out infinite;
}

/* Responsive steps. Under 1024px site.css moves the stage above the copy and
   caps it at 560px; it is widened a little here so a tablet gets a proper
   showcase, and the corner cards stay (slightly smaller) down to 641px. On a
   phone they are clutter rather than depth, so the card stands alone. */
@media (max-width: 1024px) {
    .hero-showcase {
        max-width: 640px;
    }

    .hero-float {
        width: 30%;
        padding: 6px;
    }

    .hero-float-cap {
        padding: 8px 4px 2px;
    }

    .hero-float-cap b {
        font-size: .78rem;
    }
}

@media (max-width: 640px) {
    .hero-float {
        display: none;
    }

    .hero-frame {
        padding: 0;
    }
}

/* Thumbnail rail, with the prev / next arrows at its two ends — on the card
   they would sit under the corner cards. The gold bar on the active thumb IS
   the autoplay timer: site.js turns the slide when its animation ends, so
   hovering (or keyboard focus) pauses both at once. */
.hero-thumbs {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    margin-top: var(--s4);
}

/* Same side padding as .hero-frame, so the rail centres under the card. */
@media (min-width: 641px) {
    .hero-thumbs {
        padding-inline: 9% 7%;
    }
}

.hero-thumbs-list {
    display: flex;
    gap: 10px;
    flex: 0 1 auto;
    min-width: 0;
}

.hero-thumbs .cbtn {
    width: 40px;
    height: 40px;
    background: rgba(255, 255, 255, .08);
    border-color: rgba(255, 255, 255, .24);
    color: #fff;
}

.hero-thumbs .cbtn:hover {
    background: var(--g-gold);
    border-color: transparent;
    color: #221904;
    box-shadow: var(--sh-gold);
}

.hero-thumbs [data-hero-prev] svg {
    transform: scaleX(-1);
}

.hero-thumb {
    position: relative;
    flex: 0 1 76px;
    min-width: 0;
    padding: 0;
    border: 2px solid transparent;
    border-radius: var(--r);
    overflow: hidden;
    cursor: pointer;
    background: none;
    opacity: .72;
    transition: opacity .3s var(--ease), border-color .3s var(--ease), transform .3s var(--ease);
}

.hero-thumb .art {
    aspect-ratio: 1 / 1;
}

.hero-thumb .art img {
    padding: 10%;
}

.hero-thumb:hover {
    opacity: .9;
    transform: translateY(-2px);
}

.hero-thumb.is-on {
    opacity: 1;
    border-color: var(--gold-lift);
}

.hero-thumb:focus-visible {
    outline: 2px solid var(--gold-lift);
    outline-offset: 3px;
}

.hero-thumb-bar {
    position: absolute;
    inset: auto 8px 7px;
    height: 3px;
    border-radius: 2px;
    background: rgba(27, 29, 82, .14);
    overflow: hidden;
    opacity: 0;
}

.hero-thumb.is-on .hero-thumb-bar {
    opacity: 1;
}

.hero-thumb-bar i {
    display: block;
    height: 100%;
    background: var(--g-gold);
    transform: scaleX(0);
    transform-origin: left;
}

.hero-thumb.is-on .hero-thumb-bar i {
    animation: hero-timer 6s linear forwards;
}

.hero-showcase:hover .hero-thumb-bar i,
.hero-showcase:has(:focus-visible) .hero-thumb-bar i {
    animation-play-state: paused;
}

@keyframes hero-timer {
    to { transform: scaleX(1); }
}

@media (max-width: 560px) {
    .hero-slide-desc {
        display: none;
    }

    .hero-thumbs-list {
        gap: 8px;
    }

    /* Too tight for arrows beside six thumbs; a swipe or a tap on a thumb does it. */
    .hero-thumbs .cbtn {
        display: none;
    }
}

/* No motion means no autoplay either: with the timer off, its animationend
   never fires. The active bar is simply shown full. */
@media (prefers-reduced-motion: reduce) {
    .hero-showcase .carousel-slide .art > *,
    .hero-showcase .carousel-slide .hero-slide-body > *,
    .hero-thumb.is-on .hero-thumb-bar i,
    .hero-float {
        animation: none;
    }

    .hero-thumb.is-on .hero-thumb-bar i {
        transform: none;
    }
}

/* The "printing since" badge would land on the dots; it becomes a sticker on
   the top corner of the artwork instead, where nothing else is drawn. */
.hero-showcase .hero-badge {
    inset: -14px -10px auto auto;
    z-index: 6;
}

@media (max-width: 560px) {
    .hero-showcase .hero-badge {
        inset: 10px 10px auto auto;
    }
}

/* ---------- Quotation: the optional block ----------
   Artwork, deadline, delivery address and notes are collapsed behind a native
   <details>, so the form reads as two short steps instead of six long ones.
   It is rendered open whenever it holds an error or an answer the visitor has
   already given — see the note in partials/quote/form. */

.quote-more {
    margin-top: var(--s5);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    background: var(--tint);
    overflow: hidden;
}

.quote-more[open] {
    background: var(--white);
}

.quote-more-summary {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    cursor: pointer;
    list-style: none;
    transition: background .2s;
}

/* Safari still draws the disclosure triangle without this. */
.quote-more-summary::-webkit-details-marker {
    display: none;
}

.quote-more-summary:hover {
    background: var(--brand-tint);
}

.quote-more-label {
    flex: 1;
    display: grid;
    gap: 2px;
}

.quote-more-label strong {
    font-size: .9rem;
    font-weight: 750;
    color: var(--brand);
}

.quote-more-label span {
    font-size: .78rem;
    color: var(--ink-50);
}

.quote-more-chev {
    width: 18px;
    height: 18px;
    flex: none;
    color: var(--ink-50);
    transition: transform .3s var(--ease);
}

.quote-more[open] .quote-more-chev {
    transform: rotate(180deg);
}

.quote-more-body {
    padding: var(--s4);
    border-top: 1px solid var(--rule);
}

.quote-more-body .field:last-child {
    margin-bottom: 0;
}

/* The long free-text boxes do not need to open at 130px in a form this short. */
.textarea--short {
    min-height: 92px;
}

/* ---------- Quotation: the catalogue column ----------
   The left column answers "which one do I want?": one panel per category, all
   rendered, the unused ones hidden, swapped by partials/quote/script. */

.quote-panel {
    padding: var(--s5) var(--s4);
    border: 1px solid var(--rule);
    border-radius: var(--r-lg);
    background: var(--g-brand-soft);
}

.quote-panel[hidden] {
    display: none;
}

.quote-panel-title {
    font-size: 1.22rem;
    margin-bottom: var(--s2);
}

.quote-panel .card-desc {
    margin-bottom: var(--s4);
}

.quote-picks,
.quote-cats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
    gap: var(--s3);
}

.quote-picks {
    margin-bottom: var(--s3);
}

.qpick,
.qcat {
    display: block;
    width: 100%;
    padding: 0;
    overflow: hidden;
    text-align: left;
    font: inherit;
    color: var(--ink);
    background: var(--white);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    cursor: pointer;
    transition: border-color .2s, box-shadow .25s var(--ease), transform .25s var(--ease);
}

.qpick:hover,
.qcat:hover,
.qpick:focus-visible,
.qcat:focus-visible {
    border-color: var(--brand);
    box-shadow: var(--sh-1);
    transform: translateY(-2px);
}

.qpick.is-on {
    border-color: var(--brand);
    box-shadow: 0 0 0 3px var(--brand-tint);
}

.qpick-art,
.qcat-art {
    display: block;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    background: var(--neutral);
}

.qpick-art img,
.qpick-art svg,
.qcat-art img,
.qcat-art svg {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.qpick-name,
.qcat-name {
    display: block;
    padding: 9px 11px;
    font-size: .8rem;
    font-weight: 700;
    line-height: 1.35;
    color: var(--brand);
}

.quote-picks-note {
    margin: 0 0 var(--s3);
}

/* The success panel replaces the two columns entirely — there is nothing left
   to choose once the request is in. */
.quote-done-wrap {
    max-width: 720px;
    margin-inline: auto;
}

/* ---------- Quotation: one product's quote page ----------
   pages/quote-product: the product on the left, the form on the right. The form
   column follows the visitor down the page. It is usually taller than the
   screen, so it scrolls inside itself rather than hiding its own submit button
   below the fold. Only side by side — stacked on a phone it is an ordinary
   block. The page wrapper has no overflow set, which sticky needs. */

@media (min-width: 901px) {
    .quote-main--sticky {
        position: sticky;
        top: calc(var(--hdr) + 20px);
        max-height: calc(100vh - var(--hdr) - 40px);
        overflow-y: auto;
        overscroll-behavior: contain;
        scrollbar-width: thin;
        border-radius: var(--r-lg);
    }
}

.quote-side #info h1 {
    margin-bottom: var(--s2);
}

.quote-side #info .tabsec {
    margin-top: var(--s5);
}

.quote-side #info .tlink {
    display: inline-flex;
    margin-top: var(--s4);
}

/* The locked product line at the top of the form, in place of the selects. */
.quote-locked {
    margin: 0 0 var(--s4);
    padding: 12px 16px;
    border-radius: var(--r);
    background: var(--g-brand-soft);
}

.quote-locked .meta {
    display: block;
    margin-top: 2px;
}

/* ---------- Editable page text ----------
   The about page's mission block is editor HTML now (/admin/pages), so the
   design's inline font-size on its opening paragraph moved here. */
.about-mission > p:first-child {
    font-size: 1.08rem;
}

/* ---------- Scroll reveal: drop the compositing layer when it lands ----------
   site.css puts `will-change: opacity, transform` on every [data-reveal] block
   and never takes it off, so each one keeps its own GPU layer for the life of
   the page. Text on a composited layer loses subpixel antialiasing, and the
   layer is rasterised at whatever fractional offset the band happens to sit at
   — which is why moving a section up or down could make a whole band's copy go
   soft with no change to its own CSS. The hint is only needed while the block
   is animating in; once it has, hand it back. */
[data-reveal].is-in {
    will-change: auto;
}

/* =============================================================================
   PRODUCT DETAIL
   ============================================================================= */

/* ---------- Gallery: a carousel stage over a scrolling thumb rail ----------
   partials/product-gallery, shared by the product page and a product's quote
   page. The design's version was a static frame over a fixed four-up grid of
   thumbnails, which wrapped into ragged rows the moment a product had five
   photos and gave the visitor no way to see one full size. This is a
   one-slide-per-view carousel with a rail that scrolls sideways instead, so
   the column's height no longer depends on how many photos were uploaded.
   Behaviour (and the lightbox) is initProductGallery() in site.js. */

/* A carousel inside a grid item needs this or --slide-w runs away — see the
   note on .hero-showcase. #gallery is a grid item of .pd-grid on the product
   page and of .quote-side on the quote page. */
#gallery {
    min-width: 0;
}

/* Same reason: the about page's story and mission photo carousels are grid items of .split. */
.story-photos,
.mission-photos {
    min-width: 0;
}

.pgal {
    display: grid;
    gap: var(--s3);
}

/* And again one level down: .pgal is itself a grid, so the stage is a grid
   item whose automatic minimum size is the carousel track's min-content — the
   sum of every slide. Without this the gallery measures --slide-w in the tens
   of millions and the page collapses. */
.pgal > * {
    min-width: 0;
}

.pgal-stage {
    position: relative;
}

/* site.css hangs the side arrows 12px OUTSIDE the viewport, which is right for
   a full-width rail of cards and wrong for a half-width column — there they
   land on the copy beside them. Bring them inside the frame. */
.pgal .carousel--arrows-side .carousel-nav {
    margin-inline: 14px;
}

.pgal .cbtn {
    width: 42px;
    height: 42px;
    background: rgba(255, 255, 255, .92);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}

/* site.css drops side arrows under 820px because a rail of cards is swiped.
   A gallery is different: the visitor's thumb is on the photo, and the arrows
   are the only affordance that says there is more than one. */
@media (max-width: 820px) {
    .pgal .carousel--arrows-side .carousel-nav {
        display: flex;
    }
}

.pgal-shot {
    margin: 0;
    border: 1px solid var(--rule);
    border-radius: var(--r-lg);
    overflow: hidden;
    background: var(--white);
    box-shadow: var(--sh-2);
}

/* contain, not cover: this is the one place a product is shown whole rather
   than cropped to a card. A portrait studio shot cropped to 4/3 loses half the
   product. The fixed ratio is what keeps every slide the same height. */
.pgal-shot img,
.pgal-shot svg {
    width: 100%;
    /* Square, the frame product photography is shot for: 4/3 left the gallery
       column standing about 400px shorter than the copy beside it, and neither
       a portrait nor a landscape shot fills a 4/3 box anyway once contained. */
    aspect-ratio: 1 / 1;
    object-fit: contain;
    background: linear-gradient(160deg, #FFFFFF 0%, #F7F8FC 100%);
}

.pgal-zoom {
    position: absolute;
    right: 16px;
    bottom: 16px;
    z-index: 6;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 9px 15px;
    border: 1px solid rgba(255, 255, 255, .28);
    border-radius: var(--r-pill);
    background: rgba(12, 14, 43, .74);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: #fff;
    font: inherit;
    font-size: .7rem;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .28s var(--ease), color .28s var(--ease),
        border-color .28s var(--ease), transform .28s var(--ease);
}

.pgal-zoom:hover {
    background: var(--g-gold);
    border-color: transparent;
    color: #221904;
    transform: translateY(-2px);
}

.pgal-zoom svg {
    width: 15px;
    height: 15px;
}

/* The rail scrolls rather than wraps, so twenty photos cost the same vertical
   space as four. The 4px padding and matching negative margin give the active
   thumb's gold shadow somewhere to render without being clipped. */
.pgal-rail {
    display: flex;
    gap: var(--s2);
    overflow-x: auto;
    overflow-y: hidden;
    padding: 4px 4px 9px;
    margin-inline: -4px;
    scroll-snap-type: x proximity;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--rule-2) transparent;
}

.pgal-rail::-webkit-scrollbar {
    height: 6px;
}

.pgal-rail::-webkit-scrollbar-track {
    background: transparent;
}

.pgal-rail::-webkit-scrollbar-thumb {
    background: var(--rule-2);
    border-radius: var(--r-pill);
}

.pgal-thumb {
    flex: 0 0 92px;
    scroll-snap-align: start;
    padding: 0;
    cursor: pointer;
    background: var(--neutral);
    border: 2px solid var(--rule);
    border-radius: var(--r-sm);
    overflow: hidden;
    transition: border-color .25s var(--ease), transform .25s var(--ease),
        box-shadow .25s var(--ease);
}

.pgal-thumb:hover {
    transform: translateY(-2px);
    border-color: var(--rule-2);
}

.pgal-thumb.is-on {
    border-color: var(--gold);
    box-shadow: var(--sh-gold);
}

.pgal-thumb .art {
    display: block;
}

@media (max-width: 560px) {
    .pgal-thumb {
        flex-basis: 74px;
    }
}

/* ---------- Lightbox (Viewer.js) ----------
   Vendored at public/vendor/viewerjs and loaded only by a page whose product
   actually has photographs. Only the handful of places its default chrome
   clashes with the site are restyled: its 50% black backdrop barely dims a
   white page, and the hover state should be the site's gold.

   The .viewer-container prefix is not decoration — the partial pushes the
   vendor stylesheet onto @stack('styles'), which the layout prints AFTER this
   file, so a bare selector here loses to the vendor's own rule. Note the
   backdrop is not a child: Viewer puts both classes on the one element. */
.viewer-container.viewer-backdrop {
    background-color: rgba(9, 11, 34, .94);
}

.viewer-container .viewer-navbar {
    background-color: rgba(9, 11, 34, .55);
}

.viewer-container .viewer-toolbar > ul > li {
    background-color: rgba(255, 255, 255, .14);
}

.viewer-container .viewer-toolbar > ul > li:focus,
.viewer-container .viewer-toolbar > ul > li:hover {
    background-color: var(--gold);
}

.viewer-container .viewer-title {
    color: #C3C8E0;
    font-size: .8rem;
    opacity: .85;
}

/* ---------- The gallery follows the copy ----------
   The right-hand column is the taller of the two, so above this width the
   photos ride down with it instead of leaving a column of dead space. The
   offset clears the sticky header, matching html{scroll-padding-top}. */
@media (min-width: 1100px) {
    .pd-grid > #gallery {
        position: sticky;
        top: calc(var(--hdr) + 20px);
    }
}

/* ---------- Assurances under the buttons ----------
   config/content.php 'trust', the same copy the about page uses. It answers
   the question a quote-only product page always leaves open: what do I get for
   sending this enquiry? */
.pd-assure {
    list-style: none;
    display: grid;
    gap: var(--s4);
    margin: var(--s5) 0 0;
    padding: var(--s5) 0 0;
    border-top: 1px solid var(--rule);
}

.pd-assure li {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 14px;
    align-items: start;
}

.pd-assure svg {
    width: 20px;
    height: 20px;
    margin-top: 2px;
    color: var(--gold-deep);
}

.pd-assure strong {
    display: block;
    margin-bottom: 3px;
    font-size: .93rem;
    color: var(--ink);
}

.pd-assure p {
    margin: 0;
    font-size: .86rem;
    line-height: 1.55;
    color: var(--ink-50);
}

/* ---------- The long copy below the fold ----------
   Scoped to #detail, which only the product page has — .tabsec is also the
   portfolio project page's hairline-separated block and stays as designed
   there. Cards give the five blocks an edge to sit on, which a page this long
   needs more than a rule does. */
#detail {
    display: grid;
    gap: var(--s4);
    margin-top: var(--s6);
}

#detail .tabsec {
    margin: 0;
    padding: clamp(24px, 3vw, 40px);
    border: 1px solid var(--rule);
    border-radius: var(--r-xl);
    background: var(--white);
    box-shadow: var(--sh-1);
}

#detail .tabsec > h3 {
    position: relative;
    margin-bottom: var(--s4);
    padding-bottom: 14px;
}

#detail .tabsec > h3::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    width: 54px;
    height: 3px;
    border-radius: 3px;
    background: var(--g-gold);
}

/* Floating "Get Free Quotation" button, stacked above .wa-float (site.css)
   and sized to match it. */
.quote-float {
    position: fixed;
    right: 20px;
    bottom: 99px;
    z-index: 70;
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 14px 20px;
    border-radius: var(--r-pill);
    text-decoration: none;
    font-size: .85rem;
    font-weight: 750;
    color: #fff;
    background: var(--g-gold);
    box-shadow: 0 16px 34px -14px rgba(142, 104, 31, .7);
    transition: transform .28s var(--ease);
}

.quote-float:hover {
    transform: translateY(-3px) scale(1.03);
}

.quote-float svg {
    width: 21px;
    height: 21px;
}

@media (max-width: 640px) {
    .quote-float {
        right: 14px;
        bottom: 82px;
        padding: 16px;
    }

    .quote-float span {
        display: none;
    }
}

@media print {
    .quote-float {
        display: none;
    }
}

/* ---------- Offer section, Offers page and the "Offers" menu item ----------
   partials/offer/section (homepage + /offers): a banner slider of products
   wearing the discount beside the short quote form. The banners are built in
   markup, not images, so the percentage set at /admin/settings is on every
   one of them. Brand gradient + gold burst, the same pair as the CTA band. */

.offer-sec {
    overflow: hidden;
}

.offer-sec > .wrap {
    position: relative;
    z-index: 1;
}

.offer-grid {
    display: grid;
    grid-template-columns: 1fr 1.05fr;
    gap: clamp(28px, 4vw, 56px);
    align-items: start;
}

/* REQUIRED for the slider column — see the carousel/min-width note in
   .ai/rules/css.md. Without it the track widens the column without end. */
.offer-side,
.offer-main {
    min-width: 0;
}

@media (max-width: 960px) {
    .offer-grid {
        grid-template-columns: 1fr;
    }
}

.offer-slide {
    position: relative;
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: clamp(14px, 2vw, 24px);
    min-height: 360px;
    padding: clamp(22px, 3vw, 36px);
    border-radius: var(--r-xl);
    overflow: hidden;
    background: var(--g-brand);
    color: #C3C8E0;
    text-decoration: none;
}

.offer-slide::before {
    content: '';
    position: absolute;
    width: 340px;
    height: 340px;
    right: -90px;
    top: -110px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(227, 188, 106, .3), transparent 70%);
    pointer-events: none;
}

.offer-slide--plain {
    grid-template-columns: 1fr;
}

.offer-slide-copy {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}

.offer-burst {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    margin-bottom: 10px;
    padding: 10px 18px 12px;
    border-radius: var(--r-lg);
    background: var(--g-gold);
    color: #221904;
    box-shadow: var(--sh-gold);
    transform: rotate(-3deg);
}

.offer-burst b {
    font-size: clamp(2.6rem, 5vw, 3.8rem);
    font-weight: 900;
    line-height: 1;
    letter-spacing: -.04em;
}

.offer-burst small {
    font-size: 1rem;
    font-weight: 850;
    letter-spacing: .14em;
}

.offer-slide-meta {
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--gold-lift);
}

.offer-slide-name {
    color: #fff;
    font-size: clamp(1.3rem, 2.2vw, 1.75rem);
    font-weight: 800;
    line-height: 1.15;
    letter-spacing: -.02em;
}

.offer-slide-line {
    margin-bottom: 10px;
    font-family: var(--f-display);
    font-style: italic;
    color: #E4E6F2;
}

.offer-slide-art {
    position: relative;
    z-index: 1;
    display: block;
    aspect-ratio: 1;
    border-radius: var(--r-lg);
    overflow: hidden;
    background: #fff;
    box-shadow: 0 24px 50px -24px rgba(0, 0, 0, .6);
}

/* Show the whole product, as the hero does, rather than cropping it. */
.offer-slide-art img {
    object-fit: contain;
}

.offer-slide:hover .offer-slide-art img {
    transform: scale(1.05);
}

@media (max-width: 560px) {
    .offer-slide {
        grid-template-columns: 1fr;
        min-height: 0;
    }

    .offer-slide-art {
        order: -1;
        aspect-ratio: 16 / 10;
    }
}

.offer-perks {
    display: grid;
    gap: 10px;
    margin: var(--s4) 0 0;
    padding: 0;
    list-style: none;
}

.offer-perks li {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 650;
    color: var(--brand);
}

.offer-perks svg {
    flex: none;
    width: 20px;
    height: 20px;
    color: var(--gold);
}

.offer-form {
    border-top: 4px solid var(--gold);
}

.offer-form .field {
    margin-bottom: var(--s3);
}

.offer-form-head {
    margin-bottom: var(--s4);
}

.offer-form-head h3 {
    margin: 12px 0 4px;
}

.offer-form-head p {
    margin: 0;
    font-size: .92rem;
    color: var(--ink-50);
}

.offer-pill {
    display: inline-block;
    padding: 6px 14px;
    border-radius: var(--r-pill);
    background: var(--gold-tint);
    color: var(--gold-deep);
    font-size: .78rem;
    font-weight: 850;
    letter-spacing: .12em;
}

.offer-note {
    min-height: 76px;
}

.offer-terms {
    margin: 12px 0 0;
    font-size: .78rem;
    text-align: center;
    color: var(--ink-50);
}

/* Offers page: a gold "30% OFF" tag on each product card. The tag sits above
   the card's stretched link but lets clicks through to it. */
.offer-card {
    position: relative;
}

.offer-card > .card {
    height: 100%;
}

.offer-ribbon {
    position: absolute;
    top: 14px;
    left: 14px;
    z-index: 3;
    padding: 6px 12px;
    border-radius: var(--r-pill);
    background: var(--g-gold);
    color: #221904;
    font-size: .72rem;
    font-weight: 850;
    letter-spacing: .08em;
    box-shadow: var(--sh-gold);
    pointer-events: none;
}

/* The "30%" on the Offers menu item (desktop menu and mobile drawer). */
.nav-badge {
    display: inline-block;
    padding: 2px 7px;
    border-radius: var(--r-pill);
    background: var(--g-gold);
    color: #221904;
    font-size: .66rem;
    font-weight: 850;
    line-height: 1.4;
}

/* Seven menu items instead of six: tighten them a little on the narrowest
   desktops, before the burger takes over at 1024px. */
@media (min-width: 1025px) and (max-width: 1240px) {
    .nav-link {
        padding-inline: 10px;
    }
}

/* Homepage "Premium Finishes": show-only cards, like a swatch book. They link
   nowhere, so they keep the gentle photo zoom but not the lift, shadow and
   gold bar that say "click me" on the other cards. */
.finish-card {
    cursor: default;
}

.finish-card:hover {
    transform: none;
    box-shadow: none;
    border-color: var(--rule);
}

.finish-card:hover::before,
.finish-card:hover::after {
    opacity: 0;
    transform: scaleX(0);
}

.finish-card .card-title {
    font-size: 1.08rem;
}

.finish-card .card-desc {
    margin-bottom: 0;
}

/* Skeleton shimmer on a photo that is still downloading. site.js puts
   .is-skeleton on the <img> and a document-level load/error listener takes it
   off, so the shimmer sits under the image only until the image arrives.
   Reduced motion is already handled by site.css's global animation reset. */
.art img.is-skeleton,
.pgal-shot img.is-skeleton,
.qpick-art img.is-skeleton,
.qcat-art img.is-skeleton {
    background-color: var(--neutral);
    background-image: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, .7) 50%, transparent 70%);
    background-size: 200% 100%;
    animation: skeleton-shimmer 1.3s linear infinite;
}

@keyframes skeleton-shimmer {
    from { background-position: 150% 0; }
    to { background-position: -50% 0; }
}

/* site.css starts every reveal / stagger block hidden and waits for site.js
   to fade it in. The layout puts .js on <html> from an inline script in the
   head, so without JavaScript that class never arrives and the blocks are
   shown as they are. */
html:not(.js) [data-reveal],
html:not(.js) [data-stagger] > * {
    opacity: 1;
    transform: none;
}

/* The submit spinner in site.css is fixed white, which all but vanishes on
   .btn--gold and .btn--ghost. Draw it in the button's own --fg instead: on
   the white-text buttons that is the same white as before. The first
   border-color is the fallback for a browser without color-mix(). */
.btn.is-loading::before {
    border-color: rgba(255, 255, 255, .35);
    border-color: color-mix(in srgb, var(--fg) 35%, transparent);
    border-top-color: var(--fg);
}

/* Section labels. site.css draws .eyebrow as a gold pill with a glowing dot,
   which the client read as template / AI-made. Same words, same place, now
   a plain printed label: the text sits on the page with a short CMYK strip
   in front, the colour bar a press runs along every sheet (the same
   --g-cmyk the page heroes and footer already use). On dark bands the K
   segment turns gold, as the footer strip does, so it stays visible. */
.eyebrow {
    gap: 10px;
    padding: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    letter-spacing: .14em;
}

.eyebrow::before {
    width: 22px;
    height: 3px;
    border-radius: 1px;
    background: var(--g-cmyk);
    box-shadow: none;
}

.section--dark .eyebrow,
.phero .eyebrow,
.hero .eyebrow {
    background: none;
    box-shadow: none;
}

.section--dark .eyebrow::before,
.phero .eyebrow::before,
.hero .eyebrow::before {
    background: var(--g-cmyk-dark);
}

/* Footer bottom bar: copyright left, payment logos centre, legal links right.
   A 1fr / auto / 1fr grid keeps the centre group on the page's true centre
   whatever the side items' widths. Logos sit on small white chips because the
   footer is dark navy and the Easypaisa / Visa marks are dark ink. Display
   only — no payment is taken. */
.footer-bot {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
}

.footer-bot > :first-child {
    justify-self: start;
}

.footer-bot > .footer-legal {
    justify-self: end;
}

.pay-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-footer .pay-row li {
    margin: 0;
}

.pay-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 28px;
    padding: 0 8px;
    border-radius: 5px;
    background: #fff;
    color: var(--brand);
    font-size: .68rem;
    font-weight: 700;
    line-height: 1;
    white-space: nowrap;
}

.pay-badge img {
    display: block;
    width: auto;
    height: 15px;
}

.pay-badge img[alt="Mastercard"] {
    height: 18px;
}

.pay-badge svg {
    width: 14px;
    height: 14px;
    flex: none;
    color: var(--gold-deep);
}

/* Centre group: the small free-shipping truck, then the payment chips. */
.footer-mid {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 10px 14px;
}

.ship-badge {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    color: #fff;
    font-size: .74rem;
    font-weight: 700;
    letter-spacing: .02em;
    white-space: nowrap;
}

.ship-badge svg {
    width: 44px;
    height: auto;
    flex: none;
}

.footer-legal {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 6px 18px;
}

@media (max-width: 1100px) {
    .footer-bot {
        grid-template-columns: 1fr;
        justify-items: center;
        text-align: center;
    }

    .footer-bot > :first-child,
    .footer-bot > .footer-legal {
        justify-self: center;
    }
}

/* Homepage delivery band: copy left, truck illustration right.
   The band is commented out in pages/home.blade.php for now; these rules stay
   so it works as soon as it is uncommented. */
.delivery-points {
    display: grid;
    gap: 10px;
    margin: 18px 0 0;
    padding: 0;
    list-style: none;
}

.delivery-points li {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 600;
    color: var(--ink);
}

.delivery-points svg {
    width: 20px;
    height: 20px;
    flex: none;
    padding: 3px;
    border-radius: 50%;
    background: var(--gold-tint);
    color: var(--gold-deep);
}

.delivery-art {
    min-width: 0;
}

.delivery-truck {
    display: block;
    width: 100%;
    height: auto;
    max-width: 620px;
    margin-inline: auto;
}

/* Privacy policy and terms: plain readable text inside .wrap-tight. */
.legal h2 {
    margin: 34px 0 10px;
    font-size: 1.3rem;
}

.legal h2:first-of-type {
    margin-top: 0;
}

.legal ul {
    margin: 0 0 16px;
    padding-left: 20px;
}

.legal li {
    margin-bottom: 6px;
}

.legal .meta {
    margin-bottom: 26px;
}
