/* EagleStrike theme — the prototype's dark palette, ported, with a light one beside it.
   Dark by default: this app is used in a hall under bad lighting, held at arm's length,
   often outdoors. High contrast and big tap targets beat looking clever. The light theme
   (the blocks after :root) is for daylight and for people who prefer it; it follows the
   device until somebody picks one with the sun/moon button. */

:root {
  --es-bg: #0b0d12;
  --es-surface: #14171f;
  --es-surface-2: #191d27;
  --es-line: rgba(255, 255, 255, 0.07);
  --es-fg: #f2f3f5;
  --es-mute: #8b91a0;
  --es-blue: #d4a94f;
  --es-green: #3ddc6b;
  --es-amber: #ffb547;
  --es-red: #ff6b6b;
  --es-radius: 14px;

  /* Notch and home-indicator insets. Without these the tab bar sits under the iPhone
     home indicator and the top row hides behind the notch. */
  --es-safe-top: env(safe-area-inset-top, 0px);
  --es-safe-bottom: env(safe-area-inset-bottom, 0px);

  /* The content column. One number, read by .es-page, .es-deskbar and the top bar's
     gutter, so the heading, the rows under it and the basket summary all line up on
     the same two edges however wide the window is. */
  --es-page-max: 900px;

  /* The gap between the content column and the edge of the region holding it. Grows
     with the window on desktop; on a phone the column is the window. */
  --es-pad: 16px;

  /* Width of the desktop navigation rail. 0 here because there is no rail at phone
     width — the tabs are a bar along the bottom. The desktop block near the end of
     this file is the only place that gives it a real value. */
  --es-rail: 0px;

  /* ---- theme-dependent pieces ----

     The values above and below are the dark theme; the light one is the pair of blocks after
     this. --es-blue is the gold used for text and marks, and is deepened in light so it
     reads on off-white; --es-blue-fill is a solid gold with dark type on it and stays bright
     in both. */
  color-scheme: dark;

  --es-blue-fill: #d4a94f;

  /* Channel triplets for rgba() washes: over = what a quiet surface or hover is tinted
     towards, glass = frosted bars over the page. */
  --es-over-rgb: 255, 255, 255;
  --es-glass-rgb: 13, 16, 24;

  --es-danger-ink: #e26060;
  --es-danger-soft: #ff8f8f;
  --es-warn-ink: #e8a547;
  --es-sky-ink: #e2c27f;

  /* The break-even hero: a deep green or red card with pale type in dark, a pale wash with
     deep type in light. */
  --es-hero-good-a: #0f2a18;
  --es-hero-good-b: #14532d;
  --es-hero-good-label: rgba(52, 199, 89, 0.9);
  --es-hero-good-value: #7ee89a;
  --es-hero-bad-a: #3d1a24;
  --es-hero-bad-b: #5c2230;
  --es-hero-bad-label: rgba(255, 139, 139, 0.9);
  --es-hero-bad-value: #ff9b9b;
  --es-hero-note: #c2c8d4;
}

/* The light theme. Two blocks with the same values: a light choice made in the app, then a
   device in light mode where nobody has chosen. Picking Dark sets data-theme="dark", which
   the :not() steps aside for. Keep the two identical, and in step with the pair in
   eagle-auctions.css. */
:root[data-theme="light"] {
  color-scheme: light;

  --es-bg: #e4dfd4;
  --es-surface: #f3f0e9;
  --es-surface-2: #ebe7de;
  --es-line: rgba(20, 22, 30, 0.15);
  --es-fg: #121419;
  --es-mute: #4f5561;
  --es-blue: #7a570d;
  --es-green: #146c33;
  --es-amber: #8a5105;
  --es-red: #b0222a;

  --es-over-rgb: 20, 22, 30;
  --es-glass-rgb: 228, 223, 212;

  --es-danger-ink: #a51f26;
  --es-danger-soft: #a51f26;
  --es-warn-ink: #8a5105;
  --es-sky-ink: #7a570d;

  --es-hero-good-a: #e6f3e9;
  --es-hero-good-b: #d3ecdb;
  --es-hero-good-label: #146c33;
  --es-hero-good-value: #115e2c;
  --es-hero-bad-a: #f6e6e6;
  --es-hero-bad-b: #efd6d8;
  --es-hero-bad-label: #a51f26;
  --es-hero-bad-value: #a51f26;
  --es-hero-note: #4a505c;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;

    --es-bg: #e4dfd4;
    --es-surface: #f3f0e9;
    --es-surface-2: #ebe7de;
    --es-line: rgba(20, 22, 30, 0.15);
    --es-fg: #121419;
    --es-mute: #4f5561;
    --es-blue: #7a570d;
    --es-green: #146c33;
    --es-amber: #8a5105;
    --es-red: #b0222a;

    --es-over-rgb: 20, 22, 30;
    --es-glass-rgb: 228, 223, 212;

    --es-danger-ink: #a51f26;
    --es-danger-soft: #a51f26;
    --es-warn-ink: #8a5105;
    --es-sky-ink: #7a570d;

    --es-hero-good-a: #e6f3e9;
    --es-hero-good-b: #d3ecdb;
    --es-hero-good-label: #146c33;
    --es-hero-good-value: #115e2c;
    --es-hero-bad-a: #f6e6e6;
    --es-hero-bad-b: #efd6d8;
    --es-hero-bad-label: #a51f26;
    --es-hero-bad-value: #a51f26;
    --es-hero-note: #4a505c;
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--es-bg);
  color: var(--es-fg);
  font: 400 15px/1.45 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}

.es-shell {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

.es-shell--bare { justify-content: center; }

/* ---------- top bar ---------- */

.es-topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: calc(10px + var(--es-safe-top)) 16px 10px;
  background: rgba(var(--es-glass-rgb), 0.85);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--es-line);
}

/* A <button> so it is reachable by keyboard and announced as an action, stripped back
   to look exactly like the block of text it replaced. */
.es-topbar__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* Without min-width:0 this flex child refuses to shrink and the ellipsis on
   .es-topbar__house never triggers — a long house name pushes the sync dot off. */
.es-topbar__names { min-width: 0; }

.es-topbar__right { display: flex; align-items: center; gap: 8px; }

/* The round icon button in the top bar — settings today, and the shape to reuse for
   anything else that earns a place up there. 40px rather than the icon's own 20px, because
   the thing being tapped is the button and not the drawing inside it. */
.es-iconbtn {
  display: grid;
  place-items: center;
  flex: none;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(var(--es-over-rgb), 0.06);
  color: var(--es-mute);
  text-decoration: none;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}

.es-iconbtn--on { background: rgba(212, 169, 79, 0.18); color: var(--es-blue); }

.es-iconbtn:active { background: rgba(var(--es-over-rgb), 0.14); }

@media (prefers-reduced-motion: reduce) {
  .es-iconbtn { transition: none; }
}

.es-topbar__house {
  font-weight: 700;
  font-size: 15px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.es-topbar__auction { font-size: 12px; color: var(--es-mute); }

.es-logo {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: linear-gradient(180deg, #d4a94f, #b98d33);
  color: #1a1408;
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.5px;
}

.es-logo--lg { width: 56px; height: 56px; border-radius: 16px; font-size: 20px; }

/* ---------- main ---------- */

.es-main { flex: 1; min-height: 0; overflow-y: auto; }

.es-page {
  padding: var(--es-pad);
  /* Room for the fixed tab bar so the last row is never trapped underneath it. The extra
     over the bar's own height clears the rostrum button, which stands proud of it. */
  padding-bottom: calc(112px + var(--es-safe-bottom));
  max-width: var(--es-page-max);
  margin: 0 auto;
  width: 100%;
}

/* Screens with no bottom bar - the subscriber console - should not reserve room for one.
   Equal specificity to .es-page, so it has to come after it to win; declared before, the
   112px it is meant to cancel would simply override it. */
.es-page--nobar { padding-bottom: calc(16px + var(--es-safe-bottom)); }

.es-page__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.es-h1 { font-size: 22px; font-weight: 800; margin: 4px 0 12px; }
.es-h2 { font-size: 16px; font-weight: 700; margin: 22px 0 10px; }
.es-muted { color: var(--es-mute); }
.es-small { font-size: 12px; }

/* ---------- tab bar ---------- */

.es-tabs {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  display: flex;
  /* Bottom-aligned so the raised rostrum button can hang above the bar without dragging the
     other tabs up with it. */
  align-items: flex-end;
  justify-content: space-around;
  gap: 4px;
  padding: 8px 8px calc(8px + var(--es-safe-bottom));
  background: rgba(var(--es-glass-rgb), 0.95);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--es-line);
}

/* The two halves either side of the rostrum button.

   Each half takes exactly half of whatever is left once the notch is subtracted, which is
   what keeps the button on the true centre line regardless of how many tabs sit in each
   half — an owner has five other tabs and a staff member three, and neither number splits
   evenly. Spreading every tab equally across the whole bar instead would pull the button
   off centre by however many tabs the heavier side happens to have. */
.es-tabs__side {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-around;
  gap: 4px;
}

/* Reserves the middle, so no tab ever ends up half underneath the button. */
.es-tabs__notch {
  flex: 0 0 88px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.es-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  flex: 1;
  padding: 8px 4px;
  border-radius: 12px;
  color: var(--es-mute);
  text-decoration: none;
  /* 44px is the smallest thing a thumb reliably hits. */
  min-height: 44px;
}

.es-tab--on { color: var(--es-blue); background: rgba(212, 169, 79, 0.16); }
.es-tab__label { font-size: 11px; font-weight: 700; }

/* ---------- the rostrum button ----------

   Selling is what this app is for, so the control that does it does not look like the
   others: bigger, dead centre, and lifted far enough to break the top edge of the bar. It
   is also the easiest thing on the screen to hit with a thumb while the other hand is
   holding a card machine, which is the situation it actually gets used in.

   The ring around it is painted in the page background rather than in a border colour.
   That reads as the bar being cut away behind the button instead of as an outline drawn on
   top of it, which is what makes it look like it is sitting above the bar rather than in
   it. If the page background ever changes, this ring has to change with it. */

.es-tab--raised {
  flex: none;
  width: 88px;
  /* Matches the 8px the ordinary tabs carry below their labels. Without it this label sits
     8px lower than every other one and the whole row looks misaligned. */
  padding: 0 0 8px;
  gap: 5px;
  background: none;
  /* Negative margin rather than absolute positioning: it lifts the button clear of the bar
     and, because a negative cross-axis margin also shrinks what this item contributes to
     the row, an 87px-tall button does not turn into an 87px-tall tab bar. The number is
     chosen so what is left — 57px — is exactly the height of an ordinary tab. */
  margin-top: -30px;
}

.es-tab__orb {
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  color: #1a1408;
  background: linear-gradient(180deg, #d4a94f, #b98d33);
  box-shadow:
    0 0 0 6px var(--es-bg),
    0 10px 22px rgba(0, 0, 0, 0.5);
  transition: transform 120ms ease;
}

.es-tab--raised .es-tab__label { color: var(--es-fg); }

/* No tinted rectangle behind this one. The orb is already the loudest thing on the bar,
   and a highlight box around it reads as a rendering fault rather than as selection. */
.es-tab--raised.es-tab--on { background: none; }

.es-tab--raised.es-tab--on .es-tab__orb {
  background: linear-gradient(180deg, #e6be68, #d4a94f);
  box-shadow:
    0 0 0 6px var(--es-bg),
    0 0 0 9px rgba(212, 169, 79, 0.35),
    0 10px 22px rgba(0, 0, 0, 0.5);
}

.es-tab--raised.es-tab--on .es-tab__label { color: var(--es-blue); }

/* Pressed rather than hovered. On a touch screen a hover style sticks after the tap and
   leaves the wrong tab looking selected. */
.es-tab--raised:active .es-tab__orb { transform: scale(0.94); }

@media (prefers-reduced-motion: reduce) {
  .es-tab__orb { transition: none; }
}

/* ---------- cards, rows, tables ---------- */

.es-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }

.es-card {
  display: block;
  width: 100%;
  text-align: left;
  padding: 14px;
  border-radius: var(--es-radius);
  background: var(--es-surface);
  border: 1px solid var(--es-line);
  color: inherit;
  margin-bottom: 14px;
}

.es-card--tap { cursor: pointer; }
.es-card--on { border-color: var(--es-blue); box-shadow: 0 0 0 1px var(--es-blue) inset; }
.es-card__title { font-weight: 700; margin-bottom: 2px; }
.es-card__sub { font-size: 13px; color: var(--es-mute); margin-bottom: 8px; }

.es-list { display: flex; flex-direction: column; gap: 8px; }

/* A row plus the actions that belong to it. Siblings rather than nesting, because a button
   inside a button is invalid HTML and its click bubbles out to the row behind it. */
.es-row__group { display: flex; align-items: stretch; gap: 8px; }

.es-row__group > .es-row { flex: 1; min-width: 0; }

/* A rounded square, not a pill. .es-btn is 999px and stretching it to the row's height
   turned it into an oval that changed size whenever a show's name wrapped to two lines.
   Fixed and centred instead, so a column of them lines up however tall the rows are, and
   the corner matches the row beside it so the pair reads as one control.

   Selected through the group so it outranks .es-btn, which sets border-radius: 999px and
   is defined further down this file — at equal specificity the later rule wins, and a bare
   .es-row__action lost to it and rendered as a circle. */
.es-row__group > .es-row__action {
  flex: none;
  align-self: center;
  display: grid;
  place-items: center;
  width: 62px;
  height: 62px;
  padding: 0;
  border-radius: var(--es-radius);
  background: var(--es-surface);
}

.es-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  border-radius: var(--es-radius);
  background: var(--es-surface);
  border: 1px solid var(--es-line);
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.es-row--on { border-color: var(--es-blue); }
.es-row__title { font-weight: 600; }
.es-row__sub { font-size: 12px; color: var(--es-mute); display: flex; gap: 8px; align-items: center; }

.es-table {
  border: 1px solid var(--es-line);
  border-radius: var(--es-radius);
  overflow: hidden;
  background: var(--es-surface);
}

.es-table__head,
.es-table__row {
  display: grid;
  grid-template-columns: 2fr repeat(4, minmax(56px, 0.7fr));
  gap: 8px;
  align-items: center;
  padding: 10px 12px;
}

.es-table__head {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--es-mute);
  background: var(--es-surface-2);
}

.es-table__row { border-top: 1px solid var(--es-line); }
.es-table__row--off { opacity: 0.5; }

/* The subscriber console's month table is four columns, not the stock table's five. */
.es-table--months .es-table__head,
.es-table--months .es-table__row { grid-template-columns: 1.3fr repeat(3, minmax(54px, 0.9fr)); }

/* Accounts are cards rather than table rows. Six columns of subscriber detail cannot be
   read on a phone: the headings wrap, the status pill breaks mid-phrase, and the actions
   end up stacked under a column heading that says "ACTIONS" three lines further up. */
.es-accts { display: grid; gap: 10px; }
.es-acct {
  display: grid;
  gap: 8px;
  padding: 12px 14px;
  border: 1px solid var(--es-line);
  border-radius: var(--es-radius);
  background: var(--es-surface);
}
.es-acct--off { opacity: 0.55; }
.es-acct__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.es-acct__name { font-weight: 700; line-height: 1.25; }
.es-acct__email { font-size: 12px; color: var(--es-mute); word-break: break-word; }
.es-acct__value {
  font-weight: 800;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.es-acct__meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.es-acct__meta,
.es-acct__since { font-size: 12px; color: var(--es-mute); }
.es-acct__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  padding-top: 8px;
  border-top: 1px solid var(--es-line);
}

/* ---------- subscriber console ----------

   Its own furniture rather than the trading screens': this is the only part of EagleStrike
   that is us looking at the business rather than a house looking at a sale. */

.es-console__head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }

.es-console__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  background: var(--es-surface-2);
  border: 1px solid var(--es-line);
  color: var(--es-blue);
}

.es-console__title { font-size: 24px; font-weight: 800; margin: 0; line-height: 1.15; }
.es-console__who { font-size: 12.5px; color: var(--es-blue); margin-top: 2px; word-break: break-word; }

.es-console__foot {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--es-blue);
  opacity: 0.75;
  margin: 14px 0 0;
}

.es-console__actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }


.es-stat__note { font-size: 11.5px; color: var(--es-mute); margin-top: 3px; }
.es-stat--warn .es-stat__value { color: var(--es-amber); }

/* A list of rows inside one card: heading strip, then tappable rows. Deliberately not
   .es-table — that grid is the stock take's five columns, and reusing it here is what put
   wrapped headings and a broken status pill on a phone screen. */
.es-rows {
  border: 1px solid var(--es-line);
  border-radius: var(--es-radius);
  background: var(--es-surface);
  overflow: hidden;
}

.es-rows__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--es-mute);
  background: var(--es-surface-2);
}

.es-rows__head > :first-child { flex: 1; }
.es-rows__head > :last-child { text-align: right; }

.es-row2 {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 14px;
  border: 0;
  border-top: 1px solid var(--es-line);
  background: transparent;
  color: inherit;
  text-align: left;
  font: inherit;
}

.es-rows > .es-row2:first-child { border-top: 0; }
.es-row2--tap { cursor: pointer; min-height: 60px; }
.es-row2--tap:active { background: var(--es-surface-2); }
.es-row2--off { opacity: 0.55; }

/* min-width:0 so a long business name ellipsises instead of shoving the amount off. */
.es-row2__main { flex: 1; min-width: 0; }
.es-row2__title { font-weight: 700; font-size: 14.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.es-row2__sub { font-size: 12px; color: var(--es-mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.es-row2__value {
  flex: none;
  font-weight: 700;
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.es-row2__chev { flex: none; color: var(--es-mute); display: grid; place-items: center; }

/* ---------- the console's charts ----------

   One series, so one hue: --es-blue, the same accent the rest of the app uses for "this is
   the number". No legend (the card title names the series) and only the tallest bar carries
   its value, because six labels at phone width collide and the exact figures are in the
   tiles below. Grid lines are omitted rather than drawn faintly: with six bars and a direct
   label, they would be decoration. */

.es-chart {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 132px;
  margin: 6px 0 14px;
}

.es-chart__col { flex: 1; display: flex; flex-direction: column; min-width: 0; }

/* The plot is the fixed-height part; the month label sits under it, so bars of different
   heights all start from the same baseline. */
.es-chart__plot {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 4px;
  min-height: 0;
}

.es-chart__bar {
  width: 100%;
  max-width: 46px;
  background: var(--es-blue);
  /* Rounded at the top, square where it meets the baseline: a bar rounded at both ends
     reads as floating rather than measured from zero. */
  border-radius: 4px 4px 0 0;
  min-height: 4px;
}

.es-chart__value {
  font-size: 11px;
  font-weight: 700;
  color: var(--es-mute);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.es-chart__none { color: var(--es-mute); font-size: 13px; padding-bottom: 2px; }

.es-chart__label {
  text-align: center;
  font-size: 11px;
  color: var(--es-mute);
  padding-top: 6px;
}


/* ---------- share-of-revenue rows ---------- */

.es-split { margin-top: 10px; }

.es-split__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.es-split__name { font-weight: 700; font-size: 14px; }

.es-split__meta { font-size: 12px; color: var(--es-mute); display: flex; gap: 10px; align-items: baseline; }
.es-split__meta strong { color: var(--es-fg); font-variant-numeric: tabular-nums; }

.es-meter { height: 8px; border-radius: 999px; background: var(--es-surface-2); overflow: hidden; }
.es-meter__fill { height: 100%; background: var(--es-blue); border-radius: 999px; }

/* ---------- the call list ----------

   Status colour with the words beside it, never instead of them: "Payment overdue" is the
   information, red is the emphasis. */

.es-callrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--es-line);
}

.es-callrow:first-of-type { border-top: 0; }
.es-callrow__name { font-weight: 700; font-size: 14px; min-width: 0; }
.es-callrow__why { font-size: 12.5px; color: var(--es-amber); text-align: right; flex: none; }
.es-callrow__why--urgent { color: var(--es-red); }

/* Staff is three columns, not the stock table's five. Access and Actions are sized to
   their own content rather than given a share of the row (as the stock table's numeric
   columns are) - a fr share on the pill column stretched it away from Person, leaving a
   wide dead gap before the action links. Only Person is elastic, so the slack collects
   in one place instead of splitting the row apart. */
.es-table--staff .es-table__head,
.es-table--staff .es-table__row { grid-template-columns: 1fr auto auto; }

.es-actions { display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap; }

/* A temporary password, shown once. Monospaced so an l and a 1 are distinguishable while
   it is read out, and select-all so one tap grabs the whole thing. */
.es-secret {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 19px;
  font-weight: 700;
  letter-spacing: 1.5px;
  user-select: all;
  -webkit-user-select: all;
  color: var(--es-fg);
  word-break: break-all;
}

.es-num { text-align: right; font-variant-numeric: tabular-nums; }

/* Money.R's non-breaking spaces mean a price that still doesn't fit after the widened
   .es-table--money columns above has nowhere to wrap - this is the last resort that
   breaks it rather than letting it run over the next cell again. */
.es-table--money .es-num { overflow-wrap: anywhere; }
.es-num--muted { color: var(--es-mute); }

.es-var { font-weight: 700; font-size: 11px; }
.es-var--short { color: var(--es-red); }
.es-var--over { color: var(--es-amber); }

/* ---------- stats ---------- */

/* 12px between tiles, and the same below the group: a run of tiles with no bottom margin
   sits flush against the card after it, which reads as one crowded slab rather than two
   things. The gap is the only thing separating tile from tile - they share a surface
   colour with the page - so it has to be bigger than a border would be. */
.es-stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}

/* Column-count modifiers. These MUST follow .es-stats: same specificity, so whichever is
   written last wins, and declared before it the base rule's auto-fill quietly overrode
   them. It looked correct only because auto-fill lands on two columns at phone width. */

/* Two across, which is what the console's design asks for and what keeps a rand figure on
   one line. */
.es-stats--pair { grid-template-columns: 1fr 1fr; }

/* Three across for the banked windows, which are short numbers. Inside a card, so no
   bottom margin - the card's padding closes it off. */
.es-stats--trio { grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 0; }
.es-stats--trio .es-stat { padding: 10px; }
.es-stats--trio .es-stat__value { font-size: 16px; }

/* Two-by-two for the account sheet's houses/sales/lots/hammer block. Four across would
   fit the numbers but not "Turnover" beneath them, and a wrapped label under a tile reads
   as a second tile. */
.es-stats--quad { grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 0; }
.es-stats--quad .es-stat { padding: 12px; }
.es-stats--quad .es-stat__value { font-size: 18px; }

.es-stat {
  padding: 14px;
  border-radius: var(--es-radius);
  background: var(--es-surface);
  border: 1px solid var(--es-line);
}

.es-stat__label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; color: var(--es-mute); }
.es-stat__value { font-size: 20px; font-weight: 800; font-variant-numeric: tabular-nums; margin-top: 4px; }
.es-stat--good .es-stat__value { color: var(--es-green); }
.es-stat--bad .es-stat__value { color: var(--es-red); }

/* ---------- lot grid ---------- */

.es-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }

.es-tile {
  padding: 14px 12px;
  border-radius: var(--es-radius);
  background: var(--es-surface);
  border: 1px solid var(--es-line);
  color: inherit;
  text-align: left;
  cursor: pointer;
  min-height: 76px;
}

.es-tile__name { font-size: 13px; font-weight: 600; line-height: 1.3; }
.es-tile__price { margin-top: 6px; font-weight: 800; color: var(--es-blue); font-variant-numeric: tabular-nums; }

/* ---------- desk bar ----------

   The basket summary, pinned just above the tab bar. Reserves its own space in
   .es-page--deskbar's bottom padding — more than the ordinary 112px, because this bar
   sits on top of the rostrum button rather than beside it, and the placeholder text this
   screen used to end on was found sitting half-hidden underneath the rostrum button for
   exactly this reason. Do not let this bar's own content get that close again. */

.es-page--deskbar { padding-bottom: calc(180px + var(--es-safe-bottom)); }

.es-deskbar {
  position: fixed;
  /* Inset by the page gutter, not pinned to the screen edges: this is a card with rounded
     corners, and run edge to edge those corners are cut in half by the screen. Lining it
     up with the product tiles it totals is also simply what it should do. */
  left: var(--es-pad);
  right: var(--es-pad);
  bottom: calc(82px + var(--es-safe-bottom));
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: var(--es-page-max);
  margin: 0 auto;
  padding: 12px 16px;
  background: var(--es-surface);
  border: 1px solid var(--es-line);
  border-radius: var(--es-radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}

.es-deskbar__sum { flex: 1; min-width: 0; line-height: 1.2; }
.es-deskbar__total { font-size: 17px; font-weight: 800; font-variant-numeric: tabular-nums; }
.es-deskbar__count { display: block; font-size: 11px; color: var(--es-mute); margin-top: 2px; }

/* ---------- basket ---------- */

/* Plain stacked flexbox rather than a grid with a spanning item — the grid version kept
   sizing the qty row's track wider than the panel no matter how the tracks/items were
   told to shrink, and pushed the remove button off the edge of the screen. Two ordinary
   block-level rows sidestep that: each is sized against .es-line's own width, not against
   a shared track computed from both rows at once. */
.es-line {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 0;
  border-bottom: 1px solid var(--es-line);
}

.es-line__top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.es-line__name { font-size: 13.5px; font-weight: 600; }
.es-line__total { font-variant-numeric: tabular-nums; font-size: 13.5px; white-space: nowrap; }

.es-qty { display: flex; align-items: center; gap: 6px; }

/* Icon-only, so it needs its own hit area rather than .es-link's tight 4px padding —
   trading "Remove" for a bin glyph saved the label's width but not a thumb's width. */
.es-qty__remove {
  margin-left: auto;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 9px;
  background: none;
  border: 0;
  color: var(--es-red);
}

.es-qty button {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  border: 1px solid var(--es-line);
  background: var(--es-surface-2);
  color: var(--es-fg);
  display: grid;
  place-items: center;
  font-size: 17px;
  line-height: 1;
}

.es-qty__n { min-width: 24px; text-align: center; font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }

.es-total-row { display: flex; justify-content: space-between; align-items: baseline; padding: 8px 0 0; }

.es-total-row--grand {
  font-size: 18px;
  font-weight: 800;
  padding-top: 10px;
  margin-top: 4px;
  border-top: 1px solid var(--es-line);
}

.es-tender-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }

/* .es-btn has no layout of its own — a plain button, its text centred only by the
   browser's UA default. That's fine for text alone, but an icon dropped in beside it (as
   on the tender buttons) has nothing telling it to sit level with that text: the icon
   lands at the natural start of the inline box while the centred text wraps below/beside
   it, reading as "icon in the corner" rather than "icon beside label". Same fix .es-sync
   already uses elsewhere for an icon + text pairing. */
.es-tender-grid button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }

.es-paid {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  padding: 7px 0;
  font-size: 13px;
  border-bottom: 1px dashed var(--es-line);
}

/* ---------- scan to pay ---------- */

/* The white plate is not decoration. The symbol itself is drawn black-on-white whatever
   the app theme is, because a light-on-dark code is legal but not every reader takes one,
   and the person holding the phone is a customer who will blame the house. The padding is
   the quiet zone's backing - a code that runs to the edge of a dark panel loses the margin
   readers use to find it. */
.es-qr {
  background: #fff;
  border-radius: 12px;
  padding: 12px;
  margin: 4px auto 0;

  /* Big enough to scan across a table, capped so it does not push the amount field off a
     short screen. */
  width: min(260px, 68vw);
}

.es-qr svg { display: block; width: 100%; height: auto; }

/* The dot that says the desk is still listening. Deliberately quiet: it sits under a code a
   customer is looking at, and a spinner racing away reads as something going wrong. */
.es-waiting {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--es-amber, #d98324);
  animation: es-waiting-pulse 1.4s ease-in-out infinite;
}

@keyframes es-waiting-pulse { 0%, 100% { opacity: .25 } 50% { opacity: 1 } }

/* Reduced motion still needs to say "waiting" — hold it at a steady mid opacity rather than
   leaving a dot that looks like a full stop. */
@media (prefers-reduced-motion: reduce) {
  .es-waiting { animation: none; opacity: .6 }
}

/* ---------- payment confirmed ---------- */

/* What the seller turns back to face themselves once PayFast has said yes. The amount is the
   biggest thing on it: at a table, the one question is "did the right money arrive". */
.es-paid { text-align: center; padding: 8px 0 4px }

.es-paid__tick {
  width: 52px;
  height: 52px;
  margin: 0 auto 10px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 27px;
  color: #fff;
  background: var(--es-green, #3ddc6b);
}

.es-paid__amount {
  margin: 0;
  font-size: 30px;
  font-weight: 650;
  letter-spacing: -.02em;
}

.es-paid__who { margin: 2px 0 0; font-size: 13.5px; color: var(--es-mute) }

/* Reference and time. Two columns so the values line up under each other rather than
   wrapping around labels of different lengths. */
.es-kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 14px;
  margin: 16px 0 0;
  font-size: 13px;
}

.es-kv dt { color: var(--es-mute) }
.es-kv dd { margin: 0; text-align: right; word-break: break-all }

/* ---------- slip ---------- */

.es-ticket {
  background: var(--es-surface-2);
  border: 1px solid var(--es-line);
  border-radius: 12px;
  padding: 14px;
  font-size: 12.5px;
  line-height: 1.65;
  white-space: pre-wrap;
  font-family: ui-monospace, "SF Mono", Consolas, monospace;
}

/* ---------- forms ---------- */

.es-form { display: flex; flex-direction: column; gap: 12px; width: 100%; }
.es-field { display: flex; flex-direction: column; gap: 5px; }
.es-field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.es-field__label { font-size: 12px; color: var(--es-mute); font-weight: 600; }

.es-input {
  width: 100%;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--es-surface-2);
  border: 1px solid var(--es-line);
  color: var(--es-fg);
  /* 16px stops iOS Safari zooming the whole page when a field takes focus. */
  font-size: 16px;
}

.es-input:focus { outline: 2px solid var(--es-blue); outline-offset: 1px; }

/* A file input draws its own button, and every browser draws a light grey OS control -
   which on this theme is a pale slab in the middle of a dark form, and the one control on
   the import screen. ::file-selector-button is the only handle on it. */
.es-input[type="file"] {
  padding: 8px 10px;
  font-size: 14px;
  line-height: 30px;
}

.es-input[type="file"]::file-selector-button {
  margin-right: 12px;
  padding: 7px 14px;
  border: 1px solid var(--es-line);
  border-radius: 999px;
  background: var(--es-surface);
  color: var(--es-fg);
  font: 700 13px/1 inherit;
  font-family: inherit;
  cursor: pointer;
}
.es-input--count { text-align: right; padding: 8px 10px; max-width: 90px; }

/* A textarea does not inherit the page font on its own, and left to itself it can be
   dragged wider than its column. */
.es-input--multi {
  font-family: inherit;
  line-height: 1.4;
  resize: vertical;
  min-height: 62px;
}

.es-check { display: flex; align-items: center; gap: 8px; font-size: 14px; }

/* The browser draws its own box, and its default is a light one - which on this page is a
   white square in the middle of a dark form. accent-color is the whole fix. */
.es-check input { accent-color: var(--es-blue); }

/* A checkbox inside .es-list — the roster sheet. Padded to look like the rows it sits
   among, and the target is the whole row rather than the 16px box, which is the difference
   between rostering six people quickly and missing twice. */
.es-list > .es-check {
  gap: 12px;
  padding: 10px 14px;
  border-radius: var(--es-radius);
  background: var(--es-surface);
  border: 1px solid var(--es-line);
  cursor: pointer;
}

.es-list > .es-check input { width: 20px; height: 20px; flex: none; accent-color: var(--es-blue); }

.es-list > .es-check > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.es-list > .es-check .es-row__sub { display: flex; align-items: center; gap: 8px; }

.es-btn {
  padding: 13px 18px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
  min-height: 44px;
}

.es-btn--primary { background: linear-gradient(180deg, #d4a94f, #b98d33); color: #1a1408; }
.es-btn--ghost { background: transparent; border-color: var(--es-line); color: var(--es-fg); }
.es-btn:disabled { opacity: 0.55; cursor: default; }

/* Full-width primary action - "+ Add a subscriber". */
.es-btn--block { width: 100%; margin: 4px 0 14px; }

/* The one destructive button. Colour plus the word, never colour alone. */
.es-btn--danger { color: var(--es-red); border-color: rgba(255, 107, 107, 0.4); }

/* Tinted actions: a wash of the button's own colour instead of a border. Four of these sit
   in a grid together, and outlines that close would read as a table of empty cells - the
   fill is what separates one action from the next at a glance.

   Written after .es-btn--ghost so they win where a button carries both: same specificity,
   last one declared takes it. The alpha is low on purpose - enough to tell the four apart,
   not enough to compete with the primary button anywhere else on the screen. Every one of
   them still says what it does in words; the colour only says how much to worry. */
.es-btn--tint { border-color: transparent; font-size: 12.5px; padding: 13px 8px; min-height: 48px; }
.es-btn--tint-blue { background: rgba(212, 169, 79, 0.14); color: var(--es-blue); }
.es-btn--tint-green { background: rgba(52, 199, 89, 0.14); color: var(--es-green); }
.es-btn--tint-amber { background: rgba(255, 181, 71, 0.14); color: var(--es-amber); }

/* Red text lifted to #ff8f8f against its own tint: the token red is tuned for a plain
   surface and goes muddy once it sits on a wash of itself. */
.es-btn--tint-red { background: rgba(255, 107, 107, 0.13); color: var(--es-danger-soft); }

.es-link { background: none; border: 0; color: var(--es-blue); font-weight: 600; cursor: pointer; padding: 4px; }
.es-link--danger { color: var(--es-danger-ink); }

.es-error { color: var(--es-red); font-size: 13px; margin: 0; }

.es-note {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--es-mute);
  background: var(--es-surface);
  border: 1px solid var(--es-line);
  border-radius: 12px;
  padding: 10px 12px;
  margin: 0 0 12px;
}

/* ---------- which show this screen is acting on ----------

   A band rather than a line of grey text under the heading. Screens that write into one
   specific book have to say which book, loudly enough to be read on the second day as well
   as the first. The blue edge is doing that work — it is the only thing on the page with a
   coloured border, so the eye lands on it before the numbers. */

.es-context {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  margin: 0 0 14px;
  background: var(--es-surface);
  border: 1px solid var(--es-line);
  border-left: 3px solid var(--es-blue);
  border-radius: 12px;
}

.es-context__icon { flex: none; display: grid; place-items: center; color: var(--es-blue); }

/* min-width:0 so the show name can ellipsis instead of shoving Change off the edge. */
.es-context__body { flex: 1; min-width: 0; }

.es-context__title { display: flex; align-items: center; gap: 8px; min-width: 0; }

.es-context__name {
  font-size: 15px;
  font-weight: 800;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.es-context__sub { font-size: 12px; color: var(--es-mute); margin-top: 1px; }

.es-context__change { flex: none; align-self: center; }

.es-note--warn { color: var(--es-amber); border-color: rgba(255, 181, 71, 0.35); }
.es-note--good { color: var(--es-green); border-color: rgba(52, 199, 89, 0.35); }

/* ---------- segmented control ---------- */

.es-seg { display: inline-flex; gap: 4px; padding: 4px; background: var(--es-surface); border-radius: 999px; border: 1px solid var(--es-line); }

.es-seg__btn {
  padding: 8px 14px;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: var(--es-mute);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  min-height: 36px;
}

.es-seg__btn--on { background: rgba(212, 169, 79, 0.18); color: var(--es-blue); }

/* Full width, for the console where the two halves are equal weight rather than a primary
   action and an afterthought. After .es-seg on purpose - it overrides that rule's
   inline-flex, and declared before it would simply lose. */
.es-seg--full { display: flex; width: 100%; margin-bottom: 14px; }
.es-seg--full .es-seg__btn { flex: 1; }

/* ---------- role badge ---------- */

.es-role {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.3px;
}

.es-role--admin { background: rgba(255, 181, 71, 0.16); color: var(--es-amber); }
.es-role--owner { background: rgba(52, 199, 89, 0.16); color: var(--es-green); }
.es-role--staff { background: rgba(212, 169, 79, 0.16); color: var(--es-blue); }

/* ---------- sync indicator ---------- */

.es-sync {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 10px;
  border-radius: 999px;
  border: 1px solid var(--es-line);
  background: var(--es-surface);
  color: var(--es-mute);
  cursor: pointer;
  min-height: 34px;
}

.es-sync--ok { color: var(--es-green); }
.es-sync--pending { color: var(--es-amber); }
.es-sync--offline { color: var(--es-red); }
.es-sync--busy { color: var(--es-blue); }
.es-sync--busy svg { animation: es-spin 1.1s linear infinite; }
.es-sync__count { font-size: 11px; font-weight: 800; font-variant-numeric: tabular-nums; }

@keyframes es-spin { to { transform: rotate(360deg); } }

/* ---------- sheets ---------- */

.es-sheet {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
}

.es-sheet__panel {
  width: min(560px, 100%);
  max-height: 88dvh;
  overflow-y: auto;
  background: var(--es-surface);
  border-top-left-radius: 20px;
  border-top-right-radius: 20px;
  border: 1px solid var(--es-line);
  padding: 18px 16px calc(18px + var(--es-safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.es-sheet__actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 6px; }

/* ---------- account detail ---------- */

/* Name and address on the left, close button hard right. align-items: start so the ✕ sits
   level with the name rather than floating in the middle of a two-line block. */
.es-acct__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.es-acct__name { font-size: 19px; font-weight: 800; margin: 0; line-height: 1.25; }
.es-acct__email { font-size: 13px; color: var(--es-mute); margin-top: 3px; word-break: break-word; }

/* Secondary lines inside a card - house names, last sale. Deliberately quieter than the
   tiles above them: this is the sentence you read second. */
.es-acct__note { font-size: 12.5px; color: var(--es-mute); margin-top: 10px; line-height: 1.45; }

/* Label left, value right, one fact per line. The dotted rule is what lets the eye run
   across a gap of variable width - a plain gap makes long labels look mis-paired with
   their values. The last row drops it so the card does not end on a line. */
.es-kv {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 8px 0;
  border-bottom: 1px dotted var(--es-line);
  font-size: 13px;
}

.es-kv:first-child { padding-top: 0; }
.es-kv:last-child { padding-bottom: 0; border-bottom: 0; }
.es-kv > span { color: var(--es-mute); }
.es-kv > strong { font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }

/* Invoice line: number and plan grow, date and amount are fixed to the right so a column
   of amounts lines up down the card. */
.es-invoice {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--es-line);
}

.es-invoice:last-child { padding-bottom: 0; border-bottom: 0; }
.es-invoice__main { flex: 1 1 auto; min-width: 0; }
.es-invoice__number { font-size: 13px; font-weight: 700; }
.es-invoice__meta { font-size: 11.5px; color: var(--es-mute); margin-top: 2px; }
.es-invoice__when { font-size: 11.5px; color: var(--es-mute); white-space: nowrap; }
.es-invoice__amount { font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- auth ---------- */

.es-auth { display: grid; place-items: center; min-height: 100dvh; padding: 24px; }

.es-auth__card {
  width: min(400px, 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 26px 22px;
  background: var(--es-surface);
  border: 1px solid var(--es-line);
  border-radius: 20px;
}

.es-auth__title { font-size: 24px; font-weight: 800; margin: 6px 0 0; }
.es-auth__blurb { color: var(--es-mute); font-size: 13px; margin: 0 0 10px; }
.es-auth__foot { font-size: 12px; color: var(--es-mute); text-align: center; margin: 6px 0 0; }

/* ---------- empty states ---------- */

.es-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 32px 16px;
  color: var(--es-mute);
  text-align: center;
}

.es-pill {
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  border: 1px solid transparent;
  /* A status is one phrase. Broken over three lines it stops being a pill. */
  white-space: nowrap;
}
.es-pill--live { background: rgba(52, 199, 89, 0.16); color: var(--es-green); }
.es-pill--muted { background: rgba(var(--es-over-rgb), 0.07); color: var(--es-mute); }

/* Status tones for the subscriber console. Colour is a hint on top of the words — the
   pill always says "Trial ended" or "Payment overdue" as well, because a red dot on its
   own is not information. */
.es-pill--warn {
  background: rgba(232, 165, 71, 0.16);
  color: var(--es-warn-ink);
  border-color: rgba(232, 165, 71, 0.32);
}
.es-pill--bad {
  background: rgba(226, 96, 96, 0.16);
  color: var(--es-danger-ink);
  border-color: rgba(226, 96, 96, 0.32);
}

/* Sits above a page title to say which part of the product you are in. */
.es-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--es-mute);
  margin-bottom: 2px;
}

/* Narrow phones: the stock table's four numeric columns stop fitting. Drop the two
   informational ones and keep the one being typed into. */
@media (max-width: 420px) {
  .es-table--lots .es-table__head,
  .es-table--lots .es-table__row { grid-template-columns: 1.6fr repeat(3, minmax(48px, 0.6fr)); }

  .es-table--lots span:nth-child(2) { display: none; }
}

/* Narrow phones: Price and Cost are full currency strings - Money.R joins "R", the
   thousands group and the cents with non-breaking spaces so a four-figure price like
   "R 1 615.00" can never wrap - and the default columns, sized for the stock table's
   short digit counts, left them under 56px here. The unbreakable string then overflowed
   its cell and painted straight over the next column instead of wrapping. Margin (or
   Qty, in the import preview) and the trailing action column need far less room, so
   give the money columns most of what narrows down to. */
@media (max-width: 420px) {
  .es-table--money .es-table__head,
  .es-table--money .es-table__row {
    grid-template-columns: 1.1fr minmax(78px, 1fr) minmax(78px, 1fr) minmax(44px, 0.5fr) minmax(30px, 0.4fr);
  }
}

@media (prefers-reduced-motion: reduce) {
  .es-sync--busy svg { animation: none; }
}

/* ---------- the book ----------

   The one screen an owner reads sitting down, after the room has emptied. Everything else
   in the app is built for a thumb in motion; this is built to be understood. Hence the
   larger radii, the breathing room between sections, and one number allowed to be far
   bigger than the rest — the profit is the answer, and the rest of the page is the
   working out. */

.es-book { display: flex; flex-direction: column; gap: 16px; }

/* The head and the hero own their own spacing; the flex gap does the rest, so nothing on
   this page carries a bottom margin of its own. */
.es-book > .es-page__head { margin-bottom: 0; }
.es-book > .es-page__head .es-h1 { margin: 0; }
.es-book > .es-note,
.es-book > .es-card { margin-bottom: 0; }

/* ---------- what the show made ----------

   Green or red across the whole card rather than only on the number. This is the one
   moment in the app where the colour is allowed to be the headline: an owner glancing at
   their phone on the drive home should know the answer before they have read a word. The
   words are still there underneath, because colour alone is not information. */

.es-hero {
  padding: 22px;
  border-radius: 24px;
  border: 1px solid transparent;
}

.es-hero--good {
  background: linear-gradient(150deg, var(--es-hero-good-a), var(--es-hero-good-b));
  border-color: rgba(52, 199, 89, 0.32);
}

.es-hero--bad {
  background: linear-gradient(150deg, var(--es-hero-bad-a), var(--es-hero-bad-b));
  border-color: rgba(255, 107, 107, 0.32);
}

.es-hero__label {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.es-hero--good .es-hero__label { color: var(--es-hero-good-label); }
.es-hero--bad .es-hero__label { color: var(--es-hero-bad-label); }

.es-hero__value {
  margin-top: 12px;
  font-size: 44px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
}

.es-hero--good .es-hero__value { color: var(--es-hero-good-value); }
.es-hero--bad .es-hero__value { color: var(--es-hero-bad-value); }

.es-hero__note { margin-top: 10px; font-size: 13px; line-height: 1.55; color: var(--es-hero-note); }

/* Progress to break-even, not progress to a target. The bar fills as the show pays for
   itself and then stops — past break-even there is nothing left to measure against. */
.es-hero__meter {
  height: 10px;
  margin-top: 18px;
  border-radius: 999px;
  background: rgba(var(--es-over-rgb), 0.1);
  overflow: hidden;
}

.es-hero__fill { height: 100%; border-radius: 999px; }
.es-hero--good .es-hero__fill { background: linear-gradient(90deg, #3ddc6b, #7ee89a); }
.es-hero--bad .es-hero__fill { background: linear-gradient(90deg, #ff6b6b, #ff9b9b); }

.es-hero__foot { margin-top: 10px; font-size: 12px; color: var(--es-mute); }

/* ---------- the six tiles ----------

   Always two across, never auto-fill. These are read as three pairs — takings against
   margin, stock out against stock missing, hours against cash — and a layout that
   reflows to three columns on a wide phone breaks every pairing at once. */

.es-ktiles { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

.es-ktile {
  padding: 15px;
  border-radius: 20px;
  background: var(--es-surface);
  border: 1px solid var(--es-line);
}

.es-ktile__head { display: flex; align-items: center; gap: 9px; }

.es-ktile__chip {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 10px;
}

.es-ktile__chip--blue { background: rgba(212, 169, 79, 0.14); color: var(--es-blue); }
.es-ktile__chip--green { background: rgba(52, 199, 89, 0.14); color: var(--es-green); }
.es-ktile__chip--sky { background: rgba(212, 169, 79, 0.14); color: var(--es-sky-ink); }
.es-ktile__chip--amber { background: rgba(255, 181, 71, 0.14); color: var(--es-amber); }
.es-ktile__chip--red { background: rgba(255, 107, 107, 0.14); color: var(--es-red); }

.es-ktile__label {
  flex: 1;
  min-width: 0;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--es-mute);
}

.es-ktile__value {
  margin-top: 12px;
  font-size: 24px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}

/* Only two of the six tiles ever carry a colour on the number itself, and both earn it:
   margin is the figure the whole product exists to surface, and shrinkage is the one that
   needs chasing. The other four take --none and stay in the body colour, which is what
   keeps the two that matter visible. */
.es-ktile__value--green { color: var(--es-green); }
.es-ktile__value--red { color: var(--es-red); }

.es-ktile__note { margin-top: 7px; font-size: 11.5px; line-height: 1.45; color: var(--es-mute); }

/* ---------- the sections underneath ---------- */

.es-panel { padding: 18px; border-radius: 22px; }

.es-panel__head { display: flex; align-items: center; gap: 10px; }

.es-panel__title { flex: 1; margin: 0; font-size: 17px; font-weight: 800; letter-spacing: -0.02em; }

.es-panel__blurb { margin: 7px 0 0; font-size: 12.5px; line-height: 1.55; color: var(--es-mute); }

/* Smaller than the tinted buttons in the console: this one sits beside a heading rather
   than in a grid of four, and at 48px tall it made the card look like a toolbar. */
.es-panel__action { flex: none; min-height: 36px; padding: 9px 14px; font-size: 12px; }

.es-panel__body { display: flex; flex-direction: column; gap: 15px; margin-top: 16px; }
.es-panel__body--tight { gap: 13px; margin-top: 14px; }
.es-panel__body--rows { gap: 9px; margin-top: 14px; }

/* The journal's rows are separated by their own padding, so the gap closes to nothing and
   the list reads as one run of slips rather than a stack of cards. */
.es-panel__body--flush { gap: 3px; margin-top: 12px; }

.es-panel__empty { margin: 12px 0 0; font-size: 13px; color: var(--es-mute); }

.es-book__sub { display: block; font-size: 11.5px; color: var(--es-mute); margin-top: 4px; }

.es-book__rev { font-weight: 700; font-size: 13.5px; color: var(--es-blue); font-variant-numeric: tabular-nums; }

.es-book__row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.es-book__rowmain { flex: 1; min-width: 0; }
.es-book__rowname { display: block; font-size: 13px; font-weight: 700; }

/* The neutral counterpart to --live and --bad: a sell-through figure is a fact, not a
   status, and colouring it green would read as "good" at 12%. */
.es-pill--info { background: rgba(212, 169, 79, 0.14); color: var(--es-blue); }

/* ---------- one show against another ---------- */

.es-compare {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 14px;
  border: 1px solid var(--es-line);
  border-radius: 18px;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* The show you are already looking at. Filled rather than merely outlined, because the
   row is also disabled — an outline alone reads as "not yet chosen". */
.es-compare--on {
  background: var(--es-surface-2);
  border-color: rgba(212, 169, 79, 0.35);
  cursor: default;
}

.es-compare:disabled { opacity: 1; }

.es-compare__bar { flex: none; width: 5px; height: 36px; border-radius: 999px; }
.es-compare__bar--good { background: var(--es-green); }
.es-compare__bar--bad { background: var(--es-red); }

.es-compare__main { flex: 1; min-width: 0; }

.es-compare__name {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.es-compare__value { flex: none; font-size: 16px; font-weight: 800; font-variant-numeric: tabular-nums; }
.es-compare__value--good { color: var(--es-green); }
.es-compare__value--bad { color: var(--es-red); }

/* ---------- sales journal rows ----------

   Named apart from .es-ticket (the printed receipt in Settle.razor - a bordered, monospace
   pre block) even though both once went by "slip". Sharing that class pulled the
   receipt's background, border and 1.65 line-height into every journal row, which is
   exactly the "flush, no cards" list .es-panel__body--flush was written for. */

.es-journal { display: flex; align-items: flex-start; gap: 12px; padding: 11px 10px; border-radius: 16px; }

.es-journal__badge {
  flex: none;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 13px;
  background: rgba(212, 169, 79, 0.13);
  color: var(--es-blue);
  font-size: 13px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* A refund is a slip like any other and stays in the run, but it is not a sale and the
   badge says so before the minus sign on the amount is read. */
.es-journal__badge--refund { background: rgba(255, 107, 107, 0.14); color: var(--es-red); }

.es-journal__main { flex: 1; min-width: 0; }

.es-journal__title {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.es-journal__amount { flex: none; font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums; }

/* ==================================================================================
   BEYOND THE PHONE

   Everything above this line is the phone: one column, a bar of tabs along the bottom,
   nothing that needs a mouse. That is the shape the app is used in at a market and it is
   not up for negotiation - the blocks below only add to it.

   Both hosts load this file, so "desktop" here means a wide window, not a platform. A
   browser on a laptop gets the rail; the same browser dragged narrow gets the phone
   layout back; a MAUI window on Windows, or an iPad in landscape, gets the rail too -
   which is right, because the question is how much room there is and not what is running.
   ================================================================================== */

/* ---------- keyboard focus ----------

   A phone has no tab key, so this file never needed a focus style. A browser does, and
   without one it is impossible to tell where you are while tabbing through the rostrum.
   :focus-visible rather than :focus, so a mouse click does not leave a ring behind. */

:where(a, button, [role="button"], input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--es-blue);
  outline-offset: 2px;
  border-radius: 8px;
}

/* Except the heading. FocusOnNavigate moves focus to the h1 after every navigation so a
   screen reader announces the new screen - correct, and worth keeping - but the browser
   then paints a ring around the page title on arrival, which reads as a rendering fault.
   The heading is not in the tab order, so nobody loses a cue they could have used. */
.es-h1:focus,
.es-h1:focus-visible,
h1[tabindex="-1"]:focus { outline: none; }

/* ---------- pointer ----------

   Guarded on hover, not on width. A touch screen that reports no hover keeps the pressed
   states above and none of this: a hover style on a touch device sticks after the tap and
   leaves the wrong row looking selected, which is the fault .es-tab--raised:active was
   written to avoid in the first place. */

@media (hover: hover) and (pointer: fine) {
  .es-card--tap:hover,
  .es-row:hover,
  .es-tile:hover,
  .es-compare:not(:disabled):hover { border-color: rgba(212, 169, 79, 0.55); }

  .es-row2--tap:hover { background: var(--es-surface-2); }

  .es-tab:hover { color: var(--es-fg); background: rgba(var(--es-over-rgb), 0.05); }
  .es-tab--on:hover { color: var(--es-blue); background: rgba(212, 169, 79, 0.22); }
  .es-tab--raised:hover { background: none; }
  .es-tab--raised:hover .es-tab__orb { transform: translateY(-1px); }

  .es-iconbtn:hover { background: rgba(var(--es-over-rgb), 0.12); color: var(--es-fg); }
  .es-iconbtn--on:hover { background: rgba(212, 169, 79, 0.26); color: var(--es-blue); }

  .es-btn:not(:disabled):hover { filter: brightness(1.12); }
  .es-btn--ghost:not(:disabled):hover { background: rgba(var(--es-over-rgb), 0.06); filter: none; }

  .es-link:hover { text-decoration: underline; }
  .es-seg__btn:not(.es-seg__btn--on):hover { color: var(--es-fg); }
  .es-qty button:hover { background: var(--es-blue-fill); color: #1a1408; border-color: transparent; }
  .es-qty__remove:hover { background: rgba(255, 107, 107, 0.14); }
  .es-input[type="file"]::file-selector-button:hover { background: var(--es-surface-2); border-color: var(--es-blue); }
}

/* A dark page with a bright scrollbar down the side of it looks like a rendering fault. */
@supports (scrollbar-color: auto) {
  .es-main { scrollbar-color: var(--es-surface-2) transparent; scrollbar-width: thin; }
}

/* ---------- tablet-width bottom bar ----------

   Still a bar, because a tablet held in portrait is still a thing with thumbs. But five
   tabs spread across 800px put Shows and Products at opposite edges of the screen and the
   rostrum button a hand's width from either. Cap how far apart they can get. */

@media (min-width: 560px) and (max-width: 899.98px) {
  .es-tabs {
    padding-left: max(8px, calc((100% - 560px) / 2));
    padding-right: max(8px, calc((100% - 560px) / 2));
  }
}

/* ==================================================================================
   DESKTOP - a rail instead of a bar

   Past this width the window is being looked at rather than held. Three things change and
   nothing else does:

     - the tabs become a navigation rail down the left, because a bottom bar 1400px wide
       puts the two ends of it a head-turn apart and turns every selected tab into a
       300px-wide slab;
     - the content sits in a column of a readable width rather than stretching, and the top
       bar is padded to the same two edges, so the house name lines up with the heading
       underneath it instead of hugging a corner 300px away from it;
     - the sheets stop sliding up from the bottom and become ordinary centred dialogs.

   The rail is the same <nav class="es-tabs"> markup, restyled. Nothing here needs a
   different component, which is the point: one set of screens, two shapes.
   ================================================================================== */

@media (min-width: 900px) {
  :root {
    --es-rail: 248px;
    --es-page-max: 1140px;
    --es-pad: 24px;
  }

  /* Only when there is a rail to make room for. An admin has no tabs at all and a
     signed-out visitor has no chrome, and either would otherwise sit 248px off-centre
     with nothing in the gap. */
  .es-shell:has(> .es-tabs) { padding-left: var(--es-rail); }

  /* The top bar's content, brought onto the same two edges as the column below it. The
     percentage resolves against the bar's own width - which is the window less the rail,
     the same box .es-page is centred in - so the two cannot drift apart. */
  .es-topbar {
    padding-top: calc(14px + var(--es-safe-top));
    padding-bottom: 14px;
    padding-left: calc(max(0px, (100% - var(--es-page-max)) / 2) + var(--es-pad));
    padding-right: calc(max(0px, (100% - var(--es-page-max)) / 2) + var(--es-pad));
  }

  /* No bottom bar left to clear. */
  .es-page,
  .es-page--nobar { padding-bottom: 40px; }

  /* ---------- the rail ---------- */

  .es-tabs {
    top: 0;
    bottom: 0;
    right: auto;
    width: var(--es-rail);
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 4px;
    padding: calc(16px + var(--es-safe-top)) 12px calc(16px + var(--es-safe-bottom));
    background: var(--es-surface);
    border-top: 0;
    border-right: 1px solid var(--es-line);
    overflow-y: auto;
  }

  .es-tabs__side {
    flex: none;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 4px;
  }

  /* The notch reserved the middle of a bar so no tab ended up under the rostrum button.
     A column has no middle to reserve; this is just where the button sits in the order. */
  .es-tabs__notch {
    flex: none;
    display: block;
    margin: 10px 0;
  }

  /* A row, not a stack: at 224px wide there is room for the icon and the word beside each
     other, and a column you read down is the whole point of a rail. */
  .es-tab {
    flex: none;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    padding: 11px 14px;
    min-height: 46px;
    transition: background 120ms ease, color 120ms ease;
  }

  .es-tab__label { font-size: 13.5px; }

  /* The rostrum, still the loudest thing on the rail, but as a full-width button rather than
     an orb hanging off the top edge of a bar. The negative margin and the ring painted in
     the page background were both about breaking that edge, and here there is no edge. */
  .es-tab--raised {
    width: auto;
    margin-top: 0;
    padding: 0;
    gap: 0;
  }

  .es-tab--raised .es-tab__orb {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    width: 100%;
    height: 52px;
    /* The same 14px and 12px the ordinary tabs carry, so the cart sits directly under the
       box glyph and over the bar chart rather than off in a column of its own. */
    padding: 0 14px;
    gap: 12px;
    border-radius: 14px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
  }

  /* The label moves inside the button so the two read as one control rather than as a
     button with a caption under it. */
  .es-tab--raised .es-tab__label { display: none; }

  .es-tab--raised .es-tab__orb::after {
    content: "Sell";
    font-size: 15px;
    font-weight: 800;
  }

  .es-tab--raised.es-tab--on .es-tab__orb {
    box-shadow: 0 0 0 2px rgba(212, 169, 79, 0.45), 0 6px 18px rgba(0, 0, 0, 0.35);
  }

  .es-tab--raised:active .es-tab__orb { transform: scale(0.98); }

  /* ---------- the desk bar ----------

     Above the content column rather than above a bottom bar that is no longer there, and
     inset by the rail so it is centred on the column and not on the window. */

  .es-deskbar {
    left: calc(var(--es-rail) + var(--es-pad));
    bottom: 24px;
    padding: 14px 18px;
    /* The column less its gutters, so this card lines up with the product tiles above it
       rather than sitting 24px proud of them on both sides. */
    max-width: calc(var(--es-page-max) - 2 * var(--es-pad));
  }

  .es-page--deskbar { padding-bottom: 132px; }

  /* ---------- sheets ----------

     A panel that slides up from the bottom edge is a phone gesture. On a desktop it reads
     as a dialog, so it is drawn as one: centred, rounded on all four corners, and no wider
     than the form inside it needs. */

  .es-sheet { align-items: center; padding: 24px; }

  .es-sheet__panel {
    width: min(560px, 100%);
    max-height: min(760px, 86dvh);
    border-radius: 20px;
    padding: 22px;
    box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  }

  /* ---------- room to breathe ----------

     The auto-fill grids were sized for a phone: left alone they turn 1100px into seven
     columns of 150px, which is a spreadsheet rather than a set of cards. Raising the
     minimum track keeps a tile a tile. */

  .es-cards { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
  .es-grid { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
  .es-stats { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }

  /* And the fixed-count modifiers restated after it, for exactly the reason the comment on
     .es-stats--pair up the file gives: same specificity, last one declared wins. Without
     these three lines the auto-fill above silently took the console's banked-money row
     from three columns to three-quarters of four, and the tiles stopped filling the card
     they sit in. */
  .es-stats--trio { grid-template-columns: repeat(3, 1fr); }

  /* Still two by two. It lives in the account sheet, which is 560px wide on any screen. */
  .es-stats--quad { grid-template-columns: 1fr 1fr; }

  .es-h1 { font-size: 26px; }
  .es-h2 { font-size: 17px; }
  .es-page__head { margin-bottom: 16px; }

  /* The stock take is a wide table of numbers and gains from the room. */
  .es-table__head,
  .es-table__row { padding: 12px 16px; }

  .es-row { padding: 14px 16px; }

  /* ---------- sized for a phone, and not simply to be stretched ----------

     A text field 1100px wide is not a better text field. Forms get a column of their own
     inside the page rather than a page of their own: the top bar is padded to
     --es-page-max, so a page that narrows itself pulls its heading out from under the
     house name sitting directly above it. Every screen keeps the one column width. */

  .es-form { max-width: 660px; }

  /* A slip is a receipt, and a receipt is a narrow thing. Run to the full column it stops
     looking like one: forty characters of monospace sitting in the left third of a very
     wide panel. Centred under the reference above it instead. */
  .es-ticket { max-width: 460px; margin-left: auto; margin-right: auto; }

  /* A page that is one long form - settings - rather than a page with a form on it. The
     column is capped on the children, not on .es-page itself: narrowing the page would
     re-centre it and pull the heading out from under the house name in the top bar, and
     the whole point of the measure above is that those two edges agree. */
  .es-page--form > * { max-width: 660px; }

  /* The book's six tiles are read as three pairs - takings against margin, stock out
     against stock missing, hours against cash. Two across at 1100px makes each one 540px
     of mostly empty card; three across in row order would split every pair. Filling by
     column instead gives three columns of two, and each column is exactly one pair. */
  .es-ktiles {
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(2, auto);
    grid-auto-flow: column;
  }

  /* The hero is a headline, not a paragraph. Left to the full column its supporting lines
     run to 150 characters and stop being readable. */
  .es-hero__note,
  .es-hero__foot { max-width: 62ch; }

  /* The console's tiles are a fixed count on purpose (see .es-stats--pair), and two of
     them across a full-width page is two 540px cards holding one number each. */
  .es-stats--pair { grid-template-columns: repeat(4, 1fr); }

  /* Full-width was the phone answer for both of these: a pill 1100px long is a landing
     page, not a button, and a two-way switch that wide has its two halves half a screen
     apart. Both keep their left edge and stop growing. */
  .es-btn--block { max-width: 340px; }
  .es-seg--full { max-width: 440px; }

  /* Explanatory copy, held to a readable measure rather than run to the full column. */
  .es-console__foot,
  .es-panel__blurb { max-width: 78ch; }
}

/* Hover on the rostrum button has to come after the desktop block that restyles it, or the
   translate written for the phone's orb wins and the button lifts off its own row. */
@media (min-width: 900px) and (hover: hover) and (pointer: fine) {
  .es-tab--raised:hover .es-tab__orb { transform: none; filter: brightness(1.08); }
}

/* Very wide: let the column grow rather than leaving a third of a 27-inch screen empty,
   but not past the point where one table row is a head-turn from end to end. */
@media (min-width: 1600px) {
  :root { --es-page-max: 1280px; --es-pad: 32px; }
}


/* ---------- Google sign-in mark ----------

   Google's brand guidelines require the G be shown unaltered, on white or a light surface.
   The app's own ground is dark, so the mark gets its own light chip rather than being
   recoloured to fit — recolouring it is the thing the guidelines actually forbid. */
.es-google {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin-right: 10px;
  border-radius: 8px;
  background: #fff;
  flex: none;
}

.es-btn--block .es-google { margin-left: -4px; }

/* Sign in with Apple's logo: Apple's own file (wwwroot/apple-logo.svg, see AppleSignInButton)
   as a mask, filled with the button's text colour so it follows the theme. Inline-block rather
   than grid so it lines up with the text the way the Google chip does. */
.es-apple {
  display: inline-block;
  width: 18px;
  height: 18px;
  margin-right: 10px;
  vertical-align: -3px;
  flex: none;
  background: currentColor;
  -webkit-mask: url("apple-logo.svg") center / contain no-repeat;
  mask: url("apple-logo.svg") center / contain no-repeat;
}

/* The Apple button straight under the Google one: a little air between the pair. */
* + .es-onetap-apple { margin-top: 10px; }
.es-btn--block + .es-onetap-apple { margin-top: 0; }

/* ---------- an unhandled error ----------

   Hidden until Blazor shows it, which it does only for an error nothing caught. Here rather
   than in the website's app.css so that both hosts load it: the phone app has no app.css, and
   without this rule its "Something went wrong" bar sat at the foot of every page from the
   moment the app opened, with nothing wrong at all.

   Not the same thing as a dropped connection and deliberately not styled like one: waiting
   will not fix this, so it is a banner that says so rather than a dialog that spins. */

#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1001;
    padding: 14px 18px;
    background: #ffb547;
    color: #14202f;
    font: 600 14px/1.4 'Plus Jakarta Sans', sans-serif;
    box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.4);
}

#blazor-error-ui .reload {
    color: #14202f;
    font-weight: 800;
    margin-left: 8px;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 14px;
    top: 12px;
}
