/* 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);
}

/* ── sectioned listings ─────────────────────────────────────────────────────
   A shop page that a store has declared `catalog.sections` for: a hero, some
   named rows, and the rest. Three layouts, one wrapper class, and the column
   count arrives as a custom property on that wrapper rather than as a class,
   so a merchant changing a number in config does not need a stylesheet to have
   anticipated it.

   Every grid below is `repeat(auto-fill, minmax(..., 1fr))` bounded by the
   configured count rather than a bare `repeat(var(--kc-section-columns), 1fr)`.
   That is the whole responsive story: the track floor is a real width, so the
   browser drops to fewer columns when the container cannot hold them, and the
   ceiling means a two-column section stays two columns on a wide screen
   instead of spreading to five. No media query decides any of it, which
   matters because these sections also render inside the catalog column beside
   a sidebar, where the viewport is the wrong number to be reading.

   `auto-fill` rather than `auto-fit`, which is the same choice the flat
   product grid above makes and matters most in the commonest hero of all: one
   product. `auto-fit` collapses the empty track, and a single hero card then
   stretches across the width of two with its text ending halfway. */
.kahunacart-section {
    /* Set on the element itself from the section's config, through the public
       hook rather than the internal token, so what a merchant typed into the
       Columns field and what a theme could override are the same property. The
       3 is only reached by a theme that renders sections itself out of
       kahunacart_sections() and writes no attribute. */
    --kc-section-columns: var(--kahunacart-section-columns, 3);
    margin-block-end: 2.5rem;
}
.kahunacart-section:last-child {
    margin-block-end: 0;
}
.kahunacart-section-title {
    margin: 0 0 1rem;
    font-size: 1.25rem;
    line-height: 1.25;
    /* A rule under the heading, the width of the section, so a row of products
       reads as belonging to the words above it rather than floating between
       two headings. */
    padding-block-end: 0.5rem;
    border-block-end: 1px solid var(--kc-border);
}

/* The hero. One large card per product, picture beside the text, at most two
   across because the third one is a grid. */
.kahunacart-hero-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
    gap: 1.25rem;
    list-style: none;
    margin: 0;
    padding: 0;
    max-inline-size: calc(var(--kc-section-columns) * 34rem);
}
.kahunacart-hero-card {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 1.25rem;
    padding: 1.25rem;
    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);
}
.kahunacart-hero-card:hover {
    border-color: var(--kc-border-strong);
}

/* A hero holding one product is the store's headline and takes the whole row,
   with a larger picture and title. The two-across ceiling is for heroes with
   two products. */
.kahunacart-hero-list:has(> .kahunacart-hero-card:only-child) {
    grid-template-columns: 1fr;
    max-inline-size: none;
}
.kahunacart-hero-card:only-child { padding: 1.75rem; gap: 2rem; }
.kahunacart-hero-card:only-child .kahunacart-hero-media { flex: 1 1 18rem; max-inline-size: 24rem; }
.kahunacart-hero-card:only-child .kahunacart-hero-body { flex: 2 1 22rem; }
.kahunacart-hero-card:only-child .kahunacart-hero-title { font-size: clamp(1.6rem, 3vw, 2.25rem); }
/* The picture takes a third and stops shrinking at 9rem, at which width the
   row is about to wrap anyway and a thinner column would be a stripe. */
.kahunacart-hero-media {
    flex: 1 1 9rem;
    min-inline-size: 9rem;
    max-inline-size: 16rem;
    display: block;
    overflow: hidden;
    border-radius: var(--kc-radius);
    background: var(--kc-surface);
}
.kahunacart-hero-media img {
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: var(--kc-card-ratio);
    object-fit: cover;
}
.kahunacart-hero-body {
    flex: 3 1 14rem;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.55rem;
}
.kahunacart-hero-title {
    margin: 0;
    font-size: 1.35rem;
    line-height: 1.2;
}
.kahunacart-hero-title a {
    color: inherit;
    text-decoration: none;
}
.kahunacart-hero-title a:hover {
    color: var(--kc-accent-ink);
}
.kahunacart-hero-summary {
    margin: 0;
    color: var(--kc-muted);
    max-width: 55ch;
}
.kahunacart-hero-card .kahunacart-price {
    margin: 0;
    font-size: 1.1rem;
    color: var(--kc-accent-ink);
}
.kahunacart-hero-card .kahunacart-sale-badge {
    margin: 0;
}
/* The action sits on the baseline of the card whatever length the summary
   came out, so two heroes side by side do not have their buttons at two
   different heights. */
.kahunacart-hero-action {
    margin-block-start: auto;
}

/* The grid layout. The cards are the listing's own, so all this does is bound
   the track count to what the section asked for.

   `:where()` keeps the whole selector at the weight of a single class, which
   is the same weight the unsectioned `.kahunacart-product-grid` rule above
   carries. A theme that has taken the grid over has one rule for it, written
   against its own class, and that rule has to keep winning inside a section
   too, or a store would get the theme's cards in the theme's columns on a flat
   listing and in the plugin's columns as soon as sections were switched on. */
.kahunacart-section--grid :where(.kahunacart-product-grid) {
    grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
    max-inline-size: calc(var(--kc-section-columns) * 22rem);
    margin-block-end: 0;
}

/* The compact layout. Rows rather than cards: a thumbnail, a title, a price,
   and the whole row is the link. */
.kahunacart-compact-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr));
    gap: 0.4rem 1.25rem;
    list-style: none;
    margin: 0;
    padding: 0;
    max-inline-size: calc(var(--kc-section-columns) * 26rem);
}
.kahunacart-compact-link {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.5rem 0.65rem;
    border-radius: var(--kc-radius);
    color: inherit;
    text-decoration: none;
    transition: background var(--kc-dur) var(--kc-ease);
}
.kahunacart-compact-link:hover {
    background: var(--kc-surface);
}
.kahunacart-compact-link:hover .kahunacart-compact-title {
    color: var(--kc-accent-ink);
}
.kahunacart-compact-media {
    flex: none;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    overflow: hidden;
    border-radius: var(--kc-radius);
    background: var(--kc-surface);
}
.kahunacart-compact-media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
/* The title takes the slack and the price is pushed to the end, so a column of
   rows has its prices aligned however long the names are. */
.kahunacart-compact-title {
    flex: 1 1 auto;
    min-inline-size: 0;
}
.kahunacart-compact-price {
    flex: none;
    color: var(--kc-accent-ink);
    font-variant-numeric: tabular-nums;
}
.kahunacart-compact-price .kahunacart-compare-price {
    margin-inline-end: 0.35rem;
    color: var(--kc-muted);
}

/* ── 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;
}
/* The thumbnail the large image is currently on. Written by the option picker
   when a selection swaps the gallery, and never on a page with no script: the
   thumbnails are in-page links first and a state indicator second, so nothing
   depends on this rule being there. */
.kahunacart-gallery-thumbs a.is-active {
    border-color: var(--kc-accent);
    box-shadow: inset 0 0 0 1px var(--kc-accent);
}

/* ── product media ──────────────────────────────────────────────────────────
   A gallery slide that is not a picture: a clip, or a placeholder standing in
   for a third-party player. Both fill the same box a picture would, so the
   carousel does not jump as a shopper scrolls past one, and both keep the
   picture's rounded corner and surface so a mixed gallery reads as one strip.

   The embed placeholder is a button on purpose. Pressing it does something on
   this page — kahunacart-media.js swaps it for the iframe — rather than going
   somewhere, and until somebody presses it there is no iframe, no third-party
   script and no request to the video host in the page at all. */
.kahunacart-gallery-slide video,
.kahunacart-video {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface-2);
}
.kahunacart-embed {
    position: relative;
    display: grid;
    place-items: center;
    inline-size: 100%;
    min-block-size: 12rem;
    padding: 0;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface-2);
    color: var(--kc-fg);
    cursor: pointer;
    overflow: hidden;
    transition: border-color var(--kc-dur) var(--kc-ease);
}
.kahunacart-embed:hover,
.kahunacart-embed:focus-visible {
    border-color: var(--kc-accent);
}
.kahunacart-embed img {
    grid-area: 1 / 1;
    display: block;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
}
/* The play glyph sits over the poster; on a placeholder with no poster it is
   the only thing in the box, and the grid keeps it centred either way. */
.kahunacart-embed-play {
    grid-area: 1 / 1;
    z-index: 1;
    display: grid;
    place-items: center;
    inline-size: 3.5rem;
    block-size: 3.5rem;
    border-radius: 50%;
    background: color-mix(in srgb, var(--kc-bg) 82%, transparent);
    box-shadow: 0 0 0 1px var(--kc-border-strong);
    font-size: 1.4rem;
    line-height: 1;
}
.kahunacart-embed-note {
    position: absolute;
    inset-block-end: 0;
    inset-inline: 0;
    padding: 0.4rem 0.6rem;
    background: color-mix(in srgb, var(--kc-bg) 82%, transparent);
    color: var(--kc-muted);
    font-size: 0.82rem;
    text-align: start;
}
.kahunacart-embed-frame {
    inline-size: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    border-radius: var(--kc-radius-lg);
}
/* A clip with no poster still needs something aimable in the thumbnail strip. */
.kahunacart-thumb-tile {
    display: grid;
    place-items: center;
    inline-size: 60px;
    block-size: 60px;
    background: var(--kc-surface-2);
    color: var(--kc-muted);
}

/* ── the Preview block ──────────────────────────────────────────────────────
   Public samples, under the description. Deliberately not styled like the
   downloads list in an account: a sample is something to press play on before
   buying, and a downloadable is something a purchase unlocks, so the two
   should not look like the same list in two places. */
.kahunacart-samples {
    margin-block: 1.5rem;
    padding: 1rem 1.1rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface);
}
.kahunacart-samples-title {
    margin: 0;
    font-size: 1.05rem;
}
.kahunacart-samples-note {
    margin-block: 0.35rem 0.9rem;
    color: var(--kc-muted);
    font-size: 0.88rem;
}
.kahunacart-sample-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    list-style: none;
    margin: 0;
    padding: 0;
}
.kahunacart-sample-label {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    margin-block: 0 0.4rem;
}
.kahunacart-sample-kind {
    font-weight: 600;
}
.kahunacart-sample-name {
    color: var(--kc-fg);
}
.kahunacart-sample-duration {
    color: var(--kc-muted);
    font-family: var(--kc-mono);
    font-size: 0.82rem;
}
.kahunacart-audio {
    inline-size: 100%;
}
/* Tall enough to read a first page in, and scrollable inside itself, so a
   sample chapter never pushes the buy button off the screen. */
.kahunacart-document {
    inline-size: 100%;
    block-size: min(70vh, 34rem);
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius);
    background: var(--kc-surface-2);
}
.kahunacart-sample-link {
    font-size: 0.9rem;
}

/* ── 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;
}

/* A value with a picture of its own. The thumbnail sits where the colour dot
   would, at the same size plus a little, and the label stays beside it rather
   than being replaced by it: a row of unlabelled photographs is a guessing
   game for anyone who cannot see the difference between two navies, and the
   `title` alone never reaches a keyboard or a screen reader. */
.kahunacart-option-image {
    padding-inline-start: 0.35rem;
}
.kahunacart-option-thumb {
    display: block;
    inline-size: 1.6rem;
    block-size: 1.6rem;
    object-fit: cover;
    border-radius: calc(var(--kc-radius) - 2px);
    background: var(--kc-surface);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--kc-fg) 12%, 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;
}

/* ── sales ──────────────────────────────────────────────────────────────────
   An automatic discount, on a card and in the buy form. Two elements and no
   layout of their own: the struck-through regular price is the same
   .kahunacart-compare-price the compare-at price has always used, and the badge
   is a small pill beside it. Nothing here is positioned absolutely over the
   picture — a corner flash is a design decision a theme should make, and one
   the plugin cannot make without knowing what the card looks like.

   --kc-danger is the accent because a sale is the one thing on a storefront
   that is meant to feel urgent, and it is the only token in the chain that
   already carries that meaning. A theme redefines --kahunacart-danger and the
   badge follows. */
.kahunacart-price.is-on-sale {
    display: inline-flex;
    align-items: baseline;
    gap: 0.45rem;
    flex-wrap: wrap;
    color: var(--kc-danger);
}
.kahunacart-sale-badge {
    display: inline-flex;
    align-items: center;
    align-self: center;
    padding: 0.1rem 0.5rem;
    border-radius: var(--kc-radius);
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.5;
    color: var(--kc-danger);
    background: color-mix(in srgb, var(--kc-danger) 12%, transparent);
}
.kahunacart-sale-badge[hidden] {
    display: none;
}
/* On a card the badge is its own line under the price, so a long campaign name
   does not push the price out of the card's width. */
.kahunacart-product-card .kahunacart-sale-badge {
    align-self: flex-start;
}

/* 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;
}

/* What a plugin needs said beside a line before it is bought — a free trial,
   most of all, where the price in the row is not what the card is charged
   today. Quiet and small: it is a qualification of the price above it, not a
   second thing to read. See CartLineNotes. */
.kahunacart-line-note {
    display: block;
    margin-block-start: 0.2rem;
    color: var(--kc-muted);
    font-size: 0.82em;
    line-height: 1.35;
    white-space: normal;
}
.kahunacart-line-notes {
    margin-block: 0.75rem 0;
    padding-inline-start: 1.1rem;
    color: var(--kc-muted);
    font-size: 0.88em;
}

/* 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 saved-address picker above each address section. Its label is a sibling
   of the select rather than wrapping it, since the select needs an id a theme
   can point its own label at, so the stacking rule above does not reach it. */
.kahunacart-address-picker {
    display: grid;
    gap: 0.3rem;
    margin-block-end: 1.1rem;
}
.kahunacart-address-picker > label {
    color: var(--kc-muted);
    font-size: 0.9em;
}
.kahunacart-address-picker > select {
    color: var(--kc-fg);
    font-size: 1rem;
}

/* 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);
}

/* ── expired order link ─────────────────────────────────────────────────────
   The same centred single-message shape, but neutral rather than alarming:
   an order link running out is the system working, and the page's real job is
   the button under it. So the message reads as a notice and the form gets the
   emphasis. */
.kahunacart-expired {
    max-width: 40rem;
    margin-inline: auto;
    text-align: center;
    padding-block: 2rem;
}
.kahunacart-expired-message {
    padding: 1rem 1.15rem;
    border: 1px solid var(--kc-border-strong);
    border-radius: var(--kc-radius-lg);
    background: var(--kc-surface);
}
.kahunacart-link-request-form {
    margin-block: 1.5rem;
}
.kahunacart-link-request-help {
    color: var(--kc-muted);
    font-size: 0.92em;
    margin-block-end: 0.85rem;
}
.kahunacart-expired-links {
    display: flex;
    gap: 1.25rem;
    justify-content: center;
    flex-wrap: wrap;
    font-size: 0.95em;
}

/* ── 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);
}
/* A branch that holds nothing under the filters currently on. Still a link,
   because the tree is how somebody gets out of a corner they have filtered
   themselves into, but it says out loud that going there shows nothing. */
   The row rather than the whole item: a dead branch's children are dead too
   and carry the class themselves, and two nested opacities multiply into
   something nobody can read. */
.kahunacart-cat-item.is-unavailable > .kahunacart-cat-link,
.kahunacart-cat-item.is-unavailable > .kahunacart-cat-count,
.kahunacart-cat-item.is-unavailable > .kahunacart-cat-branch > .kahunacart-cat-summary {
    opacity: 0.45;
}

/* 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);
}

/* A value nothing reachable answers. The count beside it reads 0, the input is
   `disabled` so it cannot be ticked or focused, and this is what says so on
   screen. Dimmed rather than removed on purpose: the panel keeps its length
   and its order, so ticking one box does not make every other box jump under
   the cursor. `catalog.facets.empty: hide` is the setting for a store that
   would rather they went; nothing then reaches this rule.
   Never applied to a ticked value at any count, because a filter you cannot
   take off is worse than one that reads 0. */
.kahunacart-filter-option.is-disabled,
.kahunacart-filter-range.is-disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
.kahunacart-filter-option.is-disabled input {
    cursor: not-allowed;
}
/* Under a facet whose every value is out of reach. The heading stays, so the
   shopper can see the store has the facet and that their own filters are what
   emptied it. */
.kahunacart-filter-none {
    margin: 0;
    font-size: 0.9em;
    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);
}

/* The rest of a facet the cap has shortened. A <details>, so opening it costs
   no request and no script, and the checkboxes inside it are in the same form
   as the ones above: ticking one submits exactly as ticking a visible one
   does. The list keeps its own scroll rather than pushing the panel down. */
.kahunacart-filter-more {
    margin-block-start: 0.35rem;
}
.kahunacart-filter-more-toggle {
    font-size: 0.9em;
    color: var(--kc-muted);
    cursor: pointer;
}
.kahunacart-filter-more-toggle:hover {
    color: var(--kc-fg);
}
.kahunacart-filter-more > .kahunacart-filter-options {
    margin-block-start: 0.35rem;
}

/* `hidden` means gone, including on a button this stylesheet has given a
   `display` of its own. The browser's `[hidden] { display: none }` sits at
   (0,1,0) and loses to `.kahunacart :where(.button)`, which is how a filter
   panel that applies itself ends up still painting an Apply button. Scoped to
   the wrapper so it says nothing about the rest of the page. */
.kahunacart [hidden] {
    display: none;
}

/* ── the results region, and the chip row above it ──────────────────────────
   The element a filter change replaces. It is a plain block on purpose: the
   two-column wrapper stays in the page template, so this region swaps whether
   or not there is a sidebar beside it. */
.kahunacart-catalog-results {
    /* The dim below is a fade rather than a spinner: a filtered listing comes
       back in a few tens of milliseconds on any catalog this plugin is built
       for, and a spinner that appears and vanishes inside one frame is a
       flicker. Opacity only, so nothing moves and nothing reflows. */
    transition: opacity var(--kc-dur) var(--kc-ease);
}
.kahunacart-catalog-results.is-loading {
    opacity: 0.55;
    /* The old results are on their way out; clicking one is a click on a
       product that may not be in the new set. */
    pointer-events: none;
}

/* What is narrowing this listing, said in words. A wrapped row of links, each
   of which removes one value, and Clear all at the end. Above the grid rather
   than in the sidebar, because a customer scrolling a phone has scrolled past
   the panel by the time they wonder what they asked for. */
.kahunacart-filter-chips {
    margin-block-end: 1rem;
}
.kahunacart-chip-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}
.kahunacart-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem 0.55rem;
    font-size: 0.85em;
    line-height: 1.6;
    color: var(--kc-fg);
    text-decoration: none;
    background: var(--kc-surface);
    border: 1px solid var(--kc-border);
    border-radius: 999px;
    transition: border-color var(--kc-dur) var(--kc-ease), color var(--kc-dur) var(--kc-ease);
}
.kahunacart-chip:hover,
.kahunacart-chip:focus-visible {
    color: var(--kc-accent-ink);
    border-color: var(--kc-border-strong);
    text-decoration: none;
}
/* The cross is inside the link rather than beside it, so the whole chip is the
   target: a 12px glyph is not a tap target on a phone. */
.kahunacart-chip-remove {
    font-size: 1.1em;
    line-height: 1;
    color: var(--kc-subtle);
}
.kahunacart-chip:hover .kahunacart-chip-remove,
.kahunacart-chip:focus-visible .kahunacart-chip-remove {
    color: inherit;
}
.kahunacart-chip-clear {
    font-size: 0.85em;
    color: var(--kc-muted);
    padding-inline-start: 0.25rem;
}

/* Belt and braces for a theme that emits the catalog wrapper unconditionally:
   with `catalog.filters: off` and no category tree there is no aside to put in
   the first column, and a 15rem track holding nothing would push the grid off
   to one side. The shipped templates open the wrapper only when there is a
   sidebar, so on those this rule never matches. */
.kahunacart-catalog-layout:not(:has(.kahunacart-catalog-nav)) {
    grid-template-columns: minmax(0, 1fr);
}

/* ── 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;
}

/* ── attributes: the specification table ────────────────────────────────────
   A two-column table of name and value, because that is what it is: a spec
   table is genuinely tabular data and a <dl> would say the same thing less
   usefully to a screen reader.

   The label column is a fixed width rather than sized to its content, because
   a grouped spec table is one <table> per group: sizing to content gives each
   group its own column width, and the values step left and right down the page
   as the headings change. One width for every group lines them up.

   Every colour and radius comes from the same --kc-* chain the rest of this
   stylesheet uses, so a theme that has set --kahunacart-accent and friends
   gets the spec table in its own colours with no extra work. */
.kahunacart-attributes {
    margin-block: 1.75rem;
}
.kahunacart-attributes-title {
    font-size: 0.8em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--kc-subtle);
    margin: 0 0 0.5rem;
}
.kahunacart-specs {
    width: 100%;
    /* Fixed, so the width below is the column rather than a suggestion an
       unusually long label in one group can talk the engine out of. */
    table-layout: fixed;
    border-collapse: collapse;
    margin-block: 0 1rem;
    font-size: 0.95em;
}
.kahunacart-specs:last-child {
    margin-bottom: 0;
}
/* The group heading. A caption rather than a heading element above the table,
   so the association between the two survives without markup that repeats. */
.kahunacart-specs-group {
    caption-side: top;
    text-align: left;
    font-weight: 600;
    color: var(--kc-fg);
    padding-block: 0.5rem 0.35rem;
}
.kahunacart-spec > * {
    border-top: 1px solid var(--kc-border);
    padding: 0.5rem 0.75rem 0.5rem 0;
    vertical-align: baseline;
    text-align: left;
}
.kahunacart-spec:first-child > * {
    border-top: 0;
}
.kahunacart-spec-label {
    width: 12rem;
    font-weight: 500;
    color: var(--kc-muted);
    /* A label longer than the column is trimmed with an ellipsis rather than
       allowed to wrap: the label is the half that can be trusted to be short,
       and a merchant who writes an essay in one gets a tidy table and a hint
       to shorten it. The full text is in the DOM either way. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.kahunacart-spec-value {
    padding-right: 0;
    color: var(--kc-fg);
    /* Fixed layout will not widen a column for an unbreakable string, so a
       long URL or SKU breaks rather than spilling out of the table. */
    overflow-wrap: anywhere;
}
/* Markdown inside a cell: the paragraphs a description would get, without the
   leading and trailing margin that would push the row open. */
.kahunacart-spec-prose > :first-child {
    margin-top: 0;
}
.kahunacart-spec-prose > :last-child {
    margin-bottom: 0;
}
.kahunacart-swatch {
    display: inline-block;
    width: 0.9em;
    height: 0.9em;
    border-radius: 3px;
    border: 1px solid var(--kc-border-strong);
    vertical-align: -0.1em;
    margin-right: 0.4rem;
}
.kahunacart-spec-hex {
    font-family: var(--kc-mono);
    font-size: 0.9em;
    color: var(--kc-muted);
}
/* A value that is also a place to go. Underlined on hover rather than always,
   because a spec table where every second cell is blue and underlined reads as
   a list of links rather than as a table of facts; the colour is what says it
   is one, and the underline confirms it under the pointer. */
.kahunacart-attribute-link {
    color: inherit;
    text-decoration: none;
    border-block-end: 1px dotted var(--kc-border-strong);
}
.kahunacart-attribute-link:hover,
.kahunacart-attribute-link:focus-visible {
    color: var(--kc-accent);
    border-block-end-color: currentColor;
}
/* The colour swatch links as one piece with its hex, so the whole thing is the
   target rather than six characters of monospace. */
.kahunacart-attribute-link .kahunacart-spec-hex {
    color: inherit;
}

@media (max-width: 30rem) {
    /* A fixed 12rem is most of a phone's width, so the column becomes a share
       of the table instead, and the label wraps rather than being trimmed:
       there is no room to spare and two lines beat an ellipsis. */
    .kahunacart-spec-label {
        width: 40%;
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }
}

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

/* ── pagination ─────────────────────────────────────────────────────────────

   The pager under a listing. Anchors in a list, because that is what it is
   with scripting off; the filter script only saves the page load.

   The current page is a <span> rather than a link, so it is styled by class
   rather than by :not(a) — a theme overriding the partial keeps whatever
   element it likes and the rule still finds it. */
.kahunacart-pagination {
    margin-block-start: 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: center;
}
.kahunacart-pagination-summary {
    margin: 0;
    color: var(--kc-muted);
    font-size: 0.92em;
}
.kahunacart-pagination-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
}
:where(.kahunacart-pagination-list) a,
:where(.kahunacart-pagination-list) span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-inline-size: 2.25rem;
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius);
    /* Tabular figures so a row of page numbers does not jitter in width as the
       digits change, which is the same reason every money column uses them. */
    font-variant-numeric: tabular-nums;
    line-height: 1;
    text-decoration: none;
    color: inherit;
}
:where(.kahunacart-pagination-list) a:hover {
    border-color: var(--kc-border-strong);
    background: var(--kc-surface);
}
/* The page being read: filled, and not a link. */
:where(.kahunacart-pagination-current) span {
    border-color: var(--kc-accent-strong);
    background: var(--kc-accent-strong);
    color: var(--kc-on-accent-strong);
    font-weight: 600;
}
/* Each end of the pager keeps its Previous/Next word in place when there is
   nowhere to go, so the row does not shuffle sideways between pages. */
:where(.kahunacart-pagination-prev, .kahunacart-pagination-next) span {
    border-color: transparent;
    color: var(--kc-subtle);
}
.kahunacart-pagination-gap {
    color: var(--kc-subtle);
    padding-inline: 0.2rem;
}

/* ── sale tiers ─────────────────────────────────────────────────────────────

   A sale's quantity ladder, drawn under the buy button, in the cart, and as a
   one-line badge on a card. */
.kahunacart-sale-tiers {
    margin-block-start: 0.75rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--kc-border);
    border-radius: var(--kc-radius);
    background: var(--kc-surface);
}
.kahunacart-sale-tiers-heading {
    margin: 0 0 0.35rem;
    font-size: 0.85rem;
    font-weight: 600;
}
.kahunacart-sale-tier-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 0.75rem;
    font-size: 0.9rem;
}
/* The rung in force. `aria-current` carries the same fact to a screen reader,
   so this is emphasis rather than the only signal. */
.kahunacart-sale-tier.is-current {
    font-weight: 600;
    color: var(--kc-accent-ink);
}
.kahunacart-sale-tiers-basis {
    margin: 0.35rem 0 0;
    color: var(--kc-subtle);
    font-size: 0.8rem;
}
/* The card's teaser, painted like the sale badge beside it because it is doing
   the badge's job on a product that has no lower price to show yet. */
.kahunacart-sale-tier-badge {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    margin: 0.25rem 0 0;
    padding: 0.1rem 0.5rem;
    border-radius: var(--kc-radius);
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.5;
    color: var(--kc-accent-ink);
    background: color-mix(in srgb, var(--kc-accent) 12%, transparent);
}
.kahunacart-sale-tier-state {
    margin-block: 1rem;
}
.kahunacart-sale-tier-current {
    margin: 0;
    font-weight: 600;
}
.kahunacart-sale-tier-next {
    margin: 0.2rem 0 0;
    color: var(--kc-accent-ink);
    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. */
/* Picture beside name and address, one row, above the form. */
.kahunacart-account-profile-header {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin: 0 0 1.5rem;
}
.kahunacart-account-profile-header .kahunacart-account-identity { margin: 0; }

.kahunacart-account-avatar {
    margin: 0;
    flex: none;
}

.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));
}
