/* ============================================================
   Magic Helpers Operations Platform
   Design tokens v1.1
   Locked: 30 Aug 2026 (session 12)
   v1.1, session 24: border-collapse changed from collapse to
   separate. collapse silently breaks the sticky table header.
   The served screens now import this file rather than copying it.
   Source of truth: mh-properties-visual-v16.html

   Every screen imports this file. Do not copy values into a
   screen's own CSS. If a screen needs a value that is not here,
   the value gets added here first, then used.
   ============================================================ */

/* ============================================================
   Inter, self-hosted -- session 107 Part 1. Replaces the Google
   Fonts <link> that was here before: an internal tool should not
   depend on a third party on every page load. This is the exact
   file Google's own CSS2 API was serving for the latin subset
   (fonts.gstatic.com/s/inter/v20/UcCo3FwrK3iLTcviYwY.woff2),
   downloaded once rather than re-hosted through Google -- same
   bytes, same true variable font (a single file covering the
   whole weight axis, not one static instance per weight), just
   served from this platform instead. font-weight:100 900 is a
   range, not a list: 450, 500, 550 and 600 (every weight this app
   asks for) all render from this one file, not just the four named
   in the brief. */
@font-face{
  font-family:"Inter";
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url("/fonts/inter-var-latin.woff2") format("woff2");
}

:root{

  /* ---------- Ink and neutrals ----------
     Session 157: retargeted to docs/mh-property-page-full-mockup.html,
     the whole-platform restyle Daniel asked for ("that current tan
     design that looks like a 1920's paper notebook... NOT what I
     want"). The mockup carries one ink for all body text (#4A4F55,
     already this file's own --text, unchanged) rather than a second,
     near-black --ink for "primary" text -- so --ink now equals --text
     instead of introducing a contrast level the mockup never uses.
     Headings, links and emphasis are teal (--teal-dark, below,
     already the mockup's exact hex, unchanged). --muted moved to the
     mockup's own value. */
  --ink:        #4A4F55;   /* body and primary text alike, mockup's own single "ink" */
  --text:       #4A4F55;   /* secondary text, most table cells */
  --muted:      #7A828A;   /* labels, placeholders, empty values -- mockup's own value */

  /* --line/--line-soft/--gray-bg/--paper: session 107 Part 3 measured
     these as the mockup's own warm tan (E4DCC7/F1EBDC) -- the exact
     "1920's paper notebook" cream this session removes. Session 157
     retargets all four to docs/mh-property-page-full-mockup.html's
     own flat, cool palette: borders/lines #DCE1E5, and the mockup's
     own row-divider trick of using the PAGE background colour
     (#EDEFF1, new --page-bg below) as the softer internal divider
     inside a white card (its own `.r{border-bottom:1px solid
     var(--bg)}`), which --line-soft and --gray-bg now both point at. */
  --line:       #DCE1E5;   /* borders, header underline */
  --line-soft:  #EDEFF1;   /* row dividers inside a card -- the mockup's own page-bg reused */
  --gray-bg:    #EDEFF1;   /* inactive pill fill -- the mockup's own .p-gray background */
  --page-bg:    #EDEFF1;   /* the page itself, session 157: flat, not a sky gradient */
  --white:      #FFFFFF;

  /* ---------- Brand ----------
     Teal navigates. Yellow creates and marks position.
     Amber warns. Nothing decorates. */
  --teal-dark:   #1D6E77;  /* sidebar, links, active states */
  --teal-light:  #6FD5D4;  /* reserved, sparingly */
  /* Session 110 Part 6. The mockup's own .pill.teal uses this exact hex as
     a background but never names it, session 110 Part 0 found -- given a
     name here rather than left as a magic value, since the live app now
     has two real uses for it (the say-this block, the rail's softened
     office segment). Not a new colour: ported verbatim from the mockup's
     own rule, just given a token the way every other mockup colour got
     one. */
  --teal-pale:   #DDF3F3;
  --sidebar-bg:  #1D6E77;
  --sidebar-line: rgba(255,255,255,.14);

  --yellow:      #FFD13A;  /* primary actions, hover edge, brand mark */
  --yellow-hover:#F5C21F;
  --orange:      #FF8A33;  /* reserved */

  /* ---------- Status ----------
     Session 157: retargeted to the mockup's own table (brief Part 2).
     The mockup actually carries two different ambers for two different
     jobs -- --amber-bg/text is now "missing information" (its own
     .miss/.missbg, #C9752B on #FDF3E6); a second pair, --warn-bg/text
     below, is its own "pending" pill treatment (#8A5A00 on #FFF7DE, its
     .p-amb), kept distinct on purpose so a missing-info marker and a
     pending status pill read as two different things, matching how the
     mockup itself never conflates them. */
  --amber-bg:   #FDF3E6;
  --amber-text: #C9752B;   /* missing information */
  --amber-dot:  #C9752B;   /* missing information marker -- same job, same colour */
  --warn-bg:    #FFF7DE;   /* pending / stale-overdue pill, the mockup's own p-amb */
  --warn-text:  #8A5A00;
  --green-bg:   #E3F4EA;
  --green-text: #1E6B3A;   /* active, good */

  /* ---------- Spacing scale ----------
     These seven values. No others. Ad hoc spacing is the
     single biggest cause of a screen reading as unpolished. */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 40px;

  /* ---------- Type ---------- */
  /* Full fallback stack ported verbatim from the mockup's --sans,
     session 107 Part 1: three fonts were missing (Helvetica Neue,
     Helvetica, Arial), so a failed Inter load degraded further
     than the mockup's own would have. Not renamed -- --ui-font is
     the existing name every screen already references. */
  --ui-font: "Inter","Segoe UI",-apple-system,system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;

  --tbl-size: 14px;      /* table cell text */
  --tbl-name: 14.5px;    /* row anchor, the property name */
  --tbl-head: 12.5px;    /* column headers */
  --tbl-pad:  15px;      /* row vertical padding */

  /* ---------- Layout ---------- */
  --wrap-max:  min(1680px, 100%);   /* content cap, centred. Session 107
    Part 4: was a fixed 1320px; Daniel asked for more real estate in the
    middle of the page on 9 September 2026. Mockup's exact value. */
  --side-w:    228px;    /* sidebar rail */
  --radius:    8px;      /* buttons, inputs, nav items */
  --radius-lg: 9px;      /* cards -- session 157, the mockup's own value (was 10px) */
  --pill-radius: 12px;   /* status pills, session 157: the mockup's own 11-14px range */

  /* ============================================================
     Session 148, D-105 onward: form controls. Daniel, looking at
     the opportunity screen: "UI is terrible again. I need it
     updated to the clean UI we have on other screens... just look
     at the text boxes in the new fields added." The design system
     (D-87 through D-104) never covered a single form control; the
     opportunity screen inherited the WordPress Proposal Builder's
     own input styling instead, which is why it read as a WordPress
     plugin rather than this platform. These are the values every
     control in the app now shares. See mh-design-system-v1.md for
     the full decision list.
     ============================================================ */
  --input-h:            40px;   /* D-105: fits 14px text with 10px vertical padding */
  --input-pad-y:        10px;
  --input-pad-x:        13px;
  --input-border:       var(--line);
  --input-radius:       var(--radius);
  --input-bg:           var(--white);
  --input-text:         var(--ink);
  --input-font-size:    14px;
  --input-font-weight:  450;    /* D-88's own body weight */
  --input-placeholder:  var(--muted);   /* D-106 */
  --input-focus-border: var(--yellow);  /* D-107: the Properties build's own yellow ring */
  --input-focus-ring:   rgba(255,209,58,.28);
  --input-disabled-bg:      var(--line-soft);  /* D-108 */
  --input-disabled-text:    var(--muted);
  --input-readonly-bg:      var(--line-soft);  /* D-108: visibly different from an empty editable field */
  --input-readonly-text:    var(--text);
  --input-error-border:     var(--danger-text);
  --input-error-bg:         var(--danger-bg);

  /* ============================================================
     Session 105: the design pass, v1
     Brought in from docs/mh-ops-retro-mockup-v7.html (Daniel's
     approved target, 9 Sep 2026). Everything above this point is
     untouched and unrenamed. These are additions only.
     ============================================================ */

  /* ---------- Extra brand shades ----------
     The chrome (sidebar, header) runs deeper than --teal-dark,
     which stays the accent used for links and active states. */
  --teal-deep:   #144D54;
  --teal-night:  #0E363B;
  --teal-mid:    #3E9EA4;
  --orange-deep: #D96A18;

  /* ---------- Paper and cream family ----------
     Session 157: --paper (every card/table surface) retargeted from a
     cream #FFFDF7 to the mockup's own flat white card background,
     #FFFFFF -- one token change flattens every surface that already
     uses it (cards, sticky table headers, tiles, empty states) without
     touching each rule individually. --cream/--cream-2/--red-bg are
     left defined (still used by the toast and a couple of private
     scene-adjacent spots outside this session's mockup scope) but are
     no longer a page or card surface anywhere. */
  --paper:   #FFFFFF;
  --cream:   #FBF5E6;
  --cream-2: #F3E9D1;
  --red-bg:  #FFE3D0;

  /* ---------- Type ---------- */
  --serif: 'Iowan Old Style','Palatino Linotype',Palatino,Georgia,ui-serif,serif;
  /* Corrected to the mockup's exact stack, session 107 Part 3 (was
     SFMono-Regular, no Consolas -- matched what .s-detail/
     .s-candidates already used privately in session 105, but that
     was this app's own value, not the mockup's). ui-monospace, the
     first item, is what actually resolves on most systems (a CSS
     generic keyword for the OS's own monospace font); the named
     fallbacks below it only matter on browsers that don't support
     that keyword, so this correction carries negligible practical
     risk despite being a real value change. */
  --mono:  ui-monospace,"SF Mono",Menlo,Consolas,monospace;

  /* ---------- Motion ---------- */
  --spring:   cubic-bezier(.34,1.56,.64,1);
  --ease-out: cubic-bezier(.2,.8,.2,1);

  /* ---------- Chrome geometry ---------- */
  --hdrH: 74px;

  /* ---------- Floating-over-the-scene surfaces ----------
     --page-ink/--page-text get their real, scene-varying values
     with the rest of the scene tokens further down this file
     (Part 5); Part 3 added them here defaulting to --ink/--text so
     h1/.sub could reference them before the scene work landed.
     --chip-bg/--content-line stay fixed even now: chips and buttons
     have their own translucent panel already, and Part 5 is scoped
     to the background itself, not re-opening that call -- flagged
     as a clean follow-up in the session 106 report. */
  --chip-bg:     rgba(255,253,247,.7);
  --content-line:rgba(22,24,26,.12);

  /* ---------- Values Part 0 (session 105) found hardcoded, now named ----------
     Each preserved the exact value already in use at the time.
     Session 105 found three close but different "danger" reds and
     two amber-warning shades and kept each its own token rather
     than silently unifying them, flagging it as Daniel's call.
     Session 106 was asked to make that call: measured contrast for
     every candidate against where it's actually used (paper, white,
     its own soft background) -- all of them passed AA, so the
     decision came down to which was strongest and which was already
     the dominant value. --danger-text (8.99-9.14:1, and already 8 of
     the app's ~11 "danger" instances) won over --danger-strong
     (6.43-6.54:1) and --danger-alt (6.46-6.57:1), both retired.
     --amber-text (7.06:1, the original token, session 12) won over
     --warn-soft-text (5.83:1, a session-105 discovery), also
     retired. Every call site that used a retired token now uses the
     winner instead -- a real, if small, recolouring, not just a
     token rename. */
  --yellow-ink:     #0A3F45;  /* text on --yellow or --teal-light: this file's .btn-p, shell.css .who .av, 4 private repeats */
  --danger-text:    #B3261E;  /* session 157: the mockup's own "bad" pill text (was #8A1F1F) */
  --danger-bg:      #FDE7E7;  /* session 157: the mockup's own "bad" pill background (was #FBE9E9) */
  --hover-bg:       #FAFBFB;  /* this file's .btn:hover and .s-payroll .btn-mini:hover */
  --row-glow:       #FFFCF2;  /* this file's tbody hover/focus and .s-detail .sc-pending -- already close to the mockup's own tr.def #FFFCF0 */
  --warn-soft-bg:   #FDF3E6;  /* shell.css .errbox and the Inter-fallback banner: session 157 unified with --amber-bg, the mockup's own single "missing/warn" amber rather than a third close-but-different shade */
  --warn-soft-line: #C9752B;  /* same two places, the border -- unified with --amber-text for the same reason */
  --note-bg:        #EFFAF9;  /* .s-props .note, single use */
  --highlight-row:  #FFFBEE;  /* .s-appts tr.ta-row, single use */
}

/* ============================================================
   Base
   ============================================================ */

*{ box-sizing:border-box }

html{
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

body{
  margin:0;
  font-family:var(--ui-font);
  background:var(--white);
  color:var(--ink);
  font-size:14px;
  line-height:1.5;
  font-weight:450;                 /* not 400: renders thin on Windows */
  letter-spacing:-0.006em;         /* Inter needs slight negative tracking at UI sizes */
  font-optical-sizing:auto;
  /* "tnum" added session 106 Part 5, ported from the mockup's own
     body rule: tabular figures everywhere by default, not just
     inside cells explicitly marked .num, so every number column
     lines up without having to remember the class. */
  font-feature-settings:"cv02","cv03","cv04","cv11","tnum";
  /* Session 107 Part 2: the one real gap Part 0 found at this
     level. Functionally redundant today (.app already carries
     min-height:100vh, and .sky-fix is position:fixed so it never
     depended on body's height anyway) but ported for exactness --
     harmless, and rule 2 asks for a reason not to. */
  min-height:100vh;
}

button,input{ font-family:inherit }

/* ============================================================
   Type scale
   Weight ceiling for body text is 550. Anything heavier at
   small sizes reads as a template, not a product.
   ============================================================ */

/* Session 106: h1, .swash and h2 ported verbatim from the mockup.
   Session 105's 32px was its own judgement call, withdrawn by this
   brief: "It produced the wrong answer." The swash is still a
   pure-CSS ::after rather than the mockup's inline <svg class=swash>,
   sized to the mockup's own 200x20px, so it still reaches every h1
   with no markup change and no screen at risk of being missed --
   that choice stands from session 105, only the size was wrong. */
/* Session 157: h1/h2 retargeted to the mockup ("the cream background
   and serif headings go" -- Daniel: "that current tan design that
   looks like a 1920's paper notebook"). Both drop the italic serif
   and the decorative swash underline -- the mockup's own headings are
   plain, sans-serif and bold, coloured teal (this file's --teal-dark,
   already the mockup's exact hex). Sizes are unchanged (56px/24px) to
   avoid a header-layout reflow this session did not measure; only the
   typeface, weight, style and colour change. h1 no longer reads
   --page-ink (the scene's own day/night text swap, see Part 5 below):
   with the sky gradient gone (flat --page-bg now), a scene-driven
   colour swap has nothing to react to and risked leaving light-on-
   light text at night, so h1/. sub now use the fixed --teal-dark /
   --text tokens instead. */
h1{
  font-family:var(--ui-font);
  font-style:normal;
  font-weight:700;
  font-size:56px;
  line-height:.95;
  letter-spacing:-.02em;
  margin:0;
  color:var(--teal-dark);
}

h2{
  font-family:var(--ui-font);
  font-style:normal;
  font-weight:700;
  font-size:24px;
  color:var(--teal-dark);
  margin:0;
}

.t-sub{ font-size:14px; color:var(--text) }
.t-label{ font-size:12.5px; color:var(--muted); font-weight:500 }

/* ============================================================
   Focus
   One focus treatment for the whole app.
   ============================================================ */

:focus-visible{
  outline:2px solid var(--teal-dark);
  outline-offset:2px;
  border-radius:4px;
}

/* ============================================================
   Form controls, session 148, D-105 through D-118.

   Before this session the platform had one bare, unlabeled rule
   for text inputs (shell.css, moved here so every value actually
   lives in this file, D-1's own rule), no select chevron, no
   disabled/read-only distinction, no textarea minimum, no
   checkbox/radio token (five screens each declared
   accent-color:var(--teal-dark) separately, correct every time by
   coincidence rather than by a shared rule), no segmented control
   outside the opportunity screen's own copy, and no search-dropdown
   control at all. The opportunity screen's own form styling ported
   the WordPress Proposal Builder's input CSS wholesale instead of
   using any of this, which is the entire reason it read as a
   WordPress plugin. Every value below is a token from the block
   above; nothing here is a new raw colour, size or radius.

   D-112 (label position): checked live rather than guessed. Four
   different screens (.s-detail, .s-users, .s-onboarding, dialogs)
   already put the label above the control, block, using the
   existing .t-label class; the opportunity screen alone put the
   label beside the control. Label-above wins, both because it is
   what most of the app already does and because it is what makes
   D-116's "one control width" possible: a label that sits above
   never eats a variable share of the row's own width the way a
   flex:1 label beside the control does.
   ============================================================ */

/* Session 149, Part 7. Found live, measured, not guessed: the address
   and unit boxes on the opportunity screen were roughly a third the
   width of every other control, because their own markup never set
   type="text", and an attribute selector like input[type=text] does not
   match an input with no type attribute at all, even though a browser
   treats a bare <input> as text by default. Six inputs across the
   session 148/149 build had this exact gap; each one is fixed at its
   own markup too (never rely on a bare input matching this rule), but
   input:not([type]) is added here so the missing attribute can never
   silently produce an unstyled, narrow, default-sized box again. */
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=tel], input[type=date], input[type=time], input[type=search],
input:not([type]), textarea, select{
  font-family:inherit; font-weight:var(--input-font-weight); font-size:var(--input-font-size);
  color:var(--input-text); border:1px solid var(--input-border); border-radius:var(--input-radius);
  padding:var(--input-pad-y) var(--input-pad-x); background:var(--input-bg); width:100%;
}
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=tel], input[type=date], input[type=time], input[type=search],
input:not([type]){
  height:var(--input-h);
}
::placeholder{ color:var(--input-placeholder); font-weight:var(--input-font-weight); opacity:1 }

/* D-107. Focus, one treatment for every control, the Properties
   build's own yellow ring, named and tokenised rather than
   hand-copied per screen. */
input:focus, textarea:focus, select:focus{
  outline:none; border-color:var(--input-focus-border);
  box-shadow:0 0 0 3px var(--input-focus-ring);
}

/* D-108. Disabled and read-only are visibly different from an
   empty editable field, which today they are not: a read-only
   phone field looks identical to a blank one. */
input:disabled, textarea:disabled, select:disabled{
  background:var(--input-disabled-bg); color:var(--input-disabled-text); cursor:not-allowed;
}
input[readonly], textarea[readonly]{
  background:var(--input-readonly-bg); color:var(--input-readonly-text); cursor:default;
}

/* D-109. Select, matched to the text input, with its own chevron
   rather than the browser default. */
select{
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  padding-right:34px; cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238A9096' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 13px center;
}

/* D-110. Textarea: same border and radius as a text input, a real
   minimum height rather than the browser's two-line default, and
   vertical-only resize so it can never widen out of its own row. */
textarea{ min-height:88px; resize:vertical; line-height:1.5 }

/* D-111. Checkbox and radio, one accent colour. Already the de
   facto standard (five screens each declared this separately,
   always the same value); this is that value, said once. */
input[type=checkbox], input[type=radio]{
  width:16px; height:16px; accent-color:var(--teal-dark); cursor:pointer;
}

/* D-112/D-113. Label above the control (see the note above),
   .t-label already carries the size/weight/colour; the hint line
   underneath, already shared as .hint, unchanged.
   D-93 extended: every label and hint is sentence case, the same
   rule column headers already follow. */
.t-label{ display:block; margin-bottom:var(--s1) }

/* D-114. The form row and D-116's one control width: label above,
   full-width control, a consistent gap between rows. .f/.f2 already
   existed, identically redeclared on five different screens; this
   is that declaration, said once, so a sixth screen has something
   to import instead of retyping it. */
.f{ margin-bottom:var(--s4) }
.f2{ display:grid; grid-template-columns:1fr 1fr; gap:var(--s4) }

/* D-119. Session 149, Part 7. Daniel's own screenshot: a large dead
   region on the right of the opportunity screen. Measured, not
   guessed: label-above-control plus a hint line below it is three
   lines per field (D-112/D-113 together); a dense intake form with 20
   or more fields grew far taller than the pricing side once every row
   gained roughly 22px it did not have with the label beside the
   control, and the CSS column-balancing (session 145) gave up trying
   to even the two out. This compact row keeps the label above the
   control (D-112 still applies; the office reads a label before typing
   into it, unchanged) but folds the hint onto the SAME line as the
   label, in muted text, the way the WordPress builder's own row
   already did it, closing the third line rather than reopening D-112's
   own decision. A tighter row gap (--s3, not --s4) closes a second
   sliver. Used on the opportunity screen's own dense intake rows only,
   per this brief's own instruction: measure, then use it where the
   measurement calls for it, not everywhere. */
.f-compact{ margin-bottom:var(--s3) }
.f-compact .t-label{ margin-bottom:2px }
.f-compact .hint-inline{ font-size:12.5px; color:var(--muted); font-weight:450; margin-left:6px }

/* D-115. Paired fields that are not an even split (state and ZIP:
   state is always two characters, ZIP needs the room). Full/half
   baths, being the same kind of value, use the even .f2 above. */
.f-pair{ display:flex; gap:var(--s3) }
.f-pair .f-pair-narrow{ width:64px; flex:none }
.f-pair .f-pair-wide{ flex:1 }

/* D-117. Segmented Yes/No and multi-choice, promoted from the
   opportunity screen's own copy (proposal-builder-app.php's .seg)
   into a shared class: it was already the right height and shape,
   it just was not available to any other screen. Height matches an
   input via the same padding; on-state, hover and focus tokenised. */
.seg{
  display:flex; border:1px solid var(--input-border); border-radius:var(--input-radius);
  overflow:hidden; height:var(--input-h);
}
.seg button{
  border:0; background:var(--input-bg); padding:0 var(--s4); cursor:pointer;
  font-family:inherit; font-size:var(--input-font-size); color:var(--muted);
  white-space:nowrap;
}
.seg button:hover{ background:var(--line-soft) }
.seg button.on{ background:var(--teal-dark); color:#fff; font-weight:600 }
.seg button:focus-visible{ outline:none; box-shadow:inset 0 0 0 2px var(--input-focus-border) }

/* D-117 continued. The search-with-dropdown control the Google
   address box needs: the input above uses the same base rule
   already; this is only the suggestion list, promoted from the
   opportunity screen's own address dropdown (session 147). */
.search-drop{ position:relative }
.search-drop-list{
  position:absolute; left:0; right:0; top:calc(100% + 2px); background:var(--input-bg);
  border:1px solid var(--input-border); border-radius:var(--input-radius);
  box-shadow:0 4px 14px rgba(0,0,0,.12); z-index:20; max-height:220px; overflow:auto;
}
.search-drop-row{
  padding:var(--s3) var(--s3); font-size:var(--input-font-size); cursor:pointer;
  border-bottom:1px solid var(--line-soft);
}
.search-drop-row:last-child{ border-bottom:none }
.search-drop-row:hover, .search-drop-row.active{ background:var(--line-soft) }
.search-drop-empty{
  position:absolute; left:0; right:0; top:calc(100% + 2px); background:var(--input-bg);
  border:1px solid var(--input-border); border-radius:var(--input-radius);
  padding:var(--s3); font-size:13px; color:var(--muted); z-index:20;
}

/* D-118. Required, error and helper-error, even though nothing
   uses them yet: the next screen that needs one should not have to
   invent it either. */
.f.has-error input, .f.has-error select, .f.has-error textarea{
  border-color:var(--input-error-border); background:var(--input-error-bg);
}
.f-error{ font-size:12.5px; color:var(--input-error-border); margin-top:var(--s1); display:block }
.t-label .required{ color:var(--input-error-border) }

/* Session 164, Part 1a. A single input, not wrapped in its own .f
   row (the opportunity screen's override rows, the paired bathrooms
   row) can still be marked wrong on its own, same tokens as
   .f.has-error above. oppiSetField/oppiSetOverrideField add this
   class when a number field is left holding something that still
   cannot be saved (a lone "."), and remove it the instant the field
   holds a real value or is cleared. */
input.field-invalid{
  border-color:var(--input-error-border); background:var(--input-error-bg);
}

/* ============================================================
   Buttons
   ============================================================ */

/* Session 157: retargeted to the mockup's own two button styles
   (docs/mh-property-page-full-mockup.html .btn / .btn.sec). The
   mockup's OWN default .btn is the filled orange primary action and
   its .sec is the white/teal-outline secondary -- opposite of which
   live class was "default" before. Live class names are unchanged
   (rule: map mockup values onto the live app's existing classes, do
   not rename them) and their existing roles are kept: .btn is this
   app's own secondary/ambient action (used far more often, in nearly
   every toolbar) so it takes the mockup's .sec treatment; .btn-p is
   this app's own primary CTA, so it takes the mockup's actual .btn
   (orange fill). Both drop the 999px pill radius, the backdrop blur,
   and (btn-p) the 3D bounce shadow -- none of which the flat mockup
   uses; kept the existing hover/active transitions where the mockup's
   own plain hover-only styling doesn't specify otherwise. */
.btn{
  border:2px solid var(--teal-dark);
  background:var(--white);
  color:var(--teal-dark);
  padding:8px 14px;
  border-radius:var(--radius);
  font-size:13px;
  font-weight:600;
  cursor:pointer;
  transition:background .2s;
}
.btn:hover{ background:var(--line-soft) }
.btn-ghost{
  background:transparent; border:1.5px solid var(--teal-dark); color:var(--teal-dark);
  padding:5px 11px; font-weight:550; font-size:12.5px; border-radius:var(--radius);
  cursor:pointer; transition:background .2s;
}
.btn-ghost:hover{ background:var(--line-soft) }

.btn-p{
  position:relative;
  background:var(--orange);
  color:var(--white);
  border:0;
  font-weight:600;
  font-size:14px;
  padding:8px 15px;
  border-radius:var(--radius);
  cursor:pointer;
  box-shadow:none;
  transition:background .2s;
}
.btn-p:hover{ background:var(--orange-deep) }
.btn-p:active{ transform:translateY(1px) }

/* ============================================================
   Status pills
   ============================================================ */

.pill{
  display:inline-block;
  padding:4px 11px;
  border-radius:var(--pill-radius);   /* session 157: the mockup's own 11-14px (was 20px, a true pill) */
  font-size:12.5px;
  font-weight:500;
}
.pill-active{ background:var(--green-bg); color:var(--green-text) }
.pill-inactive{ background:var(--gray-bg); color:var(--text) }
/* D-122, session 153 Part 3: pending (a client said yes but has had no
   completed clean; a property with a blocking item still missing) is
   "waiting on something", not "switched off", so it no longer shares
   inactive's grey. Session 157: retargeted from the missing-information
   amber to --warn-bg/--warn-text, the mockup's own dedicated "pending"
   pill treatment (brief Part 4: "gets its own style, from the mockup's
   own amber pending treatment") -- now distinct from BOTH inactive's
   grey and a missing-information marker's amber, not just the former.
   Used by statusPill() on every list that shows status. */
.pill-pending{ background:var(--warn-bg); color:var(--warn-text) }
/* Session 112, Part 6 proof found this: pdTurnover()'s "Same day" pill
   set its own background and colour with an inline style attribute,
   which no selector, however specific, can ever override -- the one
   thing an inline style always wins against is a stylesheet rule
   without !important. Same colours as before, now a class so a row's
   hover treatment can reach it like every other pill. Session 157:
   same retarget as .pill-pending, same reasoning -- pill-warn is a
   stale/overdue state, not a missing-information marker. */
.pill-warn{ background:var(--warn-bg); color:var(--warn-text) }

/* ============================================================
   Missing information marker
   ============================================================ */

.miss{
  display:inline-block;
  width:8px; height:8px;
  border-radius:50%;
  background:var(--amber-dot);
  margin-left:8px;
}

/* ============================================================
   Data table
   Rows run full bleed and align to the page title. No card
   border. Sticky header requires no clipping ancestor: an
   overflow:hidden anywhere above will silently break it.
   ============================================================ */

table{ width:100%; border-collapse:separate; border-spacing:0; table-layout:fixed }

/* Session 157: table header retargeted to the mockup's own table style
   (brief Part 2: "10.5px uppercase muted headers, 2px teal-light
   bottom border, 1px row separators"). This supersedes D-93 ("Column
   headers are sentence case, never all caps") for TABLE column headers
   specifically -- D-93 itself stays in force for form labels and
   header-strip stat labels (D-93 extended), which this session does
   not touch; see the design system doc's own superseded-decision note.
   box-shadow (not border-bottom) is kept: D-1's own "sticky header
   borders detach" rule (design system section 7) still applies once
   the header goes sticky. */
th{
  text-align:left;
  font-size:10.5px;
  font-weight:700;
  color:var(--muted);
  text-transform:uppercase;
  letter-spacing:.02em;
  padding:var(--s3) var(--s4);
  white-space:nowrap;
  position:sticky; top:0; z-index:2;
  background:var(--white);
  box-shadow:inset 0 -2px 0 var(--teal-light);
  cursor:pointer;
  user-select:none;
}
th:hover{ color:var(--text) }
th.sorted{ color:var(--ink) }
th .ar{ opacity:0; margin-left:5px; font-size:10px }
th.sorted .ar{ opacity:1 }

td{
  padding:var(--tbl-pad) var(--s4);
  font-size:var(--tbl-size);
  color:var(--text);
  border-bottom:1px solid var(--line);      /* session 157: solid, per the mockup (was dashed) */
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  max-width:0;                              /* required for ellipsis in a fixed table */
}

th:first-child, td:first-child{ padding-left:10px }  /* room for the hover edge */
th:last-child,  td:last-child { padding-right:0 }

td.num, th.num{ text-align:right; font-variant-numeric:tabular-nums; font-feature-settings:"tnum" }

.pname{ color:var(--ink); font-weight:550; font-size:var(--tbl-name); letter-spacing:-0.009em }
.pclient{ color:var(--teal-dark); font-weight:450; text-decoration:none }
.pclient:hover{ text-decoration:underline; text-underline-offset:2px }
.pnum{ color:var(--ink); font-weight:450 }
.dim{ color:var(--muted) }
.stale{ color:var(--amber-text) }

tbody tr{ cursor:pointer }
tbody tr:hover td{ background:var(--row-glow) }
tbody td:first-child{ box-shadow:inset 3px 0 0 transparent; transition:box-shadow .1s ease }
tbody tr:hover td:first-child{ box-shadow:inset 3px 0 0 var(--yellow) }

tbody tr:focus-visible{ outline:2px solid var(--teal-dark); outline-offset:-2px }
tbody tr:focus-visible td{ background:var(--row-glow) }
tbody tr:focus-visible td:first-child{ box-shadow:inset 3px 0 0 var(--yellow) }

/* ============================================================
   .tbl -- session 106, ported verbatim from the mockup, on the
   <table> element itself (matching the mockup: .tbl sets
   width/border-collapse, which only mean something on <table>).
   Every screen's table gets this class now (Part 4). The bare
   table/th/td rules above stay as a fallback and are otherwise
   superseded here by .tbl's higher specificity.

   Two additions beyond the mockup's literal declarations, both
   kept because the brief protects behaviour, not just look ("Only
   the container and the cell styling. Column sets, sort order and
   behaviour do not change."): sticky headers (long tables here run
   past 70 rows; the mockup's own tables are short and never needed
   it) and the click-to-sort arrow mechanism. Both layer on top of
   the mockup's own th/td declarations without changing any of them.
   ============================================================ */
.tbl{ width:100%; border-collapse:collapse; margin-top:var(--s3) }
.tbl th{
  text-align:left; font-size:10.5px; font-weight:700; color:var(--muted);
  text-transform:uppercase; letter-spacing:.02em;
  padding:10px 12px; box-shadow:inset 0 -2px 0 var(--teal-light);
  position:sticky; top:0; z-index:2; background:var(--paper);
  cursor:pointer; user-select:none; white-space:nowrap;
}
.tbl th:hover{ color:var(--text) }
.tbl th.sorted{ color:var(--ink) }
.tbl th .ar{ opacity:0; margin-left:5px; font-size:10px }
.tbl th.sorted .ar{ opacity:1 }
.tbl th:first-child, .tbl td:first-child{ padding-left:20px }
.tbl th:last-child, .tbl td:last-child{ padding-right:20px }
.tbl td{ padding:13px 12px; border-bottom:1px solid var(--line); vertical-align:middle; font-size:14px; color:var(--text) }
.tbl td.num, .tbl th.num{ text-align:right; font-variant-numeric:tabular-nums }
.tbl tbody tr{ transition:background .2s, transform .25s var(--spring), color .2s; cursor:pointer }
/* Session 112, Part 1. The mockup's own hover treatment named six
   children by class (.anchor, small, .mono, .pill, .btn-ghost, .none)
   and re-coloured only those. Every screen built since keeps adding
   its own coloured span inside a row (.pname, .pclient, .pnum, .dim,
   .stale, .age-*, .score-*, .wk-*, .st-*, .actor-pill and more to
   come), none on that list, so each one kept its own colour against
   the teal fill and either vanished or came close to it. A NAMED LIST
   IS THE BUG, not any one entry missing from it (rule 1: fix the
   rule, not the case). Below, every descendant of a hovered row takes
   the hover text colour, full stop; the few elements that carry their
   own OPAQUE surface (a real button, a pill, the "waiting on" pill)
   are named afterward only to restore what they show on that surface,
   never to opt back into the old dark-on-teal failure.

   The pre-existing plain `tbody tr:hover td{ background:var(--row-glow) }`
   above (this file, ~line 416) was never scoped away from `.tbl`
   tables, so it was firing on the SAME cell as this block's teal row
   fill: each td's own cream background painted over the row's teal,
   and text this block forced white sat on that cream almost
   invisibly, while text this block never touched (everything above)
   happened to read fine by accident, dark ink on the cream nobody
   intended. `.tbl tbody tr:hover td{ background:transparent }` below
   removes that collision so the row's own teal is what every cell
   actually shows. */
.tbl tbody tr:hover{ background:var(--teal-dark); color:var(--white); transform:translateX(3px) }
.tbl tbody tr:hover td{ background:transparent }
.tbl tbody tr:hover *{ color:var(--white) }
.tbl tbody tr:hover .pill, .tbl tbody tr:hover .actor-pill{ background:rgba(255,255,255,.18) }
/* Every .wk-pill variant except st-unknown already carries its own
   solid, opaque background (.s-staff .wk-pill.st-*), so it was never
   touched by the row's teal underneath and needs no rescue here. Only
   st-unknown is background:transparent by its own design (a dashed
   outline, deliberately unfilled), so it is the one variant that
   actually reveals the row's teal on hover; overlaying the generic
   .pill treatment on every variant lightened the solid ones' own
   backgrounds instead of leaving them alone, which made the muted
   grey text on st-no_work/st-not_available harder to read than
   before, not easier. Scoped to the one variant that needs it. */
.tbl tbody tr:hover .wk-pill.st-unknown{ background:var(--paper); color:var(--muted) }
.tbl tbody tr:hover .btn-ghost{ border-color:rgba(255,255,255,.6) }
.tbl tbody tr:hover .none{ color:var(--yellow) }
/* Found the same way, on the Dashboard's worklist tables: .corr-reason,
   .ded-flag and .sc-flag are small amber tags with their own opaque
   background, exactly like .pill, just never sharing its class. */
.tbl tbody tr:hover .corr-reason,
.tbl tbody tr:hover .ded-flag,
.tbl tbody tr:hover .sc-flag{ color:var(--amber-text) }
.tbl tbody tr:hover .ded-flag.ded-stale{ color:var(--text) }
/* The opposite failure, found the same way (render and look, not
   reasoned from the CSS): a compound, screen-scoped selector like
   `.s-payroll .pr-table td .crumb`, `.s-badges .hint.stale` or
   `.s-dash .ded-table .dim` is specific enough to beat the universal
   rule above, so THESE kept their own colour exactly like the
   six-item list used to, sitting on the row's own teal (a crumb link
   has no background of its own) or on teal bled through a cell a
   worklist table already made transparent (.hint.stale, .ded-table
   .dim). Same bug, opposite direction: the fix is still "every
   descendant," just spelled out here because these selectors are
   already more specific than a bare class. */
.tbl tbody tr:hover .crumb, .tbl tbody tr:hover .hint, .tbl tbody tr:hover .dim{ color:var(--white) }
/* A real button, or the round avatar initial, carries its own opaque
   surface and was already legible on it before hover; forcing it (or
   a caption nested inside it, like .alloc-btn's own .dim text) white
   would put white text on that same light surface instead. These
   come last and repeat the .dim/.hint/.crumb selectors' own
   specificity on purpose (.tbl tbody tr:hover .btn * ties .tbl tbody
   tr:hover .dim at three classes, two elements), so source order is
   what lets a button's own pairing win for anything nested inside
   it, while .dim wins everywhere else. Restore each one's own
   pairing rather than exempt it silently, so a reader of this file
   can see the decision was made, not missed. */
.tbl tbody tr:hover .btn, .tbl tbody tr:hover .btn *{ color:var(--ink) }
.tbl tbody tr:hover .btn-p, .tbl tbody tr:hover .btn-p *{ color:var(--teal-night) }
.tbl tbody tr:hover .avatar{ color:var(--teal-night) }
.tbl tbody tr:hover .pr-dot:not(.on){ color:var(--text) }
.tbl tbody tr:hover .pr-toggle:not(.on){ color:var(--text) }
/* Session 116, Part 2. A THIRD occurrence of the exact same bug this
   comment block already describes twice: an input, select or textarea
   carries its own opaque white surface (the base rule, this file,
   ~line 118), read fine before hover, same as a .btn. The wildcard
   above still forces its text white on hover; nothing had ever
   restored its own pairing, because nothing had put a form control
   inside a .tbl row until the training-gate pass/unpass note field
   (trainingGateRowHtml, shell.js) did. Measured directly, not judged
   by eye: hovering that row and reading the input's own computed
   style gave color:white on background:white. Fixed here, at the
   rule, so the next screen that puts any input/select/textarea inside
   a .tbl row inherits a readable one automatically, the same
   guarantee .btn already gets. */
.tbl tbody tr:hover input, .tbl tbody tr:hover select, .tbl tbody tr:hover textarea{ color:var(--ink) }
/* Session 136, Part 5. A FOURTH occurrence of the same bug class this
   block already names three times: an opaque surface nested inside a
   hovered .tbl row keeps the row's own color:white wildcard, but no
   longer sits on the row's teal fill to read against, because the
   surface's own background covers it. Two real cases, found by
   checking, not assumed from one: Daniel's screenshot
   (docs/daniel-training-grade.png) is the Training board's own inline
   grading drill (.tb-drill, its own background:var(--row-glow),
   shell.css); the Coaching board's row detail (.coach-row-detail)
   builds its entire content from .card boxes, the same opaque surface
   nearly every screen uses, with the identical fault once actually
   hovered (measured directly: computed color white on the card's own
   paper background, not judged by eye). Both opened as a nested block
   inside a row whose own :hover the pointer never actually leaves, so
   this is not a near-miss timing issue, it fires every time either
   panel is used. Restored to the row's own resting text colour, same
   as the input/select/textarea line above; a pill inside either
   surface loses the hover-only translucent treatment .pill gets on
   the bare teal (rule below) and instead reads in its own normal
   colours, still legible, since it is no longer sitting on that teal
   at all. */
.tbl tbody tr:hover .card, .tbl tbody tr:hover .card *,
.tbl tbody tr:hover .tb-drill, .tbl tbody tr:hover .tb-drill *{ color:var(--text) }
.tbl tbody tr:last-child td{ border-bottom:0 }
.tbl small{ display:block; font-size:12px; color:var(--muted) }
.anchor{ font-weight:550; color:var(--ink); font-size:14.5px; display:block }

/* .hint -- O-170, session 106. Session 105's own measurement said
   "used twice in shell.js, defined in neither stylesheet." Re-measured
   here per O-148: it's actually used well over 50 times, across most
   built screens, and was styled only inside two private scopes
   (.s-badges .hint, .s-detail .hint, both identical). Every other
   screen's .hint has been rendering as an unstyled <div> or <p> all
   along. One shared rule, matching what those two private copies
   already agreed on. */
.hint{ font-size:12.5px; color:var(--muted); margin-top:var(--s1) }
.mono{ font-family:var(--mono); font-size:12.5px; color:var(--text) }

/* ============================================================
   .chk -- the checkbox with a drawn tick, session 106 Part 5.
   Ported verbatim, but not as the mockup's own markup (a bare
   <button role="checkbox">, no real <input> underneath). The app
   has 16 real checkboxes whose .checked state and onchange
   handlers a lot of other code already depends on (property saves,
   settings toggles, bonus/deduction tickets). Rather than replace
   them and risk missing one of those readers, the real
   <input type="checkbox"> stays, visually hidden, with .chk as a
   sibling that reacts to its :checked/:disabled state through
   plain CSS -- so every existing reader keeps working unchanged,
   and the visual result (including the dash-offset tick draw and
   the tilt) matches the mockup exactly.
   ============================================================ */
.chk-wrap{ position:relative; display:inline-block; width:22px; height:22px; vertical-align:middle; flex:none }
.chk-wrap input[type="checkbox"]{
  position:absolute; inset:0; width:22px; height:22px; margin:0;
  opacity:0; cursor:pointer; z-index:1;
}
.chk{
  position:absolute; inset:0; width:22px; height:22px; border-radius:7px;
  border:2px solid #C9C1AB; background:var(--white); display:grid; place-items:center;
  pointer-events:none; transition:background .3s, border-color .3s, transform .35s var(--spring);
}
.chk svg{
  width:14px; height:14px; fill:none; stroke:var(--white); stroke-width:2.6;
  stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:24; stroke-dashoffset:24;
  transition:stroke-dashoffset .35s ease .08s;
}
.chk-wrap input[type="checkbox"]:checked + .chk{ background:var(--teal-dark); border-color:var(--teal-dark); transform:rotate(-4deg) }
.chk-wrap input[type="checkbox"]:checked + .chk svg{ stroke-dashoffset:0 }
.chk-wrap input[type="checkbox"]:disabled + .chk{ opacity:.5 }
.chk-wrap input[type="checkbox"]:focus-visible + .chk{ outline:2px solid var(--teal-dark); outline-offset:2px }

/* ============================================================
   .neon -- ported verbatim, available and unused (session 106
   Part 5). The neon counter itself is explicitly out of scope this
   session ("waits for the Dashboard and Today layout work"), but
   the brief still wants the flicker keyframes shipped now so the
   rule exists when the counter lands, same call as .seg in session
   105. Nothing in the app references .neon/.neon-count/.neon-word
   yet -- no markup change, no new query, just the rule.
   ============================================================ */
.neon{
  position:absolute; right:max(var(--s6), calc((100% - var(--wrap-max)) / 2 + var(--s6)));
  top:calc(var(--hdrH) + 18px); transform:rotate(3deg); transform-origin:80% 0;
  background:var(--teal-night); border:2px solid rgba(255,255,255,.12); border-radius:10px;
  padding:8px 16px 10px; box-shadow:0 10px 24px rgba(14,54,59,.35);
  text-align:center; min-width:150px; z-index:6;
}
.neon::before{
  content:''; position:absolute; left:18px; right:18px; top:-12px; height:12px;
  border-left:2px solid rgba(255,255,255,.18); border-right:2px solid rgba(255,255,255,.18);
}
.neon-count{
  font-family:var(--serif); font-style:italic; font-size:30px; line-height:1; color:var(--orange);
  text-shadow:0 0 6px var(--orange), 0 0 18px rgba(255,138,51,.75), 0 0 40px rgba(255,138,51,.45);
  animation:flicker 4.2s infinite;
}
.neon-word{
  font-size:10.5px; letter-spacing:.26em; text-transform:uppercase; color:var(--orange);
  margin-top:3px; text-shadow:0 0 6px rgba(255,138,51,.8);
}
.neon.clear .neon-count, .neon.clear .neon-word{
  color:var(--teal-light); text-shadow:0 0 6px var(--teal-light), 0 0 18px rgba(111,213,212,.7);
  animation:none;
}
@keyframes flicker{ 0%,18%,22%,25%,53%,57%,100%{ opacity:1 } 20%,24%,55%{ opacity:.35 } }

/* ============================================================
   .toast -- ported verbatim (Part 5). Wired to the one real
   confirmation moment the app already has that fits without
   inventing a new one: marking a payroll week paid (see shell.js,
   prMarkPaidHtml/prMarkPaid). Everywhere else that already confirms
   something (inline .msg-ok boxes, alert()) keeps doing so
   unchanged -- toast is additive, not a replacement for those.
   ============================================================ */
.toast{
  position:fixed; left:calc(50% + var(--side-w) / 2); bottom:28px; transform:translate(-50%,120px);
  z-index:50; background:var(--teal-night); color:var(--cream); padding:11px 20px;
  border-radius:999px; box-shadow:0 12px 30px rgba(14,54,59,.4); font-size:13.5px;
  transition:transform .5s var(--spring); display:flex; align-items:center; gap:10px;
}
.toast.show{ transform:translate(-50%,0) }
.toast i{ width:10px; height:10px; border-radius:50%; background:var(--yellow); display:inline-block }

/* ============================================================
   .toggle/.tg -- ported verbatim, available and unused (Part 5).
   No screen in the app has a toggle switch today (checked
   directly); every on/off control here is either a native
   checkbox (now .chk, above) or a chip. Kept ready for the first
   screen that needs one, same call as .seg and .neon.
   ============================================================ */
.toggle{ display:inline-flex; align-items:center; gap:10px; color:var(--text); font-size:13px; cursor:pointer; user-select:none }
.tg{ position:relative; width:48px; height:26px; border-radius:999px; background:#D9D2BF; transition:background .3s; flex:none }
.tg .knob{ position:absolute; top:3px; left:3px; width:20px; height:20px; border-radius:50%; background:var(--white); box-shadow:0 1px 3px rgba(0,0,0,.25) }
.tg.on{ background:var(--teal-dark) }
.tg.on .knob{ animation:mh-liq-on .55s var(--spring) forwards }
.tg:not(.on) .knob{ animation:mh-liq-off .55s var(--spring) forwards }
@keyframes mh-liq-on{ 0%{ transform:translateX(0) } 45%{ transform:translateX(11px) scaleX(1.55) scaleY(.82) } 100%{ transform:translateX(22px) } }
@keyframes mh-liq-off{ 0%{ transform:translateX(22px) } 45%{ transform:translateX(11px) scaleX(1.55) scaleY(.82) } 100%{ transform:translateX(0) } }

/* ============================================================
   .dl -- the export button with a progress ring and a checkmark,
   ported verbatim, available and unused (Part 5). Checked directly:
   the app has no export action anywhere today (grepped for one) --
   Payroll's "Upload Resort report" is an upload, not an export.
   Kept ready for the first screen that adds one.
   ============================================================ */
.dl{
  position:relative; height:40px; width:160px; border-radius:999px; background:var(--teal-dark);
  color:var(--white); border:0; cursor:pointer; font-weight:550; font-size:13px;
  transition:width .45s var(--spring), background .3s; overflow:hidden; display:grid; place-items:center;
}
.dl .t{ position:absolute; white-space:nowrap; transition:opacity .2s }
.dl svg{ position:absolute; width:28px; height:28px; transform:rotate(-90deg); opacity:0; transition:opacity .2s }
.dl circle{ fill:none; stroke-width:3.5 }
.dl .track{ stroke:rgba(255,255,255,.25) }
.dl .prog{ stroke:var(--white); stroke-linecap:round; stroke-dasharray:82; stroke-dashoffset:82 }
.dl .ck{
  position:absolute; width:16px; height:16px; fill:none; stroke:var(--white); stroke-width:3;
  stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:24; stroke-dashoffset:24; opacity:0; transform:none;
}
.dl.busy{ width:40px }
.dl.busy .t{ opacity:0 }
.dl.busy svg.ring{ opacity:1 }
.dl.busy .prog{ transition:stroke-dashoffset 2.2s cubic-bezier(.2,.7,.3,1); stroke-dashoffset:0 }
.dl.done{ background:var(--green-text) }
.dl.done svg.ring{ opacity:0 }
.dl.done .ck{ opacity:1; stroke-dashoffset:0; transition:stroke-dashoffset .4s ease .1s, opacity .1s }

/* ============================================================
   .tiles/.tile -- the four-up stat tile row, ported verbatim,
   available and unused (session 107 Part "also fold in"). No
   screen in the app has this today (checked directly); Dashboard's
   own "Active properties / Clients with active properties / ..."
   row uses .stat, a different, older pattern. Kept ready for the
   first screen that wants it, same call as .toggle/.dl/.neon in
   session 106. The four colour modifiers are the mockup's own
   (t-orange/t-amber/t-teal/t-mute); all four tokens they reference
   already exist live.
   ============================================================ */
.tiles{ display:grid; grid-template-columns:repeat(4,1fr); gap:var(--s4); margin-top:var(--s5) }
.tile{
  position:relative; overflow:hidden; background:var(--paper); border:1px solid var(--line);
  border-radius:14px 14px 14px 52px; padding:16px 20px 18px; color:var(--ink);
  transition:transform .35s var(--spring), box-shadow .35s;
}
.tile:hover{ transform:translateY(-3px) rotate(-.4deg); box-shadow:0 12px 28px rgba(14,54,59,.16) }
.tile .l{ font-size:12.5px; color:var(--muted); font-weight:500; position:relative }
.tile .v{ font-family:var(--serif); font-style:italic; font-size:44px; line-height:1; margin-top:6px; position:relative }
.tile .v small{ font-family:var(--ui-font); font-style:normal; font-size:14px; color:var(--muted); margin-left:4px }
.tile .dot{ position:absolute; right:-18px; top:-18px; width:56px; height:56px; border-radius:50%; opacity:.28; transition:transform .5s var(--spring) }
.tile:hover .dot{ transform:scale(3.2) }
.t-orange .dot{ background:var(--orange) }
.t-amber .dot{ background:var(--yellow) }
.t-teal .dot{ background:var(--teal-light) }
.t-mute .dot{ background:var(--line) }

/* Session 113 Part 3. .tiles was ported "available and unused"; the
   Coaching board is its first user, and its first use as a toggleable
   filter rather than a plain display row -- five across instead of four,
   and an "on" state reusing --teal-dark (already the app's own active-
   state token, tokens.css line 61) rather than a new colour. */
.tiles-5{ grid-template-columns:repeat(5,1fr) }
.tile[role="button"]{ cursor:pointer }
.tile.on{ box-shadow:0 0 0 2px var(--teal-dark) inset, 0 12px 28px rgba(14,54,59,.16) }

/* ============================================================
   Empty state
   ============================================================ */

.empty{ padding:56px 32px; text-align:center; color:var(--muted); font-size:13.5px }
/* .empty-t is this app's own equivalent of the mockup's ".empty b"
   (a <div>, not a <b>, since the app's empty-state markup already
   existed before the mockup). Font treatment ported: italic serif,
   session 106 Part 5. Padding/colour on .empty itself left as is --
   the mockup's own values (--s6 20px, --text) are close but not
   identical, and .empty is used broadly enough across the app that
   changing it felt like more than a font-detail port; flagged, not
   changed. */
.empty-t{ font-family:var(--serif); font-style:italic; font-weight:400; color:var(--ink); font-size:22px; margin-bottom:6px }

/* ============================================================
   Cards, for detail screens
   ============================================================ */

/* .card ported verbatim from the mockup, session 106 (was
   var(--radius-lg)=10px + a different shadow; now the mockup's
   literal 14px and two-layer shadow -- the mockup has no separate
   radius token for this, so the value is written in directly, same
   as the mockup's own CSS does). .card-head/.card-head h2 ported
   too, for screens whose main title moves into the card itself;
   .card-t stays for internal card labels that were never the
   mockup's concern. */
/* Session 157: card retargeted to the mockup's own .card exactly --
   white, no border, no drop shadow, 9px radius (--radius-lg, updated
   above). The earlier 14px/bordered/two-layer-shadow version was
   itself a port of a different, since-superseded mockup
   (mh-ops-retro-mockup-v7.html, session 106); this session's brief
   names docs/mh-property-page-full-mockup.html as the one target and
   that file's own .card carries none of the three. Padding stays for
   the reason already on record below: many existing cards hold plain
   text/form content with no padding of their own. */
.card{
  background:var(--white);
  border:none;
  border-radius:var(--radius-lg);
  color:var(--ink);
  box-shadow:none;
  padding:20px 22px;
}
/* A card whose only content is a .tbl: no padding of its own, so
   the table's own 20px cell padding is the only inset, matching
   how the mockup's own <section class="card"><table class="tbl">
   looks -- no doubled-up spacing on the left edge. */
.card-flush{ padding:0 }
.card-head{ display:flex; align-items:center; justify-content:space-between; gap:var(--s4); padding:16px 20px 0; flex-wrap:wrap }
.card-head h2{ font-family:var(--serif); font-style:italic; font-weight:400; font-size:24px; margin:0 }
/* Session 107: the mockup's own .card has no padding of its own, so
   .card-head's 16px/20px is the card's only inset there. This app's
   .card already carries its own 20px/22px (see above), so a
   card-head nested inside one would double up, and its h2 would sit
   20px further right than the field rows underneath it. This resets
   card-head back to flush when it is actually inside this app's
   padded .card, keeping the flex layout and adding back the same
   margin-bottom .card-t used to, so the swap from .card-t to
   .card-head h2 is a title-style change only, not a layout shift. */
.card > .card-head{ padding:0; margin:0 0 var(--s3) 0; border-bottom:2px solid var(--page-bg); padding-bottom:8px }
.card-t{ font-size:14.5px; font-weight:550; color:var(--ink); margin-bottom:var(--s3) }

/* Session 157: the mockup's own card heading (docs/mh-property-page-
   full-mockup.html .card h2): 14px, weight 700, teal, a 2px bottom
   border in the PAGE background colour. The mockup's own markup puts
   the subtitle (its <em>) inside the h2 itself; this app's markup puts
   it as a sibling inside .card-head (a flex row), so the border moves
   to .card-head above (spanning the whole row, under both the h2 and
   its sibling subtitle) rather than onto h2 alone, which would have
   forced the row to wrap. h2 here only carries the text treatment, at
   higher specificity than the base h2 rule above so it wins inside a
   card without touching h2 everywhere else in the app (a record-name
   title like a client or staff member's own h2 stays the bigger 24px
   teal heading, not this one). .card-head h2 is the platform's own
   dominant card-title pattern (design system doc: "used on dozens of
   cards across Dashboard, Property detail, Staff detail and Users"),
   so this one change reaches nearly every card in the app. */
.card-head h2{ font-size:14px; font-weight:700; color:var(--teal-dark) }

.fr{
  display:flex; justify-content:space-between; gap:var(--s3);
  padding:10px 0;
  border-bottom:1px solid var(--line-soft);
  font-size:14px;
}
.fr:last-child{ border-bottom:none }
.fr .k{ color:var(--text) }
.fr .v{ color:var(--ink); font-weight:500; text-align:right }
/* Session 155, Part 1. hint-inline had no unscoped rule (only
   .f-compact .hint-inline, session 141) -- everywhere else, including
   every .fr row on the property page, it rendered as plain text the same
   size and weight as its label, which is what made the Refrigerator
   row's hint wrap into the label instead of reading as a separate,
   muted note (session 154's own disclosed fault). Same declaration as
   .f-compact's, made the default everywhere the class is used. */
.hint-inline{ font-size:12.5px; color:var(--muted); font-weight:450; margin-left:6px }

/* ============================================================
   Session 105, Part 4: the scene
   Five time-of-day states, driven by the real clock (shell.js's
   applyScene()). Afternoon is the :root default; the other four
   override just the scene variables below.

   Session 106 correction: --ink/--text/--muted and everything a
   .card carries stay fixed -- a card is always opaque --paper, so
   its content never inverts. But --page-ink/--page-text (h1, .sub,
   anything sitting directly on the open sky, never inside a card)
   now DO invert here, at dusk and night, exactly as the mockup's
   own body[data-scene] blocks do. Session 105 pinned these fixed
   everywhere; this session's brief narrows that call to cards only.
   Contrast checked directly (not assumed) against both ends of
   every scene's own sky gradient before this shipped -- see the
   session 106 report -- and the one borderline number (.sub, dusk,
   against the gradient's darkest stop, 4.48:1) is noted there too:
   .sub sits at the top of the page, near --sky-1 in practice, where
   it measures 5.80:1.
   ============================================================ */

:root{
  --sky-1:#FBF5E6; --sky-2:#F7EBD2; --sky-3:#F3E3C2;
  --l1:#BFE8E6; --l2:#8FD3D2; --l3:#3E9EA4; --l4:#1D6E77; --l5:#0E363B; --l3b:#2F868B; --l4b:#144D54;
  --sun:#FFD13A; --sun-y:0px; --sun-op:1; --moon-op:0; --stars-op:0; --glow:.15; --cloud:#FFFFFF; --cloud-op:.9;
  --page-ink:#16181A; --page-text:#4A4F55;
}
body[data-scene="morning"]{
  --sky-1:#EEF8F8; --sky-2:#DDF0F0; --sky-3:#CBE9E8;
  --l1:#C6ECEA; --l2:#93D6D4; --l3:#4AA8AC; --l3b:#3A9296; --l4:#1D6E77; --l4b:#155A61; --l5:#0E363B;
  --sun-y:-40px; --glow:0; --cloud-op:.95;
}
body[data-scene="midday"]{
  --sky-1:#F6FBFB; --sky-2:#E8F5F5; --sky-3:#D5EFEF;
  --l1:#CDEFED; --l2:#9EDCDA; --l3:#54B3B6; --l3b:#43A0A3; --l4:#237A83; --l4b:#1A636A; --l5:#0F3C42;
  --sun-y:-90px; --glow:0; --cloud-op:.85;
}
body[data-scene="dusk"]{
  --sky-1:#E6D3DF; --sky-2:#F4C8A8; --sky-3:#F7B06A;
  --l1:#B8A8BE; --l2:#7E8FA2; --l3:#3F7883; --l3b:#33686F; --l4:#1E5A63; --l4b:#164A51; --l5:#0B2A30;
  --sun:#FF8A33; --sun-y:110px; --glow:.5; --cloud:#F8D3C2; --cloud-op:.9; --stars-op:.35;
  --page-ink:#1E1B22;
}
body[data-scene="night"]{
  --sky-1:#0B2328; --sky-2:#123A41; --sky-3:#1B5058;
  --l1:#1E4E56; --l2:#17434A; --l3:#123940; --l3b:#0F3238; --l4:#0C2C32; --l4b:#0A262B; --l5:#051A1E;
  --sun-y:220px; --sun-op:0; --moon-op:1; --stars-op:1; --glow:1; --cloud:#2A5F68; --cloud-op:.8;
  --page-ink:#FBF5E6; --page-text:#CFE4E5;
}

/* ---------- the full-page background, session 106 Part 5 ----------
   Session 105 pulled this back to a compact strip after body took
   the sky gradient directly and broke: the title sat in the gap
   between the strip and the first card with nothing opaque behind
   it, and dark ink on the night sky measured 1.09:1. Both reasons
   session 105 gave were sound (that measurement, and a fixed valley
   sitting behind whatever table row is scrolled into view on a
   ~5700px screen) and both are gone now that Part 4 put every
   screen's content inside .card: .sky-fix/.valley sit at z-index:0,
   behind everything, and .wrap below is the mockup's own .page,
   position:relative;z-index:2, so every card paints over them
   regardless of scroll position. The only things that ever sit
   directly on the sky now are h1/.sub (--page-ink/--page-text,
   checked above) and .chips/.btn/.seg, which carry their own
   translucent panel already. */
/* Session 157: the sky gradient and its time-of-day scene (session
   105/106) are exactly the kind of decorative, non-scaling look this
   session's brief removes ("not conducive to scaled design"). The
   scene machinery in shell.js (applyScene(), the data-scene attribute)
   is untouched -- this is a stylesheet value change only, per the
   brief's own limit -- but body/.sky-fix now paint the mockup's own
   flat page colour instead of reading the scene's sky variables, so
   the time-of-day repaint has nothing left to visibly do. .valley (the
   sun/moon/hills SVG art) is hidden the same way: display:none, no
   markup removed, nothing it draws survives on a flat page. */
body{
  background:var(--page-bg);
}
.sky-fix{
  position:fixed; inset:0; z-index:0;
  background:var(--page-bg);
}
.valley{ display:none }
.valley svg{ width:100%; height:100%; display:block }
.valley path, .valley use, .valley rect, .valley circle{ transition:fill 1.4s ease, opacity 1.4s ease }
.sheet-far{ filter:drop-shadow(0 8px 6px rgba(10,30,34,.22)) }
.sheet-near{ filter:drop-shadow(0 16px 12px rgba(10,30,34,.32)) }
.sun-g{ transform:translateY(var(--sun-y)); transition:transform 1.4s ease, opacity 1.4s ease; opacity:var(--sun-op) }
.moon-g{ opacity:var(--moon-op); transition:opacity 1.4s ease }
.stars-g{ opacity:var(--stars-op); transition:opacity 1.4s ease }
.clouds-g{ opacity:var(--cloud-op); transition:opacity 1.4s ease }
.win{ fill:var(--yellow); opacity:var(--glow); transition:opacity 1.4s ease }

/* ============================================================
   prefers-reduced-motion, session 106 Part 5. Ported verbatim,
   shipped in the same commit as the motion it guards, not after
   (the brief is explicit that this is not optional). Covers every
   CSS transition/keyframe added this session. The Spring class in
   shell.js has its own, separate reduced-motion check inside
   step() itself, for the requestAnimationFrame-driven motion this
   media query cannot reach (a JS physics loop is not a CSS
   transition or animation).
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.001s !important;
    animation-iteration-count:1 !important;
    transition-duration:.001s !important;
  }
  .neon-count{ animation:none }
}
