/*
    Core theme — base rules, the layout shell, and the ten patterns.

    Source: designs/design_handoff_core_theme/README.md, sections "Design tokens"
    and "Patterns to build once and reuse".

    Two rules hold this file together, because every other theme falls back to
    the core templates:

    1. Every value comes from a token in tokens.css. No hex value stands here.
    2. Every class is block, element, modifier: `panel`, `panel__head`,
       `btn--primary`. A template carries a class name, never a style.

    The page blocks of each screen come in the step that rebuilds that screen.
    This file holds what all of them share.
*/

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    background: var(--surface-page);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--fs-body-lg);
    line-height: var(--lh-body);
    letter-spacing: var(--ls-body);
    -webkit-font-smoothing: antialiased;
}

/* The font family must stand here. The Vite bundle carries
   `h2 { font-family: var(--font-h2) }`. Until S29 that variable held the serif
   of the old look, and this line was the only thing that kept a heading in the
   new font. S29 removed the transitional partial, thus `--font-h2` has no value
   any more and the rule of the bundle does nothing. This line stays, because it
   says which font a heading has. It does not depend on a rule somewhere else. */
h1,
h2,
h3,
h4 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    letter-spacing: -.03em;
    line-height: 1;
    text-wrap: balance;
}

h1 {
    font-size: clamp(30px, 3.8vw, 42px);
}

h2 {
    font-size: clamp(22px, 2.6vw, 34px);
    letter-spacing: var(--ls-h2);
    line-height: var(--lh-heading);
}

h3 {
    font-size: var(--fs-h3);
    letter-spacing: var(--ls-h3);
}

p {
    margin: 0;
    text-wrap: pretty;
}

/* The link colour of the PAGES, not of the design-system base. The base file
   `_ds/.../tokens/base.css` gives a link the accent colour, and then all 22
   `.dc.html` pages override it with `a { color: var(--ink) }`. The pages are the
   design, thus the page rule stands here. A link that must carry the accent
   says so with a class. */
a {
    color: var(--text-heading);
    text-decoration: none;
    transition: color var(--ease);
}

a:hover {
    color: var(--accent-deep);
}

/* The utility build draws a line under every anchor and under every label on
   hover (`tailwind.config.js`, the `addBase` block). The design has no such
   line: a link shows its state with its colour, with a bar under the header,
   or with a background. That layer loads before this file, thus one rule here
   takes the line off again.

   A component that does want a line puts it back, and each of those rules
   stands lower in this file: `.richtext a`, `.check__text a`, and
   `.field__hintlink`, which carries a `:hover` of its own because a class
   alone loses against `a:hover`. */
a:hover,
label:hover {
    text-decoration: none;
}

.band--ink a {
    color: var(--accent-on-dark);
}

.band--ink a:hover {
    color: var(--text-on-dark);
}

img,
svg,
video {
    max-width: 100%;
    height: auto;
}

/* The icons of the design are drawn with a line, thus an icon without a `fill`
   of its own gets none. The `:not([fill])` matters: a solid icon says
   `fill="currentColor"` in its own tag, and that is a presentation attribute,
   which every CSS rule beats. Without the guard the cross of a filter chip, the
   arrow of the sort button and the stars of a rating stand there invisible. */
svg:not([fill]) {
    fill: none;
}

svg {
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Read by a screen reader, never drawn. The name is the one that every
   framework uses, and the core theme must stand without the bundle of the
   application, thus the rule stands here as well. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

button {
    font: inherit;
    color: inherit;
}

/* A hairline plus a soft ring. Never a coloured glow. */
:focus-visible {
    outline: none;
    border-color: rgba(17, 17, 15, .5);
    box-shadow: var(--ring-focus);
}

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    *,
    *::before,
    *::after {
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
    }
}

/* --------------------------------------------------------------------------
   Layout shell
   -------------------------------------------------------------------------- */

/* `.shell` is the html body, and its rule stands with the page shell further
   down this file. A second `.shell` stood here that capped the width at 1200
   pixels and added the gutter. Two rules with one name both hit `<body>`, thus
   the body itself became 1200 pixels wide with 64 pixels of padding. The site
   header and the site footer are children of the body, so their band and their
   line stopped at that edge: Jonathan photographed a header that ends 97
   pixels from the left of a 1280 window (shopper#219 cmt 18195).

   A band is full width and its own `__inner` caps the content. That is how
   `.siteheader__inner`, `.sitefooter__inner` and `.shell__main` already work,
   thus the width rule needs no second home and this one is gone.

   The one element that had no `__inner` of its own was the header message.
   It has `.headermsg` now, below. */

/* A band is full width. The `__inner` inside it holds the content. */
.band {
    padding-block: var(--band-pad);
}

.band--tight {
    padding-block: var(--block-gap);
}

.band--ink {
    background: var(--surface-dark);
    color: var(--onInk-text);
}

.band--ink a {
    color: var(--r400);
}

.band--ink a:hover {
    color: var(--paper);
}

.band--ink .kicker {
    color: var(--onInk-muted);
}

.band--paper {
    background: var(--surface-raised);
}

/* The vertical rhythm inside one page. */
.page > * + * {
    margin-top: var(--block-gap);
}

.page__section + .page__section {
    margin-top: var(--section-gap);
}

.skip-link {
    position: absolute;
    left: -9999px;
}

.skip-link:focus {
    left: var(--sp-5);
    top: var(--sp-5);
    z-index: var(--z-skip);
    padding: var(--sp-3) var(--sp-5);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r1);
}

/* --------------------------------------------------------------------------
   Text helpers that the design uses on nearly every screen
   -------------------------------------------------------------------------- */

/* Mono kicker: 10 to 11px, uppercase, wide tracking. Never body copy. */
.kicker {
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    font-weight: var(--fw-regular);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.kicker--tight {
    letter-spacing: var(--ls-mono-tight);
}

.kicker--small {
    font-size: var(--fs-mono-sm);
}

/* The wordmark: Fredoka 600, lowercase, and nothing else uses this font. */
.wordmark {
    font-family: var(--font-wordmark);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--ls-word);
    line-height: 1;
    text-transform: lowercase;
}

.money {
    font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   Pattern 1 — hairline gap grid (the signature move)

   The 1px gaps of the wrapper read as rules, because each cell paints over the
   line colour of the wrapper.
   -------------------------------------------------------------------------- */

.hairgrid {
    display: grid;
    gap: 1px;
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: var(--r3);
    overflow: hidden;
}

.hairgrid__cell {
    background: var(--surface-card);
    padding: var(--sp-6);
}

.hairgrid__cell--roomy {
    padding: var(--sp-9);
}

.hairgrid__cell--paper {
    background: var(--surface-raised);
}

/* --------------------------------------------------------------------------
   Pattern 2 — panel
   -------------------------------------------------------------------------- */

.panel {
    border: 1px solid var(--line);
    border-radius: var(--r3);
    background: var(--surface-raised);
}

.panel__head {
    padding: 15px var(--sp-6);
    border-bottom: 1px solid var(--line);
}

.panel__label {
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.panel__body {
    padding: var(--sp-6);
}

/* A panel that a list fills to its edges. The corner of the last row follows
   the corner of the panel, and the background of a row on hover stops there.
   Only this modifier clips, thus a panel that holds a menu or a focus ring
   keeps it. */
.panel--list {
    overflow: hidden;
}

/* A head that carries a link on the right, as the card of the recent orders
   on "Dashboard.dc.html" does. */
.panel__head--split {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
}

.panel__link {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--r600);
}

.panel__link:hover {
    color: var(--r500);
}

/* --------------------------------------------------------------------------
   Pattern 3 — section head
   -------------------------------------------------------------------------- */

.section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sp-4);
    padding-bottom: 13px;
    border-bottom: 1px solid var(--ink20);
}

.section-head__title {
    font-weight: var(--fw-medium);
    letter-spacing: -.03em;
}

.section-head__link {
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--r600);
}

.section-head__link:hover .section-head__arrow {
    transform: var(--arrow-nudge);
}

.section-head__arrow {
    display: inline-block;
    transition: transform var(--ease);
}

/* --------------------------------------------------------------------------
   Pattern 4 — buttons
   -------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    height: var(--h-control);
    padding-inline: var(--sp-6);
    border: 1px solid transparent;
    border-radius: var(--r1);
    background: none;
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    line-height: 1;
    text-align: center;
    cursor: pointer;
    transition: background var(--ease), color var(--ease), border-color var(--ease), transform var(--ease);
}

.btn:active {
    transform: var(--press-shift);
}

.btn:hover .btn__arrow {
    transform: var(--arrow-nudge);
}

.btn__arrow {
    display: inline-block;
    transition: transform var(--ease);
}

.btn--compact {
    height: var(--h-control-compact);
    padding-inline: var(--sp-5);
}

.btn--table {
    height: var(--h-control-table);
    padding-inline: var(--sp-4);
    font-size: var(--fs-caption);
}

.btn--block {
    width: 100%;
}

.btn--primary {
    background: var(--r500);
    color: var(--paper);
}

.btn--primary:hover {
    background: var(--r600);
    color: var(--paper);
}

.btn--secondary {
    border-color: var(--ink20);
    color: var(--ink);
}

.btn--secondary:hover {
    background: var(--surface-raised);
    color: var(--ink);
}

/* A button in a tight place: a form control beside a field, never a call to
   action of a page. */
.btn--small {
    height: 32px;
    padding: 0 var(--sp-4);
    font-size: var(--fs-body-sm);
}

/* On a paper panel the secondary button fills with bone, not with paper. */
.panel .btn--secondary:hover,
.band--paper .btn--secondary:hover {
    background: var(--bone);
}

.btn--quiet {
    padding-inline: var(--sp-3);
    color: var(--ink60);
}

.btn--quiet:hover {
    color: var(--ink);
}

.btn--on-ink {
    background: var(--paper);
    color: var(--ink);
}

.btn--on-ink:hover {
    background: var(--bone);
    color: var(--ink);
}

.btn--on-ink-hairline {
    border-color: var(--onInk-lineStrong);
    color: var(--onInk-dim);
}

.btn--on-ink-hairline:hover {
    color: var(--onInk-text);
}

.btn--danger {
    background: var(--danger);
    color: var(--paper);
}

/* A heavy action that must not invite the click. `Cancel order` stands beside
   `Download invoice` on the order detail: it keeps a quiet edge, and it shows
   the danger colour only on a hover. */
.btn--hairline-danger {
    border-color: var(--line);
    color: var(--ink60);
}

.btn--hairline-danger:hover {
    border-color: var(--danger-line);
    color: var(--danger);
}

/* An `a` element has no `disabled` attribute, thus the modifier must stop the
   pointer itself. The checkout button of the cart page is such a link. */
.btn[disabled],
.btn--disabled {
    background: var(--surface-disabled);
    border-color: transparent;
    color: var(--ink40);
    cursor: not-allowed;
    pointer-events: none;
    transform: none;
}

/* --------------------------------------------------------------------------
   Pattern 5 — field, check box, radio
   -------------------------------------------------------------------------- */

.field {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.field__label {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.field__input {
    width: 100%;
    height: var(--h-field);
    padding: 0 var(--sp-4);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--r1);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    transition: border-color var(--ease), box-shadow var(--ease);
}

/* Inside a paper panel the input takes the bone fill, so it stays readable. */
.panel .field__input,
.cototals .field__input,
.band--paper .field__input {
    background: var(--bone);
}

.field__input:focus {
    outline: none;
    border-color: rgba(17, 17, 15, .5);
    box-shadow: var(--ring-focus);
}

.field__input::placeholder {
    color: var(--ink40);
}

.field__input--area {
    height: auto;
    min-height: 120px;
    padding: var(--sp-4);
    line-height: var(--lh-body);
    resize: vertical;
}

.field__error {
    font-size: var(--fs-caption);
    color: var(--danger);
}

.field--error .field__input {
    border-color: var(--danger);
}

.field--error .field__input:focus {
    border-color: var(--danger);
    box-shadow: var(--ring-error);
}

.field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-5);
}

/* Two rows of an address are not half and half. A street name needs the room,
   a house number does not; a city needs the room, a postal code does not. */
.field-row--street {
    grid-template-columns: 2fr 1fr;
}

.field-row--postal {
    grid-template-columns: 1fr 2fr;
}

.check {
    display: inline-flex;
    align-items: flex-start;
    gap: var(--sp-3);
    font-size: var(--fs-body-sm);
    cursor: pointer;
}

/* The style sheet of the application gives each `label` an underline when the
   pointer is above it. That is correct for a link and wrong for the text of a
   checkbox, thus this block takes the underline away again. */
.check:hover {
    text-decoration: none;
}

.check__box {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin: 0;
    appearance: none;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--r1);
    cursor: pointer;
    transition: background var(--ease), border-color var(--ease);
}

.check__box:checked {
    background: var(--r500) no-repeat center;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18' fill='none' stroke='%23FAF8F3' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 9.4l3.2 3.2L14 5.8'/%3E%3C/svg%3E");
    border-color: var(--r500);
}

.option {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 13px 14px;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--r2);
    cursor: pointer;
    transition: background var(--ease), border-color var(--ease);
}

.option__radio {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    margin: 0;
    appearance: none;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: 50%;
    cursor: pointer;
    transition: background var(--ease), border-color var(--ease), box-shadow var(--ease);
}

.option__radio:checked {
    background: var(--r500);
    border-color: var(--r500);
    box-shadow: inset 0 0 0 3px var(--paper);
}

/* The dot of a chosen option is a box-shadow, and so is the focus ring. Without
   this rule the checked state wins and a visitor who moves with the keyboard
   sees no ring on the row that is on. */
.option__radio:checked:focus-visible {
    box-shadow: inset 0 0 0 3px var(--paper), var(--ring-focus);
}

.option--selected {
    background: var(--accentSoft);
    border-color: var(--r500);
}

/* The same state, but the control gives it. The option holds a real radio
   button, thus the browser knows which row is on and no script writes a class.
   The checkout builds its shipping list again after every change of the cart,
   so a class would have to travel through that script. This rule does not. */
.option:has(.option__radio:checked) {
    background: var(--accentSoft);
    border-color: var(--r500);
}

/* An option with a second line below the name, for example a price. */
.option__text {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.option__title {
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
}

.option__meta {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    color: var(--ink40);
}

/* A mark at the end of the row, for example the logo of a payment method. */
.option__icon {
    margin-left: auto;
    flex: none;
    width: auto;
    height: 26px;
}

/* The nine partials in `resources/views/themes/core/shared/form/` build on the
   pattern above. What follows is what those partials need on top of it. */

/* The marker after the text of a required label. */
.field__req {
    margin-left: 3px;
    color: var(--danger);
}

/* The help balloon. `public/js/form-fields.js` shows and hides it with the
   Tailwind classes `invisible`, `visible`, `opacity-0` and `opacity-100`. That
   script is one file for every theme, thus those four keep their name and this
   rule only places the balloon and gives it its look. */
.field__helpwrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    margin-left: var(--sp-2);
}

.field__help {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    padding: 0;
    background: transparent;
    border: 0;
    cursor: pointer;
    color: var(--text-muted);
    transition: color var(--ease);
}

.field__help:hover {
    color: var(--text-heading);
}

.field__help svg {
    width: 16px;
    height: 16px;
}

.field__tip {
    position: absolute;
    left: 0;
    top: 22px;
    z-index: var(--z-tip);
    min-width: 200px;
    padding: var(--sp-2) var(--sp-3);
    background: var(--surface-dark);
    color: var(--text-on-dark);
    border-radius: var(--r1);
    font-family: var(--font-body);
    font-size: var(--fs-caption);
    letter-spacing: 0;
    text-transform: none;
    line-height: 1.45;
    pointer-events: none;
    transition: opacity var(--ease);
}

/* A line under a control that explains it. It is not an error. */
.field__hint {
    margin: 0;
    font-size: var(--fs-caption);
    color: var(--text-secondary);
}

/* The same line, but it warns. The checkout draws it below the email address
   when the address already has an account. */
.field__hint--warn {
    color: var(--status-warn);
}

.field__hintlink,
.field__hintlink:hover {
    font-weight: var(--fw-medium);
    text-decoration: underline;
}

/* A value that the page shows but does not let the visitor change. The
   checkout draws the email address of a user who is logged in this way. */
.field__static {
    width: 100%;
    min-height: var(--h-field);
    padding: 10px var(--sp-4);
    margin: 0;
    background: var(--surface-disabled);
    border: 1px solid var(--line);
    border-radius: var(--r1);
    color: var(--ink80);
    font-size: var(--fs-body);
}

/* `public/js/shopper-form.js` and the script of the checkout page write these
   two class names on a control. They come from the bundle of the application,
   thus they exist, but `.field__input` sets the border after them in this
   file. These two rules are one point more specific, so the state wins. */
.field__input.border-danger {
    border-color: var(--danger);
}

.field__input.border-success {
    border-color: var(--status-ok);
}

/* The state, not the colour class. `public/js/shopper-form.js` writes
   `data-state="invalid"` on the control, next to the Tailwind border classes
   that it also writes. Those classes come from the bundle of the application,
   thus they exist here as well; this rule is one point more specific, and this
   file loads later, thus the design wins. */
.field__input[data-state="invalid"] {
    border-color: var(--danger);
}

.field__input[data-state="invalid"]:focus {
    border-color: var(--danger);
    box-shadow: var(--ring-error);
}

.field__input:disabled {
    opacity: .5;
    cursor: not-allowed;
}

/* The arrow of a select is a background image, thus the element needs no
   wrapper and no second element that holds an svg. */
.field__input--select {
    appearance: none;
    padding-right: var(--sp-9);
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%2311110F' opacity='.4'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--sp-4) center;
    background-size: 18px 18px;
    cursor: pointer;
}

/* A control with a button inside it: the password field. */
.field__control {
    position: relative;
}

.field__input--reveal {
    padding-right: var(--sp-10);
}

.field__reveal {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    padding: 0 var(--sp-4);
    background: transparent;
    border: 0;
    cursor: pointer;
    color: var(--text-muted);
    transition: color var(--ease);
}

.field__reveal:hover {
    color: var(--text-heading);
}

.field__reveal svg {
    width: 18px;
    height: 18px;
}

/* The text beside a check box or a radio. */
.check__text {
    line-height: 1.45;
    color: var(--text-body);
}

/* A link in that text. The terms of the checkout stand in a sentence, thus the
   link needs a line under it to stay visible. */
.check__text a {
    text-decoration: underline;
}

.check__text a:hover {
    color: var(--r600);
}

.check__box--radio {
    border-radius: 50%;
}

.check__box--radio:checked {
    background: var(--r500);
    background-image: none;
    border-color: var(--r500);
    box-shadow: inset 0 0 0 3px var(--paper);
}

.check__box:disabled {
    opacity: .5;
    cursor: not-allowed;
}

/* The profile picture. */
.avatarfield {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
}

.avatarfield__preview {
    position: relative;
    flex: 0 0 auto;
    width: 64px;
    height: 64px;
    overflow: hidden;
    border-radius: 50%;
    background: var(--surface-disabled);
}

/* `form-fields.js` builds this image itself and it writes a Tailwind class list
   on it. That script serves three themes, thus the rule stands on the element
   and not on that class. */
.avatarfield__preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.avatarfield__empty {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: var(--text-muted);
}

.avatarfield__empty svg {
    width: 40px;
    height: 40px;
    fill: currentColor;
}

.avatarfield__actions {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-2);
}

.avatarfield__remove {
    padding: 0;
    background: transparent;
    border: 0;
    cursor: pointer;
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    color: var(--danger);
}

.avatarfield__remove:hover {
    text-decoration: underline;
}

/* --------------------------------------------------------------------------
   Pattern 6 — status badge
   -------------------------------------------------------------------------- */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 4px 8px;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    background: none;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--ink60);
}

.badge__dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

/* The badge as the lead of a page. The order confirmation puts it above the
   title, where the small size of a badge in a list is too quiet. */
.badge--lead {
    padding: 5px 10px;
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
}

/* The status of an order names its own class: `badge--status-<slug>`. The Blade
   page and the JavaScript that draws a later page both build that class out of
   the slug, thus the map of a status to a colour stands here alone. A slug with
   no rule keeps the neutral `.badge`, which is what `refunded` uses. */
.badge--ok,
.badge--status-completed {
    background: var(--status-ok-tint);
    border-color: var(--status-ok-line);
    color: var(--status-ok);
}

.badge--warn,
.badge--status-pending {
    background: var(--status-warn-tint);
    border-color: var(--status-warn-line);
    color: var(--status-warn);
}

.badge--accent,
.badge--status-processing {
    background: var(--accentSoft);
    border-color: var(--r500);
    color: var(--r600);
}

.badge--danger,
.badge--status-cancelled {
    background: var(--danger-ring);
    border-color: var(--danger);
    color: var(--danger);
}

/* The quiet state of a badge. The handoff draws the sold-out pill of the product
   page with the hairline of the base badge and no fill, thus this modifier
   changes the colour only. Red is not correct there: an article that you can
   order with a lead time is not an error. */
.badge--muted {
    color: var(--ink40);
}

/* --------------------------------------------------------------------------
   Pattern 7 — quantity stepper
   -------------------------------------------------------------------------- */

.stepper {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    overflow: hidden;
}

.stepper__btn {
    width: var(--h-field);
    height: var(--h-control);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    color: var(--ink60);
    cursor: pointer;
    transition: background var(--ease), color var(--ease);
}

.stepper__btn:hover {
    background: var(--bone);
    color: var(--ink);
}

.stepper__btn--minus {
    border-right: 1px solid var(--line);
}

.stepper__btn--plus {
    border-left: 1px solid var(--line);
}

.stepper__input {
    width: 54px;
    border: none;
    background: none;
    text-align: center;
    font-family: var(--font-label);
    font-size: var(--fs-body);
    color: var(--ink);
    appearance: textfield;
}

.stepper__input::-webkit-outer-spin-button,
.stepper__input::-webkit-inner-spin-button {
    appearance: none;
    margin: 0;
}

/* --------------------------------------------------------------------------
   Pattern 8 — image placeholder

   The handoff ships no imagery. Every image slot is a striped area with a mono
   caption that states its pixel size.
   -------------------------------------------------------------------------- */

.imgph {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--boneDeep);
    background-image: repeating-linear-gradient(135deg, rgba(17, 17, 15, .06) 0 10px, rgba(17, 17, 15, 0) 10px 22px);
    border: 1px solid var(--line);
    border-radius: var(--r3);
}

.imgph--container {
    border-radius: var(--r2);
}

.imgph__caption {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

/* --------------------------------------------------------------------------
   Pattern 9 — empty state
   -------------------------------------------------------------------------- */

.empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-11) var(--sp-8);
    border: 1px dashed var(--ink20);
    border-radius: var(--r3);
    text-align: center;
}

.empty__icon {
    color: var(--ink20);
    stroke: currentColor;
    stroke-width: var(--icon-stroke);
}

.empty__title {
    font-size: 25px;
    font-weight: var(--fw-medium);
}

.empty__text {
    max-width: 44ch;
    font-size: var(--fs-body);
    color: var(--ink60);
}

/* --------------------------------------------------------------------------
   Pattern 10 — list row
   -------------------------------------------------------------------------- */

.listrow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: 15px var(--sp-6);
    border-bottom: 1px solid var(--lineSoft);
    transition: background var(--ease);
}

.listrow:hover {
    background: var(--bone);
}

.listrow:last-child {
    border-bottom: none;
}

/* The rows stand in a `ul`, thus every row is the only child of its own `li`.
   `.listrow:last-child` above thus matches all of them, and it takes the line
   off every row of the list. The first rule below puts the line back, the
   second one takes it off the last row alone. */
.listrows {
    margin: 0;
    padding: 0;
    list-style: none;
}

.listrows > li > .listrow {
    border-bottom: 1px solid var(--lineSoft);
}

.listrows > li:last-child > .listrow {
    border-bottom: none;
}

/* A row holds two groups: the name of the thing on the left, and its state
   and its amount on the right. */
.listrow__main {
    display: grid;
    gap: 5px;
    min-width: 0;
}

.listrow__side {
    display: flex;
    align-items: center;
    gap: 14px;
}

.listrow__side .badge {
    white-space: nowrap;
}

.listrow__id {
    font-family: var(--font-label);
    font-size: var(--fs-body-sm);
    color: var(--r600);
}

.listrow__meta {
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--ink40);
}

.listrow__amount {
    font-size: 18px;
    font-weight: var(--fw-medium);
    font-variant-numeric: tabular-nums;
}

/* The last cell of a row in "Order history.dc.html": the word `View` and an
   arrow, in the label face, quiet. The row itself is the link, thus this is a
   `span` and not an `a`. */
.listrow__link {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--ink40);
}

.listrow:hover .listrow__arrow {
    transform: var(--arrow-nudge);
}

.listrow__arrow {
    display: inline-block;
    transition: transform var(--ease);
}

/* The foot of the order list of "Order history.dc.html": a dot and one line
   that says that older orders are on the way. It stands below the rows, inside
   the same panel. */
.listmore {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 22px;
    border-top: 1px solid var(--lineSoft);
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.listmore__dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--r500);
}

.listmore[hidden] {
    display: none;
}

/* The row of a standing order, "Scheduled orders.dc.html". It is the list row
   of the order history, and it holds one more thing: a button that pauses or
   activates the standing order. Thus the row itself is not the link, and this
   modifier gives the link inside it the free space of the row. */
.listrow--split .listrow__main {
    flex: 1 1 auto;
}

.schedrow__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

/* The date of the next delivery, in the accent colour. The design writes it in
   the label face at 12.5 pixels, a step below the meta line of a list row. */
.schedrow__when {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    font-family: var(--font-label);
    font-size: 12.5px;
    letter-spacing: var(--ls-mono-tight);
    color: var(--r600);
}

.schedrow__icon {
    flex: none;
}

/* The action below an account list: the button that makes a new standing
   order. */
.accactions {
    margin-top: 22px;
}

/* --------------------------------------------------------------------------
   Modal — the shell of `shared/modal.blade.php`

   The handoff README names ten patterns and the modal is not one of them,
   because only "Scheduled orders.dc.html" holds a dialog. Five templates use
   this one shell, thus it stands here with the shared blocks and not in the
   section of a page.

   The head is a bar, not a title page: one mono label at the left, a square
   close button at the right, a hairline below. That is `.panel__head--split`
   with a button in it, and the numbers are the numbers of the panel head. The
   body below is a grid, so two fields stand apart without a margin on each
   one.

   Two numbers of the design get the nearest token instead of a value of their
   own. The dialog padding of the design is 20px and the panel body is 18px
   (`--sp-6`); the modal takes the panel number, because a modal is a panel
   that floats. The action buttons of the design are 42px high and `.btn` is
   44px (`--h-control`); two pixels are not worth a second control height.

   The scrim writes its colour out, in the same way as `.cookiescrim`. A
   curtain colour is not a surface of the theme, thus it has no token.
   -------------------------------------------------------------------------- */

.modal {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: grid;
    place-items: center;
    padding: var(--sp-6);
    background: rgba(17, 17, 15, .5);
    overflow-y: auto;
}

/* The JavaScript of each modal adds and removes `hidden`. The name comes from
   the other theme and every modal template writes it, thus it stays. */
.modal.hidden {
    display: none;
}

/* The page below an open modal must not scroll. The cart modal writes this
   class on the `body`, and the name comes from the other theme in the same
   way. */
body.overflow-hidden {
    overflow: hidden;
}

.modal__dialog {
    width: 100%;
    max-width: 440px;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--r3);
    box-shadow: var(--shadow-elevation);
    overflow: hidden;
}

/* A modal that holds a list of products asks for more room than a modal that
   holds two fields. */
.modal__dialog--wide {
    max-width: 900px;
}

.modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: 15px var(--sp-6);
    border-bottom: 1px solid var(--line);
}

.modal__title {
    margin: 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    font-weight: var(--fw-regular);
    letter-spacing: var(--ls-mono);
    line-height: 1;
    text-transform: uppercase;
    color: var(--ink40);
}

.modal__close {
    flex: none;
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    background: none;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    color: var(--ink60);
    cursor: pointer;
    transition: color var(--ease), border-color var(--ease);
}

.modal__close:hover {
    color: var(--ink);
    border-color: var(--ink20);
}

.modal__body {
    display: grid;
    gap: var(--sp-5);
    padding: var(--sp-6);
}

/* The sentence below the head. The design writes it in the body and not in the
   head bar, because that bar is one line high. */
.modal__lead {
    margin: 0;
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--ink60);
}

/* The buttons stand at the right, as in the design. On a telephone the pair of
   the "create scheduled order" dialog is 288px wide in a dialog of 301px, thus
   a longer word in another language must be able to go to a second line. */
.modal__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    justify-content: flex-end;
}

/* The dialog is paper, thus its secondary button fills with bone. Same rule as
   the one for a panel. */
.modal__dialog .btn--secondary:hover {
    background: var(--bone);
}

/* Same reason for a field: a paper input on a paper dialog shows the border
   only. The panel and the checkout totals card have this rule already. */
.modal__dialog .field__input {
    background: var(--bone);
}

/* --------------------------------------------------------------------------
   The "reorder" dialog — `customer/replicate-order-modal.blade.php`

   The handoff has no page for this dialog. It draws one button, "Reorder all
   lines", and nothing behind it. The body is therefore made out of parts that
   the handoff does give: the line of the checkout (`coline`), the label of a
   panel head (`kicker`), the checkbox of a form (`check`) and the two buttons
   of `modal__actions`. Only the five rules below are new.

   The JavaScript of the dialog adds and removes the name `hidden` on six
   elements. One rule holds all six, because they all stand in this wrapper.
   -------------------------------------------------------------------------- */

.replicate,
.replicate__results {
    display: grid;
    gap: var(--sp-5);
}

.replicate .hidden {
    display: none;
}

/* The line that says that the availability check runs. */
.replicate__wait {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    font-size: var(--fs-body-sm);
    color: var(--ink60);
}

.replicate__spinner {
    flex: none;
    width: 18px;
    height: 18px;
    color: var(--r500);
    animation: pdpspin 1s linear infinite;
}

.replicate__group {
    display: grid;
    gap: var(--sp-3);
}

/* The heading of the lines that cannot go in the cart. */
.replicate__reject {
    color: var(--danger);
}

/* An order can hold many lines. The list scrolls, thus the dialog keeps a
   height that fits on a telephone. */
.replicate__list {
    max-height: 280px;
    overflow-y: auto;
}

.replicate__none {
    font-size: var(--fs-body-sm);
    color: var(--ink60);
}

/* The reason why a line cannot go in the cart. It is a meta line of `coline`
   with the danger colour. */
.coline__reason {
    margin: 5px 0 0;
    font-size: var(--fs-body-sm);
    color: var(--danger);
}

/* --------------------------------------------------------------------------
   Breadcrumb trail — the `nav` at the top of "Catalogue.dc.html"

   The design writes the trail in words, not in an icon: `HOME / CATALOGUE /
   FASTENERS` in the mono face, small, wide, and uppercase. The core partial
   held a house icon in `currentColor` with the word behind `sr-only`, and the
   colour classes came from the other two themes, thus the icon was not
   visible and the trail opened with a bare slash (ticket #219, Q20).

   Every number here stands in the handoff: the gap is 9 pixels (`--sp-3`), the
   space under the trail is 26 (`--sp-8`), the links are `--ink40` and the page
   itself is `--ink80`.
   -------------------------------------------------------------------------- */

.crumbs {
    margin-bottom: var(--sp-8);
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.crumbs__list {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-3);
}

.crumbs__item {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}

.crumbs__link {
    color: var(--text-muted);
    transition: color .18s ease;
}

.crumbs__link:hover {
    color: var(--text-heading);
}

/* The page that the visitor is on. It is the one item with no link. */
.crumbs__current {
    color: var(--text-body);
}

/* --------------------------------------------------------------------------
   Site header — "Site header.dc.html"

   Two blocks that stand next to each other:
   `.siteheader`  the sticky bar with the wordmark, the navigation and the actions
   `.drawer`      the full-screen menu below 980 pixels

   The design of Jonathan has an ink strip above the bar with a shipping
   promise and the contact details. It is out of the core theme on his word
   (ticket shopper#219, Q15): the strip holds shop text, and no theme holds
   shop text. A shop that wants it builds it in its own theme.

   The breakpoint of the design is 980 pixels, and it stands in the responsive
   section at the bottom of this file.
   -------------------------------------------------------------------------- */

.siteheader {
    position: sticky;
    top: 0;
    z-index: var(--z-header);
    background: var(--nav-bg);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--line);
}

.siteheader__inner {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: 0 var(--pad);
    height: var(--nav-h);
    display: flex;
    align-items: center;
    gap: var(--sp-9);
}

.siteheader__brand {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
    flex-shrink: 0;
}

.siteheader__brand:hover {
    color: var(--text-heading);
}

.siteheader__wordmark {
    font-size: 25px;
}

.siteheader__brand-suffix {
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.siteheader__logo {
    display: block;
    height: 40px;
    width: auto;
}

.siteheader__nav {
    display: flex;
    align-items: center;
    gap: var(--sp-8);
    align-self: stretch;
}

.siteheader__link {
    align-self: stretch;
    display: flex;
    align-items: center;
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    color: var(--text-body);
    border-bottom: 2px solid transparent;
    transition: color var(--ease), border-color var(--ease);
}

.siteheader__link:hover {
    color: var(--text-heading);
    border-bottom-color: var(--accent);
}

.siteheader__link--active {
    color: var(--text-heading);
    border-bottom-color: var(--accent);
}

.siteheader__actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--sp-4);
}

.siteheader__user {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    height: 34px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--r1);
    background: transparent;
    cursor: pointer;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--text-secondary);
    transition: background var(--ease), color var(--ease);
}

.siteheader__user:hover {
    background: var(--surface-raised);
    color: var(--text-heading);
}

.siteheader__user span {
    white-space: nowrap;
}

.siteheader__cart {
    display: flex;
    align-items: center;
    gap: 9px;
    height: 34px;
    padding: 0 13px;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    background: var(--surface-raised);
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    transition: border-color var(--ease), color var(--ease);
}

.siteheader__cart:hover {
    border-color: var(--text-muted);
    color: var(--text-heading);
}

.siteheader__cart-count {
    color: var(--accent-deep);
}

.siteheader__burger {
    display: none;
    width: 34px;
    height: 34px;
    place-items: center;
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    cursor: pointer;
    color: var(--text-heading);
}

.siteheader__burger:hover {
    border-color: var(--text-muted);
}

/* The account menu. The design shows the signed-out state, thus a plain link.
   A customer who is signed in gets the same button with this panel below it,
   in the row pattern of "Account nav.dc.html". */

.usermenu {
    position: relative;
}

.usermenu__panel {
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    z-index: var(--z-header-menu);
    min-width: 240px;
    padding: var(--sp-3);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--r2);
    box-shadow: var(--shadow-elevation);
}

.usermenu__head {
    padding: var(--sp-3) var(--sp-4) var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
    margin-bottom: var(--sp-2);
}

.usermenu__label {
    display: block;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.usermenu__name {
    display: block;
    margin-top: 6px;
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
}

.usermenu__mail {
    display: block;
    margin-top: 3px;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

.usermenu__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    padding: 11px 12px;
    border: 0;
    border-radius: var(--r1);
    background: transparent;
    cursor: pointer;
    text-align: left;
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    color: var(--text-body);
    transition: background var(--ease), color var(--ease);
}

.usermenu__item:hover {
    background: var(--surface-page);
    color: var(--text-heading);
}

.usermenu__arrow {
    font-family: var(--mono);
    font-size: var(--fs-mono);
    color: var(--text-muted);
}

.usermenu__sep {
    margin: var(--sp-2) 0 0;
    padding-top: var(--sp-3);
    border-top: 1px solid var(--border-soft);
}

/* The mobile drawer. It is a full-screen dialog, thus it needs no transform. */

.drawer {
    position: fixed;
    inset: 0;
    z-index: var(--z-panel);
    background: var(--surface-page);
    display: flex;
    flex-direction: column;
}

.drawer__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 15px var(--pad);
    border-bottom: 1px solid var(--line);
    background: var(--surface-raised);
}

.drawer__title {
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.drawer__close {
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    cursor: pointer;
    color: var(--text-heading);
}

.drawer__body {
    flex: 1;
    overflow-y: auto;
    padding: var(--sp-2) var(--pad) var(--sp-8);
}

.drawer__nav {
    display: grid;
}

.drawer__link {
    padding: var(--sp-5) 0;
    border-bottom: 1px solid var(--border-soft);
    font-size: 19px;
    font-weight: var(--fw-medium);
    letter-spacing: var(--ls-h3);
    color: var(--text-heading);
}

.drawer__link--active {
    color: var(--accent-deep);
}

.drawer__actions {
    margin-top: var(--sp-8);
    display: grid;
    gap: 10px;
}

/* The cart button comes from `shared.cart-header-button`, thus it arrives in its
   own wrapper element. That wrapper is the grid item, so it must stretch too. */
.drawer__actions [data-cart-header-button] {
    display: grid;
}

.drawer__cart {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: var(--h-control);
    padding: 0 var(--sp-5);
    background: var(--accent);
    color: var(--text-on-dark);
    border-radius: var(--r1);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
}

.drawer__cart:hover {
    color: var(--text-on-dark);
}

.drawer__cart-count {
    font-family: var(--mono);
    font-size: var(--fs-mono);
}

.drawer__foot {
    border-top: 1px solid var(--line);
    padding: var(--sp-5) var(--pad);
    background: var(--surface-raised);
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Site footer — "Site footer.dc.html"

   One ink band with a brand column and one column for each footer menu, then a
   hairline, then the legal row. The two breakpoints of the design, 900 and 620
   pixels, stand in the responsive section at the bottom of this file.
   -------------------------------------------------------------------------- */

.sitefooter {
    background: var(--surface-dark);
    color: var(--text-on-dark);
}

.sitefooter__inner {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: 0 var(--pad);
}

.sitefooter__cols {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: var(--block-gap) var(--pad) var(--sp-9);
    display: grid;
    grid-template-columns: 1.3fr repeat(3, 1fr);
    gap: 34px;
}

.sitefooter__wordmark {
    font-size: 24px;
}

/* The logo of the brand column, when a shop uploads one for a dark background.
   It stands in the place of the wordmark. At 40px it reads at the same weight as the 24px wordmark. */
.sitefooter__logo {
    display: block;
    height: 40px;
    width: auto;
}

.sitefooter__about {
    margin: var(--sp-4) 0 0;
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--onInk-muted);
    max-width: 30ch;
}

.sitefooter__phone {
    margin: 14px 0 0;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    color: var(--onInk-faint);
}

.sitefooter__coltitle {
    margin: 0;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    font-weight: 400;
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--onInk-faint);
}

.sitefooter__links {
    margin-top: 15px;
    display: grid;
    gap: 10px;
}

.sitefooter__link {
    font-size: var(--fs-body-sm);
    color: var(--onInk-dim);
    transition: color var(--ease);
}

.sitefooter__link:hover {
    color: var(--onInk-text);
}

.sitefooter__rule {
    border-top: 1px solid var(--onInk-line);
}

.sitefooter__bottom {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: var(--sp-6) var(--pad) 34px;
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    justify-content: space-between;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--onInk-faint);
}

/* The colour must stand here. `.sitefooter__bottom` gives it, but the legal line
   is a `p`, and the base rule for `p` sets the body colour. That rule is more
   specific than an inherited value, thus the line was almost black on the ink
   band. */
.sitefooter__legal {
    margin: 0;
    color: var(--onInk-faint);
}

.sitefooter__cookie {
    background: transparent;
    border: 0;
    padding: 0;
    color: var(--onInk-faint);
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--ease);
}

.sitefooter__cookie:hover {
    color: var(--onInk-text);
}

.sitefooter__blocks {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: var(--sp-8) var(--pad);
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    gap: var(--sp-6);
}

/* --------------------------------------------------------------------------
   The shell of a page, and the blocks of a content page

   `.shell`       the html body: a column that fills the screen
   `.shell__main` the content column, at the width of the design
   `.pageblocks`  the rows of a content page, twelve columns each

   The width of a block comes from the page editor as `full`, `3/4`, `2/4` or
   `1/4`. The template turns that into a modifier here, and it does not use the
   Tailwind span that `BladeLogic` also gives. That map is one method for three
   themes, thus a change there would change the other two.
   -------------------------------------------------------------------------- */

.shell {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

.shell__body {
    flex: 1 1 auto;
}

/* A panel that covers the screen locks the page behind it. Without this the
   page keeps its own scrollbar next to the one of the panel, and a swipe in
   the panel scrolls the page under it (ticket #219, comment 18210).

   `overflow: hidden` on the body is enough: the root element stands at
   `visible`, thus the body propagates its overflow to the viewport.

   Two panels can stand on one page — the menu and the filter drawer — so each
   one writes its own class. A shared class would let the panel that closes
   first unlock the page while the other one is still open. */
body.is-locked-menu,
body.is-locked-filters {
    overflow: hidden;
    /* The scrollbar of the page goes with the scroll, thus the page would grow
       by the width of that bar and everything behind the panel would jump
       sideways. That space is given back here. `--sbw` is measured once by the
       site header, which stands on every page; `scrollbar-gutter: stable` does
       not do this job, because the body only lends its overflow to the viewport
       and the gutter stays with the body. A telephone has an overlay scrollbar
       of zero width, thus there this reserves nothing and costs nothing. */
    padding-right: var(--sbw, 0px);
}

/* The shop writes this message itself and it can hold any HTML. It is the one
   child of the body with no `__inner` of its own, thus it gets the cap and the
   gutter here. */
.headermsg {
    max-width: var(--maxw);
    margin: 0 auto;
    padding-inline: var(--pad);
}

/* The notice bar inside that message, drawn by
   `layouts/app/header-message-banner.blade.php`. The module writes the words,
   the theme writes the shape.

   The icon of the bar arrives with the message, thus it carries the class
   names of the other two themes: `h-4 w-4 flex-none`. Core holds no rule for
   those, and the base rule above gives an `svg` an automatic height, so the
   browser drew the default box of 300 by 150 pixels. The size stands here
   (ticket #219, S22). It is the defect of Q20 in a second place. */
.headermsg__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    margin-top: var(--sp-5);
    padding: var(--sp-3) var(--sp-5);
    background: var(--accent-tint);
    border: 1px solid var(--border-hairline);
    border-radius: var(--r2);
}

.headermsg__text {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    color: var(--accent-deep);
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
}

.headermsg__text svg {
    width: 16px;
    height: 16px;
    flex: none;
}

.headermsg__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-5);
}

.headermsg__action,
.headermsg__link {
    padding: 0;
    background: none;
    border: 0;
    color: var(--accent-deep);
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--ease);
}

.headermsg__action:hover,
.headermsg__link:hover {
    color: var(--ink);
}

.shell__main {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: var(--sp-10) var(--pad) var(--sp-12);
}

/* --------------------------------------------------------------------------
   Page head of a content page — "CMS page.dc.html"

   The title of a CMS page is larger than the title of a shop page: the handoff
   gives it `clamp(34px, 5vw, 58px)` against the `clamp(30px, 3.8vw, 42px)` of
   the base `h1`. It also gets a tighter letter-spacing and a measure of 24
   characters, so a long title breaks into two short lines and not into one
   line that runs the full 1200 pixels.

   The narrow-screen rule at the end of this file puts every `h1` on 30 pixels.
   `.pagehead__title` is a class, thus it wins there, and the title keeps the
   34 pixels that the handoff asks for below 640 pixels.
   -------------------------------------------------------------------------- */

.pagehead {
    /* The handoff puts 40 pixels between the head and the first block. No
       spacing token holds that number; `--block-gap` starts at 40 but grows
       with the viewport, and this space must not. */
    margin-bottom: 40px;
}

.pagehead__title {
    font-size: clamp(34px, 5vw, 58px);
    line-height: 1;
    letter-spacing: -.04em;
    max-width: 24ch;
}

.pageblocks {
    display: flex;
    flex-direction: column;
    gap: var(--block-gap);
}

.pageblocks__row {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    gap: var(--sp-6);
    align-items: start;
}

.pageblocks__item {
    grid-column: span 12;
    min-width: 0;
}

.pageblocks__item--threequarter {
    grid-column: span 9;
}

.pageblocks__item--half {
    grid-column: span 6;
}

.pageblocks__item--quarter {
    grid-column: span 3;
}

/* A block with a colour of its own from the page editor. */
.pageblocks__item--tinted {
    border-radius: var(--r3);
    padding: var(--sp-7);
}

/* --------------------------------------------------------------------------
   Rich text — what the page editor writes

   The base rules take the margin off every heading and paragraph, because a
   page of this design places each element itself. Text out of the editor is
   the one place where that is wrong: nobody places those elements, thus the
   rhythm must come back here.
   -------------------------------------------------------------------------- */

.richtext > * + * {
    margin-top: var(--sp-5);
}

.richtext h2,
.richtext h3 {
    margin-top: var(--sp-9);
}

.richtext > :first-child {
    margin-top: 0;
}

.richtext p,
.richtext li {
    color: var(--text-body);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
}

.richtext a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.richtext a:hover {
    color: var(--accent-deep);
}

.richtext strong {
    font-weight: var(--fw-semibold);
    color: var(--text-heading);
}

.richtext ul,
.richtext ol {
    margin: 0;
    padding-left: var(--sp-7);
}

.richtext li + li {
    margin-top: var(--sp-2);
}

.richtext blockquote {
    margin: 0;
    padding-left: var(--sp-6);
    border-left: 2px solid var(--border-hairline);
    color: var(--text-secondary);
}

.richtext img {
    max-width: 100%;
    height: auto;
    border-radius: var(--r3);
}

.richtext hr {
    border: 0;
    border-top: 1px solid var(--border-hairline);
}

/* Rich text on the ink band of the footer. The base colours of `.richtext` are
   for the bone page: a paragraph in `--text-body` on the ink band is almost
   invisible, and a heading of the page size is far too large for a column of
   the footer. The shop writes these blocks in the page editor, thus the theme
   must make them readable wherever they land. */
.sitefooter .richtext h2,
.sitefooter .richtext h3 {
    margin-top: 0;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    font-weight: var(--fw-medium);
    letter-spacing: var(--ls-mono);
    line-height: 1;
    text-transform: uppercase;
    color: var(--onInk-faint);
}

.sitefooter .richtext p,
.sitefooter .richtext li {
    color: var(--onInk-dim);
    font-size: var(--fs-body-sm);
}

.sitefooter .richtext a {
    color: var(--accent-on-dark);
}

.sitefooter .richtext a:hover {
    color: var(--text-on-dark);
}

.sitefooter .richtext > * + * {
    margin-top: var(--sp-4);
}

/* --------------------------------------------------------------------------
   Cookie consent — "Cookie consent.dc.html"

   Three blocks:
   `.cookiebar`    the bar along the bottom of the screen, layer 1
   `.cookieprefs`  the sheet with the four categories, layer 2
   `.cookiefab`    the round button that opens the sheet again

   Two class names in this section are NOT of this design system: `cc-hidden`
   and `cc-anim`. `public/js/cookie-consent.js` adds and removes them, and that
   controller is one file for every theme. They are a contract with the script,
   the same as a `data-cy` hook, thus they keep their name and they hold no
   look of their own beyond the one line that each needs.

   The bar is legal text. Both answer buttons have the same size and the same
   place, because a reject that is harder to find than an accept is not a free
   choice.
   -------------------------------------------------------------------------- */

.cc-hidden {
    display: none !important;
}

.cookiescrim {
    position: fixed;
    inset: 0;
    z-index: var(--z-consent-scrim);
    background: rgba(17, 17, 15, .32);
    animation: cookie-fade .22s ease both;
}

.cookiebar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-consent);
    background: var(--surface-raised);
    border-top: 1px solid var(--border-hairline);
    box-shadow: var(--shadow-elevation);
    padding: var(--sp-7) var(--pad);
    display: flex;
    align-items: center;
    gap: var(--sp-9);
}

.cookiebar.cc-anim {
    animation: cookie-rise .42s cubic-bezier(.22, .61, .36, 1) both;
}

.cookiebar__copy {
    flex: 1;
    min-width: 0;
}

.cookiebar__title {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-h3);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--ls-h3);
    color: var(--text-heading);
}

.cookiebar__text {
    margin: 0;
    max-width: 64ch;
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--text-body);
}

.cookiebar__link {
    color: var(--accent);
}

.cookiebar__link:hover {
    color: var(--accent-deep);
}

.cookiebar__actions {
    flex: none;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}

/* The two answers share a grid of two equal columns, thus a longer label does
   not make its button wider than the other one. */
.cookiebar__pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-3);
}

.cookiebar__pair .btn {
    width: 100%;
}

.cookiebar__prefs {
    background: none;
    border: 0;
    padding: 0 var(--sp-4);
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-secondary);
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--ease);
}

.cookiebar__prefs:hover {
    color: var(--text-heading);
}

.cookieprefs {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-consent-sheet);
    background: var(--surface-raised);
    border-top: 1px solid var(--border-hairline);
    box-shadow: var(--shadow-elevation);
    padding: var(--sp-8) var(--pad) var(--sp-9);
    max-height: 92vh;
    overflow: auto;
}

.cookieprefs.cc-anim {
    animation: cookie-rise .34s cubic-bezier(.22, .61, .36, 1) both;
}

.cookieprefs__inner {
    max-width: 720px;
    margin: 0 auto;
}

.cookieprefs__head {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-5);
}

.cookieprefs__title {
    margin: 0;
    font-size: var(--fs-h3);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--ls-h3);
    color: var(--text-heading);
}

.cookieprefs__close {
    margin-left: auto;
    flex: none;
    height: var(--h-control-table);
    width: var(--h-control-table);
    display: grid;
    place-items: center;
    background: none;
    border: 1px solid var(--border-hairline);
    border-radius: var(--r1);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background var(--ease), color var(--ease);
}

.cookieprefs__close:hover {
    background: var(--bone);
    color: var(--text-heading);
}

.cookieprefs__intro {
    margin: var(--sp-3) 0 var(--sp-5);
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--text-secondary);
}

.cookieprefs__cat {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-5);
    padding: var(--sp-5) 0;
    border-top: 1px solid var(--border-soft);
}

.cookieprefs__cat:first-child {
    border-top: 0;
}

.cookieprefs__catbody {
    flex: 1;
    min-width: 0;
}

.cookieprefs__cattitle {
    margin: 0 0 var(--sp-1);
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-heading);
}

.cookieprefs__locked {
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
    border: 1px solid var(--border-hairline);
    border-radius: 999px;
    padding: 2px var(--sp-3);
}

.cookieprefs__catdesc {
    margin: 0;
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--text-secondary);
}

.cookieprefs__switch {
    flex: none;
    position: relative;
    height: 26px;
    width: 46px;
    margin-top: 2px;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: var(--ink20);
    cursor: pointer;
    transition: background var(--ease);
}

.cookieprefs__knob {
    position: absolute;
    top: 3px;
    left: 3px;
    height: 20px;
    width: 20px;
    border-radius: 50%;
    background: var(--paper);
    box-shadow: 0 1px 2px rgba(17, 17, 15, .35);
    transition: transform var(--ease);
}

.cookieprefs__switch[aria-checked="true"] {
    background: var(--accent);
}

.cookieprefs__switch[aria-checked="true"] .cookieprefs__knob {
    transform: translateX(20px);
}

.cookieprefs__switch[data-locked="true"] {
    cursor: not-allowed;
    opacity: .85;
}

.cookieprefs__actions {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin-top: var(--sp-7);
    padding-top: var(--sp-6);
    border-top: 1px solid var(--border-hairline);
}

.cookieprefs__actions .btn {
    flex: 1;
}

.cookiefab {
    position: fixed;
    left: var(--sp-7);
    bottom: var(--sp-7);
    z-index: var(--z-fab);
    height: 46px;
    width: 46px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 1px solid var(--border-hairline);
    border-radius: 50%;
    background: var(--surface-raised);
    box-shadow: var(--shadow-elevation);
    color: var(--text-secondary);
    cursor: pointer;
    transition: transform var(--ease), border-color var(--ease), color var(--ease);
}

.cookiefab.cc-anim {
    animation: cookie-pop .32s cubic-bezier(.22, .61, .36, 1) both;
}

.cookiefab:hover {
    transform: translateY(-2px);
    border-color: var(--ink40);
    color: var(--text-heading);
}

.cookiefab__cookie {
    position: relative;
    height: 24px;
    width: 24px;
    border-radius: 50%;
    background: var(--bone);
    border: 1.5px solid currentColor;
}

.cookiefab__cookie i {
    position: absolute;
    border-radius: 50%;
    background: currentColor;
}

.cookiefab__cookie i:nth-child(1) { height: 4px; width: 4px; top: 4px; left: 5px; }
.cookiefab__cookie i:nth-child(2) { height: 3px; width: 3px; top: 11px; left: 13px; }
.cookiefab__cookie i:nth-child(3) { height: 3.5px; width: 3.5px; top: 13px; left: 4px; }
.cookiefab__cookie i:nth-child(4) { height: 3px; width: 3px; top: 5px; left: 14px; }

@keyframes cookie-rise {
    from { opacity: 0; transform: translateY(110%); }
    to { opacity: 1; transform: translateY(0); }
}

@keyframes cookie-pop {
    from { opacity: 0; transform: translateY(8px) scale(.85); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes cookie-fade {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* --------------------------------------------------------------------------
   Page editor blocks — image and text, gallery

   `.mediatext`  one image next to rich text
   `.gallery`    a row of images out of the page editor

   The handoff has no screen for these two: they are a feature of the page
   editor, not a page of the design. Thus they follow the design system — the
   spacing scale, the radii and the rhythm — and they invent nothing.
   -------------------------------------------------------------------------- */

.mediatext {
    display: flex;
    gap: clamp(24px, 4vw, 44px);
    align-items: start;
}

/* The image stands first in the markup for every position. `order` moves it,
   thus the three positions of the page editor need one structure. */
.mediatext--reverse .mediatext__media {
    order: 2;
}

.mediatext--stacked {
    flex-direction: column;
    gap: var(--sp-7);
}

.mediatext__media {
    position: relative;
    flex: 0 0 auto;
    width: 50%;
}

.mediatext--third .mediatext__media {
    width: 33.3333%;
}

.mediatext--quarter .mediatext__media {
    width: 25%;
}

.mediatext--stacked .mediatext__media {
    width: 100%;
}

.mediatext__media img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--r3);
    object-fit: cover;
}

/* The link covers the image. The image itself carries no `href`, thus the
   whole area is one target. */
.mediatext__overlay {
    position: absolute;
    inset: 0;
    cursor: pointer;
}

.mediatext__body {
    flex: 1 1 0;
    min-width: 0;
}

.mediatext__body h2 {
    margin-bottom: var(--sp-5);
}

.gallery {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* One image at the width of the block. The radius stands on the block, and it
   clips the corners of the image. */
.gallery--single {
    display: block;
    border-radius: var(--r3);
    overflow: hidden;
}

/* Without a gap the images make one surface. The surface is round and it clips
   the corners of the images; with a gap each image is round for itself. */
.gallery--seamless {
    border-radius: var(--r3);
    overflow: hidden;
}

.gallery__cell {
    position: relative;
    overflow: hidden;
}

.gallery__cell--square {
    aspect-ratio: 1 / 1;
}

/* The height of these two comes from the page editor, thus the `style`
   attribute on the element holds it. */
.gallery__cell--capped {
    display: flex;
    align-items: center;
    justify-content: center;
}

.gallery__img {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: var(--r3);
}

.gallery--seamless .gallery__img,
.gallery--single .gallery__img {
    border-radius: 0;
}

.gallery__img--fit {
    object-fit: contain;
    max-height: 100%;
}

.gallery__img--fill {
    object-fit: cover;
}

.gallery__link {
    position: absolute;
    inset: 0;
    cursor: pointer;
}

/* --------------------------------------------------------------------------
   Toasts — "Toasts.dc.html"

   `.toaststack`  the stack in the top right corner
   `.toast`       one message

   The markup comes from `public/themes/core/js/notifications.js`, which is the
   renderer of the core theme. The shared renderer in
   `resources/js/components/NotificationManager.js` stays for Retailsale and
   Hopping Borders; it writes Tailwind classes and it never reads this file.
   Jonathan chose that split on ticket shopper#219, Q17 (comment 18155).

   The type gives one colour, and that colour serves the badge and the progress
   rule. There is no tinted panel: the toast is always paper.

   The design page names 24 pixels for the inset and 10 pixels for the gap. The
   spacing scale holds neither, thus both snap to the nearest step, the same as
   the cookie bar does. A measurement inside one toast (the 24 pixel badge, the
   13 pixel gap, the 384 pixel width, the 2 pixel rule) is a size of the
   component and stays literal.
   -------------------------------------------------------------------------- */

.toaststack {
    position: fixed;
    top: var(--sp-7);
    right: var(--sp-7);
    z-index: var(--z-toast);
    width: 100%;
    max-width: 384px;
    display: grid;
    gap: var(--sp-3);
}

.toast {
    position: relative;
    overflow: hidden;
    display: flex;
    gap: 13px;
    padding: var(--sp-5) var(--sp-5) 18px;
    background: var(--surface-raised);
    border: 1px solid var(--border-hairline);
    border-radius: var(--r2);
    box-shadow: var(--shadow-elevation);
    animation: toast-in 220ms ease-out both;
}

/* The exit of a toast. The renderer removes the element after 220 ms, which is
   the time of this animation. */
.toast--out {
    animation: toast-out 220ms ease-in both;
}

/* One custom property per type. Every rule below reads it, thus a new type
   costs one modifier and nothing else. */
.toast--success { --toast-ink: var(--status-ok); }
.toast--error { --toast-ink: var(--danger); }
.toast--attention { --toast-ink: var(--status-warn); }

.toast__badge {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--toast-ink);
    color: var(--surface-raised);
}

/* `fill` must stand here. The base rule sets `svg { fill: none }`, because
   almost every icon of this design is a stroke icon. The three type icons are
   fill icons, and a presentation attribute on the element loses against a rule,
   thus `fill="currentColor"` in the markup does nothing by itself. */
.toast__badge svg {
    width: 15px;
    height: 15px;
    fill: currentColor;
}

.toast__body {
    flex: 1;
    min-width: 0;
}

.toast__title {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    line-height: 1.35;
    letter-spacing: 0;
    color: var(--text-heading);
}

.toast__text {
    margin: 7px 0 0;
    font-size: var(--fs-caption);
    line-height: 1.55;
    color: var(--text-secondary);
}

.toast__list {
    margin: var(--sp-2) 0 0;
    padding-left: var(--sp-5);
    display: grid;
    gap: 4px;
    list-style: disc;
}

.toast__list li {
    font-size: var(--fs-caption);
    line-height: 1.5;
    color: var(--text-secondary);
}

.toast__links {
    margin-top: 13px;
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
}

.toast__link {
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--accent-deep);
}

.toast__link:hover {
    color: var(--accent-deep);
    text-decoration: underline;
}

.toast__dismiss {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    padding: 0;
    background: transparent;
    border: 0;
    cursor: pointer;
    color: var(--text-muted);
    transition: color var(--ease);
}

.toast__dismiss:hover {
    color: var(--text-heading);
}

.toast__dismiss svg {
    width: 13px;
    height: 13px;
}

/* The timer. It runs for the same 7 seconds as the lifetime in the renderer. */
.toast__timer {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 2px;
    width: 100%;
    background: var(--toast-ink);
    animation: toast-shrink 7s linear forwards;
}

@keyframes toast-in {
    from { opacity: 0; transform: translateY(-8px); }
    to { opacity: 1; transform: translateY(0); }
}

@keyframes toast-out {
    from { opacity: 1; transform: translateY(0); }
    to { opacity: 0; transform: translateY(-8px); }
}

@keyframes toast-shrink {
    from { width: 100%; }
    to { width: 0%; }
}

/* --------------------------------------------------------------------------
   Product card and the product grid

   The card of "Catalogue" in the handoff. Four places include the same partial:
   the product list, the upsells of a product page, the upsells of the cart
   modal, and the page editor block `product-selection`.

   The design shows a line with the brand and the article number above the
   title. `ProductParser` gives an article number and no brand, thus that line
   holds the article number alone. It also shows a unit and a flag; the parser
   gives neither, so the card does not invent them.
   -------------------------------------------------------------------------- */

.pgrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
    gap: var(--sp-8);
}

.pcard {
    position: relative;
    display: flex;
    flex-direction: column;
}

.pcard__media {
    position: relative;
    aspect-ratio: 1 / 1;
    border: 1px solid var(--line);
    border-radius: var(--r3);
    background: var(--boneDeep);
    overflow: hidden;
}

/* The box stays a square in both states, thus the cards of a row keep one
   line. The setting `square_images` of the core theme decides what happens
   inside it: off, the whole picture stands in the box; on, the picture fills
   the box and a picture that is not a square loses its edges. */
.pcard__img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    transition: transform .3s ease;
}

.pcard__media--square .pcard__img {
    object-fit: cover;
}

.pcard:hover .pcard__img {
    transform: scale(1.05);
}

.pcard__body {
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: var(--sp-1);
    padding-top: var(--sp-4);
}

/* The whole card is one hit area. The span inside the link covers the card, so
   the title and the picture both lead to the product. */
.pcard__overlay {
    position: absolute;
    inset: 0;
}

.pcard__sku {
    display: block;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.pcard__title {
    margin: var(--sp-2) 0 0;
    font-size: var(--fs-body);
    font-weight: 500;
    line-height: var(--lh-tight);
}

.pcard__meta {
    display: block;
    margin-top: 5px;
    font-size: var(--fs-caption);
    color: var(--text-muted);
}

.pcard__foot {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-top: auto;
    padding-top: var(--sp-3);
    border-top: 1px solid var(--lineSoft);
}

.pcard__price {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 19px;
    letter-spacing: -.02em;
}

.pcard__bulk {
    margin: 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--accent-deep);
}

/* --------------------------------------------------------------------------
   Page editor block — product selection

   The block puts product cards in a grid. The page editor holds one choice:
   three wide, or the standard grid. `BlockParser` also gives a `grid_cols` key
   with Tailwind classes, but that is one method for three themes, thus this
   template reads `display` and picks a modifier.

   These are `min-width` rules on purpose. They replace Tailwind rules that are
   also `min-width`, so the numbers are the Tailwind numbers.
   -------------------------------------------------------------------------- */

.productgrid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-5);
}

.productgrid--three {
    grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 640px) {
    .productgrid,
    .productgrid--three {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (min-width: 1024px) {
    .productgrid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    .productgrid--three {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* --------------------------------------------------------------------------
   Page editor block — form

   Three form types (contact, test sample, VAT reverse charge) share one shape:
   an intro column with a heading and a line of text, and a column of fields
   next to it. The fields come from `shared/form/field-*`, thus this section
   only holds the frame, the grid, and the panel that replaces the form after a
   send.
   -------------------------------------------------------------------------- */

.formblock {
    border: 1px solid var(--line);
    border-radius: var(--r3);
    background: var(--surface-raised);
    padding: var(--sp-9);
}

.formblock__form {
    transition: opacity var(--ease);
}

/* The send hides the form. `display: none` from the attribute must beat the
   `display` of the rules above it, thus both states stand here. */
.formblock__form[hidden],
.formsuccess[hidden] {
    display: none;
}

.formblock__form--out {
    opacity: 0;
}

/* The intro stands left and the fields right. The ratio is one to two, which is
   the `md:grid-cols-3` plus `md:col-span-2` of the template that this replaces. */
.formsplit {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: var(--sp-9);
    align-items: start;
}

.formsplit__intro h2 {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-h3);
}

.formsplit__introtext {
    margin: 0;
    font-size: var(--fs-body-sm);
    color: var(--text-secondary);
}

/* Six columns, because the design gives a field a half or a whole line. The
   modifiers below carry that choice; a field without one takes the full line. */
.formgrid {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: var(--sp-7) var(--sp-5);
    max-width: 42rem;
}

.formgrid > .field {
    grid-column: 1 / -1;
}

.formgrid > .field--half {
    grid-column: span 3;
}

/* The form grid inside a card. The card gives the width and the padding, thus
   the cap of 42rem must go, and the rows stand closer than on a page that
   carries a form alone. */
.formgrid--card {
    max-width: none;
    gap: 15px;
}

/* The row with the submit button. A `div` is not a `.field`, thus it needs its
   own rule to take the whole line. */
.formgrid__actions {
    grid-column: 1 / -1;
    display: flex;
    justify-content: flex-end;
}

/* The row with the button of a form that carries more than one card. The
   button belongs to the form and not to a card, thus it stands below them all.
   "Addresses.dc.html" puts it there. */
.formend {
    display: flex;
    justify-content: flex-end;
}

.formblock__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-5);
    margin-top: var(--sp-8);
    padding-top: var(--sp-7);
    border-top: 1px solid var(--line);
}

/* The Turnstile widget of Cloudflare. The widget makes its own frame, thus this
   rule only gives it a place and holds the error line under it. */
.turnstile {
    margin-top: var(--sp-7);
}

.turnstile__error {
    margin: var(--sp-2) 0 0;
    font-size: var(--fs-caption);
    color: var(--danger);
}

/* The panel that takes the place of the form after a send. */
.formsuccess {
    display: flex;
    gap: var(--sp-5);
    align-items: flex-start;
    padding: var(--sp-7);
    border: 1px solid var(--status-ok-line);
    border-radius: var(--r2);
    background: var(--status-ok-tint);
    opacity: 0;
    transition: opacity var(--ease);
}

.formsuccess--in {
    opacity: 1;
}

.formsuccess__badge {
    flex: none;
    color: var(--status-ok);
    line-height: 0;
}

/* `fill` must stand here. The base rule sets `svg { fill: none }` for the stroke
   icons of this design, and a presentation attribute loses against a rule. */
.formsuccess__badge svg {
    width: 20px;
    height: 20px;
    fill: currentColor;
}

.formsuccess__title {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-body-lg);
    color: var(--status-ok);
}

.formsuccess__text {
    margin: 0;
    font-size: var(--fs-body-sm);
    color: var(--text-body);
}

/* --------------------------------------------------------------------------
   The catalogue — the head row, the two columns, and the sort control

   `.cathead`   the title, the intro, and the tools on one line
   `.catgrid`   the filter column of 240 pixels next to the product column
   `.sortpick`  the sort button and its menu

   The handoff collapses this page at 1000 pixels, and that number is its own.
   It is not a Tailwind breakpoint, thus the rule below is a `max-width` rule
   at 1000 and not at 1023. The old template used `lg:grid-cols-4`, so the page
   changed 24 pixels later than the design says.

   The sidebar partial and the mobile drawer still hold Tailwind class names.
   They come in the next part of S7, and the `max-width` rule here keeps them
   in step until then.
   -------------------------------------------------------------------------- */

.cathead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sp-8);
    padding-bottom: var(--sp-7);
    border-bottom: 1px solid var(--ink20);
}

.cathead__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: clamp(34px, 4.4vw, 52px);
    line-height: 1;
    letter-spacing: -.035em;
}

.cathead__intro {
    margin: var(--sp-4) 0 0;
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    color: var(--text-secondary);
    max-width: 56ch;
}

.cathead__tools {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-shrink: 0;
}

/* The button of the mobile filter drawer. It stands next to the sort control
   and it appears when the sidebar goes away. */
.catfilterbtn {
    display: none;
    align-items: center;
    gap: var(--sp-3);
    height: 38px;
    padding: 0 var(--sp-5);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--r1);
    cursor: pointer;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--text-body);
    transition: border-color var(--ease);
}

.catfilterbtn:hover {
    border-color: var(--ink40);
}

.catlist {
    margin-top: var(--sp-9);
}

.catgrid {
    display: grid;
    grid-template-columns: 240px minmax(0, 1fr);
    gap: clamp(28px, 4vw, 52px);
    align-items: start;
}

/* The filter column. It stands still while the product column scrolls. The
   partial inside it still holds Tailwind class names; only this wrapper is
   converted, because the wrapper is what decides the column of the grid. Its
   old class pair was `hidden lg:block`, which took the sidebar away at 1023
   while the grid here goes to one column at 1000. That gap of 24 pixels left
   an empty column of 240 pixels, thus the wrapper changes with the shell and
   not with the partial. */
.catgrid__side {
    position: sticky;
    top: 88px;
}

/* --------------------------------------------------------------------------
   The sort control

   `sort-selector.js` reads `[data-sort-selector]` and `[data-sort-value]`, and
   Alpine holds the open state. Neither hook changes here.
   -------------------------------------------------------------------------- */

.sortpick {
    position: relative;
}

.sortpick__btn {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    height: 38px;
    padding: 0 var(--sp-5);
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--r1);
    cursor: pointer;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--text-body);
    transition: border-color var(--ease);
    white-space: nowrap;
}

.sortpick__btn:hover {
    border-color: var(--ink40);
}

.sortpick__icon {
    width: 13px;
    height: 13px;
    flex-shrink: 0;
}

.sortpick__menu {
    position: absolute;
    right: 0;
    top: 44px;
    z-index: var(--z-dropdown);
    width: 226px;
    background: var(--surface-raised);
    border: 1px solid var(--line);
    border-radius: var(--r2);
    box-shadow: var(--shadow-elevation);
    overflow: hidden;
}

.sortpick__opt {
    display: block;
    width: 100%;
    text-align: left;
    padding: 11px var(--sp-5);
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--lineSoft);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    color: var(--text-body);
    transition: background var(--ease);
}

.sortpick__opt:last-child {
    border-bottom: 0;
}

.sortpick__opt:hover,
.sortpick__opt--on {
    background: var(--bone);
    color: var(--text-heading);
}

.sortpick__opt--on {
    font-weight: 500;
}

/* --------------------------------------------------------------------------
   The filter sidebar — the category list, the facets, and the range slider

   `.catnav`    the list of categories above the facets
   `.pfilter`   one facet: a toggle, and a list of values with a count
   `.prange`    the two-handle slider of a numeric property

   Three scripts read this markup and none of them changes here:

   `property-filter.js` adds and removes the class `hidden` on
   `[data-filter-content]` and on the two icons. That class is a Tailwind
   utility, and `components.css` loads after Tailwind, thus a plain
   `display` rule here would beat it and the facet could not close. The rule
   `.pfilter__content.hidden` therefore stands next to the open rule, and it
   has one class more, so it wins.

   `property-range-slider.js` writes `left` and `right` on `[data-range-fill]`
   and text in `[data-range-label]`.

   `product-list.js` clones the `label` of a value to build the chip text, and
   it removes `[data-pv-count]` from that clone first. The count must thus stay
   inside the label, and the label must hold no other text.
   -------------------------------------------------------------------------- */

.catnav__title {
    margin: 0 0 var(--sp-4);
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    font-weight: 400;
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.catnav__list {
    margin: 0 0 var(--sp-6);
    padding: 0;
    list-style: none;
    display: grid;
    gap: 2px;
}

.catnav__link {
    display: flex;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: 7px 8px;
    margin: 0 -8px;
    border-radius: var(--r1);
    font-size: var(--fs-body-sm);
    color: var(--text-body);
    transition: background var(--ease), color var(--ease);
}

.catnav__link:hover {
    background: var(--surface-raised);
    color: var(--text-heading);
}

/* The label takes the free space, thus the count and the remove button stay
   together on the right side of the row. */
.catnav__label {
    flex: 1 1 auto;
    min-width: 0;
}

.catnav__count {
    flex: 0 0 auto;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    color: var(--text-muted);
}

.catnav__link--on {
    background: var(--text-heading);
    color: var(--text-on-dark);
}

.catnav__link--on:hover {
    background: var(--text-heading);
    color: var(--text-on-dark);
}

.catnav__link--on .catnav__count {
    color: var(--text-on-dark);
    opacity: .7;
}

.catnav__remove {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    color: inherit;
    opacity: .7;
    transition: opacity var(--ease);
}

.catnav__remove:hover {
    opacity: 1;
}

.catnav__remove svg {
    width: 12px;
    height: 12px;
}

/* One indent step for each level of the path. Level 0 is the root row. */
.catnav__item--lvl1 { padding-left: var(--sp-3); }
.catnav__item--lvl2 { padding-left: var(--sp-5); }
.catnav__item--lvl3 { padding-left: var(--sp-7); }
.catnav__item--lvl4 { padding-left: var(--sp-8); }

.pfilter {
    border-top: 1px solid var(--line);
    padding: var(--sp-5) 0;
}

.pfilter__head {
    margin: 0;
    font-size: inherit;
    font-weight: inherit;
}

.pfilter__toggle {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: 0;
    background: transparent;
    border: 0;
    cursor: pointer;
    color: var(--text-heading);
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    font-weight: 500;
    text-align: left;
}

/* The glyph is `+` when the facet is closed and `-` when it is open.
   `property-filter.js` swaps the two with the class `hidden`, thus these two
   rules must not name `display`. */
.pfilter__glyph {
    font-family: var(--mono);
    font-size: 14px;
    line-height: 1;
    color: var(--text-muted);
}

.pfilter__content {
    display: grid;
    gap: var(--sp-3);
    padding-top: 13px;
}

.pfilter__content.hidden {
    display: none;
}

.pfilter__opt {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    font-size: var(--fs-body-sm);
    color: var(--text-body);
}

.pfilter__box {
    display: grid;
    place-items: center;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.pfilter__input {
    grid-area: 1 / 1;
    appearance: none;
    -webkit-appearance: none;
    margin: 0;
    width: 16px;
    height: 16px;
    border: 1px solid var(--ink20);
    border-radius: var(--r1);
    background: var(--surface-raised);
    cursor: pointer;
    transition: border-color var(--ease), background var(--ease);
}

.pfilter__input:checked {
    border-color: var(--accent);
    background: var(--accent);
}

.pfilter__input:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.pfilter__check {
    grid-area: 1 / 1;
    width: 11px;
    height: 11px;
    pointer-events: none;
    opacity: 0;
    color: var(--text-on-dark);
    transition: opacity var(--ease);
}

.pfilter__input:checked + .pfilter__check {
    opacity: 1;
}

.pfilter__label {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex: 1;
    min-width: 0;
    cursor: pointer;
}

.pfilter__name {
    flex: 1;
    min-width: 0;
}

.pfilter__count {
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

/* A colour or an image says what the value is. The swatch is a circle, which is
   the value that the old template also had. */
.pfilter__swatch {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border: 1px solid var(--ink20);
    border-radius: 9999px;
}

.pfilter__img {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    object-fit: cover;
    border: 1px solid var(--ink20);
    border-radius: 9999px;
}

/* --------------------------------------------------------------------------
   The range slider

   Two `input[type=range]` elements lie on the same rail. The rail itself takes
   no pointer, only the two thumbs do, so a click between them does not move a
   handle by accident.
   -------------------------------------------------------------------------- */

.prange__label {
    display: block;
    text-align: center;
    margin-bottom: var(--sp-3);
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    color: var(--text-muted);
}

.prange__rail {
    position: relative;
    height: 34px;
    display: flex;
    align-items: center;
}

.prange__track {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--ink20);
}

/* `property-range-slider.js` writes `left` and `right` on this element. */
.prange__fill {
    position: absolute;
    height: 2px;
    background: var(--accent);
}

.prange__input {
    position: absolute;
    left: 0;
    right: 0;
    width: 100%;
    height: 30px;
    margin: 0;
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    pointer-events: none;
}

.prange__input::-webkit-slider-thumb {
    -webkit-appearance: none;
    pointer-events: auto;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--surface-raised);
    border: 1px solid var(--ink);
    cursor: grab;
}

.prange__input::-moz-range-thumb {
    pointer-events: auto;
    appearance: none;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--surface-raised);
    border: 1px solid var(--ink);
    cursor: grab;
}

.prange__input:focus-visible::-webkit-slider-thumb {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   The filter drawer of a small screen

   Alpine holds the open state. `x-show` writes `display: none` on the element
   when it is closed and it takes that inline value away when it opens, thus
   the open value must stand here: `.filterdrawer__wrap` is a flex box in this
   file and Alpine only hides it.

   The drawer itself hides above the width where the sidebar comes back. Its
   old class was `lg:hidden`, at 1024. The page collapses at 1000, thus the
   rule stands with the other rules of that breakpoint.

   The facets in the drawer are the same partial as in the sidebar, with the
   modifier `pfilter--drawer`: a line below in the place of a line above, and
   a box of 20 pixels for a finger.
   -------------------------------------------------------------------------- */

/* No `position` and no `z-index` here, and that is the point. This element is
   only the switch that the breakpoint turns on. When it had
   `position: relative; z-index: 40` it made a stacking context, so the
   `--z-panel` of `.filterdrawer__wrap` counted INSIDE it and the 40 counted
   outside. The site header stands lower, thus the header painted over the
   drawer and hid the head of the panel (shopper#219 cmt 18194). The wrap now
   stands at `--z-panel` in the root, the top layer of the page furniture. The
   scale itself is in tokens.css, under "Layers". */
.filterdrawer {
    display: none;
}

/* The curtain is a SIBLING of the wrap, not a child of it, because Alpine
   fades the two with transitions of their own. Thus it needs the layer written
   on it as well. Without a `z-index` it painted at `auto`: over the plain page,
   but UNDER every element after it that is positioned — each product card is
   `position: relative` — so the cards and the cookie button stood in full
   colour over a curtain that was drawn under them (shopper#219 cmt 18214).

   It carries the same layer as the wrap. The two are next to each other in one
   template and the curtain comes first, thus the wrap paints over it and the
   panel stays readable. */
.filterdrawer__backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--z-panel);
    background: rgba(17, 17, 15, .42);
}

.filterdrawer__wrap {
    position: fixed;
    inset: 0;
    z-index: var(--z-panel);
    display: flex;
    justify-content: flex-end;
}

.filterdrawer__panel {
    width: 330px;
    max-width: 88vw;
    background: var(--bone);
    border-left: 1px solid var(--line);
    display: flex;
    flex-direction: column;
}

.filterdrawer__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--sp-5) var(--sp-6);
    border-bottom: 1px solid var(--line);
    background: var(--surface-raised);
}

.filterdrawer__title {
    margin: 0;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    font-weight: 400;
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.filterdrawer__close {
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    cursor: pointer;
    color: var(--text-heading);
    transition: border-color var(--ease);
}

.filterdrawer__close:hover {
    border-color: var(--ink40);
}

.filterdrawer__body {
    flex: 1;
    overflow-y: auto;
    padding: var(--sp-2) var(--sp-6) var(--sp-6);
}

.filterdrawer__foot {
    border-top: 1px solid var(--line);
    padding: var(--sp-5) var(--sp-6);
    background: var(--surface-raised);
    display: flex;
    gap: var(--sp-3);
}

.filterdrawer__btn {
    flex: 1;
    height: 44px;
}

/* The facet inside the drawer. */
.pfilter--drawer {
    border-top: 0;
    border-bottom: 1px solid var(--line);
    padding: 15px 0;
}

.pfilter--drawer .pfilter__content {
    gap: 13px;
}

.pfilter--drawer .pfilter__opt {
    gap: 11px;
    min-height: 24px;
    font-size: var(--fs-body);
}

.pfilter--drawer .pfilter__box,
.pfilter--drawer .pfilter__input {
    width: 20px;
    height: 20px;
}

.pfilter--drawer .pfilter__check {
    width: 13px;
    height: 13px;
}

/* A finger needs more than a mouse, thus the rail and the two inputs are
   higher here than in the sidebar. */
.prange--drawer .prange__rail {
    height: 46px;
}

.prange--drawer .prange__input {
    height: 44px;
}

/* --------------------------------------------------------------------------
   The bar of active filters, above the product grid

   `product-list.js` writes this bar again after every answer of the server. It
   builds each chip with `document.createElement` and it takes the class list
   from the attribute `data-chip-class` on the chip wrapper; without that
   attribute it falls back to a list of Tailwind names in the script. The
   wrapper in the template therefore carries
   `data-chip-class="filterbar__chip"`. That attribute is the extension point
   of the script, thus no JavaScript changed.

   The `svg` of the cross is a constant in that same script, and it carries the
   Tailwind class `size-3.5`. The rule below names the `svg` inside the chip, so
   it has one element more than that class and it wins for a chip of the server
   and for a chip of the script alike.

   The script also adds and removes the Tailwind class `hidden` on the bar and
   on the clear button, so neither rule here names `display` on its own; the
   `.hidden` rules stand next to them with one class more, the same as
   `.pfilter__content`.

   The handoff names 10 and 8 pixels for the two gaps. The scale of the theme
   holds 9 and 6, and the scale wins: a value of the handoff maps to the token
   that is nearest to it. A page with its own numbers is a page that drifts.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   `.catpath` — the category path above the product grid

   The chips repeat the path list of the sidebar. The first chip is the root
   row, and it has no remove button. Every other chip removes its own category.
   -------------------------------------------------------------------------- */

.catpath {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-5);
}

.catpath__label {
    margin-right: var(--sp-2);
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.catpath__sep {
    color: var(--text-muted);
}

.catpath__chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 4px var(--sp-3);
    border: 1px solid var(--line);
    border-radius: var(--r1);
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--text-body);
    transition: background var(--ease), color var(--ease);
}

.catpath__chip:hover {
    background: var(--surface-raised);
    color: var(--text-heading);
}

.catpath__chip--on {
    background: var(--text-heading);
    border-color: var(--text-heading);
    color: var(--text-on-dark);
}

.catpath__chip--on:hover {
    background: var(--text-heading);
    color: var(--text-on-dark);
}

.catpath__chip svg {
    width: 11px;
    height: 11px;
    flex-shrink: 0;
}

.filterbar {
    margin-bottom: var(--sp-7);
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    border-bottom: 1px solid var(--line);
    padding-bottom: var(--sp-5);
}

.filterbar:not(.hidden) {
    display: flex;
}

.filterbar__total {
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.filterbar__chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
}

.filterbar__chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 4px var(--sp-3);
    background: var(--accent-tint);
    color: var(--accent-deep);
    border: 1px solid rgba(58, 110, 232, .28);
    border-radius: var(--r1);
    cursor: pointer;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    transition: background var(--ease);
}

.filterbar__chip:hover {
    background: rgba(58, 110, 232, .18);
}

.filterbar__chip svg {
    width: 11px;
    height: 11px;
    flex-shrink: 0;
}

.filterbar__clear {
    margin-left: auto;
    background: transparent;
    border: 0;
    cursor: pointer;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-secondary);
    transition: color var(--ease);
}

.filterbar__clear:hover {
    color: var(--text-heading);
}

.filterbar__clear.hidden {
    display: none;
}

/* --------------------------------------------------------------------------
   Product detail — the page frame, the gallery, and the head of the details
   column. Handoff: design/Product detail.dc.html.

   The buy form, the specifications, the accordions, the tabs, the upsells and
   the sticky bar of a telephone are the next two parts of S8; they get their
   own blocks below this one.
   -------------------------------------------------------------------------- */

.pdp {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: clamp(34px, 4.5vw, 60px);
    align-items: start;
}

/* The handoff writes 20px here. The spacing scale steps 18 -> 22, thus no
   token holds this value and the literal stays (shopper#219, comment 18227). */
.pdp__details {
    display: grid;
    gap: 20px;
}

/* The gallery ------------------------------------------------------------- */

.pdpgallery {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
}

.pdpgallery__stage {
    position: relative;
    border: 1px solid var(--line);
    border-radius: var(--r3);
    background: var(--boneDeep);
    overflow: hidden;
}

/* Without the setting the stage takes the shape of the picture. With it the
   stage is a square, the same square as the product card and the thumbnails. */
.pdpgallery__img {
    display: block;
    width: 100%;
    object-fit: contain;
    cursor: zoom-in;
}

.pdpgallery--square .pdpgallery__img {
    aspect-ratio: 1 / 1;
    object-fit: cover;
}

.pdpgallery__thumbs {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--sp-4);
}

/* `product-gallery.js` writes the class `on` on the active thumbnail. That is
   the whole contract, thus the shared script needs no change here. The script
   also paints a ring span when it finds one; core has no ring, so the span is
   gone and the script skips that half by itself. */
.pdpgallery__thumb {
    position: relative;
    height: 88px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--r2);
    background: var(--boneDeep);
    overflow: hidden;
    cursor: pointer;
    transition: border-color var(--ease);
}

/* The row under the large image. A thumbnail is 88 pixels high by itself; the
   setting makes it a square that grows with the column. */
.pdpgallery__thumb img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.pdpgallery--square .pdpgallery__thumb {
    height: auto;
    aspect-ratio: 1 / 1;
}

.pdpgallery--square .pdpgallery__thumb img {
    object-fit: cover;
}

.pdpgallery__thumb:hover {
    border-color: var(--ink40);
}

.pdpgallery__thumb.on {
    border-color: var(--r500);
}

.pdpgallery__thumb:focus-visible {
    outline: 2px solid var(--r500);
    outline-offset: 2px;
}

/* The head of the details column ------------------------------------------ */

.pdphead__meta {
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.pdphead__title {
    margin: 10px 0 0;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: clamp(28px, 3.6vw, 40px);
    line-height: 1.05;
    letter-spacing: -.03em;
    text-wrap: balance;
}

.pdpstatus {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-4);
}

/* Both children are conditional: the badge is absent when the shop does not
   manage stock, the article number when the product has no SKU. An empty row
   would still take the gap of the details column, thus it goes away. `:empty`
   cannot do this — the row holds whitespace — so the test is on element
   children. */
.pdpstatus:not(:has(*)) {
    display: none;
}

.pdpstatus__sku {
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--text-muted);
}

/* The price --------------------------------------------------------------- */

.pdpprice {
    border-top: 1px solid var(--ink20);
    border-bottom: 1px solid var(--line);
    padding: var(--sp-6) 0;
}

.pdpprice__now {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 38px;
    line-height: 1;
    letter-spacing: -.035em;
}

.pdpprice__net {
    margin: 8px 0 0;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    color: var(--text-muted);
}

.pdpprice__net:empty {
    display: none;
}

.pdpprice__spinner {
    display: inline-block;
    width: 22px;
    height: 22px;
    animation: pdpspin 1s linear infinite;
}

@keyframes pdpspin {
    to { transform: rotate(360deg); }
}

/* The bulk price tiers ---------------------------------------------------- */

.pdptiers {
    margin: var(--sp-5) 0 0;
    border-top: 1px solid var(--lineSoft);
    padding-top: 14px;
}

.pdptiers.hidden {
    display: none;
}

.pdptiers__label {
    display: block;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    font-weight: 400;
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.pdptiers__label p,
.pdptiers__foot p {
    margin: 0;
}

.pdptiers__label strong,
.pdptiers__foot strong {
    font-weight: 400;
}

.pdptiers__lines {
    display: grid;
    gap: 5px;
    margin-top: var(--sp-3);
    font-size: var(--fs-body-sm);
    color: var(--text-body);
    max-width: 34ch;
}

.pdptiers__foot {
    display: block;
    margin-top: var(--sp-3);
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

/* The scheduled-order button ----------------------------------------------

   A hook writes this button below the buy form, thus the wrapper carries the
   gap that the form itself cannot give. The disabled look comes from
   `.btn[disabled]`; the mirror script sets the attribute and no class. */

.schedbtn {
    margin-top: var(--sp-4);
}

/* The short description --------------------------------------------------- */

.pdpintro {
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    color: var(--text-body);
    max-width: 48ch;
    text-wrap: pretty;
}

.pdpintro > :first-child { margin-top: 0; }
.pdpintro > :last-child { margin-bottom: 0; }

/* The buy form ------------------------------------------------------------

   The handoff writes a control height of 46px on this page, where the design
   system stands on `--h-control` (44px). The page specification wins here, and
   the stepper and the submit button both take the 46. */

.pdpform {
    display: grid;
    gap: 20px;
}

/* One property of a variation: a head with the name and the number of options,
   and a list of choices below it. */

.pdpprop__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
}

.pdpprop__title {
    margin: 0;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    font-weight: 400;
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.pdpprop__note {
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    color: var(--text-muted);
}

.pdpprop__set {
    margin: var(--sp-4) 0 0;
    padding: 0;
    border: 0;
}

.pdpprop__list {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.pdpprop__list--swatches {
    align-items: center;
    gap: var(--sp-4);
}

/* The card of one choice. `active-ring` is the state class of the shared
   script, and `product-variation-detail.cy.js` asserts it for all three
   themes. Thus core paints that name and does not invent one of its own. */

.pdpchoice {
    position: relative;
    display: grid;
    gap: 3px;
    min-width: 104px;
    padding: 10px 14px;
    border: 1px solid var(--line);
    border-radius: var(--r2);
    background: var(--paper);
    color: var(--text-body);
    cursor: pointer;
    transition: border-color var(--ease), background var(--ease), color var(--ease);
}

.pdpchoice:hover {
    border-color: var(--ink40);
    color: var(--ink);
}

.pdpchoice__input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.pdpchoice__label {
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
}

.pdpchoice__meta {
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    color: var(--text-muted);
}

.pdpchoice.active-ring {
    border-color: var(--r500);
    background: var(--accentSoft);
    color: var(--ink);
}

.pdpchoice.active-ring .pdpchoice__meta {
    color: var(--r600);
}

.pdpchoice:focus-within {
    outline: 2px solid var(--r500);
    outline-offset: 2px;
}

/* A colour or an image is a round swatch, not a card. */

.pdpswatch {
    position: relative;
    display: grid;
    place-items: center;
    padding: 3px;
    border: 1px solid transparent;
    border-radius: 50%;
    cursor: pointer;
    transition: border-color var(--ease);
}

.pdpswatch:hover {
    border-color: var(--ink40);
}

.pdpswatch.active-ring {
    border-color: var(--r500);
}

.pdpswatch:focus-within {
    outline: 2px solid var(--r500);
    outline-offset: 2px;
}

.pdpswatch__dab {
    width: 32px;
    height: 32px;
    border: 1px solid var(--line);
    border-radius: 50%;
}

.pdpswatch__img {
    width: 44px;
    height: 44px;
    border: 1px solid var(--line);
    border-radius: 50%;
    object-fit: cover;
}

/* The buy row: the stepper and the submit button next to each other. */

.pdpbuy {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: var(--sp-4);
}

.pdpbuy__submit {
    flex: 1;
    min-width: 200px;
    height: 46px;
}

.pdpbuy__note {
    margin: 0;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

/* The quantity stepper. `shared/quantity-update.blade.php` of core draws it,
   and the cart uses the same partial, thus these class names are not tied to
   the product page. `/js/quantity-update.js` reads the data attributes; it
   never reads a class. */

.qty {
    display: inline-flex;
    align-items: stretch;
    overflow: hidden;
    max-width: 208px;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    background: var(--paper);
}

.qty__btn {
    width: 44px;
    height: 46px;
    padding: 0;
    border: 0;
    background: transparent;
    font-size: 17px;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
    transition: background var(--ease), color var(--ease);
}

.qty__btn--dec {
    border-right: 1px solid var(--line);
}

.qty__btn--inc {
    border-left: 1px solid var(--line);
}

.qty__btn:hover:not(:disabled) {
    background: var(--bone);
    color: var(--ink);
}

.qty__btn:disabled {
    opacity: .45;
    cursor: not-allowed;
}

.qty__input {
    width: 60px;
    height: 46px;
    border: 0;
    background: transparent;
    text-align: center;
    font-family: var(--mono);
    font-size: 15px;
    color: var(--ink);
    appearance: textfield;
    -moz-appearance: textfield;
}

.qty__input::-webkit-outer-spin-button,
.qty__input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.qty__input:disabled {
    opacity: .45;
    cursor: not-allowed;
}

/* The compact stepper of the buy bar of a telephone. The handoff draws it two
   pixels shorter than the stepper of the form, with narrower buttons, because
   the bar must hold three things on one row. */

.qty--compact {
    max-width: none;
}

.qty--compact .qty__btn {
    width: 36px;
    height: 44px;
    font-size: 16px;
}

/* The number of the compact stepper. It is a span, not an input — the reason
   stands in products/show/sticky-buy.blade.php. */

.qty--compact .qty__input {
    width: 46px;
    height: 44px;
    font-size: 14px;
}

.qty__num {
    display: grid;
    place-items: center;
    width: 38px;
    height: 44px;
    font-family: var(--mono);
    font-size: 14px;
    color: var(--ink);
}

/* The buy bar of a telephone ----------------------------------------------

   A fixed row at the bottom of the screen with the price, a stepper and the
   add-to-cart button. It appears below 640px and the buy row of the details
   column goes away at the same width, thus the page has one set of controls at
   every size. The bar itself holds no logic; see the partial.

   The spacer stands before the bar in the document. The bar is fixed, so it
   would cover the end of the page. The two share the same media query, thus
   the room is there when the bar is there and gone when the bar is gone.
   -------------------------------------------------------------------------- */

.stickybuy {
    display: none;
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: var(--z-stickybuy);
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    background: var(--paper);
    border-top: 1px solid var(--line);
}

.stickybuy__figures {
    display: grid;
    gap: 1px;
    min-width: 78px;
}

.stickybuy__price {
    font-family: var(--font-display);
    font-size: 18px;
    font-weight: var(--fw-medium);
    letter-spacing: -.02em;
}

.stickybuy__stock {
    font-family: var(--mono);
    font-size: 9px;
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--text-muted);
}

.stickybuy__submit {
    flex: 1;
    height: 44px;
}

.stickybuy__spacer {
    display: none;
}

/* The specifications ------------------------------------------------------

   A definition list of two columns. The key column is mono and uppercase, the
   value column is body type. Below 640px the two columns become one, which is
   the rule `[data-r="specs-row"]` of the handoff. */

.pdpspecs {
    border-top: 1px solid var(--ink20);
    padding-top: var(--sp-5);
}

.pdpspecs__title {
    margin: 0;
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    font-weight: 400;
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.pdpspecs__list {
    display: grid;
    margin: var(--sp-4) 0 0;
}

.pdpspecs__row {
    display: grid;
    grid-template-columns: 1fr 1.6fr;
    gap: var(--sp-4);
    padding: 10px 0;
    border-bottom: 1px solid var(--lineSoft);
}

.pdpspecs__key {
    font-family: var(--mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--text-muted);
}

.pdpspecs__val {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
    margin: 0;
    font-size: var(--fs-body-sm);
    color: var(--text-body);
}

.pdpspecs__pair {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
}

.pdpspecs__dab {
    width: 20px;
    height: 20px;
    border: 1px solid var(--line);
    border-radius: 50%;
}

.pdpspecs__img {
    width: 32px;
    height: 32px;
    border: 1px solid var(--line);
    border-radius: 50%;
    object-fit: cover;
}

.pdpspecs__sep {
    color: var(--text-muted);
}

/* The accordions ---------------------------------------------------------

   The handoff opens and closes each one with a `+` and a `-` glyph in the mono
   face, not with an icon. */

.pdpdisc__item {
    border-bottom: 1px solid var(--lineSoft);
}

.pdpdisc__btn {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-6);
    width: 100%;
    padding: 14px 0;
    border: 0;
    background: transparent;
    color: var(--ink);
    font-family: inherit;
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    text-align: left;
    cursor: pointer;
}

.pdpdisc__btn:focus-visible {
    outline: 2px solid var(--r500);
    outline-offset: 2px;
}

.pdpdisc__glyph {
    font-family: var(--mono);
    font-size: 15px;
    line-height: 1;
    color: var(--text-muted);
}

.pdpdisc__panel {
    margin: 0 0 var(--sp-5);
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--text-muted);
    max-width: 58ch;
}

.pdpdisc__panel > :first-child { margin-top: 0; }
.pdpdisc__panel > :last-child { margin-bottom: 0; }

/* The tabs ----------------------------------------------------------------

   The handoff puts the tab block UNDER the grid, at the width of the gallery
   column. It writes `calc(58.33% - 28px)` for that width, which holds only when
   the gap is 56px. The gap of `.pdp` is a clamp, thus that literal misses the
   column edge by some pixels at most widths. The rule below says the same thing
   with the numbers of the grid itself — 7 of 12 parts of the space that is left
   after the gap — and it aligns at every width. Below 1000px the grid is one
   column and this block fills the page.

   The state comes out of `aria-selected`, which `/js/product-tabs.js` sets. That
   script is shared with the two other themes and it also writes Tailwind class
   names on the button. Core does not read those, and it does not render them:
   the template writes `aria-selected` only. The utility classes that the script
   adds after a click paint nothing, because these rules stand later in the
   cascade and win on equal specificity. */

.pdptabs {
    width: calc((100% - clamp(34px, 4.5vw, 60px)) * 7 / 12);
    margin-top: clamp(44px, 5vw, 64px);
}

/* The tab names keep their line (`white-space: nowrap` on the tab), thus four
   long names are wider than a telephone. The strip scrolls in itself; without
   the `overflow-x` the page scrolls sideways instead — measured at 390: the
   document was 558 wide against a viewport of 375. The scrollbar chrome stays
   hidden, because the strip is only one row high and the bar covers a tab. */
.pdptabs__strip {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid var(--ink20);
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.pdptabs__strip::-webkit-scrollbar {
    display: none;
}

.pdptabs__tab {
    margin-bottom: -1px;
    padding: 11px 14px;
    border: 0;
    /* Every tab keeps the same border, thus the strip does not move 1 pixel
       when a tab becomes the selected one. Only the colour changes. */
    border-bottom: 3px solid transparent;
    background: transparent;
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--ease), border-color var(--ease);
    white-space: nowrap;
}

.pdptabs__tab:hover {
    color: var(--ink);
}

.pdptabs__tab[aria-selected="true"] {
    border-bottom-color: var(--r500);
    color: var(--ink);
}

.pdptabs__tab:focus-visible {
    outline: 2px solid var(--r500);
    outline-offset: 2px;
}

.pdptabs__panel {
    display: grid;
    gap: var(--sp-4);
    max-width: 66ch;
    padding: var(--sp-8) 0 0;
    font-size: var(--fs-body-lg);
    line-height: var(--lh-body);
    color: var(--text-body);
}

/* `/js/product-tabs.js` hides a panel with the Tailwind class `hidden`. This
   rule carries one more class than the rule above it, thus it wins over the
   `display: grid` and the shared script keeps working untouched. */
.pdptabs__panel.hidden {
    display: none;
}

.pdptabs__panel > :first-child { margin-top: 0; }
.pdptabs__panel > :last-child { margin-bottom: 0; }

/* The upsell section ------------------------------------------------------

   A heading with a rule under it, and a row of the product cards of S7. The
   handoff puts a link to the catalogue on the right of the heading. The upsell
   partial gets no category URL, only the names, thus that link is absent here
   (shopper#219, S8 part 3b). */

.upsells {
    margin-top: clamp(48px, 6vw, 72px);
}

.upsells__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-6);
    border-bottom: 1px solid var(--ink20);
    padding-bottom: var(--sp-4);
}

.upsells__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 22px;
    letter-spacing: -.025em;
}

.upsells__grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 20px;
    margin-top: var(--sp-7);
}

/* --------------------------------------------------------------------------
   The shopping cart — "Cart.dc.html"

   Two columns: the lines on the left, the order summary on the right. The
   summary stays in view while the list scrolls. Below 1000 pixels the two
   become one column and the summary loses its sticky position. That rule
   stands in the responsive section at the end of this file.

   The empty state uses the pattern `empty`, and a line without an image uses
   the pattern `imgph`. This block adds nothing for either of them.

   `.alertbar` is the warning bar of the handoff. It stands here and not with
   the patterns, because the cart page is the page that draws it. The checkout
   page includes the same partial (ticket #219, S9).
   -------------------------------------------------------------------------- */

.cartpage__title {
    margin: 0 0 var(--sp-8);
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: clamp(30px, 4vw, 44px);
    line-height: 1.02;
    letter-spacing: -.035em;
}

.cartgrid {
    display: grid;
    grid-template-columns: minmax(0, 8fr) minmax(0, 4fr);
    gap: clamp(30px, 4vw, 48px);
    align-items: start;
}

.cartgrid__lines {
    min-width: 0;
}

/* The alert bar. `hidden` is the switch of the script in cart.show: the bar
   always stands in the page, and the script shows it when the order is below
   the minimum. */
.alertbar {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-4);
    padding: 13px 15px;
    margin-bottom: var(--sp-7);
    border: 1px solid var(--status-warn-line);
    border-radius: var(--r2);
    background: var(--status-warn-tint);
}

.alertbar.hidden {
    display: none;
}

.alertbar__mark {
    display: grid;
    place-items: center;
    flex: none;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--status-warn);
    color: var(--paper);
    font-size: var(--fs-caption);
    line-height: 1;
}

.alertbar__text {
    margin: 0;
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--ink80);
}

.alertbar--danger {
    border-color: var(--danger);
    background: var(--danger-ring);
}

.alertbar--danger .alertbar__mark {
    background: var(--danger);
}

.alertbar--ok {
    border-color: var(--status-ok-line);
    background: var(--status-ok-tint);
}

.alertbar--ok .alertbar__mark {
    background: var(--status-ok);
}

.alertbar--info {
    border-color: var(--r500);
    background: var(--accentSoft);
}

.alertbar--info .alertbar__mark {
    background: var(--r500);
}

.cartlines {
    margin: 0;
    padding: 0;
    list-style: none;
    border-top: 1px solid var(--ink20);
}

.cartline {
    display: grid;
    grid-template-columns: 116px minmax(0, 1fr);
    gap: var(--sp-7);
    padding: var(--sp-7) 0;
    border-bottom: 1px solid var(--line);
}

.cartline__media {
    aspect-ratio: 1 / 1;
}

.cartline__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border: 1px solid var(--line);
    border-radius: var(--r2);
}

.cartline__body {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    min-width: 0;
}

.cartline__top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-6);
}

.cartline__info {
    min-width: 0;
}

.cartline__title {
    margin: 0;
    font-size: var(--fs-body-lg);
    font-weight: var(--fw-medium);
    line-height: var(--lh-tight);
}

.cartline__title a:hover {
    color: var(--r600);
}

.cartline__props {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    margin: var(--sp-2) 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--ink40);
}

.cartline__unit {
    margin: 10px 0 0;
    font-size: var(--fs-body-sm);
    color: var(--ink80);
}

.cartline__net {
    margin: 3px 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    color: var(--ink40);
}

/* The dot takes the colour of the line, thus one modifier colours both. */
.cartline__stock {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin: 10px 0 0;
    font-size: var(--fs-caption);
    line-height: 1;
}

.cartline__stockdot {
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

.cartline__stock--ok {
    color: var(--status-ok);
}

.cartline__stock--warn {
    color: var(--status-warn);
}

.cartline__stock--danger {
    color: var(--danger);
}

.cartline__total {
    flex: none;
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 22px;
    letter-spacing: -.025em;
    white-space: nowrap;
}

.cartline__bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sp-5);
}

.cartline__hint {
    margin: 7px 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--status-warn);
}

.cartline__remove,
.coline__remove {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 0;
    border: 0;
    background: none;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
    cursor: pointer;
    transition: color var(--ease);
}

.cartline__remove:hover,
.coline__remove:hover {
    color: var(--danger);
}

.cartback {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    margin-top: var(--sp-7);
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--ink60);
    transition: color var(--ease);
}

.cartback:hover {
    color: var(--r600);
}

.cartsum {
    position: sticky;
    top: 92px;
    padding: var(--sp-8);
    border: 1px solid var(--line);
    border-radius: var(--r3);
    background: var(--paper);
}

.cartsum__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 20px;
    letter-spacing: var(--ls-h3);
}

.cartsum__rows {
    display: grid;
    gap: var(--sp-4);
    margin: var(--sp-7) 0 0;
}

.cartsum__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
}

.cartsum__row dt {
    font-size: var(--fs-body-sm);
    color: var(--ink80);
}

.cartsum__row dd {
    margin: 0;
    font-family: var(--font-label);
    font-size: 13px;
}

.cartsum__row--ruled {
    padding-top: var(--sp-4);
    border-top: 1px solid var(--lineSoft);
}

.cartsum__row--total {
    padding-top: 14px;
    border-top: 1px solid var(--ink20);
}

.cartsum__row--total dt {
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.cartsum__row--total dd {
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 28px;
    line-height: 1;
    letter-spacing: -.035em;
}

.cartsum__checkout {
    height: 46px;
    margin-top: var(--sp-7);
}

.cartempty {
    margin-top: var(--sp-5);
}

/* --------------------------------------------------------------------------
   The checkout — "Checkout.dc.html"

   Two equal columns: the form on the left, the order summary on the right.
   Below 1000 pixels the two become one column. That rule stands in the
   responsive section at the end of this file.

   The page itself uses the patterns `field`, `field-row`, `check`, `option`
   and `panel`. This block adds only what those patterns do not hold: the page
   frame and the head of one block in the form (ticket #219, S10).
   -------------------------------------------------------------------------- */

.copage__title {
    margin: 0 0 var(--sp-8);
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: clamp(30px, 4vw, 44px);
    line-height: 1.02;
    letter-spacing: -.035em;
}

.cogrid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(34px, 4.5vw, 56px);
    align-items: start;
}

.cogrid__form {
    display: grid;
    gap: var(--sp-9);
    min-width: 0;
}

.cogrid__summary {
    display: grid;
    gap: var(--sp-8);
    align-content: start;
    min-width: 0;
}

/* The wait state, while the shipping recalculation runs. The script sets
   `aria-busy` on this column; the column dims and takes no click until the
   answer is there. The Tailwind template used `opacity-50` and
   `pointer-events-none` for this, and core has neither (ticket #219, S26). */
.cogrid__summary[aria-busy="true"] {
    opacity: .5;
    pointer-events: none;
}

.coblock__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 20px;
    letter-spacing: var(--ls-h3);
}

.coblock__fields {
    display: grid;
    gap: var(--sp-5);
    margin-top: var(--sp-6);
}

/* The two address cards stand below each other with the same rhythm as the
   blocks of the form around them. */
.coaddr {
    display: grid;
    gap: var(--sp-9);
}

/* The body of an address card. The card itself is the pattern `panel`, and
   `.panel .field__input` already gives the fields the colour of the card. */
.addrform {
    display: grid;
    gap: 14px;
}

.addrform .field-row {
    gap: 14px;
}

.addrform__same {
    padding-top: 14px;
    border-top: 1px solid var(--lineSoft);
}

/* The sentence that stands in the place of the delivery fields when the
   visitor says that the delivery address is the billing address. It is the
   body of the card, thus it takes `panel__body` for its padding and this block
   gives it the colour and the size of a note. */
.addrform__note {
    margin: 0;
    font-size: var(--fs-body-sm);
    color: var(--ink60);
}

.colines {
    margin: 0;
    padding: 0;
    list-style: none;
    border-top: 1px solid var(--ink20);
}

.coline {
    display: grid;
    grid-template-columns: 68px minmax(0, 1fr) auto;
    gap: 14px;
    align-items: start;
    padding: 14px 0;
    border-bottom: 1px solid var(--lineSoft);
}

.coline__media {
    aspect-ratio: 1 / 1;
}

.coline__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border: 1px solid var(--line);
    border-radius: var(--r2);
}

.coline__body {
    min-width: 0;
}

.coline__title {
    margin: 0;
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    line-height: var(--lh-tight);
}

.coline__title a:hover {
    color: var(--r600);
}

.coline__props,
.coline__price,
.coline__net {
    margin: 5px 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    color: var(--ink40);
}

.coline__props {
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
}

/* A warning on one line: a pre-order, or a hint about the quantity. The
   handoff draws it as a box with a triangle, thus the message reads as a
   remark about this one line and not as a part of the description. Q28 gave
   the box the second job as well, because a hint is the same class of
   message. */
.coline__note {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 9px 0 0;
    padding: 7px 9px;
    border: 1px solid var(--status-warn-line);
    border-radius: var(--r1);
    background: var(--status-warn-tint);
    font-size: var(--fs-caption);
    line-height: 1.45;
    color: var(--ink80);
}

.coline__noteicon {
    flex-shrink: 0;
    margin-top: 2px;
    color: var(--status-warn);
}

/* The right column. Q28 put the numbers in the order that the eye wants: the
   line total first, the stepper below it, and the remove link last. The
   stepper stays a stepper — the script of the page reads
   `[data-quantity-input]` to build the payload for the shipping cost. */
.coline__side {
    display: grid;
    gap: 8px;
    justify-items: end;
}

.coline__totalrow {
    margin: 0;
}

.coline__total {
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: var(--fs-body-lg);
    letter-spacing: -.02em;
    white-space: nowrap;
}

/* The order confirmation shows the same line, one size larger. That page has
   no stepper and no remove button in the line, so the row can carry a wider
   image and a larger total and still stay short. One component, two sizes:
   the checkout summary and the confirmation can never drift apart. */
.colines--spaced {
    margin-top: var(--sp-10);
}

.coline--lg {
    grid-template-columns: 108px minmax(0, 1fr) auto;
    gap: var(--sp-7);
    padding: 24px 0;
}

/* The brand and the article number, above the title. */
.coline__eyebrow {
    display: block;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.coline--lg .coline__title {
    margin: var(--sp-2) 0 0;
    font-size: var(--fs-body-lg);
}

/* On the confirmation the price of one piece is body text, not a mono number.
   The line is a receipt there, and the number that counts is the line total. */
.coline--lg .coline__price {
    margin: 10px 0 0;
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    color: var(--ink80);
}

.coline--lg .coline__net {
    margin: 3px 0 0;
}

.coline--lg .coline__props {
    margin: var(--sp-1) 0 0;
}

.coline--lg .coline__total {
    margin: 0;
    font-size: 20px;
    letter-spacing: -.025em;
}

/* The totals of the order -------------------------------------------------

   One card with the rows of the amounts. A row is a label on the left and a
   number on the right. A divider runs to the edge of the card, thus it takes
   a negative margin of the padding of the card. */

.cototals {
    margin: 0;
    padding: var(--sp-7);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r3);
    display: grid;
    gap: 13px;
}

.cototals__row {
    display: flex;
    justify-content: space-between;
    gap: var(--sp-4);
}

.cototals__label {
    font-size: var(--fs-body-sm);
    color: var(--ink80);
}

.cototals__value {
    margin: 0;
    font-family: var(--font-label);
    font-size: 13px;
}

/* The code of the coupon, next to the word Discount. */
.cototals__code {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    color: var(--ink40);
}

/* The amount that the coupon takes off. It is the one number in the card that
   carries the accent colour. */
.cototals__value--discount {
    color: var(--r600);
}

.cototals__rule {
    border-top: 1px solid var(--lineSoft);
    margin: 2px calc(var(--sp-7) * -1);
}

/* The divider reaches the edge of the card with a negative margin. The order
   confirmation puts the same rows in a bare block, where there is no padding
   to cancel. */
.cototals__rule--flush {
    margin-left: 0;
    margin-right: 0;
}

.cototals__total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
    padding-top: var(--sp-6);
    border-top: 1px solid var(--ink20);
}

.cototals__totalLabel {
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.cototals__totalValue {
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 30px;
    line-height: 1;
    letter-spacing: -.035em;
}

/* The confirmation is the end of the road. Its total is the largest number on
   the page, thus it is two pixels above the one in the checkout card. */
.cototals__totalValue--lg {
    font-size: 32px;
}

/* The coupon field, in the same card. */

.cocoupon__applied {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
}

.cocoupon__code {
    font-family: var(--font-label);
    font-size: 13px;
}

.cocoupon__drop {
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    font-size: var(--fs-caption);
    color: var(--ink60);
    transition: color var(--ease);
}

.cocoupon__drop:hover {
    color: var(--danger);
}

.cocoupon__row {
    display: flex;
    gap: var(--sp-3);
}

/* The button stands next to the field, thus it takes the height of a field
   and not the height of a control. */
.cocoupon__apply {
    flex: none;
    height: var(--h-field);
}

.cocoupon__error {
    margin: var(--sp-3) 0 0;
}

/* The comments, the terms and the submit button --------------------------- */

.coterms__error {
    margin: var(--sp-3) 0 0;
}

/* The handoff writes a height of 50 pixels on this button, where the design
   system stands on `--h-control` (44px). The page specification wins here,
   because this button closes the order. */
.cosubmit {
    height: 50px;
}

/* The shipping method and the payment method, side by side. Below 980 pixels
   they stand one above the other. Both lists use the pattern `option`; the
   script of the page builds the rows of the shipping list, thus that markup
   lives in `checkout/show.blade.php` and not in a template. */
.comethods {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-7);
}

.comethods__title,
.cocomments__title {
    margin: 0 0 var(--sp-4);
    font-family: var(--font-label);
    font-weight: 400;
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.comethods__list {
    display: grid;
    gap: 8px;
}

.comethods__error {
    margin: 8px 0 0;
}

/* The shipping list waits for the answer of the server. The box has the height
   of one option row, thus the column does not jump when the rows arrive. The
   global rule for reduced motion stops the pulse. */
.comethods__wait {
    height: 49px;
    background: var(--boneDeep);
    border: 1px solid var(--lineSoft);
    border-radius: var(--r2);
    animation: comethods-wait 1.5s ease-in-out infinite;
}

@keyframes comethods-wait {
    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: .55;
    }
}

/* The bar carries a margin for the page that always shows it. Here it stands
   at the end of a column, thus that margin has nothing to separate. */
.comethods .alertbar {
    margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   The order confirmation — "Order confirmation.dc.html"

   The page has four parts: the head with the status badge, a strip of
   information cards, the list of the ordered items, and the totals. This
   block holds the first two (ticket #219, S11 part 1).

   The card strip has no border between the cards of its own. It stands on a
   background of the line colour and holds its cards apart with a gap of one
   pixel. The gap shows the background, thus every hairline is exact, also
   where a row ends. `overflow: hidden` keeps the corners of the strip round.
   -------------------------------------------------------------------------- */

.okhead {
    max-width: 62ch;
}

.okhead__title {
    margin: 20px 0 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: clamp(32px, 4.6vw, 52px);
    line-height: 1;
    letter-spacing: -.035em;
}

.okhead__lead {
    margin: var(--sp-5) 0 0;
    font-size: var(--fs-body-lg);
    color: var(--ink80);
}

.okhead__text {
    margin: 10px 0 0;
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    color: var(--ink60);
}

.okhead__reason {
    margin: var(--sp-4) 0 0;
    font-size: var(--fs-body-sm);
    color: var(--danger);
}

.okhead__retry {
    margin-top: var(--sp-8);
}

.okcards {
    margin-top: 40px;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1px;
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: var(--r3);
    overflow: hidden;
}

/* The delivery address is one card of the four. A page that does not show it
   divides the width over three. */
.okcards--three {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.okcard {
    background: var(--paper);
    padding: 20px;
}

.okcard__title,
.okcomments__title {
    margin: 0 0 var(--sp-4);
    font-family: var(--font-label);
    font-weight: 400;
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.okcard__list {
    margin: 0;
    display: grid;
    gap: var(--sp-4);
}

.okcard__term {
    font-size: var(--fs-caption);
    color: var(--ink40);
}

.okcard__value {
    margin: 4px 0 0;
    font-size: var(--fs-body-sm);
}

/* The order number is the one value that the visitor reads back to us. */
.okcard__value--number {
    font-family: var(--font-label);
    font-size: 13px;
    color: var(--r600);
}

.okcard__value--ref {
    font-family: var(--font-label);
    font-size: var(--fs-caption);
}

/* An address that a customer typed can hold a word without a space in it. */
.okcard__value--mail {
    word-break: break-all;
}

/* The lines of an address, one below the other. The order confirmation and the
   order detail both show an address, thus the block stands on its own and is
   not a part of the card of the confirmation. */
.addrlines {
    margin: 0;
    font-style: normal;
    font-size: var(--fs-body-sm);
    line-height: 1.75;
    color: var(--ink80);
}

.addrlines span {
    display: block;
}

/* The comments of the customer stand below the strip, in a box with a dashed
   edge. The dash says that the text comes from the customer and not from us. */
.okcomments {
    margin-top: var(--sp-6);
    padding: var(--sp-5) 18px;
    border: 1px dashed var(--ink20);
    border-radius: var(--r2);
}

.okcomments__title {
    margin-bottom: var(--sp-2);
}

.okcomments__text {
    margin: 0;
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--ink80);
}

/* The totals and the links of the order confirmation ----------------------

   The rows themselves are the rows of the checkout card, thus the two pages
   show one number in one shape. Only the frame is different: the confirmation
   has no card, it puts the block on the right of the page, and it lets the
   block become full width on a narrow screen. */

.oktotals {
    margin-left: auto;
    width: 58%;
    padding-top: var(--sp-8);
}

.oktotals__list {
    margin: 0;
    display: grid;
    gap: var(--sp-4);
}

/* The two links below the totals. One goes back into the catalogue, one goes
   to the order in the account. A guest has no account, so the row can hold
   one link, and then that link keeps the right edge of the totals. */
.oklinks {
    margin-top: 36px;
    border-top: 1px solid var(--lineSoft);
    padding: var(--sp-6) 0;
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    justify-content: space-between;
}

.oklinks--one {
    justify-content: flex-end;
}

.oklinks__link {
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--ink60);
}

.oklinks__link:hover {
    color: var(--ink);
}

/* The link that carries the visitor on. It takes the accent colour, thus it
   reads as the next step and the other link reads as a reference. */
.oklinks__link--go {
    color: var(--r600);
}

.oklinks__link--go:hover {
    color: var(--r500);
}

/* The account pages -------------------------------------------------------
   Every page behind "my account" has one shape: a column of 240 pixels that
   holds the navigation, and the page next to it. The grid stands in the layout
   `layouts/account`, thus a page cannot forget it and the pages cannot drift
   apart. `align-items: start` keeps the navigation at the top of a long page. */
.accgrid {
    display: grid;
    grid-template-columns: 240px minmax(0, 1fr);
    gap: clamp(28px, 4vw, 52px);
    align-items: start;
}

/* The head of an account page. The size of the title is a clamp, thus the
   handoff needs no rule for it at 640 pixels: the clamp is at its lower stop
   of 30 pixels before the screen is that narrow. */
.acchead {
    margin-bottom: var(--sp-8);
}

.acchead__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: clamp(30px, 3.8vw, 42px);
    line-height: 1.02;
    letter-spacing: -.035em;
}

.acchead__text {
    margin: var(--sp-4) 0 0;
    font-size: var(--fs-body);
    color: var(--ink60);
}

/* The cards of an account page, one below the other. */
.acccards {
    display: grid;
    gap: 20px;
}

/* The dashboard of the account --------------------------------------------
   The three blocks of "Dashboard.dc.html" stand below each other with one
   distance. The handoff writes 30 pixels between the first two and 26 between
   the last two; one number for both keeps the page one idea. */
.accstack {
    display: grid;
    gap: 30px;
}

/* The three numbers stand in the pattern `hairgrid`: the cells touch and the
   line between them is the gap of the grid. */
.accstats {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.accstat__label {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

/* The design writes a note below each number, for example "since January
   2026". The dashboard has no data for such a note, thus there is no rule for
   one. */
.accstat__value {
    display: block;
    margin-top: 10px;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 34px;
    line-height: 1;
    letter-spacing: -.035em;
}

/* The three tiles below the orders. They carry the visitor on, thus the first
   one takes the accent colour and the other two take a line. */
.acctiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 14px;
}

.acctile {
    display: grid;
    gap: var(--sp-2);
    padding: var(--sp-6);
    border: 1px solid var(--line);
    border-radius: var(--r3);
    background: var(--paper);
    transition: border-color var(--ease);
}

.acctile:hover {
    border-color: var(--ink40);
    color: var(--ink);
}

.acctile--go {
    border-color: var(--r500);
    background: var(--r500);
    color: var(--paper);
    transition: background var(--ease), border-color var(--ease);
}

.acctile--go:hover {
    border-color: var(--r600);
    background: var(--r600);
    color: var(--paper);
}

.acctile__title {
    font-size: var(--fs-h3);
    font-weight: var(--fw-medium);
    letter-spacing: var(--ls-h3);
}

.acctile__meta {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.acctile--go .acctile__meta {
    color: rgba(255, 255, 255, .72);
}

/* The account navigation --------------------------------------------------
   A card that says who is signed in, then the destinations, then the sign-out
   button. The same three parts as the menu in the site header, in a column. */
.accnav {
    display: grid;
    gap: var(--sp-7);
}

.accnav__card {
    border: 1px solid var(--line);
    border-radius: var(--r2);
    background: var(--paper);
    padding: 15px var(--sp-5);
}

.accnav__label {
    display: block;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

/* This line holds the first name, or the mail address when the account has no
   first name (Q30). A mail address has no space in it, thus it can push the
   column wider than the 240 pixels of the grid. This breaks it instead. */
.accnav__name {
    display: block;
    margin-top: var(--sp-2);
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    overflow-wrap: anywhere;
}

.accnav__list {
    display: grid;
}

.accnav__link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 11px var(--sp-4);
    border-radius: var(--r1);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    color: var(--ink80);
}

.accnav__link:hover {
    background: var(--paper);
    color: var(--ink);
}

/* The page the visitor is on. The soft accent holds the row, and the arrow
   takes the full accent colour. */
.accnav__link--on {
    background: var(--accentSoft);
    color: var(--r600);
}

.accnav__mark {
    color: var(--ink20);
}

.accnav__link--on .accnav__mark {
    color: var(--r500);
}

/* The sign-out button is not a destination, thus a line separates it from the
   list above it. */
.accnav__out {
    margin-top: 8px;
    padding-top: 10px;
    border-top: 1px solid var(--lineSoft);
}

.accnav__signout {
    width: 100%;
    padding: 11px var(--sp-4);
    border: 0;
    border-radius: var(--r1);
    background: transparent;
    text-align: left;
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    color: var(--ink60);
    cursor: pointer;
    transition: background var(--ease), color var(--ease);
}

.accnav__signout:hover {
    background: var(--paper);
    color: var(--ink);
}

/* The detail of one order -------------------------------------------------
   "Order detail.dc.html" puts a square with an arrow before the title, a bar
   with the status and the two actions below it, then the addresses, then a
   panel with the lines and the totals. */

.orderhead {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    margin-bottom: var(--sp-8);
}

/* The square that carries the visitor back to the list. It is 38 pixels, thus
   it is smaller than a button and it does not compete with the title. */
.orderhead__back {
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    background: var(--surface-raised);
    color: var(--ink);
    transition: border-color var(--ease);
}

.orderhead__back:hover {
    border-color: var(--ink40);
    color: var(--ink);
}

/* The number of the order is the title of the page. The clamp is the clamp of
   `acchead__title`, thus the two heads of the account are one size. */
.orderhead__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: clamp(26px, 3.2vw, 36px);
    line-height: 1.04;
    letter-spacing: -.035em;
}

.orderhead__meta {
    margin: 10px 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--ink40);
}

/* The bar with the status. The state stands on the left and the actions on the
   right; on a narrow screen the actions go below the state. */
.orderbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-5);
    flex-wrap: wrap;
    padding: var(--sp-6);
    border: 1px solid var(--line);
    border-radius: var(--r3);
    background: var(--surface-raised);
    margin-bottom: 20px;
}

.orderbar__badge {
    margin-top: 10px;
}

.orderbar__actions {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

/* The two addresses stand in the pattern `hairgrid`: the cells touch and the
   line between them is the gap of the grid. The delivery address is first,
   because that is the question the customer opens this page with. */
.orderaddr {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-bottom: var(--sp-8);
}

.orderaddr__title {
    display: block;
    margin: 0 0 var(--sp-4);
}

.orderaddr__note {
    display: block;
    margin: var(--sp-4) 0 0;
}

/* The lines of the order in a panel. The list is the component `coline` of the
   checkout and the confirmation; only the frame is different. The panel draws
   the line above the first row, thus the list gives its own top border up. */
.colines--panel {
    border-top: 0;
}

.coline--panel {
    grid-template-columns: 76px minmax(0, 1fr) auto;
    gap: var(--sp-5);
    padding: var(--sp-5) var(--sp-6);
    border-bottom-color: var(--lineSoft);
}

.coline--panel .coline__title {
    font-size: var(--fs-body);
}

.coline--panel .coline__total {
    font-size: var(--fs-body-lg);
}

/* The totals stand at the foot of the panel. The panel has the padding, thus
   the block gives its own up. */
.oktotals--flush {
    width: 62%;
    padding-top: 0;
}

.orderfoot {
    margin-top: var(--sp-7);
    display: flex;
    gap: var(--sp-4);
    flex-wrap: wrap;
}


/* The detail of one standing order ----------------------------------------
   "Scheduled order detail.dc.html" is the page of the order detail with one
   more bar in it: the schedule. Thus the head, the panel with the lines and
   the totals are the blocks of that page, and only the bar and the line get
   an element of their own here.

   The bar holds a state at the left and two controls at the right. A label
   with a badge below it is not as high as a label with an input below it, so
   the design lines the two up at the foot and not in the middle. That is the
   only difference with `orderbar`, thus it is a modifier and not a block. */

.orderbar--sched,
.orderbar--sched .orderbar__actions {
    align-items: flex-end;
}

/* The badges of the schedule stand in one row below the label. */
.orderbar__states {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-top: 10px;
}

/* This page writes the date to the server by itself. This word says that the
   write is busy, or that it is done. It says nothing else. */
.orderbar__flash {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

.orderbar__flash--ok {
    color: var(--status-ok);
}

/* A field in the bar is as wide as its control, and not as wide as the bar. */
.field--inline,
.field--inline .field__input {
    width: auto;
}

/* The datetime control of the design is mono, because it shows a number. */
.field__input--time {
    padding: 0 11px;
    font-family: var(--font-label);
    font-size: 13px;
}

/* A line of a standing order carries the stepper and the remove link below
   the text, where the order detail has neither. The checkout puts those two
   in `coline__side`; here the right column holds the total alone, thus the
   two elements stand in the body. */
.coline__actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-4);
    margin-top: var(--sp-4);
}

.coline__hint {
    margin: var(--sp-3) 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono-tight);
    text-transform: uppercase;
    color: var(--status-warn);
}

/* The note below the panel. It says that the page saves each change by
   itself, which is the one thing this page does that the order detail does
   not do. */
.schednote {
    margin: var(--sp-5) 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

/* --------------------------------------------------------------------------
   The "added to cart" dialog — `shared/cart-confirmation-modal.blade.php`

   The handoff has no page for this dialog. The body is made out of parts that
   the handoff does give: the receipt line of the order confirmation
   (`coline--lg`), the two buttons of `modal__actions` and the upsell block of
   the product page (`upsells`). Only the three rules below are new.
   -------------------------------------------------------------------------- */

.cartmodal {
    display: grid;
    gap: var(--sp-5);
}

/* The JavaScript of the dialog adds and removes the name `hidden` on the
   variation line, on the price and on the upsell block. All three stand in
   this wrapper, thus one rule holds all three. */
.cartmodal .hidden {
    display: none;
}

/* The upsell block starts a new part of a long page, thus it holds a large top
   margin. In a dialog the body gives the gap already. */
.cartmodal__upsells {
    margin-top: 0;
}


/* --------------------------------------------------------------------------
   Auth pages — "Auth pages.dc.html"
   -------------------------------------------------------------------------- */

/* Every auth page is one narrow column in the middle: the brand, one card, and
   one line below the card. `layouts.app` gives the cap and the gutter of the
   page, thus this block makes only the column. */
.authpage {
    max-width: 440px;
    margin: 0 auto;
}

.authpage__brand {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: var(--sp-3);
    margin-bottom: var(--sp-6);
}

.authpage__logo {
    max-height: 36px;
    width: auto;
}

.authpage__wordmark {
    font-size: 30px;
}

.authpage__suffix {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

/* The line below the card. It carries the help that a shop gives to a visitor
   who cannot get in. The translation has an empty default, thus a shop that
   sets no line gets no element. */
.authpage__foot {
    margin: 22px 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    text-align: center;
    color: var(--ink40);
}

/* The head of an auth card names the page. `.panel__label` is the mono kicker
   of an account card, and a title in the display face is a different thing,
   thus the head gets its own two elements. */
.panel__title {
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 22px;
    line-height: 1.1;
    letter-spacing: var(--ls-h3);
}

.panel__intro {
    margin: var(--sp-3) 0 0;
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
    color: var(--ink60);
}

/* A head that holds a title and an intro stands taller than a head that holds
   only the kicker. */
.panel__head--intro {
    padding-block: 20px;
}

/* A line of text that belongs to the whole form and not to one field: the
   "no account yet" line below the sign-in button, and the note below the
   button of the registration. */
.formgrid__note {
    grid-column: 1 / -1;
    margin: 0;
    font-size: var(--fs-body-sm);
    text-align: center;
    color: var(--ink60);
}

.formgrid__note--mono {
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
}

/* An alert bar inside a form grid. The grid gives the gap, thus the bar must
   not add a margin of its own. */
.formgrid > .alertbar {
    grid-column: 1 / -1;
    margin-bottom: 0;
}

/* The mark of the ok bar holds a check, not a character. */
.alertbar__mark svg {
    width: 10px;
    height: 10px;
}

/* A label with a link on its right, as the password label of the sign-in card
   carries "Forgot password?". The link is a sibling of the label and not a
   child, because a label must hold no second control. */
.field__labelrow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
}

.field__aside {
    font-size: var(--fs-caption);
    color: var(--r600);
    transition: color var(--ease);
}

.field__aside:hover {
    color: var(--ink);
}

/* Two buttons that stand next to each other, as the "resend" and the "sign
   out" of the verify card. Each takes one half of the row. */
.authpage__row {
    display: flex;
    gap: var(--sp-4);
}

.authpage__row > * {
    flex: 1 1 0;
}

/* A link that stands alone on a row of the form, under the button: "back to
   sign in" on the forgot-password card. */
.formgrid__back {
    grid-column: 1 / -1;
    justify-self: center;
    font-family: var(--font-label);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink40);
    transition: color var(--ease);
}

.formgrid__back:hover {
    color: var(--ink);
}

/* --------------------------------------------------------------------------
   Error page — "Error pages.dc.html"

   One block for the three pages 403, 404 and 500. The pages give it the same
   parts in the same order: a very large status code, a stamp with the state in
   two words, the title, one paragraph, and a row of buttons.

   Only the stamp changes with the code, thus only the stamp has modifiers.
   -------------------------------------------------------------------------- */

.errorpage {
    max-width: 720px;
    margin: 0 auto;
    padding: 20px 0 40px;
    text-align: center;
}

/* The code is decoration, not a heading. It stands in `--ink10`, thus it reads
   as a watermark behind the words and it does not compete with the title. */
.errorpage__code {
    display: block;
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: 150px;
    line-height: .84;
    letter-spacing: -.05em;
    color: var(--ink10);
}

.errorpage__stamp {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-5);
    padding: 5px 10px;
    border: 1px solid var(--line);
    border-radius: var(--r1);
    background: var(--boneDeep);
    color: var(--ink80);
    font-family: var(--mono);
    font-size: var(--fs-mono);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
}

/* The dot takes the colour of the words around it, thus a modifier of the
   stamp colours both in one rule. */
.errorpage__dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

/* 403 — the visitor is at the wrong door, and that is not an error of the
   shop. The stamp is amber and not red. */
.errorpage__stamp--warn {
    border-color: rgba(184, 147, 43, .35);
    background: rgba(184, 147, 43, .10);
    color: #B8932B;
}

/* 500 — the shop broke. The stamp is the danger colour. */
.errorpage__stamp--danger {
    border-color: rgba(154, 45, 31, .3);
    background: rgba(154, 45, 31, .07);
    color: var(--danger);
}

.errorpage__title {
    margin-top: 20px;
    font-size: clamp(30px, 4vw, 42px);
    line-height: 1.02;
    letter-spacing: -.035em;
}

.errorpage__text {
    max-width: 52ch;
    margin: var(--sp-5) auto 0;
    font-size: var(--fs-body-lg);
    line-height: var(--lh-body);
    color: var(--ink60);
}

.errorpage__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-4);
    margin-top: var(--sp-9);
}

/* --------------------------------------------------------------------------
   Responsive — the breakpoints of the handoff, "Responsive behaviour"
   -------------------------------------------------------------------------- */

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

@media (max-width: 620px) {
    /* The summary line drops to two columns. The total, the stepper and the
       remove link move under the text, and they keep their right edge. Three
       things do not always fit on one row of a telephone, thus they wrap. */
    .coline {
        grid-template-columns: 68px minmax(0, 1fr);
    }

    .coline__side {
        display: flex;
        flex-wrap: wrap;
        grid-column: 2;
        align-items: center;
        justify-content: flex-end;
        gap: var(--sp-4);
    }

    .sitefooter__cols {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 980px) {
    /* The two method columns of the checkout become one. */
    .comethods {
        grid-template-columns: minmax(0, 1fr);
    }

    /* The two addresses of the order detail go below each other. */
    .orderaddr {
        grid-template-columns: minmax(0, 1fr);
    }

    /* The card strip of the order confirmation halves. Both counts go to two,
       so a page with three cards leaves one card on the second row. */
    .okcards,
    .okcards--three {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* The totals of the confirmation give up the right column and take the
       whole width. */
    .oktotals {
        width: 100%;
    }

    /* The totals in the panel of the order detail keep their column. That panel
       stands in the account column, which is already the full width below 1000
       pixels, thus the block would look lost. It goes wide at 640. */
    .oktotals--flush {
        width: 62%;
    }

    .pageblocks__item--quarter {
        grid-column: span 6;
    }

    .pageblocks__item--threequarter {
        grid-column: span 12;
    }

    .siteheader__nav {
        display: none;
    }

    .siteheader__burger {
        display: grid;
    }

    /* The account menu goes with the navigation into the drawer. Below this
       width the bar keeps only the brand, the cart and the burger, and the
       drawer holds every account link. */
    .usermenu,
    a.siteheader__user {
        display: none;
    }
}

@media (max-width: 660px) {
    .pageblocks__item--threequarter,
    .pageblocks__item--half,
    .pageblocks__item--quarter {
        grid-column: span 12;
    }

    /* The bar becomes a column, and all three buttons get the full width. A
       thumb must reach every answer without a horizontal scroll. */
    .cookiebar {
        flex-direction: column;
        align-items: stretch;
        gap: var(--sp-6);
        padding-bottom: calc(var(--sp-6) + env(safe-area-inset-bottom));
    }

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

    .cookiebar__pair {
        grid-template-columns: 1fr;
    }

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

    /* The row is a column here, thus `flex: 1` of the row would work on the
       height: a basis of zero and a share of the free space flattens each
       button to a line. Each one takes its own height back. */
    .cookieprefs__actions .btn {
        flex: none;
    }

    .cookiefab {
        left: var(--sp-5);
        bottom: var(--sp-5);
    }

    h1 {
        font-size: 30px;
    }

    .field-row {
        grid-template-columns: 1fr;
    }

    .empty {
        padding: var(--sp-10) var(--sp-6);
    }
}

/* The handoff names 640 pixels for the toast stack, and it names no other
   block at that width. Below it the stack is the width of the screen. */
/* The handoff collapses the catalogue at 1000 pixels. That number is its own,
   not a Tailwind breakpoint, thus this rule stands at 1000 and not at 1023. */
@media (max-width: 1000px) {
    /* The two columns of the product page become one, and the gap loses its
       clamp. The handoff sets 34px flat below this width. */
    .pdptabs {
        width: 100%;
    }

    .pdp {
        grid-template-columns: minmax(0, 1fr);
        gap: 34px;
    }

    .catgrid {
        grid-template-columns: minmax(0, 1fr);
    }

    .catgrid__side {
        display: none;
    }

    .catfilterbtn {
        display: inline-flex;
    }

    /* The drawer replaces the sidebar, thus it appears at the same width. */
    .filterdrawer {
        display: block;
    }

    /* The cart collapses at the same width, and the summary stops sticking. */
    .cartgrid {
        grid-template-columns: minmax(0, 1fr);
        gap: 32px;
    }

    .cartsum {
        position: static;
    }

    /* The checkout collapses at the same width: the summary moves below the
       form instead of beside it. */
    .cogrid {
        grid-template-columns: minmax(0, 1fr);
        gap: 36px;
    }

    /* The account pages give up the navigation column. The navigation keeps
       its place above the page, because it is short. */
    .accgrid {
        grid-template-columns: minmax(0, 1fr);
        gap: 28px;
    }
}

@media (max-width: 640px) {
    /* 150 pixels of status code is wider than a telephone. The handoff puts
       it on 110 here. The title needs no rule: its clamp already stops at the
       30 pixels that the handoff asks for. */
    .errorpage__code {
        font-size: 110px;
    }

    /* The buy bar takes the bottom of the screen, and the buy row of the
       details column goes away. The handoff does the same: one set of controls
       at a time. The row stays in the document, because the form still reads
       its stepper and still sends its `name="quantity"` field. */
    .stickybuy,
    .stickybuy__spacer {
        display: flex;
    }

    .pdpbuy {
        display: none;
    }

    .stickybuy__spacer {
        height: 108px;
    }

    /* The round cookie button stands at the same bottom edge as the buy bar.
       It moves above that bar, thus neither one covers the other. Only a
       product page holds a buy bar: every other page keeps the button in its
       own corner, where it stood before this rule found it. */
    body:has(.stickybuy) .cookiefab {
        bottom: calc(64px + var(--sp-7));
    }

    /* The three numbers of the dashboard stand below each other. */
    .accstats {
        grid-template-columns: minmax(0, 1fr);
    }

    /* The two big faces of the product page step down on a telephone. */
    .pdphead__title {
        font-size: 28px;
    }

    /* The cards of the order confirmation become one column. */
    .okcards,
    .okcards--three {
        grid-template-columns: minmax(0, 1fr);
    }

    /* The order line loses its third column. The line total moves below the
       text, in the same column, because the image column is too narrow to
       hold an amount that must not break. */
    .coline--lg {
        grid-template-columns: 84px minmax(0, 1fr);
    }

    .coline--lg .coline__total {
        grid-column: 2;
        margin-top: 10px;
    }

    /* The totals of the order detail take the whole foot of the panel. */
    .oktotals--flush {
        width: 100%;
    }

    /* A cart line gets a smaller image, and its head becomes two rows, so the
       line total stands below the title and not beside it. */
    .cartline {
        grid-template-columns: 80px minmax(0, 1fr);
    }

    .cartline__top {
        flex-direction: column;
        gap: var(--sp-3);
    }

    .pdpprice__now {
        font-size: 32px;
    }

    .pdpspecs__row {
        grid-template-columns: minmax(0, 1fr);
        gap: 2px;
    }

    .upsells__grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .toaststack {
        top: var(--sp-4);
        right: var(--sp-4);
        left: var(--sp-4);
        /* `width` must go back to `auto`. The base rule holds `width: 100%`,
           and that wins from the pair `left` plus `right`, thus the stack kept
           the full width of the screen and hung over the right inset. */
        width: auto;
        max-width: none;
    }
}

/* The page editor blocks. The old templates used the Tailwind breakpoints
   `sm`, `md` and `lg`. Those are `min-width` rules, thus each one here stands
   at one pixel below its Tailwind value and the result is the same. */
@media (max-width: 767px) {
    /* `md:grid-cols-3` starts AT 768 pixels, thus the column layout stops at
       767. Below it the intro stands above the fields. */
    .formsplit {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--sp-7);
    }

    .formblock {
        padding: var(--sp-7);
    }

    .mediatext {
        flex-direction: column;
        gap: var(--sp-7);
    }

    .mediatext__media,
    .mediatext--third .mediatext__media,
    .mediatext--quarter .mediatext__media {
        width: 100%;
    }
}

@media (max-width: 1023px) {
    .gallery {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 639px) {
    /* The handoff stacks the head row at 640 pixels, so the title keeps its
       width and the tools stand below it. */
    .cathead {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--sp-5);
    }

    .cathead__title {
        font-size: 32px;
    }

    /* The handoff switches the product grid to two columns at 640 pixels. */
    .pgrid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--sp-5);
    }

    /* `sm:col-span-3` starts AT 640 pixels. Below it every field takes the
       whole line. */
    .formgrid > .field--half {
        grid-column: 1 / -1;
    }

    .gallery {
        grid-template-columns: minmax(0, 1fr);
    }

    /* The help balloon is 200px wide and it hangs from the icon. An icon that
       stands after a long label thus pushes the balloon past the right edge of
       a telephone, and the page gets a horizontal scroll bar. Below this
       breakpoint the balloon hangs from the field instead of from the icon, so
       it keeps the full width of the field and no more. `.field` carries
       `position: relative` for this. */
    .field__helpwrap {
        position: static;
    }

    .field__tip {
        left: 0;
        right: 0;
        min-width: 0;
    }
}
