/* ==========================================================================
   THE SHOP SKIN - trial branch trial/human-skin, 2026-09-16.

   Owner, after a maintenance group read the screenshots: one man took his
   comment down saying it looked like AI slop, another opened with "while you
   used ai heavily in this". The brief: keep the colours, keep the structure,
   change the LOOK so the first thought is "a person who has turned a wrench
   built this", not "a machine generated this".

   What reads as machine-made, and what this file does about it:
     emoji icons            -> drawn line icons (core/icons.js), same hand as
                               the wrench on urwrench.com
     everything a soft card -> square corners, hairlines, no glow, no bloom
     even, airy spacing     -> tighter rows; a screen shows more work
     round icon discs       -> no discs; the icon sits in the text
     bubbly pills           -> square tags, mono, uppercase
     one weight everywhere  -> a condensed face for headings and labels, the
                               way a shop drawing, a nameplate or a parts
                               catalogue is set

   THE WHOLE APP AGAIN (D162, 2026-11-10): see drafting.css. Each rule is
   scoped by tools/scope-look.mjs; the three regions marked look:global are the
   only exceptions - the drawn icons' geometry, the 44 px touch floor and the
   Strong bell - and tests/look-scope.test.mjs holds what each may contain.

   Loaded LAST in index.html, so this file alone carries the skin and pulling
   one line out of index.html puts the old look back. Nothing here changes a
   screen's structure, its copy or its behaviour.
   ========================================================================== */

:root:where(:not([data-look="plain"])){
  /* Corners: a tool is machined, not moulded. */
  --r-sm:2px; --r-md:3px; --r-lg:3px; --r-btn:2px; --r-pill:2px;

  /* Depth by line, not by shadow. A dark shop screen with glowing cards is
     the giveaway; a hairline is what a drawing uses. */
  --shadow:0 1px 0 rgba(3,8,20,.55);
  --shadow-sm:none;
  --glow:transparent;

  /* A condensed, engineering-drawing face for headings, labels and controls.
     Every name here ships with the platform: Bahnschrift is the DIN face on
     Windows, DIN Alternate and Avenir Next Condensed are on iOS and macOS,
     Roboto Condensed is on Android. No file is downloaded and nothing is
     fetched from anyone, so the "no third-party runtime code, system fonts
     only" line in the audit still holds. */
  --cond:"Bahnschrift","DIN Alternate","Roboto Condensed","Avenir Next Condensed",
         "Segoe UI Semibold",var(--sans);
}

/*@look:global svg-ic*/
/* ---------- the drawn icons ---------- */
/* One weight for every icon, taking its colour from the text beside it.
   Qualified to svg.ic on purpose: mod/docs.css already owns a plain .ic for
   the document-kind slot (a 30px div), and an unqualified rule fought it. */
svg.ic{
  width:1.06em; height:1.06em; flex:none; vertical-align:-.15em;
  stroke:currentColor; fill:none; stroke-width:1.75;
  stroke-linecap:round; stroke-linejoin:round;
}
/* An icon with words after it needs a gap. It cannot be done with
   :not(:only-child): CSS counts only ELEMENT children, and the label beside an
   icon is a TEXT node, so the icon still matches :only-child and "Full screen"
   and "Light" came out jammed against their marks. Every icon gets the gap;
   the controls that are an icon alone take it back. button.icon is the
   drawn-icon-alone button (app.css, app-ui.css: the one-line bar's buttons, a
   machine's map and print, a work order's map); without this line its icon
   sits .42em left of the box's centre. */
svg.ic{margin-right:.42em;}
button.ico svg.ic,.rolebtn .rn svg.ic,.tile .ti svg.ic,.feature-list .fi svg.ic,
button.x svg.ic,.chip button svg.ic,a.cbtn .ci svg.ic,button.icon svg.ic{margin-right:0;}
/* The documents list owns .ic for its kind slot (mod/docs.css: a 30px box at
   22px type) and passes a glyph through el(), so a drawing now sits INSIDE
   that box. .doc .ic is (0,2,0) and beats a bare svg.ic (0,1,1), which
   stretched the drawing to the box. Size it here, at (0,2,1). */
.doc svg.ic{width:22px; height:22px; margin-right:0;}
button svg.ic, a.cbtn svg.ic{opacity:.92;}

/* ---------- one weight, measured where the eye is ----------
   :47 promises "One weight for every icon" and this file does not deliver it.
   stroke-width is a length in the 24x24 viewBox, so what gets drawn is
   stroke-width x box / 24, and every slot is a different weight:
     hub tile 28px     1.6  x 28/24    = 1.87px   heaviest of the reviewed set
     inside a .pill    1.75 x 11.66/24 = 0.85px   under one device pixel
     Messages welcome  1.75 x 46.6/24  = 3.40px   heaviest in the product
       (mod/messages.css:104 sets 44px type, and nothing above reaches .msgs)
   2.20x across the set the review measured; 4.00x once the Messages mark is
   counted - a 46px drawing carrying four times the ink of the 11.7px one.

   vector-effect:non-scaling-stroke takes the viewBox transform back out, so
   stroke-width becomes a DEVICE measurement and one number is one weight. It
   is NOT inherited (SVG 2: Applies to graphics elements; Inherited: no) and
   the strokes live on the <path> and <circle> children icon() builds, so it
   has to be set on "svg.ic *". On "svg.ic" it reaches the <svg> and stops and
   does nothing whatsoever - while a stroke-width bundled beside it DOES
   inherit, which is how that one-liner thins every icon instead of levelling
   it, and raises the spread rather than closing it.

   It must not ship alone: at one constant weight the smallest boxes fill in
   (gear has eight teeth, nut three overlapping strokes), so the small slot
   carries its own lighter number here, in the same block. Device pixels:
     1.6   the graphic marks - hub tile, landing feature, the Messages mark
     1.35  everything set in or beside text
     1.1   inside a .pill, the smallest box in the set at 11.66px
   Range 1.6 / 1.1 = 1.45x against 2.20x, and nothing under one device pixel.
   Ink inside the pill goes 7.3% -> 9.4% of the box, short of the 12% that
   fills a gear in.

   Wrapped in @supports on purpose: a browser that cannot do the property
   keeps TODAY'S rendering rather than 1.35 scaled down, which would be
   thinner everywhere and worst exactly where the defect already is. */
@supports (vector-effect: non-scaling-stroke){
  svg.ic *{vector-effect:non-scaling-stroke;}
  /* (0,1,1), ties :52 and sits after it, so it wins on order. .tile .ti svg.ic
     (:201) is (0,2,1) and keeps its 1.6 whatever the order - which is the
     number the graphic marks want, so the hub needs no line here. */
  svg.ic{stroke-width:1.35;}
  /* (0,2,1) and (0,4,1), both over the (0,1,1) above. */
  .feature-list .fi svg.ic,.msgs .mwelcome .big svg.ic{stroke-width:1.6;}
  .pill svg.ic{stroke-width:1.1;}
}
/*@look:end*/

/* The em dash used to draw a line icon in empty cells, because core/icons.js
   mapped U+2014 to the `dash` shape and el() converts a leading glyph. It was
   worked around from here with a :has() rule; the mapping itself was removed
   on 2026-09-17, so the workaround is gone too. The `dash` drawing stays in
   icons.js, unmapped and unused. */

/* ---------- type ---------- */
:where(html:not([data-look="plain"])) h1,:where(html:not([data-look="plain"])) h2,:where(html:not([data-look="plain"])) h3,:where(html:not([data-look="plain"])) .tile .tn,:where(html:not([data-look="plain"])) .hub-label,:where(html:not([data-look="plain"])) .topbar h2,:where(html:not([data-look="plain"])) .hero-title,:where(html:not([data-look="plain"])) .hero-line,
:where(html:not([data-look="plain"])) button,:where(html:not([data-look="plain"])) .admtab,:where(html:not([data-look="plain"])) .tabs button,:where(html:not([data-look="plain"])) label,:where(html:not([data-look="plain"])) .kv dt{font-family:var(--cond);}
:where(html:not([data-look="plain"])) h1{letter-spacing:.005em; font-weight:700;}
:where(html:not([data-look="plain"])) h2{letter-spacing:.01em; font-weight:700;}
/* Section headings: a shop label, not a blue banner. */
:where(html:not([data-look="plain"])) h3{
  font-size:12px; letter-spacing:.14em; color:var(--muted); font-weight:700;
  margin:20px 0 8px; padding-bottom:5px; border-bottom:1px solid var(--line);
}
/* A form's field label reads as a small stamped caption. NOT a label that
   wraps a control: permission rows, the My KPIs picker and the PM checklist
   are built as <label><input> ... </label>, and uppercasing those turned whole
   sentences into shouting. */
:where(html:not([data-look="plain"])) label:not(:has(input)):not(:has(select)):not(:has(textarea)){
  font-size:12px; letter-spacing:.09em; text-transform:uppercase; color:var(--muted);
}
/* Numbers line up in columns the way they do on a parts list. */
:where(html:not([data-look="plain"])) .mono,:where(html:not([data-look="plain"])) .kv dd,:where(html:not([data-look="plain"])) .ano b,:where(html:not([data-look="plain"])) .brow .bn,:where(html:not([data-look="plain"])) .srow .n,:where(html:not([data-look="plain"])) .item .h .n,:where(html:not([data-look="plain"])) .entry .c .h .n,:where(html:not([data-look="plain"])) table.ord td,:where(html:not([data-look="plain"])) table.ord th,
:where(html:not([data-look="plain"])) .ord .ov,:where(html:not([data-look="plain"])) .ord .od,:where(html:not([data-look="plain"])) .badge,:where(html:not([data-look="plain"])) .pill{font-variant-numeric:tabular-nums;}

/* ---------- controls ---------- */
:where(html:not([data-look="plain"])) button{
  border-radius:var(--r-btn); font-size:15px; letter-spacing:.02em; font-weight:600;
  padding:10px 14px;
}
:where(html:not([data-look="plain"])) button.primary{border-color:var(--brand); font-weight:700;}
:where(html:not([data-look="plain"])) button.small{font-size:13.5px; padding:6px 10px; min-height:32px;}
/* 40px shipped here in v14.1.60 and it is under the app's own floor: app.css:59
   declares --tap:44px and app.css:106 applies it to every button at (0,0,1).
   This rule is (0,1,1) in the LAST sheet index.html loads, so it beat that
   floor everywhere a module had not already sized the control - including
   "Change" and "Remove" on a marked spot (mod/report.js:216-218), which sit in
   .spotrow and are matched by none of the rescues in app-ui.css or routing.css.
   Same rule, the app's own number. Every module rule that qualifies .small with
   a container is more specific and keeps the size it chose on purpose; the ones
   that were under --tap for no reason are lifted at the end of this file. */
@media (pointer:coarse){ :where(html:not([data-look="plain"])) button.small{min-height:var(--tap);} }
:where(html:not([data-look="plain"])) input,:where(html:not([data-look="plain"])) textarea,:where(html:not([data-look="plain"])) select{border-radius:var(--r-btn);}
:where(html:not([data-look="plain"])) input:focus,:where(html:not([data-look="plain"])) textarea:focus,:where(html:not([data-look="plain"])) select:focus{box-shadow:none; border-color:var(--accent); outline:1px solid var(--accent);}

/* Actions sit at their own width on a wide screen instead of three stretched
   bars across a card; they still stretch on a phone, where thumbs need it. */
@media (min-width:601px){
  :where(html:not([data-look="plain"])) .btnrow{gap:6px; flex-wrap:wrap;}
  :where(html:not([data-look="plain"])) .btnrow > *{flex:0 1 auto;}
  :where(html:not([data-look="plain"])) .btnrow > .spacer{flex:1 1 auto;}
}

/* On a phone the three actions on a job used to be three stacked slabs, with
   "Open machine" breaking onto two lines. Same three buttons, sized like
   controls instead of banners, each on one line. */
@media (max-width:600px){
  :where(html:not([data-look="plain"])) .btnrow{gap:6px;}
  :where(html:not([data-look="plain"])) .btnrow button{font-size:13.5px; min-height:40px; padding:8px 9px; white-space:nowrap;}
  :where(html:not([data-look="plain"])) .kpi-chips button.seg,:where(html:not([data-look="plain"])) .kpi-groups button.seg{font-size:12.5px; padding:5px 10px; min-height:34px;}
  :where(html:not([data-look="plain"])) .kpi-barbtns button{font-size:13.5px;}
}

/* ---------- surfaces ---------- */
:where(html:not([data-look="plain"])) .card,:where(html:not([data-look="plain"])) .item,:where(html:not([data-look="plain"])) .vaddr,:where(html:not([data-look="plain"])) .permlist,:where(html:not([data-look="plain"])) #findWrap,:where(html:not([data-look="plain"])) .mailbody{border-radius:var(--r-md); box-shadow:none;}
:where(html:not([data-look="plain"])) .card{padding:14px;}
:where(html:not([data-look="plain"])) .item{padding:11px 12px; margin-bottom:8px;}
:where(html:not([data-look="plain"])) #toast{border-radius:var(--r-md); box-shadow:0 2px 0 rgba(3,8,20,.6);}

/* Tags, not bubbles: square, mono, small caps, colour on the edge. */
:where(html:not([data-look="plain"])) .pill{
  border-radius:var(--r-pill); font-size:11px; letter-spacing:.06em;
  text-transform:uppercase; padding:3px 7px; background:transparent;
}
:where(html:not([data-look="plain"])) .pill.accent{background:transparent;}
/* A pill that is a button (app.css button.pill.link: the machine on a work
   order, the marked spot). app.css resets it with font:inherit at (0,2,1),
   which beats the .pill row above and would set it in the body face at 12.5px
   with 4/11 padding beside pills in mono small caps. Same specificity, this
   file, so it wins on order: the tag's own type. */
:where(html:not([data-look="plain"])) button.pill.link{font-family:var(--mono); font-size:11px; letter-spacing:.06em; padding:3px 7px;}
/* Two chip treatments on a job card, not five. A boxed chip means severity or
   state (SAFETY, HIGH, EMERGENCY, WAITING TO BE ASSIGNED). What KIND of job it
   is (MACHINE, BUILDING, OTHER, PM, a marked spot) is just a word: no box, no
   dashed edge. The dashed borders came from routing.css and map.css and taught
   the reader nothing, which is how a screen ends up looking auto-generated. */
:where(html:not([data-look="plain"])) .pill.cat,:where(html:not([data-look="plain"])) .pill.spot,:where(html:not([data-look="plain"])) .pill.uncounted{
  border-color:transparent; background:transparent; color:var(--muted);
  padding-left:0; padding-right:10px; border-style:solid;
}
/* ...but the marked spot is a CONTROL, not a classification: one tap opens the
   map centred on the place the reporter marked (queue.js, myrequests.js).
   Flattened in with the tags above it became two grey words that look exactly
   like the inert OTHER beside them. It gets the one cue a person already reads
   as "this goes somewhere" - the accent, and a rule under the words. No box, so
   the plain tags stay plain.
   Listed twice on purpose. Bare, .pill.spot is (0,2,0): enough in dark, where
   it beats routing.css:33 on load order. In LIGHT it would lose to theme.css:45
   html[data-theme="light"] .pill (0,2,1), which is the same arithmetic that put
   the boxes back on the category chips. The second selector is (0,3,1) and takes
   that one on class count, 3 > 2, so both themes get the same treatment. */
:where(html:not([data-look="plain"])) .pill.spot,
html:where(:not([data-look="plain"]))[data-theme="light"] .pill.spot{
  color:var(--accent); border-width:1px; border-color:transparent;
  border-bottom:1px solid var(--accent-line); padding-right:3px;
}
/* routing.css:34 paints all four edges accent on hover, which would put the box
   back on the way past. Same (0,3,0) as that rule, later file, so this wins. */
:where(html:not([data-look="plain"])) .pill.spot:hover,:where(html:not([data-look="plain"])) .pill.spot:focus-visible,
html:where(:not([data-look="plain"]))[data-theme="light"] .pill.spot:hover,
html:where(:not([data-look="plain"]))[data-theme="light"] .pill.spot:focus-visible{
  color:var(--accent); border-color:transparent; border-bottom-color:var(--accent);
}
:where(html:not([data-look="plain"])) .chip.spot,:where(html:not([data-look="plain"])) .chip.gps.sim{border-style:solid;}
:where(html:not([data-look="plain"])) .chip{border-radius:var(--r-md); box-shadow:none;}
:where(html:not([data-look="plain"])) .chip button{border-radius:var(--r-btn);}
:where(html:not([data-look="plain"])) button.seg,:where(html:not([data-look="plain"])) button.vend,:where(html:not([data-look="plain"])) a.cbtn{border-radius:var(--r-btn);}
:where(html:not([data-look="plain"])) .badge{border-radius:2px; min-width:19px; height:19px; font-family:var(--mono); font-weight:700;}

/* ---------- the top bar ---------- */
:where(html:not([data-look="plain"])) .topbar h2{
  font-size:15px; letter-spacing:.12em; text-transform:uppercase; font-weight:700;
}
/* the blue rule under the title: a stamped underline, not a rounded pill */
:where(html:not([data-look="plain"])) #modTitle::after{width:22px; height:2px; border-radius:0; margin-top:5px;}
:where(html:not([data-look="plain"])) .topbar button{border-radius:var(--r-btn); font-size:13px; letter-spacing:.04em;}

/* ---------- the hub ---------- */
/* A faint diagonal hatch, the way a drawing shades a cut face. 2%: texture,
   never something to look at. */
:where(html:not([data-look="plain"])) #screen-hub::before{
  content:''; position:absolute; inset:0; z-index:0; pointer-events:none; opacity:.022;
  background:repeating-linear-gradient(45deg, var(--brand-lt) 0 1px, transparent 1px 9px);
}
:where(html:not([data-look="plain"])) .hub-ornament{opacity:.03;}
:where(html:not([data-look="plain"])) .hub-label{
  color:var(--muted); font-size:11px; letter-spacing:.2em; margin:4px 2px 9px;
  padding-bottom:6px; border-bottom:1px solid var(--line);
}
:where(html:not([data-look="plain"])) .hub-grid{gap:8px; grid-template-columns:repeat(auto-fill, minmax(158px, 1fr));}
:where(html:not([data-look="plain"])) .tile{
  min-height:0; padding:12px 13px 13px; gap:7px; border-radius:var(--r-md);
  background:var(--chrome);
}
/* no disc: the icon is a drawing on the tile, in the accent, sized like a
   stamped mark in the corner of a drawing */
:where(html:not([data-look="plain"])) .tile .ti{
  width:26px; height:26px; border-radius:0; background:transparent; border:0;
  color:var(--accent); overflow:visible;
}
:where(html:not([data-look="plain"])) .tile .ti svg.ic{width:26px; height:26px; stroke-width:1.6;}
:where(html:not([data-look="plain"])) .tile:hover .ti,:where(html:not([data-look="plain"])) .tile:focus-visible .ti{background:transparent; border:0;}
:where(html:not([data-look="plain"])) .tile .tn{font-size:15px; letter-spacing:.03em; font-weight:700;}
:where(html:not([data-look="plain"])) .tile .td{font-size:11.5px; line-height:1.35; color:var(--muted);}
/* the top rule on hover: a full-width machined edge */
:where(html:not([data-look="plain"])) .tile::before{left:0; right:0; height:2px; border-radius:0;}
:where(html:not([data-look="plain"])) .tile .badge{top:9px; right:9px;}
:where(html:not([data-look="plain"])) .hub-foot{font-size:12px; letter-spacing:.16em;}
/* On a monitor the old 640px column left half the screen empty, which reads as
   a phone app stretched wide. The same tiles, in the same order, filling the
   bench. */
@media (min-width:900px){
  :where(html:not([data-look="plain"])) .hub-in{max-width:1040px;}
  :where(html:not([data-look="plain"])) .hub-grid{grid-template-columns:repeat(auto-fill, minmax(196px, 1fr)); gap:10px;}
  :where(html:not([data-look="plain"])) .tile{padding:14px 15px 15px;}
  :where(html:not([data-look="plain"])) .tile .ti,:where(html:not([data-look="plain"])) .tile .ti svg.ic{width:28px; height:28px;}
}

/* ---------- landing ---------- */
:where(html:not([data-look="plain"])) .landing-glow{display:none;}
:where(html:not([data-look="plain"])) .landing-motif{opacity:.045;}
:where(html:not([data-look="plain"])) .hero-title{letter-spacing:.1em;}
:where(html:not([data-look="plain"])) .landing-band{border-radius:0;}
:where(html:not([data-look="plain"])) .feature-list .fi{
  width:28px; height:28px; border-radius:0; background:transparent;
  border:0; color:var(--accent);
}
:where(html:not([data-look="plain"])) .feature-list .fi svg.ic{width:24px; height:24px;}

/* ---------- lists and rows: more work per screen ---------- */
:where(html:not([data-look="plain"])) .arow,:where(html:not([data-look="plain"])) .vrow,:where(html:not([data-look="plain"])) .srow,:where(html:not([data-look="plain"])) .urow,:where(html:not([data-look="plain"])) .contact,:where(html:not([data-look="plain"])) .entry{padding-top:9px; padding-bottom:9px;}
:where(html:not([data-look="plain"])) .brow{padding:9px 6px;}
:where(html:not([data-look="plain"])) .ord{padding:10px 0;}
:where(html:not([data-look="plain"])) .tabs button{border-radius:var(--r-sm) var(--r-sm) 0 0; font-family:var(--cond); letter-spacing:.04em;}
:where(html:not([data-look="plain"])) button.admtab{border-radius:var(--r-sm) var(--r-sm) 0 0; letter-spacing:.05em;}
:where(html:not([data-look="plain"])) .ahead .no{letter-spacing:0;}
:where(html:not([data-look="plain"])) .photos img,:where(html:not([data-look="plain"])) button.pcell,:where(html:not([data-look="plain"])) button.pthumb,:where(html:not([data-look="plain"])) button.pthumb img,:where(html:not([data-look="plain"])) img.preview{border-radius:var(--r-sm);}
:where(html:not([data-look="plain"])) .ptag{border-radius:var(--r-sm); font-variant-numeric:tabular-nums;}

/* Light mode: a plain button was filling with the light-grey panel colour
   while an outlined one beside it stayed white, so "Open machine" read as
   disabled next to "Est. hours". Plain buttons are white; the primary and the
   one warm action keep their fill. */
html:where(:not([data-look="plain"]))[data-theme="light"] button:not(.primary):not(.cta):not(.danger):not(.seg.on):not(.admtab.on){
  background:#fff;
}
/* ...but NOT the buttons sitting on a navy bar. theme.css gives those white
   text on a translucent fill so they read against the navy; the rule above was
   winning and painting them solid white, which left every top-bar button a
   blank white box in light mode (caught in the light-mode capture, 2026-09-17).
   Restore the bar's own treatment at matching specificity.
   The :not() chain on the rule above counts toward specificity (0,6,2), so a
   plain ".topbar button" (0,2,2) loses to it and the bar stayed white. These
   carry the same chain, plus the bar's own class, to win. */
html:where(:not([data-look="plain"]))[data-theme="light"] .topbar button:not(.primary):not(.cta):not(.danger):not(.seg.on):not(.admtab.on),
html:where(:not([data-look="plain"]))[data-theme="light"] .ahead button:not(.primary):not(.cta):not(.danger):not(.seg.on):not(.admtab.on),
html:where(:not([data-look="plain"]))[data-theme="light"] #findWrap .hd button:not(.primary):not(.cta):not(.danger):not(.seg.on):not(.admtab.on){
  background:rgba(255,255,255,.06); color:#fff;
}
html:where(:not([data-look="plain"]))[data-theme="light"] .topbar button:not(.primary):not(.cta):not(.danger):hover:not(:disabled),
html:where(:not([data-look="plain"]))[data-theme="light"] .ahead button:not(.primary):not(.cta):not(.danger):hover:not(:disabled),
html:where(:not([data-look="plain"]))[data-theme="light"] #findWrap .hd button:not(.primary):not(.cta):not(.danger):hover:not(:disabled){
  background:rgba(255,255,255,.14);
}
html:where(:not([data-look="plain"]))[data-theme="light"] .topbar button.ghost{background:transparent;}
html:where(:not([data-look="plain"]))[data-theme="light"] .topbar button.primary{background:var(--brand-lt);}


/* The phone's top bar was four chrome buttons over two rows, about 190px
   before any work showed. Same buttons, sized for a bar. */
@media (max-width:480px){
  :where(html:not([data-look="plain"])) .topbar{gap:7px; padding-top:calc(var(--sat) + 8px); padding-bottom:8px;}
  /* 36px, not the 32px this rule shipped with: app.css:187 already had the bar
     at 36 and the skin took 4px off the one control every module screen is left
     through. A narrow window on a mouse gets what it had; a finger gets --tap,
     in the block below. */
  :where(html:not([data-look="plain"])) .topbar button{min-height:36px; padding:5px 9px; font-size:12.5px;}
  :where(html:not([data-look="plain"])) .topbar .pill{font-size:11px; padding:3px 8px;}
  :where(html:not([data-look="plain"])) #screen-hub .topbar{row-gap:6px;}
}
/* On a real touch screen the bar carries the app's OWN target: app.css:59
   declares --tap:44px and nothing was applying it here - no (pointer:coarse)
   rule for .topbar button exists in any sheet, so a phone got 32px.
   ".topbar button" is (0,1,1) in app.css, in the phone block above, and here:
   three rules at the SAME specificity, decided by order alone. This one wins
   only by sitting after them in the last-loaded file - it must stay below the
   max-width block, never above it.
   The two-row layout is unchanged: app.css:210 already wraps the hub bar and
   app.css:217-224 the module bar, so the height lands on rows that exist. The
   bar's own padding gives 2px of it back. */
@media (pointer:coarse){
  :where(html:not([data-look="plain"])) .topbar button{min-height:var(--tap); padding-top:6px; padding-bottom:6px;}
  :where(html:not([data-look="plain"])) .topbar{padding-top:calc(var(--sat) + 7px); padding-bottom:7px;}
}

/* ---------- KPIs: a shift report, not a dashboard ----------
   The giant figure with a friendly caption under it, in a grid of identical
   soft cards, is the one layout everybody has seen a machine produce. Same
   numbers, same cards, same order; read as a report instead. */
:where(html:not([data-look="plain"])) .kpi-card{padding:13px 14px;}
:where(html:not([data-look="plain"])) .kpi-hero{
  font-size:30px; font-weight:700; letter-spacing:0; font-family:var(--cond);
  font-variant-numeric:tabular-nums; margin:1px 0 5px;
}
:where(html:not([data-look="plain"])) .kpi-tile{border-radius:var(--r-md); padding:10px;}
:where(html:not([data-look="plain"])) .kpi-tile .v{font-size:21px; font-family:var(--cond); font-variant-numeric:tabular-nums;}
:where(html:not([data-look="plain"])) .kpi-mini .v b{font-size:17px; font-variant-numeric:tabular-nums;}
:where(html:not([data-look="plain"])) .kpi-h{font-family:var(--cond); letter-spacing:.1em; text-transform:uppercase; font-size:12.5px;}
:where(html:not([data-look="plain"])) .kpi-sub{margin-bottom:9px;}
/* deltas, the "showing" mark and the parts tag: square stamps */
:where(html:not([data-look="plain"])) .kpi-delta,:where(html:not([data-look="plain"])) .kpi-showing,:where(html:not([data-look="plain"])) .kpi-hero .kpi-tag{border-radius:var(--r-pill);}
:where(html:not([data-look="plain"])) .kpi-delta{background:transparent;}
/* bars and meters end square, the way a gauge scale does */
:where(html:not([data-look="plain"])) .kpi-hbar,:where(html:not([data-look="plain"])) .kpi-stack span:last-child{border-radius:0;}
:where(html:not([data-look="plain"])) .kpi-meter,:where(html:not([data-look="plain"])) .kpi-meter i{border-radius:0;}
:where(html:not([data-look="plain"])) .kpi-meter{height:10px;}
:where(html:not([data-look="plain"])) .kpi-more .kpi-morehead{border-radius:var(--r-md); padding:10px 13px;}
:where(html:not([data-look="plain"])) .kpi-tip{border-radius:var(--r-md); box-shadow:none;}
:where(html:not([data-look="plain"])) .kpi-legend i{border-radius:0;}
/* The three states of open work were a lightness ramp and nothing else
   (--viz-o1/o2/o3, kpi.css:17), and kpi.js seg() gives each span a title and no
   text - so at 14px on a phone, or in greyscale, the bar reads as one bar. The
   house rule is that nothing signals by hue alone, and kpi.css:180
   (.kpi-hbar.hatched) already uses the technique. Each state now carries its own
   WEAVE as well as its own step: solid, one diagonal, the other diagonal. The
   legend swatch carries the same weave, so the key still matches the bar.
   Set as background-IMAGE over kpi.css:205's background-COLOUR shorthand, so the
   step stays exactly the colour the ramp chose. .kpi-stack .kpi-o2 is (0,2,0)
   and .kpi-legend i.kpi-o2 is (0,2,1); both beat the bare .kpi-o2 (0,1,0).
   White at 55% over any step of the ramp, in either theme. */
:where(html:not([data-look="plain"])) .kpi-stack .kpi-o2,:where(html:not([data-look="plain"])) .kpi-legend i.kpi-o2{
  background-image:repeating-linear-gradient(135deg, rgba(255,255,255,.55) 0 2px, transparent 2px 5px);
}
:where(html:not([data-look="plain"])) .kpi-stack .kpi-o3,:where(html:not([data-look="plain"])) .kpi-legend i.kpi-o3{
  background-image:repeating-linear-gradient(45deg, rgba(255,255,255,.55) 0 2px, transparent 2px 5px);
}
/* The caveat block (kpi.js captionMore) is honest and must not go missing, but
   on a phone it runs five wrapped lines between the range chips and the first
   figure. Its size and margin are set INLINE at kpi.js:776, which no selector
   can reach; leading is not, so the lines tighten here. .kpi-caption (0,1,0)
   ties app.css:99 .sub and wins on order. The real fix is a disclosure control
   and belongs in kpi.js. */
@media (max-width:600px){
  :where(html:not([data-look="plain"])) .kpi-caption{line-height:1.35;}
  :where(html:not([data-look="plain"])) .kpi-bar{margin-bottom:8px;}
}
:where(html:not([data-look="plain"])) button.small.kpi-everyone,:where(html:not([data-look="plain"])) .kpi-segs button.seg{border-radius:var(--r-btn);}

/* ---------- Messages: a message log, not a chat widget ----------
   The screen this file never named - .msgs appears nowhere above it - and the
   one that still reads as something lifted out of another product: 14px
   bubbles with a tail, a round avatar disc, and a green presence dot wearing a
   3px glow, inside a tool whose corners are 2-3px and which has no glow
   anywhere else. Structure, copy and behaviour are untouched; this is the same
   screen with the shop's corners on it.

   Every rule below TIES the mod/messages.css rule it has to beat and wins on
   source order - index.html loads mod/messages.css at :25 and this file at :38.
   Specificity is (id, class/attr/pseudo-class, type). */
/* The bubbles. .msgs .bub is (0,2,0) against messages.css:78 (0,2,0); .bub.mine
   has to be listed as well, because messages.css:80 is (0,3,0) and a lone
   .msgs .bub would lose the right-hand corners to it and square only theirs.
   Mine and theirs still tell apart by the side of the pane they sit on, the
   accent tint and the accent edge, and the sender's name in a group - never by
   the tail, which is all the tail was doing. */
:where(html:not([data-look="plain"])) .msgs .bub,:where(html:not([data-look="plain"])) .msgs .bub.mine{border-radius:var(--r-md);}
/* Initials on a square nameplate, the way a shop badge is set, not a disc.
   (0,3,0) against messages.css:27. */
:where(html:not([data-look="plain"])) .msgs .crow .av{border-radius:var(--r-sm);}
/* A group's avatar is the drawn `people` mark and the empty pane's is the drawn
   `chat` mark, each alone in its box - so both take back the gap :60 gives
   every icon, the way the icon-only slots at :61-62 do. Without this they sit
   .42em left of their own centre. (0,4,1); nothing else styles them. */
:where(html:not([data-look="plain"])) .msgs .crow .av svg.ic,:where(html:not([data-look="plain"])) .msgs .mwelcome .big svg.ic{margin-right:0;}
/* The dot keeps its shape - a dot IS a dot, and hollow against filled is the
   signal, not the hue (rule 8) - but not the halo, the last glow in the
   product. Measured on --chrome #101D3B: the filled --good dot is 9.34:1 and
   the hollow --muted ring 7.56:1, so nothing legible leaves with it.
   (0,3,0) against messages.css:53-54. */
:where(html:not([data-look="plain"])) .msgs .ol.on{box-shadow:none;}
/* The side pane's headings read as stamped labels, like h3 at :76 and
   .hub-label at :186; the day rule keeps the mono of a date and takes the
   small caps of a tag (:127). (0,2,0) each. */
:where(html:not([data-look="plain"])) .msgs .sh{font-family:var(--cond); letter-spacing:.13em;}
:where(html:not([data-look="plain"])) .msgs .day{text-transform:uppercase; letter-spacing:.08em;}
/* A name over a message is a caption, not a heading: the condensed face, and
   deliberately NOT uppercased - :84 above already learned that uppercasing
   something a person wrote turns it into shouting. (0,3,0) against
   messages.css:82. */
:where(html:not([data-look="plain"])) .msgs .bub .who{font-family:var(--cond); letter-spacing:.04em;}

/* ---------- light mode keeps the same discipline ---------- */
html:where(:not([data-look="plain"]))[data-theme="light"] .tile .ti,
html:where(:not([data-look="plain"]))[data-theme="light"] .feature-list .fi{border:0; background:transparent; color:var(--accent);}
html:where(:not([data-look="plain"]))[data-theme="light"] .pill{background:transparent;}
/* The classification tags stay PLAIN in light too. theme.css:45
   html[data-theme="light"] .pill is (0,2,1) and beats this file's
   .pill.cat,.pill.spot,.pill.uncounted (0,2,0) whatever the order, so light was
   painting a 1.5px #26313F box back onto every MACHINE / BUILDING / OTHER /
   NOT COUNTED YET tag: "two chip treatments, not five" held in dark only.
   Repeated here at (0,3,1), which takes it on class count, 3 > 2. Width is
   matched to dark's 1px so the two themes measure the same; the colour is
   transparent in both. .pill.spot is deliberately NOT in this list - it is a
   control, and keeps the accent rule it is given above. */
html:where(:not([data-look="plain"]))[data-theme="light"] .pill.cat,
html:where(:not([data-look="plain"]))[data-theme="light"] .pill.uncounted{
  border-width:1px; border-color:transparent; color:var(--muted);
}
html:where(:not([data-look="plain"]))[data-theme="light"] #screen-hub::before{opacity:.04;}
/* theme.css lifts the mark to 26% on white so it does not vanish; against the
   hatch and the square tiles that reads as wallpaper. Texture, not a picture.
   (Same specificity as theme.css, and this file loads after it.) */
html:where(:not([data-look="plain"]))[data-theme="light"] .hub-ornament{opacity:.05;}
html:where(:not([data-look="plain"]))[data-theme="light"] .landing-motif{opacity:.06;}
html:where(:not([data-look="plain"]))[data-theme="light"] .hub-label{color:var(--muted);}

/*@look:global touch*/
/* ---------- touch targets: the app's own --tap, on a real touch screen ------
   app.css:59 declares --tap:44px and app.css:106 puts it on every button at
   (0,0,1) - the weakest selector in the product, so ANY module rule that sizes
   a control at all beats it and the policy silently stops applying. A sweep of
   every min-height in all 21 sheets found two dozen controls under it.

   They are paid back HERE rather than in each module's own sheet for one
   reason: this is the LAST sheet index.html loads (:38), so a selector that
   TIES the rule it must beat wins on source order, while the same rule written
   in app-ui.css (:17) would lose to every sheet in mod/ that loads after it -
   the trap app-ui.css:256-267 already documents against itself.

   Specificity is (id, class/attr/pseudo-class, type). Each line ties the rule
   named beside it and wins from here on order; not one of them is shorter than
   what it has to beat, so not one of them is decorative.

   DELIBERATELY NOT REACHED, each sized on purpose and each out-ranking these:
     .modal .btnrow button.ico / .slim  (0,3,1) 36px  views.css:67-68
     .chip button                       (0,1,1) 36px  app-ui.css:202, the map
     .kpi-segs button.seg               (0,2,1) 36px  kpi.css:130
     button.small.kpi-everyone          (0,2,1) 36px  kpi.css, same card row
     .pmap-chips button.seg             (0,4,1) 36px  views.css:23
     .loctree summary / button.shelf    (0,2,1) 36px  partloc.css:46, :56
     .ck-fill .ck-choices button        (0,2,1) 36px  pm.css:31
     .topbar.oneline button             (0,2,1) 36px  app.css, the one-line bar
     .ahead button.detoggle             (0,2,1) 34px  app-ui.css, the details fold
     select.lineno                      (0,1,1) 36px  app.css, a job's Order number
                                                      in the queue card's corner and
                                                      on the Workload rows (wave 2,
                                                      port 2): at 44 it stood taller
                                                      than the card's first line
   36px is this product's one documented exception tier (owner, 2026-09-11: at
   44 the map's chips filled the screen). The 34px fold square is the one
   control under it: a square beside the 26px asset number, sized to that line
   (2026-09-15); everything else under 36 is lifted. */
@media (pointer:coarse){
  /* The v14.1.60 regression. skin.css:115 .btnrow button (0,1,1) took every
     modal button in the app to 40px, and core/ui.js:76 puts EVERY modal's
     buttons in .btnrow. Same (0,1,1), same file, below it, so this wins on
     order - and a narrow MOUSE window keeps :115's compact 40px, exactly the
     way the top bar keeps :276's 36px and takes --tap at :291. .btnrow wraps by
     width, not by height, so the queue's three actions do not restack. */
  .btnrow button{min-height:var(--tap);}

  /* The Admin's edit / remove pair: 26px on EVERY record in the product.
     (0,1,1) against records.css:8 (0,1,1) - it carries no .small at all, which
     is why button.small at :98 never matched it in the first place. .arow /
     .vrow / .urow already stand ~56px tall so those rows do not grow; a card's
     .item .h header does, for an Admin, who is on the same phone. */
  button.adm{min-height:var(--tap);}
  .chg .ch button.small{min-height:var(--tap);}         /* (0,3,1) vs records.css:44 */

  /* The two smallest targets in the product, 22px and 26px. .dchip button.x is
     the only one whose WIDTH is capped too (docs.css:42 sets width:22px), so
     the width has to move with it or a 44px-tall target stays 22px wide. */
  .dchip button.x{width:var(--tap); min-height:var(--tap);}  /* (0,2,1) vs docs.css:42 */
  .ppick button.x,.pused button.x{min-height:var(--tap);}    /* (0,2,1) vs stock.css:54 */
  .pused button.addp{min-height:var(--tap);}                 /* (0,2,1) vs stock.css:62 */
  .locchip button.x{min-height:var(--tap);}                  /* (0,2,1) vs partloc.css:25 */
  .shelfline button{min-height:var(--tap);}                  /* (0,2,1) vs partloc.css:67 */
  .doc .dacts a.small,.doc .dacts button.small{min-height:var(--tap);} /* (0,3,1) vs docs.css:15 */
  .sugg .sg{min-height:var(--tap);}                          /* (0,2,0) vs docs.css:46 */
  .ord .oacts button.small{min-height:var(--tap);}           /* (0,3,1) vs history.css:12 */
  .ordmore button{min-height:var(--tap);}                    /* (0,1,1) vs history.css:21 */
  .item button.pm-hist{min-height:var(--tap);}               /* (0,2,1) vs pm.css:9 */
  .ck-ed .ck-row button{min-height:var(--tap);}              /* (0,2,1) vs pm.css:22 */
  .help .toc button{min-height:var(--tap);}                  /* (0,2,1) vs help.css:19 */
  button.vend{min-height:var(--tap);}                        /* (0,1,1) vs app-ui.css:244 */
  /* people.css:42 already gives this 44px below 600px; a coarse tablet wider
     than that was still getting 34. (0,2,1) against people.css:32. */
  .orgrow .oto select{min-height:var(--tap);}

  /* Show / Hide on all four password fields - the sign-in screen and the
     set-password flow, the first controls anyone in the plant touches, and the
     one control with no (pointer:coarse) rule anywhere in any sheet.
     app.css:151-153 sets min-height:0 and a fixed HEIGHT of 32px, so a
     min-height here would change nothing: the height is what has to move.
     (0,2,0) against app.css:151 (0,2,0), from the later sheet. The field
     computes to ~45px (16px type, 12px padding, 1px edge), so 44 sits inside
     it; in light mode the 2px edge gives 47px and it sits inside that too. */
  .pw .pw-eye{height:var(--tap);}
}
/*@look:end*/

/*@look:global push*/
/* The bell on Strong (core/push.js, port 5): a second cue beside its drawing, a
   bell with sound arcs. A ring that also reads in greyscale; the words say
   Strong where the bar has room for them. */
[data-push-toggle][data-push-state="strong"] { box-shadow: inset 0 0 0 2px currentColor; }
/*@look:end*/
