/* Kässeli — the till's own stylesheet, loaded only on /kaesseli/.
 *
 * Two layouts live here. In a browser tab the page is an ordinary webtools page and the till is
 * revealed below it; launched from a home screen there is no browser UI left to share the screen
 * with, so the till takes all of it and the site's header and footer go away. `display-mode` does
 * that switch in CSS rather than in script, so the first paint is already right and it stays right
 * with the network off.
 */

:root {
    --k-gold: #c89a6b;
    --k-gold-strong: #a8763f;
    --k-discount: #d7263d;
    /* Settled: money handed back, a payment confirmed, a till set up. Gold asks; green answers. */
    --k-ok: #1f9d55;
    --k-ok-strong: #167a41;
    --k-surface: #fff;
    --k-sunken: #f3f1ee;
    --k-line: rgba(0, 0, 0, 0.09);
    --k-text-muted: #6b6257;
    /* The shade at the edge of a box that scrolls, where more is out of sight. */
    --k-scroll-cue: rgba(0, 0, 0, 0.16);
    /* A phone's notch and home indicator, zero on everything else. */
    --k-safe-top: env(safe-area-inset-top, 0px);
    --k-safe-bottom: env(safe-area-inset-bottom, 0px);
    --k-safe-left: env(safe-area-inset-left, 0px);
    --k-safe-right: env(safe-area-inset-right, 0px);
}

@media (prefers-color-scheme: dark) {
    :root {
        --k-surface: #1c1917;
        --k-sunken: #292524;
        --k-line: rgba(255, 255, 255, 0.12);
        --k-text-muted: #a8a29e;
        --k-scroll-cue: rgba(255, 255, 255, 0.14);
    }
}

/* ---------- landing (browser tab only) ---------- */

.k-landing {
    margin-block: 2rem 2.5rem;
}

.k-landing h1 {
    font-size: clamp(1.8rem, 1.2rem + 2.4vw, 2.6rem);
    letter-spacing: -0.02em;
}

/* The explainer film. Sized by its own aspect ratio so the page does not jump when the poster
   arrives; the shadow matches the install card's weight. */
.k-explainer {
    margin: 1.5rem 0 2rem;
}

.k-explainer video {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    border-radius: 16px;
    background: #f8f1e7;
    box-shadow: 0 12px 32px rgba(42, 29, 18, 0.14);
}

.k-explainer figcaption {
    margin-top: 0.6rem;
    font-size: 0.9rem;
    color: var(--k-text-muted);
}

/* The install walkthroughs, an iPad and an Android tablet, both on their side, at the foot of the
   install card and set off from its text by a rule: two columns where there is room, one above
   the other on a phone. The columns are in the ratio of the two films'
   aspect ratios (1600×1112 and 1600×1000), so at equal height they line up top and bottom and the
   captions sit on one line. */
/* Pico gives every <section> a bottom margin; at the foot of the card it only doubles the padding. */
.k-install-videos {
    margin: 1.5rem 0 0;
    padding-top: 1.25rem;
    border-top: 1px solid var(--k-line);
}

.k-install-videos h3 {
    margin-bottom: 0.4rem;
    font-size: 1rem;
}

.k-install-videos-grid {
    display: grid;
    gap: 1.25rem;
}

@media (min-width: 640px) {
    .k-install-videos-grid {
        grid-template-columns: 1.439fr 1.6fr;
        align-items: start;
    }
}

.k-install-video {
    margin: 0;
}

.k-install-video video,
.k-install-sheet-video video {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 12px;
    background: var(--k-sunken);
    box-shadow: 0 8px 24px rgba(42, 29, 18, 0.12);
}

.k-install-video figcaption {
    margin-top: 0.5rem;
    font-size: 0.9rem;
    color: var(--k-text-muted);
    text-align: center;
}

/* The film in the install sheet, folded away until asked for. Pico colours a summary by its own
   accordion variables, open, closed and focused, so the till's gold is set through those. */
.k-install-sheet-video {
    --pico-accordion-close-summary-color: var(--k-gold-strong);
    --pico-accordion-open-summary-color: var(--k-gold-strong);
    --pico-accordion-active-summary-color: var(--k-gold-strong);
    --pico-primary: var(--k-gold-strong);
    --pico-primary-focus: color-mix(in srgb, var(--k-gold) 40%, transparent);
    margin-bottom: 1rem;
}

.k-install-sheet-video summary {
    font-weight: 600;
}

.k-install-sheet-video video {
    margin-top: 0.75rem;
}

.k-install-card {
    margin-block: 2rem;
    padding: 1.5rem;
    border-radius: 16px;
    border: 1px solid var(--k-line);
    background: var(--pico-card-sectioning-background-color);
}

.k-install-card h2 {
    margin-bottom: 0.6rem;
    font-size: 1.15rem;
}

.k-install-steps {
    margin-block: 1rem;
}

.k-install-steps li {
    margin-bottom: 0.4rem;
}

/* What is being installed, named and pictured, above the steps to install it. */
.k-install-head {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    padding-bottom: 0.8rem;
    border-bottom: 1px solid var(--k-line);
}

.k-install-head div {
    display: grid;
}

.k-install-icon {
    width: 48px;
    height: 48px;
    border-radius: 11px;
}

/* One instruction to a line, numbered, because that is how someone follows them with a phone in
   the other hand. */
.k-install-list {
    margin: 1rem 0;
    padding-left: 1.4rem;
    display: grid;
    gap: 0.7rem;
}

.k-install-list li {
    padding-left: 0.3rem;
}

/* The browser that cannot do it, said out loud rather than left out. */
.k-install-note {
    margin-bottom: 0;
    color: var(--k-text-muted);
    font-size: 0.95rem;
}

/* The one sentence on this page that prevents a wasted evening: data set up in a tab does not
   follow the app to the home screen. Given the weight of a callout, not of a footnote. */
.k-install-warning {
    margin-bottom: 0;
    padding: 0.85rem 1rem;
    border-left: 3px solid var(--k-gold-strong);
    border-radius: 0 8px 8px 0;
    background: color-mix(in srgb, var(--k-gold) 12%, transparent);
    font-size: 0.95rem;
}

/* The two ways in, on one line and on one left edge.
 *
 * They were a blue button inside the card and a dark one adrift below it, each starting at a
 * different x — two answers to the same question, laid out as though they were unrelated. Pico
 * gives a button the full width of its container, which is why they were stacked and stretched. */
.k-install-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-block: 1.25rem;
}

.k-install-actions button {
    width: auto;
    margin: 0;
}

/* Kept for the paragraph that follows the card. */
.k-landing-actions {
    margin-block: 1.5rem;
}

.k-noscript {
    padding: 0.85rem 1rem;
    border-radius: 8px;
    background: color-mix(in srgb, var(--k-discount) 12%, transparent);
}

/* Pico sizes every control in rem, so a <button> does not inherit the till's base — and every tile
   is a button. This has to outrank Pico's bare `button` selector, which a zero-specificity
   `:where(.k-app, …)` does not; one class does. `:where()` on the controls keeps it at exactly one
   class, so the till's own rules further down — all of them one class too — win on document order
   and keep their sizes. */
.k-app :where(button, input, select, textarea, [role="button"]),
.k-dialog :where(button, input, select, textarea, [role="button"]) {
    font-size: 1em;
}

/* ---------- taps are taps ---------- */

/* A till is tapped fast and repeatedly — that is the whole interaction. Left at `auto`, a mobile
   engine is free to read the second tap on a tile as a double-tap-to-zoom, and an app launched from
   a home screen has no address bar to undo that with. `manipulation` drops the double-tap gesture
   and keeps panning and pinch-zoom, which a seller with poor eyesight may need; `user-scalable=no`
   would stop the same gesture by taking pinch-zoom away from everyone, which is not a trade to make.

   Both the containers and the controls: touch-action does not inherit, so the containers cover a
   tap that lands in the gap between two tiles, and naming the controls keeps it true for the modals,
   which are appended to <body> and are nobody's descendants. */
.k-app,
.k-dialog,
.k-tile,
.k-pay-row,
.k-complete,
.k-icon-button,
.k-line-remove,
.k-pad button,
.k-modal-actions button,
.k-modal-actions [role="button"],
.k-segmented button,
.k-banner button {
    touch-action: manipulation;
}

/* The way back, shown only while the page is actually scaled. Fixed to the visual viewport rather
   than the page: a zoomed page is also panned, and a control the seller has scrolled away from is
   no way back at all. app.js keeps the transform in step with visualViewport. */
.k-zoom {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    max-width: 100vw;
    padding: 0.5rem 0.8rem;
    border-bottom-right-radius: 12px;
    background: var(--k-gold);
    color: #241a0f;
    font-size: 0.9em;
    transform-origin: top left;
    box-shadow: 0 10px 24px -12px rgba(0, 0, 0, 0.6);
}

.k-zoom[hidden] {
    display: none;
}

.k-zoom button {
    width: auto;
    margin: 0;
    padding: 0.35rem 0.8rem;
    border: 0;
    border-radius: 999px;
    background: #241a0f;
    color: #fff;
    font-size: 0.85em;
    font-weight: 700;
    white-space: nowrap;
    touch-action: manipulation;
}

/* ---------- the till ---------- */

.k-app[hidden] {
    display: none;
}

.k-app {
    /* The till sets its own type size and does not ride Pico's.
       Pico scales the root font-size up with the viewport — 16px on a phone, 20px past 1280px —
       which is right for a page of prose and wrong for a till: on an iPad every label grew by a
       quarter, "Bienenwachskerze" stopped fitting on one tile line, and the grid lost a column to
       make room. Pinned here, so `em` below means what it meant when these sizes were chosen. */
    font-size: 16px;
    display: flex;
    flex-direction: column;
    gap: 0;
    margin-block: 1.5rem;
    border: 1px solid var(--k-line);
    border-radius: 16px;
    overflow: hidden;
    background: var(--k-surface);
    /* In a tab the till is a panel on the page and must not run off the bottom of it. */
    height: min(78vh, 900px);
}

.k-bar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0.9rem;
    border-bottom: 1px solid var(--k-line);
    background: var(--k-sunken);
}

.k-bar h1 {
    flex: none;
    margin: 0;
    font-size: 1.05em;
    letter-spacing: -0.01em;
}

.k-bar .badge {
    --tool-to: var(--wt-kaesseli-to);
    margin: 0;
}

.k-icon-button {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 12px;
    background: transparent;
    color: inherit;
    cursor: pointer;
}

.k-icon-button:focus-visible {
    background: color-mix(in srgb, var(--k-gold) 22%, transparent);
}

.k-icon-button svg {
    width: 22px;
    height: 22px;
}

/* Tablet landscape is the till's home: products left, cart right, roughly 65/35 as on the iPad. */
.k-main {
    flex: 1;
    display: grid;
    grid-template-columns: 1fr;
    min-height: 0;
}

/* Wide and tall: a phone on its side is as wide as a small tablet, and a third of its height. */
@media (min-width: 820px) and (min-height: 501px) {
    .k-main {
        /* 72/28, measured off the shipped iPad app rather than taken from the old design notes,
           which said 65/35. The wider grid is what fits six tiles across an iPad instead of five. */
        grid-template-columns: minmax(0, 72fr) minmax(280px, 28fr);
    }
}

.k-products {
    overflow-y: auto;
    padding: 0.9rem;
    -webkit-overflow-scrolling: touch;
    /* A swipe past the end of the grid stays in the grid. Left to chain, it rubber-bands the
       whole page, which on an installed app is the only thing that can move and reads as the
       till coming apart. */
    overscroll-behavior: contain;
}

.k-grid {
    display: grid;
    /* 140px keeps four columns on an iPad and two on the narrowest phone, with no sideways scroll. */
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 0.75rem;
}

.k-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    /* 44px is the floor for a touch target; a tile is far above it, but the padding says why. */
    min-height: 44px;
    margin: 0;
    padding: 0.5rem 0.5rem 0.7rem;
    border: 1px solid var(--k-line);
    border-radius: 14px;
    background: var(--k-surface);
    color: inherit;
    text-align: center;
    cursor: pointer;
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.k-tile:active {
    transform: scale(0.97);
}

/* The picture, or what stands in for one: the same box either way, so names and prices line up.
   --k-photo is 4 / 3, or flatter or taller where that fills the height for the layouts with groups
   (product-area.js, photoHeight). */
.k-tile img,
.k-tile .k-photo-missing {
    width: 100%;
    aspect-ratio: var(--k-photo, 4 / 3);
    border-radius: 10px;
}

.k-tile img {
    /* contain, not cover: the whole product should be visible — that is what is being tapped. */
    object-fit: contain;
    /* The tile's own colour, so the space beside a portrait picture is part of the tile. White drew
       a bar either side of it in the dark theme. */
    background: var(--k-surface);
}

.k-tile-name {
    font-size: 0.9em;
    font-weight: 600;
    line-height: 1.25;
    /* A long product name may wrap, but it may not push the tile wider than its column.
       `anywhere` bought that at the price of splitting words mid-syllable — "Bienenwachske/rze" —
       and of breaking *before* a hyphen, so "Kaminanzünder-Box" came out with the hyphen orphaned
       onto the second line. `break-word` breaks a word only when it genuinely does not fit, and
       hyphenation gives German compounds somewhere sensible to break, with a hyphen where a reader
       expects one. Safari still wants the prefix. */
    overflow-wrap: break-word;
    -webkit-hyphens: auto;
    hyphens: auto;
}

.k-tile-price {
    font-size: 0.85em;
    font-weight: 600;
    color: var(--k-gold-strong);
}

.k-tile--discount .k-tile-price {
    color: var(--k-discount);
}

/* How many of this piece are in the cart, in the corner the discount badge does not use. Gold,
   because it is a fact about the sale in progress, not a warning. */
.k-tile-count {
    position: absolute;
    top: 0.55rem;
    left: 0.55rem;
    min-width: 1.9em;
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    background: var(--k-gold);
    color: #241a0f;
    font-size: 0.78em;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

.k-tile-badge {
    position: absolute;
    top: 0.55rem;
    right: 0.55rem;
    padding: 0.15rem 0.45rem;
    border-radius: 6px;
    background: var(--k-discount);
    color: #fff;
    font-size: 0.68em;
    font-weight: 700;
    letter-spacing: 0.02em;
}

/* ---------- groups on the till (layout.js, product-area.js) ---------- */

/* A group's tile is a product tile's frame — the same picture box, the same two lines under it — so
   the two line up in one grid and a row of them is as tall as a row of products. The group's own
   colour tints it; a group without one, and "Mehr", are neutral. */
.k-tile--group,
.k-tile--more {
    --k-group: var(--k-text-muted);
}

.k-tile--group {
    border-color: color-mix(in srgb, var(--k-group) 55%, var(--k-line));
    background: color-mix(in srgb, var(--k-group) 14%, var(--k-surface));
}

.k-tile--more {
    background: var(--k-sunken);
}

/* Up to four of the group's pictures in the box one picture takes. */
.k-collage {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
    gap: 2px;
    width: 100%;
    aspect-ratio: var(--k-photo, 4 / 3);
    overflow: hidden;
    border-radius: 10px;
    background: color-mix(in srgb, var(--k-group) 35%, var(--k-surface));
}

.k-collage[data-count="1"] > * {
    grid-area: 1 / 1 / 3 / 3;
}

.k-collage[data-count="2"] > * {
    grid-row: 1 / 3;
}

.k-collage[data-count="3"] > :first-child {
    grid-row: 1 / 3;
}

/* Cover, not contain, here: a mosaic of letterboxed pictures reads as gaps, not as a group. */
.k-tile .k-collage img,
.k-tile .k-collage .k-photo-missing {
    width: 100%;
    height: 100%;
    min-height: 0;
    aspect-ratio: auto;
    border-radius: 0;
    object-fit: cover;
}

/* "13 Produkte ›" where a product tile has its price: this tile opens, it does not add. */
.k-tile-sub {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.1rem;
    color: var(--k-text-muted);
    font-size: 0.85em;
    font-weight: 600;
}

.k-tile-sub svg {
    flex: none;
    width: 14px;
    height: 14px;
}

/* Above a group's products: the way back, and whose products these are. */
.k-shelf-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: -0.35rem 0 0.6rem;
}

.k-shelf-back {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 0.2rem;
    min-height: 44px;
    margin: 0;
    padding: 0.4rem 0.6rem 0.4rem 0.2rem;
    border: 0;
    background: transparent;
    color: var(--k-gold-strong);
    font-weight: 600;
}

.k-shelf-back svg {
    width: 20px;
    height: 20px;
}

.k-shelf-title {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    margin: 0;
    font-size: 1.1em;
    overflow-wrap: break-word;
}

/* It takes the focus so a screen reader says where it landed; it is not something to tap. */
.k-shelf-title:focus {
    outline: none;
}

/* The strip of groups. It stays at the top of the products while they scroll, flush with the
   column's edges — hence the margins that take back the column's padding. */
.k-strip {
    position: sticky;
    top: -0.9rem;
    z-index: 2;
    margin: -0.9rem -0.9rem 0.75rem;
    border-bottom: 1px solid var(--k-line);
    background: var(--k-surface);
}

.k-strip-scroll {
    --k-fade-before: 0px;
    --k-fade-after: 0px;
    display: flex;
    gap: 0.5rem;
    padding: 0.6rem 0.9rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--k-fade-before), #000 calc(100% - var(--k-fade-after)), transparent);
    mask-image: linear-gradient(to right, transparent, #000 var(--k-fade-before), #000 calc(100% - var(--k-fade-after)), transparent);
}

/* A group of buttons to a screen reader — and Pico draws any `[role="group"]` as one joined button
   bar: square inner corners, a shadow round it, a margin under it. None of that here: these are
   separate buttons, and the margin was a gap between the strip and the tiles. */
.k-strip-scroll[role="group"] {
    width: auto;
    margin: 0;
    border-radius: 0;
    box-shadow: none;
}

.k-strip-scroll[role="group"] > .k-chip {
    display: inline-flex;
    margin: 0;
    border-radius: 999px;
}

.k-strip-scroll::-webkit-scrollbar {
    display: none;
}

/* Where a group is out of sight at either end, the strip fades there — as the setup's body does. */
.k-strip-scroll[data-more-before] {
    --k-fade-before: 2rem;
}

.k-strip-scroll[data-more-after] {
    --k-fade-after: 2.5rem;
}

.k-chip {
    --k-group: var(--k-text-muted);
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 0.45rem;
    min-height: 44px;
    margin: 0;
    padding: 0.35rem 0.95rem;
    border: 1px solid color-mix(in srgb, var(--k-group) 55%, var(--k-line));
    border-radius: 999px;
    background: color-mix(in srgb, var(--k-group) 12%, var(--k-surface));
    color: inherit;
    font-size: 0.9em;
    font-weight: 600;
    white-space: nowrap;
    touch-action: manipulation;
}

.k-chip[data-top] {
    border-color: var(--k-line);
    background: var(--k-sunken);
}

.k-chip[aria-pressed="true"] {
    border-color: var(--k-gold-strong);
    background: var(--k-gold);
    color: #241a0f;
}

.k-chip-dot {
    flex: none;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
    background: var(--k-group, var(--k-text-muted));
}

/* In a chip, the cart's count sits in line after the name rather than in a tile's corner. */
.k-chip .k-tile-count {
    position: static;
    min-width: 0;
    box-shadow: none;
}

/* The day-one pick: every product with a box to tick, and how many of the places are taken —
   which stays in sight while the list scrolls under it. */
.k-pick-head {
    position: sticky;
    top: 0;
    z-index: 1;
    margin: 0 -1.25rem;
    padding: 0.5rem 1.25rem;
    border-bottom: 1px solid var(--k-line);
    background: var(--k-surface);
    text-align: center;
}

/* "Danach ordnen die Verkäufe sie von selbst.": the one line of the pick that must not be missed,
   in the gold of the till's own notices, with a bar and an icon, and in bold. */
.k-pick-after {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: -0.25rem 0 1rem;
    padding: 0.65rem 0.85rem;
    border-left: 4px solid var(--k-gold-strong);
    border-radius: 8px;
    background: color-mix(in srgb, var(--k-gold) 22%, transparent);
    font-weight: 700;
    text-align: left;
}

.k-pick-after svg {
    flex: none;
    width: 1.4em;
    height: 1.4em;
    color: var(--k-gold-strong);
}

.k-pick-count {
    margin: 0;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.k-pick-full {
    margin: 0.15rem 0 0;
    color: var(--k-text-muted);
    font-size: 0.85em;
}

.k-pick-list,
.k-pick-list li {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Pico sizes a label that holds a checkbox to fit its content (label:has([type=checkbox]), which
   outranks a class alone), which cut the line under each row off halfway across. */
.k-pick-list .k-pick-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    margin: 0;
    padding: 0.5rem 0.25rem;
    border-bottom: 1px solid var(--k-line);
    cursor: pointer;
}

.k-pick-row input {
    flex: none;
    width: 1.4em;
    height: 1.4em;
    margin: 0;
}

/* Pico draws the tick box itself, in the site's indigo, and fills an empty one that has the focus
   — which made a box just unticked look ticked. Both states, then: empty is empty, ticked is the
   till's gold, as its buttons are; the focus is the ring round it. */
.k-pick-row input:not(:checked) {
    border-color: var(--k-text-muted);
    background-color: var(--k-surface);
}

.k-pick-row input:checked {
    border-color: var(--k-gold-strong);
    background-color: var(--k-gold-strong);
}

.k-pick-row:has(input:disabled) {
    cursor: default;
    opacity: 0.55;
}

@media (max-height: 850px) {
    .k-pick-head {
        margin: 0 -1rem;
        padding-inline: 1rem;
    }
}

/* ---------- cart ---------- */

.k-cart {
    display: flex;
    flex-direction: column;
    min-height: 0;
    border-top: 1px solid var(--k-line);
    background: var(--k-sunken);
}

@media (min-width: 820px) and (min-height: 501px) {
    .k-cart {
        border-top: 0;
        border-left: 1px solid var(--k-line);
    }
}

/* A <button>, because on a phone it opens the sheet; on a tablet it is a heading and does nothing.
   So it is drawn as a heading everywhere, not as a Pico button: that gave the tablet a white label on
   the cart's beige, in a primary-coloured frame. */
.k-cart-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    margin: 0;
    padding: 0.85rem 1rem;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: inherit;
    font-size: 1em;
    font-weight: 600;
    text-align: left;
    cursor: default;
    flex-shrink: 0;
}

.k-cart-head svg {
    width: 20px;
    height: 20px;
}

.k-cart-scroll {
    position: relative;
    flex: 1;
    min-height: 0;
    display: flex;
}

/* A cart with more lines than fit is cut through a card by the edge of its own scroll box, which
   reads as a rendering fault rather than as "there is more". These two fades sit over the content
   at whichever edge is hiding something — app.js sets the flags, because the classic pure-CSS
   scroll shadow paints on the container's background and every line here is an opaque card on top
   of it. */
.k-cart-scroll::before,
.k-cart-scroll::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    height: 1.6rem;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease;
}

.k-cart-scroll::before {
    top: 0;
    background: linear-gradient(var(--k-sunken), rgba(0, 0, 0, 0));
}

.k-cart-scroll::after {
    bottom: 0;
    background: linear-gradient(rgba(0, 0, 0, 0), var(--k-sunken));
}

.k-cart-scroll[data-above="true"]::before,
.k-cart-scroll[data-below="true"]::after {
    opacity: 1;
}

.k-cart-lines {
    flex: 1;
    min-height: 0;
    margin: 0;
    /* Bottom padding so the last line ends clear of the totals rather than flush against them. */
    padding: 0 0.7rem 0.5rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    list-style: none;
}

.k-cart-empty {
    display: grid;
    place-items: center;
    gap: 0.5rem;
    height: 100%;
    color: var(--k-text-muted);
}

.k-cart-empty svg {
    width: 42px;
    height: 42px;
    opacity: 0.55;
}

.k-line {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: center;
    gap: 0.3rem 0.6rem;
    padding: 0.6rem 0.5rem;
    border-radius: 10px;
    background: var(--k-surface);
    margin-bottom: 0.5rem;
}

/* A line whose count just changed lights up and fades back, so a tap that landed in a line the
   cashier was not looking at is still seen to land. The cart scrolls to it first. */
.k-line--changed {
    animation: k-line-changed 0.9s ease-out;
}

@keyframes k-line-changed {
    from {
        background: color-mix(in srgb, var(--k-gold) 55%, var(--k-surface));
    }

    to {
        background: var(--k-surface);
    }
}

@media (prefers-reduced-motion: reduce) {
    .k-line--changed {
        animation: none;
        outline: 2px solid var(--k-gold);
    }
}

.k-line-name {
    grid-column: 1;
    font-weight: 600;
    overflow-wrap: break-word;
}

.k-line-detail {
    grid-column: 1;
    grid-row: 2;
    color: var(--k-text-muted);
    font-size: 0.85em;
}

.k-line-total {
    grid-column: 2;
    grid-row: 1 / span 2;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.k-line--discount .k-line-total {
    color: var(--k-discount);
}

.k-line-remove {
    grid-column: 3;
    grid-row: 1 / span 2;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--k-discount);
    cursor: pointer;
}

.k-line-remove svg {
    width: 18px;
    height: 18px;
}

/* The bar's two extra parts exist only on a phone. Everywhere else the head is a heading. */
.k-cart-summary {
    display: none;
    flex: 1;
    min-width: 0;
    margin-left: auto;
    color: var(--k-text-muted);
    font-weight: 500;
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.k-cart-toggle {
    display: none;
    flex-shrink: 0;
    color: var(--k-text-muted);
}

.k-cart-toggle svg {
    width: 20px;
    height: 20px;
    transform: rotate(-90deg);
    transition: transform 0.15s ease;
}

/* A bar that has just taken a tap says so, the same gold the changed line wears. */
.k-cart-head--changed {
    animation: k-line-changed 0.9s ease-out;
}

/* ---------- a phone: the till is the screen, and the cart is a sheet ---------- */

/* The till is built for a tablet, where grid and cart sit side by side. On a phone there is no
   side: stacked, the cart was a strip under a grid that took the whole screen, and the totals
   were wherever the page had scrolled to. So on a phone the till takes the screen — in a browser
   tab as well as installed, since a till a thumb's width high is no till — the grid gets all of it,
   and the cart is a sheet along the bottom edge: closed, one bar that says how many things and how
   much; open, it rises over the grid with its lines, the two payment rows and Complete Sale. The
   tablet's split is untouched; everything here is behind the phone breakpoint.

   A phone is narrow or short: on its side it is 844 to 932 px wide, past any width breakpoint that
   leaves an iPad a tablet, but at most 440 px high, where no iPad is under 744. Split in a screen
   that low, the cart column had no room for its lines and cut Complete Sale off. */
@media (max-width: 819px), (max-height: 500px) {
    .k-app {
        position: fixed;
        inset: 0;
        z-index: 40;
        height: auto;
        margin: 0;
        border: 0;
        border-radius: 0;
    }

    .k-main {
        position: relative;
        display: flex;
        flex-direction: column;
        min-height: 0;
    }

    .k-products {
        flex: 1;
        min-height: 0;
        /* Room for the closed sheet's bar, so the last row of tiles can be scrolled clear of it. */
        padding-bottom: 1.25rem;
    }

    .k-cart {
        flex: none;
        border-top: 1px solid var(--k-line);
        box-shadow: 0 -10px 24px -18px rgba(0, 0, 0, 0.5);
    }

    /* Closed: the bar is all there is. */
    .k-cart:not(.k-cart--open) .k-cart-scroll,
    .k-cart:not(.k-cart--open) .k-pay {
        display: none;
    }

    /* Open: the sheet rises over the grid, which stays where it was underneath. */
    .k-cart--open {
        position: absolute;
        inset: 12% 0 0;
        z-index: 5;
        display: flex;
        flex-direction: column;
        border-top-left-radius: 18px;
        border-top-right-radius: 18px;
        box-shadow: 0 -16px 40px -20px rgba(0, 0, 0, 0.6);
    }

    .k-cart-head {
        padding: 0.8rem 1rem;
        cursor: pointer;
        touch-action: manipulation;
    }

    .k-cart-summary {
        display: inline;
    }

    .k-cart-toggle {
        display: inline-flex;
    }

    .k-cart--open .k-cart-toggle svg {
        transform: rotate(90deg);
    }
}

/* A phone on its side: short, but wide. Stacked, the sheet's head and totals alone were taller
   than the screen leaves it, and the lines got nothing. Side by side there is room for both: the
   sheet takes the whole of the till below its bar, lines on the left, totals and Complete Sale on
   the right. The bar gives back the padding its 44 px buttons do not need. */
@media (max-height: 500px) and (min-width: 560px) {
    .k-bar {
        padding-block: 0.2rem;
    }

    .k-cart--open {
        inset: 0;
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(260px, 36%);
        grid-template-rows: auto minmax(0, 1fr);
        border-radius: 0;
    }

    .k-cart--open .k-cart-head {
        grid-column: 1 / -1;
    }

    .k-cart--open .k-pay {
        /* The lines scroll; the totals only would on a screen shorter than any phone. */
        overflow-y: auto;
        border-top: 0;
        border-left: 1px solid var(--k-line);
    }
}


.k-pay {
    padding: 0.7rem;
    border-top: 1px solid var(--k-line);
    /* Never squeezed: the list of lines scrolls, the totals and the button always stay whole.
       Without this the note under Complete Sale is clipped by the panel's own edge. */
    flex-shrink: 0;
}

.k-pay-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    margin-bottom: 0.5rem;
    padding: 0.7rem 0.85rem;
    border: 1px solid transparent;
    border-radius: 12px;
    background: var(--k-surface);
    color: inherit;
    font-size: 1em;
    cursor: pointer;
}

.k-pay-row[aria-pressed="true"] {
    border-color: var(--k-gold-strong);
    background: color-mix(in srgb, var(--k-gold) 20%, var(--k-surface));
}

.k-pay-row svg {
    width: 20px;
    height: 20px;
}

.k-pay-name {
    flex: 1;
    text-align: left;
    font-weight: 600;
}

.k-pay-hint {
    color: var(--k-text-muted);
    font-size: 0.8em;
    font-weight: 400;
}

.k-pay-amount {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.k-complete {
    width: 100%;
    margin: 0;
    padding: 0.9rem;
    border: 0;
    border-radius: 12px;
    background: var(--k-gold);
    color: #241a0f;
    font-size: 1.05em;
    font-weight: 700;
}

.k-complete[disabled] {
    background: color-mix(in srgb, var(--k-text-muted) 35%, transparent);
    color: var(--k-text-muted);
    cursor: not-allowed;
}

.k-complete-note {
    margin: 0.5rem 0 0;
    color: var(--k-text-muted);
    font-size: 0.8em;
    text-align: center;
}

/* ---------- banner ---------- */

.k-banner {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    padding: 0.6rem 0.9rem;
    border-bottom: 1px solid var(--k-line);
    background: color-mix(in srgb, var(--k-gold) 22%, transparent);
    font-size: 0.9em;
}

.k-banner p {
    flex: 1;
    margin: 0;
    min-width: 12rem;
}

.k-banner button {
    width: auto;
    margin: 0;
    padding: 0.35rem 0.8rem;
    font-size: 0.85em;
}

/* ---------- modal ---------- */

/* The panel inside the <dialog>. Pico owns the dialog element itself — it is the full-screen
   overlay, with the dimming and the blur — and styles `dialog > article` as the panel. Anything
   appended straight to the dialog is laid out by `dialog{display:flex}` instead, in a row. */
.k-modal {
    /* Same base as the till: the modals are its screens, and they are not on the page. */
    font-size: 16px;
    width: 100%;
    max-width: 26rem;
    padding: 1.25rem;
    border-radius: 18px;
    background: var(--k-surface);
    color: inherit;
}

.k-modal h2 {
    margin-bottom: 0.15rem;
    font-size: 1.2em;
    text-align: center;
}

.k-modal-sub {
    margin-bottom: 1rem;
    color: var(--k-text-muted);
    text-align: center;
}

/* ---------- What's New ---------- */

/* Wider than the other sheets: it carries pictures of the app, and they are no use at thumbnail size. */
.k-news-dialog .k-modal {
    max-width: 40rem;
}

.k-news-dialog h2:focus {
    outline: none;
}

.k-news {
    text-align: left;
}

.k-news + .k-news {
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--k-line);
}

.k-news-version {
    margin: 0.75rem 0 0.5rem;
    font-size: 0.85em;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--k-text-muted);
}

.k-news h4 {
    margin: 1rem 0 0.35rem;
    font-size: 1.05em;
    /* Pico greys out headings below h3; these lead their paragraph and have to read as headings. */
    font-weight: 600;
    color: inherit;
}

.k-news p,
.k-news ul {
    margin-bottom: 0.6rem;
}

.k-news ul {
    padding-left: 1.25rem;
}

.k-news li {
    list-style: disc;
}

.k-news li {
    margin-bottom: 0.25rem;
}

.k-news-picture,
video.k-news-film {
    display: block;
    width: 100%;
    height: auto;
    margin: 0.5rem 0 0.9rem;
    border: 1px solid var(--k-line);
    border-radius: 12px;
    background: var(--k-sunken);
}

.k-news-film--offline {
    margin: 0.5rem 0 0.9rem;
}

.k-news-film--offline .k-news-picture {
    margin-bottom: 0.35rem;
}

.k-news-film--offline figcaption,
.k-news-missing {
    color: var(--k-text-muted);
    font-size: 0.9em;
}

/* A picture that is not on the device, said in words: a box where it would have been. */
.k-news-missing {
    padding: 1rem;
    border: 1px dashed var(--k-line);
    border-radius: 12px;
    background: var(--k-sunken);
}

/* "Heubode Kafi wird eingerichtet …": a spinner over everything while an inventory is built. */
.k-working .k-modal {
    text-align: center;
}

.k-working-title {
    margin-bottom: 0.25rem;
    font-weight: 600;
}

.k-spinner {
    display: block;
    width: 2.5rem;
    height: 2.5rem;
    margin: 0.5rem auto 1rem;
    border: 3px solid color-mix(in srgb, var(--k-gold) 25%, transparent);
    border-top-color: var(--k-gold-strong);
    border-radius: 50%;
    animation: k-spin 0.9s linear infinite;
}

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

/* Slower, not still: a spinner that stops looks like a till that has hung. */
@media (prefers-reduced-motion: reduce) {
    .k-spinner {
        animation-duration: 2.4s;
    }
}

.k-readout {
    padding: 0.7rem;
    border-radius: 12px;
    background: var(--k-sunken);
    font-size: 1.9em;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.k-readout-total {
    margin-top: 0.5rem;
    padding: 0.55rem;
    border-radius: 12px;
    background: color-mix(in srgb, var(--k-gold) 18%, transparent);
    color: var(--k-gold-strong);
    font-size: 1.2em;
    font-weight: 700;
    text-align: center;
}

.k-pad {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    margin-block: 1rem;
}

.k-pad button {
    margin: 0;
    padding: 0.85rem 0;
    border: 1px solid var(--k-line);
    border-radius: 12px;
    background: var(--k-sunken);
    color: inherit;
    font-size: 1.25em;
    font-weight: 600;
}

.k-pad button[value="clear"] {
    color: var(--k-discount);
}

/* A setting and its control on one row of a card: the label on the left, the control on the
   right, and — this is the part that was missing — every control in a card sharing one right
   edge and one width, so the column reads as a column. */
.k-field {
    display: flex;
    align-items: center;
    gap: 1rem;
    width: 100%;
    padding: 0.5rem 1rem;
    border-bottom: 1px solid var(--k-line);
}

.k-field:last-child {
    border-bottom: 0;
}

.k-field > span:first-child {
    flex: 1;
    min-width: 0;
}

/* Every control on the right-hand side is this wide, whatever it is. */
.k-field-control {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.4rem;
    width: min(18rem, 55%);
}

/* The icon a settings row leads with, in the app's colour, as the iPad app has them. */
.k-row-lead {
    flex-shrink: 0;
    display: inline-flex;
    color: var(--k-gold-strong);
}

.k-row-lead svg {
    width: 22px;
    height: 22px;
}

/* The QR code and the button under it, inside the picker panel. */
.k-picker-preview {
    display: grid;
    gap: 0.6rem;
    justify-items: center;
}

.k-field-control > :where(input, select) {
    width: 100%;
    margin: 0;
    /* Pico sizes form controls for a page, not for a row of a list — left alone, one setting was
       as tall as three rows of the daily close beside it. */
    padding: 0.45rem 0.7rem;
    font-size: 1em;
}

/* A percentage or a price is three characters wide. It does not need the width of a name. */
.k-field-control--short {
    width: auto;
}

.k-field-control--short > input {
    width: 6rem;
}

/* Numbers read right-aligned against their unit, the way money does on a receipt — also an amount
   typed into a text field (`.k-amount`), which the app formats itself. */
.k-field-control input[type="number"],
.k-field-control input.k-amount {
    text-align: right;
    -moz-appearance: textfield;
    appearance: textfield;
}

.k-field-control input[type="number"]::-webkit-outer-spin-button,
.k-field-control input[type="number"]::-webkit-inner-spin-button {
    margin: 0;
    -webkit-appearance: none;
    appearance: none;
}

.k-field-suffix {
    flex-shrink: 0;
    color: var(--k-text-muted);
}

/* A field whose control needs the whole width — a long name, a description. */
.k-field--stacked {
    display: grid;
    gap: 0.35rem;
    align-items: stretch;
}

.k-field--stacked .k-field-control {
    width: 100%;
}

/* The grey line under a card that explains what the card just did. Left-aligned under the
   card's own text, never centred in the middle of the screen. */
.k-note {
    margin: 0.5rem 0 1.25rem 1rem;
    color: var(--k-text-muted);
    font-size: 0.85em;
}

/* Cancel and confirm, always on screen.
 *
 * The panel scrolls when it is taller than the display, and on a 768 px tall iPad the cash pad is
 * taller — so the two buttons sat below the fold and a seller saw a number pad with no way to
 * finish. They stick to the foot of the panel now and the pad scrolls under them. Full-bleed, so
 * the content passing beneath is covered rather than showing through the gap at the sides. */
.k-modal-actions {
    position: sticky;
    bottom: 0;
    z-index: 1;
    display: grid;
    /* auto-fit, so a row with one button fills it rather than leaving half the row empty. */
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
    gap: 0.6rem;
    /* No negative margin at the bottom. One there shortens the panel's scrollport, and sticky
       measures its offset against that — so the bar was pulled up into the content above it and
       painted over the last thing in the panel. The shadow covers the panel's own bottom padding
       instead, so content scrolling past still passes behind rather than through. */
    margin: 0.75rem -1.25rem 0;
    padding: 0.75rem 1.25rem;
    background: var(--k-surface);
    box-shadow: 0 1.25rem 0 var(--k-surface);
}

/* A display short enough that the pad would have to scroll at all: give the numbers back some of
   the room the padding was using, so most tills never scroll in the first place. */
@media (max-height: 850px) {
    .k-modal {
        padding: 1rem;
    }

    .k-modal-actions {
        margin: 0.6rem -1rem 0;
        padding: 0.6rem 1rem;
        box-shadow: 0 1rem 0 var(--k-surface);
    }

    .k-pad {
        gap: 0.4rem;
        margin-block: 0.6rem;
    }

    .k-pad button {
        padding: 0.55rem 0;
    }

    .k-readout {
        padding: 0.5rem;
        font-size: 1.6em;
    }
}

/* A phone on its side: under 400 px high, one column fit the readout and the top row of keys, and
   every weight meant scrolling the pad up past the buttons. It is wide, though, so a panel with a
   pad goes into two columns — what is being typed on the left, the keys on the right, the buttons
   under both. The pad spans more rows than the left column can ever have; the empty ones are
   nothing high, since the gaps between rows are the children's own margins. */
@media (max-height: 500px) and (min-width: 560px) {
    .k-modal:has(> .k-pad) {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        align-content: start;
        column-gap: 1rem;
        max-width: 44rem;
    }

    .k-modal:has(> .k-pad) > .k-pad {
        grid-column: 2;
        grid-row: 1 / span 20;
        align-self: start;
        margin-block: 0;
    }

    .k-modal:has(> .k-pad) > .k-modal-actions {
        grid-column: 1 / -1;
    }

    /* The cash pad has more to say on the left than there is height for: the notes go beside the
       keys instead, one per row of them, a fourth column of the pad — the same height and the same
       size of type, so the two read as one block — and the rest closes up. */
    .k-modal:has(> .k-chips) {
        grid-template-columns: minmax(0, 5fr) minmax(0, 4fr) 4.5rem;
        padding-block: 0.75rem;
    }

    .k-modal:has(> .k-chips) > .k-chips {
        grid-column: 3;
        grid-row: 1 / span 20;
        grid-template-columns: 1fr;
        align-self: start;
        gap: 0.4rem;
        margin-top: 0;
        margin-left: -0.6rem;
    }

    .k-modal:has(> .k-chips) > .k-chips button {
        padding: 0.55rem 0;
        font-size: 1.25em;
    }

    .k-modal:has(> .k-chips) .k-modal-link {
        margin-bottom: 0.4rem;
    }

    .k-modal:has(> .k-chips) .k-figure {
        padding-block: 0.45rem;
    }

    .k-modal:has(> .k-chips) .k-modal-sub {
        margin: 0.3rem 0;
    }
}

.k-modal-actions button,
.k-modal-actions [role="button"] {
    margin: 0;
    padding: 0.8rem;
    border-radius: 12px;
    font-weight: 600;
}

/* The confirming action wears the till's gold, the same as Complete Sale — Pico's indigo would make
   the one button that commits something look like it belongs to a different application. */
.k-modal-actions button:not(.secondary),
.k-modal-actions [role="button"]:not(.secondary) {
    border-color: var(--k-gold);
    background: var(--k-gold);
    color: #241a0f;
}

.k-modal-actions button:not(.secondary)[disabled] {
    border-color: transparent;
    background: color-mix(in srgb, var(--k-text-muted) 35%, transparent);
    color: var(--k-text-muted);
}

.k-modal-actions button.secondary,
.k-modal-actions [role="button"].secondary {
    border-color: var(--k-gold-strong);
    background: transparent;
    color: var(--k-gold-strong);
}

/* ---------- checkout ---------- */

/* "Pay cash instead" — a way across to the other payment screen, not an action of its own. */
.k-modal-link {
    display: block;
    width: auto;
    margin: 0 auto 1rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--k-gold-strong);
    font-size: 0.9em;
    text-decoration: underline;
}

.k-figure {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.7rem 0.9rem;
    border-radius: 12px;
    background: var(--k-sunken);
    font-size: 1.05em;
}

/* The notes a Swiss stall actually sees. */
.k-chips {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.5rem;
    margin-top: 0.5rem;
}

.k-chips button {
    margin: 0;
    padding: 0.5rem 0;
    border: 1px solid var(--k-gold);
    border-radius: 999px;
    background: color-mix(in srgb, var(--k-gold) 12%, transparent);
    color: var(--k-gold-strong);
    font-size: 0.95em;
    font-weight: 700;
    touch-action: manipulation;
}

/* Change is money going back across the counter, and reads green here as it does in the app this
   mirrors — the one figure on the screen the customer also counts. */
.k-change {
    margin-top: 0.5rem;
    padding: 0.55rem;
    border-radius: 12px;
    background: color-mix(in srgb, var(--k-ok) 14%, transparent);
    color: var(--k-ok-strong);
    font-size: 1.2em;
    font-weight: 700;
    text-align: center;
}

@media (prefers-color-scheme: dark) {
    .k-change {
        color: #5fd394;
    }
}

/* Nothing handed over yet: not a figure, so not a colour either. */
.k-change--hint {
    background: var(--k-sunken);
    color: var(--k-text-muted);
    font-size: 0.95em;
    font-weight: 400;
}

/* Handed over less than is owed: the figure turns into a warning rather than a total. */
.k-change--short {
    background: color-mix(in srgb, var(--k-discount) 14%, transparent);
    color: var(--k-discount);
}

/* The gate on an electronic payment: the till cannot know, so the seller says.
   Two classes deep on purpose — Pico sizes any label containing a checkbox to `fit-content`, which
   left this one a stub ending in the middle of the modal, and a single class does not outrank it. */
.k-modal .k-verify {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    margin-block: 1rem;
    padding: 1rem;
    border: 1px solid var(--k-gold);
    border-radius: 12px;
    background: color-mix(in srgb, var(--k-gold) 10%, transparent);
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

/* Ticked, it says so at a glance: this is the one thing standing between the seller and the sale.
   Green, not a deeper gold — gold is the till's colour for "press this", and a ticked box is the
   money having arrived, the same green the change readout wears. */
.k-modal .k-verify:has(input:checked) {
    border-color: var(--k-ok);
    background: color-mix(in srgb, var(--k-ok) 16%, transparent);
    color: var(--k-ok-strong);
}

/* Pico restyles a checkbox rather than colouring it, so `accent-color` does nothing and the tick
   came out in the site's indigo — the one control in the till wearing another application's paint. */
.k-modal .k-verify input {
    width: 1.4em;
    height: 1.4em;
    margin: 0;
}

.k-modal .k-verify input:checked {
    border-color: var(--k-ok-strong);
    background-color: var(--k-ok-strong);
}

/* The code the customer scans. White behind it always: a QR on a dark card is unreadable, and a
   phone camera at a stall has one attempt before the queue notices. */
.k-qr {
    display: block;
    width: min(14rem, 60%);
    margin: 1rem auto;
    padding: 0.5rem;
    border-radius: 12px;
    background: #fff;
}

/* The code the customer scans, shown at the size it will be scanned from. */
.k-qr-preview {
    display: grid;
    gap: 0.6rem;
    justify-items: center;
}

/* ---------- what just happened ---------- */

/* In the till's bar, between its name and the wallet: there it takes nothing from the products. The
   wallet and the buttons after it keep to the right whether it shows or not. */
.k-bar .k-wallet-button {
    margin-left: auto;
}

.k-flash {
    /* Its width is what is left between the name and the wallet; what it says is chosen by that. */
    container-type: inline-size;
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    min-width: 0;
    min-height: 40px;
    padding: 0.2rem 0.25rem 0.2rem 0.9rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--k-gold) 22%, transparent);
    font-size: 0.9em;
    font-weight: 600;
    line-height: 1.25;
}

.k-flash[hidden] {
    display: none;
}

.k-flash-text {
    min-width: 0;
}

/* "CHF 9'999.90" is read as one figure: the line breaks before it, never inside it. */
.k-flash-total {
    white-space: nowrap;
}

.k-flash--error {
    background: color-mix(in srgb, var(--k-discount) 16%, transparent);
    color: var(--k-discount);
}

/* How it was paid, what was tendered, what went back: for the seller about to change the sale.
   Only where the most of it — "· Bar · Gegeben CHF 10'000.00 · Rückgeld CHF 0.10" after a total of
   thousands — takes two lines at most beside the button. A third made the bar taller, and the
   tiles under it moved. Not on an iPad held upright, then, and not on a phone. */
@container (max-width: 559px) {
    .k-flash-detail {
        display: none;
    }
}

@media (max-width: 819px), (max-height: 500px) {
    .k-flash-detail {
        display: none;
    }
}

/* Upright, a phone has room for the total and the button, and for those only once the name makes
   way; "Verkauf aufgezeichnet" is still read out. On its side it has room for the words. */
@media (max-width: 819px) {
    .k-flash-what {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .k-bar:has(.k-flash:not([hidden])) h1 {
        display: none;
    }

    .k-flash {
        gap: 0.5rem;
        padding-left: 0.75rem;
        font-size: 0.85em;
    }

    /* Two classes: the button's own rule comes after this one. */
    .k-bar .k-flash button {
        padding-inline: 0.8rem;
    }
}

.k-flash button {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 0.35rem;
    width: auto;
    min-height: 36px;
    margin: 0;
    padding: 0.4rem 1rem;
    white-space: nowrap;
    border: 0;
    border-radius: 999px;
    background: #241a0f;
    color: #fff;
    font-size: 0.85em;
    font-weight: 700;
    touch-action: manipulation;
}

/* On the dark bar, the pill's own near-black would hide the button's edge: gold, as the till's other
   buttons are there. */
@media (prefers-color-scheme: dark) {
    .k-flash button {
        background: var(--k-gold);
        color: #241a0f;
    }
}

.k-flash button svg {
    width: 16px;
    height: 16px;
}

/* ---------- the statistics: periods, charts, products ---------- */

/* Cash and card, the two series every revenue chart has. Checked for colour-blind separation and
   contrast against the surface they sit on, in both schemes; text never wears them. */
:root {
    --k-chart-cash: #b07a3f;
    --k-chart-electronic: #3f78b5;
    --k-chart-count: #7a6f63;
    --k-chart-grid: rgba(0, 0, 0, 0.08);
    --k-chart-base: rgba(0, 0, 0, 0.28);
}

@media (prefers-color-scheme: dark) {
    :root {
        --k-chart-cash: #c08439;
        --k-chart-electronic: #4f8fd6;
        --k-chart-count: #a8a29e;
        --k-chart-grid: rgba(255, 255, 255, 0.1);
        --k-chart-base: rgba(255, 255, 255, 0.32);
    }
}

/* The periods, as chips that wrap onto a second line on a phone rather than scrolling away. */
.k-period {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.75rem;
}

/* Separate chips, though a group to a screen reader: none of Pico's joined button bar (see the
   tile strip), for the period chips here and the sort chips below. */
.k-period[role="group"],
.k-stats-sort[role="group"] {
    display: flex;
    width: auto;
    border-radius: 0;
    box-shadow: none;
}

.k-period[role="group"] {
    margin: 0 0 0.75rem;
}

.k-stats-sort[role="group"] {
    margin: 0;
}

.k-period[role="group"] > .k-chip,
.k-stats-sort[role="group"] > .k-chip {
    margin: 0;
    border-radius: 999px;
}

.k-period .k-chip {
    min-height: 40px;
    padding-inline: 0.85rem;
}

.k-day-nav.k-period-range {
    justify-content: center;
    margin: 0;
    font-weight: 600;
}

.k-day-nav.k-period-inputs {
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.75rem;
}

.k-period-field {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    font-weight: 600;
}

.k-period-field input {
    width: auto;
    margin: 0;
}

.k-stats-samples {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.75rem 0 0;
    padding: 0.7rem 0.9rem;
    border-radius: 12px;
    background: color-mix(in srgb, var(--k-gold) 14%, var(--k-surface));
    font-size: 0.9em;
}

.k-stats-samples svg {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    color: var(--k-gold-strong);
}

.k-stats-currency {
    margin: 1.5rem 0 -0.5rem;
    font-size: 1.1em;
}

.k-stats-note {
    margin: -0.75rem 0 1.25rem;
    color: var(--k-text-muted);
    font-size: 0.8em;
}

.k-stats-chart .k-stats-note {
    margin: 0.4rem 0 0.6rem;
}

.k-chart {
    padding: 0.75rem 0.75rem 0.25rem;
    border-radius: 14px;
    background: var(--k-surface);
}

/* What the chart is drawn into: its width is the width the SVG is given, so it has no padding. */
.k-chart-room {
    min-height: 180px;
}

.k-chart-svg {
    display: block;
    overflow: visible;
}

.k-chart-grid {
    stroke: var(--k-chart-grid);
    stroke-width: 1;
}

.k-chart-base {
    stroke: var(--k-chart-base);
    stroke-width: 1;
}

.k-chart-tick,
.k-chart-label {
    fill: var(--k-text-muted);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.k-chart-bar--cash {
    fill: var(--k-chart-cash);
}

.k-chart-bar--electronic {
    fill: var(--k-chart-electronic);
}

.k-chart-bar--count {
    fill: var(--k-chart-count);
}

.k-chart-hit {
    fill: transparent;
}

.k-chart-hit--tap {
    cursor: pointer;
}

@media (hover: hover) {
    .k-chart-hit--tap:hover {
        fill: color-mix(in srgb, var(--k-text-muted) 10%, transparent);
    }
}

.k-chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
    margin-bottom: 0.5rem;
    font-size: 0.85em;
}

.k-chart-key {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.k-chart-swatch {
    width: 0.8rem;
    height: 0.8rem;
    border-radius: 3px;
}

.k-chart-swatch--cash {
    background: var(--k-chart-cash);
}

.k-chart-swatch--electronic {
    background: var(--k-chart-electronic);
}

/* Pico colours a summary through its accordion variables (see the install sheet): set there, or
   its own rule wins over this one's colour. */
.k-stats-table {
    --pico-accordion-close-summary-color: var(--k-gold-strong);
    --pico-accordion-open-summary-color: var(--k-gold-strong);
    --pico-accordion-active-summary-color: var(--k-gold-strong);
}

.k-stats-table summary {
    margin: 0 0 0.5rem;
    font-size: 0.9em;
    font-weight: 600;
    cursor: pointer;
}

.k-stats-table .k-day-label {
    flex-wrap: wrap;
    text-align: left;
}

.k-stats-controls {
    display: flex;
    flex-wrap: wrap;
    /* The sort chips keep their own height beside the taller fields. */
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
}

.k-stats-controls .k-stats-search {
    flex: 1 1 12rem;
    margin: 0;
}

.k-stats-controls .k-stats-group {
    flex: 1 1 10rem;
    width: auto;
    margin: 0;
}

/* The search, the group and the date fields in the till's own colours, side by side as one kind
   of control: the same corners, the card's fill, gold when focused. */
.k-stats-controls .k-stats-search,
.k-stats-controls .k-stats-group,
.k-period-field input {
    --pico-form-element-focus-color: color-mix(in srgb, var(--k-gold) 45%, transparent);
    border-color: var(--k-line);
    border-radius: 12px;
    background-color: var(--k-surface);
    color: inherit;
}

/* Chromium alone draws a clear button, in blue; Firefox none. Neither, so both look alike. */
.k-stats-search::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

.k-nowrap {
    white-space: nowrap;
}

.k-day-label--wrap {
    flex-wrap: wrap;
    row-gap: 0.1rem;
}

.k-stats-sort {
    display: flex;
    gap: 0.4rem;
}

.k-stats-sort .k-chip {
    min-height: 40px;
}

/* A product's row, and under it a bar against the first: the list is also the chart. */
.k-stats-product {
    flex-wrap: wrap;
    row-gap: 0.45rem;
}

.k-stats-bar {
    flex-basis: 100%;
    height: 6px;
    border-radius: 3px;
    background: var(--k-sunken);
    overflow: hidden;
}

.k-stats-bar > span {
    display: block;
    height: 100%;
    border-radius: 3px;
    background: var(--k-chart-cash);
}

.k-stats-more {
    width: 100%;
    margin-top: 0.6rem;
}

/* Outlined gold like the other second choices in the till, not Pico's slate. */
.k-stats-more.secondary {
    border-color: var(--k-gold-strong);
    background: transparent;
    color: var(--k-gold-strong);
}

.k-stats-more[hidden] {
    display: none;
}

/* Sample sales with a template: a switch under the choices, its explanation under its name. */
.k-samples-switch {
    box-sizing: border-box;
    /* As wide as the list under it, and indented like its rows, so the toggle stands over the chevrons. */
    width: 100%;
    margin: 0 0 1rem;
    padding: 0.8rem 1.5rem;
    border-radius: 14px;
    background: var(--k-surface);
}

.k-samples-text {
    display: grid;
    gap: 0.2rem;
}

/* ---------- the daily close ---------- */

.k-day {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--k-surface);
}

.k-day-body {
    flex: 1;
    overflow-y: auto;
    padding: 1rem;
    background: var(--k-sunken);
}

.k-day-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-radius: 14px;
    background: var(--k-surface);
}

.k-day-date {
    font-size: 1.05em;
}

/* What the day took, in the size it deserves: this is the number the seller came for. */
.k-day-total {
    display: grid;
    gap: 0.25rem;
    margin-block: 1rem;
    padding: 1.5rem 1rem;
    border-radius: 14px;
    background: var(--k-surface);
    text-align: center;
}

.k-day-total strong {
    font-size: 2.2em;
    letter-spacing: -0.02em;
}

.k-day-total span {
    color: var(--k-text-muted);
}

.k-day-empty {
    padding: 2rem 1rem;
    color: var(--k-text-muted);
    text-align: center;
}

.k-day-section {
    margin-bottom: 1.25rem;
}

.k-day-section h2 {
    margin-bottom: 0.5rem;
    color: var(--k-text-muted);
    font-size: 0.9em;
    font-weight: 600;
}

.k-day-rows {
    border-radius: 14px;
    background: var(--k-surface);
    overflow: hidden;
}

.k-day-row {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    width: 100%;
    margin: 0;
    padding: 0.8rem 1rem;
    border: 0;
    border-bottom: 1px solid var(--k-line);
    border-radius: 0;
    background: transparent;
    color: inherit;
    font-size: 1em;
    text-align: left;
    touch-action: manipulation;
}


/* Title over subtitle, which is what a list row is: one thing, said twice at two sizes. The
   iPad app stacks them and so does this; putting them side by side is what made the product
   list read as a row of unrelated fragments. */
.k-row-text {
    flex: 1;
    display: grid;
    /* minmax(0, 1fr), not `auto`: an auto column is at least as wide as its longest word, and
       break-word only breaks a word that does not fit — so "Kaminanzündern" held the column open
       and the subtitle ran under the price. Allowed to be narrower than the word, the word breaks. */
    grid-template-columns: minmax(0, 1fr);
    gap: 0.1rem;
    min-width: 0;
}

/* break-word, never `anywhere`. The two look the same until a flex row runs short of space:
   `anywhere` also tells the browser this box may be as narrow as one character, so a neighbour
   that refuses to shrink can squeeze it to nothing — and an iPad showed "Speicher" set one letter
   per line down the screen. break-word keeps the longest word as the minimum, so the column holds
   its ground and only a word too long for the row is ever broken. */
.k-row-title {
    overflow-wrap: break-word;
}

.k-row-sub {
    color: var(--k-text-muted);
    font-size: 0.85em;
    overflow-wrap: break-word;
}

/* Who took a product's photograph: the quietest line on the row, never in the cashier's grid. */
.k-row-credit {
    color: var(--k-text-muted);
    font-size: 0.75em;
    overflow-wrap: anywhere;
}

/* The same credit in full under the editor's picture, with the licence and the source as links. */
.k-credit {
    margin: 0.25rem 0 0;
    color: var(--k-text-muted);
    font-size: 0.8em;
    text-align: center;
    overflow-wrap: anywhere;
}

.k-credit a {
    color: inherit;
}

/* A licence's name is one thing: it wraps as a whole, or not at all. */
.k-nowrap {
    white-space: nowrap;
}

/* The value a row carries: grey for a fact, gold for a price, red for one that subtracts. */
.k-row-value {
    flex-shrink: 0;
    color: var(--k-text-muted);
    font-variant-numeric: tabular-nums;
}

.k-row-price {
    flex-shrink: 0;
    color: var(--k-gold-strong);
    font-variant-numeric: tabular-nums;
}

.k-row-price--discount {
    color: var(--k-discount);
}

/* A fact about the app: a label, and a figure beside it or a sentence under it.

   The label is exactly as wide as its words, always. It may not wrap — "Speicher" set one letter
   per line was the first of these bugs — and it may not shrink either: `.k-row-text` is allowed to
   be narrower than its content, which the product list needs, and a label that shrank kept its
   words and lost its box, so the value began where the box ended and was drawn over the label's
   last word. Not shrinking, it has nothing to give, and the value takes what is left. */
.k-day-row--fact .k-row-text {
    flex: none;
}

.k-day-row--fact .k-row-title {
    white-space: nowrap;
}

.k-day-row--fact .k-row-value {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: break-word;
    text-align: right;
}

/* A row that belongs to the one above it — the figures under *Speicher*: indented, a size smaller and
   quieter, so the card reads as one topic and its details rather than as rows of equal weight. */
.k-day-row--part {
    padding-top: 0.55rem;
    padding-bottom: 0.55rem;
    padding-left: 2.25rem;
    font-size: 0.92em;
}

.k-day-row--part .k-row-title {
    color: var(--k-text-muted);
}

/* A sentence goes under its label, left-aligned, the full width, at every width. Beside the label
   it only fitted while it stayed on one line, and whether it did depended on the screen, the
   language and the sentence — a phone, then an iPad over plain HTTP, each found a case that didn't. */
.k-day-row--fact-long {
    flex-wrap: wrap;
}

.k-day-row--fact-long .k-row-value {
    flex-basis: 100%;
    text-align: left;
}

@media (max-width: 819px) {
    /* The list's subtitles stop at two lines on a phone: the column is narrow, and a description is
       read in full in the editor the row opens. */
    .k-row-sub {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        overflow: hidden;
    }
}

/* The chevron that says the row opens something. Muted: it is a hint, not a control. */
.k-row-chevron {
    flex-shrink: 0;
    color: var(--k-text-muted);
    opacity: 0.7;
}

.k-day-row .k-row-chevron svg {
    width: 18px;
    height: 18px;
}

.k-day-row:last-child {
    border-bottom: 0;
}

.k-day-row svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.k-day-label {
    flex: 1;
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    overflow-wrap: break-word;
}

.k-day-detail,
.k-day-method {
    color: var(--k-text-muted);
    font-size: 0.85em;
}

.k-day-amount {
    font-variant-numeric: tabular-nums;
}

/* ---------- managing what the till sells ---------- */

.k-icon-button--small {
    width: 36px;
    height: 36px;
}

.k-icon-button--small svg {
    width: 18px;
    height: 18px;
}

/* A product's picture in a list: small, square, and cropped to fit. */
.k-row-photo {
    width: 48px;
    height: 48px;
    flex-shrink: 0;
    /* Cover, not contain: these photographs are not all the same shape, and letterboxing them
       gave the list a column of black bars and postage stamps instead of a column of tiles. */
    object-fit: cover;
    border-radius: 10px;
    background: #fff;
}

.k-row-photo--large {
    width: 96px;
    height: 96px;
    border-radius: 12px;
}

/* No picture yet: an icon on a tint, as the wallet has, so the space reads as meant rather than as
   a picture that failed to load — and the row still lines up with the ones that have one. */
.k-photo-missing {
    display: grid;
    place-items: center;
    background: color-mix(in srgb, var(--k-gold) 22%, transparent);
    color: var(--k-gold-strong);
}

.k-photo-missing svg {
    width: 45%;
    height: 45%;
    max-width: 44px;
    max-height: 44px;
}

/* A picture, and the way to change it.
 *
 * The browser's own file control renders as a dark "Browse…" button followed by "No file
 * selected." — the one piece of furniture on these screens that came from nobody's design. The
 * input is still there and still focusable; it is the label that is visible. */
.k-picker {
    display: grid;
    gap: 0.6rem;
    justify-items: center;
    padding: 1rem;
    border-radius: 14px;
    background: color-mix(in srgb, var(--k-gold) 10%, transparent);
    text-align: center;
}

.k-picker-button {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 1rem;
    border-radius: 999px;
    color: var(--k-gold-strong);
    font-size: 0.95em;
    cursor: pointer;
    touch-action: manipulation;
}

.k-picker-button svg {
    width: 20px;
    height: 20px;
}


/* Off-screen rather than display:none — it keeps its place in the tab order. */
.k-picker input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    clip-path: inset(50%);
    overflow: hidden;
    white-space: nowrap;
}

.k-picker-actions {
    display: flex;
    gap: 0.5rem;
}

.k-picker-actions button {
    width: auto;
    margin: 0;
    padding: 0.4rem 1rem;
    font-size: 0.85em;
}

.k-group-dot {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--k-group-colour);
}

/* Which inventory the till is selling from. A tick in the gap every row leaves for one, so the
   list stays a list: the tinted band with a bar down its edge read as an error state. */
.k-active-mark {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    color: var(--k-gold-strong);
}

.k-active-mark svg {
    width: 22px;
    height: 22px;
}

.k-active-mark--none {
    visibility: hidden;
}

/* An action sheet, not a popover: the list it belonged to scrolls and clips its own overflow,
   so an absolutely positioned menu lost everything below the first item — Reset and Delete were
   drawn outside the card and cut off. A <dialog> has no container to be clipped by. */
.k-sheet {
    display: grid;
    gap: 0.4rem;
    margin-block: 1rem;
}

.k-sheet button {
    width: 100%;
    margin: 0;
    padding: 0.8rem 1rem;
    border: 1px solid var(--k-line);
    border-radius: 12px;
    background: var(--k-sunken);
    color: inherit;
    font-size: 1em;
    touch-action: manipulation;
}

.k-sheet button[disabled] {
    color: var(--k-text-muted);
    cursor: not-allowed;
}

.k-sheet .k-danger {
    color: var(--k-discount);
}

/* Why a dialog did not save, under the field that is wrong. */
.k-problem {
    margin: 0.5rem 0 0;
    color: var(--k-discount);
    font-size: 0.9em;
}

.k-palette {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-bottom: 1rem;
}

.k-swatch {
    width: 42px;
    height: 42px;
    margin: 0;
    padding: 0;
    border: 3px solid transparent;
    border-radius: 50%;
    background: var(--k-group-colour);
    touch-action: manipulation;
}

.k-swatch[aria-pressed="true"] {
    border-color: var(--k-text-muted);
}

.k-template-list {
    display: grid;
    gap: 0.5rem;
    margin-block: 1rem;
}

.k-template {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    width: 100%;
    margin: 0;
    padding: 0.8rem 1rem;
    border: 1px solid var(--k-line);
    border-radius: 12px;
    background: var(--k-surface);
    color: inherit;
    font-size: 1em;
    text-align: left;
    touch-action: manipulation;
}

.k-template-icon {
    font-size: 1.6em;
}

/* Two languages, sized to the words in them — not stretched across the row. Pico gives a
   <button> width:100%, which turned the chosen language into a gold bar half the screen wide. */
.k-segmented {
    display: inline-flex;
    flex-shrink: 0;
    gap: 0.2rem;
    padding: 0.2rem;
    border-radius: 999px;
    background: var(--k-sunken);
}

.k-segmented button {
    width: auto;
    margin: 0;
    padding: 0.35rem 0.9rem;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--k-text-muted);
    font-size: 0.9em;
    touch-action: manipulation;
}

.k-segmented button[aria-pressed="true"] {
    background: var(--k-surface);
    color: inherit;
    font-weight: 600;
}

/* Deleting is not the same kind of act as saving, and must not wear the same colour. */
.k-modal-actions button.k-danger:not(.secondary) {
    border-color: var(--k-discount);
    background: var(--k-discount);
    color: #fff;
}

/* Pico's focus ring is indigo, which belongs to the site around the till rather than to the
   till. Inside the app it takes the app's own colour. */
.k-app,
.k-modal {
    --pico-primary-focus: color-mix(in srgb, var(--k-gold) 45%, transparent);
    --pico-form-element-active-border-color: var(--k-gold-strong);
    --pico-primary: var(--k-gold-strong);
}

/* ---------- first start: the setup assistant ---------- */

/* The three screens the iPad app opens with, in the till's frame: a step bar across the top, a
   scrolling middle and one gold button at the foot. The body and the foot share one measure, so
   the button sits under the text it finishes. */
.k-setup {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.k-setup-tag {
    padding: 0.7rem 1rem 0;
    color: var(--k-text-muted);
    font-size: 0.75em;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.k-setup-progress {
    display: grid;
    /* One column per step, however many: the wallet made it four. */
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 0.5rem;
    margin: 0.4rem 1rem 0;
    padding: 0;
    list-style: none;
}

.k-setup-progress li {
    padding-top: 0.45rem;
    border-top: 4px solid color-mix(in srgb, var(--k-gold) 25%, transparent);
    color: var(--k-text-muted);
    font-size: 0.85em;
    text-align: center;
}

.k-setup-progress .k-setup-done {
    border-top-color: var(--k-gold);
}

.k-setup-progress [aria-current="step"] {
    color: inherit;
    font-weight: 600;
}

.k-setup-body {
    --k-fade-top: 0px;
    --k-fade-bottom: 0px;
    flex: 1;
    min-height: 0;
    padding: 0.5rem 1rem 1rem;
    overflow-y: auto;
    /* Where more is out of sight, the content fades out at that edge. On an iPad in landscape the
       welcome's third step ended cut in half at the button bar, and nothing said the page went on.
       Drawn over the content, not behind it: a shade in the background was hidden by the grey card
       it was meant to point past. app.js says which edges have more (`data-more-*`). */
    -webkit-mask-image: linear-gradient(to bottom, transparent, #000 var(--k-fade-top),
        #000 calc(100% - var(--k-fade-bottom)), transparent);
    mask-image: linear-gradient(to bottom, transparent, #000 var(--k-fade-top),
        #000 calc(100% - var(--k-fade-bottom)), transparent);
}

.k-setup-body[data-more-above] {
    --k-fade-top: 1.5rem;
}

.k-setup-body[data-more-below] {
    --k-fade-bottom: 3rem;
}

/* And the bar lifts off what runs on underneath it. */
.k-setup:has(> .k-setup-body[data-more-below]) .k-setup-foot {
    box-shadow: 0 -8px 14px -10px var(--k-scroll-cue);
}

.k-setup-inner {
    max-width: 34rem;
    margin: 0 auto;
}

.k-setup-hero {
    margin-top: 1.5rem;
    text-align: center;
}

.k-setup-emoji {
    font-size: 3.2em;
    line-height: 1.2;
}

.k-setup-hero h1,
.k-setup-title {
    margin: 0.4rem 0 0.3rem;
    font-size: 1.7em;
    letter-spacing: -0.02em;
    text-align: center;
}

.k-setup-lead {
    margin: 0 0 1.25rem;
    color: var(--k-text-muted);
    text-align: center;
}

.k-setup-card {
    margin-bottom: 1rem;
    padding: 1rem 1.25rem;
    border-radius: 16px;
    background: var(--k-sunken);
}

.k-setup-feature {
    display: flex;
    align-items: flex-start;
    gap: 0.8rem;
    padding: 0.45rem 0;
}

.k-setup-feature svg {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    margin-top: 0.15rem;
    color: var(--k-gold-strong);
}

.k-setup-feature strong,
.k-setup-question strong {
    display: block;
}

.k-setup-feature span,
.k-setup-question span {
    color: var(--k-text-muted);
    font-size: 0.9em;
}

.k-setup-question {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
}

.k-setup-question strong {
    font-size: 1.05em;
}

/* The number in the gold circle, as the iPad app counts its two questions. */
.k-setup-number {
    display: grid;
    flex-shrink: 0;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--k-gold);
    color: #241a0f;
    font-size: 0.9em;
    font-weight: 700;
}

/* Pico's switch, in the till's colours and at a thumb's size. The question above it is answered
   with a thumb, and a checkbox the size of a full stop is the wrong control for that. */
.k-switch {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin: 0.9rem 0 0;
    font-size: 1.05em;
    cursor: pointer;
}

.k-switch input[role="switch"] {
    --pico-switch-background-color: color-mix(in srgb, var(--k-text-muted) 40%, transparent);
    --pico-switch-checked-background-color: var(--k-gold);
    --pico-switch-thumb-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    flex-shrink: 0;
    margin: 0;
    font-size: 1.45em;
}

.k-setup-details {
    margin-top: 0.75rem;
}

.k-setup-details[hidden] {
    display: none;
}

.k-setup-details .k-day-rows {
    margin-bottom: 0.75rem;
}

.k-setup-label {
    margin: 0.75rem 0 0.1rem;
    font-weight: 600;
}

.k-setup-hint {
    margin: 0.2rem 0 0.6rem;
    color: var(--k-text-muted);
    font-size: 0.85em;
}

.k-setup-foot {
    /* Positioned so its shadow is drawn over the body's text, not under it. */
    position: relative;
    padding: 0.75rem 1rem calc(0.75rem + var(--k-safe-bottom));
    border-top: 1px solid var(--k-line);
    background: var(--k-surface);
    text-align: center;
}

.k-setup-foot .k-setup-inner {
    display: grid;
    gap: 0.4rem;
}

.k-setup-primary {
    width: 100%;
    margin: 0;
    padding: 0.9rem;
    border: 0;
    border-radius: 14px;
    background: var(--k-gold);
    color: #241a0f;
    font-size: 1.1em;
    font-weight: 700;
    touch-action: manipulation;
}

.k-setup-link,
.k-setup-back {
    width: auto;
    margin: 0 auto;
    padding: 0.4rem 0.6rem;
    border: 0;
    background: transparent;
    color: var(--k-gold-strong);
    font-size: 1em;
    touch-action: manipulation;
}

.k-setup-back {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    margin: 0.25rem 0 0 -0.6rem;
}

.k-setup-back svg {
    width: 20px;
    height: 20px;
}

.k-setup-check {
    display: grid;
    gap: 0.6rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.k-setup-check li {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.k-setup-check svg {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    color: var(--k-ok);
}

.k-setup .k-day-rows {
    border: 1px solid var(--k-line);
}

/* The send-off has nothing at the foot but its button, and the inventory step nothing at all:
   an empty bar under a list is a line with no meaning. */
.k-setup-foot:has(.k-setup-inner:empty) {
    display: none;
}

/* Emoji and icons in one column, so the five rows of the inventory step line up. */
.k-setup .k-template-icon,
.k-setup .k-row-lead {
    width: 40px;
    flex-shrink: 0;
    justify-content: center;
    text-align: center;
}

/* The chooser behind "Import a Kässeli file": real, in the document, and out of sight, the same
   way the picture pickers keep theirs. */
.k-file-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    clip-path: inset(50%);
    overflow: hidden;
    white-space: nowrap;
}

/* A row that cannot be tapped right now — export with nothing to export — says so quietly. */
.k-day-row--tap[disabled] {
    color: var(--k-text-muted);
    cursor: not-allowed;
}

/* ---------- hover, only where something can hover ---------- */

/* A finger does not hover, but iOS pretends it did: after a tap the element stays in :hover until
   the next touch lands somewhere else, and that next touch is spent clearing it. On the till that
   showed as a tile left standing raised after a tap — and, reported on an iPad, as the very next
   swipe on the cart not scrolling the cart but rubber-banding the whole page, until a tap
   anywhere put things right (docs/bugs/open/cannot_move_shopping_cart_items.md). So none of
   these apply unless the device has a pointer that genuinely hovers. */
@media (hover: hover) {
    .k-icon-button:hover {
        background: color-mix(in srgb, var(--k-gold) 22%, transparent);
    }

    .k-tile:hover {
        box-shadow: 0 10px 22px -14px rgba(0, 0, 0, 0.55);
    }

    /* A row that is itself a button — the whole width of it, not a link-sized target in the
       middle. */
    .k-day-row--tap:hover {
        background: color-mix(in srgb, var(--k-gold) 8%, transparent);
    }

    .k-picker-button:hover {
        background: color-mix(in srgb, var(--k-gold) 18%, transparent);
    }
}

/* ---------- no memory, no till ---------- */

.k-failure {
    max-width: 34rem;
    margin: 0 auto;
    padding: 2rem 1.25rem;
    text-align: center;
}

.k-failure h2 {
    color: var(--k-discount);
}

.k-failure-detail {
    margin-top: 1rem;
    color: var(--k-text-muted);
    font-family: var(--pico-font-family-monospace, monospace);
    font-size: 0.85em;
}

/* ---------- installed: the till is the whole screen ---------- */

@media (display-mode: standalone) {
    .k-landing {
        display: none;
    }

    .container > .site-header,
    .container > .site-footer {
        display: none;
    }

    .container {
        max-width: none;
        padding: 0;
    }

    body {
        background-image: none;
    }

    /* Nothing on the page scrolls but the till's own boxes; the page itself has no business
       moving under a finger that missed one of them. */
    html,
    body {
        overscroll-behavior: none;
    }

    .k-app {
        height: 100dvh;
        margin: 0;
        border: 0;
        border-radius: 0;
        padding-top: var(--k-safe-top);
        padding-bottom: var(--k-safe-bottom);
        padding-left: var(--k-safe-left);
        padding-right: var(--k-safe-right);
    }
}

/* The start screen of an installed till (index.jte): standalone only, since a tab shows the
   explanation meanwhile; faded in late, so a quick start never flashes it; gone as soon as the app
   has said which view it drew. */
.k-boot {
    display: none;
}

@media (display-mode: standalone) {
    .k-boot {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        min-height: 100vh;
        min-height: 100dvh;
        /* The ground the till will stand on, so the screen does not change colour when it comes. */
        background: var(--k-sunken);
        color: var(--k-text-muted);
        animation: k-boot-in 0.3s ease-out 0.4s both;
    }
}

html[data-k-view] .k-boot {
    display: none;
}

@keyframes k-boot-in {
    from {
        opacity: 0;
    }
}

/* A visitor who chose "use it in this tab anyway" gets the same treatment without installing:
   the explanation has been read, and it should not sit above the till on every reload. */
html[data-k-view="till"] .k-landing {
    display: none;
}

@media (prefers-reduced-motion: reduce) {
    .k-tile,
    .k-tile:active {
        transition: none;
        transform: none;
    }
}

/* ---------- the wallet ---------- */

/* The bar's wallet button grows a figure beside its icon once the wallet has been counted: what
   should be in it right now. On a phone the icon stands alone. */
.k-wallet-button {
    flex: none;
    width: auto;
    gap: 0.4rem;
    grid-auto-flow: column;
    padding-inline: 0.7rem;
}

.k-wallet-amount {
    /* One line, however little room the bar after a sale leaves beside it. */
    white-space: nowrap;
    font-size: 0.95em;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.k-wallet-amount:empty {
    display: none;
}

.k-wallet-button:not(.k-wallet-button--amount) {
    width: 44px;
    padding: 0;
}

@media (max-width: 819px), (max-height: 500px) {
    .k-wallet-button {
        width: 44px;
        padding: 0;
    }

    .k-wallet-amount {
        display: none;
    }
}

.k-wallet-head {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-top: 1rem;
}

.k-wallet-text {
    display: grid;
    gap: 0.15rem;
}

.k-wallet-text span {
    color: var(--k-text-muted);
}

.k-wallet-photo {
    width: 72px;
    height: 72px;
    flex: none;
    border-radius: 18px;
    object-fit: cover;
}

.k-wallet-photo--icon {
    display: grid;
    place-items: center;
    background: color-mix(in srgb, var(--k-gold) 22%, transparent);
    color: var(--k-gold-strong, #8a6a2b);
}

.k-wallet-photo--icon svg {
    width: 36px;
    height: 36px;
}

.k-wallet-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-block: 1rem;
}

.k-wallet-actions button {
    display: inline-flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: auto;
    margin: 0;
    padding: 0.8rem;
    border-radius: 12px;
    font-weight: 600;
}

/* Gold like Abschliessen and Save, not Pico's blue: the till has one primary colour. */
.k-wallet-actions button:not(.secondary) {
    border-color: var(--k-gold);
    background: var(--k-gold);
    color: #241a0f;
}

.k-wallet-actions button.secondary {
    border-color: var(--k-gold-strong);
    background: transparent;
    color: var(--k-gold-strong);
}

.k-wallet-actions button svg {
    width: 20px;
    height: 20px;
}

.k-wallet-intro {
    display: grid;
    justify-items: start;
    gap: 0.5rem;
    margin-top: 1rem;
}

/* In the assistant the intro sits under a centred heading; the icon joins it in the middle. */
.k-setup .k-wallet-intro {
    justify-items: stretch;
}

.k-setup .k-wallet-intro .k-wallet-photo {
    justify-self: center;
}

.k-wallet-intro h2 {
    margin: 0.5rem 0 0;
}

.k-wallet-intro p {
    margin: 0;
}

/* The verdict on a count in the history: tips in green, missing money in red. */
.k-wallet-diff {
    font-size: 0.85em;
    font-weight: 700;
    white-space: nowrap;
}

.k-wallet-diff--tips {
    color: var(--k-ok-strong);
}

.k-wallet-diff--short {
    color: var(--k-discount);
}

.k-wallet-diff--even {
    color: var(--k-text-muted);
    font-weight: 400;
}

/* Deposit or withdrawal: two buttons that read as one control, the pressed one filled. */
.k-toggle {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.4rem;
    margin-bottom: 0.75rem;
}

.k-toggle button {
    margin: 0;
    border: 1px solid var(--k-line);
    background: var(--k-surface);
    color: inherit;
}

.k-toggle button[aria-pressed="true"] {
    border-color: var(--k-gold);
    background: color-mix(in srgb, var(--k-gold) 22%, transparent);
    font-weight: 700;
}

.k-wallet-note {
    margin-top: 0.75rem;
}

/* The day's verdict on the wallet: tips in green, missing money in red, in the amount column. */
.k-day-row--tips .k-day-amount {
    color: var(--k-ok-strong);
}

.k-day-row--short .k-day-amount {
    color: var(--k-discount);
}
