/*
 * DoyaPos design system — single source of truth for both the terminal and the panels.
 *
 * Mobile-first and touch-first. The terminal is used standing up, one-handed, in salon
 * lighting, by someone who wants to be done in five seconds. Every rule below serves
 * that: large hit targets, high contrast, no hover-dependent affordances, and the
 * payment buttons parked where a thumb naturally lands.
 *
 * Palette follows the customer concept PDF (deep navy surfaces, DOYA blue accent).
 */

:root {
    /* Surfaces — darkest at the back, lighter as elements come forward. */
    --dp-bg:            #0b1220;
    --dp-surface:       #101c33;
    --dp-surface-2:     #16273f;
    --dp-surface-3:     #1d3252;
    --dp-border:        #24406b;

    /* Brand */
    --dp-blue:          #1f6fff;
    --dp-blue-strong:   #0d5ae8;
    --dp-navy:          #16305c;

    /* Payment. Cash and card must never be confused at a glance, so they differ in
       hue AND in position — colour alone is not an accessible distinction. */
    --dp-cash:          #1a7a3c;
    --dp-cash-strong:   #14612f;
    --dp-card:          var(--dp-blue);
    --dp-card-strong:   var(--dp-blue-strong);

    /* Text */
    --dp-text:          #eaf1ff;
    --dp-text-muted:    #93a7c8;
    --dp-text-dim:      #64789b;

    /* Status */
    --dp-danger:        #d64545;
    --dp-warn:          #d99a2b;
    --dp-ok:            #2fa36b;

    /* Touch targets. 56px is the floor; primary actions get considerably more.
       Anything smaller gets mis-tapped by wet or gloved hands. */
    --dp-tap-min:       56px;
    --dp-tile-h:        84px;
    --dp-pay-h:         112px;

    --dp-radius:        14px;
    --dp-radius-sm:     10px;
    --dp-gap:           10px;

    --dp-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

    /* Safe areas so the payment buttons clear the iOS home indicator. */
    --dp-safe-bottom: env(safe-area-inset-bottom, 0px);
    --dp-safe-top:    env(safe-area-inset-top, 0px);
}

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

/* The `hidden` attribute only sets `display: none` at the user-agent level, so ANY
   `display` declaration silently overrides it. That is not cosmetic: a hidden full-screen
   overlay keeps its layout box and swallows every click on the page beneath it. This rule
   makes `hidden` mean hidden, whatever a component's own display value happens to be. */
[hidden] { display: none !important; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--dp-bg);
    color: var(--dp-text);
    font-family: var(--dp-font);
    /* The terminal is an app, not a document: no rubber-band scroll, no accidental zoom,
       no long-press text selection while tapping tiles quickly. */
    overscroll-behavior: none;
    -webkit-text-size-adjust: 100%;
    -webkit-tap-highlight-color: transparent;
}

body.dp-terminal {
    user-select: none;
    height: 100dvh;
    overflow: hidden;
}

/* ------------------------------------------------------------------ layout */

.dp-app {
    display: flex;
    flex-direction: column;
    height: 100dvh;
}

.dp-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: calc(10px + var(--dp-safe-top)) 14px 10px;
    background: var(--dp-navy);
    border-bottom: 1px solid var(--dp-border);
    flex: 0 0 auto;
}

.dp-header__staff {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: .2px;
}

.dp-header__logo { height: 22px; opacity: .9; }

.dp-main {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 12px 12px 0;
}

.dp-footer {
    flex: 0 0 auto;
    padding: 12px 12px calc(12px + var(--dp-safe-bottom));
    background: linear-gradient(to top, var(--dp-bg) 70%, transparent);
}

/* ------------------------------------------------------------------- tiles */

.dp-section-title {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--dp-blue);
    margin: 14px 2px 8px;
}
.dp-section-title:first-child { margin-top: 2px; }

/* Two columns on a phone, more as the viewport grows. The tablet shows "mehr Kacheln
   auf einen Blick" with identical logic — same markup, only the column count changes. */
.dp-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--dp-gap);
}

.dp-tile {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    min-height: var(--dp-tile-h);
    padding: 12px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);
    color: var(--dp-text);
    text-align: left;
    font: inherit;
    cursor: pointer;
    /* Feedback has to be instant — a barber taps and looks away before the render lands. */
    transition: transform .06s ease, background-color .06s ease;
}
.dp-tile:active {
    transform: scale(.97);
    background: var(--dp-surface-3);
}
.dp-tile__name {
    font-size: .95rem;
    font-weight: 600;
    line-height: 1.25;
}
.dp-tile__price {
    font-size: .95rem;
    font-weight: 700;
    color: var(--dp-blue);
}
.dp-tile--submenu .dp-tile__price::after { content: "›"; font-size: 1.3rem; }

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

/* The running receipt for the customer in the chair. It sits above the finish button,
   capped in height so a long visit scrolls instead of pushing the tiles off screen. */
.dp-cart {
    margin-bottom: 10px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);
    overflow: hidden;
}
.dp-cart__lines {
    max-height: 30dvh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.dp-cart__line {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--dp-border);
}
.dp-cart__line:last-child { border-bottom: none; }
.dp-cart__name {
    font-size: .92rem;
    /* A typed "Sonstiges" description can be long; it must not push the price out of view. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dp-cart__price {
    font-size: .92rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.dp-cart__remove {
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    background: transparent;
    border: 1px solid var(--dp-border);
    border-radius: 50%;
    color: var(--dp-text-muted);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}
.dp-cart__remove:active {
    background: var(--dp-danger);
    color: #fff;
}
.dp-cart__total {
    display: flex;
    justify-content: space-between;
    padding: 10px 12px;
    background: var(--dp-surface-3);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.dp-btn:disabled {
    background: var(--dp-surface-3);
    color: var(--dp-text-dim);
    cursor: default;
}

/* The same lines again on the payment screen — read-only this time, because money is
   about to change hands and the list should not shift under the barber's thumb. */
.dp-receipt {
    margin-bottom: 12px;
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);
    overflow: hidden;
}
.dp-receipt__line {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 12px;
    background: var(--dp-surface-2);
    border-bottom: 1px solid var(--dp-border);
    font-size: .92rem;
    font-variant-numeric: tabular-nums;
}
.dp-receipt__line:last-child { border-bottom: none; }

/* ---------------------------------------------------------------- payment */

.dp-pay {
    display: grid;
    gap: var(--dp-gap);
}
.dp-pay__btn {
    min-height: var(--dp-pay-h);
    border: none;
    border-radius: var(--dp-radius);
    color: #fff;
    font-size: 1.6rem;
    font-weight: 800;
    letter-spacing: .06em;
    cursor: pointer;
    transition: transform .06s ease;
}
.dp-pay__btn:active { transform: scale(.98); }
.dp-pay__btn--cash { background: var(--dp-cash); }
.dp-pay__btn--cash:active { background: var(--dp-cash-strong); }
.dp-pay__btn--card { background: var(--dp-card); }
.dp-pay__btn--card:active { background: var(--dp-card-strong); }

.dp-amount {
    padding: 16px;
    margin-bottom: 14px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);
}
.dp-amount__label { font-size: .85rem; color: var(--dp-text-muted); }
.dp-amount__value { font-size: 2rem; font-weight: 800; margin-top: 4px; }

/* ---------------------------------------------------------------- buttons */

.dp-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--dp-tap-min);
    width: 100%;
    padding: 0 18px;
    border: none;
    border-radius: var(--dp-radius);
    background: var(--dp-blue);
    color: #fff;
    font-size: 1rem;
    font-weight: 700;
    cursor: pointer;
}
.dp-btn:active { background: var(--dp-blue-strong); }
.dp-btn--ghost {
    background: transparent;
    border: 1px solid var(--dp-border);
    color: var(--dp-text-muted);
}
.dp-btn--link {
    width: auto;
    min-height: auto;
    background: none;
    color: var(--dp-text-muted);
    font-size: .85rem;
    font-weight: 500;
    padding: 6px 4px;
}

/* Focus rings are shown for keyboard navigation but not after a tap. Without
   :focus-visible the browser leaves a stray outline on whatever was last touched,
   which reads as a stuck or selected control on a touch-only device. */
.dp-btn:focus,
.dp-tile:focus,
.dp-pay__btn:focus { outline: none; }

.dp-btn:focus-visible,
.dp-tile:focus-visible,
.dp-pay__btn:focus-visible {
    outline: 3px solid var(--dp-text);
    outline-offset: 2px;
}

/* ------------------------------------------------------------ form fields */

.dp-field {
    display: block;
    margin-bottom: 14px;
}
.dp-field > span {
    display: block;
    margin-bottom: 6px;
    font-size: .85rem;
    font-weight: 600;
    color: var(--dp-text-muted);
}
.dp-field input {
    width: 100%;
    min-height: var(--dp-tap-min);
    padding: 0 14px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius-sm);
    color: var(--dp-text);
    /* 16px minimum: iOS Safari zooms the whole page on focus for anything smaller,
       which throws off every tap target on the screen. */
    font-size: 1rem;
    font-family: inherit;
}
.dp-field input:focus {
    outline: 2px solid var(--dp-blue);
    outline-offset: 1px;
}

.dp-hint {
    margin: 0 2px 16px;
    font-size: .9rem;
    line-height: 1.5;
    color: var(--dp-text-muted);
}
#customError {
    color: var(--dp-danger);
    font-weight: 600;
}

/* ---------------------------------------------------------------- keypad */

.dp-grid--keypad {
    grid-template-columns: repeat(3, 1fr);
    margin-bottom: 12px;
}
@media (min-width: 600px) {
    /* Overrides the 3-column responsive rule above — a numeric keypad is always 3 wide,
       whatever the viewport. */
    .dp-grid--keypad { grid-template-columns: repeat(3, 1fr); }
}
.dp-tile--key {
    align-items: center;
    justify-content: center;
    min-height: 68px;
}
.dp-tile--key .dp-tile__name {
    font-size: 1.6rem;
    font-weight: 700;
}

#pinDots {
    letter-spacing: .3em;
    transition: color .15s ease;
}
#pinDots.is-error { color: var(--dp-danger); }

/* ----------------------------------------------------------------- toast */

/* Confirmation is a glance, not a dialog: it must never need dismissing, because the
   barber has already moved on to the next customer.

   Anchored to the TOP. It used to sit above the finish button, but the footer grows with
   the cart, so on a three-line receipt the toast landed on top of the lines and made the
   running total unreadable — obscuring the number the barber is about to charge. The top
   edge is the only position that stays clear however long the visit gets. */
.dp-toast {
    position: fixed;
    left: 50%;
    top: calc(64px + var(--dp-safe-top));
    transform: translateX(-50%);
    z-index: 50;
    padding: 12px 20px;
    max-width: calc(100vw - 32px);
    background: var(--dp-ok);
    color: #04170d;
    border-radius: 999px;
    font-size: .95rem;
    font-weight: 700;
    box-shadow: 0 8px 24px rgb(0 0 0 / .35);
    animation: dp-toast-in .16s ease-out;
}
@keyframes dp-toast-in {
    from { opacity: 0; transform: translate(-50%, -8px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .dp-toast { animation: none; }
}

/* ------------------------------------------------------- offline indicator */

/* Offline is a normal state here, not an error — the shop keeps working. It is shown
   calmly and persistently rather than as an alarm, with the pending count so the owner
   can see nothing was lost. */
.dp-offline {
    display: none;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: var(--dp-warn);
    color: #241a02;
    font-size: .85rem;
    font-weight: 700;
}
body.is-offline .dp-offline { display: flex; }

/* -------------------------------------------------------------- responsive */

/* Large phone / small tablet portrait. */
@media (min-width: 600px) {
    .dp-grid { grid-template-columns: repeat(3, 1fr); }
}

/* Tablet — the "mehr Kacheln auf einen Blick" layout from the concept. */
@media (min-width: 900px) {
    :root { --dp-tile-h: 96px; --dp-gap: 12px; }
    .dp-grid { grid-template-columns: repeat(4, 1fr); }
    .dp-main { padding: 16px 16px 0; }
    .dp-tile__name { font-size: 1.02rem; }
}

@media (min-width: 1280px) {
    .dp-grid { grid-template-columns: repeat(5, 1fr); }
}

/* Landscape tablet: put the payment buttons side by side so neither hand has to travel. */
@media (min-width: 720px) and (orientation: landscape) {
    .dp-pay { grid-template-columns: 1fr 1fr; }
}

/* Respect the user's motion preference — the scale feedback is a nicety, not a necessity. */
@media (prefers-reduced-motion: reduce) {
    .dp-tile, .dp-pay__btn { transition: none; }
    .dp-tile:active, .dp-pay__btn:active { transform: none; }
}

/* ==========================================================================
   PANEL — the owner's surface.
   Shares the terminal's palette and touch sizing, but is read at a desk on a
   laptop as often as on a phone, so it favours density over thumb reach.
   ========================================================================== */

body.dp-panel { min-height: 100dvh; }
body.dp-panel .dp-app { height: auto; min-height: 100dvh; }
body.dp-panel .dp-main { overflow: visible; padding-bottom: 40px; }

.dp-main--narrow { max-width: 420px; margin: 0 auto; width: 100%; }

/* Wide screens: cap the reading width. A table of sales stretched across a 27" monitor
   is a row of numbers nobody can track back to its label. */
@media (min-width: 900px) {
    body.dp-panel .dp-main,
    body.dp-panel .dp-tabs { max-width: 1080px; margin-inline: auto; width: 100%; }
}

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

.dp-tabs {
    display: flex;
    gap: 2px;
    padding: 0 12px;
    background: var(--dp-surface);
    border-bottom: 1px solid var(--dp-border);
    overflow-x: auto;
    /* The tab strip scrolls on a narrow phone rather than wrapping — a second row of
       tabs pushes the actual content below the fold. */
    scrollbar-width: none;
}
.dp-tabs::-webkit-scrollbar { display: none; }

.dp-tab {
    flex: 0 0 auto;
    min-height: 48px;
    padding: 0 16px;
    background: none;
    border: none;
    border-bottom: 3px solid transparent;
    color: var(--dp-text-muted);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}
.dp-tab.is-active {
    color: var(--dp-text);
    border-bottom-color: var(--dp-blue);
}

/* ---------------------------------------------------------------- toolbar */

.dp-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 10px;
    margin-bottom: 16px;
}
.dp-btn--auto { width: auto; }

.dp-field--inline { margin-bottom: 0; }
.dp-field--inline input { min-width: 160px; }

.dp-field select {
    width: 100%;
    min-height: var(--dp-tap-min);
    padding: 0 12px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius-sm);
    color: var(--dp-text);
    font-size: 1rem;
    font-family: inherit;
}

.dp-check {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 12px;
    font-size: .9rem;
    cursor: pointer;
}
.dp-check input {
    /* Checkboxes default to ~13px, which is far below a reliable tap target. */
    width: 22px;
    height: 22px;
    accent-color: var(--dp-blue);
}

/* ------------------------------------------------------------------ stats */

.dp-stats {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--dp-gap);
    margin-bottom: 18px;
}
@media (min-width: 700px) { .dp-stats { grid-template-columns: repeat(4, 1fr); } }

.dp-stat {
    padding: 14px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);
}
.dp-stat__label {
    display: block;
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--dp-text-muted);
}
.dp-stat__value {
    display: block;
    margin-top: 4px;
    font-size: 1.5rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ lists */

.dp-group {
    margin-bottom: 18px;
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);
    overflow: hidden;
}
.dp-group__head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: var(--dp-surface-3);
    font-weight: 700;
}

.dp-row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 4px 12px;
    padding: 12px;
    background: var(--dp-surface-2);
    border-bottom: 1px solid var(--dp-border);
}
.dp-row:last-child { border-bottom: none; }

/* Actions drop under the row on a phone rather than squeezing the amount. */
.dp-row__actions {
    grid-column: 1 / -1;
    display: flex;
    gap: 12px;
}
@media (min-width: 700px) {
    .dp-row { grid-template-columns: 1fr auto auto; }
    .dp-row__actions { grid-column: auto; }
}

.dp-row__title { font-size: .95rem; font-weight: 600; line-height: 1.45; }
.dp-row__meta { margin-top: 2px; font-size: .82rem; color: var(--dp-text-muted); }
.dp-row__value {
    font-size: 1.05rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A reversed sale is dimmed and struck through, never removed. The record is
   append-only, so the owner must see that a correction happened rather than a gap. */
.dp-row.is-reversed .dp-row__title,
.dp-row.is-reversed .dp-row__value { text-decoration: line-through; opacity: .55; }

.dp-list { margin-bottom: 24px; }

.dp-tag {
    display: inline-block;
    padding: 1px 7px;
    margin-left: 4px;
    background: var(--dp-surface-3);
    border-radius: 999px;
    font-size: .72rem;
    font-weight: 700;
    color: var(--dp-text-muted);
    vertical-align: middle;
}
.dp-tag--danger { background: var(--dp-danger); color: #fff; }

.dp-alert {
    padding: 10px 12px;
    margin: 0 0 14px;
    background: rgb(214 69 69 / .15);
    border: 1px solid var(--dp-danger);
    border-radius: var(--dp-radius-sm);
    color: #ffc9c9;
    font-size: .9rem;
}

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

.dp-modal {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgb(0 0 0 / .6);
    overflow-y: auto;
}
.dp-modal__box {
    width: 100%;
    max-width: 460px;
    max-height: 90dvh;
    overflow-y: auto;
    padding: 20px;
    background: var(--dp-surface);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);
}
.dp-modal__title { margin: 0 0 16px; font-size: 1.15rem; }
.dp-modal__actions {
    display: flex;
    gap: 10px;
    margin-top: 18px;
}
.dp-modal__actions .dp-btn { flex: 1; }

/* The pairing link. Wraps rather than truncating — it is copied by hand onto a tablet
   and is shown exactly once, so every character has to be visible. */
.dp-code {
    padding: 12px;
    background: var(--dp-bg);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius-sm);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: .82rem;
    word-break: break-all;
    user-select: all;
}

/* ==========================================================================
   CHARTS
   Palette validated with the dataviz validator against the dark chart surface
   (#16273f): lightness band, chroma floor, CVD separation and contrast all pass.
     BAR   #2fa36b   (= --dp-ok)
     KARTE #1f6fff   (= --dp-blue)
   Deutan ΔE 27.5, normal ΔE 29.4 — well clear. Tritan separation is 6.4, in the
   floor band, which is legal only with secondary encoding: both segments carry a
   direct label AND a 2px surface gap, so identity never rests on hue alone.
   ========================================================================== */

.dp-chart {
    margin-bottom: 24px;
    padding: 16px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);
}
.dp-chart__title {
    margin: 0 0 4px;
    font-size: .95rem;
    font-weight: 700;
}
.dp-chart__sub {
    margin: 0 0 16px;
    font-size: .8rem;
    color: var(--dp-text-muted);
}
.dp-chart__empty {
    padding: 8px 0 4px;
    font-size: .9rem;
    color: var(--dp-text-muted);
}

/* ------------------------------------------------------- ranked bars (h) */

/* Horizontal because the labels are long German service names — rotating them or
   truncating would cost more than the vertical space saves. */
.dp-bars { display: flex; flex-direction: column; gap: 10px; }

.dp-bar__row {
    display: grid;
    grid-template-columns: minmax(90px, 27%) 1fr auto;
    align-items: center;
    gap: 10px;
}
.dp-bar__label {
    font-size: .85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dp-bar__track {
    /* display:block is load-bearing, not tidiness: these are spans, and height has no
       effect on an inline box — the bars rendered as empty tracks with no fill at all. */
    display: block;
    position: relative;
    height: 20px;                 /* mark spec: bars capped well under 24px */
    background: var(--dp-bg);
    border-radius: 4px;
}
.dp-bar__fill {
    display: block;
    height: 100%;
    min-width: 3px;
    background: var(--dp-blue);
    /* Square at the baseline, 4px rounded at the data end — the end is where the
       value is, so that is the end that gets shaped. */
    border-radius: 0 4px 4px 0;
}
.dp-bar__value {
    font-size: .85rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.dp-bar__count {
    font-size: .78rem;
    color: var(--dp-text-muted);
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ split bar */

/* Two values, so this is one bar rather than a pie: a two-slice pie asks the reader
   to compare angles when a single length answers it instantly. */
.dp-split {
    display: flex;
    height: 28px;
    border-radius: 6px;
    overflow: hidden;
    /* The 2px surface gap between segments — separation by negative space, never a
       stroke, which would add ink that is not data. */
    gap: 2px;
    background: var(--dp-surface-2);
}
.dp-split__seg {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 3px;
    font-size: .8rem;
    font-weight: 700;
    color: #fff;
    font-variant-numeric: tabular-nums;
}
.dp-split__seg--cash { background: var(--dp-ok); border-radius: 6px 0 0 6px; }
.dp-split__seg--card { background: var(--dp-blue); border-radius: 0 6px 6px 0; }

.dp-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin-top: 12px;
    font-size: .82rem;
    /* Text wears text tokens, never the series colour — the swatch beside it carries
       identity, so the label stays readable at any contrast. */
    color: var(--dp-text-muted);
}
.dp-legend__item { display: flex; align-items: center; gap: 6px; }
.dp-legend__dot { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }

/* -------------------------------------------------------- hour columns */

.dp-hours {
    display: grid;
    grid-auto-flow: column;
    /* Capped so a day with a single busy hour renders as one column with air beside it,
       not a wall of colour that reads as a rendering fault. Mark spec caps bars at 24px;
       columns get a little more because they carry the hour label underneath. */
    grid-auto-columns: minmax(0, 34px);
    justify-content: start;
    align-items: end;
    gap: 2px;                     /* the surface gap again, between adjacent columns */
    height: 130px;
}
.dp-hour { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.dp-hour__col {
    display: block;
    background: var(--dp-blue);
    border-radius: 4px 4px 0 0;   /* rounded cap, square on the baseline */
    min-height: 2px;
}
.dp-hour__col--empty { background: var(--dp-border); }
.dp-hour__label {
    margin-top: 6px;
    font-size: .68rem;
    color: var(--dp-text-dim);
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ table view */

/* Every chart's numbers are also available as text — the contrast warning on any
   fill is discharged here, and a screen reader never depends on a bar's length. */
.dp-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.dp-table th, .dp-table td {
    padding: 7px 10px;
    text-align: left;
    border-bottom: 1px solid var(--dp-border);
}
.dp-table th { color: var(--dp-text-muted); font-weight: 600; font-size: .78rem; }
.dp-table td:last-child, .dp-table th:last-child { text-align: right; font-variant-numeric: tabular-nums; }

.dp-details { margin-top: 14px; }
.dp-details > summary {
    cursor: pointer;
    font-size: .82rem;
    color: var(--dp-text-muted);
    padding: 4px 0;
}

/* ==========================================================================
   DESKTOP LAYOUTS
   The terminal is a tablet app and stays touch-first. The panel and the admin
   are desk tools opened on a laptop, and were wrong: a phone column stretched
   across 1440px with one field per row. These give them the shape DoyaOps uses —
   a split sign-in, a capped content column, and forms that use the width.
   ========================================================================== */

/* -------------------------------------------------------------- sign-in */

/* Two columns on a desktop, stacked on a phone: brand and promise on the left,
   the form on the right — the same split cloud.doyaops.com uses, so someone who
   signs in there does not arrive somewhere that feels like a different product. */
.dp-auth {
    min-height: 100dvh;
    display: grid;
    grid-template-columns: 1fr;
    align-items: center;
    gap: 32px;
    padding: 24px;
    /* A faint grid, like the DoyaOps sign-in. Two gradients rather than an image:
       nothing to download, and it scales to any viewport. */
    background:
        linear-gradient(rgba(36, 64, 107, .16) 1px, transparent 1px) 0 0 / 44px 44px,
        linear-gradient(90deg, rgba(36, 64, 107, .16) 1px, transparent 1px) 0 0 / 44px 44px,
        radial-gradient(1200px 600px at 20% 15%, #12203a 0%, var(--dp-bg) 60%);
}

@media (min-width: 900px) {
    .dp-auth {
        grid-template-columns: 1fr minmax(380px, 440px);
        gap: 64px;
        padding: 48px min(8vw, 96px);
    }
}

/* Grid children default to min-width:auto, so one unbreakable string — the
   cloud.doyaops.com line under the button — was widening the column past the
   viewport and pushing the card off the right edge of a phone. */
.dp-auth > * { min-width: 0; }
.dp-auth__foot { overflow-wrap: anywhere; }

.dp-auth__brand { max-width: 560px; }

.dp-auth__logo { height: 46px; margin-bottom: 40px; }

.dp-auth__eyebrow {
    font-size: .74rem;
    font-weight: 700;
    letter-spacing: .22em;
    text-transform: uppercase;
    color: var(--dp-text-dim);
    margin: 0 0 14px;
}
.dp-auth__tagline {
    margin: 0 0 16px;
    font-size: clamp(1.6rem, 3.2vw, 2.4rem);
    font-weight: 800;
    line-height: 1.15;
}
.dp-auth__body {
    margin: 0 0 32px;
    font-size: 1rem;
    line-height: 1.6;
    color: var(--dp-text-muted);
    max-width: 46ch;
}
.dp-auth__features { display: flex; flex-direction: column; gap: 14px; }
.dp-auth__feature {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: .95rem;
    color: var(--dp-text);
}
.dp-auth__bullet {
    width: 34px; height: 34px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    border-radius: 10px;
    background: var(--dp-surface-3);
    color: var(--dp-blue);
    font-weight: 800;
}

/* The brand column is the part that goes when there is no room: on a phone the
   form is the whole job, and a wall of marketing above it just pushes it down. */
@media (max-width: 899px) {
    .dp-auth__body, .dp-auth__features { display: none; }
    .dp-auth__logo { height: 36px; margin-bottom: 18px; }
    .dp-auth__brand { max-width: none; text-align: center; }
}

.dp-auth__card {
    width: 100%;
    padding: 32px;
    background: var(--dp-surface);
    border: 1px solid var(--dp-border);
    border-radius: 18px;
    box-shadow: 0 24px 60px rgb(0 0 0 / .35);
}
.dp-auth__title { margin: 0 0 6px; font-size: 1.45rem; font-weight: 800; }
.dp-auth__sub { margin: 0 0 24px; font-size: .92rem; color: var(--dp-text-muted); }
.dp-auth__foot {
    margin: 20px 0 0;
    font-size: .82rem;
    color: var(--dp-text-dim);
    text-align: center;
}

/* -------------------------------------------------- language switcher */

.dp-lang {
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 40;
}
.dp-lang__select {
    appearance: none;
    min-height: 40px;
    padding: 0 34px 0 14px;
    background: var(--dp-surface-2) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2393a7c8' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>") no-repeat right 10px center / 16px;
    border: 1px solid var(--dp-border);
    border-radius: 999px;
    color: var(--dp-text);
    font: inherit;
    font-size: .88rem;
    cursor: pointer;
}
.dp-lang--inline { position: static; }

/* ----------------------------------------------------- panel / admin */

/* Capped, not stretched. A sales table pulled across a 27" monitor is a row of
   numbers nobody can trace back to its label. */
@media (min-width: 900px) {
    body.dp-panel .dp-main,
    body.dp-panel .dp-tabs,
    body.dp-panel .dp-header { max-width: 1200px; margin-inline: auto; width: 100%; }

    body.dp-panel .dp-header { border-radius: 0 0 14px 14px; }
    body.dp-panel .dp-main { padding: 24px 24px 60px; }
}

/* Forms lay out in columns on a desk and stack on a phone — the toolbar was a
   single column at every width, which is what made the desktop look unfinished. */
@media (min-width: 700px) {
    .dp-toolbar { align-items: end; }
    .dp-toolbar .dp-field--inline { flex: 1 1 200px; margin-bottom: 0; }
    .dp-toolbar .dp-btn--auto { flex: 0 0 auto; }
}

/* A modal on a laptop can afford two columns; on a phone it must not. */
@media (min-width: 700px) {
    .dp-modal__box { max-width: 560px; padding: 28px; }
    .dp-form-grid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0 16px;
    }
    .dp-form-grid .dp-field--wide { grid-column: 1 / -1; }
}

@media (min-width: 1100px) {
    .dp-stats { grid-template-columns: repeat(4, 1fr); }
    /* Two charts side by side once there is room for both to stay readable. */
    .dp-charts-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
}

/* --------------------------------------------- terminal on a wide screen */

/*
 * On a phone the receipt lives in a bar pinned to the bottom, because the tiles need
 * every pixel of a narrow screen. On a landscape tablet or a counter monitor there is
 * width to spare, so the receipt moves into a column of its own on the right: the tiles
 * stop being pushed up by a growing cart, and the running total sits at eye level next
 * to them rather than under the thumb.
 *
 * Purely a layout change — same markup, same elements, same JavaScript. The screens that
 * are not the catalog (login, payment, the free-amount form) keep the full width, since
 * a receipt column beside a two-field form would be empty furniture.
 */
@media (min-width: 1024px) {
    body.dp-terminal .dp-app {
        display: grid;
        grid-template-columns: 1fr minmax(300px, 360px);
        grid-template-rows: auto 1fr;
        align-items: start;
        max-width: 1600px;
        margin-inline: auto;
        column-gap: 20px;
        padding-inline: 20px;
    }

    body.dp-terminal .dp-offline,
    body.dp-terminal .dp-header { grid-column: 1 / -1; }

    body.dp-terminal .dp-main { grid-column: 1; grid-row: 2; }

    /* Only the catalog shares the row; every other screen takes the whole width. */
    body.dp-terminal .dp-main:not(#screenCatalog) { grid-column: 1 / -1; }

    body.dp-terminal .dp-footer {
        position: sticky;
        top: 20px;
        grid-column: 2;
        grid-row: 2;
        padding: 0;
        border-top: none;
        background: none;
        box-shadow: none;
    }

    /* With a column to itself the receipt can grow — it no longer eats the tiles. */
    body.dp-terminal .dp-cart__lines { max-height: 52dvh; }
    body.dp-terminal .dp-footer .dp-btn { min-height: 64px; font-size: 1.1rem; }
}
