/* CHROME: cross-cutting primitives used by more than one chrome page — status
 * badges, order metadata, and the structural (non-input) parts of the address
 * form (map picker, city autocomplete, button row). NOT opt-out-able (see PRD
 * Bagian I), same reasoning as overlays.css: these surface across multiple
 * chrome pages, so a partial exclude leaves holes.
 *
 * UPDATE (2026-09-24, Tailwind→plain-CSS migration): .btn, .eco-field-input
 * and .eco-form-input ARE now here (below), superseding the "Layer 2 Form
 * control stays theme-owned" note that used to be here — extracted verbatim
 * from themes/default/index.html as unstyled-until-migrated themes lean on
 * these for basic usability (a button/input with zero border/padding is
 * unusable, not just unstyled). Every theme's own unlayered CSS still wins
 * on top of this exactly as before; nothing about the override mechanism
 * changed, only the baseline every theme falls back to.
 *
 * UPDATE 2 (2026-09-24, same migration): .eco-checkbox*, .eco-form-label,
 * .eco-form-strength* (password strength meter) and the rest of the form
 * scaffolding (.eco-field*, .eco-form-row/-help/-group/-error) are ALSO here
 * now, superseding the two notes that used to be here about both of those
 * staying theme-owned — including the password-strength meter one, which
 * had specifically documented that all 7 themes render it differently
 * (2px/3px/4px/7px bars, hairline vs boxed, mono vs sans labels). That
 * per-theme variance is unchanged; this is still only the DEFAULT fallback
 * a theme falls back to before writing its own unlayered override, same as
 * every other rule in this file — themes that already have their own look
 * keep winning exactly as before.
 *
 * Deliberately still NOT here: .eco-cta (a distinct "big CTA" component,
 * not a form control — chrome only REFERENCES it, never redefines it).
 *
 * This chunk is compiled standalone (see resources/css/chrome/_tokens.css's own
 * header comment) so it keeps its own copy of every plain Tailwind design token
 * its rules below actually use — only what's referenced via var(), nothing more.
 */
:root {
    --color-foreground: #17140f;
    --color-background: #fff;
    --color-danger: var(--color-rose-700);
    --color-rose-700: oklch(51.4% 0.222 16.935);
    --color-success: var(--color-green-700);
    --color-green-700: oklch(52.7% 0.154 150.069);
    --color-pending: var(--color-amber-700);
    --color-amber-700: oklch(55.5% 0.163 48.998);
    --color-black: #000;
    --color-line: #e4e1d9;
    --color-gray-400: oklch(70.7% 0.022 261.325);
    --spacing: 0.25rem;
    --text-xs: 0.75rem;
    --text-xs--line-height: calc(1 / 0.75);
    --text-sm: 0.875rem;
    --text-sm--line-height: calc(1.25 / 0.875);
    --text-base: 1rem;
    --text-base--line-height: calc(1.5 / 1);
    --text-lg: 1.125rem;
    --text-lg--line-height: calc(1.75 / 1.125);
    --font-weight-normal: 400;
    --font-weight-medium: 500;
    --font-weight-semibold: 600;
    --leading-relaxed: 1.625;
}
@layer chrome {
    /* Baseline button (.btn / .btn-primary, also aliased by .eco-btn-login /
       .eco-btn-register on auth pages) and text-input field (.eco-field-input,
       .eco-form-input) look — extracted verbatim from themes/default/
       index.html. See the file header note above for why this is here now.
       Every theme's own unlayered .btn/.eco-field-input/.eco-form-input rule
       still wins on top of this by normal cascade-layer precedence. */
    .btn,
    .eco-btn-login,
    .eco-btn-register {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 0.5rem;
        padding-inline: 1.25rem;
        padding-block: 0.625rem;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 25%, transparent);
        background-color: transparent;
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
        font-weight: var(--font-weight-medium, 500);
        text-transform: uppercase;
        letter-spacing: 0.1em;
        transition: background 0.18s, color 0.18s, border-color 0.18s;
    }
    :is(.btn, .eco-btn-login, .eco-btn-register):hover:not([disabled]) {
        border-color: color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        background-color: color-mix(in oklab, var(--color-foreground, #17140f) 10%, transparent);
    }
    :is(.btn, .eco-btn-login, .eco-btn-register)[disabled] {
        cursor: not-allowed;
        opacity: 0.5;
    }
    :is(.btn, .eco-btn-login, .eco-btn-register).btn-primary {
        border-color: var(--color-foreground, #17140f);
        background-color: var(--color-foreground, #17140f);
        color: var(--color-background, #fff);
    }
    :is(.btn, .eco-btn-login, .eco-btn-register).btn-primary:hover:not([disabled]) {
        border-color: var(--color-foreground, #17140f);
        background-color: var(--color-foreground, #17140f);
        opacity: 0.8;
    }

    .eco-field-input {
        width: 100%;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        padding-inline: 1rem;
        padding-block: 0.5rem;
        appearance: textfield;
    }
    .eco-field-input::-webkit-outer-spin-button,
    .eco-field-input::-webkit-inner-spin-button {
        margin: 0;
        appearance: none;
    }

    .eco-form-input {
        width: 100%;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        padding-inline: 1rem;
        padding-block: 0.5rem;
        background-color: transparent;
        font-size: var(--text-sm, 0.875rem);
        line-height: var(--text-sm--line-height, 1.4286);
        appearance: textfield;
    }
    .eco-form-input::placeholder {
        opacity: 0.4;
    }
    .eco-form-input.eco-form-input--error {
        border-color: color-mix(in oklab, var(--color-danger, #be123c) 60%, transparent);
    }
    .eco-form-input::-webkit-outer-spin-button,
    .eco-form-input::-webkit-inner-spin-button {
        margin: 0;
        appearance: none;
    }
    textarea.eco-form-input {
        resize: none;
    }
    button.eco-form-input {
        display: flex;
        align-items: center;
        justify-content: space-between;
        text-align: start;
        cursor: pointer;
    }
    button.eco-form-input svg {
        width: 1rem;
        height: 1rem;
        flex: none;
        margin-inline-start: auto;
        opacity: 0.5;
    }

    /* Mobile container padding — chrome pages only. The equivalent theme-owned
       selectors (.container, .eco-hero-inner, product-detail's product-main, …)
       stay in each theme's own CSS; see PRD Bagian VI migration notes. */
    @media (max-width: 767px) {
        [data-page="cart-index"],
        [data-page="checkout-index"],
        [data-page="checkout-success"],
        [data-page^="account-"],
        [data-page="wishlist"],
        [data-page="coupons"] { padding-inline: calc(var(--spacing) * 4); }
    }

    /* Address form — structural pieces only (map picker, city autocomplete,
       button row). The actual field/checkbox/button LOOK is theme-owned; this
       file only lays the pieces out and reuses whatever the theme declares. */
    .eco-address-form {
        display: flex;
        flex-direction: column;
    }
    .eco-form-label-optional {
        font-weight: var(--font-weight-normal);
        opacity: .5;
    }
    .eco-map-picker {
        display: flex;
        flex-direction: column;
        gap: calc(var(--spacing) * 2);
        .eco-map-picker-bar {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: calc(var(--spacing) * 3);
        }
        .eco-map-picker-coords {
            display: flex;
            align-items: center;
            gap: calc(var(--spacing) * 1.5);
            font-size: var(--text-xs);
            line-height: var(--text-xs--line-height);
            svg { width: calc(var(--spacing) * 3.5); height: calc(var(--spacing) * 3.5); flex: none; }
        }
        .eco-map-picker-coords--empty {
            opacity: .4;
        }
        .eco-map-picker-toggle {
            font-size: var(--text-xs);
            line-height: var(--text-xs--line-height);
            flex: none;
            padding-block: calc(var(--spacing) * 1);
            padding-inline: calc(var(--spacing) * 3);
        }
        .eco-map-picker-resync {
            text-align: left;
            font-size: var(--text-xs);
            line-height: var(--text-xs--line-height);
            text-decoration: underline;
            text-underline-offset: 2px;
            opacity: .7;
            &:hover { opacity: 1; }
            &:disabled { text-decoration: none; opacity: .4; cursor: not-allowed; }
        }
        .eco-map-wrap {
            position: relative;
            border: 1px solid color-mix(in oklab, var(--color-foreground) 20%, transparent);
        }
        .eco-map-container {
            /* grayscale here, not on .eco-map-wrap — keeps the crosshair/controls
               layered on top at full contrast, only the tile imagery desaturates.
               h-96 (24rem) doubled to 48rem — width already fills the ~920px modal
               body (no narrow label column here, unlike admin's Koordinat field),
               so more room for precise pin placement only needed on this axis. */
            width: 100%;
            height: 48rem;
            filter: grayscale(1);
        }
        .eco-map-crosshair {
            position: absolute;
            pointer-events: none;
            z-index: 10;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -100%);
            svg {
                width: calc(var(--spacing) * 9);
                height: calc(var(--spacing) * 9);
                fill: var(--color-foreground);
                stroke: var(--color-background);
                stroke-width: 1.5px;
                filter: drop-shadow(0 2px 6px rgb(0 0 0 / 0.5));
            }
        }
        .eco-map-controls {
            position: absolute;
            top: calc(var(--spacing) * 2);
            right: calc(var(--spacing) * 2);
            z-index: 10;
            display: flex;
            flex-direction: column;
            gap: calc(var(--spacing) * 1);
            .eco-map-btn {
                width: calc(var(--spacing) * 7);
                height: calc(var(--spacing) * 7);
                display: flex;
                align-items: center;
                justify-content: center;
                background-color: var(--color-background);
                border: 1px solid color-mix(in oklab, var(--color-foreground) 20%, transparent);
                &:hover {
                    background-color: color-mix(in oklab, var(--color-foreground) 5%, transparent);
                }
                svg { width: calc(var(--spacing) * 3.5); height: calc(var(--spacing) * 3.5); }
            }
        }
        .eco-map-hint {
            position: absolute;
            bottom: calc(var(--spacing) * 2);
            left: 50%;
            translate: -50% 0;
            font-size: var(--text-xs);
            line-height: var(--text-xs--line-height);
            background-color: color-mix(in oklab, var(--color-background) 90%, transparent);
            padding-inline: calc(var(--spacing) * 2.5);
            padding-block: calc(var(--spacing) * 1);
            border: 1px solid color-mix(in oklab, var(--color-foreground) 10%, transparent);
            white-space: nowrap;
            pointer-events: none;
        }
    }
    .eco-field-select-wrap {
        position: relative;
    }
    .eco-location-dropdown {
        position: fixed;
        z-index: 50;
        border: 1px solid color-mix(in oklab, var(--color-foreground) 20%, transparent);
        background-color: var(--color-background);
        overflow: hidden;
        display: flex;
        flex-direction: column;
        min-width: 20rem;
        max-height: 16rem;
    }
    .eco-location-search {
        flex: none;
        border: 0;
        border-bottom: 1px solid color-mix(in oklab, var(--color-foreground) 20%, transparent);
        font-size: var(--text-sm);
        line-height: var(--text-sm--line-height);
        &:focus {
            outline: none;
        }
    }
    .eco-location-list {
        flex: 1;
        overflow-y: auto;
    }
    .eco-location-item {
        width: 100%;
        text-align: start;
        padding-inline: calc(var(--spacing) * 4);
        padding-block: calc(var(--spacing) * 2.5);
        font-size: var(--text-sm);
        line-height: var(--text-sm--line-height);
        cursor: pointer;
        &:hover {
            background-color: color-mix(in oklab, var(--color-foreground) 4%, transparent);
        }
        &.eco-location-item--selected {
            background-color: color-mix(in oklab, var(--color-foreground) 6%, transparent);
        }
        mark {
            background-color: transparent;
            font-weight: var(--font-weight-medium);
        }
    }
    .eco-location-empty {
        padding-inline: calc(var(--spacing) * 4);
        padding-block: calc(var(--spacing) * 3);
        font-size: var(--text-sm);
        line-height: var(--text-sm--line-height);
        opacity: .5;
    }
    .eco-address-form-btn-row {
        display: flex;
        justify-content: flex-end;
        gap: calc(var(--spacing) * 2);
        margin-top: calc(var(--spacing) * 4);
    }

    /* Product card image box + slide arrows — structural piece of
       components/ecommerce/product-card-ssr.blade.php (the shared SSR
       primitive, see its own header comment), extracted verbatim from
       themes/default/index.html. Not scoped under .eco-product-grid like
       default's original rule was — this markup also renders inside
       quick-preview/cart-sheet contexts, so the selector is generalized to
       plain .eco-product-card. Other themes (bloom, mosaic, …) still style
       this differently today (aspect-ratio, radius, arrow look) — their own
       unlayered CSS keeps winning here exactly as before, this is only the
       default fallback for a theme that hasn't defined its own. */
    .eco-product-card {
        position: relative;
        overflow: hidden;
        /* Safety cap for when .eco-product-grid's own column sizing isn't
           present (chrome-only testing with the theme's <style> disabled) —
           wider than the grid's own widest column (354px at its 96rem/4-col
           max) so it never actually kicks in on a real page; the grid's
           minmax(0, 1fr) columns keep winning as before. */
        max-width: 24rem;
    }
    .eco-product-card:hover .eco-card-slider-arrow.eco-card-slider-arrow--prev {
        inset-inline-start: 0;
    }
    .eco-product-card:hover .eco-card-slider-arrow.eco-card-slider-arrow--next {
        inset-inline-end: 0;
    }
    .eco-product-card .eco-product-card-img {
        width: 100%;
        aspect-ratio: 1 / 1.2;
    }
    .eco-product-card .eco-card-slider-arrow {
        position: absolute;
        top: 0;
        width: 2.5rem;
        height: 2.5rem;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        background-color: var(--color-background, #fff);
        color: var(--color-foreground, #17140f);
        opacity: 0.7;
        transition: all 0.15s ease;
    }
    .eco-product-card .eco-card-slider-arrow:hover {
        opacity: 1;
    }
    .eco-product-card .eco-card-slider-arrow.eco-card-slider-arrow--prev {
        margin-inline-start: 0.5rem;
        margin-top: 50%;
        inset-inline-start: -3.5rem;
    }
    .eco-product-card .eco-card-slider-arrow.eco-card-slider-arrow--next {
        margin-inline-end: 0.5rem;
        margin-top: 50%;
        inset-inline-end: -3.5rem;
    }

    /* Rest of the product card (name, wishlist button, promos, sold count) —
       same extraction as the image box above, extracted verbatim from
       themes/default/index.html, selector generalized from
       .eco-product-grid .eco-product-card to plain .eco-product-card for the
       same reason. Other themes' own unlayered CSS keeps winning on top of
       this, this is only the default fallback. */
    .eco-product-card:hover .eco-product-card-wishlist {
        inset-inline-end: 0.5rem;
        top: 0.5rem;
    }
    .eco-product-card .eco-product-card-name {
        margin-bottom: 0.25rem;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
    .eco-product-card .eco-product-card-wishlist {
        position: absolute;
        inset-inline-end: -3.5rem;
        top: -3.5rem;
        width: 2.5rem;
        height: 2.5rem;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        background-color: var(--color-background, #fff);
        color: var(--color-danger, #be123c);
        opacity: 0.7;
        transition: all 0.15s ease;
    }
    .eco-product-card .eco-product-card-wishlist:hover {
        opacity: 1;
    }
    .eco-product-card .eco-product-card-wishlist.eco-wishlisted svg {
        fill: var(--color-danger, #be123c);
    }
    .eco-product-card .eco-product-card-promos {
        margin-top: 0.75rem;
    }
    .eco-product-card .eco-product-card-promos .eco-badge-promo {
        margin-bottom: 0.375rem;
    }
    .eco-product-card .eco-product-card-sold {
        margin-top: 0.5rem;
    }

    /* Price row (current price, strikethrough compare price, discount badge)
       — generic, not scoped under .eco-product-grid in the original theme
       rule either, so this is used wherever the product-price markup
       renders (card grid, product detail, quick-preview), not just cards.
       Extracted verbatim from themes/default/index.html. */
    .eco-product-prices {
        display: flex;
        align-items: center;
        gap: 0.5rem;
    }
    .eco-product-prices .eco-product-price {
        font-weight: var(--font-weight-medium, 500);
    }
    .eco-product-prices:has(.eco-product-discounted-price) .eco-product-price {
        font-weight: var(--font-weight-medium, 500);
        color: var(--color-danger, #be123c);
    }
    .eco-product-prices .eco-price-from {
        font-weight: var(--font-weight-normal, 400);
        opacity: 0.6;
    }
    .eco-product-prices .eco-product-discounted-price {
        font-weight: var(--font-weight-medium, 500);
        opacity: 0.5;
        text-decoration-line: line-through;
    }
    .eco-product-prices .eco-product-price-badge {
        border: 1px solid var(--color-danger, #be123c);
        background-color: color-mix(in oklab, var(--color-danger, #be123c) 5%, transparent);
        padding-inline: 0.25rem;
        font-size: 0.75rem;
        line-height: calc(1 / 0.75);
        color: var(--color-danger, #be123c);
    }

    /* Promo badge (bundle/combo copy, e.g. "Beli X, Bonus Diskon Y 30%") —
       shown on both the product card and product detail's promo list.
       Extracted verbatim from themes/default/index.html. The
       [data-page="products-show"] font-weight override stays theme-owned
       (page-specific, not structural). */
    .eco-badge-promo,
    .eco-product-badge-promo {
        max-width: 100%;
        min-width: 0;
    }
    .eco-badge-promo-head {
        min-width: 0;
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow: hidden;
    }
    .eco-badge-promo-tail {
        white-space: nowrap;
        flex: none;
    }
    .eco-product .eco-product-badge-promo {
        position: relative;
        display: inline-flex;
        align-items: center;
        gap: 0.375rem;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        padding-inline: 0.625rem;
        padding-block: 0.25rem;
        color: color-mix(in oklab, var(--color-foreground, #17140f) 80%, transparent);
        font-size: 10px;
        text-transform: uppercase;
    }
    .eco-product .eco-product-badge-promo:hover {
        background-color: color-mix(in oklab, var(--color-foreground, #17140f) 5%, transparent);
    }
    .eco-product .eco-product-badge-promo:after {
        content: "";
        display: inline-block;
        width: 0.875rem;
        height: 0.875rem;
        background-color: currentColor;
        -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E") 50% / contain no-repeat;
        mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E") 50% / contain no-repeat;
    }

    /* Product detail gallery: main image + prev/next arrows + dots + the
       vertical thumbnail strip (nav buttons, scroll track, individual
       thumbs). Structural piece of components/product-detail.blade.php
       (the shared gallery markup, also reused by quick-preview). Extracted
       verbatim from themes/default/index.html, selector generalized from
       .eco-product-view .eco-product-view-gallery to plain
       .eco-product-view-gallery for the same reason as the card rules
       above. [data-page="products-show"]'s own gap/thumb-size overrides
       stay theme-owned (page-specific layout, not shared structure). */
    .eco-product-view-gallery {
        display: flex;
        flex-direction: row-reverse;
        flex: none;
        width: 50%;
        gap: 0.5rem;
    }
    .eco-product-view-gallery:hover .eco-product-image .eco-gallery-arrow.eco-gallery-arrow--prev {
        margin-inline-start: 0.5rem;
        inset-inline-start: 0;
    }
    .eco-product-view-gallery:hover .eco-product-image .eco-gallery-arrow.eco-gallery-arrow--next {
        margin-inline-end: 0.5rem;
        inset-inline-end: 0;
    }
    .eco-product-view-gallery > .eco-product-image {
        aspect-ratio: 1;
        flex: 1;
        align-self: flex-start;
    }
    .eco-product-view-gallery > .eco-product-image .eco-gallery-arrow {
        position: absolute;
        inset-block: 0;
        margin-block: auto;
        width: 2.5rem;
        height: 2.5rem;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        background-color: var(--color-background, #fff);
        color: var(--color-foreground, #17140f);
        opacity: 0.7;
        transition: all 0.15s ease;
    }
    .eco-product-view-gallery > .eco-product-image .eco-gallery-arrow:hover {
        opacity: 1;
    }
    .eco-product-view-gallery > .eco-product-image .eco-gallery-arrow.eco-gallery-arrow--prev {
        margin-inline-start: 0.5rem;
        inset-inline-start: -3.5rem;
    }
    .eco-product-view-gallery > .eco-product-image .eco-gallery-arrow.eco-gallery-arrow--next {
        margin-inline-end: 0.5rem;
        inset-inline-end: -3.5rem;
    }
    .eco-product-view-gallery > .eco-product-image .eco-gallery-dots {
        position: absolute;
        bottom: 0;
        z-index: 50;
        margin-bottom: 1.25rem;
        display: flex;
        justify-content: center;
        gap: 0.5rem;
        width: 100%;
    }
    .eco-product-view-gallery > .eco-product-image .eco-gallery-dots .eco-gallery-dot {
        width: 0.75rem;
        height: 0.75rem;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        border-radius: 999px;
        background-color: var(--color-background, #fff);
        opacity: 0.5;
    }
    .eco-product-view-gallery > .eco-product-image .eco-gallery-dots .eco-gallery-dot.eco-gallery-dot--active {
        opacity: 1;
    }
    .eco-product-view-gallery .eco-product-view-thumbs {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 0.5rem;
    }
    .eco-product-view-gallery .eco-product-view-thumbs .eco-thumb-nav {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        opacity: 0.6;
        transition: opacity 0.15s ease;
    }
    .eco-product-view-gallery .eco-product-view-thumbs .eco-thumb-nav .eco-icon {
        width: 1rem;
        height: 1rem;
    }
    .eco-product-view-gallery .eco-product-view-thumbs .eco-thumb-nav:hover {
        opacity: 1;
    }
    .eco-product-view-gallery .eco-product-view-thumbs .eco-product-view-thumbs-list {
        max-height: 19.5rem;
        overflow: hidden;
    }
    .eco-product-view-gallery .eco-product-view-thumbs .eco-product-view-thumbs-track {
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
        transition: transform 0.3s;
    }
    .eco-product-view-gallery .eco-product-view-thumbs .eco-product-view-thumb {
        width: 3.5rem;
        height: 3.5rem;
        border: 2px solid var(--color-background, #fff);
    }

    /* Variant picker chips (size/color etc, data-role="variant-group") —
       generic markup shared by product detail and quick-preview. Extracted
       verbatim from themes/default/index.html. */
    .eco-variant-group {
        display: flex;
        flex-direction: column;
        gap: 0.25rem;
    }
    .eco-variant-group .eco-variant-label span:first-child {
        font-weight: var(--font-weight-medium, 500);
    }
    .eco-variant-group .eco-variant-label .eco-variant-selected {
        opacity: 0.7;
    }
    .eco-variant-group .eco-variant-chips {
        display: flex;
        flex-wrap: wrap;
        gap: 0.375rem;
    }
    .eco-variant-group .eco-variant-chips .eco-chip {
        font-weight: var(--font-weight-normal, 400);
    }
    .eco-variant-group .eco-variant-chips .eco-chip.eco-chip--active {
        background-color: var(--color-foreground, #17140f);
        color: var(--color-background, #fff);
        font-weight: var(--font-weight-medium, 500);
    }
    .eco-product-view-variants,
    .eco-cart-sheet-variants {
        display: flex;
        flex-direction: column;
        gap: 1rem;
    }

    /* Quantity input row (product detail's "Jumlah" — the same .eco-cart-
       item-qty markup as the cart sheet, which already owns its base look
       via chrome/cart-sheet.css; this only adds the product-view-specific
       spacing/sizing wrapper). Extracted verbatim from themes/default/
       index.html. */
    .eco-product-view-qty {
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
        margin-top: 1.25rem;
    }
    .eco-product-view-qty > span:first-child {
        font-weight: var(--font-weight-medium, 500);
    }
    .eco-product-view-qty .eco-cart-item-qty {
        gap: 0.75rem;
    }
    .eco-product-view-qty .eco-cart-item-qty .eco-qty-input {
        width: 5rem;
    }
    .eco-product-view-qty .eco-cart-item-qty .eco-qty-max {
        opacity: 0.7;
    }

    /* Add-to-cart / buy-now buttons + wishlist toggle (data-role="detail-
       actions"). Extracted verbatim from themes/default/index.html.
       .eco-product-view-actions is the buy-button pair only; the wishlist
       heart is its SIBLING inside the role wrapper, not a child (see
       product-detail.blade.php's own note — several themes' alignment math
       depends on that). */
    .eco-product-view-actions {
        display: flex;
        gap: 0.5rem;
        width: 100%;
        margin-top: 1.25rem;
    }
    .eco-product-view-actions .btn {
        width: 100%;
    }

    /* Wishlist heart. Two placement patterns exist across the themes:
         floating  heart sits over the gallery, outside the button row
                   (default, aurum, editorial, hydro, lumen, magazine,
                   mosaic — 7 themes, byte-identical CSS between them)
         inline    heart is the third control in the button row
                   (bloom, onyx)
       Floating is the baseline here since it's what most themes use. A
       theme switches to inline via markup, not CSS: add
       data-wishlist-style="inline" to its data-role="detail-actions"
       placeholder. ThemeRenderer::injectRoles() carries the attribute onto
       the rendered role content's root tag — same mechanism
       data-field-style="floating" uses for the login/register/forgot forms
       (see THEME-ARCHITECTURE.md's floating-label section for the full
       rationale on markup-vs-CSS for this kind of switch), and the same
       one data-quick-preview="off" on product-grid sections originated. */
    .eco-wishlist-btn {
        width: 2.5rem;
        height: 2.5rem;
        padding: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        background-color: var(--color-background, #fff);
        color: var(--color-danger, #be123c);
        opacity: 0.7;
        transition: all 0.15s ease;
        position: absolute;
        inset-inline-start: 50%;
        top: 0;
        margin-inline-start: -3rem;
        margin-top: 0.5rem;
    }
    [data-wishlist-style="inline"] .eco-wishlist-btn {
        position: static;
        inset-inline-start: auto;
        top: auto;
        margin-inline-start: 0;
        margin-top: 0;
    }
    /* The role wrapper holds the buy-button pair and the heart as two
       siblings. Laid out as an explicit grid — two equal buy buttons plus
       an auto-width third track — with display:contents lifting the two
       .btn children up so all three controls share one track list.
       Nested flex does NOT work here: .eco-product-view-actions above is
       width:100%, which fights every flex-shrink attempt and lets the
       heart overlap the buy button.
       Scoped to [data-wishlist-style="inline"]: for the floating themes an
       absolutely positioned heart is out of flow and never claims the
       third track anyway, but leaving the grid unscoped would still lay
       out .eco-product-view-actions's two buttons across 2 of 3 columns
       instead of the plain 2-column split those themes' own CSS expects. */
    [data-wishlist-style="inline"][data-role="detail-actions"] {
        display: grid;
        grid-template-columns: 1fr 1fr auto;
        align-items: center;
        gap: 0.75rem;
    }
    [data-wishlist-style="inline"][data-role="detail-actions"] .eco-product-view-actions {
        display: contents;
    }
    /* The heart shares the row with the two buy buttons (~38px tall, from
       .btn's padding+font), so it takes its height from them instead of a
       theme's own fixed size. height:100% (not aspect-ratio +
       align-self:stretch) on purpose — the grid column above is
       auto-sized, and an aspect-ratio'd, stretched item feeds back into
       that auto column: a taller row grows the (square) heart wider, a
       wider heart grows its own height back, round and round. Caught live
       on bloom: it settled at 66px instead of matching the ~38px buttons
       (onyx happened to settle correctly with the circular version, but
       the loop is the same one — not something to rely on staying
       stable). The theme's own fixed width (its unlayered .eco-wishlist-btn
       rule) stays untouched, so the auto column has a stable number to
       size around and height is the only axis actually borrowed from the
       row. */
    [data-wishlist-style="inline"][data-role="detail-actions"] .eco-wishlist-btn {
        margin-top: 0;
        height: 100%;
    }
    .eco-wishlist-btn:hover {
        opacity: 1;
    }
    .eco-wishlist-btn.eco-wishlisted svg {
        fill: var(--color-danger, #be123c);
    }

    /* Product spec table (Informasi Produk: SKU/Kategori/Status/Berat) +
       description with expand/collapse. Extracted verbatim from themes/
       default/index.html, selector generalized from [data-page="products-
       show"] .eco-product-detail .eco-product-details to plain
       .eco-product-details — this markup only renders on the product
       detail page today, but isn't itself page-specific structure. */
    .eco-product-details .eco-specs-heading {
        margin-bottom: 0.25rem;
        font-weight: var(--font-weight-medium, 500);
    }
    .eco-product-details .eco-specs-list {
        display: flex;
        flex-direction: column;
        margin-bottom: 2rem;
    }
    .eco-product-details .eco-specs-list .eco-specs-row {
        display: flex;
        gap: 1rem;
        border-bottom: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 10%, transparent);
        padding-block: 0.5rem;
    }
    .eco-product-details .eco-specs-list .eco-specs-row .eco-specs-key {
        flex: none;
        width: 8rem;
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
        opacity: 0.6;
    }
    .eco-product-details .eco-specs-list .eco-specs-row .eco-specs-value {
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
    }
    .eco-product-details .eco-product-description {
        font-size: var(--text-sm, 0.875rem);
        line-height: var(--leading-relaxed, 1.625);
        opacity: 0.8;
        max-height: var(--eco-desc-full-height, none);
        overflow: hidden;
        transition: max-height 0.3s;
    }
    .eco-product-details .eco-product-description.eco-description-clamped {
        max-height: 12rem;
        -webkit-mask-image: linear-gradient(#000 70%, transparent 100%);
        mask-image: linear-gradient(#000 70%, transparent 100%);
    }
    .eco-product-details .eco-description-toggle {
        margin-top: 0.5rem;
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
        font-weight: var(--font-weight-medium, 500);
        text-underline-offset: 2px;
        opacity: 0.8;
        text-decoration-line: underline;
    }
    .eco-product-details .eco-description-toggle:hover {
        opacity: 1;
    }

    /* Global: status badge + order metadata (used by account order list/detail
       AND checkout success). Semantic colour only — danger/pending/success are
       already per-theme tokens, no new decision needed here. */
    .badge {
        display: inline-flex;
        align-items: center;
        padding-inline: calc(var(--spacing) * 2);
        padding-block: calc(var(--spacing) * .5);
        font-size: var(--text-xs);
        line-height: var(--text-xs--line-height);
        white-space: nowrap;
        &.badge-soft-red {
            background-color: color-mix(in oklab, var(--color-danger) 10%, transparent);
            border: 1px solid color-mix(in oklab, var(--color-danger) 30%, transparent);
            color: var(--color-danger);
        }
        &.badge-soft-amber {
            background-color: color-mix(in oklab, var(--color-pending) 10%, transparent);
            border: 1px solid color-mix(in oklab, var(--color-pending) 30%, transparent);
            color: var(--color-pending);
        }
        &.badge-soft-green {
            background-color: color-mix(in oklab, var(--color-success) 10%, transparent);
            border: 1px solid color-mix(in oklab, var(--color-success) 30%, transparent);
            color: var(--color-success);
        }
    }
    .eco-order-card-num,
    .eco-order-title {
        font-weight: var(--font-weight-medium);
    }
    .eco-order-card-date,
    .eco-order-date {
        font-size: var(--text-xs);
        line-height: var(--text-xs--line-height);
        opacity: .6;
    }

    /* Default size for every inline svg — size-4 stroke-1.2. Plain element
       selector (not .eco-icon) so it also covers raw <svg> markup written
       directly in a theme's HTML without that class (e.g. bottom-nav/hero
       icons) — any more specific selector (a class, an ancestor-scoped
       rule) still wins on top of this by normal cascade. */
    svg {
        width: 1rem;
        height: 1rem;
        stroke-width: 1.2px;
    }

    /* Breadcrumb (Home > Category > Product) — same responsive max-width
       ladder as .container/.eco-product-grid, plus its own margin/padding.
       Extracted verbatim from themes/default/index.html. The mobile
       padding-inline rule stays theme-owned (default.css's shared
       @media(max-width:767px) block also covers .container and several
       other selectors — splitting .eco-breadcrumb out of that group rule
       isn't worth the duplication for one declaration). */
    .eco-breadcrumb {
        width: 100%;
        margin-inline: auto;
        margin-top: 0.75rem;
        margin-bottom: 0.5rem;
        padding-block: 0.75rem;
        display: flex;
        align-items: center;
    }
    @media (min-width: 40rem) {
        .eco-breadcrumb { max-width: 40rem; }
    }
    @media (min-width: 48rem) {
        .eco-breadcrumb { max-width: 48rem; }
    }
    @media (min-width: 64rem) {
        .eco-breadcrumb { max-width: 64rem; }
    }
    @media (min-width: 80rem) {
        .eco-breadcrumb { max-width: 80rem; }
    }
    @media (min-width: 96rem) {
        .eco-breadcrumb { max-width: 96rem; }
    }
    .eco-breadcrumb .eco-breadcrumb-current {
        font-weight: var(--font-weight-medium, 500);
    }

    /* Empty state (no products / no results) — same responsive max-width
       ladder as .container/.eco-breadcrumb. Extracted verbatim from
       themes/default/index.html. */
    .eco-empty {
        width: 100%;
        margin-inline: auto;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 0.25rem;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        padding-block: 10rem;
    }
    @media (min-width: 40rem) {
        .eco-empty { max-width: 40rem; }
    }
    @media (min-width: 48rem) {
        .eco-empty { max-width: 48rem; }
    }
    @media (min-width: 64rem) {
        .eco-empty { max-width: 64rem; }
    }
    @media (min-width: 80rem) {
        .eco-empty { max-width: 80rem; }
    }
    @media (min-width: 96rem) {
        .eco-empty { max-width: 96rem; }
    }
    .eco-empty .eco-empty-title {
        font-size: var(--text-lg, 1.125rem);
        line-height: var(--text-lg--line-height, 1.5556);
        font-weight: var(--font-weight-medium, 500);
    }
    .eco-empty .eco-empty-text {
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
    }
    .eco-empty .btn {
        margin-top: 1.25rem;
        display: block;
    }

    /* .container utility — same responsive max-width ladder as .eco-empty/
       .eco-breadcrumb/.eco-product-grid. HIGH RISK selector (see docs/
       THEME-ARCHITECTURE.md / project memory): a past production incident
       happened from a wrong reimplementation of this exact class, so the
       breakpoints here are copied verbatim, not re-derived. */
    .container {
        width: 100%;
    }
    @media (min-width: 40rem) {
        .container { max-width: 40rem; }
    }
    @media (min-width: 48rem) {
        .container { max-width: 48rem; }
    }
    @media (min-width: 64rem) {
        .container { max-width: 64rem; }
    }
    @media (min-width: 80rem) {
        .container { max-width: 80rem; }
    }
    @media (min-width: 96rem) {
        .container { max-width: 96rem; }
    }

    /* Pagination controls (prev/next + page numbers). Extracted verbatim
       from themes/default/index.html. Contextual spacing (e.g. .eco-plp-
       body's margin-top on this) stays theme-owned. */
    .eco-pagination {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 0.5rem;
    }

    /* Form scaffolding: field wrapper/label, select, password-toggle input
       wrap, strength meter, checkbox, row/help/group/error/placeholder.
       Extracted verbatim from themes/default/index.html — see file header
       for why this (including the strength meter) is here now. */
    .eco-field {
        display: flex;
        flex-direction: column;
        gap: 0.375rem;
        margin-bottom: 0.75rem;
    }
    .eco-field-label {
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
    }

    /* ── floating-label variant (signature.form = "floating-label") ──
       Opt-in via markup, not CSS: a theme adds data-field-style="floating"
       to its data-role="login-form" / "register-form" / "address-form" /
       "forgot-password-form" placeholder (ThemeRenderer::injectRoles()
       carries the attribute onto the rendered Blade form's root tag — see
       the comment there). Used by aurum and bloom.

       Why markup instead of a CSS custom-property flag on .eco-field (what
       --eco-wishlist-inline does): that trick only cancels a property back
       to its initial value, and here nearly every property below needs its
       OWN non-initial value (static→absolute position, flex→block display,
       auto→50% top, …) — a single var() flag can't carry that many distinct
       values at once without one custom property per declaration, which
       just relocates the same complexity into the theme file instead of
       removing it. A form-level attribute lets one declaration in the
       theme's markup switch the whole block below in one go.

       Mechanics only — border, radius, colors, font-size, spacing stay
       theme-owned via the theme's own unlayered .eco-field-input rule,
       which still wins on top of everything here by normal layer order. */
    [data-field-style="floating"] .eco-field {
        display: block;
        position: relative;
    }
    [data-field-style="floating"] .eco-field-label {
        position: absolute;
        top: 50%;
        inset-inline-start: 1rem;
        translate: 0 -50%;
        padding-inline: 0.25rem;
        background-color: var(--color-background, #fff);
        font-size: var(--text-sm, 0.875rem);
        line-height: var(--text-sm--line-height, 1.4286);
        opacity: 0.5;
        pointer-events: none;
        transition: top 0.15s ease, font-size 0.15s ease, opacity 0.15s ease;
        /* Password fields wrap their input in .eco-form-input-wrap (for the
           show/hide toggle), and that wrapper is position:relative for its
           own absolutely-positioned button — which lifts it to the same
           positioned stacking tier as this label. Being later in DOM order
           it would paint its opaque input background over the label, so the
           label needs an explicit z-index to win. Plain .eco-field-input
           siblings stay position:static and never hit this. */
        z-index: 1;
    }
    /* field-input.blade.php always renders a real placeholder string (e.g.
       "nama@email.com"); the stacked default shows it as hint text, but
       here the resting label already occupies that spot and the two
       collide. The placeholder stays in the DOM so :placeholder-shown keeps
       working as the float trigger below — only its paint is suppressed. */
    [data-field-style="floating"] .eco-field-input::placeholder {
        color: transparent;
    }
    /* Float triggers. field-input.blade.php renders <label> BEFORE <input>,
       so :has() reaches backward to style the label from the input's state.
       Two conditions: :focus (always safe) and :not(:placeholder-shown),
       which stays true after blur once the field has content. */
    [data-field-style="floating"] .eco-field:has(.eco-field-input:focus) .eco-field-label,
    [data-field-style="floating"] .eco-field:has(.eco-field-input:not(:placeholder-shown)) .eco-field-label {
        top: 0;
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
        opacity: 0.7;
    }
    /* <select> has no :placeholder-shown equivalent, so it can never report
       "empty" — a floating label over one would be stuck covering the
       chosen option. Those fields keep the plain stacked label. */
    [data-field-style="floating"] .eco-field:has(.eco-field-select) .eco-field-label {
        position: static;
        translate: none;
        display: block;
        margin-bottom: 0.375rem;
        padding-inline: 0;
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
        opacity: 0.7;
    }

    .eco-field-select {
        width: 100%;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        padding-inline: 1rem;
        padding-block: 0.5rem;
    }
    .eco-form-input-wrap {
        position: relative;
    }
    .eco-form-input-wrap .eco-form-toggle-pw {
        position: absolute;
        inset-block: 0;
        inset-inline-end: 0.375rem;
        margin-block: auto;
        width: 1.75rem;
        height: 1.75rem;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .eco-form-input-wrap .eco-form-toggle-pw:hover {
        background-color: color-mix(in oklab, var(--color-foreground, #17140f) 5%, transparent);
    }
    .eco-form-strength {
        display: flex;
        align-items: center;
        gap: 0.75rem;
        margin-top: 0.25rem;
    }
    .eco-form-strength-bars {
        display: flex;
        flex: 1;
        gap: 0.25rem;
    }
    .eco-form-strength-bar {
        flex: 1;
        height: 3px;
        background-color: color-mix(in oklab, var(--color-foreground, #17140f) 15%, transparent);
        transition: background-color 0.2s;
    }
    .eco-form-strength-1 {
        background-color: var(--color-danger, #be123c);
    }
    .eco-form-strength-2 {
        background-color: var(--color-pending, #b45309);
    }
    .eco-form-strength-3 {
        background-color: color-mix(in oklab, var(--color-success, #15803d) 60%, transparent);
    }
    .eco-form-strength-4 {
        background-color: var(--color-success, #15803d);
    }
    .eco-form-strength-label {
        flex: none;
        min-width: 3.5rem;
        text-align: end;
        font-size: 0.68rem;
        font-weight: var(--font-weight-semibold, 600);
        letter-spacing: 0.14em;
        text-transform: uppercase;
        opacity: 0.6;
    }
    .eco-form-strength:has(.eco-form-strength-1) .eco-form-strength-label {
        color: var(--color-danger, #be123c);
        opacity: 0.9;
    }
    .eco-form-strength:has(.eco-form-strength-2) .eco-form-strength-label {
        color: var(--color-pending, #b45309);
        opacity: 0.9;
    }
    .eco-form-strength:has(.eco-form-strength-3) .eco-form-strength-label,
    .eco-form-strength:has(.eco-form-strength-4) .eco-form-strength-label {
        color: var(--color-success, #15803d);
        opacity: 0.9;
    }
    .eco-checkbox {
        display: flex;
        align-items: center;
        gap: 0.5rem;
    }
    .eco-checkbox input {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
    .eco-checkbox:has(input:checked) .eco-checkbox-icon {
        opacity: 1;
    }
    .eco-checkbox .eco-checkbox-box {
        flex: none;
        width: 1rem;
        height: 1rem;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
    }
    .eco-checkbox .eco-checkbox-box .eco-checkbox-icon {
        width: 1rem;
        height: 1rem;
        stroke-width: 2.5px;
        opacity: 0;
        transition: all 0.15s ease;
    }
    .eco-form-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
    .eco-form-help {
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
        opacity: 0.7;
    }
    .eco-checkbox-label {
        font-size: var(--text-sm, 0.875rem);
        line-height: var(--text-sm--line-height, 1.4286);
    }
    .eco-form-group {
        display: flex;
        flex-direction: column;
        gap: 0.375rem;
        margin-bottom: 0.75rem;
    }
    .eco-form-label {
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
        opacity: 0.7;
    }
    .eco-form-label .req {
        color: color-mix(in oklab, var(--color-danger, #be123c) 70%, transparent);
    }
    .eco-form-error,
    .eco-field-error {
        font-size: var(--text-xs, 0.75rem);
        line-height: var(--text-xs--line-height, 1.3333);
        color: var(--color-danger, #be123c);
    }
    .eco-form-placeholder {
        opacity: 0.4;
    }

    /* Mobile bottom nav (< 768px) — fixed 5-column tab bar + the .eco-content
       padding-bottom that keeps page content from being covered by it.
       Extracted verbatim from themes/default/index.html. Hidden by default,
       shown only under the same max-width:767px media query the original
       rule used. */
    .eco-bottomnav {
        display: none;
    }
    @media (max-width: 767px) {
        .eco-bottomnav {
            position: fixed;
            inset-inline: 0;
            bottom: 0;
            z-index: 50;
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            align-items: stretch;
            border-top: 1px solid var(--color-line, #e4e1d9);
            background-color: var(--color-background, #fff);
            padding-bottom: env(safe-area-inset-bottom, 0px);
        }
        .eco-bottomnav-item {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 0.25rem;
            padding-block: 0.5rem;
            color: var(--color-foreground, #17140f);
            opacity: 0.55;
            text-decoration-line: none;
            transition: opacity 0.15s;
        }
        .eco-bottomnav-item.is-active {
            opacity: 1;
        }
        .eco-bottomnav-item:active {
            opacity: 0.4;
        }
        .eco-bottomnav-icon {
            position: relative;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .eco-bottomnav-icon svg {
            width: 1.5rem;
            height: 1.5rem;
        }
        .eco-bottomnav-label {
            font-size: 0.6rem;
            line-height: 1;
            font-weight: var(--font-weight-medium, 500);
            text-transform: uppercase;
            letter-spacing: 0.06em;
        }
        .eco-bottomnav-badge {
            position: absolute;
            top: -0.35rem;
            inset-inline-start: 60%;
            display: flex;
            align-items: center;
            justify-content: center;
            min-width: 1rem;
            height: 1rem;
            padding-inline: 0.2rem;
            border-radius: 999px;
            background-color: var(--color-foreground, #17140f);
            color: var(--color-background, #fff);
            font-size: 0.6rem;
            line-height: 1;
            font-weight: var(--font-weight-semibold, 600);
        }
        .eco-content {
            padding-bottom: calc(3.75rem + env(safe-area-inset-bottom, 0px));
        }
    }

    /* Leftover plain Tailwind utility classes used ad-hoc in a few spots
       (not part of the eco-* design system) — extracted verbatim from
       themes/default/index.html. Verified safe to put in chrome: the
       marketing/system pages (tokoten.com/harga, /tentang, …) that also
       happen to use classes with these same names (.flex, .text-xs, …)
       load resources/css/system.css via Vite and never load theme-css at
       all — confirmed via curl, no <link> to /theme-css/ in their response.
       So there's no cross-contamination between the two. */
    .mx-auto { margin-inline: auto; }
    .my-4 { margin-block: 1rem; }
    .mt-4 { margin-top: 1rem; }
    .mt-10 { margin-top: 2.5rem; }
    .mt-16 { margin-top: 4rem; }
    .mb-5 { margin-bottom: 1.25rem; }
    .flex { display: flex; }
    .h-full { height: 100%; }
    .h-px { height: 1px; }
    .w-full { width: 100%; }
    .flex-1 { flex: 1; }
    .items-center { align-items: center; }
    .gap-3 { gap: 0.75rem; }
    .self-start { align-self: flex-start; }
    .bg-black { background-color: var(--color-black, #000); }
    .bg-foreground\/15 { background-color: color-mix(in oklab, var(--color-foreground, #17140f) 15%, transparent); }
    .px-4 { padding-inline: 1rem; }
    .py-1 { padding-block: 0.25rem; }
    .py-12 { padding-block: 3rem; }
    .pt-16 { padding-top: 4rem; }
    .pb-10 { padding-bottom: 2.5rem; }
    .text-center { text-align: center; }
    .\!text-base { font-size: var(--text-base, 1rem) !important; line-height: var(--text-base--line-height, 1.5) !important; }
    .text-xs { font-size: var(--text-xs, 0.75rem); line-height: var(--text-xs--line-height, 1.3333); }
    .text-\[0\.68rem\] { font-size: 0.68rem; }
    .text-background { color: var(--color-background, #fff); }
    .text-gray-400 { color: var(--color-gray-400, oklch(70.7% 0.022 261.325)); }
    .uppercase { text-transform: uppercase; }
    .not-italic { font-style: normal; }
    .opacity-50 { opacity: 0.5; }
    .opacity-80 { opacity: 0.8; }

    /* Submit-button loading spinner (<x-field-button>'s data-role="form-
       submit-spinner"), swapped in for the label by the parent form's JS
       while a request is in flight — used by login/register/checkout/
       address forms, anywhere <x-field-button> renders. Extracted verbatim
       from themes/default/index.html. */
    .eco-btn-spinner {
        display: inline-block;
        width: 1rem;
        height: 1rem;
        border: 2px solid;
        border-color: transparent currentColor currentColor;
        border-radius: 999px;
        animation: eco-spin 0.65s linear infinite;
    }
    @keyframes eco-spin {
        to { transform: rotate(360deg); }
    }

    /* Radio button (filter.blade.php's sort options: "Harga rendah →
       tinggi", "Terbaru", dst — data-role="filter-radio"). Native <input
       type="radio"> visually hidden, state shown via the sibling
       .eco-radio-dot's :checked-driven fill. No verbatim source to extract
       from — default.css never styled this before — so this mirrors the
       existing .eco-shipping-radio-dot look (chrome/overlays.css) for
       visual consistency with the rest of the design system's radio
       pattern (16px ring, 8px filled center dot). */
    .eco-radio {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        cursor: pointer;
    }
    .eco-radio-input {
        display: none;
    }
    .eco-radio-dot {
        position: relative;
        flex: none;
        width: 1rem;
        height: 1rem;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid color-mix(in oklab, var(--color-foreground, #17140f) 30%, transparent);
        border-radius: 999px;
    }
    .eco-radio-dot::after {
        content: "";
        width: 0.5rem;
        height: 0.5rem;
        border-radius: 999px;
        background-color: var(--color-foreground, #17140f);
        opacity: 0;
    }
    .eco-radio-input:checked ~ .eco-radio-dot {
        border-color: var(--color-foreground, #17140f);
    }
    .eco-radio-input:checked ~ .eco-radio-dot::after {
        opacity: 1;
    }
    .eco-radio-label {
        font-size: var(--text-sm, 0.875rem);
        line-height: var(--text-sm--line-height, 1.4286);
    }

    /* Footer selalu full-bleed, lepas dari batasan container induknya.

       Beberapa block data-page BUKAN cuma wadah polos — ada yang dikasih
       perilaku .container sendiri (width:100% + max-width ladder +
       margin-inline:auto): cart-index (chrome/cart.css), wishlist/coupons/
       account-* (chrome/account.css), checkout-* dst. Footer hidup SEBAGAI
       ANAK block itu (placeholder <div data-section="footer"> ada di dalam
       tiap data-page di index.html tiap tema), jadi tanpa ini footer ikut
       menyempit dan nyisa gap putih kiri-kanan — persis yang kejadian di
       /cart dan /wishlist.

       Tiga properti, masing-masing menangani satu hal:

       - width/max-width 100vw: melepas footer dari max-width induk.
       - margin-left: calc(50% - 50vw): membatalkan pemusatan induk. 50% di
         sini relatif ke lebar induk, jadi nilainya PERSIS minus offset kiri
         induk saat induknya di-center dengan margin-inline:auto — yang
         dipakai hampir semua block data-page (chrome/cart.css,
         account.css, checkout*.css, auth.css). Kalau induknya sudah selebar
         viewport (home/faq/login), nilainya 0 dan tidak berefek.
       - grid-column 1/-1: hanya berarti kalau induknya `display:grid`
         (halaman akun/wishlist/coupons punya sidebar 14rem + konten 1fr).
         Tanpa ini footer cuma menempati kolom konten di samping sidebar;
         di induk non-grid properti ini diabaikan browser.

       PENTING: margin-LEFT saja, JANGAN margin-inline. Versi pertama rule
       ini pakai `margin-inline: calc(50% - 50vw)` (teknik full-bleed yang
       lazim di artikel) dan itu menggeser DUA KALI offset yang seharusnya —
       footer malah melesat ke kiri (terukur x=-528 di halaman akun setelah
       login, viewport 1440). Gejalanya cuma muncul SETELAH LOGIN karena
       saat guest grid-nya 1 kolom (.is-guest) dan kebetulan hasilnya masih
       terlihat wajar — itulah kenapa lolos dari pengujian awal yang
       semuanya dilakukan tanpa login. Lihat e2e/_support/lint-theme-chrome.mjs
       yang sekarang menjaga rule ini supaya tidak berubah jadi margin-inline
       lagi. */
    [data-section="footer"] {
        grid-column: 1 / -1;
        width: 100vw;
        max-width: 100vw;
        margin-left: calc(50% - 50vw);
        margin-right: 0;
    }

    /* Blok data-page yang punya margin-bottom sendiri (mis. halaman akun:
       `margin-bottom: calc(var(--spacing) * 20)` = 80px di chrome/account.css)
       memasang jarak itu SEBELUM footer ikut pindah ke dalam blok. Sekarang
       footer ada di dalamnya, jadi margin itu jatuh DI BAWAH footer dan jadi
       pita putih di ujung halaman — terlihat jelas di canvas editor tema,
       yang tingginya mengikuti scrollHeight dokumen.

       Dinolkan HANYA untuk blok yang memang berisi footer (:has), supaya blok
       data-page lain yang tidak punya footer tetap memakai jaraknya seperti
       semula. Jarak konten-ke-footer tidak hilang: footer sudah membawa
       margin-top-nya sendiri dari markup tema (mt-16), terukur tetap 64px
       sebelum dan sesudah perubahan ini. */
    [data-page]:has(> [data-section="footer"]) {
        margin-bottom: 0;
    }
}
