:root {
    --brand-900: #373636;
    --brand-700: #bebdbd;
    --brand-500: #f8e89c;
    --brand-100: #f8f2d4;
    --accent: #d97706;
    --accent-soft: #fff4df;
    --success: #1b7f5a;
    --text: #252526;
    --muted: #6b7280;
    --border: #c7c7c7;
    --card-bg: #ffffff;
    --header-bg: #eef1f5;
    --shadow: 0 18px 45px rgba(17, 24, 39, 0.08);
}

html, body {
    background: #f5f7fb;
    color: var(--text);
    font-family: "Inter", "Segoe UI", sans-serif;
}

.product-page {
    max-width: 1280px;
    margin: 0 auto;
    padding: 24px 16px 80px;
}

.product-header-inline {
    /* Three tracks so the centre block stays optically centred regardless of
       how wide the title or the back button happen to be. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 12px;
    margin-bottom: 24px;
    padding: 14px 18px;
    background: var(--header-bg);
    border: 1px solid var(--border);
    border-radius: 14px;
}

.product-header-left {
    display: flex;
    align-items: center;
    justify-self: start;
    min-width: 0;
}

.product-header-center {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 10px;
    justify-self: center;
}

.product-header-right {
    display: flex;
    align-items: center;
    justify-self: end;
}

/* Used as an <a> on the list page and as a <button> on the detail page, so it
   sets font-family/line-height explicitly — a button does not inherit the page
   font, and would otherwise render in the browser's default UI face. */
.product-back-link-inline {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    border-radius: 8px;
    background: #fff;
    border: 1px solid var(--border);
    text-decoration: none;
    color: var(--brand-900);
    font-family: inherit;
    font-weight: 500;
    font-size: 0.9rem;
    line-height: 1.4;
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
    cursor: pointer;
}

.product-back-link-inline:hover {
    background: var(--brand-100);
}

/* Detail page bar holding the back button. It had only a media-query rule and
   no base layout, so the row was never actually a flex container. */
.product-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 20px;
}

.product-topbar-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.product-title-inline {
    margin: 0;
    font-weight: 700;
    font-size: 1.3rem;
    color: var(--brand-900);
    letter-spacing: -0.02em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.product-search-wrap-inline {
    position: relative;
    flex: 1 1 auto;
    min-width: 484px;
    max-width: 652px;
}

.product-search-wrap-inline input {
    width: 100%;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: #fff;
    /* Right padding leaves room for the clear button so typed text can't run
       underneath it. */
    padding: 10px 34px 10px 36px;
    font-size: 0.9rem;
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.02);
}

.product-search-wrap-inline .search-ico {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--muted);
    font-size: 16px;
}

/* Revealed by JS only while the field has text. */
.search-clear {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    display: none;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: #e4e9f0;
    color: var(--muted);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.search-clear.is-visible {
    display: flex;
}

.search-clear:hover {
    background: var(--accent-soft);
    color: var(--accent);
}

/* Refresh shares the advanced-search button's rule rather than copying it: the
   two sit shoulder to shoulder in the header, and any drift in height, radius
   or border colour between them reads as a rendering bug. */
.toggle-advanced,
.refresh-catalog {
    position: relative;
    border: 1px solid var(--border);
    background: #fff;
    color: var(--brand-900);
    border-radius: 10px;
    padding: 10px 12px;
    font-weight: 600;
    cursor: pointer;
    font-size: 0.9rem;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s ease;
}

/* Advanced filters are active. The panel is collapsed by default, so without
   this there is nothing on screen explaining a reduced result count.

   A SOLID accent fill, not the pale accent-soft used for hover elsewhere: an
   active state has to be distinguishable from a mouse-over on the same button,
   and a filled control reads as "on" at a glance. */
.toggle-advanced.has-filters {
    background: var(--accent);
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.toggle-advanced.has-filters:hover {
    filter: brightness(0.94);
}

/* Count of active fields, so the badge says how much is filtered, not just
   that something is. */
.advanced-count {
    position: absolute;
    top: -6px;
    right: -6px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: var(--brand-900);
    color: #fff;
    font-size: 0.68rem;
    font-weight: 700;
    line-height: 1;
}

/* The advanced toggle has no hover state because it has an explicit "on" look
   (.has-filters) that a hover would compete with. Refresh has no on/off state,
   so hover is the only affordance telling the visitor it is clickable. */
.refresh-catalog:hover:not(:disabled) {
    background: var(--accent-soft);
    border-color: var(--accent);
}

.refresh-catalog .refresh-ico {
    display: block;
    line-height: 1;
    font-size: 1.15rem;
}

/* Disabled only while a refresh is in flight, so the cursor must not suggest
   the control is unavailable in general. */
.refresh-catalog:disabled {
    cursor: default;
}

.refresh-catalog.is-refreshing .refresh-ico {
    animation: catalog-refresh-spin 0.7s linear infinite;
}

@keyframes catalog-refresh-spin {
    to {
        transform: rotate(360deg);
    }
}

/* Spinning is the only signal that a refresh is running when the response beats
   the loading overlay's delay, so it is dimmed rather than dropped entirely -
   removing the animation with nothing in its place would leave the click with
   no visible effect at all. */
@media (prefers-reduced-motion: reduce) {
    .refresh-catalog.is-refreshing .refresh-ico {
        animation: none;
        opacity: 0.45;
    }
}



/* Narrow viewports: keep title/back on one row, drop the search block below.
   Raised from 900px to 1024px when the "In Stock Only" chip joined the centre
   block, then to 1100px when the refresh button joined it — search (484px) +
   toggle (40px) + refresh (40px) + two 10px gaps + chip no longer fit beside
   the title and back button below roughly 1100px.

   This block MUST stay after the .product-search-wrap-inline base rule above —
   a media query adds no specificity, so when it sat earlier in the file the
   later min-width:484px won and the search box overflowed small screens. */
@media (max-width: 1100px) {
    .product-header-inline {
        grid-template-columns: 1fr auto;
        grid-template-areas:
            "left right"
            "center center";
        row-gap: 12px;
    }

    .product-header-left {
        grid-area: left;
    }

    .product-header-right {
        grid-area: right;
    }

    .product-header-center {
        grid-area: center;
        justify-self: stretch;
    }

    .product-search-wrap-inline {
        min-width: 0;
        max-width: none;
    }
}

.product-advanced-panel {
    display: none;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 18px;
    padding: 18px;
    box-shadow: var(--shadow);
    margin-bottom: 24px;
}

.product-advanced-panel.active {
    display: block;
}

.advanced-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 12px;
}

.advanced-grid input,
.advanced-grid select {
    width: 100%;
    border: 1px solid var(--border);
    background: #fff;
    border-radius: 10px;
    padding: 10px 12px;
    color: var(--text);
}

.advanced-actions {
    margin-top: 14px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 10px;
}

/* margin-right:auto pushes the hint to the left edge while the buttons stay
   right-aligned, without needing a second wrapper element. */
.advanced-hint {
    margin-right: auto;
    color: var(--muted);
    font-size: 0.8rem;
}

.btn-primary,
.btn-secondary,
.btn-outline {
    border: none;
    border-radius: 10px;
    padding: 10px 14px;
    font-weight: 600;
    cursor: pointer;
}

.btn-primary {
    background: var(--brand-900);
    color: #fff;
}

.btn-secondary {
    background: var(--accent-soft);
    color: var(--accent);
}

.btn-outline {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--brand-900);
}

/* Whole chip is the label, so the text is a click target too, not just the box. */
.stock-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    /* Height matches .toggle-advanced sitting next to it. */
    height: 40px;
    margin: 0;
    padding: 0 14px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: #fff;
    color: var(--brand-900);
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    transition: all 0.2s ease;
}

.stock-toggle:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.stock-toggle input {
    width: 16px;
    height: 16px;
    margin: 0;
    cursor: pointer;
    accent-color: var(--accent);
}

/* ---------------------------------------------------------------------------
   Clear button interaction states.

   The site theme ships Bootstrap's .btn-* palette, and its state rules are far
   more specific than our flat ones:

       .btn-secondary                                          -> 0,1,0  (ours)
       .btn-secondary:hover                                    -> 0,2,0
       .btn-secondary:not(:disabled):not(.disabled):active     -> 0,4,0

   so clicking Clear handed the button to the theme: dark grey fill, white text
   and a focus ring - and it stayed that way, because a clicked button keeps
   focus until something else takes it.

   Scoped to .btn-secondary inside the advanced panel only. Apply and the detail
   page buttons are deliberately left on their existing behaviour.
   --------------------------------------------------------------------------- */
.advanced-actions .btn-secondary,
.advanced-actions .btn-secondary:hover,
.advanced-actions .btn-secondary:focus,
.advanced-actions .btn-secondary:active {
    background: var(--accent-soft) !important;
    border-color: var(--accent-soft) !important;
    color: var(--accent) !important;
}

/* Suppress the theme's focus ring for pointer clicks only - keyboard users
   still get a visible ring below. */
.advanced-actions .btn-secondary:focus {
    outline: none !important;
    box-shadow: none !important;
}

.advanced-actions .btn-secondary:focus-visible {
    outline: 2px solid var(--accent) !important;
    outline-offset: 2px !important;
}

/* auto-fill, not auto-fit: auto-fit collapses the unused tracks, so a single
   search result would stretch across the whole row. auto-fill keeps the empty
   tracks in place, holding every card at one column width no matter how few
   results come back. */
.product-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 20px;
}

.product-card {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: 18px;
    overflow: hidden;
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.04);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.product-card:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow);
}

/* An <a> wrapping the slides — display:block is required or the width/height
   below would not apply to an inline element. */
.product-card-image {
    display: block;
    position: relative;
    overflow: hidden;
    width: 100%;
    height: 220px;
    background: linear-gradient(135deg, #f6f8fa, #edf2f7);
    cursor: pointer;
}

.product-card-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Slides are stacked and pushed sideways rather than shown/hidden, so the card
   never flashes an empty box between images.

   Every idle slide parks off the right edge, the incoming one travels to 0 and
   the outgoing one carries on out to the left — so the strip always reads as
   moving right-to-left. .no-anim is how a spent slide is returned to the right
   without being seen crossing the card backwards. */
.product-card-image .card-slide {
    position: absolute;
    inset: 0;
    transform: translateX(100%);
    transition: transform 0.6s ease;
}

.product-card-image .card-slide.is-active {
    transform: translateX(0);
    z-index: 1;
}

.product-card-image .card-slide.is-leaving {
    transform: translateX(-100%);
    z-index: 1;
}

.product-card-image .card-slide.no-anim {
    transition: none;
}

/* Item code chip over the picture. Absolutely positioned and inside the fixed
   height image box, so it costs no layout: the card is exactly the size it was
   before. z-index clears the slides, which raise themselves while moving. */
.product-card-sku {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
    max-width: calc(100% - 16px);
    padding: 3px 7px;
    border-radius: 6px;
    background: rgba(15, 23, 42, 0.62);
    color: #fff;
    font-size: 0.68rem;
    font-weight: 700;
    line-height: 1.3;
    letter-spacing: 0.02em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* The whole picture is a link to the detail page - the chip must not eat
       the click. */
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .product-card-image .card-slide {
        transition: none;
    }
}

.product-card-body {
    padding: 10px 12px 12px;
}

/* Clamped to two lines rather than given a min-height: a min-height reserves
   the second line even for one-line names, which is where the dead space came
   from. The clamp caps tall names without padding short ones. */
.product-card-name {
    margin: 0 0 6px;
    font-size: 0.90rem;
    font-weight: 700;
    line-height: 1.35;
    color: var(--brand-900);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Item group: a quiet classification line under the name, deliberately smaller
   and lighter than the name so it reads as a label rather than competing. */
.product-card-group {
    margin: 0 0 6px;
    color: var(--muted);
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Price and stock share one row directly under the name. */
.product-card-meta {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

.product-card-price {
    margin: 0;
    color: var(--accent);
    font-weight: 800;
    font-size: 1rem;
    white-space: nowrap;
}

.product-card-stock {
    margin: 0;
    color: var(--muted);
    font-size: 0.82rem;
    white-space: nowrap;
}

.product-card-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Tighter than the old 8px: three buttons now share a 240px card. */
    gap: 6px;
    margin-top: 10px;
}

/* Shared action-button appearance for the card footer (View / Save / Share) and
   for the detail page and popup action rows, so the two cannot drift apart.
   font-family is explicit because these render as both <button> and <a>, and a
   button does not inherit the page font. */
.card-detail-btn,
.card-watch-btn,
.card-share-btn,
.create-quotation-btn,
.detail-actions > button,
.detail-actions > a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #e8e8e8;
    color: var(--brand-900);
    font-family: inherit;
    font-weight: 600;
    font-size: 0.85rem;
    line-height: 1.4;
    text-decoration: none;
    cursor: pointer;
    transition: all 0.2s ease;
}

/* Card footer: the three buttons split the row evenly, and min-width lets them
   shrink rather than the widest label forcing the row wider than the card. */
.card-detail-btn,
.card-watch-btn,
.card-share-btn {
    flex: 1;
    min-width: 0;
    padding: 6px 8px;
    white-space: nowrap;
}

/* Detail rows: size to the label instead, since these sit in a free-flowing
   row and carry longer text such as "View Full Page". */
.detail-actions > button,
.detail-actions > a {
    flex: 0 0 auto;
    padding: 8px 16px;
}

/* Hover highlight, and the saved state, declared together so the "Saved"
   button is highlighted with exactly the hover background the other buttons
   use — one rule, no chance of the two drifting apart. */
.card-detail-btn:hover,
.card-watch-btn:hover,
.card-share-btn:hover,
.create-quotation-btn:hover,
.detail-actions > button:hover,
.detail-actions > a:hover,
.btn-load-more:hover,
.card-watch-btn.is-saved,
.detail-actions [data-watch-item].is-saved {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent);
}

/* Share popover. Fixed positioning, placed by JS from the button's rect, so it
   escapes the card's overflow:hidden and stays inside the viewport. */
.share-menu {
    position: fixed;
    z-index: 2100;
    display: none;
    min-width: 190px;
    padding: 6px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow);
}

.share-menu.is-open {
    display: block;
}

.share-menu a,
.share-menu button {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--text);
    font-size: 0.88rem;
    font-weight: 500;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.share-menu a:hover,
.share-menu button:hover {
    background: var(--brand-100);
}

.share-menu .share-ico {
    width: 18px;
    text-align: center;
    flex: 0 0 auto;
}

/* QR dialog. z-index sits above the product modal (2000) and the share menu
   (2100), since it can be opened from inside either. */
.qr-modal {
    position: fixed;
    inset: 0;
    z-index: 2200;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(15, 23, 42, 0.6);
}

.qr-modal.is-open {
    display: flex;
}

.qr-modal-panel {
    position: relative;
    width: min(92vw, 400px);
    max-height: 90vh;
    overflow: auto;
    padding: 24px;
    background: #fff;
    border-radius: 18px;
    box-shadow: 0 25px 60px rgba(15, 23, 42, 0.28);
    text-align: center;
}

.qr-modal-close {
    position: absolute;
    top: 10px;
    right: 12px;
    width: 30px;
    height: 30px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}

.qr-modal-close:hover {
    background: var(--brand-100);
    color: var(--brand-900);
}

.qr-modal-title {
    margin: 0 0 6px;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}

.qr-modal-name {
    margin-bottom: 16px;
    font-weight: 700;
    color: var(--brand-900);
    overflow-wrap: anywhere;
}

.qr-modal-canvas {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 200px;
}

/* The canvas is drawn at an integer module scale; capping by width keeps it
   responsive without resampling it blurry on small screens. */
.qr-modal-canvas canvas {
    max-width: 100%;
    height: auto;
    border: 1px solid var(--border);
    border-radius: 12px;
}

.qr-modal-status {
    color: var(--muted);
    font-size: 0.9rem;
}

.qr-modal-url {
    margin: 14px 0 16px;
    font-size: 0.78rem;
    color: var(--muted);
    overflow-wrap: anywhere;
}

.qr-modal-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
}

/* Full-screen image viewer. Above the product modal (2000), share menu (2100)
   and QR dialog (2200), since it opens from the carousel inside them. */
/* overflow:hidden because the zoomed image scales past the viewport and would
   otherwise add scrollbars to the page behind it. */
.image-lightbox {
    position: fixed;
    inset: 0;
    z-index: 2300;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 32px;
    overflow: hidden;
    background: rgba(9, 12, 18, 0.92);
}

.image-lightbox.is-open {
    display: flex;
}

/* contain, not cover: the whole picture has to be visible at full size. */
.image-lightbox-img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 6px;
    cursor: zoom-in;
    transition: transform 0.25s ease;
}

/* Scaled about the cursor, which JS keeps updated via transform-origin. No
   transition while zoomed, or panning would lag a frame behind the pointer. */
.image-lightbox-img.is-zoomed {
    transform: scale(2.4);
    cursor: zoom-out;
    transition: none;
}

.image-lightbox-hint {
    position: absolute;
    z-index: 2;
    bottom: 18px;
    left: 50%;
    transform: translateX(-50%);
    padding: 6px 14px;
    border-radius: 20px;
    background: rgba(255, 255, 255, 0.14);
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.8rem;
    pointer-events: none;
}

/* The hint has served its purpose once the image is magnified. */
.image-lightbox.is-zoomed .image-lightbox-hint {
    opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
    .image-lightbox-img {
        transition: none;
    }
}

/* Above the image: once scaled, it paints over this corner otherwise. */
.image-lightbox-close {
    position: absolute;
    z-index: 2;
    top: 18px;
    right: 22px;
    width: 44px;
    height: 44px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
    color: #fff;
    font-size: 26px;
    line-height: 1;
    cursor: pointer;
    transition: background 0.2s ease;
}

.image-lightbox-close:hover {
    background: rgba(255, 255, 255, 0.32);
}

/* Signals that the picture opens full screen. */
.detail-carousel img {
    cursor: zoom-in;
}

/* Hover magnifier. The lens sits over the picture, below the carousel controls
   (which are z-index auto inside the same stacking context, so keep this low). */
.zoom-lens {
    position: absolute;
    top: 0;
    left: 0;
    display: none;
    border: 1px solid rgba(255, 255, 255, 0.85);
    background: rgba(255, 255, 255, 0.22);
    box-shadow: 0 0 0 9999px rgba(15, 23, 42, 0.18);
    pointer-events: none;
    z-index: 2;
}

.zoom-lens.is-visible {
    display: block;
}

/* Fixed, and positioned from the image's rect, so neither the image box's
   overflow:hidden nor the grid column can clip it. */
.zoom-panel {
    position: fixed;
    display: none;
    overflow: hidden;
    z-index: 1900;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: 0 24px 55px rgba(15, 23, 42, 0.22);
    pointer-events: none;
}

.zoom-panel.is-visible {
    display: block;
}

/* object-fit:cover matches the crop of the source image, so the magnified view
   shows the same framing, only larger.

   max-width/max-height must be cleared: the site theme ships a global
   `img{max-width:100%;height:auto}`, which capped this image to the panel width
   and left the translated view showing blank panel. An inline width does not
   override a max-width, so it has to be reset here. */
.zoom-panel-img {
    position: absolute;
    top: 0;
    left: 0;
    max-width: none;
    max-height: none;
    min-width: 0;
    min-height: 0;
    object-fit: cover;
    will-change: transform;
}

@media (max-width: 640px) {
    .image-lightbox {
        padding: 16px;
    }

    .image-lightbox-close {
        top: 10px;
        right: 12px;
        width: 38px;
        height: 38px;
        font-size: 22px;
    }
}

/* Card action icons. Fixed box so the two buttons stay the same height
   regardless of each source PNG's aspect ratio. */
.card-btn-icon {
    display: block;
    width: 20px;
    height: 20px;
    object-fit: contain;
    opacity: 0.85;
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.card-detail-btn:hover .card-btn-icon,
.card-watch-btn:hover .card-btn-icon {
    opacity: 1;
    transform: scale(1.08);
}

.card-watch-btn.is-saved .card-btn-icon {
    opacity: 1;
}

.product-detail-layout {
    display: grid;
    grid-template-columns: 1.2fr 0.8fr;
    gap: 28px;
    padding: 20px 0 36px;
}

/* Holds the carousel and, beneath it, the description block. align-self:start
   keeps it from stretching to match a taller spec panel. */
.detail-image-box {
    display: flex;
    flex-direction: column;
    align-self: start;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 22px;
    overflow: hidden;
    box-shadow: var(--shadow);
}

/* The box itself is unpadded so the carousel can bleed to its edges; the
   description needs its own padding and a rule to separate it. */
.detail-image-box .detail-section {
    margin-top: 0;
    padding: 18px 20px 20px;
    border-top: 1px solid var(--border);
}

/* Fixed height, because the images below are taken out of flow so they can be
   stacked and slid across; nothing is left to hold the box open. */
.detail-carousel {
    position: relative;
    overflow: hidden;
    background: linear-gradient(135deg, #f8fafc, #edf2f7);
    height: 460px;
}

/* Same right-to-left travel as the cards, but directional: .pos-right and
   .pos-left name which side a slide is parked on, so Prev can run the move the
   other way round instead of always pulling from the right. */
.detail-carousel img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transform: translateX(100%);
    transition: transform 0.6s ease;
}

.detail-carousel img.pos-right {
    transform: translateX(100%);
}

.detail-carousel img.pos-left {
    transform: translateX(-100%);
}

.detail-carousel img.is-active {
    transform: translateX(0);
    z-index: 1;
}

.detail-carousel img.no-anim {
    transition: none;
}

@media (prefers-reduced-motion: reduce) {
    .detail-carousel img {
        transition: none;
    }
}

.carousel-controls {
    position: absolute;
    inset: auto 16px 16px 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.carousel-btn {
    border: none;
    background: rgba(17, 24, 39, 0.5);
    color: #fff;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    cursor: pointer;
    font-size: 20px;
}

.carousel-dots {
    display: flex;
    align-items: center;
    gap: 6px;
    background: rgba(15, 23, 42, 0.18);
    border-radius: 999px;
    padding: 6px 10px;
}

.carousel-dot {
    width: 8px;
    height: 8px;
    background: rgba(255,255,255,0.7);
    border-radius: 50%;
    display: inline-block;
}

.carousel-dot.active {
    background: white;
    width: 18px;
    border-radius: 99px;
}

.detail-panel {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 22px;
    padding: 20px 20px 16px;
    box-shadow: var(--shadow);
}

.detail-panel h2 {
    margin: 0 0 10px;
    font-size: clamp(1.8rem, 2vw, 2.4rem);
    color: var(--brand-900);
}

/* Body-sized, matching the price on the list cards. It was 1.8rem, nearly as
   large as the product title above it. Colour and weight are unchanged. */
.detail-price {
    font-size: 1rem;
    font-weight: 800;
    color: var(--accent);
    margin-bottom: 12px;
}

.detail-meta {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}

/* Filled with the same accent used for the hover/saved highlight, white text on
   top. The previous pale-on-pale pairing (--brand-100 behind --brand-700) sat
   at roughly 1.3:1 contrast, which is why the tags read as washed out. */
.detail-meta span {
    background: var(--accent);
    color: #fff;
    border-radius: 999px;
    padding: 6px 10px;
    font-size: 0.8rem;
    font-weight: 600;
}

.detail-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 18px;
    flex-wrap: wrap;
}


.detail-section {
    margin-top: 18px;
}

.detail-section h3 {
    margin: 0 0 10px;
    font-size: 1rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
}

.detail-specs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px 18px;
    padding-top: 10px;
}

.detail-spec {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    border-radius: 12px;
    background: #f8fafc;
    border: 1px solid #edf2f7;
}

/* These are display captions, not form labels — a <label> with no associated
   control fails accessibility checks, so they are spans. */
.detail-spec .detail-spec-label {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
}

.detail-spec span {
    color: var(--text);
    line-height: 1.5;
}

.detail-description {
    color: var(--text);
    line-height: 1.7;
}

/* Catalog loading overlay.

   pointer-events:none is deliberate — search runs as you type, so this appears
   on every query. Blocking input would make the field feel broken; the user has
   to stay able to keep typing and scrolling while results are fetched.

   z-index sits above the page but below the share menu (2100), QR dialog (2200)
   and lightbox (2300). */
.catalog-loading {
    position: fixed;
    inset: 0;
    z-index: 1800;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background: rgba(240, 242, 246, 0.82);
    pointer-events: none;
}

.catalog-loading.is-active {
    display: flex;
}

/* Fixed box: the source GIF is 475x480, far larger than it should render. */
.catalog-loading-img {
    width: 72px;
    height: 72px;
    object-fit: contain;
}

.catalog-loading-text {
    color: var(--muted);
    font-size: 0.9rem;
    font-weight: 600;
}

.empty-state {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 18px;
    padding: 28px;
    text-align: center;
    box-shadow: 0 10px 24px rgba(15, 23, 42, 0.04);
    color: var(--muted);
}

/* The "no results" message is injected into the grid, so it has to span every
   column — otherwise auto-fill confines it to a single 240px track. */
.product-grid .empty-state {
    grid-column: 1 / -1;
}

.watch-later-drawer {
    position: fixed;
    right: 20px;
    bottom: 90px;
    width: min(360px, calc(100vw - 24px));
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 18px;
    box-shadow: var(--shadow);
    display: none;
    z-index: 1500;
    overflow: hidden;
}

.watch-later-drawer.active {
    display: block;
}

.watch-later-header {
    padding: 16px 18px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.watch-later-list {
    max-height: 360px;
    overflow: auto;
    padding: 12px 14px 16px;
}

.watch-later-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 0;
    border-bottom: 1px solid #eef2f7;
}

.watch-later-item:last-child {
    border-bottom: none;
}

/* min-width:0 lets a long product name wrap inside the row instead of pushing
   the Remove button out of the drawer. */
.watch-later-item-link {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.watch-later-item-thumb {
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: 8px;
    border: 1px solid var(--border);
    object-fit: cover;
    background: #f6f8fa;
}

.watch-later-item-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.watch-later-item-code {
    color: var(--brand-900);
    font-weight: 700;
    font-size: 0.85rem;
}

.watch-later-item-name {
    color: var(--muted);
    font-weight: 500;
    font-size: 0.82rem;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

.watch-later-item-link:hover .watch-later-item-code {
    color: var(--accent);
}

.watch-later-item-link:hover .watch-later-item-name {
    color: var(--brand-900);
}

/* Action row at the bottom of the Saved Products drawer. Only rendered for a
   logged-in Desk user, so it is absent entirely for guests. */
.watch-later-footer {
    padding: 12px 14px 14px;
    border-top: 1px solid var(--border);
}

/* Same look as the card actions; only the drawer layout differs. */
.create-quotation-btn {
    width: 100%;
    padding: 9px 12px;
}

.create-quotation-btn:disabled {
    opacity: 0.6;
    cursor: default;
}

.watch-later-note {
    margin-top: 8px;
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--muted);
}

.watch-later-note.is-warn {
    color: var(--accent);
}

.watch-later-note.is-error {
    color: #b3261e;
}

.watch-later-item button {
    border: none;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    flex: 0 0 auto;
    white-space: nowrap;
}

/* Carries the same colours as an active card action button (the hover/saved
   state shared by View / Save / Share), so the floating control and the card
   controls read as one family. */
.floating-watch-later {
    position: fixed;
    right: 22px;
    bottom: 22px;
    z-index: 1400;
    border: 1px solid var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
    border-radius: 50px;
    padding: 14px 18px;
    box-shadow: 0 18px 35px rgba(43, 68, 87, 0.28);
    font-weight: 700;
    cursor: pointer;
}

/* Sits directly above the Watch Later pill (22px + its ~50px height + gap)
   so the two fixed controls never overlap. */
.scroll-top-btn {
    position: fixed;
    right: 22px;
    bottom: 84px;
    z-index: 1400;
    width: 44px;
    height: 44px;
    display: none;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: #fff;
    color: var(--brand-900);
    font-size: 1.15rem;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 10px 24px rgba(15, 23, 42, 0.16);
    transition: background 0.2s ease, transform 0.2s ease;
}

.scroll-top-btn.is-visible {
    display: flex;
}

.scroll-top-btn:hover {
    background: var(--brand-100);
    transform: translateY(-2px);
}

.floating-watch-later .count-badge {
    display: inline-flex;
    width: 24px;
    height: 24px;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    margin-left: 8px;
    font-size: 0.78rem;
}

@media (max-width: 860px) {
    .product-detail-layout {
        grid-template-columns: 1fr;
    }

    .detail-carousel {
        height: 360px;
    }

    .detail-specs {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 640px) {
    .product-header-block,
    .product-topbar,
    .product-controls {
        flex-direction: column;
        align-items: stretch;
    }

    .product-page {
        padding-top: 20px;
    }

    .product-card-image {
        height: 200px;
    }

    .floating-watch-later {
        right: 14px;
        bottom: 14px;
    }

    .scroll-top-btn {
        right: 14px;
        bottom: 72px;
        width: 40px;
        height: 40px;
    }
}

.pagination-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-top: 32px;
    padding: 20px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
}

.pagination-info {
    font-size: 0.95rem;
    color: var(--text);
    font-weight: 500;
}

.pagination-controls {
    display: flex;
    align-items: center;
    gap: 12px;
}

.items-per-page-select {
    border: 1px solid var(--border);
    background: #fff;
    border-radius: 8px;
    padding: 8px 12px;
    font-size: 0.9rem;
    color: var(--text);
    cursor: pointer;
}

.btn-load-more {
    border: 1px solid var(--border);
    background: #fff0e3;
    color: #000000;
    border-radius: 8px;
    padding: 10px 16px;
    font-weight: 600;
    cursor: pointer;
    font-size: 0.9rem;
    transition: all 0.2s ease;
}
/* 
.btn-load-more:hover {
    background: #ff8d21;
    border-color: #954800;
    transform: translateY(-1px);
} */

@media (max-width: 640px) {
    .pagination-footer {
        flex-direction: column;
        align-items: stretch;
    }

    .pagination-controls {
        flex-direction: column;
    }

    .items-per-page-select,
    .btn-load-more {
        width: 100%;
    }
}

/* --------------------------------------------------------------------------
   Small-screen refinements. Appended last on purpose: media queries add no
   specificity, so an override only wins if it comes after the rule it targets.
   -------------------------------------------------------------------------- */

@media (max-width: 860px) {
    .detail-carousel {
        height: 360px;
    }
}

@media (max-width: 640px) {
    .product-page {
        padding-left: 12px;
        padding-right: 12px;
    }

    .product-header-inline {
        padding: 12px;
    }

    /* Full row for the field; the toggle and In Stock chip wrap underneath. */
    .product-search-wrap-inline {
        flex: 1 1 100%;
    }

    .product-title-inline {
        font-size: 1.15rem;
    }

    .detail-carousel {
        height: 260px;
    }

    .detail-panel {
        padding: 16px 14px 14px;
    }

    /* clamp()'s floor is 1.8rem, which is oversized for a long product name on
       a phone. */
    .detail-panel h2 {
        font-size: 1.35rem;
    }

    .detail-image-box .detail-section {
        padding: 14px 16px 16px;
    }

    /* Line up with the floating button, which moves to right:14px here. */
    .watch-later-drawer {
        right: 14px;
        bottom: 78px;
    }

    /* "Showing 20 of 1,239 items" beside a select and a button does not fit on
       one row at phone widths — stack them and let the controls fill it. */
    .pagination-footer {
        flex-direction: column;
        align-items: stretch;
        gap: 12px;
        padding: 14px;
    }

    .pagination-controls {
        flex-direction: column;
        align-items: stretch;
    }

    .pagination-info {
        text-align: center;
    }

    .items-per-page-select,
    .btn-load-more {
        width: 100%;
    }

    /* Long unbroken product names must wrap rather than widen the card. */
    .product-card-name,
    .detail-panel h2 {
        overflow-wrap: anywhere;
    }
}
