/* ───────────────────────────── حاسبي · components */

/* ── app frame */

#app {
  position: relative;
  z-index: 1;
  min-height: 100dvh;
}

.screen {
  position: relative;
  min-height: 100dvh;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  padding: calc(18px + var(--safe-top)) var(--gutter) calc(28px + var(--safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.screen.wide {
  max-width: 1180px;
}

.screen.center {
  justify-content: center;
}

/* ── glass surfaces */

/* No backdrop blur here: a card appears 150+ times per screen, and a blur re-renders everything behind it whenever
   that changes, which on a phone was the whole cost of the "heavy" feeling (2026-10-12). The sky behind the cards
   stands still on the work surfaces (base.css), so a plain translucent gradient reads the same. Blur stays on the
   few overlays that sit over moving content (the network bar, the cart bar, the success overlay, the scanner's
   chips, the setup's sticky bar and tour), one surface at a time. */
.glass {
  position: relative;
  background: linear-gradient(180deg, rgba(var(--fg-rgb), 0.085), rgba(var(--fg-rgb), 0.032));
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow:
    inset 0 1px 0 rgba(var(--fg-rgb), 0.07),
    var(--shadow-card);
}

html.lite .glass {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: linear-gradient(180deg, var(--surface-card-a), var(--surface-card-b));
}

.card {
  padding: 18px;
}

/* Gradient hairline border for emphasis. */
.glow-border {
  border: 1px solid transparent;
  background:
    linear-gradient(180deg, var(--surface-raised), var(--surface-raised-2)) padding-box,
    linear-gradient(135deg, rgba(196, 181, 253, 0.7), rgba(99, 102, 241, 0.25) 45%, rgba(96, 165, 250, 0.65)) border-box;
}

.section-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font: 600 15px/1.3 var(--font-display);
  color: var(--text-1);
}

.section-title .ico {
  color: var(--violet-300);
}

/* ── buttons */

.btn {
  --btn-h: 56px;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: var(--btn-h);
  padding: 0 22px;
  border-radius: 16px;
  font: 600 17px/1.2 var(--font-display);
  color: var(--text-0);
  white-space: nowrap;
  user-select: none;
  transition:
    transform 0.2s var(--ease-spring),
    box-shadow 0.25s var(--ease-out),
    background-color 0.25s,
    opacity 0.2s;
}

.btn:active {
  transform: scale(0.965);
}

.btn[disabled],
.btn.is-busy {
  opacity: 0.55;
  pointer-events: none;
}

.btn .ico {
  flex: none;
}

.btn-block {
  width: 100%;
}

.btn-sm {
  --btn-h: 42px;
  padding: 0 14px;
  font-size: 15px;
  border-radius: 13px;
}

.btn-lg {
  --btn-h: 64px;
  font-size: 19px;
  border-radius: 20px;
}

.btn-primary {
  color: var(--on-brand);
  background: var(--grad-brand);
  box-shadow:
    0 12px 32px -12px rgba(124, 58, 237, 0.9),
    inset 0 1px 0 rgba(255, 255, 255, 0.28),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18);
  overflow: hidden;
}

.btn-primary::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(110deg, transparent 25%, rgba(var(--fg-rgb), 0.32) 47%, transparent 69%);
  background-size: 260% 100%;
  background-position: 140% 0;
  animation: sheen 5s ease-in-out infinite;
  pointer-events: none;
}

@keyframes sheen {
  0%,
  55% {
    background-position: 140% 0;
  }
  100% {
    background-position: -140% 0;
  }
}

.btn-primary:hover {
  box-shadow:
    0 16px 40px -12px rgba(124, 58, 237, 1),
    inset 0 1px 0 rgba(var(--fg-rgb), 0.3);
}

.btn-glass {
  background: rgba(var(--fg-rgb), 0.06);
  border: 1px solid var(--line-2);
  box-shadow: inset 0 1px 0 rgba(var(--fg-rgb), 0.06);
}

.btn-glass:hover {
  background: rgba(var(--fg-rgb), 0.09);
  border-color: var(--line-3);
}

.btn-ghost {
  color: var(--text-1);
}

.btn-ghost:hover {
  color: var(--text-0);
  background: rgba(var(--fg-rgb), 0.05);
}

.btn-danger {
  background: rgba(251, 113, 133, 0.12);
  border: 1px solid rgba(251, 113, 133, 0.4);
  color: var(--bad-ink-2);
}

.btn-icon {
  --btn-h: 44px;
  width: var(--btn-h);
  padding: 0;
  border-radius: 14px;
}

.btn .spinner {
  width: 18px;
  height: 18px;
}

.spinner {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2.5px solid rgba(var(--fg-rgb), 0.22);
  border-top-color: currentColor;
  animation: spin 0.8s linear infinite;
}

/* The hero action on the counter: rotating conic neon border. */
.mega {
  --angle: 0deg;
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  gap: 18px;
  width: 100%;
  min-height: 132px;
  padding: 22px 24px;
  border-radius: 28px;
  text-align: start;
  background:
    radial-gradient(120% 140% at 100% 0%, rgba(139, 92, 246, 0.42) 0%, transparent 55%),
    radial-gradient(120% 140% at 0% 100%, rgba(59, 130, 246, 0.38) 0%, transparent 55%),
    linear-gradient(160deg, var(--surface-deep-a) 0%, var(--surface-deep-b) 100%);
  box-shadow:
    0 24px 60px -24px rgba(124, 58, 237, 0.85),
    inset 0 1px 0 rgba(var(--fg-rgb), 0.12);
  transition: transform 0.25s var(--ease-spring);
}

.mega::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  padding: 2px;
  border-radius: inherit;
  background: conic-gradient(from var(--angle), #8b5cf6, #3b82f6, #22d3ee, #e879f9, #8b5cf6);
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: spin-angle 4s linear infinite;
}

.mega::after {
  content: '';
  position: absolute;
  inset: -1px;
  z-index: -2;
  border-radius: inherit;
  background: conic-gradient(from var(--angle), rgba(139, 92, 246, 0.6), rgba(59, 130, 246, 0.5), rgba(34, 211, 238, 0.45), rgba(232, 121, 249, 0.5), rgba(139, 92, 246, 0.6));
  filter: blur(18px);
  opacity: 0.55;
  animation: spin-angle 4s linear infinite;
}

html.lite .mega::after {
  display: none;
}

.mega:active {
  transform: scale(0.975);
}

.mega-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  border-radius: 22px;
  background: var(--grad-brand);
  box-shadow:
    0 12px 30px -10px rgba(124, 58, 237, 0.9),
    inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

.mega-title {
  font: 700 clamp(24px, 7.2vw, 32px) / 1.15 var(--font-display);
  white-space: nowrap;
}

.mega-sub {
  margin-top: 4px;
  color: var(--text-1);
  font-size: 15px;
}

/* ── form fields */

.field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.field[hidden] {
  display: none;
}

.field > label,
.label {
  font: 500 14px/1.3 var(--font-display);
  color: var(--text-1);
}

.input {
  width: 100%;
  min-height: 56px;
  padding: 0 18px;
  border-radius: 16px;
  border: 1px solid var(--line-2);
  background: var(--field-bg);
  color: var(--text-0);
  font: 500 18px/1.2 var(--font-display);
  outline: none;
  transition:
    border-color 0.2s,
    box-shadow 0.25s,
    background 0.2s;
}

.input::placeholder {
  color: var(--text-3);
}

.input:focus {
  border-color: var(--violet-400);
  background: var(--field-focus-bg);
  box-shadow:
    0 0 0 4px rgba(139, 92, 246, 0.18),
    0 0 24px -4px rgba(139, 92, 246, 0.55);
}

.input.ltr {
  text-align: left;
}

.input-wrap {
  position: relative;
}

.input-wrap .ico {
  position: absolute;
  inset-inline-start: 16px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-3);
  pointer-events: none;
}

.input-wrap .input {
  padding-inline-start: 50px;
}

.field-error {
  color: var(--bad-ink);
  font-size: 14px;
  min-height: 1.2em;
}

/* Six single-digit boxes (device code). */
.code-boxes {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 10px;
  direction: ltr;
}

.code-boxes .box {
  height: 64px;
  display: grid;
  place-items: center;
  border-radius: 16px;
  border: 1px solid var(--line-2);
  background: var(--field-bg);
  font: 600 30px/1 var(--font-display);
  transition: all 0.25s var(--ease-out);
}

.code-boxes .box.filled {
  border-color: rgba(167, 139, 250, 0.7);
  box-shadow: 0 0 22px -6px rgba(139, 92, 246, 0.8);
  background: rgba(40, 26, 100, 0.5);
}

.code-boxes .box.caret {
  border-color: var(--violet-300);
  box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.25);
}

/* ── keypad & PIN */

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  direction: ltr;
  width: 100%;
  max-width: 340px;
  margin: 0 auto;
}

.key {
  position: relative;
  height: 72px;
  border-radius: 24px;
  display: grid;
  place-items: center;
  font: 500 30px/1 var(--font-display);
  color: var(--text-0);
  background: linear-gradient(180deg, rgba(var(--fg-rgb), 0.075), rgba(var(--fg-rgb), 0.025));
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(var(--fg-rgb), 0.07);
  transition:
    transform 0.15s var(--ease-spring),
    background 0.2s,
    box-shadow 0.2s,
    border-color 0.2s;
  touch-action: manipulation;
}

.key:active,
.key.pressed {
  transform: scale(0.93);
  background: rgba(139, 92, 246, 0.26);
  border-color: rgba(196, 181, 253, 0.55);
  box-shadow:
    0 0 0 1px rgba(167, 139, 250, 0.35),
    0 0 30px -2px rgba(139, 92, 246, 0.6);
}

.key.fn {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  color: var(--text-2);
}

.key.fn:active {
  background: rgba(var(--fg-rgb), 0.06);
}

.pin-dots {
  display: flex;
  justify-content: center;
  gap: 18px;
  direction: ltr;
  min-height: 24px;
}

.pin-dots span {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid rgba(196, 181, 253, 0.45);
  transition:
    transform 0.25s var(--ease-spring),
    background 0.2s,
    box-shadow 0.25s,
    border-color 0.2s;
}

.pin-dots span.on {
  background: var(--violet-300);
  border-color: transparent;
  transform: scale(1.18);
  box-shadow: 0 0 16px rgba(167, 139, 250, 0.95);
}

.pin-dots.error {
  animation: shake 0.5s;
}

.pin-dots.error span {
  border-color: var(--bad);
}

.pin-dots.success span {
  background: var(--ok);
  border-color: transparent;
  box-shadow: 0 0 16px rgba(52, 211, 153, 0.9);
}

/* ── avatars */

.avatar {
  --h: 265;
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  font: 600 22px/1 var(--font-display);
  color: #fff;
  background:
    radial-gradient(circle at 30% 25%, hsl(var(--h) 90% 72% / 0.95), hsl(var(--h) 75% 45% / 0.95) 70%);
  box-shadow:
    0 0 0 3px var(--ring-bg),
    0 0 0 5px hsl(var(--h) 90% 70% / 0.55),
    0 10px 28px -8px hsl(var(--h) 90% 55% / 0.8);
}

.avatar.sm {
  width: 40px;
  height: 40px;
  font-size: 15px;
  box-shadow:
    0 0 0 2px var(--ring-bg),
    0 0 0 3.5px hsl(var(--h) 90% 70% / 0.5);
}

/* ── pills, chips, badges */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 30px;
  padding: 0 12px;
  border-radius: 999px;
  font: 500 13px/1 var(--font-display);
  color: var(--text-1);
  background: rgba(var(--fg-rgb), 0.06);
  border: 1px solid var(--line);
  white-space: nowrap;
}

.pill .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}

.pill.live {
  color: var(--cyan-300);
  border-color: rgba(34, 211, 238, 0.35);
  background: rgba(34, 211, 238, 0.08);
}

.pill.live .dot {
  animation: pulse-dot 2s ease-in-out infinite;
}

.pill.ok {
  color: var(--ok-ink);
  border-color: rgba(52, 211, 153, 0.35);
  background: var(--ok-soft);
}

.pill.warn {
  color: var(--warn-ink);
  border-color: rgba(251, 191, 36, 0.35);
  background: var(--warn-soft);
}

.pill.bad {
  color: var(--bad-ink);
  border-color: rgba(251, 113, 133, 0.35);
  background: var(--bad-soft);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 14px;
  background: rgba(var(--fg-rgb), 0.05);
  border: 1px solid var(--line);
  font: 500 15px/1.2 var(--font-display);
  color: var(--text-1);
  transition: all 0.2s var(--ease-out);
}

.chip.active {
  color: #fff;
  border-color: rgba(196, 181, 253, 0.6);
  background: rgba(139, 92, 246, 0.24);
  box-shadow: 0 0 22px -6px rgba(139, 92, 246, 0.7);
}

/* ── top bar */

.topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
}

.topbar .brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.topbar .brand .mark {
  width: 36px;
  height: 36px;
}

.topbar .brand-name {
  font: 600 17px/1.2 var(--font-display);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar .brand-sub {
  font-size: 12.5px;
  color: var(--text-2);
}

.topbar .spacer {
  flex: 1;
}

/* ── the animated brand mark (SVG built by components/mark.js) */

.mark {
  overflow: visible;
}

.mark .m-orbit {
  fill: none;
  stroke-width: 3;
  opacity: 0.35;
  transform-origin: center;
  animation: spin 60s linear infinite;
  stroke-dasharray: 6 14;
}

.mark .m-spokes line {
  stroke-width: 7;
  stroke-linecap: round;
  opacity: 0.55;
}

.mark .m-ring line {
  stroke-width: 5;
  stroke-linecap: round;
  opacity: 0.35;
}

.mark .m-flow line {
  stroke-width: 7;
  stroke-linecap: round;
  stroke-dasharray: 16 160;
  stroke-dashoffset: 176;
  opacity: 0.95;
  animation: flow 2.8s linear infinite;
  animation-delay: calc(var(--i, 0) * -0.47s);
}

@keyframes flow {
  to {
    stroke-dashoffset: 0;
  }
}

.mark .m-nodes circle {
  transform-box: fill-box;
  transform-origin: center;
  animation: node-pulse 3.2s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * 0.45s);
}

@keyframes node-pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.9;
  }
  50% {
    transform: scale(1.22);
    opacity: 1;
  }
}

.mark .m-halo {
  transform-box: fill-box;
  transform-origin: center;
  animation: breathe 4s ease-in-out infinite;
}

@keyframes breathe {
  0%,
  100% {
    transform: scale(0.92);
    opacity: 0.45;
  }
  50% {
    transform: scale(1.12);
    opacity: 0.75;
  }
}

.mark .m-rot {
  transform-origin: 0 0;
  animation: spin 80s linear infinite;
}

.mark.still .m-rot,
.mark.still .m-flow line,
.mark.still .m-orbit {
  animation: none;
}

/* ── lists */

.list {
  display: flex;
  flex-direction: column;
}

.list-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}

.list-item:last-child {
  border-bottom: 0;
}

.list-item .grow {
  flex: 1;
  min-width: 0;
}

.list-item .title {
  font: 500 16px/1.35 var(--font-display);
}

.list-item .sub {
  font-size: 13.5px;
  color: var(--text-2);
}

/* ── toast */

.toasts {
  position: fixed;
  z-index: 50;
  top: calc(12px + var(--safe-top));
  inset-inline: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  pointer-events: none;
  padding: 0 var(--gutter);
}

.toast {
  /* Informational only: a toast never swallows a tap on what is under it. */
  pointer-events: none;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: 520px;
  width: 100%;
  padding: 14px 16px;
  border-radius: 18px;
  font: 500 15px/1.4 var(--font-display);
  animation: toast-in 0.45s var(--ease-spring) both;
}

.toast.out {
  animation: toast-out 0.3s var(--ease-out) both;
}

.toast.ok .ico {
  color: var(--ok);
}

.toast.bad .ico {
  color: var(--bad);
}

.toast.info .ico {
  color: var(--cyan-300);
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translate3d(0, -24px, 0) scale(0.96);
  }
}

@keyframes toast-out {
  to {
    opacity: 0;
    transform: translate3d(0, -16px, 0) scale(0.97);
  }
}

/* ── bottom sheet */

.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--scrim);
  animation: fade-in 0.25s both;
}

.sheet {
  position: fixed;
  z-index: 41;
  inset-inline: 0;
  bottom: 0;
  max-height: 92dvh;
  overflow-y: auto;
  margin: 0 auto;
  max-width: 560px;
  padding: 10px var(--gutter) calc(22px + var(--safe-bottom));
  border-radius: 28px 28px 0 0;
  animation: sheet-in 0.5s var(--ease-out) both;
}

.sheet .grabber {
  width: 44px;
  height: 5px;
  border-radius: 3px;
  margin: 2px auto 14px;
  background: rgba(var(--fg-rgb), 0.2);
}

/* The dialog's close button: the computer's frame only (css/desktop.css, ADR 0044); a sheet closes by its backdrop. */
.sheet-close {
  display: none;
}

/* The sheet takes the focus itself when it opens as a dialog: no ring around the whole panel. */
.sheet:focus {
  outline: none;
}

@keyframes sheet-in {
  from {
    transform: translate3d(0, 100%, 0);
  }
}

.sheet.out {
  animation: sheet-out 0.3s var(--ease-in-out) both;
}

@keyframes sheet-out {
  to {
    transform: translate3d(0, 100%, 0);
  }
}

/* ── empty & error states */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 36px 20px;
  color: var(--text-2);
}

.empty .ico {
  color: var(--violet-300);
  opacity: 0.8;
}

/* ── connection banner: a slim strip at the top that never covers an action */

.netbar {
  position: fixed;
  z-index: 30;
  top: 0;
  inset-inline: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: calc(30px + var(--safe-top));
  padding-top: var(--safe-top);
  font: 500 13px/1 var(--font-display);
  color: var(--warn-ink-2);
  background: linear-gradient(90deg, rgba(120, 83, 10, 0.55), rgba(146, 99, 12, 0.75), rgba(120, 83, 10, 0.55));
  border-bottom: 1px solid rgba(251, 191, 36, 0.4);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  pointer-events: none;
  animation: netbar-in 0.35s var(--ease-out) both;
}

@keyframes netbar-in {
  from {
    transform: translate3d(0, -100%, 0);
  }
}

body.is-offline .screen {
  padding-top: calc(48px + var(--safe-top));
}

/* ── bursts (motion.js burst()) */

.burst-layer {
  position: fixed;
  z-index: 60;
  left: var(--x);
  top: var(--y);
  width: 0;
  height: 0;
  pointer-events: none;
}

.burst-layer i {
  position: absolute;
  width: var(--s);
  height: var(--s);
  border-radius: 50%;
  background: var(--c);
  box-shadow: 0 0 12px var(--c);
  animation: burst 1s var(--ease-out) var(--d) both;
}

@keyframes burst {
  0% {
    transform: translate3d(-50%, -50%, 0) scale(0.4);
    opacity: 1;
  }
  70% {
    opacity: 1;
  }
  100% {
    transform: translate3d(calc(var(--dx) - 50%), calc(var(--dy) - 50%), 0) scale(0.2);
    opacity: 0;
  }
}

/* ── small layout helpers */

.row-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.sheet-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-bottom: 4px;
}

.sheet-title {
  font-size: 20px;
}

.center-text {
  text-align: center;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.tile-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  color: var(--violet-200);
  background: rgba(139, 92, 246, 0.16);
  border: 1px solid rgba(167, 139, 250, 0.25);
}

.pw-toggle {
  position: absolute;
  inset-inline-end: 6px;
  top: 50%;
  transform: translateY(-50%);
}

.pw-toggle:active {
  transform: translateY(-50%) scale(0.94);
}

.shake-x {
  animation: shake 0.5s;
}

/* The loader waits in the staff grid across its columns (ADR 0034 amendment). */
.staff-grid > .loader {
  grid-column: 1 / -1;
}

.owner-link {
  align-self: center;
  margin-top: 6px;
}

.boot {
  display: grid;
  place-items: center;
  min-height: 100dvh;
}

.boot-orb {
  width: 74px;
  height: 74px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff 0%, var(--violet-ink-3) 20%, #8b5cf6 55%, rgba(59, 130, 246, 0.2) 75%, transparent 76%);
  box-shadow:
    0 0 60px rgba(139, 92, 246, 0.8),
    0 0 120px rgba(59, 130, 246, 0.45);
  animation: breathe 1.8s ease-in-out infinite;
}

.noscript {
  position: relative;
  z-index: 2;
  padding: 24px;
  text-align: center;
}

/* A closing sheet must not swallow the next tap. */
.sheet.out,
.sheet-backdrop.out {
  pointer-events: none;
}

.sheet-backdrop.out {
  animation: fade-out 0.25s both;
}

/* ── Receipt (counter sheet, owner views): a glass slip with the e-invoice QR ── */
.receipt-card {
  position: relative;
  display: grid;
  gap: 10px;
  padding: 18px 16px 16px;
  border: 1px solid var(--line-2);
  border-radius: 18px;
  background: linear-gradient(180deg, rgba(var(--fg-rgb), 0.07), rgba(var(--fg-rgb), 0.02));
  box-shadow: var(--glow-violet);
  overflow: hidden;
}
.receipt-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--grad-brand);
}
.rc-head {
  display: grid;
  justify-items: center;
  gap: 2px;
  text-align: center;
}
.rc-head strong {
  font-size: 17px;
}
.rc-kind {
  margin-top: 6px;
  padding: 2px 12px;
  border-radius: 999px;
  font-size: 12px;
  color: var(--violet-200);
  background: var(--grad-brand-soft);
}
.rc-meta {
  display: flex;
  justify-content: space-between;
  padding-bottom: 8px;
  border-bottom: 1px dashed var(--line-2);
  font-size: 13px;
}
.rc-lines {
  display: grid;
  gap: 6px;
}
.rc-line {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 14px;
}
.rc-disc {
  color: var(--violet-300);
}
/* Returns (M12): under the total, each with its lines, then what the sale came to after them. */
.rc-returns {
  display: grid;
  gap: 8px;
  padding-top: 10px;
  border-top: 1px dashed var(--line-2);
}
.rc-ret {
  display: grid;
  gap: 4px;
}
.rc-ret-head {
  font-size: 13px;
  font-weight: 600;
  color: var(--bad);
}
.rc-ret-line {
  padding-inline-start: 10px;
  font-size: 13px;
  color: var(--text-2);
}
.rc-net {
  padding-top: 8px;
  border-top: 1px solid var(--line-2);
  font-weight: 600;
}
/* Packages (M18): a service the package paid for, and what is left of each package the sale sold or used. */
.rc-covered-tag {
  font-size: 12px;
  font-weight: 600;
  color: var(--violet-300);
}
.rc-pkgs {
  display: grid;
  gap: 4px;
  padding: 8px 12px;
  border-radius: 12px;
  background: rgba(139, 92, 246, 0.12);
  box-shadow: inset 0 0 0 1px rgba(139, 92, 246, 0.3);
}
.rc-pkg {
  font-size: 13px;
  font-weight: 600;
}
.rc-pkg-state {
  color: var(--violet-300);
  text-align: end;
}
.rc-total {
  display: flex;
  justify-content: space-between;
  padding-top: 10px;
  border-top: 1px solid var(--line-2);
  font-size: 18px;
  font-weight: 600;
}
/* What was handed over at cash and the change (ADR 0030). */
.rc-tender {
  display: grid;
  gap: 2px;
}
.rc-change {
  font-weight: 600;
  color: var(--ok-ink);
}
.rc-einv {
  display: grid;
  justify-items: center;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px dashed var(--line-2);
}
.rc-qr {
  width: 156px;
  padding: 8px;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(var(--fg-rgb), 0.4), 0 12px 40px -10px rgba(139, 92, 246, 0.85);
  animation: qr-in 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
.qr-svg {
  display: block;
  width: 100%;
  height: auto;
}
.rc-einv-no {
  display: flex;
  gap: 8px;
  font-size: 12px;
  color: var(--text-2);
}
.rc-einv.pending {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-2);
}
.receipt-card.voided .rc-lines,
.receipt-card.voided .rc-total {
  opacity: 0.45;
  text-decoration: line-through;
}
.receipt-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 12px;
}
.receipt-note {
  margin: 8px 0 0;
  font-size: 13px;
  text-align: center;
}
.void-link {
  color: var(--bad);
}
.sheet-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.sheet-head-row .sheet-title {
  margin-bottom: 0;
}
@keyframes qr-in {
  from {
    opacity: 0;
    transform: scale(0.9);
    filter: blur(4px);
  }
  to {
    opacity: 1;
    transform: none;
    filter: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .rc-qr {
    animation: none;
  }
}

/* ── barcode scanner (components/scanner.js): the camera full screen, a neon viewfinder */

.scanner {
  position: fixed;
  inset: 0;
  /* Above sheets and toasts: while it is open the camera is the whole screen (it shows its own results). */
  z-index: 55;
  display: grid;
  grid-template-rows: auto 1fr auto;
  overflow: hidden;
  background: #05040e;
  color: var(--text-0);
  animation: fade-in 0.2s both;
}
.scanner.out {
  animation: fade-out 0.2s both;
}
.scan-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.4s ease;
}
.scanner.live .scan-video {
  opacity: 1;
}
.scan-shade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(70% 38% at 50% 46%, transparent 55%, rgba(5, 4, 14, 0.78) 100%),
    linear-gradient(180deg, rgba(5, 4, 14, 0.7), transparent 22%, transparent 70%, rgba(5, 4, 14, 0.85));
}
.scan-top,
.scan-stage,
.scan-bottom {
  position: relative;
  z-index: 1;
}
.scan-top {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: calc(12px + var(--safe-top)) 16px 8px;
}
.scan-title {
  flex: 1;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  text-align: center;
}
.scan-btn {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 1px solid var(--line-2);
  border-radius: 50%;
  background: rgba(17, 14, 44, 0.55);
  color: var(--text-0);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}
.scan-btn[hidden] {
  visibility: hidden;
  display: grid;
}
.scan-torch[aria-pressed='true'] {
  border-color: var(--warn);
  color: var(--warn);
  box-shadow: 0 0 22px -4px var(--warn);
}
.scan-stage {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 14px;
  padding: 0 16px;
}
.scan-frame {
  position: relative;
  width: min(84vw, 440px);
  aspect-ratio: 16 / 10;
  border-radius: 22px;
  box-shadow:
    0 0 0 1px rgba(167, 139, 250, 0.28),
    inset 0 0 60px -20px rgba(139, 92, 246, 0.55);
}
.scan-frame .c {
  position: absolute;
  width: 38px;
  height: 38px;
  border: 3px solid var(--violet-300);
  filter: drop-shadow(0 0 8px rgba(167, 139, 250, 0.9));
}
.scan-frame .tl {
  top: -2px;
  left: -2px;
  border-right: 0;
  border-bottom: 0;
  border-top-left-radius: 22px;
}
.scan-frame .tr {
  top: -2px;
  right: -2px;
  border-left: 0;
  border-bottom: 0;
  border-top-right-radius: 22px;
  border-color: var(--blue-300);
}
.scan-frame .bl {
  bottom: -2px;
  left: -2px;
  border-right: 0;
  border-top: 0;
  border-bottom-left-radius: 22px;
  border-color: var(--blue-300);
}
.scan-frame .br {
  bottom: -2px;
  right: -2px;
  border-left: 0;
  border-top: 0;
  border-bottom-right-radius: 22px;
}
.scan-laser {
  position: absolute;
  inset-inline: 7%;
  top: 14%;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--cyan-300), var(--violet-400), var(--magenta-400), transparent);
  box-shadow:
    0 0 12px 2px rgba(103, 232, 249, 0.55),
    0 0 28px 6px rgba(167, 139, 250, 0.35);
  animation: scan-sweep 2.1s ease-in-out infinite alternate;
}
@keyframes scan-sweep {
  to {
    top: 84%;
  }
}
.scan-frame.hit {
  animation: scan-hit 0.55s ease-out;
}
.scan-frame.miss {
  animation: scan-miss 0.45s ease-out;
}
@keyframes scan-hit {
  30% {
    box-shadow:
      0 0 0 3px var(--ok),
      0 0 60px 6px rgba(52, 211, 153, 0.55),
      inset 0 0 80px -10px rgba(52, 211, 153, 0.5);
  }
}
@keyframes scan-miss {
  20%,
  60% {
    transform: translateX(-6px);
    box-shadow:
      0 0 0 3px var(--bad),
      0 0 40px 2px rgba(251, 113, 133, 0.45);
  }
  40%,
  80% {
    transform: translateX(6px);
  }
}
.scanner.no-camera .scan-frame {
  border: 2px dashed var(--line-3);
  box-shadow: none;
}
.scanner.no-camera .scan-laser,
.scanner.no-camera .scan-frame .c {
  display: none;
}
.scan-hint {
  margin: 0;
  padding: 6px 14px;
  border-radius: 999px;
  color: var(--text-0);
  text-align: center;
  background: rgba(10, 8, 32, 0.66);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.scan-status {
  min-height: 1.4em;
  max-width: 420px;
  color: var(--text-2);
  font-size: 14px;
  text-align: center;
}
.scan-bottom {
  display: grid;
  gap: 12px;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  padding: 8px 16px calc(16px + var(--safe-bottom));
}
.scan-result {
  display: none;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  padding: 12px 16px;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: rgba(17, 14, 44, 0.72);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  font-weight: 600;
}
.scan-result.show {
  display: flex;
  animation: fade-in 0.2s both;
}
.scan-result.ok {
  border-color: rgba(52, 211, 153, 0.55);
  color: var(--ok);
}
.scan-result.bad {
  border-color: rgba(251, 113, 133, 0.55);
  color: var(--bad);
}
.scan-result span {
  color: var(--text-0);
}
.scan-manual {
  display: flex;
  gap: 8px;
}
.scan-manual[hidden] {
  display: none;
}
.scan-input {
  flex: 1;
  min-width: 0;
  height: 54px;
  padding: 0 14px;
  border: 1px solid var(--line-3);
  border-radius: var(--r-sm);
  background: rgba(10, 8, 32, 0.85);
  color: var(--text-0);
  font-size: 20px;
  letter-spacing: 0.08em;
  text-align: left;
}
.scan-input:focus {
  outline: 2px solid var(--violet-400);
  outline-offset: 1px;
}
.scan-add {
  min-width: 88px;
}
.scan-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.scan-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 48px;
  padding: 0 16px;
  white-space: nowrap;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: rgba(17, 14, 44, 0.6);
  color: var(--text-1);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}
.scan-done {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 52px;
}
.scan-count {
  display: none;
  min-width: 26px;
  height: 26px;
  padding: 0 7px;
  border-radius: 13px;
  background: rgba(var(--fg-rgb), 0.2);
  font-size: 14px;
  line-height: 26px;
  text-align: center;
}
.scanner.has-count .scan-count {
  display: inline-block;
}
.scanner.still .scan-laser {
  top: 50%;
  animation: none;
}
@media (prefers-reduced-motion: reduce) {
  .scan-laser {
    top: 50%;
    animation: none;
  }
  .scan-frame.hit,
  .scan-frame.miss {
    animation: none;
  }
  .scan-frame.hit {
    box-shadow: 0 0 0 3px var(--ok);
  }
  .scan-frame.miss {
    box-shadow: 0 0 0 3px var(--bad);
  }
}

/* M22: an optional photo on a form, and the viewer of a stored file */
.photo-pick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
}
.photo-pick .photo-pick-thumb,
.photo-pick .photo-pick-drop {
  display: none;
}
.photo-pick.has-photo .photo-pick-thumb {
  display: block;
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid rgba(var(--fg-rgb), 0.14);
}
/* ADR 0043: the photo a form came with, while it loads (or offline): an empty frame, never a broken image. */
.photo-pick.has-photo .photo-pick-thumb:not([src]) {
  background: rgba(var(--fg-rgb), 0.05);
}
.photo-pick.has-photo .photo-pick-drop {
  display: inline-flex;
}
.photo-pick .hint {
  flex-basis: 100%;
}
.file-viewer {
  display: grid;
  gap: 12px;
}
.file-viewer img {
  display: block;
  width: 100%;
  max-height: 70vh;
  object-fit: contain;
  border-radius: 12px;
  background: #000;
}

/* ── the owner's counter bar (home) and the language switch (sign-in and staff screens) */
.ask-bar.counter-bar {
  width: 100%;
  font: inherit;
  color: inherit;
  text-align: start;
  cursor: pointer;
}
.ask-bar.counter-bar .grow {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ask-bar.counter-bar .choice-title {
  font-weight: 600;
}
.ask-bar.counter-bar .choice-sub {
  font-size: 13px;
  color: var(--text-3);
}
.lang-switch {
  align-self: center;
  margin-top: 6px;
  color: var(--text-3);
}

/* ── the light theme's surfaces (2026-10-13): glass whiter than the page, not an ink tint */
html[data-theme='light'] .glass {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.84), rgba(255, 255, 255, 0.62));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    var(--shadow-card);
}

html[data-theme='light'] .btn-glass {
  background: rgba(255, 255, 255, 0.72);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
}

html[data-theme='light'] .btn-glass:hover {
  background: rgba(255, 255, 255, 0.92);
}

html[data-theme='light'] .pill {
  background: rgba(255, 255, 255, 0.7);
}

/* The active chip: dark violet on a pale violet tint in light (white on a translucent purple reads only on dark). */
html[data-theme='light'] .chip.active {
  color: var(--violet-200);
  border-color: rgba(124, 58, 237, 0.5);
  background: rgba(124, 58, 237, 0.14);
}

/* A sheet is a dialog over the page: in light it is near-opaque, or the screen behind bleeds through its fields (the
   oil-change sheet showed the cart's amounts inside the phone field, 2026-10-13). */
html[data-theme='light'] .sheet.glass {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(250, 248, 255, 0.985));
}

/* ── A searchable list (ADR 0033): the field, then the rows in the flow under it (a sheet's scrolling body never clips
   them); `.ss-float` lays them over the page for a filter bar. */
.ss {
  position: relative;
  display: grid;
  gap: 6px;
  min-width: 0;
}

.ss-field {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}

.ss-field .input {
  flex: 1;
  width: 100%;
  min-width: 0;
  padding-inline: 46px 46px;
  text-overflow: ellipsis;
}

.ss-ico {
  position: absolute;
  inset-inline-start: 16px;
  color: var(--text-3);
  pointer-events: none;
}

.ss-clear {
  position: absolute;
  inset-inline-end: 8px;
  display: none;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  color: var(--text-2);
  background: transparent;
}

.ss.has-value .ss-clear {
  display: grid;
}

.ss-clear:hover,
.ss-clear:focus-visible {
  color: var(--text-0);
  background: rgba(var(--fg-rgb), 0.06);
}

.ss-list {
  max-height: 272px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px;
  border-radius: 16px;
  border: 1px solid var(--line-2);
  background: var(--surface-raised);
  box-shadow: var(--shadow-card);
}

.ss-float .ss-list {
  position: absolute;
  top: calc(100% + 6px);
  inset-inline: 0;
  z-index: 30;
  min-width: min(320px, calc(100vw - 32px));
}

.ss-opt {
  min-height: 44px;
  padding: 12px;
  border-radius: 12px;
  font: 500 15px/1.35 var(--font-display);
  color: var(--text-1);
  cursor: pointer;
  overflow-wrap: anywhere;
  -webkit-tap-highlight-color: transparent;
}

.ss-opt.chosen {
  color: var(--violet-200);
  font-weight: 600;
}

.ss-opt.active {
  color: var(--text-0);
  background: rgba(139, 92, 246, 0.16);
}

@media (hover: hover) {
  .ss-opt:hover {
    background: rgba(var(--fg-rgb), 0.06);
  }
}

.ss-empty {
  padding: 12px;
  font-size: 14px;
  color: var(--text-3);
}

/* ── a short shift's question (ADR 0029): the two buttons, or the decision — on alert cards, shift rows, the books tab */
.shortage {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin-top: 8px;
  padding: 8px 10px;
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.4;
}

.shortage.open {
  color: var(--bad-ink-2);
  background: var(--bad-soft);
}

.shortage.decided {
  color: var(--text-1);
  background: rgba(var(--fg-rgb), 0.05);
}

.shortage.decided.advance > svg {
  color: var(--violet-ink);
}

.shortage.decided.keep > svg {
  color: var(--ok-ink);
}

.shortage .grow {
  flex: 1 1 160px;
  min-width: 0;
}

.shortage-text {
  display: block;
  font-weight: 500;
}

.shortage-sub {
  display: block;
  font-size: 12px;
  color: var(--text-2);
}

.shortage-sub.moved {
  color: var(--warn-ink);
}

.shortage-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* ── the station's pictures (ADR 0034, components/art.js)
   One 120 × 120 drawing per section, painted by class with a role ink that has a light and a dark value. The base
   style of every element is the final frame and every passing part rests at opacity 0, so a still is the drawing with
   nothing running; keyframes apply only under `.art.play` (a success moment, once; transform and opacity only, never a
   loop; every part done 100 ms before the overlay fades, so the last frame is seen whole — `tests/unit/moment.test.ts`
   times them). The close's moment is inline under its card's entrance: its delays add `--art-delay` (0 elsewhere). Rotations and scales name their origin in user units (`transform-box: view-box`, the SVG
   default) or use the shape's own box. Stroke widths are a custom property, thicker on the small stills. */

.art {
  --sw: 4px;
  display: block;
  flex: none;
  color: var(--violet-ink);
  overflow: hidden;
}
.art.play {
  overflow: visible;
}
.art-sm {
  --sw: 5.5px;
}
.art-wash,
.art-bay,
.art-car,
.art-pass {
  color: var(--cyan-ink);
}
.art-oil {
  color: var(--warn-ink);
}
.art-interior {
  color: var(--violet-ink-3);
}
.art-carpet {
  color: var(--ok-ink);
}
.art-maintenance {
  color: var(--orange-ink-2);
}
.art-goods {
  color: var(--pink-ink);
}
.art-package {
  color: var(--violet-ink);
}
.art-close {
  color: var(--blue-ink);
}

.art path,
.art circle,
.art rect {
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ar-solid {
  fill: var(--surface-card-a);
}
.ar-body {
  fill: currentColor;
  fill-opacity: 0.18;
  stroke: currentColor;
  stroke-width: var(--sw);
}
.ar-line {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--sw);
}
.ar-thin {
  fill: none;
  stroke: currentColor;
  stroke-width: calc(var(--sw) * 0.6);
}
.ar-ink,
.ar-hub,
.ar-oil,
.ai-dust,
.ap-dot {
  fill: currentColor;
}
.ar-glass {
  fill: currentColor;
  fill-opacity: 0.4;
  stroke: currentColor;
  stroke-width: calc(var(--sw) * 0.5);
}
.ar-tyre {
  fill: var(--surface-card-a);
  stroke: currentColor;
  stroke-width: var(--sw);
}
.ar-ground {
  fill: none;
  stroke: rgba(var(--fg-rgb), 0.22);
  stroke-width: calc(var(--sw) * 0.75);
}
.ar-arch {
  fill: none;
  stroke: currentColor;
  stroke-width: calc(var(--sw) * 1.3);
}
.ar-water {
  fill: none;
  stroke: var(--blue-ink);
  stroke-width: calc(var(--sw) * 0.7);
}
.ar-foam {
  fill: var(--art-foam);
  stroke: var(--cyan-ink);
  stroke-width: 1.2px;
}
.ar-ghost {
  fill: none;
  stroke: currentColor;
  stroke-opacity: 0.55;
  stroke-width: calc(var(--sw) * 0.6);
  stroke-dasharray: 4 4;
}
.ar-seam {
  fill: none;
  stroke: currentColor;
  stroke-opacity: 0.5;
  stroke-width: calc(var(--sw) * 0.5);
  stroke-dasharray: 3 3;
}
.ar-fringe {
  fill: none;
  stroke: currentColor;
  stroke-opacity: 0.75;
  stroke-width: calc(var(--sw) * 0.5);
}
.ar-flap {
  fill: currentColor;
  fill-opacity: 0.4;
  stroke: currentColor;
  stroke-width: calc(var(--sw) * 0.75);
}
.ar-tick {
  fill: none;
  stroke: var(--surface-card-a);
  stroke-width: 2px;
}
.ar-needle {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--sw);
}
.ar-hi {
  fill: none;
  stroke: var(--art-foam);
  stroke-opacity: 0.85;
  stroke-width: 2.2px;
}
.ar-air {
  fill: none;
  stroke: currentColor;
  stroke-opacity: 0.75;
  stroke-width: calc(var(--sw) * 0.6);
}
.ar-paper {
  fill: var(--art-paper);
  stroke: rgba(var(--fg-rgb), 0.3);
  stroke-width: 1px;
}
.ar-faint {
  fill: none;
  stroke: var(--art-paper-ink);
  stroke-opacity: 0.4;
  stroke-width: 1.6px;
}
.ar-zee {
  fill: none;
  stroke: var(--art-paper-ink);
  stroke-width: 3px;
}
.ar-slot {
  fill: none;
  stroke: rgba(var(--fg-rgb), 0.5);
  stroke-width: 3px;
}
.ar-key {
  fill: currentColor;
  fill-opacity: 0.55;
}
.ar-spark {
  fill: var(--art-spark);
}
.az-led {
  fill: var(--ok-ink);
}
.art.still .az-led {
  opacity: 0.4;
}
.ao-track {
  fill: none;
  stroke: rgba(var(--fg-rgb), 0.16);
  stroke-width: calc(var(--sw) * 1.6);
}
.ao-lit {
  fill: none;
  stroke: currentColor;
  stroke-width: calc(var(--sw) * 1.6);
}
.ao-needle {
  transform-origin: 60px 80px;
}
.ao-label {
  fill: currentColor;
  font: 700 13px/1 var(--font-display);
}
.ao-ripple {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5px;
}
.ap-count {
  fill: currentColor;
  font: 700 25px/1 var(--font-display);
}
.am-spark {
  fill: none;
  stroke: var(--art-spark);
  stroke-width: 3px;
}
.aw-glint,
.ap-glint {
  fill: var(--art-foam);
}

/* Origins (user units) and the passing parts, at rest. */
.ai-swirl {
  transform-origin: 46px 52px;
}
.ac-rug {
  transform-origin: 98px 66px;
}
.am-wrench,
.am-spark {
  transform-origin: 64px 52px;
}
.ag-h1,
.ag-h2,
.ag-flap {
  transform-origin: 60px 50px;
}
.ag-seal {
  transform-origin: 60px 61px;
}
.ap-card {
  transform-origin: 60px 60px;
}
.az-drawer {
  transform-origin: 60px 89px;
}
.ar-spark,
.ar-foam,
.ai-dust,
.ao-ripple,
.ap-count,
.ap-dot {
  transform-box: fill-box;
  transform-origin: center;
}
.aw-carriage,
.aw-glint,
.ap-glint,
.aw-foam .ar-foam,
.ao-ripple,
.ai-swirl,
.ai-dust,
.ac-roll,
.ag-items,
.am-spark,
.art.play .ao-drop {
  opacity: 0;
}
.art.play .ao-drop {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

/* ── the moments, once */

.art.play .ar-spark {
  animation: art-spark 0.2s var(--ease-spring) calc(var(--art-delay, 0ms) + var(--d, 0ms)) both;
}
@keyframes art-spark {
  from {
    opacity: 0;
    transform: scale(0) rotate(-45deg);
  }
}
@keyframes art-pop {
  from {
    opacity: 0;
    transform: scale(0.3);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes art-gone {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
    transform: scale(0.6);
  }
}
@keyframes art-show {
  from {
    opacity: 0;
  }
}
@keyframes art-glint {
  0% {
    opacity: 0;
    transform: translateX(112px);
  }
  20%,
  80% {
    opacity: 0.85;
  }
  100% {
    opacity: 0;
    transform: translateX(14px);
  }
}

/* wash: the carriage runs right to left under the beam, foam stays on the body, a glint, sparkles */
.art.play .aw-carriage {
  animation: art-carriage 0.42s var(--ease-in-out) 0.04s both;
}
@keyframes art-carriage {
  0% {
    opacity: 0;
    transform: translateX(104px);
  }
  12%,
  88% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateX(16px);
  }
}
.art.play .aw-foam .ar-foam {
  animation:
    art-pop 0.14s var(--ease-out) var(--d, 0ms) both,
    art-gone 0.12s ease-in 0.5s forwards;
}
.art.play .aw-glint {
  animation: art-glint 0.24s var(--ease-in-out) 0.36s both;
}

/* oil: the drop falls, a ripple, the needle swings to full, the dial lights, the word for full */
.art.play .ao-drop {
  animation: art-drop 0.36s cubic-bezier(0.5, 0, 0.9, 0.6) 0.02s both;
}
@keyframes art-drop {
  0% {
    opacity: 0;
    transform: translateY(-24px);
  }
  15% {
    opacity: 1;
  }
  70% {
    opacity: 1;
    transform: translateY(30px);
  }
  100% {
    opacity: 0;
    transform: translateY(32px) scale(1.3, 0.5);
  }
}
.art.play .ao-ripple {
  animation: art-ripple 0.3s var(--ease-out) 0.27s both;
}
@keyframes art-ripple {
  from {
    opacity: 0.9;
    transform: scale(0.3);
  }
  to {
    opacity: 0;
    transform: scale(1.8);
  }
}
.art.play .ao-needle {
  animation: art-needle 0.3s var(--ease-out) 0.27s both;
}
/* From empty (150° back from where it rests at full), past full, and settled — «ممتلئ» rises as it settles. */
@keyframes art-needle {
  0% {
    transform: rotate(-150deg);
  }
  70% {
    transform: rotate(10deg);
  }
  85% {
    transform: rotate(-4deg);
  }
  100% {
    transform: rotate(0deg);
  }
}
.art.play .ao-lit {
  animation: art-show 0.12s linear var(--d, 0ms) both;
}
.art.play .ao-label {
  animation: art-rise 0.16s var(--ease-out) 0.48s both;
}
@keyframes art-rise {
  from {
    opacity: 0;
    transform: translateY(5px);
  }
}

/* interior: the vacuum swirls and pulls the dust in */
.art.play .ai-swirl {
  animation: art-swirl 0.56s linear 0.04s both;
}
@keyframes art-swirl {
  0% {
    opacity: 0;
    transform: rotate(0deg) scale(0.7);
  }
  18%,
  78% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: rotate(-600deg) scale(0.35);
  }
}
.art.play .ai-dust {
  animation: art-dust 0.32s var(--ease-in-out) var(--d, 0ms) both;
}
@keyframes art-dust {
  0% {
    opacity: 0;
    transform: translate(var(--dx, 0px), var(--dy, 0px));
  }
  25% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translate(0px, 0px) scale(0.2);
  }
}

/* carpet: the rug unrolls right to left behind its roll */
.art.play .ac-rug {
  animation: art-unroll 0.44s var(--ease-out) 0.06s both;
}
@keyframes art-unroll {
  from {
    transform: scaleX(0.06);
  }
}
.art.play .ac-roll {
  animation: art-roll 0.44s var(--ease-out) 0.06s both;
}
@keyframes art-roll {
  0% {
    opacity: 1;
    transform: translateX(0px);
  }
  85% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateX(-76px);
  }
}

/* maintenance: the wrench turns the nut, a spark */
.art.play .am-wrench {
  animation: art-turn 0.5s var(--ease-out) 0.06s both;
}
@keyframes art-turn {
  0% {
    transform: rotate(-50deg);
  }
  62% {
    transform: rotate(10deg);
  }
  80% {
    transform: rotate(-4deg);
  }
  100% {
    transform: rotate(0deg);
  }
}
.art.play .am-spark {
  animation: art-flash 0.24s var(--ease-out) 0.38s both;
}
@keyframes art-flash {
  0% {
    opacity: 0;
    transform: scale(0.5);
  }
  40% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: scale(1.15);
  }
}

/* goods: the bottle and the box drop in, the handles meet, the top folds, the seal */
.art.play .ag-items {
  animation: art-items 0.34s cubic-bezier(0.5, 0, 0.8, 0.6) 0.02s both;
}
@keyframes art-items {
  0% {
    opacity: 0;
    transform: translateY(-30px);
  }
  18%,
  90% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateY(40px);
  }
}
.art.play .ag-h1 {
  animation: art-handle-a 0.22s var(--ease-out) 0.24s both;
}
.art.play .ag-h2 {
  animation: art-handle-b 0.22s var(--ease-out) 0.24s both;
}
@keyframes art-handle-a {
  from {
    transform: translateX(-8px) rotate(-16deg);
  }
}
@keyframes art-handle-b {
  from {
    transform: translateX(8px) rotate(16deg);
  }
}
.art.play .ag-flap {
  animation: art-fold 0.14s var(--ease-out) 0.36s both;
}
@keyframes art-fold {
  from {
    transform: scaleY(0);
  }
}
.art.play .ag-seal {
  animation: art-seal 0.2s var(--ease-spring) 0.42s both;
}
@keyframes art-seal {
  from {
    opacity: 0;
    transform: scale(0);
  }
}

/* package: the card rises and settles, the count pops, the punch dots one by one, a glint */
.art.play .ap-card {
  animation: art-card 0.4s var(--ease-spring) 0.04s both;
}
@keyframes art-card {
  from {
    opacity: 0;
    transform: translateY(28px) rotate(-12deg) scale(0.86);
  }
}
.art.play .ap-count {
  animation: art-seal 0.24s var(--ease-spring) 0.32s both;
}
.art.play .ap-dot {
  animation: art-seal 0.16s var(--ease-out) var(--d, 0ms) both;
}
.art.play .ap-glint {
  animation: art-glint 0.24s var(--ease-in-out) 0.38s both;
}

/* the close: the drawer shuts, its light comes on, the Z slip comes out of the slot — after its card has come in
   (`--art-delay`, set under `.close-moment` in counter.css) */
.art.play .az-drawer {
  animation: art-shut 0.34s var(--ease-out) calc(var(--art-delay, 0ms) + 0.04s) both;
}
@keyframes art-shut {
  0% {
    transform: translateY(9px) scale(1.12);
  }
  65% {
    transform: translateY(-1px) scale(0.99);
  }
  100% {
    transform: none;
  }
}
.art.play .az-led {
  animation: art-show 0.08s linear calc(var(--art-delay, 0ms) + 0.36s) both;
}
.art.play .az-slip {
  animation: art-slip 0.34s var(--ease-out) calc(var(--art-delay, 0ms) + 0.38s) both;
}
@keyframes art-slip {
  from {
    transform: translateY(36px);
  }
}

/* ── the loader (ADR 0034, amendment of 2026-10-18; components/loader.js)
   Wherever the app waits for data with nothing to show yet, a car passes under the wash arch (`art('pass')`). The
   loader comes in only once the wait passes `--loader-after` (300 ms), so a quick answer never flashes it, and nothing
   waits for it: the content replaces it the moment it arrives. Under `.art.loop` (decided in code: never with reduced
   motion or the lite mode, which get the still) the car drives out to the left, the next comes in from the right and
   stands under the spray — one cycle starts and ends on the rest frame, the car under the arch, which is the still; the
   drops and the shine rest at opacity 0. Transform and opacity only; this is the one drawing that loops, and only while
   the app waits (`tests/unit/loader.test.ts`). */

.loader {
  --loader-after: 300ms;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 6px;
  min-height: 180px;
  padding: 20px 12px;
  color: var(--text-2);
  text-align: center;
  animation: loader-in 0.24s var(--ease-out) var(--loader-after) both;
}
@keyframes loader-in {
  from {
    opacity: 0;
  }
}
.loader-text {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
}
/* In a sheet, a list or a card: as much room as a line of content. */
.loader.compact {
  min-height: 0;
  padding: 14px 12px;
}
/* Beside its words (the photo being read): the picture at the start of the line. */
.loader.row {
  grid-auto-flow: column;
  justify-content: start;
  align-items: center;
  gap: 10px;
  min-height: 64px;
  padding: 0 4px;
  color: var(--text-1);
  text-align: start;
}
/* Shown by a timer that already waited (the router's), so it comes in at once. */
.loader.now {
  --loader-after: 0ms;
}
/* A first load is not a refetch: a screen's refetch dims the numbers it keeps, never the loader that stands for none. */
:is(.report-body, .books-content, .payroll, .acc-statement-box).refetching:has(> .loader) {
  opacity: 1;
}

/* A screen whose code is still coming: over the screen it leaves, never in the way of a tap. */
.route-wait {
  position: fixed;
  z-index: 45;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.route-wait .loader {
  min-height: 0;
  padding: 12px 22px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--surface-card-a), var(--surface-card-b));
  box-shadow: 0 18px 40px -18px rgba(76, 29, 149, 0.45);
}

.al-drop {
  fill: none;
  stroke: var(--blue-ink);
  stroke-width: calc(var(--sw) * 0.7);
  opacity: 0;
}
.al-shine {
  fill: var(--art-spark);
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.art.loop .al-car {
  animation: art-pass 2.4s linear var(--loader-after, 0ms) infinite both;
}
@keyframes art-pass {
  0%,
  12% {
    opacity: 1;
    transform: none;
    animation-timing-function: cubic-bezier(0.55, 0, 0.9, 0.55);
  }
  30% {
    opacity: 1;
  }
  38% {
    opacity: 0;
    transform: translateX(-104px);
  }
  38.01% {
    opacity: 0;
    transform: translateX(104px);
    animation-timing-function: cubic-bezier(0.1, 0.55, 0.4, 1);
  }
  50% {
    opacity: 1;
  }
  72%,
  100% {
    opacity: 1;
    transform: none;
  }
}
.art.loop .al-drop {
  animation: art-drip 0.7s linear calc(var(--loader-after, 0ms) + var(--d, 0ms)) infinite both;
}
@keyframes art-drip {
  0% {
    opacity: 0;
    transform: none;
  }
  20% {
    opacity: 0.9;
  }
  100% {
    opacity: 0;
    transform: translateY(12px);
  }
}
.art.loop .al-shine {
  animation: art-shine 2.4s linear calc(var(--loader-after, 0ms) + var(--d, 0ms)) infinite both;
}
@keyframes art-shine {
  0%,
  84% {
    opacity: 0;
    transform: scale(0);
  }
  92% {
    opacity: 1;
    transform: none;
  }
  100% {
    opacity: 0;
    transform: scale(0.5);
  }
}

/* The still, whatever happens to the motion setting while the loader shows (the code already chose the still when it
   was off at the start). */
@media (prefers-reduced-motion: reduce) {
  .art.loop * {
    animation: none !important;
  }
}
html.lite .art.loop * {
  animation: none !important;
}

/* ── end of the loader */

/* ADR 0040: a split sale's parts under the total. */
.rc-parts {
  display: grid;
  gap: 2px;
}
.rc-part {
  padding-inline-start: 10px;
  font-size: 14px;
  color: var(--text-1);
}

/* ADR 0050: the scanner's photo mode — a square frame without the laser, and the shutter. */
.scanner.photo-mode .scan-frame {
  aspect-ratio: 1;
  width: min(72vw, 360px);
}
.scanner.photo-mode .scan-laser {
  display: none;
}
.scan-shutter {
  justify-self: center;
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  border: 4px solid rgba(var(--fg-rgb), 0.85);
  border-radius: 50%;
  background: linear-gradient(140deg, var(--violet-500), var(--blue-500));
  color: var(--text-0);
  box-shadow:
    0 0 0 6px rgba(139, 92, 246, 0.25),
    0 0 36px -4px rgba(139, 92, 246, 0.85);
}
.scan-shutter[hidden] {
  display: none;
}
.scan-shutter:active {
  transform: scale(0.94);
}
.scan-chip[hidden] {
  display: none;
}
/* Three controls do not fit a phone's row: with the photo chip there, «انتهى» takes the row under the two chips. */
.scanner:not(.no-camera) .scan-actions:has(> .scan-photo:not([hidden])) .scan-done {
  flex: 1 0 100%;
  justify-content: center;
}
/* No camera (none, or not allowed): no still can come, so neither the shutter nor the chip shows. */
.scanner.no-camera .scan-shutter,
.scanner.no-camera .scan-photo {
  display: none;
}
