/* KahunaCart — self-contained, theme-agnostic storefront styles.

   Token resolution: --kahunacart-* (site override) → --grav-* (theme design
   system, e.g. event-horizon) → --theme-accent (generic hook any theme or
   plugin can publish) → neutral fallback. On a token-publishing theme the
   storefront picks up the full design language (elevated cards, the theme's
   own accent, its radii); anywhere else it stays a clean neutral that follows
   the page's own palette. Never couple to a specific theme — extend, don't
   stomp.

   The neutral layer (borders, muted text, faint fills) is derived from a
   single --kc-fg anchor rather than repeating CanvasText/Canvas literally at
   every rule, so a theme or site can retarget the whole neutral ramp with one
   --kahunacart-fg. The anchor stays an *absolute* colour on purpose:
   currentColor would track the page's real text colour, but it re-resolves at
   every descendant, so muted-inside-muted would compound (0.68 × 0.68 ≈ 35%
   alpha) and a download's expiry line would fade away inside an already-muted
   row. See the colour-scheme sync below for how the system-colour fallback is
   kept pointing at the mode the page renders.

   Component rules are wrapped in :where() wherever the outer selector is ours
   and not load-bearing, so a theme's own rule at plain class weight wins
   without anyone reaching for !important. */

/* Also on :root: a theme may render the storefront inside a wrapper that sits
   outside .kahunacart (a sticky cart summary, a mini-cart in the header), and
   those still need the palette. */
:root {
    --kc-fg: var(--kahunacart-fg, var(--grav-fg, CanvasText));
    --kc-bg: var(--kahunacart-bg, var(--grav-bg, Canvas));
    --kc-border: var(--kahunacart-border, var(--grav-border, color-mix(in srgb, var(--kc-fg) 13%, transparent)));
    --kc-border-strong: var(--kahunacart-border-strong, var(--grav-border-strong, color-mix(in srgb, var(--kc-fg) 24%, transparent)));
    --kc-muted: var(--kahunacart-muted, var(--grav-fg-muted, color-mix(in srgb, var(--kc-fg) 68%, transparent)));
    --kc-subtle: var(--kahunacart-subtle, var(--grav-fg-subtle, color-mix(in srgb, var(--kc-fg) 60%, transparent)));
    --kc-accent: var(--kahunacart-accent, var(--grav-purple-bright, var(--theme-accent, light-dark(#0b57d0, #7cacf8))));
    --kc-accent-strong: var(--kahunacart-accent-strong, var(--grav-purple, var(--theme-accent, light-dark(#0b57d0, #2563eb))));
    /* The accent used as *text* on the page background — a shade deeper than
       --kc-accent where the theme offers one, but never --kc-accent-strong's
       fallback, which is tuned to carry white and reads too dark for a price. */
    --kc-accent-ink: var(--kahunacart-accent-ink, var(--grav-purple, var(--kc-accent)));
    --kc-surface: var(--kahunacart-surface, var(--grav-bg-elevated, color-mix(in srgb, var(--kc-fg) 3.5%, transparent)));
    --kc-surface-2: var(--kahunacart-surface-2, var(--grav-bg-subtle, var(--kc-surface)));
    /* Cards default to transparent rather than the faint fill: a product grid
       is a lot of rectangles at once, and on a theme with a patterned or
       gradient page background a stack of near-invisible grey panels reads as
       dirt. The border does the separating; a theme that wants filled cards
       sets --kahunacart-card. */
    --kc-card: var(--kahunacart-card, var(--grav-bg-elevated, transparent));
    --kc-input: var(--kahunacart-input, var(--grav-bg-subtle, transparent));
    --kc-radius: var(--kahunacart-radius, var(--grav-radius-md, 8px));
    --kc-radius-lg: var(--kahunacart-radius-lg, var(--grav-radius-lg, 12px));
    --kc-mono: var(--kahunacart-font-mono, var(--grav-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace));
    --kc-ease: var(--kahunacart-ease, var(--grav-ease, ease));
    --kc-dur: var(--kahunacart-duration, var(--grav-duration-2, 160ms));
    --kc-success: var(--kahunacart-success, var(--grav-success, #1d9d61));
    --kc-warning: var(--kahunacart-warning, var(--grav-warning, #b8860b));
    --kc-danger: var(--kahunacart-danger, var(--grav-danger, #c0392b));
}

/* ── on-accent pairing ──────────────────────────────────────────────────────
   The two accents are split by job: --kc-accent is ink and borders (prices,
   link hover, the outline of a chosen shipping row), --kc-accent-strong is
   the only one that ever fills a box behind a label — the active category
   chip, the checkout and place-order buttons. Only the filling one needs a
   paired label colour, and the failure mode there is a pale accent still
   being handed white text.

   So pick the label colour from how light the fill actually resolves rather
   than assuming white. The channel maths is BT.709 luma against a mid
   threshold; multiplying by 1000 drives the result past the channel range and
   the clamp lands it on a flat 0 or 255 — black or white, never a grey in
   between. Sites can pin the token outright. Browsers without relative colour
   syntax keep plain white. */
:root {
    --kc-on-accent-strong: var(--kahunacart-on-accent-strong, #fff);
}
@supports (color: rgb(from red r g b)) {
    :root {
        --kc-on-accent-strong: var(--kahunacart-on-accent-strong, rgb(from var(--kc-accent-strong)
            clamp(0, (128 - (0.2126 * r + 0.7152 * g + 0.0722 * b)) * 1000, 255)
            clamp(0, (128 - (0.2126 * r + 0.7152 * g + 0.0722 * b)) * 1000, 255)
            clamp(0, (128 - (0.2126 * r + 0.7152 * g + 0.0722 * b)) * 1000, 255)));
    }
}

/* ── colour-scheme sync ─────────────────────────────────────────────────────
   `Canvas`/`CanvasText` — like `light-dark()` — follow the used
   `color-scheme`, which plenty of themes never update when they flip dark
   mode, leaving a light-mode neutral ramp on a dark page. These rules
   translate the common dark-mode conventions into the one property that
   matters, scoped to storefront elements only, so the host page's own
   controls are left alone. Themes publishing --grav-* or --kahunacart-* never
   reach a system colour at all. */
:where(html[data-theme="dark"], html[data-bs-theme="dark"], html.dark, body[data-theme="dark"], body.dark) .kahunacart {
    color-scheme: dark;
}
:where(html[data-theme="light"], html[data-bs-theme="light"], html.light, body[data-theme="light"], body.light) .kahunacart {
    color-scheme: light;
}

/* ── page wrapper ───────────────────────────────────────────────────────────
   The tokens are restated on the wrapper as well as :root so a theme can
   retarget the storefront alone — `.kahunacart { --kahunacart-accent: … }` in
   theme CSS is the obvious thing to write, and it only works if our own
   resolution happens at this level too. */
.kahunacart {
    --kc-fg: var(--kahunacart-fg, var(--grav-fg, CanvasText));
    --kc-bg: var(--kahunacart-bg, var(--grav-bg, Canvas));
    --kc-border: var(--kahunacart-border, var(--grav-border, color-mix(in srgb, var(--kc-fg) 13%, transparent)));
    --kc-border-strong: var(--kahunacart-border-strong, var(--grav-border-strong, color-mix(in srgb, var(--kc-fg) 24%, transparent)));
    --kc-muted: var(--kahunacart-muted, var(--grav-fg-muted, color-mix(in srgb, var(--kc-fg) 68%, transparent)));
    --kc-subtle: var(--kahunacart-subtle, var(--grav-fg-subtle, color-mix(in srgb, var(--kc-fg) 60%, transparent)));
    --kc-accent: var(--kahunacart-accent, var(--grav-purple-bright, var(--theme-accent, light-dark(#0b57d0, #7cacf8))));
    --kc-accent-strong: var(--kahunacart-accent-strong, var(--grav-purple, var(--theme-accent, light-dark(#0b57d0, #2563eb))));
    --kc-accent-ink: var(--kahunacart-accent-ink, var(--grav-purple, var(--kc-accent)));
    --kc-surface: var(--kahunacart-surface, var(--grav-bg-elevated, color-mix(in srgb, var(--kc-fg) 3.5%, transparent)));
    --kc-surface-2: var(--kahunacart-surface-2, var(--grav-bg-subtle, var(--kc-surface)));
    --kc-card: var(--kahunacart-card, var(--grav-bg-elevated, transparent));
    --kc-input: var(--kahunacart-input, var(--grav-bg-subtle, transparent));
    --kc-radius: var(--kahunacart-radius, var(--grav-radius-md, 8px));
    --kc-radius-lg: var(--kahunacart-radius-lg, var(--grav-radius-lg, 12px));
    --kc-mono: var(--kahunacart-font-mono, var(--grav-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace));
    --kc-ease: var(--kahunacart-ease, var(--grav-ease, ease));
    --kc-dur: var(--kahunacart-duration, var(--grav-duration-2, 160ms));
    --kc-success: var(--kahunacart-success, var(--grav-success, #1d9d61));
    --kc-warning: var(--kahunacart-warning, var(--grav-warning, #b8860b));
    --kc-danger: var(--kahunacart-danger, var(--grav-danger, #c0392b));
    --kc-on-accent-strong: var(--kahunacart-on-accent-strong, #fff);

    line-height: 1.55;
}
@supports (color: rgb(from red r g b)) {
    .kahunacart {
        --kc-on-accent-strong: var(--kahunacart-on-accent-strong, rgb(from var(--kc-accent-strong)
            clamp(0, (128 - (0.2126 * r + 0.7152 * g + 0.0722 * b)) * 1000, 255)
            clamp(0, (128 - (0.2126 * r + 0.7152 * g + 0.0722 * b)) * 1000, 255)
            clamp(0, (128 - (0.2126 * r + 0.7152 * g + 0.0722 * b)) * 1000, 255)));
    }
}

/* ── focus ──────────────────────────────────────────────────────────────────
   One visible ring for everything focusable in the storefront, stated once at
   :focus-visible so pointer clicks stay quiet. Themes that suppress outlines
   on their own controls (a common reset) leave keyboard users with nothing to
   follow through a five-field checkout, which is exactly where losing your
   place costs a sale. */
.kahunacart :is(a, button, input, select, textarea):focus-visible {
    outline: 2px solid var(--kc-accent-strong);
    outline-offset: 2px;
}

/* ── money ──────────────────────────────────────────────────────────────────
   Every amount, wherever it renders — cart cells, the totals block, a variant
   option in the buy form. Tabular figures keep the decimal points of a column
   in one line; without them a proportional font staggers them by a fraction
   of an em per row and a receipt stops reading as arithmetic. */
.kahunacart :is(.kahunacart-price, .kahunacart-cart-table td, .kahunacart-totals td, .kahunacart-order-items td) {
    font-variant-numeric: tabular-nums;
}
.kahunacart-price {
    font-weight: 600;
    color: var(--kc-accent-ink);
}

/* ── buttons ────────────────────────────────────────────────────────────────
   The templates use the bare `.button` class every Grav theme is expected to
   define, so this is a *fallback*, not a claim: `.kahunacart :where(.button)`
   resolves at (0,1,0) — the specificity of `.kahunacart` alone, since
   :where() contributes nothing — which a theme's own `.button` at (0,1,0)
   beats on source order once its stylesheet loads after ours, and any
   theme-scoped selector beats outright. On a theme with no button styling at
   all this is what keeps "Add to cart" from rendering as a browser default. */
.kahunacart :where(.button) {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    border: 1px solid var(--kc-border-strong);
    border-radius: var(--kc-radius);
    padding: 0.5rem 1.1rem;
    background: var(--kc-surface);
    color: inherit; /* themes paint buttons for light backgrounds; follow the page instead */
    font: inherit;
    font-weight: 500;
    line-height: 1.2;
    text-decoration: none;
    cursor: pointer;
    transition: border-color var(--kc-dur) var(--kc-ease), background var(--kc-dur) var(--kc-ease);
}
.kahunacart :where(.button):hover {
    border-color: var(--kc-accent);
    text-decoration: none;
}

/* The two buttons that end a flow are the ones worth filling: everything else
   on the page is a step, these are the step. Stated at (0,2,0) so they still
   read as primary on a theme that supplies its own flat .button. */
.kahunacart .kahunacart-place-order,
.kahunacart .kahunacart-checkout-link {
    background: var(--kc-accent-strong);
    border-color: transparent;
    color: var(--kc-on-accent-strong);
    font-weight: 600;
}
.kahunacart .kahunacart-place-order:hover,
.kahunacart .kahunacart-checkout-link:hover {
    background: color-mix(in srgb, var(--kc-accent-strong) 85%, var(--kc-fg));
    border-color: transparent;
}
.kahunacart-place-order {
    margin-block-start: 1.25rem;
}

/* ── inputs ─────────────────────────────────────────────────────────────────
   Scoped to the storefront and left at :where() weight so a theme's form
   styling takes over without a fight. `font: inherit` is the load-bearing
   part: form controls do not inherit type by default, and a checkout in the
   browser's 13px system font inside a theme's own typography is the single
   most obvious tell that a plugin dropped markup into a page. */
.kahunacart :where(input:not([type="checkbox"], [type="radio"]), select, textarea) {
    /* Stated rather than assumed: most themes set border-box globally, but the
       ones that do not leave the notes textarea — the only control we give a
       literal width: 100% — hanging past the fieldset that contains it. */
    box-sizing: border-box;
    border: 1px solid var(--kc-border-strong);
    border-radius: var(--kc-radius);
    padding: 0.5rem 0.65rem;
    background: var(--kc-input);
    color: inherit;
    font: inherit;
    max-width: 100%;
    transition: border-color var(--kc-dur) var(--kc-ease), box-shadow var(--kc-dur) var(--kc-ease);
}
.kahunacart :where(input:not([type="checkbox"], [type="radio"]), select, textarea):focus {
    border-color: var(--kc-accent-strong);
}
.kahunacart :where(textarea) {
    width: 100%;
    resize: vertical;
}
/* A <select> is the one control that must not be see-through: several engines
   paint the open option list on the control's own background, and --kc-input
   resolves to `transparent` on a theme publishing no tokens — which renders
   the variant list as text over whatever is behind it. --kc-bg is the page's
   own ground, so the list stays opaque and still follows the theme. */
.kahunacart :where(select) {
    background-color: var(--kc-bg);
}
.kahunacart :where(fieldset) {
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius-lg);
    padding: 1rem 1.15rem 1.15rem;
    margin-block: 1.25rem;
    min-inline-size: 0; /* a fieldset defaults to min-content width and will not shrink inside a grid without this */
}
.kahunacart :where(legend) {
    padding-inline: 0.4rem;
    font-weight: 600;
    font-size: 0.95em;
}

/* ── flash messages ─────────────────────────────────────────────────────────
   Scopes come from Grav's own message queue (error, warning, info, success),
   so the modifier set is fixed by the platform rather than by us. Each is
   tinted from its semantic token instead of a literal, which keeps a theme's
   own idea of "danger" in force. */
.kahunacart-messages {
    display: grid;
    gap: 0.5rem;
    margin-block: 1rem;
}
.kahunacart-message {
    margin: 0;
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--kc-border);
    border-inline-start: 3px solid var(--kc-border-strong);
    border-radius: var(--kc-radius);
    background: var(--kc-surface);
}
.kahunacart-message--error {
    border-inline-start-color: var(--kc-danger);
    background: color-mix(in srgb, var(--kc-danger) 9%, transparent);
}
.kahunacart-message--warning {
    border-inline-start-color: var(--kc-warning);
    background: color-mix(in srgb, var(--kc-warning) 9%, transparent);
}
.kahunacart-message--success {
    border-inline-start-color: var(--kc-success);
    background: color-mix(in srgb, var(--kc-success) 9%, transparent);
}
.kahunacart-message--info {
    border-inline-start-color: var(--kc-accent);
    background: color-mix(in srgb, var(--kc-accent) 9%, transparent);
}

/* ── category nav ───────────────────────────────────────────────────────────
   The chips are a flat wrapped row on purpose — the partial renders children
   inline after their parent, so there is no nesting to express here even
   though the data is a tree. */
.kahunacart-categories {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    margin-block: 1.1rem 1.6rem;
}
.kahunacart-category-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.85rem;
    border: 1px solid var(--kc-border);
    border-radius: 999px;
    color: inherit;
    text-decoration: none;
    font-size: 0.92em;
    transition: border-color var(--kc-dur) var(--kc-ease), color var(--kc-dur) var(--kc-ease);
}
.kahunacart-category-chip:hover {
    border-color: var(--kc-accent);
    color: var(--kc-accent-ink);
    text-decoration: none;
}
.kahunacart-category-chip.is-active {
    background: var(--kc-accent-strong);
    border-color: transparent;
    color: var(--kc-on-accent-strong);
}
/* The count rides inside a filled chip as often as an outlined one, so it
   tints whatever colour the chip is already painting its text in rather than
   picking a neutral that would vanish against the accent. */
.kahunacart-category-count {
    font-family: var(--kc-mono);
    font-size: 0.8em;
    padding: 0.05em 0.45em;
    border-radius: 999px;
    background: color-mix(in srgb, currentColor 15%, transparent);
}
.kahunacart-category-description {
    color: var(--kc-muted);
    max-width: 65ch;
    margin-block-end: 1.5rem;
}

/* ── product grid ───────────────────────────────────────────────────────────
   auto-fill + minmax does the whole responsive job: the track count follows
   the container, not a guess at the viewport, so the grid also behaves in a
   theme's narrow sidebar or inside a two-column page layout where a
   viewport-width media query would be reading the wrong number. min(15rem,
   100%) keeps the single-column case from overflowing on a 320px phone. */
.kahunacart-product-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
    gap: 1.25rem;
    list-style: none;
    margin: 0 0 1.5rem;
    padding: 0;
}
.kahunacart-product-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1.1rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-card);
    transition: border-color var(--kc-dur) var(--kc-ease), transform var(--kc-dur) var(--kc-ease);
}
.kahunacart-product-card:hover {
    border-color: var(--kc-border-strong);
}
.kahunacart-product-card h3 {
    margin: 0;
    font-size: 1.05rem;
    line-height: 1.3;
}
.kahunacart-product-card > a {
    color: inherit;
    text-decoration: none;
}
.kahunacart-product-card > a:hover h3 {
    color: var(--kc-accent-ink);
}
.kahunacart-product-card p {
    margin: 0;
    color: var(--kc-muted);
}
.kahunacart-product-card .kahunacart-price {
    color: var(--kc-accent-ink);
}
/* The card's own button is the last child and the cards in a row are not the
   same height, so push it to the bottom rather than letting it float
   mid-card on the short ones. */
.kahunacart-product-card > .button {
    margin-block-start: auto;
    align-self: flex-start;
}

/* ── card media ─────────────────────────────────────────────────────────────
   The picture bleeds to the card's edges rather than sitting inside its
   padding: a photograph inset by an even margin on three sides and butted
   against text on the fourth is the thing that makes a card look like a
   document instead of a product.

   Every card image is cropped to the same ratio server-side, so aspect-ratio
   here is describing what the file already is rather than distorting it — it
   is present so the row does not jump while the images load on a browser that
   ignores the width and height attributes. object-fit covers the remaining
   case: a store that turned resized copies off and is serving originals at
   whatever proportions it uploaded. */
:where(.kahunacart) {
    --kc-card-ratio: var(--kahunacart-card-ratio, 4 / 3);
}
.kahunacart-card-media {
    margin: -1.1rem -1.1rem 0.1rem;
    overflow: hidden;
    border-radius: calc(var(--kc-radius-lg) - 1px) calc(var(--kc-radius-lg) - 1px) 0 0;
    background: var(--kc-surface);
}
.kahunacart-card-media img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: var(--kc-card-ratio);
    object-fit: cover;
}
/* The whole media-plus-title block is one link, so the underline the theme
   would put on it has to come off the picture as well as the heading. */
.kahunacart-card-link {
    display: block;
    color: inherit;
    text-decoration: none;
}
.kahunacart-card-link:hover h3 {
    color: var(--kc-accent-ink);
}

/* ── product gallery ────────────────────────────────────────────────────────
   A scroll-snapping row of full-size images with thumbnails that link to them
   by id. No JavaScript is involved: the thumbnails are ordinary in-page links,
   so the gallery works with a click, a swipe, a keyboard and with scripting
   off, and there is no plugin script for a theme's bundler to argue with.

   A product with one image gets a row of one, which reads as a plain image —
   which is the point: nothing about a single-picture product should look like
   a carousel someone forgot to fill. */
.kahunacart-gallery {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-block: 1.25rem;
}
.kahunacart-gallery-main {
    display: flex;
    gap: 0.75rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    /* The strip is focusable so a keyboard can scroll it; without this the
       only way to the second image would be the thumbnail link. */
    scrollbar-width: thin;
}
.kahunacart-gallery-slide {
    flex: 0 0 100%;
    margin: 0;
    scroll-snap-align: center;
}
.kahunacart-gallery-slide img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface);
}
.kahunacart-gallery-thumbs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    list-style: none;
    margin: 0;
    padding: 0;
}
.kahunacart-gallery-thumbs a {
    display: block;
    overflow: hidden;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius);
    transition: border-color var(--kc-dur) var(--kc-ease);
}
.kahunacart-gallery-thumbs a:hover,
.kahunacart-gallery-thumbs a:focus-visible {
    border-color: var(--kc-accent);
}
.kahunacart-gallery-thumbs img {
    display: block;
    width: 60px;
    height: 60px;
    object-fit: cover;
}

/* ── search ─────────────────────────────────────────────────────────────────
   One row that collapses to two: the field takes whatever is left after the
   button, and on a narrow screen the button drops beneath it rather than
   squeezing the field down to nothing. */
.kahunacart-search {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-block-end: 1.25rem;
}
.kahunacart-search-field {
    flex: 1 1 14rem;
}
.kahunacart-search-field input {
    width: 100%;
}
.kahunacart-search-summary {
    color: var(--kc-muted);
}
.kahunacart-search-more {
    color: var(--kc-muted);
    font-size: 0.95em;
}

/* Text that exists for screen readers and nothing else. The clip-path pair is
   the modern form; the clip/1px box is what older engines still read. */
.kahunacart-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── product detail ─────────────────────────────────────────────────────── */

.kahunacart-summary {
    font-size: 1.1em;
    color: var(--kc-muted);
    max-width: 65ch;
}
.kahunacart-description {
    max-width: 70ch;
    margin-block: 1.25rem;
}
.kahunacart-description :where(img) {
    max-width: 100%;
    height: auto;
}

/* The buy form is a single row of controls that wraps: variant select (only
   when there is more than one), price, quantity, submit. flex-wrap rather
   than a grid because which of those exist changes per product.

   This is the one panel on the storefront that has to be found without being
   looked for, so it takes the deeper of the two fills — a theme that
   distinguishes --grav-bg-subtle from --grav-bg-elevated separates it from
   the download rows and message strips that use the lighter one. */
.kahunacart-buy {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    padding: 1rem 1.1rem;
    margin-block: 1.5rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface-2);
}
.kahunacart-buy .kahunacart-price {
    font-size: 1.25rem;
}
/* Both labels in the buy form wrap their control and carry no text of their
   own, so they are pure layout — no reason to let them stack. */
.kahunacart-buy :where(label) {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0;
}
.kahunacart-qty input {
    width: 5rem;
}

/* ── option picker ────────────────────────────────────────────────────────
   Size / Colour / Material, drawn as a dropdown, a row of pills, or colour
   swatches. The whole block ships hidden and is revealed by
   assets/kahunacart-options.js, so a browser with no JavaScript sees only the
   plain variant <select> the form actually posts — which is why `hidden` here
   is honoured rather than overridden by the flex display below.

   The axes stack and the form's own flex row is broken before them, because a
   picker sitting inline between a price and a quantity box reads as a toolbar
   rather than as the choice it is. */
.kahunacart-options[hidden] {
    display: none;
}
.kahunacart-options {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    flex-basis: 100%;
}
.kahunacart-option {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.kahunacart-option-name {
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--kc-muted);
}
.kahunacart-option-select {
    max-width: 18rem;
}
.kahunacart-option-choices {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

/* One button style for both widgets; the swatch adds a colour dot and drops
   the border weight, because the colour is the label there and a heavy chrome
   around it changes what the colour looks like. */
.kahunacart-option-choice {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.7rem;
    border: 1px solid var(--kc-border-strong);
    border-radius: var(--kc-radius);
    background: var(--kc-bg);
    color: inherit;
    font: inherit;
    line-height: 1.2;
    cursor: pointer;
    transition: border-color var(--kc-dur) var(--kc-ease), box-shadow var(--kc-dur) var(--kc-ease);
}
.kahunacart-option-choice:hover:not([data-kc-unavailable]) {
    border-color: var(--kc-accent);
}
.kahunacart-option-choice.is-selected {
    border-color: var(--kc-accent);
    /* An inset ring rather than a thicker border: growing the border by a pixel
       moves every sibling pill when one is picked. */
    box-shadow: inset 0 0 0 1px var(--kc-accent);
}

/* Unselectable — no variant left in the current selection wears it. Faded and
   struck through, not removed: a customer who picked Large should be able to
   see that Navy exists in other sizes rather than watch it vanish. */
.kahunacart-option-choice.is-unavailable {
    opacity: 0.4;
    cursor: not-allowed;
    text-decoration: line-through;
}
/* A real combination that has run out. Still clickable, so the price and the
   sold-out message can be shown for it. */
.kahunacart-option-choice.is-sold-out {
    border-style: dashed;
    color: var(--kc-muted);
}

.kahunacart-option-swatch {
    padding-inline-start: 0.45rem;
}
.kahunacart-option-dot {
    /* Overridden per value by an inline --kc-swatch from the stored colour; the
       fallback keeps a value with no colour from drawing a black hole. */
    --kc-swatch: var(--kahunacart-swatch, color-mix(in srgb, var(--kc-fg) 12%, transparent));
    inline-size: 1.05rem;
    block-size: 1.05rem;
    border-radius: 50%;
    background: var(--kc-swatch);
    /* The ring is the fg colour at low alpha rather than a fixed grey so a
       white swatch stays visible in light mode and a black one in dark. */
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--kc-fg) 22%, transparent);
    flex: none;
}

.kahunacart-option-status {
    margin: 0;
    font-size: 0.85rem;
    color: var(--kc-muted);
    min-block-size: 1.2em; /* reserved, so resolving a variant does not jump the form */
}
.kahunacart-compare-price {
    color: var(--kc-subtle);
}
.kahunacart-compare-price[hidden] {
    display: none;
}

/* The fallback select once the picker has taken over. Hidden from sight but
   not from the form: a `disabled` or removed control is not submitted, and it
   is still the field carrying variant_id. */
.kahunacart-visually-hidden {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── cart ─────────────────────────────────────────────────────────────────── */

.kahunacart-cart-table {
    width: 100%;
    border-collapse: collapse;
    margin-block: 1.25rem;
}
.kahunacart-cart-table :where(th, td) {
    padding: 0.6rem 0.7rem;
    border-block-end: 1px solid var(--kc-border);
    text-align: start;
    vertical-align: middle;
}
.kahunacart-cart-table :where(thead th) {
    font-size: 0.78em;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--kc-subtle);
    font-weight: 600;
    white-space: nowrap;
}
/* Price, quantity and line total; the trailing remove cell is left alone so
   its × stays at the outside edge. */
.kahunacart-cart-table :where(td:nth-child(2), td:nth-child(3), td:nth-child(4)),
.kahunacart-cart-table :where(th:nth-child(2), th:nth-child(3), th:nth-child(4)) {
    text-align: end;
    white-space: nowrap;
}
.kahunacart-cart-table :where(td:last-child) {
    text-align: end;
    width: 1%;
}
.kahunacart-cart-table input[type="number"] {
    width: 4.5rem;
    text-align: end;
}

/* Remove is destructive and unlabelled (the × carries an aria-label), so it
   stays quiet until pointed at rather than sitting red in every row. */
.kahunacart-remove {
    border: 1px solid transparent;
    border-radius: var(--kc-radius);
    background: none;
    color: var(--kc-subtle);
    font: inherit;
    font-size: 1.15rem;
    line-height: 1;
    padding: 0.15rem 0.5rem;
    cursor: pointer;
    transition: color var(--kc-dur) var(--kc-ease), border-color var(--kc-dur) var(--kc-ease);
}
.kahunacart-remove:hover {
    color: var(--kc-danger);
    border-color: color-mix(in srgb, var(--kc-danger) 40%, transparent);
}

/* The per-item remove forms are rendered after the main cart form and hold
   nothing but hidden inputs — each one still generates a block box, stacking
   empty margins under the table. `display: contents` removes the boxes
   without touching the controls, which are submitted by the buttons that
   reference them through `form=`. */
.kahunacart-cart > form[id^="kahunacart-remove-"] {
    display: contents;
}

/* Update and Checkout are siblings inside the cart form with no wrapper of
   their own, so the spacing has to hang off the table they follow. */
.kahunacart-cart-table ~ .button {
    margin-block-start: 0.25rem;
    margin-inline-end: 0.5rem;
}

/* ── coupon ─────────────────────────────────────────────────────────────── */

.kahunacart-coupon {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    margin-block: 1.75rem;
    padding: 0.9rem 1.1rem;
    border: 1px dashed var(--kc-border-strong);
    border-radius: var(--kc-radius-lg);
}
.kahunacart-coupon label {
    font-weight: 600;
    margin: 0;
}
.kahunacart-coupon input[type="text"] {
    text-transform: uppercase;
    font-family: var(--kc-mono);
    letter-spacing: 0.04em;
    width: 12rem;
}
/* Removing a code is the undo, not the action — it sits next to Apply and
   should not compete with it. */
.kahunacart-coupon-remove {
    color: var(--kc-muted);
}
.kahunacart-coupon-active {
    color: var(--kc-success);
    font-weight: 600;
}

/* ── totals ─────────────────────────────────────────────────────────────────
   Right-aligned receipt block that sits at the end of the content column
   rather than stretching across it: totals read as a column of figures, and
   a full-width version puts a metre of whitespace between each label and its
   amount. */
.kahunacart-totals {
    margin-block: 1.5rem;
    margin-inline-start: auto;
    border-collapse: collapse;
    min-width: min(22rem, 100%);
}
.kahunacart-totals :where(th, td) {
    padding: 0.4rem 0.2rem;
    text-align: start;
    font-weight: 400;
}
.kahunacart-totals :where(td) {
    text-align: end;
    padding-inline-start: 2rem;
    white-space: nowrap;
}
.kahunacart-totals :where(th) {
    color: var(--kc-muted);
}
/* Adjustment types come from the calculator and are a closed set — discount,
   shipping, tax. Only discount gets a colour: it is the one line a customer
   looks for, and tinting shipping or tax would read as a warning about a
   charge that is simply part of the price. */
.kahunacart-adjustment--discount :where(th, td) {
    color: var(--kc-success);
}
.kahunacart-adjustment em {
    color: var(--kc-subtle);
    font-size: 0.9em;
    font-style: normal;
}
.kahunacart-total :where(th, td) {
    border-block-start: 2px solid var(--kc-border-strong);
    padding-block-start: 0.7rem;
    font-weight: 700;
    font-size: 1.08em;
    color: inherit;
}

/* ── checkout ───────────────────────────────────────────────────────────── */

.kahunacart-checkout-form {
    max-width: 42rem;
}
/* Field labels wrap their input and carry the field name as a bare text node
   in front of it. As grid items that text node becomes its own row, which
   gives a stacked label-over-input pair without the markup having a
   <span> to hang it on. The three label kinds that put a control *first*
   (checkbox, provider radio, shipping radio) are excluded — they are
   genuinely inline and stacking them would orphan the control above its own
   text. */
.kahunacart-checkout-form :where(fieldset > label:not(.kahunacart-checkbox, .kahunacart-provider, .kahunacart-shipping-method)) {
    display: grid;
    gap: 0.3rem;
    margin-block-end: 0.85rem;
    color: var(--kc-muted);
    font-size: 0.9em;
}
.kahunacart-checkout-form :where(fieldset > label > input) {
    color: var(--kc-fg);
    font-size: 1rem;
}
.kahunacart-checkbox,
.kahunacart-provider,
.kahunacart-shipping-method {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-block: 0.4rem;
    cursor: pointer;
}
/* The one line of explanation under a checkbox that needs one — what happens
   after the order completes if "create an account" is ticked. Sits under the
   control it belongs to, indented past the box. */
.kahunacart-hint {
    margin-block: 0 0.6rem;
    margin-inline-start: 1.6rem;
    color: var(--kc-muted);
    font-size: 0.85em;
}

/* Payment and shipping options are rows the customer picks between, so give
   them the outline of a control instead of leaving a bare radio floating in
   a fieldset. */
.kahunacart-provider,
.kahunacart-shipping-method {
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius);
    transition: border-color var(--kc-dur) var(--kc-ease), background var(--kc-dur) var(--kc-ease);
}
.kahunacart-provider:hover,
.kahunacart-shipping-method:hover {
    border-color: var(--kc-border-strong);
    background: var(--kc-surface);
}
/* :has() is the only way to reach the row from its own radio; where it is
   unsupported the rows simply stay uniform, which is a lesser look rather
   than a broken one. */
.kahunacart-provider:has(input:checked),
.kahunacart-shipping-method:has(input:checked) {
    border-color: var(--kc-accent);
    background: color-mix(in srgb, var(--kc-accent) 8%, transparent);
}
/* The method's price is pushed to the far edge so a column of options reads
   as a price list. */
.kahunacart-shipping-method .kahunacart-price {
    margin-inline-start: auto;
}
.kahunacart-shipping-hint,
.kahunacart-shipping-none {
    margin: 0;
    color: var(--kc-muted);
    font-size: 0.95em;
}
/* No route ships here means the order cannot proceed — worth saying in the
   colour the rest of the page uses for a blocked state. */
.kahunacart-shipping-none {
    color: var(--kc-danger);
}

/* ── address check ──────────────────────────────────────────────────────── */

/* Shown at most once per address, at the top of the checkout form, when a
   validation service has a correction to offer. It is a question, not an
   error: the accent border says "read this" without the red that would
   suggest the order has been refused — because it has not been, and either
   button carries on to payment. */
.kahunacart-address-check {
    margin-block-end: 1.4rem;
    padding: 1rem 1.1rem;
    border: 1px solid var(--kc-border);
    border-inline-start: 3px solid var(--kc-accent);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface);
}
/* An address the service could not find at all is the one case worth a
   warning colour — still not a refusal, still both buttons. */
.kahunacart-address-check-warning {
    border-inline-start-color: var(--kc-warning);
}
.kahunacart-address-check-title {
    margin-block: 0 0.3rem;
    font-size: 1.05em;
}
.kahunacart-address-check-lead {
    margin-block: 0 1rem;
    color: var(--kc-muted);
    font-size: 0.95em;
}
/* Side by side where there is room, stacked where there is not: comparing two
   addresses is the whole job, and two columns is how a comparison reads. */
.kahunacart-address-check-options {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.kahunacart-address-check-option {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    align-items: start;
    padding: 0.8rem 0.9rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius);
    background: var(--kc-bg);
}
.kahunacart-address-check-label {
    margin: 0;
    font-size: 0.8em;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--kc-subtle);
}
.kahunacart-address-check-option .kahunacart-address {
    margin: 0;
    font-style: normal;
    line-height: 1.6;
}
/* The suggestion is the recommended answer, so it gets the filled button and
   the outlined card; keeping what you typed stays available and stays plain. */
.kahunacart-address-check-suggested {
    border-color: var(--kc-accent);
}
.kahunacart-address-check-keep {
    margin-block-start: auto;
}
.kahunacart-address-check-accept {
    margin-block-start: auto;
    border-color: var(--kc-accent-strong);
    background: var(--kc-accent-strong);
    color: var(--kc-on-accent-strong);
}

/* ── order confirmation ─────────────────────────────────────────────────── */

.kahunacart-order-meta {
    padding: 0.9rem 1.1rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface);
    line-height: 1.9;
}
.kahunacart-order-items {
    width: 100%;
    border-collapse: collapse;
    margin-block: 1.5rem;
}
.kahunacart-order-items :where(th, td) {
    padding: 0.55rem 0.7rem;
    border-block-end: 1px solid var(--kc-border);
    text-align: start;
}
.kahunacart-order-items :where(thead th) {
    font-size: 0.78em;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--kc-subtle);
}
.kahunacart-order-items :where(th:not(:first-child), td:not(:first-child)) {
    text-align: end;
    white-space: nowrap;
}
/* Bank details, a reference to quote, a "we ship on Monday" note — merchant
   markdown, so it needs to read as an instruction panel and not as another
   receipt row. */
.kahunacart-offline-instructions {
    padding: 1rem 1.15rem;
    margin-block: 1.5rem;
    border: 1px solid var(--kc-warning);
    border-radius: var(--kc-radius-lg);
    background: color-mix(in srgb, var(--kc-warning) 8%, transparent);
}
.kahunacart-offline-instructions > :first-child { margin-block-start: 0; }
.kahunacart-offline-instructions > :last-child { margin-block-end: 0; }

/* The guest's account offer, under the receipt. An invitation rather than a
   step, so it borrows the surface treatment of the meta panel instead of the
   warning colour the offline instructions use — nothing here is a thing the
   customer has to do. The accent edge is the one signal that it is an offer and
   not another summary block. */
.kahunacart-account-offer {
    margin-block: 2rem;
    padding: 1rem 1.15rem;
    border: 1px solid var(--kc-border);
    border-inline-start: 3px solid var(--kc-accent);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface);
}
.kahunacart-account-offer-title {
    margin-block: 0 0.35rem;
    font-size: 1.05em;
}
.kahunacart-account-offer-text {
    margin-block: 0 0.9rem;
    color: var(--kc-muted);
    font-size: 0.92em;
}
.kahunacart-account-offer-form {
    margin: 0;
}
/* The already-registered case is one line, not a panel: the customer has
   nothing to decide, so it reads like the order links it sits above. */
.kahunacart-account-offer-signin {
    margin-block: 1.5rem 0.5rem;
    font-size: 0.92em;
}

/* ── downloads ──────────────────────────────────────────────────────────── */

.kahunacart-downloads {
    margin-block: 2rem;
}
.kahunacart-download-list {
    display: grid;
    gap: 0.5rem;
    list-style: none;
    margin: 0;
    padding: 0;
}
.kahunacart-download {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 1rem;
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius);
    background: var(--kc-surface);
}
.kahunacart-download > a {
    font-weight: 600;
}
/* Remaining count and expiry date: the terms of the grant, not the file, so
   they sit at the far edge in the muted ramp. */
.kahunacart-download-meta {
    margin-inline-start: auto;
    color: var(--kc-muted);
    font-size: 0.88em;
    font-variant-numeric: tabular-nums;
}

/* ── releases ───────────────────────────────────────────────────────────────
   A block per version, holding that version's builds. The heading carries the
   weight and the block itself stays flat, so a customer scanning a long list
   of versions reads the numbers rather than a stack of boxes. */
.kahunacart-release {
    margin-block: 1.5rem;
}
.kahunacart-release-version {
    margin-block-end: 0.15rem;
}
.kahunacart-release-latest {
    padding: 0.1em 0.5em;
    border-radius: var(--kc-radius);
    border: 1px solid var(--kc-border);
    background: var(--kc-surface);
    color: var(--kc-muted);
    font-size: 0.7em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    vertical-align: middle;
}
.kahunacart-release-date {
    margin-block: 0 0.6rem;
    color: var(--kc-muted);
    font-size: 0.88em;
    font-variant-numeric: tabular-nums;
}
.kahunacart-release-changelog {
    margin-block-end: 0.8rem;
    font-size: 0.94em;
}
.kahunacart-release-changelog > :first-child {
    margin-block-start: 0;
}
.kahunacart-release-changelog > :last-child {
    margin-block-end: 0;
}

/* ── processing & denial ────────────────────────────────────────────────────
   Both are terminal single-message pages, so they get the same centred
   treatment and differ only in tone. The denied page also carries a
   --{reason} modifier (not_found, revoked, expired, exhausted,
   order_refunded, unreadable) that we deliberately leave unstyled: the
   distinction matters to a theme that wants to branch, not to a customer who
   just needs to know what to do next. */
.kahunacart-processing,
.kahunacart-denied {
    max-width: 40rem;
    margin-inline: auto;
    text-align: center;
    padding-block: 2rem;
}
.kahunacart-processing p {
    color: var(--kc-muted);
}
.kahunacart-denied-message {
    padding: 1rem 1.15rem;
    border: 1px solid color-mix(in srgb, var(--kc-danger) 45%, transparent);
    border-radius: var(--kc-radius-lg);
    background: color-mix(in srgb, var(--kc-danger) 8%, transparent);
    font-size: 1.05em;
}
/* Refunded is a bookkeeping outcome rather than a failure — the file is gone
   because the money went back, which is not something to alarm anyone about. */
.kahunacart-denied--order_refunded .kahunacart-denied-message {
    border-color: var(--kc-border-strong);
    background: var(--kc-surface);
}

/* ── account area ───────────────────────────────────────────────────────────
   Four pages sharing one nav and one panel rhythm. The nav is a plain list of
   links rather than tabs: it has to survive a theme that already styles nav
   elements, and a row of underlined links does that where a tab strip does
   not. The current item is a span, not a link, so it needs its own weight. */

.kahunacart-account-nav {
    margin-block: 1rem 2rem;
    border-block-end: 1px solid var(--kc-border);
}
.kahunacart-account-nav :where(ul) {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.4rem;
    list-style: none;
    margin: 0;
    padding: 0;
}
.kahunacart-account-nav :where(li) {
    padding-block-end: 0.5rem;
    margin-block-end: -1px;
}
.kahunacart-account-nav-item--current {
    border-block-end: 2px solid var(--kc-accent);
    font-weight: 600;
}

.kahunacart-account-identity {
    color: var(--kc-muted);
}
.kahunacart-account-panel {
    margin-block: 2rem;
}
.kahunacart-account-panel :where(h2) {
    margin-block-end: 0.75rem;
    font-size: 1.15em;
}
/* Every "you have nothing here yet" line on the account pages. Muted rather
   than alarming: an empty account is the ordinary state of a new one. */
/* An empty section, and the notice a protected demo login gets in place of
   the credentials forms — both are a quiet sentence where a panel would be. */
.kahunacart-account-empty,
.kahunacart-account-frozen {
    color: var(--kc-muted);
    padding: 0.9rem 1.05rem;
    border: 1px dashed var(--kc-border);
    border-radius: var(--kc-radius);
}

.kahunacart-account-orders {
    inline-size: 100%;
    border-collapse: collapse;
}
.kahunacart-account-orders :where(th, td) {
    padding: 0.6rem 0.75rem;
    border-block-end: 1px solid var(--kc-border);
    text-align: start;
    vertical-align: top;
}
.kahunacart-account-orders :where(thead th) {
    border-block-end-color: var(--kc-border-strong);
    font-size: 0.85em;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--kc-subtle);
}
.kahunacart-account-order-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.9rem;
    font-size: 0.9em;
}

/* The three status axes read as one line of small caps-ish chips. Each carries
   a --{value} modifier we leave unstyled on purpose: which statuses deserve a
   colour is a merchant's judgement, and a theme can branch on the modifier. */
.kahunacart-status {
    display: inline-block;
    margin-inline-end: 0.4rem;
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius);
    background: var(--kc-surface);
    font-size: 0.82em;
    white-space: nowrap;
}
.kahunacart-status--paid {
    border-color: color-mix(in srgb, var(--kc-success) 45%, transparent);
    background: color-mix(in srgb, var(--kc-success) 9%, transparent);
}
.kahunacart-status--pending {
    border-color: color-mix(in srgb, var(--kc-warning) 45%, transparent);
    background: color-mix(in srgb, var(--kc-warning) 9%, transparent);
}
.kahunacart-status--failed,
.kahunacart-status--canceled {
    border-color: color-mix(in srgb, var(--kc-danger) 45%, transparent);
    background: color-mix(in srgb, var(--kc-danger) 9%, transparent);
}

/* The product a download came from, between the file name and the terms. */
.kahunacart-download-product {
    color: var(--kc-subtle);
    font-size: 0.9em;
}

/* ── address book ─────────────────────────────────────────────────────────── */

.kahunacart-address {
    font-style: normal;
    line-height: 1.5;
}
.kahunacart-address-grid,
.kahunacart-address-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: 1rem;
    list-style: none;
    margin: 0;
    padding: 0;
}
.kahunacart-address-card {
    padding: 1rem 1.1rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-card);
}
.kahunacart-address-card--default {
    border-color: var(--kc-border-strong);
}
.kahunacart-address-card :where(h2, h3) {
    margin-block: 0 0.5rem;
    font-size: 1em;
}
.kahunacart-address-flags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-block: 0 0.6rem;
}
.kahunacart-address-flag {
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--kc-accent);
    border-radius: var(--kc-radius);
    color: var(--kc-accent-ink);
    font-size: 0.78em;
}
.kahunacart-address-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 0.9rem;
    margin-block-start: 0.8rem;
}
.kahunacart-address-actions :where(form) {
    margin: 0;
}
/* A form control that has to read as a link: these actions are POSTs (they
   change data and carry a nonce), but "use for delivery" is not a button-sized
   decision and a row of grey rectangles would say it was. */
.kahunacart-link-button {
    padding: 0;
    border: 0;
    background: none;
    color: var(--kc-accent-ink);
    font: inherit;
    font-size: 0.9em;
    text-decoration: underline;
    cursor: pointer;
}
.kahunacart-link-button--danger {
    color: var(--kc-danger);
}
.kahunacart-address-edit {
    margin-block-start: 0.9rem;
    font-size: 0.95em;
}
.kahunacart-address-edit :where(summary) {
    cursor: pointer;
    color: var(--kc-muted);
}
.kahunacart-address-form {
    display: grid;
    gap: 0.6rem;
    margin-block-start: 0.9rem;
    max-inline-size: 34rem;
}

/* ── invoice ────────────────────────────────────────────────────────────────
   A document rather than a page: fixed measure, its own header rule, and a
   toolbar that exists only on screen. */

.kahunacart-invoice {
    max-inline-size: 48rem;
    margin-inline: auto;
}
.kahunacart-invoice-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
}
.kahunacart-invoice-header {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 1.5rem;
    padding-block-end: 1.25rem;
    margin-block-end: 1.5rem;
    border-block-end: 2px solid var(--kc-border-strong);
}
.kahunacart-invoice-store :where(h2) {
    margin-block: 0 0.4rem;
    font-size: 1.2em;
}
.kahunacart-invoice-meta {
    text-align: end;
}
.kahunacart-invoice-meta :where(h1) {
    margin-block: 0 0.5rem;
    font-size: 1.6em;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
/* Label above value, right-aligned, one pair per row — a definition list
   rather than a table so it reflows on a phone without a scroll port. */
.kahunacart-invoice-meta :where(dl) {
    margin: 0;
    font-size: 0.9em;
}
.kahunacart-invoice-meta :where(dt) {
    color: var(--kc-subtle);
    font-size: 0.85em;
}
.kahunacart-invoice-meta :where(dd) {
    margin: 0 0 0.4rem;
    font-variant-numeric: tabular-nums;
}
.kahunacart-invoice-tax-id {
    margin-block: 0.4rem 0;
    color: var(--kc-muted);
    font-size: 0.9em;
}
.kahunacart-invoice-parties {
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
    margin-block-end: 1.75rem;
}
.kahunacart-invoice-party :where(h3) {
    margin-block: 0 0.4rem;
    font-size: 0.85em;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--kc-subtle);
}
.kahunacart-invoice-email {
    margin-block: 0.3rem 0;
    color: var(--kc-muted);
    font-size: 0.9em;
}
.kahunacart-invoice-items {
    inline-size: 100%;
}
.kahunacart-invoice-footer {
    margin-block-start: 2.5rem;
    padding-block-start: 1rem;
    border-block-start: 1px solid var(--kc-border);
    color: var(--kc-muted);
    font-size: 0.9em;
}

/* ── printing ───────────────────────────────────────────────────────────────
   The invoice extends the theme like every other template, so printing it
   means suppressing the theme's chrome without knowing what the chrome is.

   visibility does that where display cannot: hiding the body outright would
   take the invoice inside it, whereas `visibility: hidden` is overridable per
   descendant, so the invoice paints and everything around it does not. What
   it leaves behind is the space the chrome occupied, which is why the invoice
   is then taken out of flow and pinned to the top of the sheet — otherwise a
   tall header prints as a blank first page.

   Anything the customer would not want on paper carries
   .kahunacart-no-print, which is display: none in both directions. */
@media print {
    body {
        visibility: hidden;
        background: none;
    }
    .kahunacart-invoice,
    .kahunacart-invoice * {
        visibility: visible;
    }
    .kahunacart-invoice {
        position: absolute;
        inset-block-start: 0;
        inset-inline-start: 0;
        inline-size: 100%;
        max-inline-size: none;
        padding: 0;
        margin: 0;
    }
    .kahunacart-no-print {
        display: none !important;
    }
    /* Print colour is expensive and unreliable; the document reads on its
       rules and its type instead. Rows must not break across sheets. */
    .kahunacart-invoice :where(table, tr, .kahunacart-invoice-header) {
        break-inside: avoid;
    }
    .kahunacart-invoice :where(a) {
        text-decoration: none;
        color: inherit;
    }
}

/* ── narrow viewports ───────────────────────────────────────────────────────
   The grid, the chip row and every flex row already reflow on their own; what
   does not is the tables, and the checkout's fixed-width plumbing, which has
   to give up its fixed widths before a 320px screen. */
/* ── catalog layout: tree sidebar + listing ─────────────────────────────────
   Two columns, and only when there is a sidebar to put in the first one. The
   layout wrapper is not emitted at all on a store with no categories and no
   tags (see the templates), so a flat catalog gets exactly the single-column
   page it always had — this whole section is dead CSS there rather than a rule
   that has to be neutralised.

   grid-template-columns names the sidebar in `rem` and lets the listing take
   the rest: a product grid is auto-fill and will happily fill whatever it is
   given, while a facet panel has a width its content decides. minmax(0, 1fr)
   on the second track rather than a bare 1fr, because a bare 1fr floors at
   min-content and a wide product card would push the sidebar off the page. */
.kahunacart-catalog-layout {
    display: grid;
    grid-template-columns: 15rem minmax(0, 1fr);
    gap: 2rem;
    align-items: start;
}
.kahunacart-catalog-main {
    min-width: 0; /* let the grid inside it shrink rather than overflow */
}

.kahunacart-catalog-nav {
    display: flex;
    flex-direction: column;
    gap: 1.75rem;
    font-size: 0.95em;
}
.kahunacart-nav-title {
    font-size: 0.8em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--kc-subtle);
    margin: 0 0 0.6rem;
}

/* ── category tree ──────────────────────────────────────────────────────────
   Nested <ul>s with a <details> per branch. The indent is a padding on the
   inner list rather than a margin on the item, so the hover and current-page
   backgrounds still run the full width of the rail at every depth. */
.kahunacart-cat-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.kahunacart-cat-list .kahunacart-cat-list {
    padding-inline-start: 0.85rem;
    border-inline-start: 1px solid var(--kc-border);
    margin-inline-start: 0.35rem;
}
.kahunacart-cat-item {
    display: block;
}
.kahunacart-cat-link {
    display: inline-block;
    padding: 0.25rem 0.35rem;
    color: inherit;
    text-decoration: none;
    border-radius: calc(var(--kc-radius) / 1.5);
}
.kahunacart-cat-link:hover {
    color: var(--kc-accent-ink);
    text-decoration: none;
}
.kahunacart-cat-link.is-current {
    font-weight: 600;
    color: var(--kc-accent-ink);
}
.kahunacart-cat-item.is-ancestor > .kahunacart-cat-branch > .kahunacart-cat-summary > .kahunacart-cat-link {
    font-weight: 600;
}
.kahunacart-cat-count {
    font-family: var(--kc-mono);
    font-size: 0.78em;
    color: var(--kc-subtle);
}

/* The <summary> carries the disclosure marker and the link side by side.
   list-item display keeps the browser's own triangle — a hand-rolled one would
   have to be re-invented for every writing mode and would not rotate on open. */
.kahunacart-cat-summary {
    display: list-item;
    list-style-position: outside;
    cursor: pointer;
    padding-inline-start: 0.1rem;
}
.kahunacart-cat-summary::marker {
    color: var(--kc-subtle);
    font-size: 0.8em;
}
.kahunacart-cat-summary > .kahunacart-cat-link {
    cursor: pointer;
}

/* ── filter panel ───────────────────────────────────────────────────────── */
.kahunacart-filters {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
}
.kahunacart-filters :where(.kahunacart-filter-group) {
    border: 0;
    border-block-start: 1px solid var(--kc-border);
    margin: 0;
    padding: 0.9rem 0 0;
}
.kahunacart-filters :where(legend) {
    font-size: 0.8em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--kc-subtle);
    padding: 0;
    margin-block-end: 0.5rem;
}
.kahunacart-filter-options {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    /* A long facet list is a scroll inside the panel rather than a rail that
       runs past the product grid. max-height in `em` so it tracks the font
       size a theme sets rather than a pixel guess. */
    max-height: 18em;
    overflow-y: auto;
}
.kahunacart-filter-option {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    cursor: pointer;
}
.kahunacart-filter-option > span:first-of-type {
    flex: 1 1 auto;
}
.kahunacart-filter-count {
    font-family: var(--kc-mono);
    font-size: 0.78em;
    color: var(--kc-subtle);
}
.kahunacart-filter-range {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.kahunacart-filter-field {
    flex: 1 1 0;
    min-width: 0;
}
.kahunacart-filter-field input {
    width: 100%;
}
.kahunacart-filter-dash {
    color: var(--kc-subtle);
}
.kahunacart-filter-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.kahunacart-filter-clear {
    font-size: 0.9em;
    color: var(--kc-muted);
}

/* ── breadcrumbs ────────────────────────────────────────────────────────── */
.kahunacart-breadcrumbs {
    margin-block-end: 0.75rem;
    font-size: 0.9em;
    color: var(--kc-muted);
}
.kahunacart-breadcrumb-list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    list-style: none;
    margin: 0;
    padding: 0;
}
/* The separator is generated rather than written into the markup: it is
   decoration, and a screen reader reading "slash" between every crumb is
   noise. aria-hidden on a written-out separator would do the same job with
   more markup. */
.kahunacart-breadcrumb-item + .kahunacart-breadcrumb-item::before {
    content: "/";
    margin-inline-end: 0.4rem;
    color: var(--kc-border-strong);
}
.kahunacart-breadcrumb-item a {
    color: inherit;
}

/* ── subcategory cards (the department landing page) ────────────────────────
   The same auto-fill grid the product grid uses, at a slightly wider minimum:
   a department card carries a banner image and a name, and reads badly at
   product-card width. */
.kahunacart-subcategory-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr));
    gap: 1.25rem;
    list-style: none;
    margin: 0 0 2rem;
    padding: 0;
}
.kahunacart-subcategory-card {
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-card);
    overflow: hidden;
    transition: border-color var(--kc-dur) var(--kc-ease);
}
.kahunacart-subcategory-card:hover {
    border-color: var(--kc-accent);
}
.kahunacart-subcategory-link {
    display: block;
    color: inherit;
    text-decoration: none;
    height: 100%;
}
.kahunacart-subcategory-link:hover {
    text-decoration: none;
}
.kahunacart-subcategory-media {
    margin: 0;
    aspect-ratio: 16 / 9;
    background: var(--kc-surface);
}
.kahunacart-subcategory-media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.kahunacart-subcategory-title {
    margin: 0;
    padding: 0.9rem 1rem 0.2rem;
    font-size: 1.05em;
}
.kahunacart-subcategory-count {
    display: block;
    padding: 0 1rem 0.95rem;
    font-size: 0.85em;
    color: var(--kc-subtle);
}

/* ── category header image ──────────────────────────────────────────────── */
.kahunacart-category-media {
    margin: 0 0 1.25rem;
    border-radius: var(--kc-radius-lg);
    overflow: hidden;
}
.kahunacart-category-media img {
    display: block;
    width: 100%;
    height: auto;
}

/* ── tags ───────────────────────────────────────────────────────────────────
   Deliberately not styled as the category chips are. A tag links into a
   filtered listing, not to a place in the catalog, and making the two look
   identical would be telling customers something untrue about where a click
   goes. */
.kahunacart-tags {
    margin-block: 1.75rem;
}
.kahunacart-tags-title {
    font-size: 0.8em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--kc-subtle);
    margin: 0 0 0.5rem;
}
.kahunacart-tag-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    list-style: none;
    margin: 0;
    padding: 0;
}
.kahunacart-tag {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    border-radius: calc(var(--kc-radius) / 1.5);
    background: var(--kc-surface);
    color: var(--kc-muted);
    text-decoration: none;
    font-size: 0.85em;
}
.kahunacart-tag:hover {
    color: var(--kc-accent-ink);
    background: color-mix(in srgb, var(--kc-accent) 10%, transparent);
    text-decoration: none;
}

.kahunacart-listing-more {
    color: var(--kc-muted);
    font-size: 0.92em;
}

@media (max-width: 40rem) {
    /* A table is the one element on the page that cannot wrap, so five columns
       of product titles would push the whole document sideways. `display:
       block` turns the table element itself into the scroll port; the row
       groups inside it are consecutive, so the engine wraps them in a single
       anonymous table box that keeps header and body columns aligned and is
       free to be wider than the port. That box sizes to its content, which is
       why the treatment is confined to widths where the content is going to
       exceed the port anyway — at desktop width it would leave a table
       narrower than the column it sits in. */
    .kahunacart-cart-table,
    .kahunacart-account-orders,
    .kahunacart-order-items {
        display: block;
        max-inline-size: 100%;
        overflow-x: auto;
        overscroll-behavior-inline: contain;
    }
    .kahunacart-coupon input[type="text"] {
        width: 100%;
    }
    .kahunacart-coupon .button {
        flex: 1;
        justify-content: center;
    }
    .kahunacart-totals {
        min-width: 100%;
    }
    .kahunacart :where(fieldset) {
        padding-inline: 0.85rem;
    }
    .kahunacart-buy {
        align-items: stretch;
    }
    .kahunacart-buy .button {
        justify-content: center;
    }
    /* The invoice header is two blocks side by side at desktop width; on a
       phone the right-aligned meta column reads as a mistake. */
    .kahunacart-invoice-meta {
        text-align: start;
    }
    /* The sidebar stops being a sidebar. It stays *above* the listing rather
       than below it: a customer on a phone who has just tapped a department
       needs the subcategory links and the filters before the grid, not after
       fifty product cards. */
    .kahunacart-catalog-layout {
        grid-template-columns: minmax(0, 1fr);
        gap: 1.5rem;
    }
    .kahunacart-filter-options {
        max-height: 14em;
    }
}

/* ── motion preferences ─────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
    .kahunacart,
    .kahunacart *,
    .kahunacart *::before,
    .kahunacart *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }
}

/* The customer's picture on the credentials page. Round, because it is a
   portrait; sunk background so a transparent or slow image still reads as a
   frame rather than a hole. */
.kahunacart-account-avatar {
    margin: 0 0 1.25rem;
}

.kahunacart-account-avatar img {
    display: block;
    width: 5rem;
    height: 5rem;
    border-radius: 50%;
    object-fit: cover;
    background: var(--kahunacart-surface-sunk, rgba(127, 127, 127, .12));
}
