/*
 * 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;
    /* Comfortably above the 44px a thumb needs, and small enough that a phone shows a
       whole category without scrolling. */
    --dp-tile-h:        76px;
    --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;
}

/* Brand block: the mark and the name it stands for, kept together on the left. */
.dp-header__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}
.dp-header__brand .dp-header__staff {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dp-header__tools {
    display: flex;
    align-items: center;
    gap: 14px;
    flex: 0 0 auto;
}

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

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

.dp-footer {
    /* The toast hangs off its top edge. */
    position: relative;
    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;
    min-height: var(--dp-tile-h);

    /* The name and its price belong together. Spread to the corners of an 84px box they
       read as two separate things with a hole between them, and the hole cost a row of
       tiles on a phone. */
    gap: 2px;
    justify-content: center;
    padding: 10px 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);
}
/* Used on its own, not alongside .dp-btn — so it has to undo the browser's default
   <button> chrome itself. Without the border reset every "Abmelden", "Bearbeiten" and
   "Mit DOYA koppeln" rendered as a boxed control instead of a quiet link. */
.dp-btn--link {
    width: auto;
    min-height: auto;
    padding: 6px 4px;
    border: none;
    border-radius: 6px;
    background: none;
    color: var(--dp-text-muted);
    font: inherit;
    font-size: .85rem;
    font-weight: 500;
    cursor: pointer;
}
.dp-btn--link:hover { color: var(--dp-text); }

/* 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 {
    /* Fixed under the header, centred. It used to hang off the receipt's top edge, which
       works on a phone where the receipt is at the bottom — but on a wide screen the footer
       sits beside the tiles near the top, so the message landed in the header, half-hidden
       behind the language button. That is where a barber least expects it and exactly where
       "Tag abgeschlossen" was being missed. Fixed to the viewport, it is in the same place on
       every screen and covers nothing that is being tapped. */
    position: fixed;
    left: 50%;
    top: calc(var(--dp-safe-top) + 64px);

    transform: translateX(-50%);
    z-index: 900;
    padding: 10px 18px;
    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); } }

/* Ab der fuenften Kachel drei Spalten statt vier. Der Tagesabschluss weist seit der
   Produkt-Trennung sechs Werte aus; in einem Viertel der ohnehin schmalen Spalte brach
   "LEISTUNGEN" mitten im Wort um ("LEISTUN GEN"). Drei Spalten geben jeder Kachel genug
   Breite, und sechs Werte stehen dann als zwei saubere Reihen. */
.dp-stats:has(> :nth-child(5)) { grid-template-columns: repeat(3, 1fr); }

.dp-stat {
    padding: 14px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: var(--dp-radius);

    /* Ohne min-width:0 nimmt eine Gitterzelle die Breite ihres laengsten Wortes als
       Untergrenze. Mit der sechsten Kachel im Tagesabschluss ("Produkte (Laden)") wurde
       die Reihe dadurch breiter als der Bildschirm: BAR war abgeschnitten und darunter
       lag ein Querbalken. Auf einem Tablet heisst das, der Barber sieht seine
       Bareinnahme beim Abschluss nicht. */
    min-width: 0;
}
.dp-stat__label {
    display: block;
    /* Umbrechen statt ueberstehen — siehe min-width oben. Break-word, nicht anywhere:
       zuerst an der Wortgrenze, mitten im Wort nur als letzter Ausweg. */
    overflow-wrap: break-word;
    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;
}

/* On a phone the header runs out of room: shop name, logo, language and sign-out do not
   fit across 390px, and the sign-out ran off the right edge. The logo goes first — it
   repeats a wordmark already spelled out beside it — and the language name follows,
   leaving the flag, which is the part that identifies the control anyway. */
@media (max-width: 599px) {
    .dp-lang--inline .dp-lang__name { display: none; }
    .dp-lang--inline .dp-lang__toggle { padding-inline: 10px; gap: 6px; }
}

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

/*
 * A flag, the language name and a chevron — the shape DoyaOps uses, so the switcher is
 * recognisable to anyone arriving from there.
 *
 * Built on <details>, so it opens and closes and answers the keyboard without a line of
 * JavaScript. The one thing that costs is closing on an outside click, which <details>
 * does not do on its own; a full-screen backdrop behind the open menu buys it back
 * without script.
 */
.dp-lang {
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 40;
}
/* Inside a header the switcher stops being fixed to the viewport — but it must stay a
   positioned box, or the menu below it anchors to the page instead and lands at the
   bottom of the document. */
.dp-lang--inline { position: relative; top: auto; right: auto; }

.dp-lang__toggle {
    display: flex;
    align-items: center;
    gap: 9px;
    min-height: 40px;
    padding: 0 14px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: 999px;
    color: var(--dp-text);
    font-size: .88rem;
    cursor: pointer;
    user-select: none;
    list-style: none;          /* Safari keeps a disclosure triangle without this. */
}
.dp-lang__toggle::-webkit-details-marker { display: none; }
.dp-lang__toggle:hover { border-color: var(--dp-blue); }
.dp-lang:not([open]) .dp-lang__toggle:focus-visible,
.dp-lang__toggle:focus-visible { outline: 2px solid var(--dp-blue); outline-offset: 2px; }

/* 4:3 keeps the German bands square-ish and the Union Jack from stretching. */
.dp-lang__flag {
    width: 22px;
    height: 16px;
    flex: 0 0 auto;
    border-radius: 3px;
    /* A hairline, so a white-heavy flag still reads as an object on a dark surface. */
    box-shadow: 0 0 0 1px rgb(255 255 255 / .18);
    object-fit: cover;
}
.dp-lang__name { font-weight: 600; }
.dp-lang__chevron { width: 14px; height: 14px; color: var(--dp-text-dim); }
.dp-lang[open] .dp-lang__chevron { transform: rotate(180deg); }

.dp-lang__menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 2;
    min-width: 100%;
    padding: 6px;
    background: var(--dp-surface);
    border: 1px solid var(--dp-border);
    border-radius: 12px;
    box-shadow: 0 16px 40px rgb(0 0 0 / .45);
}

/* Clicking anywhere else closes the menu — no script, just a backdrop that swallows
   the click and lets the <summary> toggle handle it. */
.dp-lang[open] .dp-lang__toggle::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 1;
}

.dp-lang__item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-radius: 8px;
    color: var(--dp-text);
    font-size: .9rem;
    text-decoration: none;
    white-space: nowrap;
}
.dp-lang__item:hover { background: var(--dp-surface-2); }
.dp-lang__item.is-current { color: var(--dp-blue); font-weight: 700; }

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

    /* Same inset as the content below it, so the header's edges line up with the first
       field rather than sitting ten pixels proud of it. */
    body.dp-panel .dp-header { padding-inline: 24px; }
    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; }
}

/* ------------------------------------------------- platform console (admin) */

/*
 * DOYA's own sign-in, deliberately unlike the shop owner's.
 *
 * The owner's screen sells the product: two columns, a promise, three bullets. This one
 * has no one to persuade — the people who reach it already work here, and everyone else
 * should realise within a second that they have the wrong address. So: one narrow card on
 * a plain surface, a steel accent instead of the product blue, and no marketing at all.
 *
 * The grid the customer screens use is dropped too. Shared styling was how this surface
 * ended up looking like a second front door for shop owners in the first place.
 */
.dp-console {
    min-height: 100dvh;
    display: grid;
    place-items: center;
    padding: 24px;
    background:
        radial-gradient(900px 500px at 50% -10%, #16233b 0%, var(--dp-bg) 70%);
}

.dp-console__card {
    width: 100%;
    max-width: 400px;
    padding: 32px 28px;
    background: var(--dp-surface);
    border: 1px solid var(--dp-border);
    border-radius: 16px;
    /* A flat top rule in steel, not the product blue: this is tooling, not the product. */
    border-top: 3px solid #64748b;
    box-shadow: 0 20px 50px rgb(0 0 0 / .4);
    text-align: left;
}

.dp-console__logo { height: 30px; margin-bottom: 22px; opacity: .9; }

.dp-console__eyebrow {
    margin: 0 0 6px;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: var(--dp-text-dim);
}

.dp-console__title { margin: 0 0 6px; font-size: 1.3rem; font-weight: 800; }

.dp-console__sub {
    margin: 0 0 24px;
    font-size: .88rem;
    line-height: 1.5;
    color: var(--dp-text-muted);
}

.dp-console__foot {
    margin: 20px 0 0;
    padding-top: 16px;
    border-top: 1px solid var(--dp-border);
    font-size: .78rem;
    line-height: 1.5;
    color: var(--dp-text-dim);
}

/* --------------------------------------------------------- pairing dialog */

/*
 * Three ways to get one token onto a tablet, numbered, because they are alternatives
 * rather than steps to follow in order — scanning is enough on its own.
 */
.dp-pair__step {
    margin: 18px 0 8px;
    font-size: .82rem;
    font-weight: 700;
    letter-spacing: .04em;
    color: var(--dp-text-muted);
}
.dp-pair__step:first-child { margin-top: 4px; }

.dp-pair__qr {
    display: block;
    width: 180px;
    height: 180px;
    /* White padding around it: a QR needs its quiet zone against a dark page or the
       camera cannot find the corner markers. */
    padding: 10px;
    background: #fff;
    border-radius: 10px;
}

.dp-code--copy {
    cursor: pointer;
    transition: border-color .15s ease;
}
.dp-code--copy:hover,
.dp-code--copy:focus-visible { border-color: var(--dp-blue); outline: none; }

/* The one route that works when the panel and the till are the same phone. */
.dp-pair__open {
    display: flex;
    text-decoration: none;
    margin-bottom: 8px;
}
.dp-pair__openhint { margin: 0 2px 4px; font-size: .82rem; }

/* ------------------------------------------------------------ my sales */

.dp-mine__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}
.dp-mine__head .dp-section-title { margin: 0; }

.dp-mine__date {
    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: inherit;
    font-size: 1rem;
}

/* A corrected sale stays on the list, struck through — the record is the product. */
.dp-row.is-reversed .dp-row__title,
.dp-row.is-reversed .dp-row__value { text-decoration: line-through; opacity: .65; }

#screenMine .dp-btn--ghost { margin-top: 16px; }

/* A header button that keeps its words on a desk and becomes its icon on a phone. */
.dp-icon-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
}
.dp-icon-btn svg { width: 19px; height: 19px; flex: 0 0 auto; }

@media (max-width: 599px) {
    /* The barber's name is the one thing on this bar that must not be cut, and with the
       label spelled out there was not room for it. */
    .dp-icon-btn__label { display: none; }
    .dp-icon-btn { padding: 6px 2px; }
}

/* The reason a sale was corrected — never struck through, even on a struck-through row:
   it is the one line on that row that still has to be read. */
.dp-row__reason {
    margin-top: 3px;
    color: var(--dp-text);
    font-style: italic;
    text-decoration: none;
    opacity: 1;
}
.dp-row.is-reversed .dp-row__reason { text-decoration: none; opacity: 1; }


/* Under the button, not above the fields: what a barber has to do on this screen is type
   two things, and the explanation of what else happens can wait until they have. */
.dp-claim__note { margin: 16px 2px 0; text-align: center; }

/* -------------------------------------------------------- install prompt */

/*
 * A slim bar at the foot of the sign-in screen offering "add to home screen". Above the
 * home indicator, out of the way of the form, and gone once dismissed or once the app is
 * running installed.
 */
.dp-install {
    position: fixed;
    left: 12px;
    right: 12px;
    bottom: calc(12px + var(--dp-safe-bottom));
    z-index: 60;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: var(--dp-surface-2);
    border: 1px solid var(--dp-border);
    border-radius: 14px;
    box-shadow: 0 12px 34px rgb(0 0 0 / .45);
}
.dp-install__hint {
    flex: 1 1 auto;
    font-size: .86rem;
    line-height: 1.4;
    color: var(--dp-text);
}
.dp-install__go {
    flex: 1 1 auto;
    min-height: 44px;
    padding: 0 16px;
    border: none;
    border-radius: 10px;
    background: var(--dp-blue);
    color: #fff;
    font: inherit;
    font-weight: 700;
    cursor: pointer;
}
.dp-install__x {
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    border: none;
    border-radius: 10px;
    background: none;
    color: var(--dp-text-dim);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
}
.dp-install__x:hover { color: var(--dp-text); }

/* Analytics trend badge — sits under a headline figure, shows the change vs the previous
   equal-length period. Colour carries direction; the arrow carries it again for anyone who
   cannot separate the two greens/reds by hue. */
.dp-stat__delta { display: inline-block; margin-top: 3px; font-size: .72rem; font-weight: 600; letter-spacing: .01em; }
.dp-stat__delta--up { color: var(--dp-ok); }
.dp-stat__delta--down { color: var(--dp-danger); }
.dp-stat__delta--flat { color: var(--dp-text); opacity: .5; }

/* Quick-range chips beside the date pickers. */
.dp-presets { display: inline-flex; gap: 12px; align-items: center; flex-wrap: wrap; }

/* "Logbuch, keine Kasse" notice — a one-time full-screen scrim the user clicks away. */
.dp-disclaimer { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(4, 8, 16, .72); }
.dp-disclaimer__card { max-width: 440px; width: 100%; background: var(--dp-card); border: 1px solid var(--dp-border); border-radius: var(--dp-radius); padding: 28px; text-align: center; box-shadow: 0 20px 60px rgba(0, 0, 0, .5); }
.dp-disclaimer__title { margin: 0 0 12px; font-size: 1.15rem; }
.dp-disclaimer__body { margin: 0 0 22px; line-height: 1.55; font-size: .95rem; color: var(--dp-text); opacity: .85; }
.dp-disclaimer .dp-btn { width: 100%; }

/* Kiosk (shared till): big name tiles + a numeric PIN pad, sized for tapping on a tablet. */
.dp-kiosk-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; }
.dp-kiosk-tile { min-height: 84px; border: 1px solid var(--dp-border); border-radius: var(--dp-radius); background: var(--dp-tile); color: var(--dp-text); font-size: 1.15rem; font-weight: 600; cursor: pointer; }
.dp-kiosk-tile:active { background: var(--dp-blue); color: #fff; }

.dp-pin { display: flex; gap: 16px; justify-content: center; margin: 14px 0 20px; }
.dp-pin__dot { width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--dp-border); }
.dp-pin__dot.is-filled { background: var(--dp-blue); border-color: var(--dp-blue); }

.dp-pinpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; max-width: 320px; margin: 0 auto; }
.dp-pinpad__key { min-height: 68px; border: 1px solid var(--dp-border); border-radius: var(--dp-radius); background: var(--dp-tile); color: var(--dp-text); font-size: 1.4rem; font-weight: 600; cursor: pointer; }
.dp-pinpad__key:active { background: var(--dp-blue); color: #fff; }
.dp-pinpad__key--muted { background: transparent; font-size: 1.2rem; opacity: .8; }

/* ---------------------------------------------------- shared panel filter
 *
 * One bar above the panes that governs Auswertung and Verkäufe alike. The chips carry a real
 * selected state: the old text links looked identical whether or not they were the range on
 * screen, so "7 Tage" and "30 Tage" were indistinguishable after the click.
 */
.dp-filterbar {
    /* Same gutter and cap as .dp-main / .dp-tabs, so the bar lines up with the cards it
       governs instead of floating off to the page edge. */
    padding: 14px 12px 10px;
    border-bottom: 1px solid var(--dp-border);
}
@media (min-width: 900px) {
    body.dp-panel .dp-filterbar { max-width: 1080px; margin-inline: auto; width: 100%; }
}
.dp-filterbar .dp-toolbar { margin-bottom: 6px; }
.dp-filterbar__summary {
    margin: 0;
    font-size: .88rem;
    color: var(--dp-muted);
}

.dp-chip {
    border: 1px solid var(--dp-border);
    background: transparent;
    color: var(--dp-muted);
    border-radius: 999px;
    padding: 7px 14px;
    font-size: .88rem;
    font-weight: 600;
    cursor: pointer;
}
.dp-chip:hover { color: var(--dp-text); border-color: var(--dp-border-strong, var(--dp-border)); }
.dp-chip.is-active {
    background: var(--dp-blue);
    border-color: var(--dp-blue);
    color: #fff;
}

/* "Nothing here" has to say so. An empty pane reads as a broken page. */
/* Spans the chart grid: a "nichts gefunden" box squeezed into one column reads like a
   chart that failed to draw, next to an empty space where the others should be. */
.dp-charts-2 > .dp-empty { grid-column: 1 / -1; }

.dp-empty {
    border: 1px dashed var(--dp-border);
    border-radius: var(--dp-radius);
    padding: 34px 22px;
    text-align: center;
}
.dp-empty__msg { margin: 0; font-size: 1rem; font-weight: 600; color: var(--dp-text); }
.dp-empty__hint { margin: 6px 0 0; font-size: .88rem; color: var(--dp-muted); }

/* A barber who has left still owns past numbers; the row says so instead of showing a name
 * nobody on the floor recognises. */
.dp-bar__row .dp-bar__label { white-space: normal; overflow: visible; text-overflow: clip; }

.dp-bar__off {
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--dp-muted);
    opacity: .8;
}

/* The day and time the till is on. Quiet, but never absent: the overview can be pointed at
   another date, and without this there is nothing on screen that says which day a tap would
   actually book. */
.dp-header__clock {
    margin-left: 10px;
    font-size: .8rem;
    color: var(--dp-muted);
    white-space: nowrap;
}
@media (max-width: 480px) { .dp-header__clock { display: none; } }

/* The header has to hold a logo, a name and up to five actions on a phone. It was letting the
   brand and the tools overlap — "DOYA" printed on top of "Logbuch" — because neither side was
   allowed to give way. The brand shrinks and truncates; the actions keep their size and scroll
   sideways if they must, so a button is never unreachable and never covered. */
.dp-terminal .dp-header { gap: 8px; }
.dp-terminal .dp-header__brand {
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
}
.dp-terminal .dp-header__staff {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dp-terminal .dp-header__tools {
    flex: 0 0 auto;
    gap: 10px;
    overflow-x: auto;
    scrollbar-width: none;
}
.dp-terminal .dp-header__tools::-webkit-scrollbar { display: none; }

/* Under ~430px the logo alone carries the branding: a truncated shop name earns nothing and
   the actions are what a barber actually needs to reach. */
@media (max-width: 430px) {
    .dp-terminal .dp-header__staff { display: none; }
}

/* A sale the server refused for good: the customer paid and nothing was recorded. Loud on
   purpose and it does not fade — the toast pattern is wrong here, because the only repair is
   a person re-entering the booking. Pinned under the header, the one spot free on every
   screen (same reasoning as .dp-toast). */
.dp-lost {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    top: 72px;
    z-index: 60;
    max-width: min(92vw, 460px);
    padding: 14px 16px;
    border-radius: 12px;
    border: 2px solid #f0b23c;
    background: #3b2408;
    color: #ffe9c2;
    font-size: 15px;
    line-height: 1.45;
    box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
}

.dp-lost strong { display: block; margin-bottom: 6px; font-size: 16px; }
.dp-lost__hint { margin-top: 8px; font-weight: 600; }
.dp-lost .dp-btn--link { margin-top: 10px; color: #ffe9c2; text-decoration: underline; }

/* Ankreuzfeld im Formular-Baustein.
 *
 * `.dp-field input` stylt Texteingaben: volle Breite, 44 px hoch, gefuellter Hintergrund. Auf
 * ein <input type="checkbox"> angewandt ergab das einen 160 px breiten, leeren weissen Kasten
 * ueber der Beschriftung — der Schalter "Namen auf der Theke anzeigen" sah aus wie ein
 * kaputtes Textfeld, und ob er an oder aus war, konnte man ueberhaupt nicht sehen. */
.dp-field input[type="checkbox"] {
    width: 22px;
    min-width: 22px;
    height: 22px;
    min-height: 22px;
    padding: 0;
    margin: 0;
    accent-color: var(--dp-blue);
    cursor: pointer;
}

/* Kaestchen und Beschriftung nebeneinander, nicht untereinander. */
.dp-field--inline {
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
}
.dp-field--inline > span {
    display: inline;
    margin-bottom: 0;
    color: var(--dp-text);
}

/* Zweizeilige Kachel: Nummer klein und zurueckgenommen, Name gross und fett. Der Name ist,
   was der Barber sucht; die Nummer ist nur die Bruecke zur alten neutralen Beschriftung. */
.dp-kiosk-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    line-height: 1.2;
    padding: 8px 10px;
}
.dp-kiosk-tile__num {
    font-size: .82rem;
    font-weight: 600;
    color: rgba(255, 255, 255, .42);
    letter-spacing: .04em;
}
.dp-kiosk-tile__name { font-size: 1.35rem; font-weight: 800; }

/* Dieselbe Ordnung in der Mitarbeiterliste des Inhabers. */
.dp-row__kiosk {
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .04em;
    color: var(--dp-text-muted);
    margin-bottom: 2px;
}
