/* ============================================================================
   LOX / OBOGS Tracker - stylesheet

   Layout is a CSS grid on <body>: a dark navy rail down the left holding the
   nav, a light bar across the top for identity and counts, and the content in
   the remaining space. Below 900px the rail unfolds into a horizontal strip,
   because a fixed sidebar on a phone costs more width than it earns.

   Class and id names are load-bearing - app.js writes markup against them.
   Nothing here renames one.
   ========================================================================= */

:root{
  /* ---- surface ---- */
  --ink:#131923; --muted:#5c6a7d; --faint:#8592a3;
  --line:#e0e6ef; --line-soft:#eef2f7;
  --bg:#eef1f6; --card:#fff;

  /* ---- brand ----
     Bottle green, from the cart on the icon. The old navy meant nothing; the
     equipment's own colors are green, black, red and white, and the app was
     already using three of them - red for overdue, charcoal for missing, white
     for every surface. Only the brand was off-system.

     --brand is a dark FILL carrying white lettering (rail, table head, primary
     button). --brand-ink is the same identity used as TEXT, and it has to lift on
     a dark ground. Deliberately deeper than the status green: one is a large
     surface, the other is small text meaning "serviceable", and they must not be
     mistaken for each other. ---- */
  /* --brand is a FILL that carries white lettering (rail, table head, primary
     button) and stays dark in both themes. --brand-ink is the same brand color
     used as TEXT on a page surface, and that one has to lift in dark. */
  --brand:#12572c; --brand-deep:#0b3a1c; --brand-lift:#1c7a3d; --brand-ink:#12572c;
  --brand-soft:#e6f2e9;

  /* Height of the fixed phone menu bar. Three things must agree on it - the bar,
     the padding that stops it covering the page, and the dropdown's max-height -
     so it is one number here rather than three that can drift apart. Unused
     above 900px, where the rail replaces the bar. */
  --navbar-h:52px;
  --accent:#1f7a3f;

  /* ---- status. These are load-bearing: the dots, pills and row tints all
         read off the same hues, and they are picked to stay distinguishable
         without color. Change one and change its partners. ---- */
  --red:#b3261e;    --red-bg:#fdecea;    --red-deep:#8c1d18;
  --amber:#8a5a00;  --amber-bg:#fff4d6;  --amber-mid:#c98a00;
  --green:#1c6b3c;  --green-bg:#e6f4ea;
  --blue:#14567a;   --blue-bg:#e2f0f7;
  --purple:#6b3fa0; --purple-bg:#f1eafb;

  /* Surfaces and lines that were hardcoded. They have to be tokens for dark mode
     to exist at all - a literal #fff on an input is invisible to a theme. */
  --field:#fff; --hover:#f7f9fc; --hover-soft:#fbfcfe;
  --missing-bg:#e6e8ec; --unreadable:#5f1512;
  --amber-line:#e8d59a; --green-line:#bfe0c9; --red-line:#f0c8c4;
  --disabled:#b5bdc9;

  /* A chip's FILL is a separate decision from the same color used as text. On a
     dark ground the text color has to lift to stay readable, while the fill must
     stay dark enough to carry white lettering - so they cannot be one token. */
  --chip-red:#8c1d18; --chip-amber:#8a5e08; --chip-blue:#14567a;
  --chip-purple:#6b3fa0; --chip-green:#1c6b3c;

  /* ---- shape ---- */
  --r-sm:7px; --r:10px; --r-lg:14px;
  --shadow-sm:0 1px 2px rgba(19,25,35,.06), 0 1px 3px rgba(19,25,35,.05);
  --shadow:0 2px 4px rgba(19,25,35,.05), 0 8px 20px rgba(19,25,35,.07);
  --shadow-lg:0 18px 50px rgba(19,25,35,.28);
  --rail:236px;
  color-scheme:light;
}


/* ============================================================== DARK MODE ===
   ONE list, not two. `data-theme` is always present - index.html ships it as
   dark and app.js only ever swaps it for light - so the theme is never decided
   by what the machine reports.

   There used to be a second copy of every token behind
   `@media (prefers-color-scheme: dark)`, guarded with :not([data-theme="light"])
   for the days when no attribute was set. Once dark became the default and the
   attribute became unconditional, that block could no longer match anything the
   rule below does not already cover, and it was forty lines of duplicated
   values that had to be kept in step by hand. Deleted.

   If a "follow the device" option ever comes back, it belongs in app.js -
   read the media query there and set the attribute - NOT as a second palette
   here. One list of tokens, one place to change a color.

   The rail, the table head and the primary button keep --brand: they are dark
   surfaces carrying white lettering in BOTH themes, and lifting them would put
   white text on pale blue.
   ========================================================================= */
:root[data-theme="dark"]{
  color-scheme:dark;
  --ink:#e6ebf3; --muted:#9aa8bb; --faint:#75839a;
  --line:#2a3547; --line-soft:#222c3c;
  --bg:#0f141d; --card:#171f2c;

  --brand-ink:#6ec97f;
  --brand-soft:#12291c;
  --accent:#58b872;

  --red:#f28b82;    --red-bg:#3a1f1d;    --red-deep:#c8483f;
  --amber:#e0ad3a;  --amber-bg:#332a10;  --amber-mid:#c98a00;
  --green:#7fc99a;  --green-bg:#16301f;
  --blue:#7cc4e8;   --blue-bg:#12283a;
  --purple:#c4a3ea; --purple-bg:#241b38;

  --field:#1c2534; --hover:#1b2433; --hover-soft:#1b2433;
  --missing-bg:#252c39; --unreadable:#7d2b26;
  --amber-line:#4a3a12; --green-line:#1f4430; --red-line:#5a2420;
  --disabled:#3a4454;

  --chip-red:#a8322a; --chip-amber:#916407; --chip-blue:#1d5f85;
  --chip-purple:#6b3fa0; --chip-green:#1c6b3c;

  --shadow-sm:0 1px 2px rgba(0,0,0,.34), 0 1px 3px rgba(0,0,0,.28);
  --shadow:0 2px 4px rgba(0,0,0,.3), 0 8px 20px rgba(0,0,0,.34);
}
/* MISSING is charcoal on a light ground; on a dark one charcoal disappears, so it
   inverts to bone. It stays the one status that is not a color. */
:root[data-theme="dark"] .pill.missing{background:#d6dae2; color:#0f141d}
:root[data-theme="dark"] .dot.missing{background:#d6dae2; box-shadow:0 0 0 3px rgba(214,218,226,.2)}

/* Every printout is landscape. These lists are far wider than they are tall and
   a portrait sheet cuts columns off the right-hand edge. Declared at the top
   level rather than inside the print block - that is where browsers honor it
   most reliably, and @page only ever applies to paper anyway. */
@page{ size: letter landscape; margin: 11mm; }

*{box-sizing:border-box}

body{
  margin:0; background:var(--bg); color:var(--ink); min-height:100vh;
  font:16px/1.5 "Segoe UI",system-ui,-apple-system,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
  display:grid;
  grid-template-columns:var(--rail) minmax(0,1fr);
  grid-template-rows:auto 1fr;
  grid-template-areas:"nav header" "nav main";
}

/* A direct child of <body> with no grid area gets auto-placed into the first
   free cell - which is the nav rail. A section that lands there renders at
   236px wide with its content crushed, and it looks like a styling problem
   rather than the markup mistake it is. This makes that failure benign. */
body > :not(nav):not(header):not(main):not(dialog):not(#loginWrap){grid-area:main}

/* ---------------------------------------------------------------- header -- */
header{
  grid-area:header;
  background:var(--card); border-bottom:1px solid var(--line);
  padding:13px 26px; display:flex; align-items:center; gap:18px; flex-wrap:wrap;
}
header h1{
  font-size:16px; margin:0; font-weight:650; letter-spacing:-.1px; color:var(--ink);
}
header .count{
  margin-left:auto; font-size:13px; color:var(--muted); font-variant-numeric:tabular-nums;
  text-align:right; line-height:1.4;
}
#signOut{white-space:nowrap}
/* The header's own controls, grouped so they can move into the phone menu as
   one thing. Sizing lives here rather than in style= attributes on each button,
   because the menu has to be able to restyle them as full-width rows. */
.headertools{display:flex; align-items:center; gap:10px; flex-wrap:wrap}
.headerbtn{padding:6px 12px; font-size:13px}
/* Empty and inert until a phone puts something in it. */
.menutools{display:none}

/* ------------------------------------------------------------------ rail -- */
nav{
  grid-area:nav;
  background:linear-gradient(180deg,var(--brand) 0%,var(--brand-deep) 100%);
  display:flex; flex-direction:column; gap:2px; padding:0 12px 18px;
  border-right:1px solid var(--brand-deep);
}
/* Every one of these is scoped to `nav`. "brand" is ALSO a tile tone
   (.tile.brand, the first three Overview tiles), so a bare `.brand` selector
   reaches across the page and restyles them - align-items and gap were leaking
   onto the tiles on the desktop, and the mobile `display:none` below deleted
   them outright. Keep this block scoped. */
nav .brand{
  display:flex; align-items:center; gap:11px; padding:18px 8px 20px; margin-bottom:8px;
  border-bottom:1px solid rgba(255,255,255,.1);
}
nav .brand img{width:34px; height:34px; border-radius:8px; flex:0 0 auto}
nav .brand span{
  display:flex; flex-direction:column; color:#fff; font-weight:700; font-size:15px;
  letter-spacing:.3px; line-height:1.15;
}
nav .brand small{font-weight:500; font-size:11.5px; letter-spacing:1.4px; color:#9ecfae; text-transform:uppercase}

/* The rail is always in view on a desktop, so there is nothing to open, and the
   wrapper the phone dropdown needs must not become a box here - `display:contents`
   lets the buttons stay direct flex children of the rail exactly as before. */
.navtoggle{display:none}
.navmenu{display:contents}

nav button{
  border:0; background:none; padding:11px 14px; font-size:14.5px; font-weight:600;
  color:#cbe3d3; cursor:pointer; text-align:left; border-radius:var(--r-sm);
  position:relative; font-family:inherit; transition:background .13s, color .13s;
}
nav button:hover{background:rgba(255,255,255,.07); color:#fff}
nav button.on{background:var(--brand-lift); color:#fff}
nav button.on::before{
  content:""; position:absolute; left:0; top:9px; bottom:9px; width:3px;
  border-radius:0 3px 3px 0; background:#7fd699;
}
nav button:focus-visible{outline:2px solid #7fd699; outline-offset:1px}

/* ---- Incoming: the one ACTION in a rail of destinations ----
   Every aircraft that comes through the shop is booked in here, and it was
   reading as just another tab among nine. It is now a filled block set between
   two rules.

   The contrast is LUMINANCE, not hue: the rail is already green, so a greener
   green would not separate from it at all. A near-white fill on a dark green
   rail is the same move a primary button makes anywhere else, which is why it
   reads as professional rather than decorated - it is not a new idiom, it is
   the ordinary one applied in a place that had none.

   🔴 The class is `navcta`, NOT `primary`. `button.primary` is already the app's
   green Save button, defined further down this file - naming the nav item
   `primary` would have made every Incoming button match BOTH, which is exactly
   the `.brand` collision that deleted three Overview tiles on every phone: one
   name meaning two things, and the second meaning arriving from a rule nobody
   was looking at. Same trap waits for any other name used as both a component
   and a role.

   `nav button.on` sets its own background, so every rule here has to outrank it:
   `.navcta` alone loses to `.on`, hence `nav button.navcta`. */
nav button.navcta{
  background:#f2f8f3; color:var(--brand-deep); font-weight:700;
  margin:2px 0; padding:12px 14px; letter-spacing:.1px;
  box-shadow:0 1px 2px rgba(0,0,0,.18);
}
/* Content, not markup - the phone menu's closed label is this button's
   textContent, and "+ Incoming" reads there as a stray character. */
nav button.navcta::before{
  content:"+"; display:inline-block; width:17px; font-weight:700; font-size:16px;
  line-height:1; opacity:.75;
}
nav button.navcta:hover{background:#fff; color:var(--brand-deep)}
/* Being the current tab is said with an accent bar and NOT with the fill.
   There is nowhere brighter for the fill to go - it is already the lightest
   thing in a dark green rail - and a `.on{background:#fff}` here was measured
   doing nothing at all, so it is not carried as a rule that looks load-bearing
   and is not. The bar is the whole signal. */
nav button.navcta.on::after{
  content:""; position:absolute; left:0; top:9px; bottom:9px; width:3px;
  border-radius:0 3px 3px 0; background:var(--brand-deep);
}
/* `nav button.on::before` is the accent bar for every other tab; here ::before
   is already the plus, so that rule has to be called off or the two land in one
   box. The bar moves to ::after above. */
nav button.navcta.on::before{
  content:"+"; position:static; width:17px; height:auto; background:none;
  border-radius:0; opacity:1;
}

/* The second tier: findable, not competing. Archive is aircraft that are not
   here any more, so it must not sit at the same weight as the work in front of
   somebody. Still full-size targets - quiet is about contrast, not about making
   them harder to hit, least of all on a phone. */
.navgroup{
  margin:16px 8px 12px; height:0; border-top:1px solid rgba(255,255,255,.12);
}
nav button.quiet{font-size:13.5px; font-weight:500; color:#a8c9b5; padding:9px 14px}
nav button.quiet:hover{color:#fff}
nav button.quiet.on{background:var(--brand-lift); color:#fff; font-weight:600}

/* ------------------------------------------------------------------ main -- */
main{grid-area:main; padding:24px 26px 40px; max-width:1500px; width:100%}
.card{
  background:var(--card); border:1px solid var(--line); border-radius:var(--r);
  padding:24px; margin-bottom:18px; box-shadow:var(--shadow-sm);
}
.card h2{margin:0 0 5px; font-size:17px; font-weight:680; letter-spacing:-.1px}
.card p.sub{margin:0 0 20px; color:var(--muted); font-size:14px}

/* ------------------------------------------------------- overview search -- */
.homesearch{position:relative; margin-bottom:18px}
.homesearch input{
  width:100%; padding:14px 16px; font-size:15.5px; font-family:inherit;
  border:1.5px solid var(--line); border-radius:var(--r); background:var(--field); color:var(--ink);
}
.homesearch input:focus{outline:0; border-color:var(--accent); box-shadow:0 0 0 3px rgba(31,122,63,.18)}
.results{
  position:absolute; z-index:20; left:0; right:0; top:calc(100% + 6px);
  background:var(--card); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--shadow); overflow:hidden;
}
.resultrow{
  display:flex; align-items:center; gap:14px; width:100%; text-align:left;
  padding:11px 16px; border:0; background:none; cursor:pointer; font-family:inherit;
  font-size:14.5px; color:var(--ink); border-bottom:1px solid var(--line-soft);
}
.resultrow:last-child{border-bottom:0}
.resultrow:hover,.resultrow:focus-visible{background:var(--hover); outline:0}
.r-tail{font-weight:700; font-family:"Consolas",monospace; min-width:92px}
.r-ser{font-family:"Consolas",monospace; color:var(--muted); min-width:104px}
.r-where{color:var(--faint); font-size:13px}
.r-flag{margin-left:auto}
.noresult{padding:13px 16px; color:var(--muted); font-size:14px}

/* ------------------------------------------------------- availability line -- */
.stockline{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:6px 12px;
  margin:0 0 16px; padding:10px 14px; border-radius:var(--r-sm);
  background:var(--card); border:1px solid var(--line);
  font-size:13.5px; color:var(--muted);
}
.stockline b{color:var(--ink); font-variant-numeric:tabular-nums}
.stocktag{
  font-size:10.5px; font-weight:700; letter-spacing:1px; text-transform:uppercase;
  padding:3px 8px; border-radius:4px; background:var(--line-soft); color:var(--muted);
}
.stocktag.ok{background:var(--green-bg); color:var(--green)}
.stockhint{color:var(--faint); font-size:12.5px}
/* Short is the only state that gets to shout, and then it shouts properly. */
.stockline.short{background:var(--red-bg); border-color:var(--red-deep); color:var(--red-deep); font-weight:600}
.stockline.short b{color:var(--red-deep)}
.stockline.short .stocktag{background:var(--red-deep); color:#fff}

/* ---------------------------------------------------------------- footer -- */
.sitefoot{
  display:flex; flex-wrap:wrap; gap:7px; align-items:center; justify-content:center;
  margin:34px 0 0; padding:16px 0 0; border-top:1px solid var(--line);
  color:var(--faint); font-size:12.5px;
}
.sitefoot strong{color:var(--muted); font-weight:650}
.sitefoot .sep{opacity:.6}

/* --------------------------------------------------------------- offline -- */
.offline{
  display:none; align-items:center; gap:9px;
  background:var(--amber-bg); color:var(--amber); border-bottom:1px solid var(--amber-line);
  padding:9px 26px; font-size:13.5px; font-weight:650;
}
body.is-offline .offline{display:flex}
.offline::before{
  content:""; width:8px; height:8px; border-radius:50%; background:var(--amber-mid); flex:0 0 auto;
}

/* The install hint. Brand green rather than a warning color - it is an offer,
   not a problem, and the shop has enough red on this screen already. It sits in
   the flow rather than floating: a bar that covers content is the thing people
   dismiss without reading. */
.installhint{
  display:flex; align-items:center; gap:10px;
  background:var(--brand-soft); color:var(--brand-ink);
  border-bottom:1px solid var(--green-line);
  padding:10px 26px; font-size:13.5px; font-weight:600;
}
.installhint[hidden]{display:none}
.installhint-go{
  margin-left:auto; flex:0 0 auto;
  background:var(--brand); color:#fff; border:0; border-radius:var(--r-sm);
  padding:6px 14px; font-size:13px; font-weight:650; font-family:inherit; cursor:pointer;
}
.installhint-go:hover{background:var(--brand-lift)}
.installhint-x{
  flex:0 0 auto; background:none; border:0; color:var(--brand-ink);
  font-size:20px; line-height:1; padding:2px 6px; cursor:pointer; font-family:inherit;
  opacity:.7;
}
.installhint-x:hover{opacity:1}
@media (max-width:700px){
  /* Same padding as everything else at this width. The text is short enough to
     share a row with both controls now, and vertical space is scarce on a phone
     - the fixed menu bar already takes 52px before any content. It may still
     wrap if the name ever grows; it must never be truncated, because a cut-off
     hint reads as a broken one. */
  .installhint{padding:10px 12px; gap:8px; font-size:13px; flex-wrap:wrap}
  #installHintText{flex:1 1 auto; min-width:0}
}

/* ======================================================== OVERVIEW ======== */
/* The tiles ARE the Overview now - the ranked list that used to sit under them
   moved to the Active tab as a report. That freed the whole lower half of the
   screen, so the numbers are sized to be read across a shop rather than leaned
   into: bigger figures, more room, and a wider minimum so a long label like
   "Cryo - Bottles being Purged" sets on two lines instead of four. */
/* ============================================== THE BOARD ================
   Three ranked groups, not thirteen equal tiles. Every figure still opens its
   list - the board is the navigation - but the tiles gave a jet nobody can find
   the same box as the count of spare bottles, so nothing led. Type and a
   severity rule do the ranking now; there are no boxes.

   The severity ramp is ordered by LIGHTNESS (s1 darkest to s5 lightest), so it
   survives a photocopy and red-green color blindness. The old red and amber were
   0.7 apart in deutan and all landed inside 2% gray on paper - measured, not
   guessed. Change one, keep the order. */
:root{
  --s0:#5c6a7d; --s1:#7f1d1d; --s2:#a33a2a; --s3:#c07a35; --s4:#d9b46a; --s5:#dcd8cc;
  /* s0 is a MID gray in light and a LIGHT gray in dark, so its lettering has
     to flip with it - one fixed color fails on one of the two. */
  --s0-ink:#fff;
}
:root[data-theme="dark"]{
  --s0:#8592a3; --s1:#8f2320; --s2:#b9482f; --s3:#cf8a3c; --s4:#e0c07c; --s5:#4a5148;
  --s0-ink:#131923;
}

/* The readiness PERCENTAGE and its state line are gone, markup and styles both.
   It does not matter at depot, and it measured the wrong thing anyway - the
   readiness of jets that are not flying. Removed rather than hidden: an orphaned
   rule is the next reader's wild goose chase. */

/* ---- posture strip: the flex weights ARE the counts, so it is always to scale ---- */
.posturebar{display:flex; height:22px; gap:2px; margin-bottom:8px}
.posturebar .pb{display:flex; align-items:center; justify-content:center;
  font-size:11.5px; font-weight:650; font-variant-numeric:tabular-nums;
  min-width:16px; overflow:hidden}
.posturekey{display:flex; flex-wrap:wrap; gap:5px 17px; font-size:12px;
  color:var(--muted); margin-bottom:22px}
.posturekey span{display:flex; align-items:center; gap:6px}
.posturekey i{width:10px; height:10px; border-radius:2px; flex:0 0 auto}
.pb.s0{background:var(--s0); color:var(--s0-ink)}
.pb.s1{background:var(--s1); color:#fff}
.pb.s2{background:var(--s2); color:#fff}
.pb.s3{background:var(--s3); color:#fff}
.pb.s4{background:var(--s4); color:#2c2410}
/* --ink, not a literal: s5 is a LIGHT swatch in light mode and a DARK one in
   dark mode, so a fixed ink color is unreadable in one of them. */
.pb.s5{background:var(--s5); color:var(--ink)}
.pb.hatch{color:var(--ink);
  background:repeating-linear-gradient(45deg,var(--muted),var(--muted) 3px,transparent 3px,transparent 7px)}

/* ---- FLIGHT TEST: the top of the board ----
   The only part of the Overview allowed to shout, and it shouts in NAMES.

   At depot every bottle goes out of purge eventually, so a big red count of
   overdue bottles is the weather - and a red number nobody can act on trains
   people to stop reading red. What has a deadline is a jet in flight test, so
   that is what leads, as a list of specific aircraft a supervisor can walk to.

   Tone classes a/b/k ride the SAME lightness-ordered ramp as the posture strip,
   so a photocopied sheet still carries the ranking. */
.ftblock{margin-bottom:26px}
.glab{display:flex; justify-content:space-between; align-items:baseline; gap:12px;
  font-size:11.5px; font-weight:700; letter-spacing:1.7px; text-transform:uppercase;
  color:var(--faint); border-bottom:1px solid var(--line); padding-bottom:5px; margin-bottom:2px}
.glab em{font-style:normal; letter-spacing:.3px; text-transform:none;
  font-size:12.5px; font-weight:500; color:var(--muted); white-space:nowrap}
.glab em b{color:var(--ink); font-weight:700}

table.ft{width:100%; border-collapse:collapse; font-size:14.5px}
table.ft td{padding:11px 10px 11px 0; border-bottom:1px solid var(--line); vertical-align:middle}
table.ft tr:last-child td{border-bottom:0}
table.ft tr{cursor:pointer}
table.ft tr:hover td{background:var(--brand-soft)}
table.ft .tail{font-weight:700; font-variant-numeric:tabular-nums; width:104px;
  font-family:"Consolas",monospace}
table.ft .kind{color:var(--faint); font-size:12.5px; width:76px}
table.ft .when{text-align:right; white-space:nowrap; width:104px}
table.ft .when b{display:block; font-size:16px; font-weight:700;
  font-variant-numeric:tabular-nums; color:var(--ink)}
table.ft .when span{font-size:11.5px; color:var(--faint)}
/* The severity rule down the left edge, not a filled box - the same decision the
   tiles made when they stopped being thirteen colored squares. */
.need{display:block; border-left:3px solid var(--line); padding-left:11px}
.need b{display:block; font-size:13.5px; font-weight:700; letter-spacing:.7px;
  text-transform:uppercase; line-height:1.25}
.need span{display:block; font-size:12.5px; color:var(--faint); line-height:1.35}
/* 🔴 --red / --amber / --brand-ink, NOT the s1..s5 ramp.

   The ramp is built for FILLED swatches - a solid block with contrasting ink on
   it, which is what the posture strip is - and its values are chosen dark so
   white lettering sits on them. Used as TEXT they fail: --s1 is #8f2320 in dark
   mode against a #0f141d page, which measures 1.3:1. The most urgent flag on the
   board would have been all but invisible in the theme that is the DEFAULT.

   These three are the app's existing text triple and they already lift in dark
   the way --brand-ink does; both themes clear 5:1. Ranking does not lean on
   color here anyway - the flag says NEEDS CRYO or NOT FITTED in words, so hue
   is reinforcement rather than the carrier, which is the opposite of the strip
   where a colored block is all there is. That is why the strip keeps the
   measured ramp and this does not. */
.need.a{border-left-color:var(--red)}       .need.a b{color:var(--red)}
.need.b{border-left-color:var(--amber)}     .need.b b{color:var(--amber)}
/* Tone c is CRYO, and it is deliberately not on the severity ramp at all. Every
   other tone here ranks how bad something is; cryo ranks nothing, because the
   item is already away being dealt with. Blue is what this app has meant by Cryo
   since the first version - the AT CRYO pill on every other list is the same
   token - so the board is not inventing a color, it is using the one the shop
   already reads. */
/* Tone n is NOT-YET: on the board, but nothing is being asked of its item.
   Deliberately the quietest thing on the list - it is neither a fault nor a
   clearance, and giving it any of the status colors would make an aircraft in
   the paint shop read as a verdict on its oxygen. */
.need.n{border-left-color:var(--line)}     .need.n b{color:var(--muted)}
.need.c{border-left-color:var(--blue)}      .need.c b{color:var(--blue)}
.need.k{border-left-color:var(--brand-ink)} .need.k b{color:var(--brand-ink)}
.ftempty{margin:14px 0 0; font-size:14px; color:var(--muted); max-width:60ch}

/* The same flag on a printed list row, where it is a cell rather than the page's
   leading element - so it is smaller and carries no left rule to fight the row. */
.ftflag{display:block; font-size:12.5px; font-weight:700; letter-spacing:.6px;
  text-transform:uppercase}
.ftflag.a{color:var(--red)} .ftflag.b{color:var(--amber)}
.ftflag.c{color:var(--blue)} .ftflag.k{color:var(--brand-ink)}
.ftflag.n{color:var(--muted)}

/* ---- the quiet lines ----
   Everything that is not a jet in flight test. Still every figure, still every
   figure opening its own list - the board is the navigation and that has not
   changed. It just no longer competes with the aircraft above it.

   The links are 44px targets on a phone rather than dotted text, because a count
   that opens a list is a control and has to be tappable like one. */
.quietblock{margin-top:30px}
.quietblock .glab{margin-top:22px}
.qline{display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 20px;
  font-size:13.5px; color:var(--muted); padding:7px 0}
.qline span{display:flex; align-items:baseline; gap:6px}
.qline b{color:var(--ink); font-weight:700; font-variant-numeric:tabular-nums; font-size:15px}
.qlink{border:0; background:none; padding:6px 2px; margin:0; font:inherit; color:var(--muted);
  cursor:pointer; border-bottom:1px dotted var(--line); border-radius:0;
  min-height:0; transition:color .12s, border-color .12s}
.qlink:hover{color:var(--ink); border-bottom-color:var(--brand-ink)}
.qlink:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

/* ---- the report pack ----
   Three sheets and a cover, queued into one document. Each sheet starts a new
   page - `+` rather than a class on all of them, so the first does not throw a
   blank page before itself. */
/* The report picker. A quiet panel rather than a form: it sits between the name
   field and the Print button, which is the order the job is done in. */
.packpick{border:1px solid var(--line);border-radius:10px;
  background:var(--panel);margin:0 0 16px;overflow:hidden}
.packpickhead{display:flex;justify-content:space-between;align-items:baseline;
  gap:12px;padding:10px 14px;border-bottom:1px solid var(--line);
  background:var(--field);font-weight:600;font-size:14px;color:var(--ink)}
.packpickhead em{font-style:normal;font-weight:400;font-size:13px;color:var(--muted)}
.packpicklist{display:grid;gap:2px 18px;padding:10px 14px;
  grid-template-columns:repeat(auto-fill,minmax(260px,1fr))}
.packopt{display:flex;align-items:center;gap:9px;padding:5px 2px;
  font-size:14px;color:var(--ink);cursor:pointer;min-width:0}
.packopt input{flex:0 0 auto;margin:0;cursor:pointer}
.packopt span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.packopt em{font-style:normal;font-size:12px;color:var(--muted);white-space:nowrap}
/* The mandatory three read as settled rather than broken: the label keeps its
   full strength and only the "always included" note is quiet. Greying the whole
   row would make it look like something that had failed to load. */
.packopt.must,.packopt.must input{cursor:default}
/* "All reports" spans the grid and is ruled off, because it is a control over
   the list rather than another line in it. */
.packall{grid-column:1/-1;border-bottom:1px solid var(--line);
  margin-bottom:4px;padding-bottom:8px;font-weight:600}

.packsheet + .packsheet{break-before:page; page-break-before:always}
/* The single-report cover. Never on screen: a title page above every list is the
   opposite of useful. On paper it is page one, and the report follows on page
   two. */
.listcover{display:none}
.packsheet{margin-bottom:34px}
.packcount{font-size:13px; color:var(--muted); margin:0 0 10px}

.packcover{text-align:left}
.coverhead{border-bottom:3px solid var(--brand); padding-bottom:14px; margin-bottom:22px}
.covereyebrow{font-size:12px; font-weight:700; letter-spacing:1.8px; text-transform:uppercase;
  color:var(--brand-ink); margin-bottom:8px}
.coverhead h1{margin:0; font-size:30px; font-weight:720; letter-spacing:-.4px; color:var(--ink)}
.coverfacts{display:grid; grid-template-columns:auto 1fr; gap:7px 20px; margin:0 0 22px; font-size:14px}
.coverfacts dt{font-weight:700; color:var(--muted)}
.coverfacts dd{margin:0; color:var(--ink); font-family:"Consolas",monospace}
.coverstate{padding:12px 14px; border-left:4px solid var(--brand); background:var(--brand-soft);
  font-size:14px; color:var(--ink); margin-bottom:24px}
.coverstate b{font-weight:700}
.covercontents h3{margin:0 0 8px; font-size:14px; font-weight:700}
.covercontents ol{margin:0; padding-left:20px; font-size:14px}
.covercontents li{padding:5px 0; border-bottom:1px solid var(--line); display:flex;
  justify-content:space-between; gap:16px}
.covercontents li:last-child{border-bottom:0}
.covercontents em{font-style:normal; color:var(--muted); white-space:nowrap}
/* A report that came back empty. Greyed rather than hidden: it was run, and
   saying so is the point - a name missing from the contents and a name against
   "none" are different facts. */
.covercontents li.none{color:var(--muted)}
.covercontents li.none em{font-style:italic}
.covernote{margin:26px 0 0; font-size:12px; color:var(--muted); max-width:60ch}

/* ---- the top-of-page alert ----
   A message about something that just happened, not a status readout. It is red
   because the only thing that raises it is a jet entering flight test carrying a
   problem, and that is the one moment in this app where a dormant fact becomes a
   deadline.

   Dismissible, and Escape closes it too - a bar that can only be shut by hitting
   a small x is a bar people learn to scroll past. */
.alertbar{
  display:flex; align-items:flex-start; gap:12px;
  padding:13px 14px 13px 16px; margin:0 0 16px;
  border-radius:var(--r-sm); border:1.5px solid var(--red-deep); border-left-width:5px;
  background:var(--red-bg); color:var(--ink); font-size:14.5px; line-height:1.5;
  box-shadow:var(--shadow-sm);
}
/* 🔴 `display:flex` beats the user-agent `[hidden]{display:none}` rule, so the
   attribute stops working the moment a component sets its own display - the
   empty bar sat at the top of every page showing nothing but its dismiss x.
   Any component here that sets display AND is toggled by `hidden` needs this
   line with it. */
.alertbar[hidden]{display:none}
.alertbar.ok{border-color:var(--green); background:var(--green-bg)}
.alertbody{flex:1 1 auto}
.alertbody b{color:var(--red-deep)}
.alertbar.ok .alertbody b{color:var(--green)}
.alertx{
  flex:0 0 auto; border:0; background:none; cursor:pointer; font-size:22px; line-height:1;
  padding:0 4px; color:var(--muted); font-family:inherit;
}
.alertx:hover{color:var(--ink)}
/* On the alert LINE the dismiss belongs at the far right, where a dismiss always
   is. As an ordinary flex child it sat immediately after the last tail, reading
   as though it belonged to that aircraft rather than to the whole notice.
   `margin-left:auto` eats the remaining space on its line; the box already wraps,
   so on a narrow screen it lands at the end of the last line rather than being
   pushed off it. */
.stockline .alertx{margin-left:auto; align-self:center}
.alertx:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

/* ---- the elevated note above a list ----
   A standing fact about the fleet, so unlike the alert bar it PRINTS. Quieter
   than the alert too: nothing here has just happened, it is a condition that has
   been true for a while and will stay true until somebody goes and finds the
   thing. */
.listnote{
  padding:11px 14px; margin:0 0 14px;
  border:1px solid var(--red-line); border-left:4px solid var(--red-deep);
  border-radius:var(--r-sm); background:var(--red-bg);
  font-size:13.5px; line-height:1.5; color:var(--ink);
}
.listnote b{color:var(--red-deep); font-weight:680}
/* The tails in the note are controls, not decoration - each one opens the dialog
   that puts an item on that aircraft. Sized as a real target because this is the
   one place somebody acts on a missing bottle. */
.notego{
  display:inline-block; margin:4px 6px 0 0; padding:6px 11px; min-height:32px;
  border:1px solid var(--red-deep); border-radius:var(--r-sm);
  background:var(--card); color:var(--red-deep); font:inherit; font-weight:680;
  font-family:"Consolas",monospace; cursor:pointer; transition:background .12s, color .12s;
}
.notego:hover{background:var(--red-deep); color:#fff}
.notego:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
/* The Overview's alert carries the same buttons on a tighter line, so they sit
   inline with the sentence rather than starting a row of their own. */
.notego.sm{margin:0 4px 0 0; padding:3px 9px; min-height:26px; font-size:13px}

/* A source that is not the one chosen. Greyed and its controls disabled, so it
   is visibly still there - somebody can see the other two ways exist - without
   being fillable. Hiding them instead would make the form look like it changes
   shape under you every time the source select moves. */
.srcblk{transition:opacity .12s}
.srcblk.off{opacity:.4}
.srcblk.off select,.srcblk.off input{cursor:not-allowed}
@media (max-width:700px){
  .notego{min-height:44px; padding:10px 13px}
  .notego.sm{min-height:34px; padding:7px 11px}
}

/* ---- the "what are you changing?" chooser ----
   Two targets, each big enough to read a sentence in. They are sized as PANELS
   rather than buttons on purpose: the whole reason this dialog exists is that
   one-word controls were not saying what they did, so there has to be room for
   the words that do.

   Stacked, never side by side. Side by side halves the width available to the
   very text that is doing the work. */
.pickgrid{display:flex; flex-direction:column; gap:10px; margin:4px 0 6px}
.pick{
  display:block; width:100%; text-align:left; cursor:pointer; font-family:inherit;
  background:var(--field); color:var(--ink);
  border:1.5px solid var(--line); border-left:4px solid var(--brand-ink);
  border-radius:var(--r-sm); padding:14px 16px;
  transition:border-color .12s, background .12s;
}
.pick b{display:block; font-size:15.5px; font-weight:680; margin-bottom:2px}
.pick span{display:block; font-size:13px; color:var(--muted); line-height:1.4}
.pick:hover{background:var(--hover); border-color:var(--brand-ink)}
.pick:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

/* The flight-test dropdown on a table row. It reads as ordinary until the jet is
   actually in test, and then it says so - this is the one row control whose
   VALUE is the point rather than just a way to change it. */
.phasesel.on{font-weight:700; color:var(--brand-ink); border-color:var(--brand-ink)}

@media (max-width:700px){
  table.ft .kind{display:none}
  table.ft .tail{width:86px; font-size:13.5px}
  table.ft .when{width:70px}
  table.ft .when b{font-size:14px}
  /* A tap target, not a word. 44px is the floor, and these are the only way into
     the lists they name. */
  .qlink{padding:11px 2px; min-height:44px; display:inline-flex; align-items:center}
  .qline{gap:0 16px}
}

/* The Hot List that lived under the tiles is now a report on the
   Active tab, so it is styled by the table rules like every other list. Its own
   styles are gone rather than left orphaned. */

/* ------------------------------------------------------------------ form -- */
.field{margin-bottom:19px; max-width:520px}
.field label{display:block; font-weight:650; margin-bottom:7px; font-size:15px}
.field label .n{
  display:inline-block; width:23px; height:23px; line-height:23px; text-align:center;
  background:var(--brand); color:#fff; border-radius:50%; font-size:12.5px; margin-right:9px;
  font-weight:700;
}
.field input,.field select,.field textarea{
  width:100%; padding:12px 14px; font-size:17px; font-family:inherit;
  border:1.5px solid var(--line); border-radius:var(--r-sm); background:var(--field); color:var(--ink);
  transition:border-color .12s, box-shadow .12s;
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:0; border-color:var(--accent); box-shadow:0 0 0 3px rgba(31,122,63,.18);
}
.hint{font-size:13px; color:var(--muted); margin-top:6px}
.flag{margin-top:8px; font-size:14px; font-weight:650; padding:9px 12px; border-radius:var(--r-sm); display:none}
.flag.show{display:block}
.flag.ok{background:var(--green-bg); color:var(--green)}
.flag.warn{background:var(--amber-bg); color:var(--amber)}

.banner{padding:15px 17px; border-radius:var(--r-sm); margin-bottom:21px; font-size:15px; font-weight:600; line-height:1.45}
.banner.idle{background:var(--line-soft); color:var(--muted); font-weight:500}
.banner.new{background:var(--green-bg); color:var(--green)}
.banner.seen{background:var(--amber-bg); color:var(--amber)}
.banner.stop{background:var(--red-bg); color:var(--red)}

button.primary{
  background:var(--brand); color:#fff; border:0; padding:13px 26px; font-size:16px;
  font-weight:650; border-radius:var(--r-sm); cursor:pointer; font-family:inherit;
  box-shadow:var(--shadow-sm); transition:background .13s, transform .1s;
}
button.primary:hover:not(:disabled){background:var(--brand-lift)}
button.primary:active:not(:disabled){transform:translateY(1px)}
button.primary:disabled{background:var(--disabled); cursor:not-allowed; box-shadow:none}
button.ghost{
  background:var(--field); color:var(--brand-ink); border:1.5px solid var(--line); padding:9px 16px;
  font-size:14px; font-weight:650; border-radius:var(--r-sm); cursor:pointer; font-family:inherit;
  transition:border-color .12s, background .12s;
}
button.ghost:hover{border-color:var(--accent); background:var(--hover-soft)}
button:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.row{display:flex; gap:10px; flex-wrap:wrap; align-items:center}

/* ----------------------------------------------------------------- table -- */
.toolbar{display:flex; gap:10px; align-items:center; margin-bottom:15px; flex-wrap:wrap}
.toolbar input[type=search]{
  padding:10px 14px; font-size:15.5px; border:1.5px solid var(--line); border-radius:var(--r-sm);
  min-width:260px; font-family:inherit;
  /* Never set a surface or a text color, so it fell through to the browser's own
     dark gray while every field beside it used the app's. Readable either way, but
     visibly not part of the same form. */
  background:var(--field); color:var(--ink);
}
.toolbar input[type=search]:focus{outline:0; border-color:var(--accent); box-shadow:0 0 0 3px rgba(31,122,63,.18)}
.toolsel{
  padding:9px 11px; font-size:14.5px; font-family:inherit; color:var(--ink);
  border:1.5px solid var(--line); border-radius:var(--r-sm); background:var(--field);
}
.toolsel:focus{outline:0; border-color:var(--accent); box-shadow:0 0 0 3px rgba(31,122,63,.18)}
table{width:100%; border-collapse:collapse; font-size:14.5px}
th{
  background:var(--brand); color:#fff; text-align:left; padding:11px 10px;
  font-weight:650; font-size:12px; text-transform:uppercase; letter-spacing:.5px;
  position:sticky; top:0; z-index:2;
}
td{padding:11px 10px; border-bottom:1px solid var(--line-soft); vertical-align:top}
tbody tr{transition:background .1s}
tbody tr:hover{background:var(--hover)}
/* Only states that need somebody get a tinted row. Overdue and unreadable are
   red because the aircraft cannot fly; 30-days-or-less is amber because it is the
   last band where there is still time to act comfortably. Everything past that is
   white and carries its band on a pill - tinting every row is the same as tinting
   none of them. */
tr.overdue td{background:var(--red-bg)}
tr.d30 td{background:var(--amber-bg)}
tr.cryo td{background:var(--blue-bg)}
tr.missing td{background:var(--missing-bg)}
tr.nobottle td{background:var(--purple-bg)}
tr.badbottle td{background:var(--red-bg)}
tr.unreadable td{background:var(--red-bg)}

.pill{display:inline-block; padding:3px 10px; border-radius:11px; font-size:11.5px; font-weight:700;
      white-space:nowrap; letter-spacing:.3px}
.pill.click{cursor:pointer; text-decoration:underline; text-underline-offset:2px}
.pill.click:hover{filter:brightness(1.12)}
.pill.overdue{background:var(--chip-red); color:#fff}
.pill.d30{background:var(--chip-amber); color:#fff}
.pill.d60{background:var(--amber-bg); color:var(--amber); border:1px solid var(--amber-line)}
.pill.d90{background:var(--line-soft); color:var(--muted); border:1px solid var(--line)}
.pill.ok{background:var(--green-bg); color:var(--green); border:1px solid var(--green-line)}
.pill.cryo{background:var(--chip-blue); color:#fff}
.pill.nobottle{background:var(--chip-purple); color:#fff}
.pill.badbottle{background:var(--chip-red); color:#fff}
/* Cannot be certified. Deliberately the same red family as overdue - the
   operational position is identical - but labeled differently so nobody goes
   hunting for a date that is no longer legible. */
.pill.unreadable{background:var(--unreadable); color:#fff}
/* Missing is charcoal, not a fourth shade of red. Overdue, bad bottle and
   unreadable are already three dark reds and a fourth would be told apart only
   by someone who knew the scale; charcoal reads as "gone" at a glance and is the
   one status that survives a black-and-white printer intact. */
.pill.missing{background:var(--ink); color:#fff}

/* Loan note sits under the bottle badge - quieter than a pill, still readable. */
.loan{display:block; margin-top:3px; font-size:11.5px; color:var(--purple); font-weight:650}
.nofit{color:var(--purple); font-weight:700; white-space:nowrap}
/* Notes are rare and long. On screen the cell holds a small button; the full
   text only renders on the printout, where there is room for it. */
.notebtn{border:1px solid var(--line); border-radius:5px; padding:3px 9px; font-size:12.5px; background:var(--field)}
.notebtn:hover{border-color:var(--accent); text-decoration:none}
.printonly{display:none}

/* Color-blind-safe second cue: the dot carries the same signal as the pill, and
   the ring shape differs from the filled ones so it reads without color too. */
td.dotcell{width:26px; padding-left:14px}
.dot{display:inline-block; width:13px; height:13px; border-radius:50%; vertical-align:middle}
.dot.overdue{background:var(--chip-red); box-shadow:0 0 0 3px rgba(140,29,24,.22)}
.dot.d30{background:var(--chip-amber)}
.dot.d60{background:var(--amber-line)}
.dot.d90{background:transparent; border:2px solid var(--line)}
.dot.ok{background:transparent; border:2px solid #9fb3a6}
.dot.cryo{background:var(--chip-blue); box-shadow:0 0 0 3px rgba(20,86,122,.2)}
.dot.nobottle{background:var(--chip-purple); box-shadow:0 0 0 3px rgba(107,63,160,.2)}
.dot.badbottle{background:var(--chip-red); box-shadow:0 0 0 3px rgba(140,29,24,.22)}
.dot.unreadable{background:var(--unreadable); box-shadow:0 0 0 3px rgba(95,21,18,.28)}
.dot.missing{background:var(--ink); box-shadow:0 0 0 3px rgba(19,25,35,.25)}
th.dothead{width:26px; padding-left:14px}

.empty{padding:48px; text-align:center; color:var(--muted)}
.mono{font-variant-numeric:tabular-nums; font-family:"Consolas",monospace; white-space:nowrap}
.act{color:var(--accent); background:none; border:0; font-weight:650; cursor:pointer;
     font-size:13.5px; padding:4px 6px; border-radius:5px; font-family:inherit}
td.actions{white-space:nowrap; text-align:right}   /* keep edit/bottle/deliver on one line */
.act:hover{background:var(--brand-soft)}

/* Location is editable in place on anything still active - it reads as plain
   text until you hover, so the list stays calm but the change is one click. */
.locsel{
  font:inherit; color:inherit; padding:4px 22px 4px 6px; border-radius:6px; cursor:pointer;
  border:1px solid transparent; background:transparent; appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 12px) 52%,calc(100% - 7px) 52%;
  background-size:5px 5px,5px 5px; background-repeat:no-repeat;
}
.locsel:hover{border-color:var(--accent); background-color:var(--field)}
/* 🔴 The OPEN popup takes its background from the control, and `.locsel` is
   deliberately transparent so it disappears into the row until you point at it.
   The moment the pointer left the open list, `:hover` stopped applying, the
   background went back to transparent and the popup painted itself white -
   unreadable in dark mode, over a list somebody was mid-way through choosing
   from.

   The options carry their own background instead, which does not depend on a
   pointer being anywhere. Set on every select, not just this one: nothing else
   was reported, but the same trap is one `background:transparent` away in any of
   them. */
select option{background-color:var(--field); color:var(--ink)}
.locsel:focus{outline:2px solid var(--accent); outline-offset:-1px}

/* The table scrolls sideways inside its own box rather than dragging the whole
   page with it - that is what makes the lists usable on a phone. */
.tablewrap{overflow-x:auto; -webkit-overflow-scrolling:touch}

/* --------------------------------------------------------------- dialogs -- */
dialog{
  border:0; border-radius:var(--r-lg); padding:0; max-width:620px; width:calc(100% - 32px);
  box-shadow:var(--shadow-lg); color:var(--ink);
}
dialog::backdrop{background:rgba(19,25,35,.55)}
.dlg{padding:26px}
.dlg h3{margin:0 0 3px; font-size:19px; font-weight:680}
.dlg .who{margin:0 0 20px; color:var(--muted); font-size:14px}
.grid{display:grid; grid-template-columns:1fr 1fr; gap:14px}
.grid .full{grid-column:1/-1}
.grid label{display:block; font-weight:650; font-size:12.5px; margin-bottom:5px; color:var(--muted);
            text-transform:uppercase; letter-spacing:.4px}
.grid input,.grid select,.grid textarea{
  width:100%; padding:10px 12px; font-size:15.5px; font-family:inherit;
  border:1.5px solid var(--line); border-radius:var(--r-sm); background:var(--field); color:var(--ink);
}
.grid input:focus,.grid select:focus,.grid textarea:focus{
  outline:0; border-color:var(--accent); box-shadow:0 0 0 3px rgba(31,122,63,.18);
}
#editErr{margin-top:14px; white-space:pre-line}
menu{display:flex; gap:10px; margin:22px 0 0; padding:0; justify-content:flex-end; flex-wrap:wrap}
menu .spacer{margin-right:auto}
button.danger{color:var(--red); border-color:var(--red-line)}
button.danger:hover{border-color:var(--red); background:var(--red-bg)}

/* The M is fixed furniture, not something to type - it reads as one field. */
.stampbox{
  width:200px; max-width:100%; padding:11px 15px; font-size:20px; font-weight:700;
  font-family:inherit; letter-spacing:3px; text-transform:uppercase;
  border:1.5px solid var(--brand-ink); border-radius:var(--r-sm); background:var(--field); color:var(--ink);
}
.stampbox:focus{outline:0; box-shadow:0 0 0 3px rgba(31,122,63,.22)}

/* A native date input always renders in the browser's locale format, so the
   visible box stays plain text in YYYYMMDD and the calendar lives behind a
   button that writes back in that same format. */
.datefield{display:flex; align-items:stretch}
.datefield input{flex:1; min-width:0; border-radius:var(--r-sm) 0 0 var(--r-sm) !important}
.datefield .calbtn{
  border:1.5px solid var(--line); border-left:0; border-radius:0 var(--r-sm) var(--r-sm) 0;
  background:var(--field); cursor:pointer; padding:0 13px; font-size:17px; line-height:1;
}
.datefield .calbtn:hover{border-color:var(--accent); background:var(--brand-soft)}

/* ---------------------------------------------------------- sign-in gate -- */
#loginWrap{
  position:fixed; inset:0; z-index:100; display:flex; align-items:center; justify-content:center;
  background:radial-gradient(1100px 620px at 22% 8%, var(--brand-lift) 0%, var(--brand) 42%, var(--brand-deep) 100%);
  padding:20px;
}
#loginWrap[hidden]{display:none}
.loginbox{
  background:var(--card); border-radius:var(--r-lg); padding:34px; width:100%; max-width:394px;
  box-shadow:0 24px 70px rgba(0,0,0,.4);
}
.loginmark{display:flex; align-items:center; gap:12px; margin-bottom:20px}
.loginmark img{width:42px; height:42px; border-radius:10px}
.loginbox h1{margin:0; font-size:20px; color:var(--brand-ink); font-weight:700; letter-spacing:-.2px}
.loginbox p{margin:0 0 24px; color:var(--muted); font-size:14px}
.loginbox label{display:block; font-weight:650; font-size:12.5px; margin:0 0 6px; color:var(--muted);
                text-transform:uppercase; letter-spacing:.4px}
.loginbox input{width:100%; margin-bottom:17px}
.loginbox input[type=password]{
  padding:12px 14px; font-size:17px; font-family:inherit; letter-spacing:2px;
  border:1.5px solid var(--line); border-radius:var(--r-sm); background:var(--field); color:var(--ink);
}
.loginbox input[type=password]:focus{outline:0; border-color:var(--accent); box-shadow:0 0 0 3px rgba(31,122,63,.18)}
.loginbox button{width:100%}
.loginbox .flag{margin:0 0 14px}

/* ============================================================== responsive */
@media (max-width:900px){
  body{grid-template-columns:minmax(0,1fr); grid-template-areas:"header" "nav" "main"}
  /* The menu bar is FIXED to the top, and the page is padded down to clear it.
     position:sticky does not work here: nav is a grid item, so its containing
     block is its own grid area - exactly its own height - and it has nowhere to
     travel. Fixed takes it out of flow, which collapses that grid row, hence the
     padding on body rather than a margin anywhere else.

     It carries the brand green, the same surface as the desktop rail. On the
     card color it read as a heading sitting above the content rather than as
     the navigation, which is the whole reason it was easy to miss. */
  body{padding-top:var(--navbar-h)}
  nav{
    display:block; position:fixed; top:0; left:0; right:0; z-index:70;
    height:var(--navbar-h); padding:0; overflow:visible;
    background:linear-gradient(180deg,var(--brand) 0%,var(--brand-deep) 100%);
    border-right:0; border-bottom:1px solid var(--brand-deep);
    box-shadow:0 1px 6px rgba(0,0,0,.18);
  }
  /* Scope this to the nav. `brand` is also a TILE TONE (.tile.brand), so a bare
     `.brand{display:none}` here silently deleted the first three Overview tiles
     on every phone while looking perfectly correct on the desktop. */
  nav .brand{display:none}                    /* the header already says the name */

  /* The closed state: the hamburger, then the tab you are on. The icon is the
     part that says "menu"; the words only say where you are, and on an internal
     page those words sat directly above the page's own heading and read as a
     second one. */
  .navtoggle{
    display:flex; align-items:center; gap:14px;
    width:100%; height:var(--navbar-h); border:0; background:none;
    font-family:inherit; padding:0 16px; font-size:15.5px; font-weight:650;
    color:#fff; text-align:left; cursor:pointer;
  }
  .navtoggle:active{background:rgba(0,0,0,.12)}
  .navtoggle:focus-visible{outline:2px solid #7fd699; outline-offset:-3px}

  /* Drawn, not a glyph: ☰ renders at wildly different weights across phones,
     and this has to read as an icon at a glance. */
  .burger{
    position:relative; flex:0 0 auto; width:19px; height:2px;
    background:currentColor; border-radius:2px; transition:background .15s;
  }
  .burger::before,.burger::after{
    content:""; position:absolute; left:0; width:19px; height:2px;
    background:currentColor; border-radius:2px; transition:transform .15s;
  }
  .burger::before{top:-6px}
  .burger::after{top:6px}
  /* Open, it becomes the close control it now is - so one button never shows
     two different meanings without saying which it is in. */
  nav.open .burger{background:transparent}
  nav.open .burger::before{transform:translateY(6px) rotate(45deg)}
  nav.open .burger::after{transform:translateY(-6px) rotate(-45deg)}

  /* The panel floats OVER the page rather than pushing it down, so opening the
     menu never shifts the thing you were looking at. It scrolls on its own if a
     short screen cannot hold every tab. */
  .navmenu{
    display:none; position:absolute; left:0; right:0; top:100%; z-index:60;
    flex-direction:column; padding:6px 0;
    background:var(--card); border-bottom:1px solid var(--line);
    box-shadow:var(--shadow);
    max-height:calc(100vh - var(--navbar-h)); overflow-y:auto;
    -webkit-overflow-scrolling:touch;
  }
  nav.open .navmenu{display:flex}
  .navmenu button{
    width:100%; text-align:left; color:var(--ink); border-radius:0;
    padding:13px 16px; border-left:3px solid transparent; white-space:nowrap;
    min-height:46px;
  }
  /* 🔴 `nav button.quiet` outranks `.navmenu button` - one class and two elements
     against one class and one - so EVERY property the rail sets has to be undone
     here, not just the ones somebody happened to notice.

     The padding was caught when the second tier came out 36px tall. The COLOR
     was not: the rail paints quiet items #a8c9b5, a pale green chosen to sit on
     a dark green rail, and this panel is `--card` - white in the light theme.
     That put the whole second tier at about 1.9:1 on white. Half the menu was
     legible and half was not, which reads as a rendering fault rather than a
     hierarchy.

     BOTH tiers are --ink, and the hierarchy is carried by size and weight
     instead of by color. Painting the second tier --muted fixed the light theme
     and left it at 3:1 in the dark one - a token tuned for secondary prose on a
     card is not a safe color for the only label a menu item has. Quiet is about
     ranking against the item above it, never about being hard to read against
     the page, and weight says that in both themes without a second measurement. */
  .navmenu button.quiet{
    padding:13px 16px; font-size:14.5px; font-weight:500; color:var(--ink);
  }
  .navmenu button:hover{background:var(--brand-soft); color:var(--ink)}
  .navmenu button.on{
    background:var(--brand-soft); color:var(--brand-ink); font-weight:650;
    border-left-color:var(--brand-ink);
  }
  .navmenu button.on::before{display:none}
  /* The same divider, in the panel's own line color - the rail's white-on-green
     rule would be invisible against a card background. */
  .navgroup{margin:6px 0; border-top-color:var(--line)}

  /* Theme / Install / Sign out, moved down here by placeTools(). They become
     rows in the list rather than buttons floating in it: the header was costing
     two whole rows of a phone screen before any content, and these belong with
     the other things you go and do. Same 46px target as every tab above. */
  .menutools{display:block}
  .menutools .headertools{flex-direction:column; gap:0; align-items:stretch}
  .menutools .headerbtn{
    width:100%; min-height:46px; padding:13px 16px; font-size:14.5px;
    font-weight:500; text-align:left; border:0; border-radius:0;
    border-left:3px solid transparent; background:none; color:var(--muted);
  }
  .menutools .headerbtn:hover{background:var(--brand-soft); color:var(--ink)}
  main{padding:18px 16px 34px}
  header{padding:12px 16px}
  .offline{padding:9px 16px}
}

@media (max-width:700px){
  main{padding:12px 12px 30px}
  /* Tighter cells so the table needs less sideways swiping, and finger-sized
     action buttons - 21px targets are not hittable on a phone. */
  th{font-size:11px; padding:9px 7px; letter-spacing:.2px}
  td{padding:8px 7px; font-size:13px}
  .act{padding:9px 8px; font-size:13px; display:inline-block; min-height:34px; line-height:18px}
  td.actions{white-space:nowrap}
  .pill{font-size:11px; padding:3px 8px}
  .locsel{font-size:13px; padding:7px 20px 7px 5px; min-height:34px}
  .notebtn{min-height:34px; padding:7px 10px}
  /* On a phone these controls stack, and mismatched heights turned that stack
     into a tall band of mostly empty space above the list. One height for
     every control, tight gaps, and the running count rides along at the end of
     the last row instead of claiming a row of its own. */
  /* "Print change log" is 142px of a 317px row. The extra words earn their
     place on a desktop and cost a whole row on a phone, where the button sits
     in the toolbar of the thing it prints anyway. */
  .longlabel{display:none}
  .toolbar{gap:6px; margin-bottom:10px}
  .toolbar button,.toolbar .toolsel,.toolbar input[type=search]{
    height:38px; min-height:38px; padding-top:0; padding-bottom:0;
    display:inline-flex; align-items:center;
  }
  .toolbar button,.toolbar .toolsel{justify-content:center; flex:0 1 auto}
  .toolbar .toolsel{flex:1 1 130px; min-width:0}
  #hCount,#listCount{margin:0 0 0 auto; font-size:13px; white-space:nowrap}
  /* Search gets its own row; the report picker and Print share the next one. */
  .toolbar input[type=search]{width:100%; flex:1 1 100%; min-width:0}
  .toolbar select{flex:1 1 auto; width:auto; min-width:0}
  .toolbar button{flex:0 0 auto}
  /* The Purge Status pill goes on a phone - the colored dot and the Days
     column already carry that signal, and it buys back a whole column. */
  .col-due{display:none}
  .card{padding:16px}
  header h1{font-size:15px}
  header .count{margin-left:0; width:100%; font-size:12.5px; text-align:left}
  .field{max-width:none}
  /* The board sizes itself at this width - see the flight-test block. */
  .r-ser{min-width:0}
  .r-where{display:none}
  .grid{grid-template-columns:1fr}          /* dialogs go single column */
  dialog{max-width:none; width:calc(100% - 18px)}
  .dlg{padding:19px}
  menu{flex-direction:column-reverse}
  menu button{width:100%}
  menu .spacer{margin-right:0}
  th{position:static}                        /* sticky headers fight touch scroll */
}


/* ===================================================== phone card layout ==
   A ten-column table on a 375px screen means dragging the list sideways to
   read one aircraft, which is how you misread a tail number. Below 760px each
   row becomes its own card with the column name printed against each value,
   and nothing scrolls sideways at all. The header row is dropped because every
   value now carries its own label. Print undoes all of this - see the print
   block, which re-asserts table display. */
@media (max-width:760px){
  .tablewrap{overflow-x:visible}
  #v-list table,#v-list tbody,#v-list tr,#v-list td,
  #v-history table,#v-history tbody,#v-history tr,#v-history td,
  #v-people table,#v-people tbody,#v-people tr,#v-people td{display:block; width:auto}
  #v-list thead,#v-history thead,#v-people thead{display:none}

  #v-list tr,#v-history tr,#v-people tr{
    background:var(--card); border:1px solid var(--line); border-left:4px solid var(--line);
    border-radius:var(--r); margin-bottom:10px; padding:11px 13px; box-shadow:var(--shadow-sm);
  }
  #v-list tbody tr:hover,#v-history tbody tr:hover,#v-people tbody tr:hover{background:var(--card)}

  /* The card's left edge carries the color the row tint used to. */
  #v-list tr.overdue  {border-left-color:var(--red-deep)}
  #v-list tr.d30      {border-left-color:var(--amber-mid)}
  #v-list tr.cryo     {border-left-color:var(--blue)}
  #v-list tr.missing  {border-left-color:var(--ink)}
  #v-list tr.nobottle {border-left-color:var(--purple)}
  #v-list tr.badbottle{border-left-color:var(--red-deep)}
  #v-list tr.unreadable{border-left-color:#5f1512}
  #v-list tr.overdue td,#v-list tr.d30 td,#v-list tr.d90 td,#v-list tr.unreadable td,
  #v-list tr.cryo td,#v-list tr.nobottle td,#v-list tr.badbottle td,
  #v-list tr.missing td{background:transparent}
  /* The credit survives onto paper; the contact details do not, because a
     printed list is a working document and the number is a click away on screen. */
  .sitefoot{margin:10px 0 0; padding:6px 0 0; font-size:9px; border-top:1px solid #ddd;
            justify-content:flex-start; color:#666}

  #v-list td,#v-history td,#v-people td{
    border:0; padding:3px 0; font-size:14px;
    display:flex; gap:10px; align-items:baseline;
  }
  #v-list td::before,#v-history td::before,#v-people td::before{
    content:attr(data-label); flex:0 0 112px; min-width:112px;
    color:var(--muted); font-size:11px; font-weight:700;
    text-transform:uppercase; letter-spacing:.4px; line-height:1.5;
  }
  /* An empty cell prints an orphan label, so drop it entirely. */
  #v-list td:empty,#v-history td:empty,#v-people td:empty{display:none}
  #v-list td.dotcell{display:none}          /* the card edge already says this */

  #v-list td.actions,#v-history td.actions,#v-people td.actions{
    display:flex; gap:4px; justify-content:flex-start; text-align:left;
    margin-top:9px; padding-top:9px; border-top:1px solid var(--line-soft);
  }
  #v-list td.actions::before,#v-history td.actions::before,#v-people td.actions::before{content:none}
  #v-people td.actions .act{flex:1 1 auto; text-align:center; background:var(--brand-soft); min-height:38px}
  #v-people td[data-label="Stamp"]{font-size:17px; font-weight:700; padding-bottom:6px}
  #v-list td.actions .act{
    flex:1 1 auto; text-align:center; background:var(--brand-soft); min-height:38px;
  }
  /* The tail is the identifier - it gets to be the card's headline. */
  #v-list td[data-label="Acft Serial #"]{font-size:17px; font-weight:700; padding-bottom:6px}
  .col-due{display:none}
  .empty{padding:30px 12px}
}

/* ========================================== iOS safe areas (standalone) ====
   This does nothing at all until the app has been added to the home screen and
   is running standalone. Then it is the difference between an app and a web
   page in a costume.

   The page sends `viewport-fit=cover` and a translucent status bar, which is
   what lets the green nav run to the very top of the screen instead of leaving
   a grey band above it. The cost of that is that the VIEWPORT now extends under
   the Dynamic Island, under the status bar, and under the home indicator.
   Nothing here accounted for it, so on an installed copy the hamburger sat
   behind the clock and the bottom of every list sat behind the home bar.

   Three things worth knowing about the shape of this block:

   1. In a browser tab, on every desktop, and on paper, each env() below is 0px
      and every rule computes to exactly the padding it already had. This is a
      no-op everywhere except an installed iOS copy.

   2. These are LONGHANDS placed after the shorthands they adjust, deliberately.
      A browser too old to understand env() throws the whole declaration away -
      so it falls back to the padding already set rather than to nothing.

   3. LANDSCAPE is not an afterthought. The notch takes its bite out of one SIDE
      when the phone is turned, and a list read sideways is exactly how somebody
      ends up with a tail number under the camera housing. Every rule below
      carries the left and right insets, not just the top and bottom. */

/* ---- wide: nav is the left rail, header and main are the right column ---- */
@media (min-width:901px){
  nav{
    padding-top:env(safe-area-inset-top,0px);
    padding-left:calc(12px + env(safe-area-inset-left,0px));
    padding-bottom:calc(18px + env(safe-area-inset-bottom,0px));
  }
  header{
    padding-top:calc(13px + env(safe-area-inset-top,0px));
    padding-right:calc(26px + env(safe-area-inset-right,0px));
  }
  main{
    padding-right:calc(26px + env(safe-area-inset-right,0px));
    padding-bottom:calc(40px + env(safe-area-inset-bottom,0px));
  }
  .offline{padding-right:calc(26px + env(safe-area-inset-right,0px))}
}

/* ---- phone: the nav bar is FIXED at top:0, so it is the element that has to
        grow upward. It keeps its 52px of usable height and the inset is added
        ON TOP, filled with the same green - so the status bar reads as part of
        the app rather than as a strip laid over it. The body padding that
        clears the bar has to grow by exactly the same amount or the header
        slides underneath it. --------------------------------------------- */
@media (max-width:900px){
  nav{
    height:calc(var(--navbar-h) + env(safe-area-inset-top,0px));
    padding-top:env(safe-area-inset-top,0px);
  }
  body{padding-top:calc(var(--navbar-h) + env(safe-area-inset-top,0px))}
  .navtoggle{
    padding-left:calc(16px + env(safe-area-inset-left,0px));
    padding-right:calc(16px + env(safe-area-inset-right,0px));
  }
  /* The panel hangs off the BOTTOM of the bar, so it already starts below the
     inset - it only has to give back the height the inset took, or a long menu
     runs off the end of a short screen. Its own bottom padding is what lets the
     last tab clear the home indicator once the list scrolls. */
  .navmenu{
    max-height:calc(100vh - var(--navbar-h) - env(safe-area-inset-top,0px));
    padding-bottom:calc(6px + env(safe-area-inset-bottom,0px));
  }
  /* No top inset on the header: the fixed bar above it already absorbed it. */
  header{
    padding-left:calc(16px + env(safe-area-inset-left,0px));
    padding-right:calc(16px + env(safe-area-inset-right,0px));
  }
  main{
    padding-left:calc(16px + env(safe-area-inset-left,0px));
    padding-right:calc(16px + env(safe-area-inset-right,0px));
    padding-bottom:calc(34px + env(safe-area-inset-bottom,0px));
  }
  .offline{
    padding-left:calc(16px + env(safe-area-inset-left,0px));
    padding-right:calc(16px + env(safe-area-inset-right,0px));
  }
}

@media (max-width:700px){
  main{
    padding-left:calc(12px + env(safe-area-inset-left,0px));
    padding-right:calc(12px + env(safe-area-inset-right,0px));
    padding-bottom:calc(30px + env(safe-area-inset-bottom,0px));
  }
}

/* A dialog is centred by auto margins, but `width:calc(100% - 32px)` leaves it
   16px a side and the landscape notch takes 44 - so one edge of every dialog
   slid under the camera housing with the phone turned. Taking the insets out of
   the WIDTH keeps the auto margins doing the centring.

   Vertically they are left alone on purpose: a dialog is centred rather than
   anchored to the bottom, and the browser's own max-height already holds it off
   the bottom edge. Padding every dialog down by the home indicator would make
   the short ones - which are most of them - look bottom-heavy to fix a case
   they never hit. */
dialog{
  width:calc(100% - 32px
             - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px));
}
@media (max-width:700px){
  dialog{
    width:calc(100% - 18px
               - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px));
  }
}

/* The sign-in gate is fixed to all four edges and centres its box, so it is the
   one element that can be clipped on any side at once. */
#loginWrap{
  padding-top:calc(20px + env(safe-area-inset-top,0px));
  padding-right:calc(20px + env(safe-area-inset-right,0px));
  padding-bottom:calc(20px + env(safe-area-inset-bottom,0px));
  padding-left:calc(20px + env(safe-area-inset-left,0px));
}


/* =================================================================== print */
.printhead{display:none}
@media print{
  /* Landscape by default. These sheets are wide - eleven columns on the Inducted
     list - and portrait was squeezing them to the point where the Notes column
     was a word per line. `size` sets what the print dialog opens on; anybody who
     wants portrait can still change it there. */
  @page{size:landscape; margin:12mm}

  /* Paper is white whatever the screen is doing. The tokens are forced back to
     the light palette here, or a dark-mode user prints pale gray text on nothing
     and empties a toner cartridge finding out. */
  :root, :root[data-theme="dark"]{
    --ink:#131923; --muted:#5c6a7d; --faint:#8592a3;
    --line:#e0e6ef; --line-soft:#eef2f7; --bg:#fff; --card:#fff;
    --brand-ink:#12572c; --brand-soft:#e6f2e9; --accent:#1f7a3f;
    --red:#b3261e; --red-bg:#fdecea; --red-deep:#8c1d18;
    --amber:#8a5a00; --amber-bg:#fff4d6; --amber-mid:#c98a00;
    --green:#1c6b3c; --green-bg:#e6f4ea;
    --blue:#14567a; --blue-bg:#e2f0f7;
    --purple:#6b3fa0; --purple-bg:#f1eafb;
    --field:#fff; --hover:#fff; --hover-soft:#fff;
    --missing-bg:#e6e8ec; --unreadable:#5f1512;
    --amber-line:#e8d59a; --green-line:#bfe0c9; --red-line:#f0c8c4;
    --chip-red:#8c1d18; --chip-amber:#8a5e08; --chip-blue:#14567a;
    --chip-purple:#6b3fa0; --chip-green:#1c6b3c;
  }
  :root[data-theme="dark"] .pill.missing{background:var(--ink) !important; color:#fff !important}
  /* The grid is screen furniture. On paper the content is the whole page.
     padding-top is reset explicitly: a printed page is around 816px wide, which
     MATCHES the 900px phone query, so the space reserved for the fixed menu bar
     would otherwise be printed as a blank strip at the top of every sheet. */
  body{display:block; background:#fff; font-size:11px; padding-top:0}
  .tablewrap{overflow:visible}
  header,nav,.toolbar,.noprint,.offline,.card>h2,.card>p.sub{display:none !important}
  /* Ctrl+P on the Overview used to give a near-blank sheet. It now prints as a
     summary: the counts as a line of text, then the Hot List. */
  /* The Overview does not print - the Hot List sheet carries these figures
     in its heading instead. The old tile print rules went with the tiles. */


  .locsel{border:0 !important; background:none !important; padding:0 !important; appearance:none}
  /* The report's own cover, page one. */
  .listcover{display:block; break-after:page; page-break-after:always}
  .printonly{display:inline}
  .col-due{display:table-cell !important}   /* paper always gets every column */
  main{padding:0; max-width:none}
  .card{border:0; padding:0; margin:0; box-shadow:none}
  .printhead{display:block; margin-bottom:10px}
  .printhead h2{margin:0 0 3px; font-size:16px; color:var(--brand)}
  .printhead .meta{font-size:10px; color:#333}
  .printhead .warn{font-size:10px; color:var(--red); font-weight:700; margin-top:3px}
  /* Paper is never a phone. Undo the card layout explicitly so a narrow print
     width can never leave the lists stacked. */
  #v-list table,#v-history table,#v-people table{display:table !important; width:100% !important}
  #v-list thead,#v-history thead,#v-people thead{display:table-header-group !important}
  #v-list tbody,#v-history tbody,#v-people tbody{display:table-row-group !important}
  #v-list tr,#v-history tr,#v-people tr{display:table-row !important; border:0 !important; padding:0 !important;
     margin:0 !important; box-shadow:none !important; background:none !important}
  #v-list td,#v-history td,#v-people td{display:table-cell !important; padding:4px 6px !important}
  #v-list td::before,#v-history td::before,#v-people td::before{content:none !important}
  #v-list td.dotcell{display:table-cell !important}
  th{background:var(--brand) !important; color:#fff !important; font-size:9.5px; padding:5px 6px;
     position:static;
     -webkit-print-color-adjust:exact; print-color-adjust:exact}
  td{padding:4px 6px; font-size:10.5px}
  tr.overdue td,tr.d30 td,tr.d90 td,tr.cryo td,tr.nobottle td,tr.badbottle td,.pill,.dot{
    -webkit-print-color-adjust:exact; print-color-adjust:exact}
  .act{display:none}
}

@media (prefers-reduced-motion:reduce){
  *{transition:none !important}
}
