/* app.css - the parts of the tool's look that are NOT page-specific.
 *
 * Six screens, six <style> blocks, and the shared vocabulary copied into each one. That is how
 * Leads ended up sizing in px while Jobs, Subs and Admin sized in rem, and how #e9e7f0 and
 * #efedf6 came to exist as hand-rolled near-misses of --mute-bg. Every page links this first,
 * then declares only what is its own, so a page-specific rule still wins on specificity and
 * source order.
 *
 * WHAT IS NOT IN HERE, DELIBERATELY. .card, .chip, .badge and .empty read as shared and are
 * not: .chip is a filter pill on Admin and a label badge on Leads, .badge is a status word on
 * Subs and a red count bubble on Leads, .card is a list row everywhere except setup.html where it
 * is the login panel. Same name, different component. Merging them means renaming classes in six
 * files of page JS, so they stay where they are until that is worth doing.
 *
 * .btn and .mini WERE on that list and are not any more (2026-09-29): see "the buttons" below.
 * Nine variants restyled in eight <style> blocks is how a tap ended up with no pressed state.
 */

/* ---------------------------------------------------------------- design tokens
 * The union of what the six pages declared. Nothing here is a new value: every token that
 * appeared on more than one page already carried an identical value on every one of them. */
:root{
  --ink:#1c1840; --navy:#262161; --ink-soft:#5a5680;
  --paper:#f6f5f1; --card:#fff; --line:#dcd9e6;
  --accent:#b8923f; --accent-btn:#a17e35; --accent-ink:#8a6a2b;
  --ok:#2e7d32; --ok-bg:#e8f3e9;
  --bad:#b3261e; --bad-bg:#fbe9e7;
  --warn:#9a6b00; --warn-bg:#fdf3d9;
  --mute-bg:#eeecf4;
  /* Tints the Leads screen was carrying as raw hex before it was brought into line. */
  --accent-bg:#f4ecd9; --on-navy:#cfcbe8;
  /* The outline of a secondary button. --line is a divider (1.4:1 on white) and a button whose
     edge cannot be seen is not a button; this one is 3.4:1, the WCAG floor for a control. */
  --btn-line:#8b87a6;
}

/* ---------------------------------------------------------------- the site nav
 * ONE LOGIN, FIVE SCREENS. They always shared a session - /api/auth sets rl_session at Path=/
 * so it reaches /api/* and /jobs/* alike - there was simply no way to walk between them. */
#nav{display:flex;gap:.15rem;align-items:center;margin-left:auto;overflow-x:auto;scrollbar-width:none}
#nav::-webkit-scrollbar{display:none}
#nav a{font-size:.78rem;font-weight:600;color:rgba(255,255,255,.72);text-decoration:none;
  padding:.3rem .55rem;border-radius:.35rem;white-space:nowrap}
#nav a:hover{color:#fff;background:rgba(255,255,255,.12)}
#nav a[aria-current="page"]{color:#fff;background:rgba(255,255,255,.18)}
/* ---------------------------------------------------------------- the phone menu
 * A SCROLLING NAV IS NOT A MENU. Until now the phone answer was to let the six links overflow
 * and scroll sideways inside the bar, which meant "Sign out" sat off the right edge of every
 * screen and nothing told you it was there. Chris: "come up with a better solution for mobile.
 * maybe our burger bar from website".
 *
 * So: the same burger the marketing site uses - three bars that fold into a cross - and the same
 * behaviour, a panel that drops from under the header. Two differences, both because this bar is
 * navy where the site's is paper: the bars are white, and the panel is navy so it reads as the
 * header growing rather than a sheet landing on top of it.
 *
 * The panel is absolutely positioned against the sticky header rather than pinned to a hardcoded
 * top, so it stays welded to the bar whatever the bar ends up measuring.
 * visibility:hidden while closed keeps the links out of the keyboard tab order. */
.burger{display:none;position:relative;width:2.1rem;height:1.9rem;flex:0 0 auto;
  background:none;border:0;padding:0;cursor:pointer}
.burger span{position:absolute;left:.25rem;width:1.6rem;height:2px;background:#fff;border-radius:2px;
  transition:transform .35s cubic-bezier(.68,-.3,.32,1.3),width .35s ease,top .35s ease,left .35s ease}
/* 44px to press without changing how it looks: the bars stay where they were. */
.burger::after{content:'';position:absolute;inset:-.55rem -.4rem}
.burger span:nth-child(1){top:.45rem}
.burger span:nth-child(2){top:.9rem}
.burger span:nth-child(3){top:1.35rem}
.burger.open span:nth-child(1){top:.35rem;left:0;width:1.1rem;transform:rotate(-32deg);transform-origin:right center}
.burger.open span:nth-child(2){top:.35rem;left:1rem;width:1.1rem;transform:rotate(32deg);transform-origin:left center}
.burger.open span:nth-child(3){top:1.45rem;left:.3rem;width:1.5rem}

@media(max-width:620px){
  header h1{font-size:.95rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .burger{display:block}
  #nav{
    position:absolute;top:100%;left:0;right:0;z-index:19;
    flex-direction:column;align-items:stretch;gap:.1rem;margin-left:0;overflow:visible;
    background:var(--navy);border-bottom:1px solid rgba(255,255,255,.16);
    padding:.4rem .9rem calc(.6rem + env(safe-area-inset-bottom));
    transform:translateY(-130%);visibility:hidden;
    transition:transform .35s ease,visibility 0s .35s;
  }
  #nav.open{transform:translateY(0);visibility:visible;transition:transform .35s ease,visibility 0s}
  #nav a{font-size:.95rem;padding:.65rem .5rem;border-radius:.4rem}
  /* The current page is already named in the bar's title, so in the panel it only needs to be
     findable, not shouted at. */
  #nav a[aria-current="page"]{background:rgba(255,255,255,.14)}
}

/* ---------------------------------------------------------------- the navy bar
 * ONE BAR, ONE HEIGHT, FIVE SCREENS. Chris: "make header the same size on all pages".
 * It was 55px on Jobs, Admin and People, 64px on Subs because the action button grew it, and
 * 100px on Leads because Leads kept its view tabs inside the header. min-height fixes the bar to
 * the height its tallest occupant needs, so adding or removing a button no longer moves it, and
 * a screen with view tabs puts them in the body the way Admin already does. */
header{
  background:var(--navy); color:#fff; padding:.55rem .9rem;
  padding-top:calc(.55rem + env(safe-area-inset-top));
  min-height:calc(3.85rem + env(safe-area-inset-top));
  position:sticky; top:0; z-index:20;
  display:flex; align-items:center; gap:.6rem;
}
header h1{font-size:1.05rem;margin:0;font-weight:600;letter-spacing:.01em;flex:1;line-height:1.25}
header .btn{white-space:nowrap}
@media(max-width:620px){header .btn{padding:.5rem .6rem}}

/* ---------------------------------------------------------------- the buttons
 * Chris, 2026-09-29: "the buttons in the tool are unreactive and hard to use", and Nat "uses
 * largely on mobile". Three faults, all real: a tap showed nothing until the request had started,
 * the small ones were 24px tall with a red x sitting against the action beside it, and there were
 * two overlapping systems (.btn and .mini) with nine variants restyled in eight <style> blocks.
 *
 * THREE KINDS AND ONE SIZE. Every page draws the same button from here now.
 *   primary      filled gold                          .btn            (.btn.primary)
 *   secondary    ink on white, visible outline        .btn.secondary  (was .ghost, .quiet, .blocked)
 *   destructive  filled red                           .btn.danger
 *                outlined red, for a delete that sits among other controls: .btn.danger.secondary,
 *                or the old .del
 *   size         default, or small                    .sm             (was .mini, .small)
 * The old names still work: the HTML strings the pages build in JS were not rewritten, they are
 * aliases here. A bare .mini (no .btn) is what Jobs used inside a row and means .btn.sm.
 *
 * The four custom properties are the whole of a variant, so a new kind is four lines, not a
 * rewrite of hover, pressed and disabled.
 *
 * PRESSED IS THE POINT. :active darkens and nudges 1px in 80ms, so a tap answers before the
 * network does. touch-action removes the double-tap delay and the grey iOS flash is switched
 * off, because ours replaces it. */
.btn,.mini{
  --b-bg:var(--accent-btn); --b-fg:#fff; --b-bd:var(--accent-btn);
  --b-bg-on:var(--accent-ink); --b-bd-on:var(--accent-ink);
  display:inline-flex; align-items:center; justify-content:center; text-align:center;
  font:inherit; font-size:.85rem; font-weight:600; line-height:1.25;
  padding:.5rem .85rem; border-radius:7px; border:1px solid var(--b-bd);
  background:var(--b-bg); color:var(--b-fg); text-decoration:none; cursor:pointer;
  touch-action:manipulation; -webkit-tap-highlight-color:transparent;
  transition:background-color .08s ease,border-color .08s ease,transform .08s ease;
}
.btn.secondary,.btn.ghost,.btn.quiet,.btn.blocked,.mini.secondary,.mini.ghost,.mini.quiet{
  --b-bg:var(--card); --b-fg:var(--ink); --b-bd:var(--btn-line);
  --b-bg-on:var(--mute-bg); --b-bd-on:var(--ink-soft);
}
.btn.blocked{--b-bg:var(--mute-bg); --b-fg:var(--ink-soft); border-style:dashed}
.btn.danger,.mini.danger{
  --b-bg:var(--bad); --b-fg:#fff; --b-bd:var(--bad); --b-bg-on:#8f1d17; --b-bd-on:#8f1d17;
}
.btn.danger.secondary,.btn.danger.ghost,.btn.danger.quiet,.btn.del,.mini.del{
  --b-bg:var(--card); --b-fg:var(--bad); --b-bd:var(--bad); --b-bg-on:var(--bad-bg); --b-bd-on:var(--bad);
}
/* Small. Denser than the default on a desktop, brought back up to a finger in the phone block. */
.btn.sm,.btn.mini,.btn.small,.mini{font-size:.78rem;padding:.3rem .6rem;border-radius:6px}
.mini{white-space:nowrap}
.btn:disabled,.mini:disabled{opacity:.5;cursor:not-allowed}
@media(hover:hover){
  .btn:hover:not(:disabled),.mini:hover:not(:disabled){filter:brightness(.94)}
}
.btn:active:not(:disabled),.mini:active:not(:disabled){
  background:var(--b-bg-on); border-color:var(--b-bd-on); transform:translateY(1px) scale(.985);
  filter:none;
}
.btn:focus-visible,.mini:focus-visible{outline:2px solid var(--navy); outline-offset:2px}
/* WORKING. busy() in app.js sets this. Faded like a disabled button would read as "cannot", so it
   stays nearly full strength and shows a spinner instead. */
.is-busy{cursor:progress}
.btn.is-busy:disabled,.mini.is-busy:disabled{opacity:.85;cursor:progress}
.is-busy::before{content:'';display:inline-block;flex:none;width:.95em;height:.95em;margin-right:.45em;
  border:2px solid currentColor;border-right-color:transparent;border-radius:50%;
  vertical-align:-.12em;animation:btnspin .7s linear infinite}
/* The little in-row marks (a flag, an x) have no room for a spinner; they fade while they work. */
button:not(.btn):not(.mini).is-busy::before{display:none}
button:not(.btn):not(.mini).is-busy{opacity:.55}
@keyframes btnspin{to{transform:rotate(360deg)}}
@keyframes btnpulse{50%{opacity:.3}}
/* No movement for anyone who asked for none: no nudge on press, and the spinner pulses instead of
   turning. */
@media(prefers-reduced-motion:reduce){
  .btn,.mini{transition:none}
  .btn:active:not(:disabled),.mini:active:not(:disabled){transform:none}
  .is-busy::before{animation:btnpulse 1.2s ease-in-out infinite;border-right-color:currentColor}
}
/* EVERY OTHER BUTTON (tabs, filter chips, the little in-row icons) gets the same answer to a
   press, lighter: it darkens and gives a little. */
button{touch-action:manipulation;-webkit-tap-highlight-color:transparent;
  transition:filter .08s ease,transform .08s ease}
button:not(.btn):not(.mini):active:not(:disabled){filter:brightness(.9);transform:scale(.97)}
@media(prefers-reduced-motion:reduce){
  button{transition:none}
  button:not(.btn):not(.mini):active:not(:disabled){transform:none}
}
/* THE NAVY BAR is the one place a secondary button cannot be ink on white. */
header .btn.secondary,header .btn.ghost,header .btn.quiet{
  --b-bg:transparent; --b-fg:#fff; --b-bd:rgba(255,255,255,.55);
  --b-bg-on:rgba(255,255,255,.2); --b-bd-on:#fff;
}
/* PHONE. Nat works from a bathroom with a thumb: 44px to press, at least .85rem to read, and a
   row of buttons is a stack with room between them so the wrong one is never the near one. */
@media(max-width:620px){
  .btn,.mini{min-height:44px;padding:.6rem 1rem;font-size:.9rem}
  .btn.sm,.btn.mini,.btn.small,.mini{font-size:.85rem;padding:.55rem .9rem;white-space:normal}
  .acts,.dacts,.coacts,.lacts{display:flex;flex-wrap:wrap;gap:.6rem}
  .acts>.btn,.acts>.mini,.dacts>.btn,.dacts>.mini,.coacts>.btn,.coacts>.mini,.lacts>.btn{flex:1 1 100%}
  .acts>.meta,.dacts>.meta{flex:1 1 100%}
  dialog .actions,dialog .dacts,dialog .acts{display:flex;flex-wrap:wrap;gap:.6rem}
  dialog .actions>.btn,dialog .dacts>.btn,dialog .acts>.btn,dialog .acts>.mini{flex:1 1 calc(50% - .3rem)}
}

/* ---------------------------------------------------------------- ask / tell
 * THE BROWSER'S OWN DIALOG IS NOT OURS. confirm() and alert() render Safari's grey system box,
 * titled with the netlify hostname, in a font and at a width the tool does not control - which
 * is why "Delete this photo?" looked like it belonged to a different program. Every one of the
 * 50 of them lived in jobs.html.
 *
 * Same card as the tool's other dialogs: white, 12px, the navy backdrop. A destructive question
 * gets a red confirm button, because the one thing a system dialog genuinely did well was make
 * Delete feel different from OK, and losing that would be a step back. */
dialog.ask{border:0;border-radius:12px;padding:0;max-width:26rem;width:calc(100% - 1.8rem);
  background:var(--card);color:var(--ink);box-shadow:0 10px 40px rgba(28,24,64,.25)}
dialog.ask::backdrop{background:rgba(28,24,64,.45)}
.ask .msg{padding:1.15rem 1.15rem .35rem;font-size:.95rem;line-height:1.5;white-space:pre-wrap;
  word-break:break-word;margin:0}
/* The first line of these messages is the question; what follows is the consequence. */
.ask .msg strong{display:block;font-weight:600;margin-bottom:.5rem}
.askfield{padding:.15rem 1.15rem 0}
.askfield input,.askfield textarea{width:100%;font:inherit;font-size:16px;padding:.55rem .65rem;
  border:1px solid var(--line);border-radius:.4rem;background:var(--card);color:var(--ink)}
.askfield textarea{min-height:4.5rem;resize:vertical}
/* askPick's boxes (a void's work orders, dead-work-orders.mjs): each one a line of its own, the box beside its first line. */
.askpick{padding:.15rem 1.15rem 0}
.askpick label{display:flex;gap:.55rem;align-items:flex-start;margin:.35rem 0;font-size:.92rem;line-height:1.4;cursor:pointer}
.askpick input{margin-top:.2rem;flex:none;width:1.05rem;height:1.05rem}
.ask .acts{display:flex;gap:.5rem;justify-content:flex-end;padding:.9rem 1.15rem 1.15rem}
.ask .acts .btn{min-width:5rem}

/* ---------------------------------------------------------------- dialog, gate, errors
 * Identical on every page that has them. */
.dlg{padding:1rem}
dialog::backdrop{background:rgba(28,24,64,.45)}
.gate form{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:1.4rem;width:100%;max-width:20rem}
.gate.hidden{display:none}
.err{color:var(--bad);font-size:.82rem;min-height:1.1em;margin-top:.4rem}

/* ACTING AS SOMEBODY ELSE. Fixed to the bottom rather than the top: every screen already has a
   sticky header and pushing one down moves the whole page under it, which is how a warning bar
   ends up being switched off. Bottom is out of the way and still impossible to miss. */
.actingbar{position:fixed;left:0;right:0;bottom:0;z-index:60;display:flex;gap:.7rem;
  align-items:center;justify-content:center;flex-wrap:wrap;
  padding:.55rem .9rem;background:#5B2333;color:#fff;font-size:.82rem;
  box-shadow:0 -2px 10px rgba(0,0,0,.18)}
.actingbar b{font-weight:700}
.actingbar button{font:inherit;font-weight:600;cursor:pointer;color:#5B2333;background:#fff;
  border:0;border-radius:.4rem;padding:.25rem .7rem}
.actingbar button:disabled{opacity:.6;cursor:default}
/* So the bar never sits on top of the last row of whatever screen you are on. */
body.has-actingbar{padding-bottom:3.2rem}
/* Stop is a button too: 44px on a phone, and the page keeps clear of the taller bar. */
@media(max-width:620px){
  .actingbar button{min-height:44px;padding:.4rem 1.1rem}
  body.has-actingbar{padding-bottom:4.8rem}
}

