/* ============================================================================
   system.css — the tokens the whole app agrees on, and the one piece of chrome
   both apps share.

   WHY THIS FILE EXISTS.

   The crew said the app "feels like it's going to break". Four waves of fixes
   found the causes and they were all one shape: every screen invented its own
   layout rules. The bar that floated 92px above a 71px nav; the drawer sized in
   svh inside a webview where svh over-reports by ~96px; two body paddings at
   identical specificity where source order decided the winner; a nav that is
   54px tall in one file, 58 in another and 46 in a third. Two numbers that had
   to agree, typed separately by two people, in two files, months apart.

   There is exactly one place in this repo where that cannot happen:
   employee-field-app.css declares --crew-nav-h once, next to the nav's own
   padding, and five bars derive their position from it. This file is that idea
   applied to the whole app.

   WHAT THIS FILE IS.

   Three things, in this order:

     1. TOKENS. Every measurement, colour, weight and radius the two apps use,
        namespaced --sys-* so it can collide with nothing. A surface adopts the
        system by aliasing its own names to these — `--crew-nav-h:
        var(--sys-nav-h)` — one surface at a time.

     2. THE NAV. The bottom nav is the one component the manager and the crew
        must render identically, so it is painted ONCE, here, for both selectors
        (section "THE NAV" at the foot). Neither app's own stylesheet paints it.

     3. OPT-IN UTILITIES. A handful of .sys-* classes (segmented control, card,
        chip, tick, camera square, buttons, progress bar) that carry the contract
        anatomy so a screen can adopt a component by adding a class instead of
        retyping eight declarations. Nothing uses them until a screen opts in.

   WHERE THE VALUES COME FROM.

   The colours, type and shapes are the Apple-style contract Mario approved on
   2026-09-05 (the Tokens / Main boards): one accent blue, a grey ground with
   white borderless cards, the phone's own font, four weights. The layout
   numbers — safe area, keyboard, z-index, spacing — are unchanged from the
   measurement wave that created this file: each is either the value the app
   already used most or a platform constraint with the constraint named.

   THIS IS THE ONLY PLACE THESE VALUES ARE TYPED. A hex colour or a font-family
   in an app stylesheet is a value that will drift; read the token instead.

   LOAD ORDER — this has already caused one production bug.

   native-feel.css is a plain <link>; bento.css is document.write'n. Both wrote
   body padding-bottom at identical specificity, the plain link landed later,
   and the last row of every manager list slid under the nav pill inside the
   iOS app. So: system.css is a plain <link> and it is the FIRST stylesheet in
   every shell that loads it. First, so everything else can override a TOKEN at
   equal specificity when a documented exception needs to; and that is exactly
   why the nav rule below is written at a specificity the apps' bare `.nav` /
   `.bento-nav` rules cannot reach by source order alone.

     public/index.html        — before apple-interface.css
     public/team-clean.html   — before employee-field-app.css
     public/employees.html    — before employee-field-app.css

   Not loaded by inventory.html or photography.html in this wave; see the
   adoption plan. server.js serves by extension (contentTypes[".css"]) so there
   is no allowlist to add it to.
   ============================================================================ */

/* ----------------------------------------------------------------------------
   THE `hidden` ATTRIBUTE ACTUALLY HIDES.

   The browser's own [hidden]{display:none} is the LOWEST-specificity rule there
   is, so ANY rule of the app's that sets `display` on the same element silently
   defeats it: the attribute goes on, the JS believes the thing is gone, and it is
   still on the screen. It survives review because both halves look correct alone.

   It cost the owner a real fright. `#mdGallery .mdTrash` sets display:flex, so the
   manager's "Recently deleted" overlay was NEVER hidden — every photo library
   opened underneath it, and with nothing deleted the panel was blank. He tapped
   "Preview 11 ›" seconds after uploading eleven photos and got a screen headed
   "Recently deleted" with nothing on it. The crew app has carried the matching
   guard (#cmGallery .cm-trash[hidden]) since the day the trash shipped; the
   manager never got the line.

   One rule, first stylesheet, every shell. Pages that do not load this file
   (proposals, quote-generator, extend-approval) carry their own copy, and
   qa:system refuses a new one that does not. 2026-09-07.
   ---------------------------------------------------------------------------- */
[hidden] { display: none !important; }

:root {
  /* ==========================================================================
     1. SAFE AREA — the inset enters the arithmetic EXACTLY ONCE.

     The rule the app kept breaking is not "remember env()". It is "pay it once".
     native-feel.css pays it on body and .app-shell pays it again inside, so the
     crew shell has 155px of clearance under a 105px bar — 50px of dead scroll at
     the foot of every screen. Two galleries pay it on the bar AND on the toast
     that clears the bar. Thirteen fixed;inset:0 layers pay env(top) in their own
     header on top of a webview that already positioned them below the status bar.

     These two names exist so a consumer never types env() at all: it composes
     --sys-nav-top or --sys-bar-bottom, which already contain the inset. If your
     rule references one of those and also writes env(safe-area-inset-bottom),
     you are paying twice — that is a guard failure, not a style choice.

     Aliased rather than used raw so the double-payment is greppable: one env()
     per name, and every other file reads the name.
     ========================================================================== */
  --sys-safe-t: env(safe-area-inset-top, 0px);
  --sys-safe-b: env(safe-area-inset-bottom, 0px);

  /* THE SIDES. On every iPhone before the Duo both are 0 in portrait, so nothing
     here changes a 393pt phone by a pixel (qa:duo-width pins that). On the iPhone
     Duo folded (466 x 678 pt, iOS 27) the system owns the right 84 pt: the clock,
     the Wi-Fi mark and the vertical Dynamic Island live there, and a tap on the
     Island opens Control Center. So the app is a 382 pt column and the column is
     empty ground — Apple's own apps do the same (docs/IPHONE-DUO-PLAN.md §1).
       --sys-safe-l / --sys-safe-r   the insets, one env() each, as above
       --sys-usable-w                the width the app may draw controls in
       --sys-center-x                where "left: 50%" must point to centre a
                                     fixed pill or sheet inside that width
     <body> pays the sides once (rule below), so everything in the flow is already
     inside. A fixed layer is outside the flow and reads these names instead of
     typing env(): a sheet centres on --sys-center-x, a full-screen layer ends at
     --sys-safe-r, a pill floats at calc(12px + var(--sys-safe-r)). */
  /* THE LEFT IS TWO NAMES (2026-10-26, the open Duo's column — THE SIDE, below):
     --sys-inset-l is the raw inset, read once here and by the column itself;
     --sys-safe-l is where the app may draw from, and on the open Duo that is the
     inset PLUS the column (--sys-side-w). Everything that reads --sys-safe-l — the
     body, every full-screen layer, every sheet, the two panes, --sys-center-x and
     --sys-usable-w — moves right by the column with no second rule. */
  --sys-inset-l: env(safe-area-inset-left, 0px);
  --sys-safe-l: var(--sys-inset-l);
  --sys-safe-r: env(safe-area-inset-right, 0px);
  --sys-side-w: 0px;
  --sys-usable-w: calc(100vw - var(--sys-safe-l) - var(--sys-safe-r));
  /* THE OPEN DUO IS TWO PANES. From 600px of viewport the manager draws Houses →
     house, Messages → thread and Inventory → piece side by side, the crew Jobs →
     job (Option A "Two panes", approved 2026-10-23). 600, because the widest
     phone is the folded Duo at 466 and the narrowest iPad is 744: no phone can
     ever cross it, every open Duo (669, or 585 usable) and every tablet does,
     and it sits under the 720px frame both shells already draw. A media query
     cannot read a custom property, so the number is typed in the five files that
     branch on it (this one — the sheet rules at the foot of section 1 —
     bento.css, employee-field-app.css, messages.css, inventory.css) and in the
     two scripts (app-scroll-polish.js, employee-field-app.js); qa:duo-width fails
     on any other width. The list pane is one width in both apps, so the house
     keeps at least 325pt of the 585 — wider than the 320pt phone every rule in
     this repo already survives. From the same 600px every Menu screen in both
     apps draws across the usable width (bento.css / employee-field-app.css undo
     their phone columns there); the rules below are what those screens share. */
  --sys-pane-w: 260px;
  --sys-center-x: calc(var(--sys-safe-l) + (100% - var(--sys-safe-l) - var(--sys-safe-r)) / 2);

  /* ==========================================================================
     2. THE BOTTOM NAV, AND EVERYTHING THAT SITS ON ITS TOP EDGE.

     Three numbers describe a bottom bar and only three:

       --sys-nav-h    how tall the bar's own box is, WITHOUT any inset
       --sys-nav-gap  how far the bar floats off the bottom edge (0 if docked)
       --sys-safe-b   the home indicator

     and one derived number, which is the only one anybody else may read:

       --sys-nav-top = nav-h + nav-gap + safe-b

     ONE NAV, BOTH APPS. The manager and the crew render the same frosted pill
     (section "THE NAV" at the foot of this file), so there is one pair of
     numbers here and no per-shell override: 1px hairline + 7px + a 48px button
     + 7px + 1px = 64px, floating 14px off the bottom edge, paying the home
     indicator in its `bottom:` and nowhere else. The tablet branch that used to
     float the crew's bar at 70/14 is gone too — every width gets the same pill.

     The crew's --crew-nav-h is nav-h + nav-gap (78) and its bars add env()
     themselves; the manager's bars read --sys-nav-top / --sys-scroll-clear /
     --sys-bar-bottom. Both arrive at the same top edge: 78px + inset. Nothing
     in either app types that number.
     ========================================================================== */
  --sys-nav-h: 64px;
  --sys-nav-gap: 14px;
  --sys-nav-top: calc(var(--sys-nav-h) + var(--sys-nav-gap) + var(--sys-safe-b));

  /* Air between the nav's top edge and a floating overlay (toast, an Undo bar).
     12px, because that is what the crew toast already uses
     (employee-field-app.css, calc(--crew-nav-h + 12px + env)) and it is the
     only one of the nine spellings of this measurement that was ever derived. */
  --sys-bar-air: 12px;

  /* Air between the nav's top edge and the last row of scrolling content.
     16px is the crew shell's (.app-shell in employee-field-app.css).
     bento overrides it to 28px below — NOT because anyone disagrees about how
     tall the pill is, but because the manager's list pages genuinely end further
     from the pill. Keeping the loser's value is the point: the bar height is
     derived, the air is a choice, and this is where the choice is written down
     instead of being baked into a literal. */
  --sys-scroll-air: 16px;

  /* THE ONE OFFSET A FIXED BOTTOM BAR MAY USE.
     Docked bars (a Submit bar that ends the form) use --sys-nav-top.
     Floating overlays (toast, Undo) use --sys-bar-bottom.
     Both already contain the inset. Neither may add env() again.
     Redefined under html.kb-typing further down — that redefinition is how one
     rule moves every bar in the app when the keyboard opens. */
  --sys-bar-bottom: calc(var(--sys-nav-top) + var(--sys-bar-air));

  /* Scroll clearance for a page that sits under the nav. */
  --sys-scroll-clear: calc(var(--sys-nav-top) + var(--sys-scroll-air));

  /* THE TWO OFFSETS FOR A CONTROL THAT IS *NOT* ABOVE THE NAV.

     --sys-nav-top and --sys-bar-bottom both contain the nav's height, which is
     the right answer for anything drawn on top of a page. It is the WRONG answer
     for a control inside a full-screen layer that COVERS the nav — the house
     editor's Save bar, the quick-edit sheet, the new-message sheet. Those float
     off the bottom edge of the glass, not off a pill that is no longer visible.
     Feeding them --sys-bar-bottom would lift all of them by the nav. So they get
     their own names, and the names are what makes them keyboard-aware: all three
     are redefined together in the ONE keyboard block further down.

     Two of them, not one, because the app floats these things by two different
     numbers and both are already on screen: a bar is 12px off the edge (the
     editor Save bar, the smart-pickup bar) and a sheet is 10px (#os2Edit,
     #os2EmailSheet, .msg-sheet). Two pixels is not worth a diff on a live screen,
     and the same argument that keeps the manager's list clearance at 28px keeps
     both of these: the resting offset is a choice, the keyboard behaviour is
     derived, and writing the choice down is how it stops being a literal.

     max(), not calc(): the inset REPLACES the resting gap rather than adding to
     it, which is what all five of these rules do today. The inset is still paid
     exactly once. */
  --sys-overlay-bottom: max(12px, var(--sys-safe-b));
  --sys-sheet-bottom: max(10px, var(--sys-safe-b));

  /* And the third shape: a sheet DOCKED to the bottom edge, rounded on top only,
     paying the home indicator inside its own padding — the Extend-the-stay sheet
     and the extension-link sheet. Its resting offset really is 0, so there is
     nothing here to preserve; the token exists purely so that the one keyboard
     rule can lift it with everything else. Two manager modules used to do this
     in JavaScript with their own visualViewport listener, their own rAF and their
     own `innerWidth >= 720` bail-out (which is why neither sheet ever lifted on
     an iPad). Deleted, both of them, in favour of this. */
  --sys-dock-bottom: 0px;

  /* And the height a sheet may occupy without part of it being under the keyboard.
     A docked sheet is not just pushed up by the keyboard, it is squeezed by it: the
     two deleted attachKeyboardLift() copies set BOTH bottom and maxHeight, and a
     sheet that rises without shrinking loses its top edge off the screen instead of
     its bottom one. Full height at rest, so nothing is capped when no keyboard is
     up; redefined in the same one block below.
     A consumer still never reads --sys-kb itself — offsets come from the four names
     above, heights from this one. */
  --sys-kb-view-h: 100dvh;

  /* ==========================================================================
     3. THE KEYBOARD.

     Published by JS onto <html> as an inline style, because CSS cannot measure
     visualViewport and cannot tell a text field from a date picker. 0px here so
     every rule that reads it is already correct before the JS runs, in a browser
     with no visualViewport, and if the publisher is ever deleted — and because
     0 is the truthful answer in the Capacitor shell, which resizes the webview
     itself. Same contract employee-field-app.js already ships as --crew-kb.

     Companion classes, also published by that JS, also on <html>:
       .kb-open    the visual viewport actually shrank (>60px, so a collapsing
                   URL bar or a rubber-band bounce never reads as a keyboard)
       .kb-typing  the caret is in something that RAISES a keyboard — decided by
                   looking at the element, because a <select> and an
                   <input type="date"> open a wheel that covers nothing and keep
                   focus after it closes. :focus-within got this wrong and moved
                   the Submit bar the moment a stager picked her role.
     ========================================================================== */
  --sys-kb: 0px;

  /* ==========================================================================
     4. TAP TARGET AND THE iOS ZOOM FLOOR.

     44px is Apple's HIG minimum and is a floor, not a size: the nav's buttons
     are 48 in both apps, the manager drawer's are 46, and neither moves by
     adopting this. It exists so the next person has a number to reach for, and
     so a new control is never drawn smaller than a gloved thumb.

     16px on a field is not taste either. In Safari and the home-screen PWA a
     field under 16px zooms the page in when it takes focus and does NOT zoom
     back out, because the shells' own viewport tags leave pinch-zoom on there.
     A text input has ONE size and this is it.
     2026-09-17: this used to say the shells stay pinchable everywhere. Not in
     the App Store app, and on purpose: native-enhance.js (0a) rewrites the
     viewport to maximum-scale=1, user-scalable=no, and the Capacitor shell
     leaves page zoom off (ios.zoomEnabled is not set). Nobody can pinch there.
     Larger text in the app comes from the phone's own text size setting
     (native-enhance.js reads it), not from pinching. The 16px floor stays
     because the same pages run in Safari and the PWA.
     ========================================================================== */
  --sys-tap: 44px;
  --sys-field-size: 16px;

  /* ==========================================================================
     5. Z-INDEX AS A SCALE, NOT A PILE.

     There are 74 distinct z-index values in this app, from 1 to 2147483647, and
     exactly one comment anywhere stating an intended relationship. Three
     separate authors each reached for "the top" and wrote 2147483000, 2147483000
     and 2147483647. The manager's own toast (9800) loses to the open house
     drawer (30000), so every confirmation and every error raised while the
     drawer is open is invisible — including "save failed".

     Nine tiers, and nothing outside them. The low values are the ones already in
     the tree (nav 40, scrim 55, sheet 60) so a migrated file keeps its exact
     stacking position against files that have not migrated yet.

     MIGRATE BY NEIGHBOURHOOD, NEVER BY FILE. Everything that can be on screen at
     the same time moves in one commit, or a migrated overlay at 400 ends up
     under an unmigrated one at 99998. The Face ID guard keeps 2147483647 as a
     documented exception until the last neighbourhood lands, because it must
     beat unmigrated layers by definition.

     --sys-z-toast is above every overlay ON PURPOSE. That changes what is
     visible: a manager toast raised over an open drawer appears where today it
     does not. Making an invisible confirmation visible is the bug being fixed.
     ========================================================================== */
  --sys-z-base: 0;         /* in flow */
  --sys-z-raised: 10;      /* sticky row/header inside a scroller */
  --sys-z-nav: 40;         /* the bottom nav pill — painted in THE NAV below */
  --sys-z-scrim: 55;       /* below the sheet, above the page */
  --sys-z-sheet: 60;       /* bottom sheets and the house drawer */
  --sys-z-fullscreen: 400; /* position:fixed;inset:0 — galleries, workspaces */
  --sys-z-lightbox: 500;   /* a full-screen layer over a full-screen layer */
  --sys-z-toast: 600;      /* above everything a person can be looking at */
  --sys-z-lock: 700;       /* the biometric guard. Nothing above this. */

  /* THE ONE DOCUMENTED EXCEPTION TO THE SCALE, and it exists for the same reason
     the Face ID guard's 2147483647 does: a layer that must beat UNMIGRATED layers
     by definition cannot be expressed in a scale the unmigrated layers ignore.

     The manager's toast is at 9800 today and it loses to eight things it can be
     raised over: the house drawer (30000), the editor Save bar (40000), the
     quick-edit and email sheets (41001), the photo lightbox (42000), the media
     gallery (99998/99999), the media viewer (100000) and the bookkeeping menu
     (100120). "Save failed" behind an open drawer is invisible, which is the
     worst possible thing for a toast to be.

     Moving the toast to --sys-z-toast (600) would make that WORSE, not better,
     until every one of those eight has migrated — and three of them live in files
     this wave does not own. So the toast gets a number that beats the pile and
     stays below the two layers that are supposed to beat a toast: the native
     error/offline screen (native-feel.css, 2000000) and the biometric lock.

     This token is a ratchet handle, not a tier. The day the overlay neighbourhood
     migrates, the toast becomes var(--sys-z-toast) and this line is DELETED. It
     may never be used by anything other than the toast. */
  --sys-z-toast-legacy: 200000;

  /* ==========================================================================
     6. SPACE — a 2pt grid, not a 4pt one.

     design-system.css already contains a perfect 4pt scale. It has been dead
     since the day it was written, and in the gap the app grew 53 distinct
     padding atoms. A 4pt scale cannot be adopted here without moving screens:
     10px and 14px are both in the top ten most-used spacings in the repo, and
     neither is on a 4pt grid. So the grid is 2pt through 16 and 8pt above it,
     which is the shape the app actually has.
     ========================================================================== */
  --sys-s-2: 2px;
  --sys-s-4: 4px;
  --sys-s-6: 6px;
  --sys-s-8: 8px;
  --sys-s-10: 10px;
  --sys-s-12: 12px;
  --sys-s-14: 14px;
  --sys-s-16: 16px;
  --sys-s-20: 20px;
  --sys-s-24: 24px;
  --sys-s-32: 32px;
  --sys-s-40: 40px;
  --sys-s-48: 48px;

  /* ==========================================================================
     7. RADIUS.

     Control 12 (inputs, the camera square), card 18, sheet 20, pill 100 (fully
     round on anything under 200px tall — the app's 999px and 99px spellings are
     visually identical to it). The segmented control is the one shape with two
     radii: 10 on the grey outer track, 8 on the white segment inside it, so the
     segment sits 2px inside the track and the two curves stay concentric.
     ========================================================================== */
  --sys-r-control: 12px;
  --sys-r-card: 18px;
  --sys-r-sheet: 20px;
  --sys-r-pill: 100px;
  --sys-r-segment: 8px;        /* the white segment inside a segmented control */
  --sys-r-segment-outer: 10px; /* the grey track around it */

  /* ==========================================================================
     8. TYPE.

     The size scale is the app's own — the ten sizes that carried the most
     declarations when the file was created — plus the two the contract adds:
     the 32px page title ("Jobs", "Menu", "Money") and the 17px headline (a
     card's address, its primary line). List rows use --sys-t-lead (15px).
     Numbers set font-variant-numeric: tabular-nums; titles tighten letter-spacing
     (-.02em at 22px, -.03em at 28px and above).

     --sys-t-field is not a size in the scale, it is the floor from section 4.
     A text input uses it and nothing else.
     ========================================================================== */
  --sys-t-micro: 10px;    /* the smallest label the app may set */
  --sys-t-caption: 11px;  /* chips */
  --sys-t-small: 12px;
  --sys-t-body: 13px;     /* captions, secondary lines, segmented-control labels */
  --sys-t-body-lg: 14px;
  --sys-t-lead: 15px;     /* list rows, buttons, text actions */
  --sys-t-field: 16px;    /* every text input, always */
  --sys-t-headline: 17px; /* a card's primary line */
  --sys-t-title: 18px;
  --sys-t-display: 22px;  /* a house title, next to its status chip */
  --sys-t-hero: 26px;
  --sys-t-large-title: 32px; /* the page title */

  /* WEIGHTS. Four, and only four: 400 / 500 / 600 / 700. The system font has a
     real face at each, so what is written is what renders, in both apps, on
     every device. The two older names below still exist because rules read
     them; they now resolve onto the scale (semibold-plus -> 600, heavy -> 700)
     rather than to the 650 and 800 that only ever existed on a webfont's
     variable axis. A rule asking for 800 or 900 directly should be rewritten to
     the token; nothing above 700 is part of this design. */
  --sys-w-regular: 400;
  --sys-w-medium: 500;
  --sys-w-semibold: 600;
  --sys-w-semibold-plus: 600; /* kept for its readers; it IS semibold now */
  --sys-w-bold: 700;
  --sys-w-heavy: 700;         /* kept for its readers; it IS bold now */

  --sys-line-tight: 1.2;
  --sys-line-body: 1.45;

  /* THE PHONE'S OWN FONT. No webfont: the app renders in the same face as every
     other app on the device, exports without a fallback, and there is no
     fonts.googleapis request in the critical path. -apple-system first so iOS
     and macOS pick SF Pro; the "SF Pro" names are for a browser that exposes
     them explicitly; Helvetica Neue / Helvetica / Arial are the fallback order
     on everything else. The shells load NO other family — "Inter" appearing
     anywhere in the two apps is a regression, and qa:system checks for it. */
  --sys-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --sys-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ==========================================================================
     9. COLOUR.

     One accent, one ground, white cards, and three status tints. This is the
     palette the client already sees on the proposal page; the two apps adopt it.

     THE RULE THAT MATTERS: blue is the ONLY accent. Primary buttons, links, the
     active tab, the active nav item, the progress bar, the ticked circle — all
     --sys-accent. Green is a STATUS colour (Staged, paid, done) and never a
     button, never a link, never the active tab. Orange is watch / packing /
     pending. Red is error, not-saved, overdue. Each status colour has exactly one
     soft chip tint beside it.

     --sys-blue / --sys-blue-soft are the accent under their older names. They
     alias it rather than repeating the hex so the value is typed once; rules
     that read them are already correct.
     ========================================================================== */
  /* 2026-09-17 (Apple review W46): three of these moved a few points darker, and
     only so the words on them pass WCAG AA (4.5:1) where the crew reads them in
     the sun. Measured before: the accent #0071E3 was 4.10:1 on its own chip
     (--sys-accent-soft), 4.31 on the page grey and 4.09 on --sys-fill-2; orange
     #B25E00 was 4.21 on its chip; muted #6E6E73 was 4.42 on --sys-fill-2 and 4.07
     on the segmented track. Now: accent #0068D6 4.64 / 4.88 / 4.63 (white on it
     5.31), orange #A85900 4.62 on its chip, muted #636366 at least 4.81 on every
     grey. The soft tints, the green and the reds did not change. The placeholder
     (#8E8E93, 3.26:1 on white) is left as it is; #707074 (4.53) is the value if
     the owner wants it raised too. */
  --sys-ink: #1D1D1F;      /* text */
  --sys-muted: #636366;    /* secondary text */
  --sys-faint: #C7C7CC;    /* tick borders, disabled */
  --sys-line: rgba(0, 0, 0, .08); /* every hairline — works as a border and as a 1px div */
  --sys-page: #F5F5F7;     /* the ground behind cards: body background in BOTH apps */
  --sys-panel: #FFFFFF;    /* cards, sheets, the active segment */
  --sys-fill: #F5F5F7;     /* inputs, the camera square, quiet strips inside a card */
  --sys-fill-2: #EFEFF2;   /* pressed / secondary fill */
  --sys-track: #E5E5EA;    /* the progress bar's track */
  --sys-placeholder: #8E8E93; /* input placeholder text */

  --sys-accent: #0068D6;       /* THE accent */
  --sys-accent-soft: #E5F1FC;  /* accent-coloured chips (Upcoming) */
  --sys-blue: var(--sys-accent);
  --sys-blue-soft: var(--sys-accent-soft);

  --sys-green: #1E7B3A;        /* STATUS ONLY: Staged, paid, done */
  --sys-green-soft: #E6F6EA;
  --sys-orange: #A85900;       /* watch / packing / pending */
  --sys-orange-soft: #FFF1E0;
  --sys-red: #FF3B30;          /* error, not-saved, overdue — FILL ONLY (rails, dots) */
  --sys-red-soft: #FFECEB;
  /* 2026-09-06 (wave 2): #FF3B30 is a beautiful rail and an unreadable word.
     As 11-13px text on --sys-red-soft it measures 3.12:1, and white on it is
     3.55:1 — both under the 4.5:1 floor, on the one colour that means "not
     saved". Every pair a person has to READ (chip text, the offline pill, the
     bad toast, the danger button) uses this darker red instead: 5.74:1 on the
     soft chip, 6.54:1 under white. It is the value pwa-polish.css already
     shipped before the restyle flattened it. --sys-red keeps #FF3B30 for the
     4px rails and 8px dots, where nothing is read on top of it. */
  --sys-red-ink: #B3261E;      /* red that is TEXT, or carries white text */
  --sys-purple: #AF52DE;
  --sys-purple-soft: #F5EAFB;
  /* 2026-09-17 (W46): #AF52DE is a fill. As words on its own chip it measured
     3.55:1 ("Tomorrow" on Home) and 4.13 on white (the times under it). Purple
     that is TEXT reads this instead: 5.19 on the chip, 6.04 on white — the same
     split as --sys-red / --sys-red-ink. */
  --sys-purple-ink: #8944AB;   /* purple that is TEXT */

  /* Translucency: black at three strengths. The scrim behind a sheet, the
     hairline (same value as --sys-line, kept under its own name for the rules
     that read it), and the press state a control shows under a finger. */
  --sys-scrim: rgba(0, 0, 0, .34);
  --sys-hairline-a: rgba(0, 0, 0, .08);
  --sys-press-a: rgba(0, 0, 0, .06);

  /* ELEVATION. Cards have NO border; this hairline shadow is what lifts a white
     card off the grey ground, and it is the same in both apps. The pill shadow
     is the floating nav's; the sheet shadow is a bottom sheet's. */
  --sys-shadow-card: 0 1px 2px rgba(0, 0, 0, .04);
  --sys-shadow-pill: 0 12px 32px rgba(0, 0, 0, .14);
  --sys-shadow-sheet: 0 18px 44px rgba(0, 0, 0, .18);

  /* The four values section-2 anatomy types that had no name (2026-09-05): the
     segmented control's grey track and the shadow under its white segment, the
     shadow under a secondary (white) button, and the 1px ring around the nav
     pill. Named so bento.css, employee-field-app.css, team-home.css and
     inventory.css read them instead of retyping the rgba — the same control in
     four sheets is the same control only while it is the same number. */
  --sys-seg-track: rgba(118, 118, 128, .12);
  --sys-shadow-segment: 0 1px 3px rgba(0, 0, 0, .12);
  --sys-shadow-btn: 0 1px 3px rgba(0, 0, 0, .10);
  --sys-nav-ring: rgba(0, 0, 0, .06);

  /* MOTION. bento's easing curve, which is the only named one in the repo. */
  --sys-ease: cubic-bezier(.2, .6, .2, 1);
  --sys-dur-fast: 180ms;
  --sys-dur: 220ms;
}

/* ----------------------------------------------------------------------------
   THE SIDES ARE PAID HERE, ONCE. Both shells centre their column inside <body>
   (the manager's .app at max-width 480, the crew's .field-app at 430), so padding
   the body is what puts every header, card, bar and button of the flow inside the
   usable width — on the folded Duo the 382 pt left of the status column; on an
   iPhone, nothing, because both insets are 0. border-box so the padding never
   widens the body past the glass (app-scroll-polish.js sets width:100% on it
   while a house is open). Fixed layers do not inherit this: they read the tokens.
   ---------------------------------------------------------------------------- */
body {
  box-sizing: border-box;
  padding-left: var(--sys-safe-l);
  padding-right: var(--sys-safe-r);
}

/* THE TOP BAND THE FOLDED DUO KEEPS AND DOES NOT REPORT (2026-10-26). Folded, iOS
   says safe-area-inset-top is 0 — the status bar is in the side column — and still
   keeps the top 65pt of the display for itself: a touch there reaches the page, a
   tap never does (measured on the simulator, in Safari too; Apple's own apps draw
   nothing in it). 65 is the Dynamic Island's bottom edge. The iOS shell adds the
   band to its safe area from build 19 (DuoSafeArea.swift) and then env() says 65
   itself; until that build, and in Safari or the PWA, native-inset.js marks
   html.duo-folded when the page measures a side column (≥ 70px) with a top of 0,
   and this one rule makes every bar that reads --sys-safe-t pay the band. Never
   both: the class is set only while env(top) reads 0. qa:duo-width §1f. */
html.duo-folded { --sys-safe-t: 65px; }

/* ----------------------------------------------------------------------------
   THE OPEN DUO: WHAT EVERY FULL-WIDTH MENU SCREEN SHARES (2026-10-25). Both shells
   drop their phone column from 600px (bento.css, employee-field-app.css), and a
   screen that was 480 wide is now 585 or more. Two things follow for both apps:

   A SHEET THAT IS WIDER THAN ITS SCROLLER (.sys-hscroll: Money's house sheet,
   Non-house expenses) scrolls sideways inside its own box and SAYS SO: the edge
   it can still scroll toward is faded out, and the fade clears as the end comes
   into view. The fade is a mask driven by the scroller's own scroll position
   (animation-timeline: scroll), so a sheet that fits has no fade at all — with
   no overflow the timeline is inactive and the animation does not apply, so the
   mask lives in the keyframes only. An older engine without scroll timelines
   shows no fade and still scrolls. Each sheet keeps its own first column sticky
   (the expenses sheet already did; the non-house sheet does now).

   A BIG FIGURE NEXT TO A BREAKDOWN (.sys-figure): the cell may shrink
   (min-width: 0) and the figure may break ($161,923.93 is one unbreakable word)
   rather than run under its neighbour, whatever the font size resolves to.
   ---------------------------------------------------------------------------- */
@media (min-width: 600px) {
  .sys-hscroll {
    overflow-x: auto;
    animation: sys-hscroll-edge linear both;
    animation-timeline: scroll(self inline);
  }
  .sys-figure { min-width: 0; }
  .sys-figure strong,
  .sys-figure b { overflow-wrap: anywhere; }
}
@keyframes sys-hscroll-edge {
  from {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent);
  }
  to {
    -webkit-mask-image: linear-gradient(to right, #000 100%, transparent);
    mask-image: linear-gradient(to right, #000 100%, transparent);
  }
}

/* ----------------------------------------------------------------------------
   THE MANAGER (bento). Same names, the manager's choices about AIR.
   Higher specificity than :root, so it wins wherever bento is the UI mode and
   never touches the crew shells, which have no data-ui attribute.

   There is deliberately NO --sys-nav-h / --sys-nav-gap here any more: the two
   apps share one pill (section "THE NAV"), so the manager's nav measures what
   the crew's does and the numbers live in :root only. What survives is the air
   the manager keeps around it, which is a choice and not a measurement.
   ---------------------------------------------------------------------------- */
html[data-ui="bento"] {
  /* 28px, so --sys-scroll-clear resolves to nav 64 + float 14 + 28 = 106px +
     inset. The manager's lists end further from the pill than the crew's do;
     that is a real choice and it is kept. */
  --sys-scroll-air: 28px;

  /* 16px, so --sys-bar-bottom resolves to 78 + 16 = 94px + inset for the
     manager's toast. The crew floats its toast 12px above the nav and the
     manager floats it 16px, nobody can tell which is "right", and moving one of
     them 4px to make them agree is a screen that moved for no reason. The height
     is derived; the air is a choice; the choice is written here. */
  --sys-bar-air: 16px;

  /* The SAME card shadow as :root, on purpose: the two apps now share one card.
     The override survives as the named place the manager's elevation would be
     changed, so it is never a diff away from becoming a literal in bento.css. */
  --sys-shadow-card: 0 1px 2px rgba(0, 0, 0, .04);
}

/* ----------------------------------------------------------------------------
   THE KEYBOARD RULE — one redefinition, every bar in the app moves.

   A bar anchored to a constant `bottom:` measures against a viewport the
   keyboard already shrank, so it walks up into the middle of the screen and
   swallows taps meant for the field under it. That is how a stager lost what she
   had typed and landed on the hours form.

   Rather than every surface writing its own keyboard rule, --sys-bar-bottom is
   REDEFINED here. Any bar built on the token moves correctly without knowing the
   keyboard exists. 8px because that is the gap the crew's Submit bar already
   uses and the two must not diverge.

   In Safari and the PWA the layout viewport does not shrink, so --sys-kb carries
   the lift. In the Capacitor shell the webview resizes itself, --sys-kb is 0, and
   the same expression lands 8px above the keyboard there too. One rule, both
   shells, which is the whole point.

   BEHIND A POINTER QUERY, and this is not optional. An earlier draft moved and
   hid bars on any focused field with no query; at 1100px wide, clicking into the
   Jobs search made the menu vanish, stay exactly where it was, and a tap where it
   had been fell through and opened a house. An invisible control that eats taps
   is the precise bug this wave exists to kill, so it may not be reintroduced by
   the fix for it. hover/pointer rather than width, because an iPad is a touch
   device with a keyboard and a wide screen.
   ---------------------------------------------------------------------------- */
@media (hover: none) and (pointer: coarse) {
  html.kb-open,
  html.kb-typing {
    --sys-bar-bottom: calc(var(--sys-kb) + 8px + var(--sys-safe-b));
    /* The same lift for the two offsets that are NOT measured off the nav. A bar
       inside a full-screen editor and a floating sheet both have to clear the
       keyboard, and they have to clear it by the same 8px the crew's Submit bar
       does — three names, one arithmetic, one place to change it. This is the
       whole mechanism behind the manager's Save bar becoming keyboard-aware
       without a line of manager JavaScript measuring anything. */
    --sys-overlay-bottom: calc(var(--sys-kb) + 8px + var(--sys-safe-b));
    --sys-sheet-bottom: calc(var(--sys-kb) + 8px + var(--sys-safe-b));
    /* A docked sheet rises by the keyboard exactly, with no gap and no inset: it
       is flush with the bottom edge, so flush with the top of the keyboard is the
       matching answer — and the home indicator is not on screen while the keyboard
       is. This is character-for-character what the two deleted attachKeyboardLift()
       copies did (sheet.style.bottom = kb + "px"). */
    --sys-dock-bottom: var(--sys-kb);
    --sys-kb-view-h: calc(100dvh - var(--sys-kb));
  }
}

/* ----------------------------------------------------------------------------
   NOT A TOKEN — light, declared before any script runs.

   The app is light-only and deliberately so: the two orphan dark blocks were
   removed in a prior wave and both files carry a comment forbidding their
   return. But neither shell ever declares it. The manager gets light
   imperatively from apple-manager.js, after DOM-ready; the crew gets nothing.
   So on a phone set to dark, the crew app's native form controls, date pickers,
   scrollbars and autofill render dark inside a white app — the date picker on
   the hours form, in particular.

   One line, in a stylesheet, for both apps, so it lands before any JS runs.
   ---------------------------------------------------------------------------- */
:root {
  color-scheme: light;
}

/* ============================================================================
   THE NAV — one ruleset, both apps.

   The bottom nav is the component the manager and the crew must render
   identically, so it is painted here and ONLY here. The manager's is
   `html[data-ui="bento"] .bento-nav` (3 buttons, bento-shell.js ensureNav(),
   active = .on) and the crew's is `.field-app .nav` (4 buttons,
   employee-field-app.js bottomNav(), active = .active). bento.css and
   employee-field-app.css keep their layout hooks (hide-for-keyboard, hide-under-
   drawer, the unread dot) and paint nothing.

   SPECIFICITY IS THE MECHANISM, NOT !important. This file is the first link in
   every shell, so an app rule at equal specificity would win by source order.
   `.field-app .nav` is (0,2,0) against the crew's bare `.nav` (0,1,0);
   `html[data-ui="bento"] .bento-nav` is the manager's own selector, so bento.css
   must not repeat it — and does not.

   THE ARITHMETIC IS THE TOKEN. 1px border + 7px padding + 48px button + 7px +
   1px = 64px = --sys-nav-h, with the hairline inside the contract's 8px ring.
   min-height rather than height, so the box still grows if a button ever
   outgrows 48 — and --sys-nav-h is the number to change first if it does.

   FROSTED, and the blur is in the SAME rule as the translucent background on
   purpose: qa:touch-targets and qa:layout-contract accept a see-through bottom
   bar only when it is blurred, because a bar you can read the list through is a
   bar people tap through. Split these two declarations across rules and both
   suites go red.

   flex:1 buttons rather than a grid with a column count, so the one rule fits 3
   buttons and 4.
   ============================================================================ */
html[data-ui="bento"] .bento-nav,
.field-app .nav {
  position: fixed;
  left: var(--sys-center-x);
  bottom: calc(var(--sys-nav-gap) + var(--sys-safe-b));
  transform: translateX(-50%);
  width: min(358px, calc(var(--sys-usable-w) - 32px));
  min-height: var(--sys-nav-h);
  box-sizing: border-box;
  margin: 0;
  padding: 7px;
  border: 1px solid var(--sys-nav-ring);
  border-radius: var(--sys-r-pill);
  background: rgba(255, 255, 255, .78);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  backdrop-filter: blur(24px) saturate(180%);
  box-shadow: var(--sys-shadow-pill);
  z-index: var(--sys-z-nav);
  display: flex;
  align-items: stretch;
  gap: 4px;
}

html[data-ui="bento"] .bento-nav button,
.field-app .nav button {
  flex: 1 1 0;
  min-width: 0;
  min-height: 48px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--sys-r-pill);
  background: none;
  color: var(--sys-muted);
  font-family: var(--sys-font);
  font-size: 10.5px;
  font-weight: var(--sys-w-medium);
  line-height: 1.1;
  letter-spacing: 0;
  text-transform: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  position: relative; /* the manager's unread dot / count badge anchor to the button */
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Active: the accent colour and a step up in weight. NO background pill — the
   colour is the whole signal. */
html[data-ui="bento"] .bento-nav button.on,
.field-app .nav button.active {
  color: var(--sys-accent);
  font-weight: var(--sys-w-semibold);
  background: none;
}

/* 22px icons. The stroke weight lives in the icon markup — 1.8 on the crew's
   24-unit grid, 1.5 on the manager's 20-unit grid — and both scale to the same
   1.65px line at 22px, so it is not restated here (a CSS stroke-width would
   also flatten the crew's dotted "More" glyph). */
html[data-ui="bento"] .bento-nav button svg,
.field-app .nav button svg {
  width: 22px;
  height: 22px;
  flex: none;
  display: block;
  stroke: currentColor;
  fill: none;
}

/* THE UNREAD COUNT ON MESSAGES — one badge, both apps. It was the manager's own
   (bento.css) while only the manager had Messages; the crew has the same entry in
   the same pill now, and a second copy of nine declarations is how two badges end
   up two different reds. Anchored at the icon's corner; the button is already
   position:relative. No width change for the entry: the pill seats flex:1 buttons.
   --sys-red-ink is what bento.css's --red alias resolves to, so the manager's badge
   is the colour it was. */
html[data-ui="bento"] .bento-nav button .nav-count,
.field-app .nav button .nav-count {
  position: absolute; top: 2px; right: calc(50% - 20px); min-width: 15px; height: 15px; padding: 0 4px;
  border-radius: var(--sys-r-pill); background: var(--sys-red-ink); color: #fff;
  font-weight: var(--sys-w-bold); font-size: 9.5px; line-height: 15px; font-family: var(--sys-font); text-align: center;
}

/* ============================================================================
   THE RAIL — the folded iPhone Duo's bars stand in the status column (2026-10-26).

   Mario, on the Duo's outer display: "en la pantalla de afuera del dúo ese menú
   debería estar de lado". Apple's rule for the folded Duo (Tech Talk "Design for
   iPhone Duo"): toolbars and tab bars appear vertically below the status bar, in
   the column the system keeps at the side — back or close first, then the
   prominent action, a gap, then the tabs as symbols — so the content gets the
   whole height. Option A of the mockup he approved the same day.

   WHEN. html.duo-column, set by native-inset.js when the page measures a status
   column on the RIGHT (--sys-safe-r ≥ 70) on a one-pane width (under the 600px
   two-pane breakpoint): the folded Duo and nothing else. An iPhone 17 has no
   column (0), the open Duo is two panes (669), a phone held sideways has 59 at
   each side and is wider than it is tall. Not html.duo-folded: that class means
   "the page pays the 65pt band itself" and is off in the shell that reports the
   band (build 19), while the column is there either way.

   WHERE. The column is 84pt, x 382–466. iOS draws its own frosted status pill
   there — the clock, the Wi-Fi ring, the signal dots — at x≈385–438, y≈20–156,
   and a tap on that pill never reaches the page; a tap on the Dynamic Island
   (y 28–65) opens Control Center. Below it every tap reaches the page (measured
   in the iOS 27.1 simulator with a tap map: y 164…672, every x of the column).
   So the rail starts at --sys-rail-top (164) and nothing of ours sits higher.

   WHAT. Three things, in Apple's order:
     1. .sys-rail — duo-rail.js's two proxies for the screen header's Back (‹ or
        the house's Back) and its prominent action (+ on Houses), 44pt circles,
        shown only while the original is the top layer (fail closed: a control
        appears only where it makes sense). The originals hide below.
     2. a gap.
     3. the nav pill itself — the SAME .bento-nav / .field-app .nav, stood up:
        one column of the same 48px buttons in the same frosted glass. No
        second nav, no new look.
   And the bottom is free: --sys-nav-h and --sys-nav-gap are 0, so --sys-nav-top,
   --sys-bar-bottom, --sys-scroll-clear and the crew's --crew-nav-h all collapse to
   the home indicator and the list scrolls to the foot of the glass.

   OPEN, NOTHING CHANGES; A 393 PHONE, NOTHING (qa:duo-width §1g, §2r, §4).
   ============================================================================ */
html.duo-column {
  --sys-nav-h: 0px;
  --sys-nav-gap: 0px;
  /* The foot of iOS's status pill in the column, plus 8px of air. */
  --sys-rail-top: 164px;
  /* Back 44 + 8 + the action 44 + the 28px gap of the mockup. */
  --sys-rail-tabs-top: calc(var(--sys-rail-top) + 124px);
  /* The pill's width in the column: 7px padding + a 50px button + 7px. */
  --sys-rail-w: 64px;
}

/* The nav, stood up in the column. Geometry only: the glass, the buttons, the
   active colour and the badge are the rules above, untouched. */
html.duo-column[data-ui="bento"] .bento-nav,
html.duo-column .field-app .nav {
  left: auto;
  right: calc((var(--sys-safe-r) - var(--sys-rail-w)) / 2);
  bottom: auto;
  top: var(--sys-rail-tabs-top);
  transform: none;
  width: var(--sys-rail-w);
  flex-direction: column;
}
html.duo-column[data-ui="bento"] .bento-nav button,
html.duo-column .field-app .nav button {
  flex: none;
  width: 100%;
  min-height: 48px;
}
/* While a fixed layer covers the page (duo-rail.js: html.duo-covered — the house,
   a sheet, Settings, the sign-in) the pill goes, as it does under that layer on a
   phone: in the column it would stand beside the layer and lead underneath it. */
html.duo-column.duo-covered[data-ui="bento"] .bento-nav,
html.duo-column.duo-covered .field-app .nav { display: none; }

/* The header's Back and + stand at the top of the rail (duo-rail.js) and leave
   the header; what stays beside the title is what is small — calendar, sort,
   the view. The crew's ‹ column goes with its button. */
html.duo-column[data-ui="bento"] .bento-hd .bento-back,
html.duo-column[data-ui="bento"] .bento-hd [data-hd-add],
html.duo-column[data-ui="bento"] #drawer.open .drawer-head #closeDrawer,
html.duo-column .field-app .app-top [data-action="back"],
html.duo-column .field-app .jz-head [data-hd-add] { display: none !important; }
html.duo-column .field-app .top-row.lead { grid-template-columns: minmax(0, 1fr); }

.sys-rail {
  display: none;
  position: fixed;
  top: var(--sys-rail-top);
  right: calc((var(--sys-safe-r) - var(--sys-tap)) / 2);
  z-index: var(--sys-z-sheet);
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
}
html.duo-column .sys-rail { display: flex; }
.sys-rail button {
  width: var(--sys-tap);
  height: var(--sys-tap);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--sys-r-pill);
  background: var(--sys-panel);
  box-shadow: var(--sys-shadow-card);
  color: var(--sys-accent);
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sys-rail button[hidden] { display: none; }
/* The prominent action: filled, as a toolbar's Done is. */
.sys-rail [data-rail-add] { background: var(--sys-accent); color: #fff; }
.sys-rail button svg { width: 22px; height: 22px; display: block; stroke: currentColor; fill: none; }

/* ============================================================================
   THE SIDE — the open iPhone Duo's one left column (2026-10-26, Mario: "también
   quiero que optimices la pantalla de adentro… es toda la experiencia del Duo";
   he picked A + B of the mockup).

   Apple's rule for the inner display: regular width, the tab bar "converts to a
   sidebar". So the SAME nav pill that stands up in the status column when the
   phone is folded (THE RAIL, above) stands as a column on the LEFT when it is
   open: one nav, three widths — a row on a phone, an icon rail folded, a sidebar
   open. In it, top to bottom, Apple's order again: duo-rail.js's two proxies for
   the header's Back and + ; Houses · Messages · Activity (the pill's own
   buttons); then a small "Menu" line and the rest of the Menu hub as rows —
   Money, Team, Inventory, Proposals, Calendar, Settings — each pressing what
   its hub tile presses (bento-shell.js HUB_DOORS; the crew's More rows press
   what More's rows press). No bottom pill, no Menu screen while open: landing
   on Menu shows Houses (bento-shell.js after()).

   WHEN. html.duo-side, set by native-inset.js on a two-pane width (≥ 600) when
   the page measures a status column on the right (--sys-safe-r ≥ 70: the open
   Duo held sideways, 951 × 669) or is an iPhone taller than it is wide (the
   open Duo upright, 669 × 951, where iOS puts the status bar along the top and
   reports no column). Nothing else: no iPhone is 600 wide upright, one held
   sideways is wider than tall and has no 84pt column, an iPad is not an iPhone.

   HOW WIDE. --sys-side-w: 190 sideways (≥ 900, the names show), 66 upright
   (icons only; the names stay in the buttons for VoiceOver, font-size 0 hides
   them). The column is its own token, and --sys-safe-l (section 1) adds it, so
   the two panes, every full-width Menu screen, every sheet's centre and the
   body all clear the column by reading the name they already read. The column
   itself and the rail sit on the raw inset, --sys-inset-l.

   A 393 PHONE AND THE FOLDED DUO: NOTHING (qa:duo-width §2s, §4; --sys-side-w
   is 0 and the class is off). A PAGE WITH NO NAV — the proposals page, a
   client page, the sign-in before the nav is drawn — pays no column either:
   the tokens hang on the nav being in the document (:has), since the column IS
   the nav (qa:duo-pages pins the proposal sheet sideways).
   ============================================================================ */
html.duo-side:has(.bento-nav, .field-app .nav) {
  --sys-nav-h: 0px;
  --sys-nav-gap: 0px;
  /* Mario, 2026-10-27, on the open Duo held sideways: "Ese menú debería ocupar
     10% de la pantalla, el otro 45% y el otro 45%". So the column is a tenth of
     the glass at every open width (67 upright, 95 sideways), icons only, and the
     two panes split what is left in half (--sys-pane-w below). */
  --sys-side-w: 10vw;
  --sys-safe-l: calc(var(--sys-inset-l) + var(--sys-side-w));
  --sys-pane-w: calc(var(--sys-usable-w) / 2);
  /* The rail's top: under the status band, with 12px of air. */
  --sys-rail-top: calc(var(--sys-safe-t) + 12px);
  /* The two proxies stack: Back 44 + 8 + the action 44 + a 20px gap. */
  --sys-side-tabs-top: calc(var(--sys-rail-top) + 116px);
}

/* The nav, stood as the column. Geometry only — the glass, the badge and the
   active colour are THE NAV's rules, untouched (qa:touch-targets: one rule
   paints the pill). top/bottom give the height, so no height is typed. */
html.duo-side[data-ui="bento"] .bento-nav,
html.duo-side .field-app .nav {
  left: var(--sys-inset-l);
  right: auto;
  top: 0;
  bottom: 0;
  transform: none;
  width: var(--sys-side-w);
  padding: var(--sys-side-tabs-top) 8px calc(var(--sys-safe-b) + 12px);
  border-width: 0 1px 0 0;
  border-radius: 0;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* The pill's buttons as rows: icon then name, left-aligned, 44pt tall; the
   active one on a white card (the mockup's row). */
html.duo-side[data-ui="bento"] .bento-nav button,
html.duo-side .field-app .nav button {
  flex: none;
  width: 100%;
  flex-direction: row;
  justify-content: flex-start;
  gap: 10px;
  min-height: var(--sys-tap);
  padding: 0 10px;
  border-radius: var(--sys-r-control);
  font-size: var(--sys-t-body);
  line-height: 1.2;
  text-align: left;
}
html.duo-side[data-ui="bento"] .bento-nav button.on,
html.duo-side .field-app .nav button.active {
  background: var(--sys-panel);
  box-shadow: var(--sys-shadow-card);
}
/* The crew's icon wrapper (<strong class="nav-ic">) is a block in the pill; a flex item here. */
html.duo-side .field-app .nav button .nav-ic { display: flex; flex: none; }
/* The badges sit at the row's right end, not at the icon's corner. */
html.duo-side[data-ui="bento"] .bento-nav button .nav-count,
html.duo-side .field-app .nav button .nav-count { top: 50%; right: 10px; transform: translateY(-50%); }
html.duo-side[data-ui="bento"] .bento-nav button .nav-dot,
html.duo-side .field-app .nav button .nav-dot { top: 50%; right: 12px; transform: translateY(-50%); }
/* Icons only, at every open width (the column is a tenth of the glass). The
   names are still the buttons' text (VoiceOver reads them); the badges keep
   their own size. */
html.duo-side[data-ui="bento"] .bento-nav button,
html.duo-side .field-app .nav button { font-size: 0; justify-content: center; padding: 0; gap: 0; }
html.duo-side[data-ui="bento"] .bento-nav button .nav-count,
html.duo-side .field-app .nav button .nav-count { top: 2px; right: 2px; transform: none; }
html.duo-side[data-ui="bento"] .bento-nav button .nav-dot,
html.duo-side .field-app .nav button .nav-dot { top: 4px; right: 6px; transform: none; }
/* The Menu line and the hub's rows (bento-shell.js [data-bento-menu],
   employee-field-app.js [data-nav-row]): in the pill they do not exist. */
html[data-ui="bento"] .bento-nav .nav-menu-label,
html[data-ui="bento"] .bento-nav [data-bento-menu],
.field-app .nav .nav-menu-label,
.field-app .nav [data-nav-row] { display: none; }
html.duo-side[data-ui="bento"] .bento-nav [data-bento-menu],
html.duo-side .field-app .nav [data-nav-row] { display: flex; }
html.duo-side[data-ui="bento"] .bento-nav .nav-menu-label,
html.duo-side .field-app .nav .nav-menu-label {
  display: block;
  margin: 14px 10px 4px;
  padding-top: 10px;
  border-top: 1px solid var(--sys-line);
  color: var(--sys-muted);
  font-size: var(--sys-t-caption);
  font-weight: var(--sys-w-semibold);
  letter-spacing: .06em;
  text-transform: uppercase;
}
html.duo-side[data-ui="bento"] .bento-nav .nav-menu-label,
html.duo-side .field-app .nav .nav-menu-label { font-size: 0; margin: 10px 6px 4px; padding-top: 0; }
/* The hub itself is not a place while the column is up: no Menu button. */
html.duo-side[data-ui="bento"] .bento-nav [data-bento-nav="home"] { display: none; }

/* The header's Back and + leave the header, as folded (THE RAIL's rule, kept
   byte for byte there; this one is the column's). The section's Back ("Back to
   menu") is not offered: the hub is not a screen here, the column is. */
html.duo-side[data-ui="bento"] .bento-hd .bento-back,
html.duo-side[data-ui="bento"] .bento-hd [data-hd-add],
html.duo-side[data-ui="bento"] #drawer.open .drawer-head #closeDrawer,
html.duo-side .field-app .app-top [data-action="back"],
html.duo-side .field-app .jz-head [data-hd-add] { display: none !important; }
html.duo-side .field-app .top-row.lead { grid-template-columns: minmax(0, 1fr); }

/* The two proxies at the top of the column, stacked, centred in it. */
html.duo-side .sys-rail {
  display: flex;
  top: var(--sys-rail-top);
  right: auto;
  left: calc(var(--sys-inset-l) + (var(--sys-side-w) - var(--sys-tap)) / 2);
}
/* A layer over the whole glass (the sign-in) covers the column too: it starts at
   the raw inset, and html.duo-covered (duo-rail.js) takes the column down. */
html.duo-side #loginModal.open { left: var(--sys-inset-l); }
html.duo-side.duo-covered[data-ui="bento"] body .bento-nav,
html.duo-side.duo-covered .field-app .nav { display: none; }

/* ============================================================================
   OPT-IN UTILITIES — the contract anatomy as classes.

   Nothing in either app uses these until a screen adds the class. They exist so
   a component restyled in one app is restyled the same in the other by
   construction, and so the values above are read rather than retyped. Each is
   deliberately flat: one selector, one box, no nesting, no state machinery.
   ============================================================================ */

/* Segmented control: grey track, white active segment.
   <div class="sys-seg"><button class="on">Overview</button><button>Pull</button></div>
   Items are 44px tall, not the 31px the label's padding alone would give: every
   tap target in this app is at least --sys-tap, and a segment is a tap target. */
.sys-seg {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--sys-seg-track);
  border-radius: var(--sys-r-segment-outer);
}
.sys-seg > * {
  flex: 1 1 0;
  min-width: 0;
  min-height: var(--sys-tap);
  margin: 0;
  padding: 8px 0;
  border: 0;
  border-radius: var(--sys-r-segment);
  background: none;
  color: var(--sys-ink);
  font-family: inherit;
  font-size: var(--sys-t-body);
  font-weight: var(--sys-w-medium);
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sys-seg > .on {
  background: var(--sys-panel);
  font-weight: var(--sys-w-semibold);
  box-shadow: var(--sys-shadow-segment);
}

/* Card: white, borderless, lifted by the hairline shadow. Rows inside it are
   the app's own markup; the contract for a row is min-height 52px, padding
   8px 16px, border-top 1px solid var(--sys-line). */
.sys-card {
  background: var(--sys-panel);
  border: 0;
  border-radius: var(--sys-r-card);
  box-shadow: var(--sys-shadow-card);
  padding: 16px;
}

/* Status chip: sentence case, never uppercase. Neutral by default; one modifier
   per status colour, and the accent pair for "Upcoming". */
.sys-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 9px;
  border-radius: var(--sys-r-pill);
  background: var(--sys-fill-2);
  color: var(--sys-muted);
  font-size: var(--sys-t-caption);
  font-weight: var(--sys-w-semibold);
  line-height: 1.2;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}
.sys-chip-accent { background: var(--sys-accent-soft); color: var(--sys-accent); }
.sys-chip-green  { background: var(--sys-green-soft);  color: var(--sys-green); }
.sys-chip-orange { background: var(--sys-orange-soft); color: var(--sys-orange); }
.sys-chip-red    { background: var(--sys-red-soft);    color: var(--sys-red-ink); }
.sys-chip-purple { background: var(--sys-purple-soft); color: var(--sys-purple); }

/* Tick: a 24px circle; ticked = accent fill with a white check drawn in CSS so
   the markup is one empty element. The tap target is the ROW the tick sits in
   (52px), not the circle. */
.sys-tick {
  width: 24px;
  height: 24px;
  flex: none;
  box-sizing: border-box;
  border: 1.5px solid var(--sys-faint);
  border-radius: var(--sys-r-pill);
  background: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.sys-tick.on {
  background: var(--sys-accent);
  border-color: var(--sys-accent);
}
.sys-tick.on::after {
  content: "";
  width: 5px;
  height: 10px;
  margin-top: -2px;
  border: solid #FFFFFF;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* Camera square: 44x44, quiet fill, accent "+" glyph (an inline SVG child). */
.sys-cam {
  width: 44px;
  height: 44px;
  flex: none;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--sys-r-control);
  background: var(--sys-fill);
  color: var(--sys-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sys-cam svg {
  width: 20px;
  height: 20px;
  display: block;
  stroke: currentColor;
  fill: none;
}

/* Buttons. Primary is the accent; secondary is a white pill with the accent as
   text and a soft shadow; text is the accent alone ("Edit"). Each class stands
   on its own, so `class="sys-btn-secondary"` is enough. */
.sys-btn,
.sys-btn-secondary,
.sys-btn-text {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: var(--sys-tap);
  margin: 0;
  padding: 0 18px;
  border: 0;
  border-radius: var(--sys-r-pill);
  background: var(--sys-accent);
  color: #FFFFFF;
  font-family: inherit;
  font-size: var(--sys-t-lead);
  font-weight: var(--sys-w-semibold);
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sys-btn-secondary {
  background: var(--sys-panel);
  color: var(--sys-accent);
  box-shadow: var(--sys-shadow-btn);
}
.sys-btn-text {
  padding: 0 8px;
  background: none;
  color: var(--sys-accent);
  font-weight: var(--sys-w-medium);
}

/* Progress bar: 4px track, accent fill. <div class="sys-progress"><div style="width:40%"></div></div> */
.sys-progress {
  height: 4px;
  border-radius: var(--sys-r-pill);
  background: var(--sys-track);
  overflow: hidden;
}
.sys-progress > * {
  height: 100%;
  border-radius: var(--sys-r-pill);
  background: var(--sys-accent);
}

/* ============================================================================
   THE SIGN-IN — one front door, drawn once, for both apps and for /join.

   Mario, 2026-09-30: "Yo quisiera hacer el login más profesional." What he was
   looking at was two different products. The manager's was a blue "S" tile over
   "Manager sign in", UPPERCASE labels, a button that said "Open". The crew's was
   a left-aligned logo over "Team workspace", a white card on a grey page, a field
   called "Name", a button that said "Open app" and a link that said "I forgot my
   password". Same company, same account, same password — and nothing on the two
   screens agreed, because each app's stylesheet drew its own.

   So it is painted here and ONLY here, exactly as the nav above is:
     · the manager's  — public/index.html, inside #loginModal
     · the crew's     — public/employee-field-app.js loginScreen()
     · the new hire's — public/join.html (the page an invitation opens)
   Each of those owns only its FRAME (where the full-screen sheet sits, and its
   safe area); everything a person reads or presses is this block.

   WHAT IS ON IT, and nothing else: the company's mark, its name, Email, Password,
   one button, one way back in, and the two legal links the App Store requires
   before an account exists (5.1.1(i)).

   SPECIFICITY IS THE MECHANISM here too. This file is the first link in every
   shell, so the apps' bare `input {…}`, `label {…}` and `button {…}` rules would
   win by source order at equal weight. Every selector below is
   `.sys-signin .sys-signin-x` (0,2,0) or heavier, which the bare element rules
   and the single-class rules of both apps cannot reach. qa:employee-app opens no
   browser, so what proves the two really match is the comparison of computed
   styles in the commit that added this — if you change a rule here, open BOTH
   apps and look.

   16px fields: under 16px iOS zooms the page in on focus and never zooms back.
   44px minimum on everything a finger lands on (--sys-tap).
   ============================================================================ */
.sys-signin {
  box-sizing: border-box;
  width: 100%;
  max-width: 340px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  font-family: var(--sys-font);
  color: var(--sys-ink);
  text-align: left;
}
.sys-signin *,
.sys-signin *::before,
.sys-signin *::after { box-sizing: border-box; }

/* The company's own logo where it has one. Drawn bare: it is a wordmark on
   white, on a white ground, so a tile around it is a box around nothing. */
.sys-signin .sys-signin-mark {
  display: block;
  width: auto;
  height: 72px;
  max-width: 100%;
  margin: 0 auto var(--sys-s-32);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  object-fit: contain;
}
.sys-signin .sys-signin-title {
  margin: 0 0 var(--sys-s-32);
  padding: 0;
  font-family: var(--sys-font);
  font-size: var(--sys-t-display);
  font-weight: var(--sys-w-semibold);
  line-height: var(--sys-line-tight);
  letter-spacing: -.01em;
  text-align: center;
  text-transform: none;
  color: var(--sys-ink);
}
/* One short line under the title, for the screens that have something to say
   before the fields (/join: who invited you, and as what). */
.sys-signin .sys-signin-lead {
  margin: calc(var(--sys-s-24) * -1) 0 var(--sys-s-24);
  font-size: var(--sys-t-lead);
  font-weight: var(--sys-w-regular);
  line-height: var(--sys-line-body);
  text-align: center;
  color: var(--sys-muted);
}
.sys-signin .sys-signin-lead b { font-weight: var(--sys-w-semibold); color: var(--sys-ink); }
/* THE NAME IS SAID ONCE. The logo is a wordmark — it already reads "StagingEstate"
   — so the name set in type directly under it was the company introducing itself
   twice. With a mark in front of it the title is still there for VoiceOver (the
   image's alt is empty, so it is read once there too) and takes no room on the
   glass. With no mark — a company that has no logo, or an image that failed to
   load and removed itself (onerror) — this rule matches nothing and the title IS
   the mark. */
.sys-signin .sys-signin-mark + .sys-signin-title {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.sys-signin .sys-signin-form {
  display: flex;
  flex-direction: column;
  gap: var(--sys-s-16);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}
.sys-signin .sys-signin-field {
  display: flex;
  flex-direction: column;
  gap: var(--sys-s-6);
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font-size: var(--sys-t-body);
  font-weight: var(--sys-w-medium);
  line-height: var(--sys-line-tight);
  letter-spacing: 0;
  text-transform: none;
  color: var(--sys-muted);
}
.sys-signin .sys-signin-field > span { display: block; }
.sys-signin .sys-signin-field input {
  display: block;
  width: 100%;
  height: 52px;
  min-height: 52px;
  margin: 0;
  padding: 0 var(--sys-s-16);
  border: 0;
  border-radius: var(--sys-r-control);
  background: var(--sys-fill);
  box-shadow: none;
  color: var(--sys-ink);
  font-family: var(--sys-font);
  font-size: var(--sys-t-field);
  font-weight: var(--sys-w-regular);
  line-height: 1.25;
  letter-spacing: 0;
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}
.sys-signin .sys-signin-field input::placeholder { color: var(--sys-placeholder); }
/* The app's one focus ring: 2px of the accent, as on every other field. */
.sys-signin .sys-signin-field input:focus {
  outline: none;
  background: var(--sys-panel);
  box-shadow: 0 0 0 2px var(--sys-accent);
}
/* A value the screen states rather than asks for (/join: the address the
   invitation was sent to). Same box, quieter, and not a thing to tap into. */
.sys-signin .sys-signin-field input[readonly] {
  color: var(--sys-muted);
  box-shadow: none;
  background: var(--sys-fill);
}
/* One rule said once, under the field it is about ("At least 8 characters"). It
   belongs to the field ABOVE it, so it takes back half of the form's gap: at the
   full 16px it sat midway and read as a caption for the next label. */
.sys-signin .sys-signin-hint {
  margin: calc(var(--sys-s-8) * -1) 0 0;
  font-size: var(--sys-t-small);
  font-weight: var(--sys-w-regular);
  line-height: var(--sys-line-body);
  color: var(--sys-muted);
}
/* What went wrong, in words, where the eye already is. Empty = takes no room. */
.sys-signin .sys-signin-error {
  margin: 0;
  font-size: var(--sys-t-body);
  font-weight: var(--sys-w-regular);
  line-height: var(--sys-line-body);
  text-align: left;
  color: var(--sys-red-ink);
}
.sys-signin .sys-signin-error:empty { display: none; }

.sys-signin .sys-signin-go {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 52px;
  min-height: 52px;
  margin: var(--sys-s-8) 0 0;
  padding: 0 var(--sys-s-20);
  border: 0;
  border-radius: var(--sys-r-pill);
  background: var(--sys-accent);
  box-shadow: none;
  color: #FFFFFF;
  font-family: var(--sys-font);
  font-size: var(--sys-t-headline);
  font-weight: var(--sys-w-semibold);
  line-height: 1.2;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sys-signin .sys-signin-go:disabled { opacity: .6; cursor: default; }

/* The quiet second action under the button: "Forgot password?". */
.sys-signin .sys-signin-link {
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-height: var(--sys-tap);
  margin: var(--sys-s-8) 0 0;
  padding: 0 var(--sys-s-12);
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: var(--sys-accent);
  font-family: var(--sys-font);
  font-size: var(--sys-t-lead);
  font-weight: var(--sys-w-medium);
  line-height: 1.2;
  letter-spacing: 0;
  text-decoration: none;
  text-transform: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.sys-signin .sys-signin-legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  margin: var(--sys-s-8) 0 0;
  padding: 0;
  font-size: var(--sys-t-body);
  font-weight: var(--sys-w-regular);
  line-height: 1;
  color: var(--sys-muted);
}
.sys-signin .sys-signin-legal a {
  display: inline-flex;
  align-items: center;
  min-height: var(--sys-tap);
  padding: 0 var(--sys-s-10);
  color: var(--sys-muted);
  font-weight: var(--sys-w-regular);
  text-decoration: none;
}
