/* ==========================================================================
   HIDDEN MEANS HIDDEN, on every surface.

   `[hidden] { display: none }` is the browser's own rule, and it lives in the
   USER-AGENT origin. Every normal declaration we write is in the AUTHOR
   origin, and origin is settled before specificity is even consulted -- so
   ANY rule of ours that sets a display on the element beats it. Not just a
   compound one: `.wheel-stage { display: flex }`, a single class, is enough.

   That distinction is the whole reason this note is long. "It needs to be
   more specific than [hidden]" is the plausible reading and it is wrong; a
   guard written to that rule passed while the bug it was written for sat two
   files away. !important is what actually beats a normal author declaration,
   which is why it is here and is not decoration.

   So an element shipped with `hidden` is on screen anyway, and script setting
   `.hidden = true` changes nothing a person can see.

   That cost the player surface its lobby: the cashback wheel is a full-screen
   fixed overlay with `display: flex`, shipped hidden and shown by script, and
   it was never hidden -- every signed-in player got a dimmed screen behind an
   empty wheel with no way past it.

   IT LIVES HERE, not in play.css, because every surface loads this file. The
   till and the console have no `hidden` element today, so they have never hit
   it; they would have hit it the first time somebody added one, and it would
   have looked like a scripting bug. Fixing the surface that bled and leaving
   the others to find out is how this happened twice already -- once for the
   reel canvas, then again for the whole rail.
   ========================================================================== */
[hidden] { display: none !important; }

:root {
  /* ---------------------------------------------------------- DARK, the
     default and the one a till runs in. Every colour in this stylesheet is
     a token defined here or in the light block below -- a literal anywhere
     else is a colour that only works in one theme, and it will be the one
     nobody notices is wrong. The single exception is @media print, which is
     black on white whatever the screen was doing. */
  /* THE BRAND'S OWN COLOURS, taken off the wordmark rather than invented:
     gold #f7a001-#fde522, the G's emerald #01ae02, and a near-black green
     for the ground. The palette was purple-black and muted gold, which was a
     perfectly good scheme belonging to no particular company. */
  --bg: #08130a; --panel: #0f2010; --line: #1e3620;
  --text: #e8f2e4; --muted: #8ba086; --accent: #fdc10a; --warn: #d98b4a;
  --field: #132514;
  --on-accent: #17140c;          /* text ON a gold button */
  --hover: #1b3a1d;              /* a control under the cursor */
  --row-hover: #12220f; --row-focus: #172c14;
  --sunken: #061007;             /* a well: receipts, code, the poster tile */
  /* Deeper than --sunken, for the arcade's lit windows: the jackpot readout,
     the purse, the winners ticker. Here rather than in play.css because the
     contrast test measures --accent against the colour a figure is REALLY
     drawn on, and it reads its palette from this block. A literal in the
     other file would have left it measuring --panel, which the pot stopped
     sitting on the moment it was recessed. */
  --well: #050d06;
  --sunken-2: #1c3320;
  --ok-bg: #12331a; --error-bg: #3a1a20; --warn-bg: #2e2119;

  /* The G's green. A THIRD accent, not a replacement for the gold: gold is
     what a control is made of, this is what money is. Kept for figures that
     mean cash and for the wordmark, never for a button -- two accent colours
     competing to be pressed is one too many. */
  --money: #01ae02;
  /* The hierarchy rails. Dim gold so the tree reads as structure without
     competing with the accent on actual controls. */
  --rail: #8a7326; --rail-glow: rgb(253 193 10 / .45);

  /* Tells the browser to draw everything it draws ITSELF from the dark end of
     its palette: the select popup, scrollbars, focus rings, autofill, the
     caret, spinners. Without it those are painted light no matter what is
     written below, because they are not ours to style. */
  color-scheme: dark;
}

/* ------------------------------------------------------------------ LIGHT
   Not the dark values lightened: a light till is read in daylight, often
   through a glare, so the text is nearly black and the lines are stronger
   than an inverted palette would make them. Gold keeps its hue but drops in
   value, because #b9a24a on white is a stripe nobody can read.

   Applied by data-theme on <html>, decided on the SERVER from the cookie --
   see backend/theme.py. Doing it in script would mean every page painting
   dark for a frame before flipping, which on a shop counter looks like a
   fault. */
:root[data-theme="light"] {
  --bg: #f1f5ef; --panel: #ffffff; --line: #d8e2d5;
  --text: #16200f; --muted: #5b6b57; --accent: #8a6d05; --warn: #a8531b;
  --field: #ffffff;
  --on-accent: #ffffff;
  --hover: #eaf2e7;
  --row-hover: #f3f8f1; --row-focus: #e9f2e6;
  --sunken: #f5f9f3; --sunken-2: #e9f0e6;
  --ok-bg: #e0f2e2; --error-bg: #fbe4e8; --warn-bg: #fbeade;
  --rail: #9a8a4a; --rail-glow: rgb(138 109 5 / .35);
  /* Darker than the dark theme's, for the same reason the gold is: #2fbf2f
     on white is a highlighter pen. */
  --money: #1a7d1a;

  color-scheme: light;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text);
       font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
main { max-width: 1100px; margin: 0 auto; padding: 24px 20px 60px; }
h1 { font-size: 22px; margin: 0 0 18px; }
h2 { font-size: 15px; text-transform: uppercase; letter-spacing: .08em;
     color: var(--muted); margin: 26px 0 10px; }
.muted { color: var(--muted); }
.warn { color: var(--warn); }

/* There was no rule for a plain link at all, so anything outside .topnav or a
   table cell -- Cancel, Back to accounts, Create an account -- came out in the
   browser's default link colour. Quiet by default: a link is secondary unless
   it says otherwise by carrying .btn. */
a { color: var(--muted); }
a:hover { color: var(--text); }

.topnav { display: flex; gap: 18px; align-items: center; padding: 12px 20px;
          background: var(--panel); border-bottom: 1px solid var(--line); }
.topnav .brand { font-weight: 800; letter-spacing: .02em; }
.topnav a { color: var(--muted); text-decoration: none; padding: 4px 0;
            border-bottom: 2px solid transparent; }
.topnav a.on { color: var(--text); border-bottom-color: var(--accent); }
.topnav .right { margin-left: auto; }
.linkish { background: none; border: 0; color: var(--muted); cursor: pointer;
           font: inherit; }

.cards { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 22px; }
.card { flex: 1 1 180px; background: var(--panel); border: 1px solid var(--line);
        border-radius: 8px; padding: 14px 16px; }
.card .label { color: var(--muted); font-size: 11px; letter-spacing: .08em;
               text-transform: uppercase; }
.card .figure { font-size: 26px; font-weight: 700; margin-top: 4px; }

table { width: 100%; border-collapse: collapse; }
th { text-align: left; color: var(--muted); font-size: 11px;
     letter-spacing: .06em; text-transform: uppercase;
     border-bottom: 1px solid var(--line); padding: 6px 8px; }
td { padding: 9px 8px; border-bottom: 1px solid var(--line); }
tr.attention td:first-child { border-left: 3px solid var(--warn);
                              padding-left: 11px; }
td a { color: var(--muted); }

.flash { padding: 10px 14px; border-radius: 6px; margin-bottom: 16px; }
.flash.error { background: var(--error-bg); }
.flash.ok { background: var(--ok-bg); }

/* ------------------------------------------------------------ form controls
   BY ELEMENT, not by wrapper. These rules used to hang off `.login` and
   `.row-form`, so a form that did not happen to carry one of those classes
   fell back to the browser's light defaults -- looking obviously wrong on a
   dark page while every rule that should have covered it sat right there in
   this file. A new form should be themed because it is a form. */

input, select, textarea {
  padding: 9px 10px; background: var(--field);
  border: 1px solid var(--line); border-radius: 6px;
  color: var(--text); font: inherit; max-width: 100%;
}
input::placeholder { color: var(--muted); opacity: 1; }

input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgb(185 162 74 / .22);
}

/* A <select> ignores background and border entirely unless its native
   appearance is switched off -- which is why this one control stayed light
   while the input beside it went dark. The chevron is ours once we do. */
select {
  -webkit-appearance: none; appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml;charset=utf-8,\
%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E\
%3Cpath fill='none' stroke='%238f88a6' stroke-width='1.6' \
stroke-linecap='round' stroke-linejoin='round' d='M1 1.8 6 6.4l5-4.6'/%3E\
%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 11px;
}
/* The popup is drawn by the OS. `color-scheme: dark` does most of it; these
   land on the platforms that let a page colour the list. */
select option, select optgroup { background: var(--panel); color: var(--text); }

/* Chrome and Brave paint an autofilled field with their own background and
   ignore `background-color`. An inset shadow the size of the box is the only
   thing that reliably covers it. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--field) inset;
  caret-color: var(--text);
  transition: background-color 9999s ease-in-out 0s;
}

/* One primary style, whether it is a submit or a link that starts a flow.
   Written once so a link-shaped action cannot drift away from a button-shaped
   one -- they mean the same thing to the person clicking. */
button[type=submit], .btn {
  display: inline-block; padding: 9px 16px; border: 0; border-radius: 6px;
  background: var(--accent); color: var(--on-accent); font: inherit; font-weight: 700;
  text-decoration: none; cursor: pointer;
}
a.btn, a.btn:hover { color: var(--on-accent); }
button[type=submit]:hover, .btn:hover { filter: brightness(1.08); }

/* `.linkish` has to come AFTER, and name the element, or it loses. A bare
   `.linkish` is 0,1,0 and `button[type=submit]` is 0,1,1, so every row action
   marked linkish -- Suspend, Reset password, Sign out -- was being painted as
   a primary gold button. Gold should mark the ONE action a screen is for; a
   table where every row shouts has nothing left to emphasise the row that
   matters. */
button.linkish, button.linkish:hover {
  padding: 0; background: none; border: 0; border-radius: 0;
  color: var(--muted); font: inherit; font-weight: inherit;
  filter: none; text-decoration: underline; cursor: pointer;
}
button.linkish:hover { color: var(--text); }

.login { max-width: 340px; margin: 12vh auto; }
.login label { display: block; margin-bottom: 12px; color: var(--muted); }
.login input { display: block; width: 100%; margin-top: 4px; }
.login .muted { display: block; margin-top: 4px; font-size: 12px; }

.row-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
            margin-bottom: 8px; }
.row-form input { min-width: 180px; }
/* A field that states its own size means it -- a rate box does not need the
   same room as a note. */
.row-form input[size] { min-width: 0; width: auto; }

/* The stacked forms -- create an account, move one, add a sign-in. */
form label { display: block; margin-bottom: 12px; color: var(--muted);
             max-width: 340px; }
/* A CHECKBOX IS NOT A TEXT FIELD. The rule below makes every input inside a
   label a full-width block, which is right for the text fields it was written
   for and wrong for a radio: it takes a whole column and leaves its own label
   stranded ninety pixels away. Anybody adding a checkbox to a form would have
   hit this too, so the exception is on the type rather than on my one class. */
form label input[type="radio"],
form label input[type="checkbox"] { display: inline-block; width: auto; }

form label input, form label select { display: block; width: 100%;
                                      margin-top: 4px; }
.row-form label { display: inline-block; margin-bottom: 0; }
.row-form label input, .row-form label select { display: inline-block;
                                                width: auto; margin-top: 0; }

.actions-below { margin-top: 18px; }
/* Cancel sits right up against Create otherwise, and the one that abandons the
   form should not look like part of the one that submits it. */
button[type=submit] + a { margin-left: 14px; }

/* ROW ACTIONS ARE BUTTONS. As underlined text they read as navigation, and
   half of them are not -- Suspend changes an account and everything beneath
   it. Something that acts on a record should look like something you press.
   Secondary, not gold: gold stays the one primary action on a screen. */
td.actions { white-space: nowrap; text-align: right; }
td.actions form { display: inline; }
td.actions a, td.actions button {
  display: inline-block; margin-left: 6px; padding: 5px 11px;
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--field); color: var(--text);
  font: inherit; font-size: 13px; font-weight: 500;
  text-decoration: none; cursor: pointer; filter: none;
}
td.actions a:hover, td.actions button:hover {
  border-color: var(--accent); color: var(--text); background: var(--hover);
}
/* Suspend and Reinstate are not the same kind of act as Move or Sign-ins, and
   the row should say so before it is clicked rather than after. */
td.actions button.danger:hover {
  border-color: var(--warn); color: var(--warn); background: var(--warn-bg);
}
code.issued { display: inline-block; margin: 4px 0; padding: 4px 8px;
              background: var(--sunken); border-radius: 4px; font-size: 15px;
              letter-spacing: .06em; }

/* ===========================================================================
   THE SHELL — sidebar, top bar, content card.

   Laid out like Golden Dragon and Orca on purpose. The people who will use
   this have spent years in those two, and a familiar shape is worth more than
   an original one: it is the difference between learning a product and
   recognising it. Ours in every other respect -- our colours, our words, our
   code -- but the furniture is where they expect to find it.
   =========================================================================== */

.shell { display: flex; flex-direction: column; min-height: 100vh; }

.topbar {
  display: flex; align-items: center; gap: 14px; padding: 0 16px; height: 46px;
  background: var(--panel); border-bottom: 1px solid var(--line); flex: 0 0 auto;
}
.topbar .brand { font-weight: 800; letter-spacing: .02em; white-space: nowrap; }
.topbar .crumbs { color: var(--muted); font-size: 13px; }
/* Only when there is a crumb to separate from the brand. */
.topbar .crumbs:not(:empty)::before { content: "/"; margin-right: 10px;
                                     color: var(--line); }
.topbar .right { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* The identity strip. Golden Dragon keeps who you are and which till you are
   standing at visible on every screen, and it is the first thing anybody
   checks when a number looks wrong. */
.pill {
  padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
  background: var(--field); border: 1px solid var(--line); color: var(--muted);
  white-space: nowrap;
}
.pill.strong { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.pill.role { background: none; border: 0; color: var(--warn); }

.shell-body { display: flex; flex: 1 1 auto; min-height: 0; }

.sidebar {
  flex: 0 0 216px; background: var(--panel);
  border-right: 1px solid var(--line); padding: 6px 0;
}
.sidebar a, .sidebar button {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 11px 14px; border: 0; border-left: 3px solid transparent;
  border-radius: 0; background: none; color: var(--muted);
  font: inherit; font-size: 14px; text-align: left; text-decoration: none;
  cursor: pointer; filter: none;
}
.sidebar a:hover, .sidebar button:hover { background: var(--field); color: var(--text); }
.sidebar a.on {
  background: var(--field); color: var(--text);
  border-left-color: var(--accent); font-weight: 600;
}
.sidebar svg { flex: 0 0 16px; width: 16px; height: 16px; opacity: .85; }
.sidebar .sep { margin: 6px 12px; border-top: 1px solid var(--line); }

.shell main { flex: 1 1 auto; max-width: none; margin: 0; padding: 16px 18px 40px;
              min-width: 0; overflow-x: auto; }

/* The content card. A titled sheet with its own header, the way both
   references frame every screen. */
.sheet { background: var(--panel); border: 1px solid var(--line);
         border-radius: 8px; margin-bottom: 16px; }
.sheet > header {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; border-bottom: 1px solid var(--line);
}
.sheet > header h1, .sheet > header h2 {
  margin: 0; font-size: 15px; font-weight: 700; text-transform: none;
  letter-spacing: 0; color: var(--text);
}
.sheet > header .sub { color: var(--muted); font-size: 13px; }
.sheet > header .right { margin-left: auto; display: flex; gap: 8px;
                         align-items: center; }
.sheet .inner { padding: 14px; }
.sheet table { margin: 0; }
.sheet table th { padding-left: 14px; padding-right: 14px; }
.sheet table td { padding-left: 14px; padding-right: 14px; }

/* The label:value grid. Golden Dragon puts the customer's details in two
   columns with the labels right-aligned against their values, which is what
   makes a dense screen scannable rather than a wall. */
.detail { display: grid; grid-template-columns: repeat(2, minmax(240px, 1fr)); }
.detail > div {
  display: flex; gap: 10px; padding: 9px 14px;
  border-bottom: 1px solid var(--line);
}
.detail .k { flex: 0 0 44%; text-align: right; color: var(--muted); }
.detail .v { font-weight: 600; }
/* An odd number of rows leaves the last cell alone in a two-column grid, and
   its border-bottom then stops halfway across the card -- which reads as a
   rendering fault rather than a rule. Light mode is where it became obvious,
   because the borders there carry more weight. */
.detail > div:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (max-width: 900px) { .detail { grid-template-columns: 1fr; } }

/* Actions grouped along the foot of a card: what you can look at on the left,
   what moves money on the right. */
.actionbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
             padding: 12px 14px; border-top: 1px solid var(--line); }
.actionbar .right { margin-left: auto; display: flex; flex-wrap: wrap; gap: 8px; }
.actionbar a, .actionbar button:not([type=submit]) {
  display: inline-block; padding: 6px 12px; border: 1px solid var(--line);
  border-radius: 5px; background: var(--field); color: var(--text);
  font: inherit; font-size: 13px; text-decoration: none; cursor: pointer;
}
.actionbar a:hover, .actionbar button:not([type=submit]):hover {
  border-color: var(--accent); background: var(--hover);
}
.actionbar button.danger:hover { border-color: var(--warn); color: var(--warn);
                                 background: var(--warn-bg); }

/* The narrow shell is the LAST section of this file, under THE SHELL ON A
   SMALL SCREEN. It has to come after every rule it overrides, and those run
   all the way to the bottom -- `.topnav .wordmark-mark` is the final line in
   the file and the same specificity as the rule that shrinks it. */

/* ------------------------------------------------------------ the tree lines
   Indentation alone tells you a row is nested; it does not tell you WHAT it
   hangs off, and at three levels with several branches open that is the only
   question worth answering. The rails carry a glow because the chain is the
   thing this product is about -- it should read as the structure, not as
   whitespace.

   Every rail is drawn from `guides` and `is_last`, computed in a depth-first
   walk. Drawing them from indentation alone cannot work: whether a line keeps
   running down past a row depends on whether an ANCESTOR has siblings still to
   come, which the row itself does not know. */

td.tree { display: flex; align-items: stretch; padding-top: 0; padding-bottom: 0; }
td.tree .rails { display: flex; align-self: stretch; flex: 0 0 auto; }
td.tree .name { display: flex; align-items: center; padding: 9px 0;
                min-width: 0; }

td.tree i { position: relative; flex: 0 0 22px; width: 22px; }

/* A vertical running the full height: an ancestor above still has siblings
   coming, so its line passes this row on the way down. */
td.tree i.line::before,
td.tree i.tee::before,
td.tree i.end::before {
  content: ""; position: absolute; left: 10px; top: 0; width: 2px;
  background: var(--rail); box-shadow: 0 0 6px var(--rail-glow);
}
td.tree i.line::before { bottom: 0; }
td.tree i.tee::before  { bottom: 0; }
/* The last child closes: the vertical stops at the elbow. */
td.tree i.end::before  { height: 50%; border-radius: 0 0 2px 2px; }

/* The horizontal reaching out to the row itself. */
td.tree i.tee::after,
td.tree i.end::after {
  content: ""; position: absolute; left: 10px; top: 50%; right: 2px;
  height: 2px; background: var(--rail); box-shadow: 0 0 6px var(--rail-glow);
  border-radius: 2px;
}

/* Your own row is the top of everything you can see, so it carries the glow
   at full strength and the rest fades beneath it. */
tr.self td.tree .name { font-weight: 700; }

/* The rate, editable where the tree is. Compact enough to sit in a table cell
   without the row growing around it -- a control that wraps onto a second line
   makes every row in the table taller than the one piece of data it carries. */
td.ratecell { white-space: nowrap; }
td.ratecell form { display: flex; align-items: center; gap: 5px;
                   flex-wrap: nowrap; }
td.ratecell input { min-width: 0; width: 58px; padding: 4px 6px;
                    font-size: 13px; text-align: right; }
td.ratecell .pct { color: var(--muted); font-size: 13px; }
/* Secondary, deliberately: there is one of these on every row, and a table
   where every row carries a gold button has nothing left to emphasise. */
/* Selected by POSITION, not by type. The script flips the button between
   submit and button as it locks and unlocks, and a selector keyed on
   [type=submit] silently drops the styling the moment it becomes Edit. */
td.ratecell button {
  padding: 4px 10px; font-size: 12px; font-weight: 600; border-radius: 5px;
  background: var(--field); border: 1px solid var(--line); color: var(--text);
  font-family: inherit; cursor: pointer; min-width: 46px;
}
td.ratecell button:hover {
  border-color: var(--accent); background: var(--hover); filter: none;
}

/* The name column has to win its width against the Operation column, or the
   tree indents into a two-line wrap and stops reading as a tree at all. */
td.tree .name { white-space: nowrap; }
/* Only the tree column, not every table's first column -- as `th:first-child`
   this squeezed unrelated tables until "Drawer 1" wrapped onto two lines. */
td.tree { width: 1%; }

/* A locked rate reads as the figure it is, not as a form waiting for input.
   Same shape as the read-only rows one line up, so a table of rates looks like
   a table of rates rather than a row of boxes. */
td.ratecell input.locked {
  background: none; border-color: transparent; box-shadow: none;
  color: var(--text); font-weight: 600; cursor: default;
}
td.ratecell input.locked:focus { border-color: transparent; box-shadow: none; }

/* Edit stays out of the way until you reach for it.
   The % and the button occupy ONE slot of fixed width, so the swap moves
   nothing: a table of rates reads as figures, and the control appears where
   the cursor already is. focus-within as well as hover, or the control is
   unreachable from a keyboard. */
td.ratecell .slot {
  position: relative; display: inline-flex; align-items: center;
  justify-content: center; min-width: 48px; height: 26px;
}
td.ratecell .slot .pct { transition: opacity .12s ease; }
td.ratecell .slot button { position: absolute; inset: 0; width: 100%;
                           transition: opacity .12s ease; }

/* Unlocked is the editing state: the button is the point, so it is always
   there and the % steps aside. */
td.ratecell form:not(.is-locked) .pct { opacity: 0; }

td.ratecell form.is-locked button { opacity: 0; pointer-events: none; }
td.ratecell form.is-locked:hover button,
td.ratecell form.is-locked:focus-within button { opacity: 1; pointer-events: auto; }
td.ratecell form.is-locked:hover .pct,
td.ratecell form.is-locked:focus-within .pct { opacity: 0; }

/* A rate that has never been set rests as words, not as an empty box. Without
   the script the box is there and this is not, which is the working form the
   server always rendered. */
td.ratecell .norate { display: none; font-size: 13px; }
td.ratecell form.is-locked.is-empty .norate { display: inline; }
td.ratecell form.is-locked.is-empty input[name="percent"] { display: none; }
td.ratecell form.is-locked.is-empty .pct { display: none; }

form label .muted { display: block; margin-top: 4px; font-size: 12px; }

/* The receipt header, shown as it prints. Narrow and centred on purpose --
   guessing what a receipt will look like from a form is how an address ends up
   wrong on every slip for a month. */
.receipt {
  display: flex; flex-direction: column; gap: 2px; align-items: center;
  max-width: 280px; padding: 14px 16px; margin: 0;
  background: var(--sunken); border: 1px dashed var(--line); border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; text-align: center;
}

.receipt .rule { width: 100%; border-top: 1px dashed var(--line); margin: 4px 0; }
.receipt .line { display: flex; justify-content: space-between; width: 100%; }

/* What actually comes out of the printer: the slip, and nothing around it. */
@media print {
  .topbar, .sidebar, .actionbar, .flash, .sheet > header,
  .nav-open, .navscrim { display: none; }
  body, .shell main, .sheet, .sheet .inner { background: #fff; color: #000;
                                             margin: 0; padding: 0; border: 0; }
  .receipt { border: 0; background: #fff; color: #000; max-width: none; }
}

/* The sign-out prompt. A native <dialog>, so Escape and focus are the
   browser's problem rather than ours. */
dialog {
  border: 1px solid var(--line); border-radius: 10px; padding: 0;
  /* 620, not 420. The reference's panels run about a third of the screen and
     ours ran a fifth, which is what made them read as cramped next to it --
     the New Customer form in particular put two fields on a line inside a box
     narrow enough that each was barely wider than its own label. `width: 92%`
     still holds it inside a small till screen. */
  background: var(--panel); color: var(--text); max-width: 620px; width: 92%;
  box-shadow: 0 24px 60px rgb(0 0 0 / .55);
}
dialog::backdrop { background: rgb(8 6 12 / .6); }
dialog h2 { margin: 0; padding: 14px 18px 0; font-size: 16px; color: var(--text);
            text-transform: none; letter-spacing: 0; }
dialog p { margin: 8px 18px 0; color: var(--muted); }
/* Inside a form the horizontal inset comes from the form, so this only sets
   the vertical rhythm -- an inline style here used to override the margin
   above and take the side inset with it. */
dialog form .intro { margin: 0 0 12px; }
.intro { margin: 0 0 12px; }
/* THE BODY SITS INSET LIKE THE TITLE AND THE BUTTONS.
   `dialog` has no padding of its own and each part paid for its own: the
   heading 18px, the actions row 18px, and the form between them nothing at
   all -- so the fields ran flush to the panel edge with inset text above and
   inset buttons below, which is what made the New Customer dialog look like
   its contents had spilled. */
dialog form:not(.dialog-dismiss) { padding: 0 18px; }

dialog .dialog-actions {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  /* Pulled back out of the form's inset, or the divider stops short of the
     panel edge and the buttons sit 36px in. */
  padding: 16px 18px; margin: 14px -18px 0;
  border-top: 1px solid var(--line);
}
dialog .dialog-actions button {
  padding: 8px 14px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--field); color: var(--text); font: inherit; cursor: pointer;
}
/* THE PRIMARY ACTION IS GOLD, however it is marked up.
   This used to match `form:first-child button` only, which is how the
   sign-out dialog is built -- so Purchase and Create, which are bare submits,
   were painted the same grey as Cancel beside them. The one button that
   commits money looked exactly like the one that abandons it. */
dialog .dialog-actions form:first-child button,
dialog .dialog-actions > button[type="submit"] {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
  font-weight: 700;
}
dialog .dialog-dismiss { position: absolute; top: 8px; right: 10px; }
dialog .dialog-dismiss button {
  background: none; border: 0; color: var(--muted); font-size: 20px;
  cursor: pointer; line-height: 1;
}

/* Purchase and Redeem side by side: they are the two things a counter does,
   and stacking them makes the second look like an afterthought. */
.counter-acts { display: flex; gap: 22px; flex-wrap: wrap; }

/* Secondary submits. A screen has ONE primary action; the others do real work
   too, and a row where everything is gold has nothing left to point at. Named
   rather than positional, because which one is primary is a judgement about
   the screen and not about the order of the markup. */
button.secondary, button.secondary:hover {
  background: var(--field); border: 1px solid var(--line); color: var(--text);
  font-weight: 600; filter: none;
}
button.secondary:hover { border-color: var(--accent); background: var(--hover); }
button.secondary.danger:hover { border-color: var(--warn); color: var(--warn);
                                background: var(--warn-bg); }

/* A pinned notice. Warn-coloured rather than the usual green or red: it is
   neither good news nor an error, it is a thing you are expected to have
   read. */
.flash.pinned {
  display: flex; gap: 12px; align-items: baseline;
  background: var(--warn-bg); border: 1px solid var(--warn); color: var(--text);
}
.flash.pinned a { color: var(--warn); }

/* ------------------------------------------------------------ game tiles
   A catalogue is looked at rather than read, so this is the one screen in
   the terminal that is not a table. Auto-fill rather than a fixed column
   count: the same markup has to sit on a shop's 1366-wide till and on a
   laptop without either one getting a scrollbar or a half-empty row. */
.tiles {
  display: grid; gap: 14px; padding: 0 14px 16px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.tile { position: relative; background: var(--field);
        border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.tile img { display: block; width: 100%; aspect-ratio: 4 / 3;
            object-fit: cover; }
/* The stand-in when there is no art. An initial rather than a broken-image
   icon: it says "no picture yet", not "this is broken". */
.tile .noart {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 4 / 3; background: var(--sunken-2);
  font-size: 40px; font-weight: 800; color: var(--muted);
}
.tile .caption { display: flex; flex-direction: column; gap: 2px;
                 padding: 9px 11px 11px; }
.tile .caption .muted { font-size: 12px; }
/* Coming soon reads as "not yet" rather than "broken": the tile is dimmed
   and the flag says which, because a greyed-out thing with no explanation is
   the most annoying kind of screen. */
.tile.soon img, .tile.soon .noart { opacity: .45; }
.tile .flag {
  position: absolute; top: 8px; right: 8px; padding: 3px 8px;
  border-radius: 999px; background: var(--warn); color: var(--on-accent);
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
}

/* ------------------------------------------------------- clickable rows
   The whole row opens the customer, but it is still ONE real link: the
   anchor in the first cell, stretched over the row by a pseudo-element. Not
   a click handler -- this keeps the keyboard, the middle click and
   open-in-new-tab, and it works with the script blocked. */
tr.rowlink { position: relative; }
tr.rowlink:hover { background: var(--row-hover); }
tr.rowlink td:first-child a { color: var(--text); text-decoration: none; }
tr.rowlink td:first-child a::after {
  content: ""; position: absolute; inset: 0;
}
/* The focus ring has to land on the ROW, because the anchor's own box is just
   the name -- a keyboard user tabbing through would otherwise see a highlight
   that does not match what Enter is about to open. */
tr.rowlink:focus-within { background: var(--row-focus); }
/* The row that IS the customer on the panel above. The reference tints it,
   and without something a clerk scanning a list of near-identical names has
   no way to see which one they are acting on. */
tr.rowlink.chosen { background: var(--row-focus); }
tr.rowlink.chosen td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr.rowlink.chosen td { font-weight: 600; }
tr.rowlink:focus-within td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr.rowlink td:first-child a:focus { outline: none; }

/* A dialog that holds a form wants its fields full width, like any other
   stacked form on the site. */
dialog form label { max-width: none; }

/* ------------------------------------------------- expandable nav groups
   <details>, so this is a disclosure widget the browser already knows how
   to open, focus and announce. The only work here is hiding the default
   triangle and putting our own chevron on the right. */
.sidebar .navgroup > summary {
  display: flex; align-items: center; gap: 10px;
  white-space: nowrap;
  padding: 9px 14px; color: var(--muted); cursor: pointer;
  list-style: none; user-select: none;
}
.sidebar .navgroup > summary::-webkit-details-marker { display: none; }
.sidebar .navgroup > summary:hover { color: var(--text); }
.sidebar .navgroup > summary::after {
  content: ""; margin-left: auto; width: 7px; height: 7px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(-45deg); transition: transform .12s ease;
}
.sidebar .navgroup[open] > summary::after { transform: rotate(45deg); }
.sidebar .navgroup[open] > summary { color: var(--text); }
/* Children indent so the group reads as a container rather than as more
   siblings. The icon column stays aligned with the leaves above it. */
.sidebar .navgroup > a { padding-left: 34px; font-size: 14px; }
.sidebar .navgroup > a svg { width: 13px; height: 13px; }

/* ----------------------------------------------------------- the drawer
   Two boxes and a keypad, side by side, as the reference has it. It wraps
   rather than shrinks: a till at 1024 puts the pad under the boxes instead
   of squeezing both into something nobody can hit. */
.drawerpad { display: flex; flex-wrap: wrap; gap: 28px; padding: 16px 14px; }

.cashform {
  display: grid; grid-template-columns: auto auto 1fr auto;
  gap: 10px 8px; align-items: center; margin: 0;
  /* The rows are one control tall each. Without this the grid stretches them
     to fill whatever the tallest row wants and the two sit half a screen
     apart. */
  grid-auto-rows: min-content;
}
/* `form label` is display:block with a bottom margin, which is right for the
   stacked forms everywhere else and wrong inside a grid row. */
.cashform label { display: contents; }
.cashform > label { display: block; margin: 0; color: var(--muted);
                    font-size: 13px; letter-spacing: .04em; }
.cashform .cur { color: var(--muted); }
.cashform input { width: 100%; min-width: 120px; text-align: right;
                  font-variant-numeric: tabular-nums; }
.cashform button[type=submit] { min-width: 104px; }
/* THE COUNT AT CLOSE. A row: label, currency, figure, button -- the same
   shape as the drawer's own cash form, so counting out reads like the two
   things above it rather than like a new kind of screen. */
.countform { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
             margin: 0 0 10px; }
.countform label { margin: 0; color: var(--muted); font-size: 13px; }
.countform input { width: 130px; text-align: right;
                   font-variant-numeric: tabular-nums; }

/* The reason is text, read left to right like any sentence. */
.cashform input.cashform-memo { text-align: left; min-width: 0;
                                font-variant-numeric: normal; }

.keypad {
  display: grid; grid-template-columns: repeat(4, 52px);
  gap: 6px; align-content: start;
}
.keypad button {
  height: 40px; padding: 0; border: 1px solid var(--line); border-radius: 6px;
  background: var(--field); color: var(--text); font: inherit; font-size: 15px;
  cursor: pointer;
}
.keypad button:hover { border-color: var(--accent); background: var(--hover); }
.keypad button:active { transform: translateY(1px); }
.keypad button.wide { grid-column: span 2; }

/* ------------------------------------------------------------- the tally
   Label left, figure right, in one column. Not the two-column .detail grid
   the other screens use: these are a running sum somebody reads downwards,
   and pairing them across would put "Fills" beside "Total Redeem". */
.tally { padding: 4px 14px 14px; max-width: 520px; }
.tally > div { display: flex; align-items: baseline; gap: 12px;
               padding: 5px 0; }
.tally .k { color: var(--muted); }
.tally .v { margin-left: auto; font-variant-numeric: tabular-nums; }
.tally .v::before { content: "$"; opacity: .55; margin-right: 1px; }
.tally .strong { font-weight: 700; color: var(--text); }
/* Money arriving reads calm, money leaving reads warm. Not red/green: this
   is a till, not an alarm, and a withdraw is an ordinary daily act. */
.tally .in { color: var(--text); }
.tally .out { color: var(--warn); }
.tally .rule { border-top: 1px dashed var(--line); padding: 0; margin: 8px 0; }

/* --------------------------------------------------------- theme switch
   Three buttons rather than a select: one tap on a touchscreen, and the
   current choice is visible without opening anything. */
.themeswitch { display: inline-flex; margin: 0; border: 1px solid var(--line);
               border-radius: 999px; overflow: hidden; }
.themeswitch button {
  padding: 4px 11px; border: 0; border-radius: 0; background: transparent;
  color: var(--muted); font: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer;
}
.themeswitch button:hover { color: var(--text); background: var(--hover); }
.themeswitch button.on, .themeswitch button.on:hover {
  background: var(--accent); color: var(--on-accent);
}

/* ------------------------------------------------- crossfading the theme
   ONLY WHILE SWITCHING. The class goes on for the length of the fade and
   comes straight off again, so hovering a button at any other time is
   instant -- a permanent colour transition on every element makes every
   hover feel like it is lagging behind the cursor.

   It is also why this is the one place !important is right: it has to beat
   whatever transition a control already has for its own hover. */
html.theming, html.theming *, html.theming *::before, html.theming *::after {
  transition: background-color 220ms ease, border-color 220ms ease,
              color 220ms ease, fill 220ms ease, box-shadow 220ms ease
              !important;
}
/* Somebody who has asked for less motion has asked for this too. */
@media (prefers-reduced-motion: reduce) {
  html.theming, html.theming *, html.theming *::before, html.theming *::after {
    transition: none !important;
  }
}

/* A dialog holding a table wants more room than one holding two fields. */
dialog.wide { max-width: min(760px, 92vw); width: 760px; }
dialog.wide table { width: 100%; }
dialog.wide .detail { grid-template-columns: 1fr; }

/* --------------------------------------------- the customer screen, shaped
   like the reference. Colours are ours; the arrangement is theirs. */

/* Search on the left, New Customer hard right, one line. */
.searchrow { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.searchrow > .btn { margin-left: auto; }

.pagesize { font-size: 13px; }
.pagesize select { padding: 4px 26px 4px 8px; background-position: right 8px center; }

/* A pager reads as one control rather than a row of links. */
.pager { display: inline-flex; gap: 4px; }
.pager a {
  min-width: 28px; padding: 4px 8px; text-align: center;
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--field); color: var(--muted); text-decoration: none;
  font-size: 13px;
}
.pager a:hover { border-color: var(--accent); color: var(--text); }
.pager a.on, .pager a.on:hover {
  background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); font-weight: 700;
}

/* A FILLED HEADER BAR, as the reference has. Ours has been an underlined row
   of muted labels, which is quieter than a screen somebody scans at speed
   wants -- the header is the thing you find the column by. */
.sheet table thead th {
  background: var(--sunken-2); color: var(--text);
  border-bottom: 1px solid var(--line);
}
/* Striping, for the same reason: a long list of near-identical names is
   easier to keep your place in when the rows alternate. Applied to the
   selectable lists only, not to every two-row table on the site. */
table tbody tr.rowlink:nth-child(even) { background: var(--sunken); }
table tbody tr.rowlink:nth-child(even):hover { background: var(--row-hover); }
table tbody tr.rowlink.chosen, table tbody tr.rowlink.chosen:nth-child(even) {
  background: var(--row-focus);
}

/* ------------------------------------------------- the money dialogs
   What the account holds, before the panel asks for anything. The reference
   opens with these and it is right: a clerk about to redeem should not have
   to remember a balance from the screen behind the popup. */
/* The rules between the facts run the full width of the panel, deliberately --
   they separate rows rather than decorating a box. THE TEXT still sits inset
   like everything else, so the padding is on the row and not a margin on the
   list: "Customer ID" and its figure used to touch the panel edges while the
   heading above them and the fields below were both 18px in. */
.dialog-facts { margin: 0 0 14px; }
.dialog-facts div {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; padding: 7px 18px; border-top: 1px solid var(--line);
}
.dialog-facts div:first-child { border-top: 0; }
.dialog-facts dt { color: var(--muted); font-size: 13px; }
.dialog-facts dd {
  margin: 0; font-weight: 700; font-variant-numeric: tabular-nums;
}

.amount-row { display: flex; align-items: flex-end; gap: 10px; }
.amount-row label { flex: 1; margin: 0; }
.amount-row input { font-size: 22px; font-variant-numeric: tabular-nums; }
/* CLEAR sits AGAINST the amount, matched to the input height so the pair
   reads as one control rather than a field with a stray button after it.

   IT IS NOT PAINTED WARN ANY MORE. Orange (#d98b4a) beside the gold accent
   (#b9a24a) is two warm tones a shade apart fighting each other, and it put
   the loudest colour in the dialog on the button that throws work away while
   the one that takes money sat grey. Resetting a field somebody has not
   submitted yet is not destructive -- it is an undo. It reads as a secondary
   control now, and the gold belongs to the action that commits. */
.amount-row button[data-amount-clear] {
  /* flex-end, not stretch: the row is as tall as the label PLUS the input,
     and a stretched button spans both and towers over the field it belongs
     to. */
  flex: 0 0 auto; align-self: flex-end; padding: 15px 18px;
  border-color: var(--line); color: var(--text); font-weight: 700;
  letter-spacing: .06em;
}
.amount-row button[data-amount-clear]:hover {
  background: var(--hover); color: var(--text); border-color: var(--muted);
}

/* Denominations, not a number pad. Somebody hands over a twenty and a five;
   these add up, and CLEAR starts again. The keypad stays on the Drawer
   screen, where a figure really is typed. */
.denoms {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px;
  margin: 12px 0 4px;
}
.denoms button {
  padding: 14px 0; font-size: 15px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* THE CLASH, and it was not a choice of colour -- it was the absence of one.
   These buttons and CLEAR set padding and type and never a background, so the
   browser painted its own: a flat neutral grey, in a panel that is otherwise
   purple-black. Next to Cancel, which IS themed, one dialog had two different
   kinds of button in it. They get the same secondary treatment as every other
   button in the app now. */
.denoms button,
.amount-row button[data-amount-clear] {
  background: var(--field); border: 1px solid var(--line); color: var(--text);
  border-radius: 6px; cursor: pointer;
}
.denoms button:hover {
  background: var(--hover); border-color: var(--muted);
}
@media (max-width: 520px) {
  .denoms { grid-template-columns: repeat(3, 1fr); }
}

/* Two fields on one line where they belong together -- first and last name,
   birthday and phone. Stacks on a narrow terminal. */
.field-row { display: flex; gap: 14px; }
.field-row > label { flex: 1; min-width: 0; }
@media (max-width: 640px) { .field-row { flex-direction: column; gap: 0; } }

/* ------------------------------------------------------------ the receipt
   A till roll: narrow, centred, sections divided by dashes. The dashes are a
   border rather than a row of hyphens, so a screen reader is not asked to say
   forty of them out loud. */
dialog.slip-dialog { max-width: 340px; padding: 0; }
dialog.slip-dialog .slip {
  padding: 22px 26px 26px; text-align: center;
  font-variant-numeric: tabular-nums;
}
.slip h2 { margin: 0 0 4px; font-size: 16px; }
.slip .where, .slip .who { margin: 0; font-size: 12px; color: var(--muted); }
.slip .kind {
  margin: 14px 0; padding: 8px 0; font-weight: 800; letter-spacing: .16em;
  border-top: 2px dashed var(--line); border-bottom: 2px dashed var(--line);
}
.slip .cid { margin: 0 0 14px; display: flex; flex-direction: column; gap: 2px; }
.slip .cid span { font-size: 11px; letter-spacing: .1em; color: var(--muted); }
.slip .cid strong { font-size: 22px; letter-spacing: .06em; }

.slip .rows { margin: 0 0 12px; padding-top: 10px;
              border-top: 1px dashed var(--line); text-align: left; }
.slip .rows div { display: flex; justify-content: space-between; gap: 12px;
                  padding: 3px 0; font-size: 13px; }
.slip .rows dt { color: var(--muted); }
.slip .rows dd { margin: 0; font-weight: 700; }
.slip .rows .note { justify-content: flex-end; }
.slip .rows .note dd { font-weight: 400; font-size: 11px; color: var(--muted); }
.slip .rows dd .muted { font-weight: 400; font-size: 11px; }

.slip .when { margin: 12px 0 0; padding-top: 10px; font-size: 12px;
              color: var(--muted); border-top: 1px dashed var(--line); }
.slip .qr { margin: 14px auto 6px; width: 132px; }
.slip .qr svg { width: 100%; height: auto; display: block; }

/* PRINTS AS ITSELF. Without this the Print button gives a page of web app
   with a slip somewhere in the middle of it. */
@media print {
  body > *:not(dialog), dialog .dialog-dismiss, .no-print { display: none !important; }
  dialog.slip-dialog { max-width: none; border: 0; box-shadow: none; }
  dialog.slip-dialog::backdrop { background: transparent; }
  .slip { color: #000; }
  .slip .qr svg rect, .slip .qr svg path { fill: #000 !important; }
}

/* A REPORT THAT PRINTS AS A PAGE. The rule above hides everything that is not
   a dialog, which is right for a slip and prints a blank sheet for a report.
   A page that is itself the document (End of Day, audit P8) opts in with
   body.print-page: its content prints, the chrome around it does not. */
@media print {
  body.print-page > .shell-body { display: block !important; }
  body.print-page .sidebar, body.print-page .topbar,
  body.print-page .flash, body.print-page .no-print { display: none !important; }
  body.print-page main { padding: 0 !important; }
  body.print-page, body.print-page .sheet { background: #fff !important;
                                            color: #000 !important; }
}

/* The four quick ranges. A clerk picks "Today" a hundred times a week and
   types a date almost never, so these are the biggest targets on the row. */
.quick-range { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
               margin-top: 12px; }
.quick-range .btn { padding: 9px 16px; }
.quick-range .muted { font-size: 12px; margin-left: 4px; }

/* Role as two radios rather than a dropdown: two options is a choice you
   should be able to see without opening anything, and on a touch terminal a
   radio is a target while a select is two taps and a scroll. */
.roles { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px;
         margin: 14px 0 0; }
.roles legend { padding: 0 6px; font-size: 12px; color: var(--muted);
                letter-spacing: .06em; text-transform: uppercase; }
.roles .radio { display: flex; gap: 10px; align-items: flex-start;
                padding: 8px 4px; cursor: pointer; max-width: none; }
.roles .radio + .radio { border-top: 1px solid var(--line); }
.roles .radio input { margin-top: 3px; }
.roles .radio strong { display: block; font-size: 14px; }
.roles .radio em { font-style: normal; font-size: 12px; color: var(--muted); }

/* A table with six columns on a till screen. Scrolls inside its own box
   rather than pushing the page sideways -- a horizontal scrollbar on the
   whole document moves the sidebar out from under the finger going for it. */
.sheet table { display: block; overflow-x: auto; max-width: 100%; }
.sheet table thead, .sheet table tbody { display: table; width: 100%; }

/* The play site, ready to hand out. */
.share { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; }
.share-url { flex: 1 1 320px; display: flex; gap: 8px; align-items: flex-end; }
.share-url label { flex: 1; margin: 0; max-width: none; }
/* The rule that stretches an input is `form label input`, and this block is
   not inside a form -- there is nothing here to submit. So it says so itself
   rather than inheriting from a selector that does not reach it. */
.share-url input { display: block; width: 100%; margin-top: 4px; }
.share-url button { white-space: nowrap; }
.share-qr { display: flex; flex-direction: column; align-items: center;
            gap: 6px; }
.share-qr svg { width: 120px; height: auto; display: block; }
.share-qr .muted { font-size: 11px; }

/* ------------------------------------------------------------ the wordmark */
/* TRIPLE in gold struck like a stacked bar, the G money green, the 's black.
   One definition, loaded by every surface -- see templates/_brand.html.

   The gold is a clipped gradient rather than a flat colour because a bar is
   not one yellow: it is a lit top face, a bright edge where the light catches,
   and a dark underside. The hard stops are what read as metal; a smooth ramp
   reads as a highlighter pen. The seam lines layered over it are the joins
   between stacked bars.

   FALLBACK MATTERS HERE. background-clip:text with transparent fill shows
   NOTHING at all where it is unsupported -- an invisible brand. The colour is
   declared solid gold first and only then clipped, and the clip is inside a
   @supports, so the worst case is flat gold rather than a hole in the page. */
.wordmark {
  font-weight: 900;
  letter-spacing: .04em;
  white-space: nowrap;
  /* The gradient cannot cast a text-shadow once it is clipped, so depth comes
     from a filter on the whole mark. */
  filter: drop-shadow(0 1px 0 rgb(0 0 0 / .55));
}

/* AN INHERITED TEXT-SHADOW IS FATAL TO CLIPPED TEXT, and it is not obvious.
   The lobby marquee draws its title with an eight-way shadow outline, which
   these spans inherit. A shadow paints BEHIND the glyph -- and the glyph's own
   fill is transparent here, because the gold is a clipped gradient. So the
   outline showed straight through the letterforms and filled them solid: the
   mark rendered as dark blobs on the rail with no gold in it at all.

   Cancelled on the mark itself rather than on the rail, because the rail is
   right to outline its own text and every other surface that sets a shadow
   would hit this the same way. The wrapper's drop-shadow does the job a
   text-shadow would have, and works on clipped text because it filters the
   painted result rather than the glyph. */
.wordmark .wm-triple,
.wordmark .wm-g,
.wordmark .wm-s { text-shadow: none; }

.wordmark .wm-triple { color: #fdc10a; }

@supports (background-clip: text) or (-webkit-background-clip: text) {
  .wordmark .wm-triple {
    background-image:
      /* the seams between stacked bars -- one hairline per bar, and faint,
         because at rail size this mark is 14px tall and anything heavier
         reads as dirt on the letters rather than as an edge */
      repeating-linear-gradient(180deg,
        rgb(60 34 0 / .30) 0 1px, rgb(255 255 255 / 0) 1px 33.4%),
      /* and the metal: lit top face, a hard catch of light across the
         middle, dark underside. The abrupt stop at 52% is the whole effect --
         a smooth ramp between them reads as plastic. */
      linear-gradient(180deg,
        #fff9cf 0%, #fde522 14%, #fdd914 30%, #b87901 46%,
        #fff4ad 52%, #fdae01 60%, #8a5a00 80%, #fde522 93%, #c58601 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* Money green: the ink, not a spring leaf. Same strike as the gold so the two
   halves of the mark look struck by the same press. */
.wordmark .wm-g { color: #01ae02; }

@supports (background-clip: text) or (-webkit-background-clip: text) {
  .wordmark .wm-g {
    background-image:
      linear-gradient(180deg,
        #8ef08d 0%, #01c202 16%, #01ae02 40%,
        #016a01 58%, #018401 72%, #2ecb2b 88%, #017601 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* Black, and it has to survive a near-black arcade.

   This is the hard third of the mark. Black on #12101a is a hole, and the
   first attempt -- three stacked text-shadows -- lit the letters from every
   side at once: at 56px a hollow outline, and at rail size a grey smudge with
   no black left in it.

   A STROKE BEHIND THE FILL instead. `paint-order` puts the light edge under
   the black rather than around it, so the letterform keeps a solid black core
   and gains a rim only where it meets the background. It reads as black with
   an edge, which is what it is, instead of as an outline of nothing. Scaled
   in em so the rim thins with the type rather than swallowing it at 14px. */
.wordmark .wm-s {
  color: #0a0a0c;
  -webkit-text-stroke: .055em rgb(255 255 255 / .92);
  paint-order: stroke fill;
}

/* Where paint-order is unsupported the stroke would paint OVER the fill and
   erase the black entirely, so those browsers get the plain letters and a
   soft halo -- less handsome, still black, never invisible. */
@supports not (paint-order: stroke) {
  .wordmark .wm-s {
    -webkit-text-stroke: 0;
    text-shadow: 0 0 2px rgb(255 255 255 / .85);
  }
}

/* Daylight needs none of it: black on a white till is simply black. */
:root[data-theme="light"] .wordmark .wm-s {
  -webkit-text-stroke: 0;
  text-shadow: none;
}

/* The painted mark, on the screens that have room for it. Capped in ch rather
   than px so it tracks the form it sits above instead of the viewport. */
.wordmark-hero {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  margin: 0 auto 6px;
}


/* ------------------------------------------------------- the front door */
/* Every sign-in screen -- till, agent console and player site -- wears the
   brand's own wallpaper. `.login` is the console and POS container, `.signin`
   the player one; they are two class names for the same job and the wallpaper
   belongs to both.

   NOT THE SEASONAL SCENE. The play blueprint hands every template a backdrop,
   which is right in the lobby and wrong here: two full-bleed pictures behind
   one small form is one too many, so the login route passes None and this
   takes the ground instead.

   Fixed rather than scrolled, because a form can overflow a short phone and a
   wallpaper that slides up with it reads as a page falling apart. Drawn
   `cover` because the source is square and a landing page is not. */
.login::before, .signin::before,
.login::after,  .signin::after {
  content: ""; position: fixed; inset: 0; pointer-events: none;
}

.login::before, .signin::before {
  z-index: -2;
  background: url("brand/wallpaper.ad5a310c.webp") center / cover no-repeat;
}

/* The scrim, and it is the lesson the lobby backdrop already taught once: the
   mosaic is bright and saturated, the panel above it is the thing being read,
   and the art gets knocked back rather than the text being shouted. */
.login::after, .signin::after {
  z-index: -1;
  background:
    radial-gradient(120% 100% at 50% 42%,
                    rgb(2 8 3 / .18) 18%, rgb(2 8 3 / .80) 100%),
    linear-gradient(rgb(3 10 4 / .46), rgb(3 10 4 / .46));
}

/* THE PANEL IS UNCONDITIONAL. It used to apply only when a backdrop happened
   to be present, which meant contrast was defended in one of two states --
   and the state it was not defended in shipped, with --muted labels straight
   on top of sunlit water. Every sign-in screen has a picture behind it now,
   so every one of them gets furniture to sit on. --panel rather than a
   literal, so the till still turns white in daylight. */
.login, .signin {
  background:
    linear-gradient(180deg, rgb(255 255 255 / .10) 0%,
                            rgb(255 255 255 / .02) 34%,
                            rgb(0 0 0 / .30) 100%),
    var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 28px 24px 24px;
  box-shadow: 0 18px 50px rgb(0 0 0 / .55);
}


/* --------------------------------------------------------- falling money */
/* Weather for the front door. Decoration only: the markup is aria-hidden and
   this file decides everything about it, so the page carries fourteen empty
   <i> elements and no inline styles.

   TRANSFORM AND OPACITY ONLY. Both are composited, so fourteen notes cost no
   layout and no repaint -- which is the difference between atmosphere and a
   sign-in screen that stutters on the phone most of these are opened on.

   Negative animation-delay starts each note PART WAY DOWN. Without it every
   note begins above the fold together and the first eight seconds of the page
   are an empty sky, which is the one moment somebody is actually looking at
   it. */
.moneyfall {
  position: fixed; inset: 0; z-index: -1;
  overflow: hidden; pointer-events: none;
}

.moneyfall i {
  position: absolute; top: 0; display: block;
  /* aspect-ratio is per note: each was cut from the sheet at its own curl,
     and one shared ratio squashed four of the six. */
  background-size: 100% 100%;
  background-repeat: no-repeat;
  will-change: transform;
  animation-name: moneyfall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

/* Falls from above the fold to below it, drifting and turning on the way.
   112vh rather than 100 so a note is fully clear of the glass before it
   wraps -- at 100 they visibly vanish at the bottom edge. */
@keyframes moneyfall {
  from { transform: translate3d(0, -22vh, 0) rotate(0deg); }
  to   { transform: translate3d(var(--drift), 112vh, 0) rotate(var(--spin)); }
}

.moneyfall i:nth-child(1) {
  left: -1.3%; width: 169px; aspect-ratio: 220 / 182;
  background-image: url("brand/note-1.f1699909.webp");
  animation-duration: 13.2s; animation-delay: -14.0s;
  opacity: 0.89; filter: blur(0px);
  --spin: -177deg; --drift: 47px;
}
.moneyfall i:nth-child(2) {
  left: 5.7%; width: 176px; aspect-ratio: 220 / 99;
  background-image: url("brand/note-2.052629c1.webp");
  animation-duration: 20.3s; animation-delay: -19.6s;
  opacity: 0.92; filter: blur(0px);
  --spin: -189deg; --drift: -31px;
}
.moneyfall i:nth-child(3) {
  left: 13.0%; width: 209px; aspect-ratio: 220 / 156;
  background-image: url("brand/note-3.0d9873f4.webp");
  animation-duration: 16.9s; animation-delay: -13.5s;
  opacity: 1.0; filter: blur(0px);
  --spin: -180deg; --drift: -85px;
}
.moneyfall i:nth-child(4) {
  left: 20.1%; width: 110px; aspect-ratio: 220 / 149;
  background-image: url("brand/note-4.4ca9f486.webp");
  animation-duration: 21.8s; animation-delay: -15.1s;
  opacity: 0.69; filter: blur(2px);
  --spin: 306deg; --drift: 62px;
}
.moneyfall i:nth-child(5) {
  left: 26.9%; width: 156px; aspect-ratio: 220 / 169;
  background-image: url("brand/note-5.5a4ba4e7.webp");
  animation-duration: 14.3s; animation-delay: -18.0s;
  opacity: 0.85; filter: blur(1px);
  --spin: -146deg; --drift: -44px;
}
.moneyfall i:nth-child(6) {
  left: 31.0%; width: 207px; aspect-ratio: 220 / 113;
  background-image: url("brand/note-6.ef2e1031.webp");
  animation-duration: 14.2s; animation-delay: -3.8s;
  opacity: 1.0; filter: blur(0px);
  --spin: -164deg; --drift: 45px;
}
.moneyfall i:nth-child(7) {
  left: 39.0%; width: 183px; aspect-ratio: 220 / 161;
  background-image: url("brand/note-7.81ab398c.webp");
  animation-duration: 19.2s; animation-delay: -18.0s;
  opacity: 0.94; filter: blur(0px);
  --spin: 219deg; --drift: -6px;
}
.moneyfall i:nth-child(8) {
  left: 44.4%; width: 125px; aspect-ratio: 220 / 72;
  background-image: url("brand/note-8.4659bd06.webp");
  animation-duration: 14.4s; animation-delay: -22.0s;
  opacity: 0.74; filter: blur(1px);
  --spin: 412deg; --drift: 11px;
}
.moneyfall i:nth-child(9) {
  left: 50.0%; width: 110px; aspect-ratio: 220 / 334;
  background-image: url("brand/note-9.5c9bda63.webp");
  animation-duration: 19.2s; animation-delay: -15.7s;
  opacity: 0.69; filter: blur(2px);
  --spin: -276deg; --drift: -45px;
}
.moneyfall i:nth-child(10) {
  left: 56.5%; width: 121px; aspect-ratio: 220 / 332;
  background-image: url("brand/note-10.7202e106.webp");
  animation-duration: 22.3s; animation-delay: -3.7s;
  opacity: 0.73; filter: blur(2px);
  --spin: -218deg; --drift: -83px;
}
.moneyfall i:nth-child(11) {
  left: 64.0%; width: 95px; aspect-ratio: 220 / 328;
  background-image: url("brand/note-11.648be034.webp");
  animation-duration: 18.6s; animation-delay: -7.0s;
  opacity: 0.64; filter: blur(2px);
  --spin: 265deg; --drift: -12px;
}
.moneyfall i:nth-child(12) {
  left: 69.2%; width: 141px; aspect-ratio: 220 / 333;
  background-image: url("brand/note-12.a21f9ea3.webp");
  animation-duration: 22.9s; animation-delay: -18.5s;
  opacity: 0.79; filter: blur(1px);
  --spin: 347deg; --drift: 13px;
}
.moneyfall i:nth-child(13) {
  left: 75.9%; width: 97px; aspect-ratio: 220 / 182;
  background-image: url("brand/note-1.f1699909.webp");
  animation-duration: 18.2s; animation-delay: -20.3s;
  opacity: 0.65; filter: blur(2px);
  --spin: -219deg; --drift: -39px;
}
.moneyfall i:nth-child(14) {
  left: 80.5%; width: 143px; aspect-ratio: 220 / 99;
  background-image: url("brand/note-2.052629c1.webp");
  animation-duration: 19.8s; animation-delay: -3.4s;
  opacity: 0.8; filter: blur(1px);
  --spin: 433deg; --drift: 41px;
}
.moneyfall i:nth-child(15) {
  left: 86.5%; width: 163px; aspect-ratio: 220 / 156;
  background-image: url("brand/note-3.0d9873f4.webp");
  animation-duration: 16.1s; animation-delay: -8.9s;
  opacity: 0.87; filter: blur(0px);
  --spin: -177deg; --drift: 34px;
}
.moneyfall i:nth-child(16) {
  left: 92.2%; width: 205px; aspect-ratio: 220 / 149;
  background-image: url("brand/note-4.4ca9f486.webp");
  animation-duration: 20.0s; animation-delay: -7.1s;
  opacity: 1.0; filter: blur(0px);
  --spin: -250deg; --drift: 35px;
}

/* SOMEBODY ASKED FOR THIS TO STOP. Motion sickness and vestibular disorders
   are the reason the setting exists, and a sign-in screen is not optional --
   it is the one page a player cannot skip. The notes stay, still, so the page
   keeps its composition without moving. */
@media (prefers-reduced-motion: reduce) {
  .moneyfall i { animation: none; }
}

/* The painted mark in chrome. Height-driven rather than width-driven: a rail
   has a height and the logo has to fit it, which is the opposite of the hero
   where the panel has a width and the logo fills it.

   The lock-up is stacked, so it needs roughly twice the height a one-line
   wordmark would. At the 40px first tried it was legible and cramped -- the
   rail was sized for text and the logo sat in it apologetically. The marquee
   is the one place on the player's screen the brand gets to be the brand, so
   it gets the room. */
.wordmark-mark {
  height: 46px;
  width: auto;
  display: block;
}

/* The till and the console are working screens read all day, and their nav is
   a strip above a table rather than a marquee. Smaller there on purpose. */
.topnav .wordmark-mark { height: 32px; }

/* ===========================================================================
   THE SHELL ON A SMALL SCREEN.

   pos.tripleg.vip is a counter terminal, and a counter terminal is a phone as
   often as it is a till: a manager checking a shift from the floor, a clerk
   with the store's tablet. It did not work on one, and it failed in three
   separate ways that all looked like the same fault.

   ONE. THE SIDEBAR WOULD NOT COLLAPSE. There was a rule to hide the labels at
   760 -- `.sidebar a span, .sidebar button span` -- and it named the two
   things a nav row can be. A GROUP HEADER is neither: `section()` in
   _shell.html renders `<summary><span>Reports</span></summary>`, and a
   <summary> is not an <a> and not a <button>. So five labels stayed, they
   carry `white-space: nowrap`, and a flex item's automatic minimum size is
   its min-content width -- which pinned the rail at 223px whatever
   `flex-basis: 56px` asked for. On a 390px phone the navigation took 57% of
   the screen and the customer panel got what was left.

   TWO. THE TOP BAR COULD NOT WRAP. `.topbar` was `display: flex` with no
   `flex-wrap`, carrying a wordmark and three `white-space: nowrap` pills. The
   row simply ran off the side, which makes the DOCUMENT scroll sideways --
   and a phone answers a document wider than itself by zooming out until it
   fits. That is what "the panels are cut off" actually was: not a clipped
   panel, a page rendered at 668px and shrunk to 390.

   THREE. THE LABEL:VALUE GRID went to one column at 900 and stopped there,
   still spending 44% of the row on a right-aligned label. At 390 that leaves
   about 180px for an email address.

   The desktop shell is untouched: every rule below is inside a query. The
   queries run WIDEST FIRST -- 1200 for the admin strip, then 760, then 560 --
   because two of them set the same properties and the narrower one has to be
   the later one to win. Written the other way round the phone asked for a
   28px wordmark and got the tablet's 32.
   =========================================================================== */

/* ------------------------------------------------------- the drawer button
   Drawn rather than illustrated: three background stripes on the checkbox
   itself. `appearance: none` is what lets a checkbox be painted at all, and
   painting the INPUT rather than wrapping it in a <label> is what keeps the
   control focusable -- see navtoggle() in _shell.html.

   Hidden by default and shown only at the phone breakpoint. Not `display:
   none` on a rule of its own that something else might have to undo: the
   element ships display:none here and the query below turns it on. */
.nav-open, .navscrim { display: none; }

.nav-open {
  -webkit-appearance: none; appearance: none;
  width: 38px; height: 32px; margin: 0; padding: 0;
  border: 1px solid var(--line); border-radius: 7px;
  background-color: var(--field);
  /* Three bars. A gradient rather than an SVG so it takes `currentColor` and
     therefore follows the theme -- a data URI cannot. */
  background-image: linear-gradient(currentColor, currentColor),
                    linear-gradient(currentColor, currentColor),
                    linear-gradient(currentColor, currentColor);
  background-size: 17px 2px;
  background-repeat: no-repeat;
  background-position: center 9px, center 15px, center 21px;
  color: var(--text); cursor: pointer;
}
.nav-open:checked { background-color: var(--hover); border-color: var(--accent); }

/* The tap-anywhere-else target. Under the drawer, over everything else. */
.navscrim {
  position: fixed; inset: 0; z-index: 30;
  background: rgb(4 10 5 / .55); cursor: pointer;
}

/* ============================================== the icon rail, 561 to 760
   A small tablet, or a phone on its side: room enough that taking the
   navigation off the page would be a loss, not room enough for the labels.
   This is the rule that was already here -- it now names the third thing a
   nav row can be. */
@media (min-width: 561px) and (max-width: 760px) {
  .sidebar {
    flex: 0 0 56px;
    /* WITHOUT THIS THE BASIS IS A SUGGESTION. A flex item will not shrink
       below its min-content width unless it is told it may, and min-content
       here is the longest nav label. Belt as well as braces: the labels are
       gone by the rule below, so min-content is an icon -- but a label added
       later that forgets to collapse should narrow the rail, not stretch it. */
    min-width: 0;
  }
  .sidebar a span, .sidebar button span,
  .sidebar .navgroup > summary span { display: none; }
  .sidebar a, .sidebar button,
  .sidebar .navgroup > summary { justify-content: center; padding: 12px 0; gap: 0; }
  /* The chevron is 7px plus `margin-left: auto` pushing it to an edge that no
     longer has a label to sit beside. It also counts toward the width. */
  .sidebar .navgroup > summary::after { display: none; }
  .sidebar .navgroup > a { padding-left: 0; }
}

/* ============================================== everything narrow, to 760 */
@media (max-width: 760px) {
  /* THE FIX FOR THE SIDEWAYS SCROLL. A fixed 46px row cannot hold a wordmark
     and three pills on a phone, and the alternative to wrapping is not a
     tidier bar -- it is a document wider than the screen. */
  .topbar {
    flex-wrap: wrap; height: auto; min-height: 46px;
    padding-top: 6px; padding-bottom: 6px; row-gap: 6px;
  }
  .topbar .right { flex-wrap: wrap; row-gap: 6px; }
  .topbar .crumbs { display: none; }
  .topbar .wordmark-mark { height: 32px; }

  /* A store name is as long as the owner made it. Truncating one is a smaller
     loss than a bar that runs off the side taking the page with it. */
  .topbar .pill { max-width: 46vw; overflow: hidden; text-overflow: ellipsis; }

  /* The customer panel. The two-column grid is already one column by 900; at
     this width the label stops being a column of its own -- 44% of 390px
     leaves an email address about 180px, and it is the value somebody came to
     read. */
  .detail > div { flex-direction: column; gap: 2px; padding: 8px 14px; }
  .detail .k { flex: none; text-align: left; font-size: 12px;
               letter-spacing: .04em; }
  .detail .v { min-width: 0; overflow-wrap: anywhere; }

  /* A field that insists on 180px plus a select plus a button does not fit
     across a phone, and the row wraps around it rather than shrinking it. */
  .row-form input { min-width: 0; flex: 1 1 8em; }
  .row-form > * { max-width: 100%; }
  /* `margin-left: auto` on one wrapped line of its own is a button stranded
     against the right edge. */
  .searchrow > .btn { margin-left: 0; }

  /* The card header's right-hand group -- Display / Records / Set -- wraps
     onto its own line here, and `margin-left: auto` on a line of its own is a
     control pushed to an edge with nothing beside it. */
  .sheet > header .right { margin-left: 0; }
}

/* ============================================== a phone, held upright
   Under 560 the rail is not worth its width at any size, so the navigation
   comes off the page entirely and slides in over it. Off-canvas rather than
   icon-only because a drawer can afford the WORDS: an anonymous column of
   sixteen-pixel glyphs is not navigation a clerk can learn from. */
@media (max-width: 560px) {
  .nav-open {
    display: block;
    /* Fixed, so it is still under the thumb after the page has scrolled --
       and it is the way back out of the drawer as well as the way in. */
    position: fixed; top: 7px; left: 10px; z-index: 60;
  }
  .nav-open:checked ~ .navscrim { display: block; }

  .topbar { padding-left: 58px; }

  .shell-body .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 40;
    flex: 0 0 auto; width: 244px; max-width: 84vw;
    /* Clear of the button, which floats over this corner. */
    padding: 48px 0 12px;
    overflow-y: auto; overscroll-behavior: contain;
    box-shadow: 6px 0 28px rgb(0 0 0 / .5);
    transform: translateX(-101%);
    transition: transform .18s ease;
  }
  .nav-open:checked ~ .shell-body .sidebar { transform: none; }

  /* A drawer is not a rail: a finger wants a taller row than a mouse does. */
  .sidebar a, .sidebar button, .sidebar .navgroup > summary { padding: 13px 16px; }
  .sidebar .navgroup > a { padding-left: 38px; }

  .shell main { padding: 12px 12px 32px; }

  /* THE IDENTITY STRIP STAYS, AND GETS SMALLER. Wrapping alone put the
     wordmark, the theme switch and three pills on four rows -- 147px of a
     844px screen spent saying who you are before the screen says anything.
     Smaller type and a truncating store name bring it back to two. Hiding one
     of them was the other option and it is the wrong one: which till you are
     standing at is the first thing anybody checks when a number looks wrong,
     which is why the bar carries it at all. */
  .topbar { gap: 8px; }
  .topbar .wordmark-mark { height: 28px; }
  /* `.right` is ONE flex item holding the switch and three pills, so it wraps
     as a block: it will not fit beside the wordmark, so it drops whole and
     leaves half the first row empty. `display: contents` hands its children
     to the bar to wrap individually, which is three rows down to two. The
     `margin-left: auto` that pins it right goes with it, and on a phone there
     is no right to pin anything to. */
  .topbar .right { display: contents; }
  .topbar .pill { font-size: 11px; padding: 3px 8px; }
  .themeswitch button { padding: 3px 9px; font-size: 11px; }
}

/* Somebody who has asked for less motion has asked for this too. */
@media (max-width: 560px) and (prefers-reduced-motion: reduce) {
  .shell-body .sidebar { transition: none; }
}

/* ============================================== a phone on its side
   Wide enough, and almost no height: a 390px viewport with a 46px bar on top
   of it. What is scarce here is vertical, so the bar and the nav rows give
   some back. Bounded above so a desktop window is never caught by it. */
@media (max-width: 950px) and (max-height: 520px) and (orientation: landscape) {
  .topbar { min-height: 38px; height: auto; padding-top: 3px; padding-bottom: 3px; }
  .topbar .wordmark-mark { height: 28px; }
  .sidebar a, .sidebar button, .sidebar .navgroup > summary {
    padding-top: 8px; padding-bottom: 8px;
  }
  .shell main { padding-top: 12px; padding-bottom: 24px; }
  /* A breadcrumb naming a customer runs to two lines at this width, and two
     lines of it is 39px of a 390px-tall screen. It is a location, not a
     heading -- the sheet below says the same thing in full. */
  .topbar .crumbs { min-width: 0; white-space: nowrap;
                    overflow: hidden; text-overflow: ellipsis; }
}

/* ---------------------------------------------------- the admin console
   `.topnav` is admin.tripleg.vip and has none of the shell above it: a flat
   strip of thirteen links and a sign-out. It has the same fault the top bar
   had -- no wrap, so anything narrower than the strip runs off the side and
   takes the document with it -- and the same cure.

   1200 RATHER THAN 760, because the strip is 1195px wide: thirteen tabs is
   more than a phone breakpoint's worth of overflow, and a 1152px laptop was
   scrolling sideways too. Desktop is left alone all the same -- a row that
   fits on one line does not wrap, whatever the query says. */
@media (max-width: 1200px) {
  .topnav { flex-wrap: wrap; gap: 10px 16px; }
}
@media (max-width: 760px) {
  .topnav { gap: 8px 12px; padding: 10px 14px; font-size: 13px; }
  /* On one wrapped line of its own, `margin-left: auto` is a sign-out button
     stranded against the right edge with the tabs left-aligned above it. */
  .topnav .right { margin-left: 0; }
  .topnav .wordmark-mark { height: 28px; }
}

/* ------------------------------------------------------------- DEV PORTAL
   The coordination board. A column per status, a card per task file.

   Columns scroll horizontally on a narrow screen rather than reflowing into
   one another: five statuses stacked vertically stop being a board and become
   a list where the whole point -- seeing the shape of the work at a glance --
   is gone. */
.devboard { display: flex; gap: 10px; align-items: flex-start;
            overflow-x: auto; padding-bottom: 8px; margin-bottom: 22px; }
.devcol { flex: 1 0 200px; min-width: 200px; background: var(--panel);
          border: 1px solid var(--line); border-radius: 8px; padding: 10px; }
.devcol > header { display: flex; align-items: center; gap: 8px;
                   color: var(--muted); font-size: 11px; text-transform: uppercase;
                   letter-spacing: .08em; margin-bottom: 8px; }
.devcol > header .count { margin-left: auto; font-size: 12px;
                          letter-spacing: 0; }
.devcol .empty { font-size: 12px; margin: 4px 0 0; }

.devcard { background: var(--field); border: 1px solid var(--line);
           border-left: 3px solid var(--line);
           border-radius: 6px; padding: 8px 10px; margin-bottom: 8px; }
.devcard .top { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.devcard .id { font-size: 11px; color: var(--muted); }
.devcard .title { font-size: 13px; font-weight: 600; line-height: 1.35; }
.devcard .meta { font-size: 11px; margin-top: 4px; }
.devcard .pill { font-size: 10px; padding: 1px 6px; }

/* Priority is the left edge, not a colour on the whole card. A board where
   four cards are four different background colours is unreadable at a glance;
   one where the urgent ones have a bright edge is not. */
.devcard.prio-urgent { border-left-color: var(--warn); }
.devcard.prio-high   { border-left-color: var(--accent); }

/* Done is present but receded. It is there so the board shows momentum, not
   so it competes with the work still outstanding. */
.devcol[data-status="done"] .devcard { opacity: .6; }

/* The two filing forms. Side by side where there is room, stacked on a phone.
   Deliberately plain: this is a form for three people, not a product. */
.devforms { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start; }
.devform { flex: 1 1 340px; background: var(--panel);
           border: 1px solid var(--line); border-radius: 8px; padding: 14px; }
.devform h3 { margin: 0 0 4px; font-size: 14px; }
.devform > p { margin: 0 0 12px; font-size: 12px; }
.devform label { display: block; margin-bottom: 10px; color: var(--muted);
                 font-size: 12px; }
.devform input[type=text], .devform input:not([type]), .devform textarea {
  display: block; width: 100%; margin-top: 4px; }
.devform textarea { font: inherit; resize: vertical; }
.devform .row-form { margin-bottom: 12px; }
.devform button[disabled] { opacity: .5; cursor: not-allowed; }

/* The start prompt. A textarea rather than a <pre> so the existing data-copy
   helper can select() it, and so it is still selectable with script blocked. */
.startprompt { margin-bottom: 22px; }
.startprompt .actionbar { margin-bottom: 8px; }
.promptbox {
  width: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; line-height: 1.5; resize: vertical;
  background: var(--field); color: var(--text);
  border: 1px solid var(--line); border-radius: 6px; padding: 10px;
}
