/* ==========================================================================
   EXTRAFRESHBINS FIELD APP — the private portal for crews, foremen and the owner.

   Every colour, face and type step here comes from `tokens.css`, which is
   hand-written after tkati.com's assets/styles.css (it once claimed to be
   generated by a tools/app-tokens.mjs that does not exist). Nothing in this
   file invents a colour; a state reads --ok, --attn or --bad.

   BUT IT IS NOT THE MARKETING SITE, and copying its layout would be the wrong
   kind of consistency. The marketing site is read on a couch, at leisure, to
   decide whether to trust a contractor. This is read ON A PHONE, IN A YARD,
   IN JULY SUN, BY SOMEONE WEARING GLOVES, one-handed, often with no signal.
   Every departure below is that difference, and only that difference:

     · Tap targets are 56px minimum, 64px for the one button that matters.
       A gloved fingertip is roughly 14mm; the marketing site's 40px buttons
       are a miss waiting to happen when the miss costs a payroll entry.
     · Type does not go below --step-0, and --faint is used for NOTHING
       load-bearing. In direct sun a phone loses most of its usable contrast
       range, and 4.9:1 in a dark office is not 4.9:1 on a jobsite.
     · No hover-only affordance anywhere. There is no cursor here.
     · State is always said in words, never only in colour — a screen read in
       sun through polarised safety glasses can lose a hue entirely.
   ========================================================================== */

/* ---- 1. Shell ---------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

/* THIS RULE IS LOAD-BEARING AND WAS FOUND BY LOOKING, NOT BY REASONING.
   `hidden` is how every screen in this app is switched — signed-in vs signed-
   out, the empty state, the outbox lines, the access and gate notes. It works
   through the user-agent rule `[hidden] { display: none }`, which carries the
   specificity of a single attribute selector. The moment a rule below says
   `#signin { display: grid }`, an ID beats it and the hidden element renders.

   The first screenshot of this stylesheet showed the sign-in form AND the job
   list on screen together, which is a signed-out surface displaying a signed-
   in one. `!important` is correct here rather than lazy: nothing should ever
   out-rank an explicit instruction not to display something. */
[hidden] { display: none !important; }

html {
  /* Prevent iOS bumping text up a size in landscape, which reflows a card
     mid-tap. */
  -webkit-text-size-adjust: 100%;
}

/* ---- 0. DARK. ---------------------------------------------------------
   OWNER DECISION 2026-08-08: the portal is dark, matching the slate the logo
   sits on. Not a new palette — `--slate-deep` and `--slate` are the marketing
   site's own inverted-band colours and `--bone` is the type that already runs
   on them, so this is the same design system with the bands turned up rather
   than a second theme to keep in step.

   It also happens to be right for the job. This screen is read at 6am in a
   truck and at 2pm in full sun; a white page at 6am is a flashbulb, and the
   dark ground keeps the one bright thing on screen — the button you are
   reaching for — the thing your eye lands on.

   The surfaces are three steps apart so a card reads as raised without a
   shadow, which is what actually survives a phone at half brightness. */
:root {
  --app-bg:      var(--slate-deep);       /* the page: #070707 */
  /* Were lilac slabs (#15121C / #1E1A28) after being blue-greys before that.
     Now tkati.com's own raised levels, white at .035 and .055 over the
     canvas, still apart so a card reads as raised without a shadow. */
  --app-surface: var(--surface-1);        /* a card, one step up from the page */
  --app-raised:  var(--surface-2);        /* an inset inside a card */
  --app-text:    var(--bone);
  --app-mute:    var(--bone-mute);
  --app-line:    var(--bone-line);
  /* The accent, which is white now (tokens.css). A marker and a link; a
     state reads --ok, --attn or --bad instead. */
  --app-accent:  var(--aqua);
}

body {
  margin: 0;
  background: var(--app-bg);
  color: var(--app-text);
  font-family: var(--body);
  font-size: var(--step-0);
  line-height: 1.5;
  /* viewport-fit=cover is set in the markup, so the notch and the home
     indicator are ours to avoid. A Clock-in button under the home bar is a
     button that gets missed. */
  padding:
    max(1rem, env(safe-area-inset-top))
    max(1rem, env(safe-area-inset-right))
    max(1.5rem, env(safe-area-inset-bottom))
    max(1rem, env(safe-area-inset-left));
  -webkit-font-smoothing: antialiased;
}

#app { max-width: 34rem; margin: 0 auto; }

h1 {
  font-family: var(--display);
  font-size: var(--step-2);
  font-weight: 700;
  letter-spacing: -0.015em;
  margin: 0 0 0.25rem;
}

/* ---- 2. Status line ---------------------------------------------------
   The first thing on screen and the most important: it says whether this
   phone is online, working from cache, or signed out. It is a full band
   rather than a sentence because it must survive being glanced at. */

#status {
  margin: 0 0 1.25rem;
  padding: 0.85rem 1rem;
  border-radius: var(--r);
  background: var(--slate);
  color: var(--bone);
  font-weight: 600;
  font-size: var(--step-0);
}

#status[hidden] { display: none; }

/* ---- 3. Sign-in ---------------------------------------------------------

   THE ONE SCREEN THAT IS NOT A WORK SCREEN.

   Everything past section 5 is laid out for speed with gloves on. This is
   different: it is the only surface a person meets before they trust the
   system, and a stack of inputs on a flat colour says "internal tool nobody
   maintains". So it is composed — a photograph of work this company actually
   built on one side, the form on the other, over a ground with depth.

   PHOTOGRAPHY IS THE ARGUMENT, which is the marketing site's own thesis, and
   the portal is the same company. On a phone the panel is gone entirely: a
   crew signing in at 6am in a truck wants the field and nothing else, and a
   hero image above the fold on a small screen is a scroll before you can type.

   It borrows the site's own material rather than inventing a look: Archivo for
   display, the same green, the same button treatment. Inverted, because this
   screen opens at 6am and a white page then is a flashbulb.
   -------------------------------------------------------------------------- */

/* The ground is the canvas and nothing else. There were two wide washes here,
   a green and a blue-slate over a lilac #0A0810, and at (1,1,1) they beat
   auth.css's plain ground, so the sign-in wore a haze tkati.com does not. */
body:has(#signin:not([hidden])) { background: var(--app-bg); }

#signin {
  display: grid;
  gap: 1.25rem;
  justify-items: center;
  align-content: start;
  padding: min(9vh, 4rem) 0 2rem;
}

/* ---- the photograph ---- */
.auth-aside { display: none; }

.auth-card {
  width: min(26rem, 100%);
  padding: 1.9rem 1.7rem 1.6rem;
  border-radius: var(--r-lg);
  /* auth.css takes the card's material away (no fill, border or shadow);
     these are only its neutral defaults. */
  background: var(--surface-1);
  border: 1px solid var(--line);
  box-shadow: none;
}

/* ---- SPLIT, from the width where a form beside a picture stops being a
        compromise and starts being a composition. ---- */
@media (min-width: 62rem) {
  /* THE SPLIT NEEDS THE WHOLE VIEWPORT, and #app is capped at 34rem for the
     work screens — which is right for them and fatal here: the first attempt
     rendered a full-bleed composition inside a 545px column, and the headline
     was clipped mid-word. The cap and the body padding are lifted for this
     screen only, by :has() on the signed-out state, so the work screens keep
     their measure. */
  body:has(#signin:not([hidden])) { padding: 0; }
  body:has(#signin:not([hidden])) #app { max-width: none; margin: 0; }

  #signin {
    justify-items: stretch;
    padding: 0;
    min-height: 100vh;
    min-height: 100dvh;
    /* stretch, not center: with content-sized rows the photograph stopped
       short of the top and bottom and the composition read as a band floating
       on a dark page. The rows fill the viewport and the CARD is what centres,
       inside its own area. */
    align-content: stretch;
    grid-template-columns: 1.05fr 0.95fr;
    grid-template-rows: 1fr auto;
    grid-template-areas: 'aside card' 'aside foot';
    align-items: stretch;
    gap: 0;
  }

  .auth-aside {
    grid-area: aside;
    display: block;
    position: relative;
    align-self: stretch;
    overflow: hidden;
    border-right: 1px solid var(--line);
  }

  .auth-aside img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Slightly desaturated and darkened so the photograph is a GROUND, not a
       competitor. The form is what the person came here to use. */
    filter: saturate(0.85) contrast(1.02);
  }

  .auth-aside__scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(0deg, rgb(0 0 0 / 0.55) 0%, transparent 55%);
  }

  .auth-aside__line {
    position: absolute;
    left: clamp(2rem, 5vw, 4.5rem);
    right: clamp(2rem, 5vw, 4.5rem);
    bottom: clamp(2.5rem, 7vh, 5rem);
    margin: 0;
  }

  .auth-aside__line p {
    margin: 0;
    font-family: var(--display);
    font-size: var(--step-3);
    font-weight: 800;
    line-height: 1.02;
    letter-spacing: -0.025em;
    color: #FFFFFF;
    text-wrap: balance;
  }

  .auth-aside__line cite {
    display: block;
    margin-top: 0.9rem;
    font-style: normal;
    font-size: var(--step--1);
    font-weight: 650;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--aqua);
  }

  .auth-card {
    grid-area: card;
    align-self: center;
    width: min(25rem, 100%);
    margin: 0 auto;
    padding-inline: clamp(1rem, 3vw, 2rem);
    /* On the split there is no ground behind the card to lift off, so it drops
       its border and shadow and becomes the right-hand PANEL. A floating card
       inside a panel is one bevel too many. */
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
  }

  .auth-foot { grid-area: foot; text-align: center; padding-bottom: 2rem; }
}

/* ---- the card's contents ---- */
.auth-head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 1.6rem;
}

/* The real mark, and no plate: tkati.com's top bar puts it straight on the
   canvas. The plate was also what auth.css's white filter turned into a grey
   tile, since a filter whitens the element's background and border too. */
.auth-logo {
  width: 56px; height: 56px;
  flex: none;
  object-fit: contain;
}

.signin-title {
  margin: 0;
  font-size: var(--step-1);
  letter-spacing: -0.012em;
}

.signin-sub {
  margin: 0.15rem 0 0;
  color: var(--app-mute);
  font-size: var(--step--1);
  font-weight: 600;
}

#signin-form { display: grid; gap: 1rem; }
.field { display: grid; gap: 0.4rem; }

.field label {
  font-weight: 650;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--app-mute);
}

/* THE FIELDS ARE auth.css's, AND ONLY auth.css's. Three rules stood here —
   `#signin input[…]`, its placeholder and its :focus — and at (1,1,1) they
   beat every `.field input` rule auth.css writes, so the sign-in rendered a
   52px dark well with a 1.44:1 edge, lilac text and a purple 3px focus glow
   instead of the monochrome field the page was designed with. They are gone
   rather than recoloured: two stylesheets styling one input is how that
   happened. */

/* The running answer to "what account is this, exactly". Sits under the field
   in the muted voice, because it is confirmation rather than instruction — and
   it must not read as an error, since the common case is everything going
   right. `min-height` holds the line whether or not there is text in it, so the
   form does not jump under the thumb as somebody types their username. */
.field-hint {
  margin: 0;
  min-height: 1.3em;
  font-size: var(--step--1);
  color: var(--app-mute);
  font-variant-numeric: tabular-nums;
}
/* A username with a character an address cannot carry is a typo to fix, not
   a failure: the attention state. (auth.css repeats it at a specificity its
   own .field-hint cannot beat.) */
.field-hint--warn { color: var(--attn); }

/* "WE NEVER ASK FOR CARD DETAILS HERE" — written to be read, not skipped.
   Owner, 2026-08-20: "very sus/creapy ppl will think its a scam asking bank
   account". A hint in the muted voice is what the eye passes over, and this is
   the one line on the form whose whole job is to stop somebody abandoning it.
   So it takes the ordinary ink and a rule above it: quieter than a label, and
   not filed with the placeholder text.

   `min-height` is dropped because it is a real paragraph rather than a slot
   that fills in as somebody types — the reason the base rule holds a line is
   to stop the form jumping, and this one never changes. */
.field-hint--safe {
  min-height: 0;
  margin-top: 0.45rem;
  padding-top: 0.45rem;
  border-top: 1px solid var(--app-line);
  color: var(--app-text);
  line-height: 1.45;
  opacity: 0.82;
}

/* The recovery form shares the card with the sign-in form and takes its turn,
   so it needs the same grid and its own heading block — it has no logo row of
   its own, because the one in .auth-head above it never leaves. */
#recover-form { display: grid; gap: 1rem; }
.recover-head { display: grid; gap: 0.15rem; }

#forgot { justify-self: start; }

.field-with-affix { position: relative; display: grid; }
.field-with-affix input { padding-right: 5rem; }

.affix {
  position: absolute;
  top: 50%;
  right: 0.45rem;
  transform: translateY(-50%);
  min-height: 40px;
  padding: 0 0.7rem;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--surface-1);
  background-image: none;
  box-shadow: none;
  color: var(--app-mute);
  font-size: var(--step--1);
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.affix:active { transform: translateY(-50%); background: var(--surface-3); }

.auth-submit {
  width: 100%;
  min-height: 54px;
  margin-top: 0.25rem;
  font-size: var(--step-0);
  letter-spacing: 0.02em;
}

#signin-msg {
  min-height: 1.4em;
  margin: 0;
  font-weight: 650;
  font-size: var(--step--1);
  /* auth.css sets this line's colour; it carries progress ("Signing in…") and
     refusals alike, so it cannot take a state colour until the two are told
     apart in the markup. */
  color: var(--app-text);
}

/* The page failed: the red state, a wash and an edge, 7.23:1. */
#boot-error {
  max-width: 34rem;
  margin: 0 auto 1rem;
  padding: 0.85rem 1rem;
  border-radius: var(--r);
  background: rgb(var(--bad-rgb) / 0.12);
  border-left: 5px solid var(--bad-mark);
  color: var(--bad);
  font-weight: 650;
}

#signup-switch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 1.4rem 0 0;
  padding-top: 1.15rem;
  border-top: 1px solid var(--line);
}

/* A button that reads as a link. It IS a button — it changes this screen's
   mode rather than navigating — and button chrome would put two equal-weight
   buttons on a sign-in form, which is how people press the wrong one. */
.linkish {
  min-height: 0;
  padding: 0;
  background: none;
  background-image: none;
  box-shadow: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  color: var(--app-accent);
  font-size: var(--step--1);
  font-weight: 650;
}
.linkish:active { background: none; transform: none; }

.signin-note {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--app-mute);
  border: 1px dashed var(--line);
  border-radius: 100px;
  padding: 0.2rem 0.6rem;
}

.auth-foot {
  margin: 0;
  color: var(--app-mute);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.03em;
}

/* The status band belongs to the work screens. On a composed sign-in surface
   it is noise above the card, and "signed out" is already what the screen
   says. The boot error is NOT suppressed — that one has to be seen. */
body:has(#signin:not([hidden])) #status { display: none !important; }


/* ---- 4. Buttons -------------------------------------------------------- */

button {
  min-height: 56px;
  padding: 0.85rem 1.25rem;
  border: 0;
  border-radius: 8px;
  background: var(--teal-soft);
  /* The fill is WHITE now (tkati.com's one filled control), so the label is
     the black one beside it: 19.80:1. It was #FFFFFF on purple at 4.04:1. */
  color: var(--on-accent);
  font-family: var(--body);
  font-size: var(--step-0);
  font-weight: 650;
  cursor: pointer;
}

/* NO GLOW UNDER THE SIGN-IN'S BUTTONS. A white shadow sat under every button
   on that screen — the submit, the Show affix, even the text links — and
   tkati.com removed its own for the reason it applies here: a white bloom
   under a white fill on a black page reads as fog, not light. The fill and
   the edge carry the object. */

button:active { background-color: var(--teal); transform: translateY(1px); }
button[disabled] { background: var(--app-raised); color: var(--app-mute); box-shadow: none; }

/* The sign-out button is not a primary action and must not compete with
   Clock in — the two must never be confused at a glance. */
#signout {
  background: transparent;
  background-image: none;
  box-shadow: none;
  color: var(--app-mute);
  border: 1.5px solid var(--app-line);
  font-weight: 600;
  min-height: 48px;
}

/* ---- 5. Freshness -----------------------------------------------------
   "Today" is a claim, and on a phone that has been in a pocket since 6am it
   can be a false one. This is where the app admits how old its data is. */

#freshness {
  margin: 0 0 1.5rem;
  color: var(--app-mute);
  font-size: var(--step--1);
  font-weight: 600;
}

/* ---- 6. Job cards ------------------------------------------------------ */

#jobs { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }

#jobs > li {
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-left: 5px solid var(--app-accent);
  border-radius: var(--r);
  padding: 1.1rem 1.15rem 1.25rem;
  /* No `--sh-sm` here. It is a LIGHT-theme shadow — rgba(27,34,41,0.05) — so on
     a black card it was invisible anyway, and the signed-in block below was
     already setting it to none. Elevation that renders as nothing is still
     elevation in the source, and the next person to read this file would have
     had to work out which of the two won. */
}

.job-title {
  font-family: var(--display);
  font-size: var(--step-1);
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0 0.35rem;
}

.job-address {
  margin: 0 0 0.5rem;
  font-weight: 600;
  color: var(--app-text);
}

.job-crew {
  margin: 0 0 0.75rem;
  color: var(--app-mute);
  font-size: var(--step--1);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Access notes and gate codes are the two things someone reads while standing
   at a locked gate with a truck behind them. They are boxed, not italicised,
   because emphasis has to survive sunlight. */
.job-access,
.job-gate {
  margin: 0 0 0.6rem;
  padding: 0.7rem 0.85rem;
  border-radius: 8px;
  background: var(--app-raised);
  border-left: 4px solid var(--slate);
  font-weight: 600;
}

.job-gate { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
.job-access[hidden], .job-gate[hidden] { display: none; }

/* ---- 7. The clock form ------------------------------------------------
   The single most important control in the product. Cost code first, then the
   button — the order is mandatory in the schema and it is mandatory here,
   because time booked against a job says the job lost money and time booked
   against a cost code says base prep always runs over, and only the second
   ever improves an estimate. */

.job-time {
  display: grid;
  gap: 0.7rem;
  margin: 1rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--app-line);
}

.job-clock {
  width: 100%;
  min-height: 64px;
  font-size: var(--step-1);
}

.job-time-msg {
  min-height: 1.4em;
  font-weight: 650;
  font-size: var(--step--1);
  color: var(--app-accent);
}

#empty {
  padding: 2rem 1rem;
  text-align: center;
  color: var(--app-mute);
  font-weight: 600;
}

/* ---- 7b. Photographs -------------------------------------------------
   The concealment line is the point of this block, so it is styled as a
   warning band rather than as helper text under a control. Somebody about to
   pour over un-photographed compaction has minutes to act on it, and the
   difference between that and a lost warranty argument two years later is
   whether they noticed. It is the amber attention state; a refused outbox
   entry, which has already failed, is the red one. */

.job-photos {
  margin: 1rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--app-line);
}

.job-concealment {
  margin: 0 0 0.85rem;
  padding: 0.8rem 0.95rem;
  border-radius: 8px;
  /* The attention state: act on it before the pour. */
  background: rgb(var(--attn-rgb) / 0.10);
  border-left: 5px solid var(--attn-mark);
  color: var(--attn);
  font-weight: 650;
}

/* Cleared is stated, not implied by absence. "Every buried element is
   photographed" is worth reading; a blank space says nothing and looks the
   same as a screen that failed to load the list. */
.job-concealment--clear {
  background: var(--app-raised);
  border-left-color: var(--ok-mark);
  color: var(--app-text);
}

.job-photo-form { display: grid; gap: 0.7rem; }

.job-photo-take {
  width: 100%;
  background: transparent;
  background-image: none;
  box-shadow: none;
  color: var(--app-accent);
  border: 2px solid var(--app-accent);
  font-weight: 650;
}

.job-photo-take:active { background-color: var(--app-raised); }

/* Deliberately quieter than Clock in. Both are primary actions on this card
   and they must not be confused at a glance by someone wearing gloves — the
   filled button is the one that costs money to get wrong. */
.job-photo-msg {
  min-height: 1.4em;
  font-weight: 650;
  font-size: var(--step--1);
  color: var(--app-accent);
}

/* ---- 8. The outbox ----------------------------------------------------
   A queue nobody can see is a queue nobody trusts, and a crew that does not
   trust it keeps a paper copy — which is the outcome the whole time-entry
   schema exists to prevent. So the outbox is loud on purpose, and each state
   says what it is in words as well as in colour. */

#outbox,
#outbox-rejected,
#outbox-degraded {
  margin: 1.25rem 0 0;
  padding: 0.9rem 1rem;
  border-radius: var(--r);
  font-weight: 650;
}

#outbox {
  background: var(--app-raised);
  border-left: 5px solid var(--slate);
  color: var(--app-text);
}

/* Rejected is the only genuinely bad state, and it is the one that must never
   be mistaken for "sending". */
#outbox-rejected {
  /* The red state: a refused entry is a failure, not a thing to look at. */
  background: rgb(var(--bad-rgb) / 0.12);
  border-left: 5px solid var(--bad-mark);
  color: var(--bad);
}

#outbox-degraded {
  background: var(--app-raised);
  border-left: 5px solid var(--app-line);
  color: var(--app-text);
}

#outbox[hidden], #outbox-rejected[hidden], #outbox-degraded[hidden] { display: none; }

/* ---- 9. Footer --------------------------------------------------------- */

#signed-in > footer {
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--app-line);
  display: grid;
  gap: 0.85rem;
  justify-items: start;
}

#signed-in > footer p {
  margin: 0;
  color: var(--app-mute);
  font-size: var(--step--1);
  font-weight: 600;
}

#who { color: var(--app-text); }

#role {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 100px;
  background: var(--app-raised);
  color: var(--app-text);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

noscript {
  display: block;
  padding: 1rem;
  background: var(--app-raised);
  border-radius: var(--r);
  font-weight: 600;
}

/* ---- 10. Motion -------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  button:active { transform: none; }
}

/* The way through to the office dashboard, for the roles that have one. */
#office-link-wrap { margin: 0; }
#office-link {
  color: var(--app-accent);
  font-weight: 650;
  font-size: var(--step--1);
  text-decoration: none;
  border-bottom: 1px solid rgb(var(--aqua-rgb) / 0.45);
}

/* ==========================================================================
   WHY NOTHING IS LOADING

   Not an error style. The three states this panel shows are instructions with
   a person attached — apply the migrations, insert a role row, or reconnect —
   and styling them as failures sends somebody to look for a fault instead of
   doing the thing. Quiet card, ordered steps, no red.
   ========================================================================== */
#setup {
  max-width: 40rem;
  margin: 1rem auto;
  padding: 1rem 1.15rem 1.1rem;
  border: 1px solid var(--app-line);
  border-radius: 10px;
  background: var(--app-surface);
}
#setup.is-blocking { border-left: 3px solid var(--attn-mark); }
#setup h2 {
  margin: 0 0 0.4rem;
  font-size: var(--step-0);
  font-family: var(--display);
  letter-spacing: -0.005em;
  color: var(--app-text);
}
#setup p { margin: 0 0 0.7rem; font-size: var(--step--1); line-height: 1.6; color: var(--app-mute); }
#setup ol { margin: 0; padding-left: 1.2rem; display: grid; gap: 0.45rem; }
#setup li { font-size: var(--step--1); line-height: 1.55; color: var(--app-mute); }


/* The day itself (0028) — above the clock, because whether the day is
   happening comes before how long it took. Uses the app palette as defined at
   the top of this file; the first draft invented --app-warn, --space-3 and
   --fs--1, none of which exist, so the line rendered unstyled and the buttons
   had no target size at all. */
.job-day {
  margin: 0.9rem 0 0.5rem;
  color: var(--app-mute);
  font-size: var(--step--1);
}
.job-day.is-off {
  color: var(--attn);
  font-weight: 650;
}

.job-advance {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  align-items: center;
  margin: 0 0 1rem;
}
/* 56px, not 44. This is a thumb in a work glove on a phone held at arm's
   length in sun, and the clock button next to it is 64. */
.job-advance button {
  min-height: 56px;
  padding: 0 1.1rem;
  border: 1px solid var(--app-line);
  border-radius: var(--r-sm);
  background: var(--app-raised);
  color: var(--app-text);
  font: inherit;
  font-weight: 650;
  font-size: var(--step-0);
  cursor: pointer;
}
.job-advance button[hidden] { display: none; }
.job-advance button:disabled { opacity: 0.55; cursor: default; }

.job-advance-msg {
  min-height: 1.4em;
  font-size: var(--step--1);
  color: var(--app-mute);
}
.job-advance-msg.is-warn { color: var(--attn); font-weight: 650; }

/* ==========================================================================
   MATTE BLACK — THE SIGNED-IN FIELD APP

   Owner, 2026-08-09: "dark black glass like the websites glass black header",
   then "glass looks cheap/half assed", then "too glossy". The frosted version
   of this block is gone; what is left is the same instruction read correctly.
   Black ground, hairline edges, no blur, no gradient, no shadow.

   THE SECOND HALF OF THE INSTRUCTION IS WHY EVERY SELECTOR IS GATED:
   "keep /admin login/signup page look the same as it is now". `#signin` and
   `#signed-in` live in the same document and are switched with `hidden`, so a
   rule written against `body` alone would repaint the screen the owner asked
   to leave alone. `body:has(#signed-in:not([hidden]))` is the same mechanism
   the sign-in split already uses, one section along.

   THE PROPERTIES HANG OFF <body>, NOT OFF #signed-in, and that was a bug
   first: `#status` and `#setup` are SIBLINGS of `#signed-in` inside `#app` —
   the status band has to be able to say "signed out" — so `#signed-in #status`
   matched nothing and the band kept the old slate while the cards under it
   went black. One grey rectangle at the top of an otherwise converted screen,
   caught in a screenshot.

   WHAT DOES NOT CHANGE, AND MUST NOT: the 56px targets, the 64px clock button,
   the floor of --step-0 on type, and the rule that state is said in words
   rather than only in colour. Those are what make this screen usable
   one-handed, in a glove, in July sun. Only surfaces move.
   ========================================================================== */

body:has(#signed-in:not([hidden])) { background: var(--paper); }

/* A card is the ground plus one hairline. The 5px sage edge STAYS — it is how
   a crew member finds the card they are working from at a glance, and it is
   the one thing on this screen that should not become subtler. */
body:has(#signed-in:not([hidden])) :is(#jobs > li, #status, #setup) {
  background: var(--paper);
  border: 1px solid var(--line);
  box-shadow: none;
}
body:has(#signed-in:not([hidden])) #jobs > li { border-left: 5px solid var(--app-accent); }
/* A setup problem somebody has to act on keeps its amber edge here too: the
   card rule above is (2,1,1) and was quietly erasing `#setup.is-blocking`, so
   "apply the migrations" looked exactly like the non-blocking "unreachable". */
body:has(#signed-in:not([hidden])) #setup.is-blocking { border-left: 3px solid var(--attn-mark); }

/* Insets sit one step off black rather than on a second grey slab. */
body:has(#signed-in:not([hidden])) :is(.job-access, .job-gate, #outbox, #outbox-degraded, #role) {
  background: var(--surface-1);
  border-left-color: var(--line-2);
}

/* The two that must NOT go quiet. A rejected entry is a failure (red);
   un-photographed concealment needs acting on (amber). Both keep a solid
   ground — a hairline would make them read as ordinary furniture.

   TWO RULES, NOT ONE :is(). `:is(#outbox-rejected, .job-concealment)` takes
   the specificity of its strongest argument, the id, even where it matched
   the class — so it outranked the --clear rule below and every CLEARED
   concealment line rendered as a warning. Split, the class rule and the
   --clear rule tie and the later one wins, as written. */
body:has(#signed-in:not([hidden])) #outbox-rejected {
  background: rgb(var(--bad-rgb) / 0.12);
  border-left-color: var(--bad-mark);
}
body:has(#signed-in:not([hidden])) .job-concealment {
  background: rgb(var(--attn-rgb) / 0.10);
  border-left-color: var(--attn-mark);
}
body:has(#signed-in:not([hidden])) .job-concealment--clear {
  background: var(--surface-1);
  border-left-color: var(--ok-mark);
}

/* Flat, and round. The gradient on a primary button is the same specular idea
   as the glass, one control at a time. */
body:has(#signed-in:not([hidden])) button {
  background-image: none;
  box-shadow: none;
  border-radius: 999px;
}

/* ============================================================================
   THE SIGN-IN PHOTOGRAPH IS A DIFFERENT PHOTOGRAPH NOW
   ----------------------------------------------------------------------------
   The panel used to hold a general-contractor jobsite — an outdoor scene, shot
   in daylight, bright almost everywhere. The scrim over it was tuned to that:
   30% dark at the left edge climbing to 86% at the card seam, plus 70% rising
   off the bottom for the pull-quote to sit on. Against a bright photo that is
   a scrim. Against this one it is a lid.

   The image is now the washed bin from the home page — a dark green bin shot
   into its own interior, in shade. It is intrinsically several stops darker
   than what the numbers were written for, so the same scrim renders it as a
   murky shape you cannot identify. That is worse than merely dim: the whole
   reason to put the AFTER photograph here is that it shows a bin that came
   back clean, and a clean bin you cannot read is an argument for the opposite.

   So the scrim comes down to where it is doing a job and no further:

     - the horizontal layer starts at 0.10 rather than 0.30, which is what
       uncovers the bin, and still lands at 0.80 against the card so the seam
       between panel and form stays a gradient rather than an edge;
     - the vertical layer drops to 0.62 and finishes its ramp at 48%, which is
       enough ground for white display type and stops eating the middle of the
       frame, where the actual subject is.

   And the filter lifts instead of only cutting: `brightness(1.10)` against a
   photograph this dark buys back more than the desaturation ever cost, and the
   saturation comes back up a little with it, because a bin that has just been
   washed is the one thing on this screen allowed to look wet.

   The form is still what the person came here to use, and none of this touches
   it — the card is opaque and sits on its own ground. This is only the half of
   the screen that was showing the wrong company two commits ago and showing it
   in the dark one commit ago.
   ========================================================================== */
@media (min-width: 62rem) {
  .auth-aside img {
    filter: saturate(0.92) contrast(1.02) brightness(1.10);
  }

  .auth-aside__scrim {
    background: linear-gradient(0deg, rgb(0 0 0 / 0.55) 0%, transparent 48%);
  }
}
