/*
 * 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 {
    /* Sits on the receipt's top edge, wherever that has ended up. Anchored to the viewport
       it covered whatever was behind it — the tiles a barber was about to tap, or the lines
       they had just added. */
    position: absolute;
    left: 50%;
    bottom: 100%;
    margin-bottom: 6px;

    transform: translateX(-50%);
    z-index: 50;
    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); } }

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

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