/*
 * Copyright Stefan Press Consulting
 * Created using Claude Code.
 */
/* Auto Appraisal Software — tablet-first UI */
:root{
  --bg:#0e1116; --bg2:#151a22; --panel:#1b212b; --panel2:#222a36;
  --line:#2e3846; --line2:#3d4a5c;
  /* --ink3 was #748296, which measured 4.14:1 on --panel and 3.70:1 on
     --panel2 — under the 4.5:1 minimum, on the colour used for every hint,
     caption and secondary line in the app. This is read at a kerbside in
     daylight, where a screen is already fighting the sun. #8b99ad measures
     5.59, 5.00 and 6.54 against panel, panel2 and bg. */
  --ink:#e8edf4; --ink2:#a7b4c6; --ink3:#8b99ad;

  /* ---------------------------------------------------------------------
     THE BRAND, AS TOKENS, BECAUSE A COMPANY CAN NOW REPLACE IT.

     These used to be two variables and a scattering of literals: #d9a63c and
     #b0821d in the primary button's gradient, #8d6716 on its border, #241a05
     for the text on top of it, and rgba(201,150,46,…) at five different
     alphas for every focus ring and soft wash in the application. All of them
     were the amber, written out by hand, and every one of them would have
     stayed amber on a firm that had chosen navy.

     So the whole family is derived from one accent and named here. ui.js
     recomputes the lot from a company's brand_accent — see applySkin() — and
     the values below are the product's own, which is what a company that has
     never opened the Appearance panel keeps.

     --brand-rgb IS THE SAME COLOUR AS --brand, as three numbers. It exists
     because rgba() cannot take a hex, and the washes need the alpha: written
     as rgba(var(--brand-rgb), .18) each rule keeps its own transparency
     instead of every one of them collapsing to a single "tint" token.

     --brand-ink is the text ON the brand, and it is the reason this is a
     family rather than a colour. Amber wants near-black on it; a navy brand
     wants white. Picking that by luminance is one line in applySkin() and
     unanswerable in a stylesheet.
     --------------------------------------------------------------------- */
  --brand:#c9962e;        /* chrome / amber */
  --brand-rgb:201,150,46;
  --brand2:#f0c05a;       /* the lighter partner: links, figures, active tabs */
  --brand-hi:#d9a63c;     /* top of the primary button's gradient */
  --brand-dark:#b0821d;   /* and its bottom */
  --brand-hi2:#e6b64c;    /* the same two, hovered */
  --brand-dark2:#c08f22;
  --brand-edge:#8d6716;   /* its border */
  --brand-ink:#241a05;    /* text and icons ON any of the above */

  --accent:#4ea3e0;
  --ok:#3fa46a; --warn:#d9973a; --bad:#d0554b;
  --radius:10px;
  --tap:46px;             /* minimum tablet tap target */
  /* Height of the sticky top bar: a 46px tap row, 10px padding top and bottom,
     and a 1px border. Four places used to hard-code this by hand as 76, 78, 60
     and 170, and three of them were already wrong. Every anchor offset now
     derives from here — if the bar ever gains a row, change this line only. */
  --bar:67px;
  --shadow:0 8px 28px rgba(0,0,0,.45);
  --font:"Segoe UI",Roboto,-apple-system,"Helvetica Neue",Arial,sans-serif;
}
/* ---------------------------------------------------------------------------
   `hidden` MUST ACTUALLY HIDE.

   The HTML `hidden` attribute is not a CSS feature — it is a rule in the
   browser's OWN stylesheet, `[hidden] { display: none }`, at user-agent origin.
   In the cascade every author declaration beats every user-agent one, whatever
   the specificity, so any rule in THIS file that sets `display` on an element
   silently cancels it. `.topbar nav a{display:inline-flex}` and
   `.btn{display:inline-flex}` did exactly that: every control the application
   hides by setting el.hidden — the Companies and Test System links, the New
   company button, the Sys Admin badge — was styled straight back into view, on
   every browser, for every account.

   Nothing caught it. The pages set the property correctly, the property read
   back as true, and the jsdom suites asserting el.hidden === true all passed:
   jsdom does not model cascade origin, so the one thing that was wrong is the
   one thing it cannot see. In a browser the link was simply there.

   !important on purpose. `hidden` means "not relevant", which is a statement
   about the document and not a layout suggestion; no styling rule has any
   business overruling it. This also has to sit ABOVE the rules it protects, and
   apply to everything rather than to the elements somebody remembered — a
   per-element patch is what was here before, one line for one button, which
   left every other hidden control still visible.
   --------------------------------------------------------------------------- */
[hidden]{display:none !important}

/* Tell the browser this is a dark UI, so the controls it draws ITSELF follow.
   Without it the native date picker, the <select> dropdown list, scrollbars and
   the form autofill highlight all render in the light theme — on Android the
   date field the appraisal form requires opens as dark text on a white sheet in
   the middle of a dark page. Nothing in a stylesheet can reach inside those;
   this one declaration is the only control over them. */
:root{color-scheme:dark}

*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:var(--bg); color:var(--ink); font-family:var(--font);
  font-size:16px; line-height:1.5; -webkit-text-size-adjust:100%;
}
a{color:var(--brand2);text-decoration:none}
a:hover{text-decoration:underline}
h1,h2,h3,h4{margin:0 0 .5em;font-weight:600;letter-spacing:.2px}
h1{font-size:1.7rem} h2{font-size:1.3rem} h3{font-size:1.05rem}
hr{border:0;border-top:1px solid var(--line);margin:1.5rem 0}

/* ---------- App chrome ---------- */
.topbar{
  display:flex;align-items:center;gap:14px;
  padding:10px 18px;background:linear-gradient(180deg,#1a212b,#141920);
  border-bottom:1px solid var(--line);position:sticky;top:0;z-index:50;
}
/* The top bar is position:sticky, so an in-page anchor scrolls its target
   underneath it — help.html#vin from the dashboard tile landed with the
   heading hidden. scroll-margin-top reserves the bar's height plus a little
   breathing room for every element an anchor can target. */
:target{scroll-margin-top:calc(var(--bar) + 10px)}
h1[id],h2[id],h3[id],section[id],div[id]{scroll-margin-top:calc(var(--bar) + 10px)}
.topbar .brand{display:flex;align-items:center;gap:10px;font-weight:600;font-size:1.05rem;color:var(--ink)}
/* The name over the identity. align-items:center on .brand keeps the mark
   centred against the pair rather than against the first line. */
.topbar .brand-stack{display:flex;flex-direction:column;align-items:flex-start;gap:1px;min-width:0}
.topbar .brand:hover{text-decoration:none}
.topbar .brand .mark{
  width:30px;height:30px;flex:none;
  /* A tyre and five-spoke rim rather than a plain amber disc. Same wheel the
     hero car is drawn with, flattened for 30 pixels: the spoke windows are
     drawn dark rather than cut out, so it reads on any background it lands on
     and does not depend on what is behind it. */
  background:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2064%2064'%3E%3Cdefs%3E%3CradialGradient%20id='r'%20cx='36%25'%20cy='30%25'%3E%3Cstop%20offset='0%25'%20stop-color='%23fff6d8'/%3E%3Cstop%20offset='55%25'%20stop-color='%23e0ae40'/%3E%3Cstop%20offset='100%25'%20stop-color='%238d6716'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle%20cx='32'%20cy='32'%20r='31'%20fill='%2312161d'/%3E%3Ccircle%20cx='32'%20cy='32'%20r='25.5'%20fill='%23242c38'/%3E%3Ccircle%20cx='32'%20cy='32'%20r='22'%20fill='url%28%23r%29'/%3E%3Ccircle%20cx='32.0'%20cy='19.6'%20r='6.2'%20fill='%230d1117'/%3E%3Ccircle%20cx='43.8'%20cy='28.2'%20r='6.2'%20fill='%230d1117'/%3E%3Ccircle%20cx='39.3'%20cy='42.0'%20r='6.2'%20fill='%230d1117'/%3E%3Ccircle%20cx='24.7'%20cy='42.0'%20r='6.2'%20fill='%230d1117'/%3E%3Ccircle%20cx='20.2'%20cy='28.2'%20r='6.2'%20fill='%230d1117'/%3E%3Ccircle%20cx='32'%20cy='32'%20r='6'%20fill='%230d1117'/%3E%3Ccircle%20cx='32'%20cy='32'%20r='2.4'%20fill='%23e0ae40'/%3E%3C/svg%3E") no-repeat center/contain;
  filter:drop-shadow(0 0 7px rgba(240,192,90,.4));
}
.topbar nav{display:flex;gap:4px;margin-left:auto;flex-wrap:wrap}
.topbar nav a{
  color:var(--ink2);padding:9px 14px;border-radius:8px;font-size:.92rem;
  min-height:var(--tap);display:inline-flex;align-items:center;
}
.topbar nav a:hover{background:var(--panel);color:var(--ink);text-decoration:none}
.topbar nav a.active{background:var(--panel2);color:var(--brand2)}

.wrap{max-width:1240px;margin:0 auto;padding:22px 18px 80px}
.wrap.narrow{max-width:900px}

.card{
  background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius);padding:18px;margin-bottom:18px;
}
.card > h2:first-child,.card > h3:first-child{margin-top:0}
.card-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:14px}
.card-head h2,.card-head h3{margin:0}
.card-head .spacer{margin-left:auto}

/* ---------- The car mark beside a page name ---------- */
/*
 * A small fastback to the right of every page heading.
 *
 * CSS RATHER THAN MARKUP, and that is the whole reason it works. The headings
 * are not all in the HTML: appraisal.js builds "New Appraisal" and
 * "<Type> Appraisal" at run time, so anything that injected a node once at page
 * load would decorate eight pages and quietly miss the ninth. A ::after is
 * applied by the selector whenever the element exists, however late it appears,
 * and there is no per-page copy to drift.
 *
 * IT IS THE HERO CAR'S OUTLINE, FLATTENED — and it is GENERATED from that
 * outline rather than drawn beside it. assets/js/car.js renders a 1200x430
 * fastback with five gradients, and at twelve pixels that is a smudge: the
 * gradients band, the chrome disappears and the glass reads as a dent. So this
 * is the same silhouette with the shading thrown away.
 *
 * The path below comes out of the hero's own body path, scaled once by a
 * script. Drawn by hand it drifts into being a different car, which is exactly
 * what happened before: this glyph was a chunky 2.46:1 saloon while the hero
 * was a 4:1 fastback, and nobody noticed because the two are never on screen at
 * the same size. The aspect ratio here is the hero's, to three figures.
 *
 * ONE PATH, NONZERO WINDING. The tyres are traced the same way round as the
 * body so they ADD to it; the wheel openings and the window are traced
 * backwards so they cut. It has to be one shape because it is used as a mask,
 * and a mask reads one element's alpha — wheels drawn as separate circles on
 * top would simply not be in it.
 *
 * NOT TALLER THAN THE ROW. The height is .72em, which sits inside the heading's
 * own line box — a heading's line-height is never below about 1.1 — so the mark
 * cannot make the row taller than the text already made it. em rather than px
 * so it tracks each heading's size: the h1 pages and app.html's smaller
 * h2.page-title both get a mark in proportion to their own text.
 *
 * Decorative, so it is a background on an empty ::after rather than an <img> or
 * an inline <svg>: nothing for a screen reader to announce, and no aria-hidden
 * to remember.
 */
.card-head h1::after,
.card-head h2.page-title::after,
.wrap > h1::after,
.g-head > h1::after,
.card > h1::after{
  content:"";
  display:inline-block;
  height:.72em;
  /* 4.016:1 — the hero car's own aspect ratio, not a rounded guess. A mark
     that is the same car at a different proportion is a different car. */
  width:2.89em;
  margin-left:.42em;
  vertical-align:baseline;
  /* MASKED, NOT PAINTED, so the car follows the company's accent. A data: URI
     cannot contain a var(), so the fill inside this SVG could never be a firm's
     colour — the mark stayed amber on a navy page, on every heading of every
     screen. As a mask only its ALPHA is read and the colour comes from
     background-color, which is a token. -webkit-mask as well: Safari, and
     therefore the iPad, still needs it. */
  background-color:var(--brand2);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 24.9'%3E%3Cpath fill='%23000' d='M0.8 17.5 L0.8 13.8 C1.1 11.7 2.9 10.5 7 9.9 L22.8 8.6 L34.6 7.4 L41.2 2.9 C45.1 1.2 54.9 0.6 62.8 1.2 C72.2 1.9 81.7 5.1 89.9 9.1 L96.3 12.1 C98.1 12.8 98.6 14.2 98.4 16.1 L98.1 17.7 Z M12.6 17.3A7.2 7.2 0 1 1 27 17.3A7.2 7.2 0 1 1 12.6 17.3Z M73.9 17.3A7.2 7.2 0 1 1 88.3 17.3A7.2 7.2 0 1 1 73.9 17.3Z M16.5 17.3A3.3 3.3 0 1 0 23.2 17.3A3.3 3.3 0 1 0 16.5 17.3Z M77.8 17.3A3.3 3.3 0 1 0 84.4 17.3A3.3 3.3 0 1 0 77.8 17.3Z M18.6 17.3A1.3 1.3 0 1 1 21.1 17.3A1.3 1.3 0 1 1 18.6 17.3Z M79.9 17.3A1.3 1.3 0 1 1 82.4 17.3A1.3 1.3 0 1 1 79.9 17.3Z M44 3.7 L45.7 5.8 L60.7 5.8 L80.7 7.4 C74.9 4.9 67.5 2.4 59.9 1.8 C53.7 1.5 47.7 1.9 44 3.7 Z'/%3E%3C/svg%3E") no-repeat center/contain;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 24.9'%3E%3Cpath fill='%23000' d='M0.8 17.5 L0.8 13.8 C1.1 11.7 2.9 10.5 7 9.9 L22.8 8.6 L34.6 7.4 L41.2 2.9 C45.1 1.2 54.9 0.6 62.8 1.2 C72.2 1.9 81.7 5.1 89.9 9.1 L96.3 12.1 C98.1 12.8 98.6 14.2 98.4 16.1 L98.1 17.7 Z M12.6 17.3A7.2 7.2 0 1 1 27 17.3A7.2 7.2 0 1 1 12.6 17.3Z M73.9 17.3A7.2 7.2 0 1 1 88.3 17.3A7.2 7.2 0 1 1 73.9 17.3Z M16.5 17.3A3.3 3.3 0 1 0 23.2 17.3A3.3 3.3 0 1 0 16.5 17.3Z M77.8 17.3A3.3 3.3 0 1 0 84.4 17.3A3.3 3.3 0 1 0 77.8 17.3Z M18.6 17.3A1.3 1.3 0 1 1 21.1 17.3A1.3 1.3 0 1 1 18.6 17.3Z M79.9 17.3A1.3 1.3 0 1 1 82.4 17.3A1.3 1.3 0 1 1 79.9 17.3Z M44 3.7 L45.7 5.8 L60.7 5.8 L80.7 7.4 C74.9 4.9 67.5 2.4 59.9 1.8 C53.7 1.5 47.7 1.9 44 3.7 Z'/%3E%3C/svg%3E") no-repeat center/contain;
  /* A flex row would otherwise let it shrink; it is already tiny. */
  flex:none;
}


/*
 * app.html's hero already carries the full car immediately below its <h1>, so
 * that heading is deliberately not in the selector list above — two cars in
 * four inches of screen reads as a mistake rather than as branding.
 */
.muted{color:var(--ink3)}
.small{font-size:.85rem}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:7px;
  min-height:var(--tap);padding:10px 16px;border-radius:8px;cursor:pointer;
  border:1px solid var(--line2);background:var(--panel2);color:var(--ink);
  font:inherit;font-size:.92rem;font-weight:500;white-space:nowrap;
  transition:background .12s,border-color .12s,transform .06s;
}
.btn:hover{background:#2b3543;border-color:#4d5c72;text-decoration:none}
.btn:active{transform:translateY(1px)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn-primary{background:linear-gradient(180deg,var(--brand-hi),var(--brand-dark));border-color:var(--brand-edge);color:var(--brand-ink);font-weight:600}
.btn-primary:hover{background:linear-gradient(180deg,var(--brand-hi2),var(--brand-dark2))}
.btn-accent{background:linear-gradient(180deg,#4c9fdb,#2e79ad);border-color:#2a6d9c;color:#04121d;font-weight:600}
.btn-accent:hover{background:linear-gradient(180deg,#5cb0ec,#3689bd)}
.btn-danger{background:#3a2224;border-color:#6d3a38;color:#f0b3ad}
.btn-danger:hover{background:#4d2b2d;border-color:#8a4844}
.btn-ghost{background:transparent;border-color:var(--line)}
.btn-sm{min-height:36px;padding:6px 11px;font-size:.84rem;border-radius:7px}
.btn-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}

/* ---------- Forms ---------- */
label{display:block;font-size:.84rem;color:var(--ink2);margin-bottom:5px;font-weight:500}
input[type=text],input[type=email],input[type=password],input[type=number],
input[type=date],input[type=search],input[type=url],input[type=tel],select,textarea{
  width:100%;min-height:var(--tap);padding:10px 12px;
  background:var(--bg2);border:1px solid var(--line2);border-radius:8px;
  color:var(--ink);font:inherit;font-size:.95rem;
}
textarea{min-height:96px;resize:vertical;line-height:1.55}
input:focus,select:focus,textarea:focus{
  outline:none;border-color:var(--brand);box-shadow:0 0 0 3px rgba(var(--brand-rgb),.18)
}
input::placeholder,textarea::placeholder{color:#5c6a7d}
select{appearance:none;padding-right:34px;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8'><path d='M1 1l5 5 5-5' stroke='%23a7b4c6' stroke-width='2' fill='none'/></svg>");
  background-repeat:no-repeat;background-position:right 12px center}
.field{margin-bottom:14px}
.grid{display:grid;gap:14px}
.grid.c2{grid-template-columns:repeat(2,1fr)}
.grid.c3{grid-template-columns:repeat(3,1fr)}
.grid.c4{grid-template-columns:repeat(4,1fr)}
@media(max-width:900px){.grid.c3,.grid.c4{grid-template-columns:repeat(2,1fr)}}
@media(max-width:620px){.grid.c2,.grid.c3,.grid.c4{grid-template-columns:1fr}}
.req::after{content:" *";color:var(--brand)}
.hint{font-size:.78rem;color:var(--ink3);margin-top:4px}
.err{border-color:var(--bad) !important}
.err-msg{font-size:.78rem;color:#eda49d;margin-top:4px}

.check{
  display:flex;align-items:flex-start;gap:9px;padding:8px 10px;border-radius:7px;
  cursor:pointer;min-height:40px;color:var(--ink);font-size:.9rem;line-height:1.35;
}
.check:hover{background:var(--panel2)}
.check input{width:20px;height:20px;flex:none;margin:1px 0 0;accent-color:var(--brand)}
.check span{padding-top:1px}

/* ---------- Equipment lines ----------
   A ticked line can be reworded in place, because the custom templates offer
   choices inside one line ("Automatic/Five speed manual transmission") and the
   line prints exactly as it reads. The pencil appears once the line is ticked. */
.check-row{display:flex;align-items:flex-start;gap:2px;border-radius:7px}
.check-row .check{flex:1;min-width:0}
.check-edit{
  flex:none;visibility:hidden;align-self:center;background:none;border:0;cursor:pointer;
  color:var(--ink2);font-size:.95rem;line-height:1;padding:8px 8px;border-radius:6px;
}
.check-row.is-on .check-edit{visibility:visible}
.check-row .check-edit:hover,.check-row .check-edit:focus-visible{background:var(--panel2);color:var(--ink)}
.check-row.is-refined > .check span{border-bottom:1px dotted var(--line)}
.check-row.needs-choice{background:rgba(217,151,58,.10);box-shadow:inset 2px 0 0 rgba(217,151,58,.65)}
.check-row.needs-choice .check-edit{visibility:visible;color:#e8bd76}
.check-editing{display:flex;align-items:center;gap:6px;flex:1;min-width:0;padding:4px 0 4px 10px}
.check-editing input[type=text]{
  padding:7px 9px;border:1px solid var(--brand);border-radius:6px;
  background:var(--panel2);color:var(--ink);font:inherit;font-size:.9rem;
}

/* ---------- Tables ---------- */
.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;border-radius:var(--radius)}
table.data{width:100%;border-collapse:collapse;font-size:.9rem;min-width:900px}
table.data th,table.data td{padding:10px 12px;border-bottom:1px solid var(--line);text-align:left;vertical-align:middle}
table.data th{
  background:var(--panel2);color:var(--ink2);font-weight:600;font-size:.78rem;
  text-transform:uppercase;letter-spacing:.6px;white-space:nowrap;cursor:pointer;user-select:none;
}
table.data th.nosort{cursor:default}
table.data th .arrow{opacity:.55;font-size:.7rem;margin-left:3px}
table.data tbody tr:hover{background:#1f2733}
table.data td.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
table.data td.actions{white-space:nowrap}
table.data .vin{font-family:ui-monospace,Consolas,monospace;font-size:.82rem;letter-spacing:.4px}

/* ---------------------------------------------------------------------------
   THE USERS GRID, MEASURED.

   Nine columns of wildly different natural widths — a middle initial beside an
   email address beside a company name — and a table left to itself gives the
   long ones whatever they ask for and pushes the buttons off the end. So the
   long fields are capped and allowed to WRAP, and the row is tall enough for
   two lines whether it needs them or not.

   TWO LINES ALWAYS, not only when something is long. A grid where some rows
   are one line and some are two has no rhythm to run your eye down, and the
   height jumps as you edit. A fixed floor costs a little vertical space and
   makes the whole list scannable.

   `height` on a table row is a MINIMUM, not a size — that is how table layout
   has always worked, and it is the reason this is one line rather than a
   wrapper div in every cell.
   --------------------------------------------------------------------------- */
#userTableHost table.data{min-width:0}
#userTableHost table.data tbody tr{height:60px}
#userTableHost table.data td{vertical-align:middle}

/* The long ones: capped, and allowed to break inside a word. An email address
   has no spaces to break at, so `anywhere` is the only thing that wraps one.
   
   THE CAP IS ON A BLOCK INSIDE THE CELL. max-width on a <td> is advisory —
   auto table layout gives a column whatever its content asks for and ignores
   the declaration — so capping the cells looked right and changed nothing. */
#userTableHost table.data .u-email-wrap{
  display:flex;gap:6px;align-items:center;flex-wrap:wrap;max-width:30ch;
  /* 30ch, not 24. A typical address wraps to two lines either way, but at
     24 both lines are full and the You / Test badge is pushed onto a third
     — one row taller than its neighbours for a decoration. At 30 the second
     line has room for the badge beside it. */
}
#userTableHost table.data .u-addr{overflow-wrap:anywhere;min-width:0}
#userTableHost table.data .u-cap{overflow-wrap:anywhere}
#userTableHost table.data td.u-company .u-cap{max-width:15ch}
#userTableHost table.data td.u-name .u-cap{max-width:11ch}

/* The short ones: as narrow as their content, so the space goes to the fields
   that need it. `width:1%` on a table cell means "as little as possible". */
#userTableHost table.data td.u-mid,
#userTableHost table.data td.u-mail,
#userTableHost table.data td.u-role{width:1%;white-space:nowrap}

/* A ten-digit number is ONE thing. Left alone it breaks after the area code
   and reads as two numbers stacked on top of each other. */
#userTableHost table.data td.u-phone{
  width:1%;white-space:nowrap;font-variant-numeric:tabular-nums;
}

/* And the buttons fit: the column takes exactly what they need and never
   wraps, so Edit and Login stay side by side instead of stacking. */
#userTableHost table.data td.actions{width:1%;white-space:nowrap}
#userTableHost table.data td.actions .btn-row{flex-wrap:nowrap}

@media (max-width:900px){
  /* Narrow enough that capping the long fields stops helping — let the table
     scroll sideways instead of squeezing an address into four characters. */
  #userTableHost table.data{min-width:820px}
}

/* condition rating grid */
table.cond{width:100%;border-collapse:collapse;font-size:.87rem}
table.cond th,table.cond td{border:1px solid var(--line);padding:7px 8px;text-align:center}
table.cond th{background:var(--panel2);color:var(--ink2);font-size:.72rem;text-transform:uppercase;letter-spacing:.5px}
table.cond td:first-child,table.cond th:first-child{text-align:left;white-space:nowrap}
table.cond input[type=radio]{width:19px;height:19px;accent-color:var(--brand);margin:0;cursor:pointer}
table.cond tbody tr:hover{background:#1f2733}

/* ---------- Misc ---------- */
.badge{
  display:inline-block;padding:3px 9px;border-radius:20px;font-size:.74rem;
  font-weight:600;letter-spacing:.4px;background:var(--panel2);color:var(--ink2);
  border:1px solid var(--line2);white-space:nowrap;
}
.badge.ok{background:rgba(63,164,106,.16);color:#79d3a2;border-color:rgba(63,164,106,.4)}
.badge.warn{background:rgba(217,151,58,.16);color:#e8bd76;border-color:rgba(217,151,58,.4)}
.badge.bad{background:rgba(208,85,75,.16);color:#eda49d;border-color:rgba(208,85,75,.4)}
.badge.info{background:rgba(78,163,224,.16);color:#8fcaf0;border-color:rgba(78,163,224,.4)}

.thumbs{display:flex;gap:6px;flex-wrap:wrap}
.thumb{
  width:52px;height:38px;border-radius:5px;object-fit:cover;
  border:1px solid var(--line2);background:var(--bg2);cursor:pointer;display:block;
}
.thumb-lg{width:124px;height:92px}
.thumb-wrap{position:relative;display:inline-block}
.thumb-wrap .x{
  position:absolute;top:-7px;right:-7px;width:22px;height:22px;border-radius:50%;
  background:#8a3b34;color:#fff;border:1px solid #b45047;font-size:13px;line-height:1;
  cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;
}
/* Move a photograph to another shot. Sits opposite the delete button, and is
   the only way an imported report's pictures reach a named slot. Same 22px
   target as the ×, which is small for a finger but matches what is already
   there — worth revisiting together rather than making one of them odd. */
.thumb-wrap .move{
  position:absolute;top:-7px;left:-7px;width:22px;height:22px;border-radius:50%;
  background:#2f4256;color:#dce6f2;border:1px solid #46617d;font-size:12px;line-height:1;
  cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;
}
.thumb-wrap .move:hover{background:var(--brand);color:var(--brand-ink);border-color:var(--brand)}

.more-count{
  width:52px;height:38px;border-radius:5px;border:1px solid var(--line2);
  background:var(--bg2);color:var(--ink3);font-size:.75rem;
  display:flex;align-items:center;justify-content:center;
}
.empty{padding:44px 20px;text-align:center;color:var(--ink3)}
.empty .big{font-size:2.4rem;opacity:.35;margin-bottom:10px}

.toolbar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:14px}
.toolbar .grow{flex:1 1 220px;min-width:170px}

.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:18px}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:13px 15px}
.stat .k{font-size:.74rem;text-transform:uppercase;letter-spacing:.7px;color:var(--ink3)}
.stat .v{font-size:1.45rem;font-weight:600;margin-top:3px;color:var(--brand2);font-variant-numeric:tabular-nums}

/* ---------- Modal ---------- */
.modal-back{
  position:fixed;inset:0;background:rgba(6,9,13,.78);
  display:flex;align-items:flex-start;justify-content:center;padding:28px 16px;
  z-index:200;overflow-y:auto;
}
.modal{
  background:var(--panel);border:1px solid var(--line2);border-radius:14px;
  width:100%;max-width:760px;box-shadow:var(--shadow);margin:auto;
}
.modal.wide{max-width:1040px}
.modal header{
  display:flex;align-items:center;gap:12px;padding:16px 20px;
  border-bottom:1px solid var(--line);
}
.modal header h2{margin:0;font-size:1.15rem}
.modal header .x{
  margin-left:auto;background:none;border:0;color:var(--ink3);font-size:1.6rem;
  cursor:pointer;line-height:1;padding:0 4px;
}
.modal header .x:hover{color:var(--ink)}
/* On iOS 100vh is the toolbars-HIDDEN height — 745px on an iPhone 14, against
   659px actually visible. The body was therefore sized 86px taller than the
   screen and the footer holding Save and Cancel sat below the fold, with no
   gesture available to reach it: .modal-back is position:fixed at exactly
   viewport height, so it never overflows and never scrolls. The vh line stays
   first as the fallback; dvh wins wherever it is understood. */
.modal .body{
  padding:20px;
  max-height:calc(100vh - 210px);
  max-height:calc(100dvh - 210px);
  overflow-y:auto;
}
.modal footer{
  display:flex;gap:10px;justify-content:flex-end;padding:14px 20px;
  border-top:1px solid var(--line);flex-wrap:wrap;
}
/* A footer button pushed to the far end, away from the one somebody is
   reaching for. Delete lives inside Edit User now rather than on every row of
   the grid, and a destructive button sitting next to Save is how it gets
   pressed by the hand rather than by the person. */
.modal footer .btn-left{margin-right:auto}

/* toast */
#toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);z-index:400;display:flex;flex-direction:column;gap:8px;align-items:center}
.toast{
  background:var(--panel2);border:1px solid var(--line2);color:var(--ink);
  padding:11px 18px;border-radius:9px;box-shadow:var(--shadow);font-size:.9rem;
  animation:tin .2s ease-out;
}
.toast.ok{border-color:rgba(63,164,106,.6)} .toast.bad{border-color:rgba(208,85,75,.6)}
@keyframes tin{from{opacity:0;transform:translateY(10px)}}

.dropzone{
  border:2px dashed var(--line2);border-radius:10px;padding:22px;text-align:center;
  color:var(--ink3);cursor:pointer;transition:.15s;
}
.dropzone:hover,.dropzone.over{border-color:var(--brand);color:var(--ink2);background:rgba(var(--brand-rgb),.06)}

.tabs{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:16px;overflow-x:auto}
.tabs button{
  background:none;border:0;border-bottom:2px solid transparent;color:var(--ink3);
  padding:11px 15px;cursor:pointer;font:inherit;font-size:.9rem;white-space:nowrap;min-height:var(--tap);
}
.tabs button.active{color:var(--brand2);border-bottom-color:var(--brand)}
.tabs button:hover{color:var(--ink)}

.mode-pill{display:inline-flex;border:1px solid var(--line2);border-radius:8px;overflow:hidden}
.mode-pill button{
  background:var(--bg2);border:0;color:var(--ink2);padding:9px 15px;cursor:pointer;
  font:inherit;font-size:.87rem;min-height:var(--tap);
}
.mode-pill button.active{background:var(--brand);color:var(--brand-ink);font-weight:600}

.pager{display:flex;gap:8px;align-items:center;justify-content:flex-end;margin-top:14px;flex-wrap:wrap}

@media print{
  .topbar,.toolbar,.btn,.no-print,.pager{display:none !important}
  body{background:#fff;color:#000}
  .card{border-color:#ccc;background:#fff;break-inside:avoid}
}

/* ---------- Photo bin & selectable thumbnails ---------- */
.bin-panel{
  background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
  margin-bottom:18px;overflow:hidden;
}
.bin-head{
  display:flex;align-items:center;gap:11px;padding:13px 16px;cursor:pointer;
  background:var(--panel2);border-bottom:1px solid var(--line);user-select:none;
}
.bin-head:hover{background:#273140}
.bin-head h2{margin:0;font-size:1.02rem}
.bin-head .caret{color:var(--ink3);font-size:.85rem;width:12px}
/*
 * COLLAPSED MEANS SMALL, not merely bodyless.
 *
 * Hiding the body alone still left two full-height panels stacked above the
 * vehicle list — a heading, a badge and a row of padding each, for two things
 * that were usually empty. Closed, a bin is one line: tighter padding, a
 * smaller heading, and no bottom margin doubling up with the next panel's top.
 * !important on display because [hidden] and author rules have fought over
 * this file before; the rest is ordinary specificity.
 */
.bin-panel.closed .bin-body{display:none}
.bin-panel.closed .bin-head{border-bottom:0;padding:7px 12px}
.bin-panel.closed .bin-head h2{font-size:.92rem}
.bin-panel.closed{margin-bottom:8px}
.bin-panel.closed + .bin-panel.closed{margin-top:-2px}
.bin-body{padding:16px}
.bin-drop{margin-bottom:14px}

.pgrid{
  display:flex;flex-wrap:wrap;gap:10px;min-height:64px;padding:8px;
  border:1px dashed transparent;border-radius:9px;transition:border-color .15s,background .15s;
}
.pgrid.drop-over{border-color:var(--brand);background:rgba(var(--brand-rgb),.07)}

.pthumb{
  position:relative;width:104px;height:78px;border-radius:7px;overflow:hidden;
  border:2px solid var(--line2);cursor:pointer;background:var(--bg2);flex:none;
  transition:border-color .12s,transform .08s,box-shadow .12s;
}
.pthumb img{width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}
.pthumb:hover{border-color:#5d6d84;transform:translateY(-1px)}
.pthumb.sel{border-color:var(--brand);box-shadow:0 0 0 3px rgba(var(--brand-rgb),.28)}
.pthumb.dragging{opacity:.4}
.pthumb .tick{
  position:absolute;top:4px;left:4px;width:20px;height:20px;border-radius:50%;
  background:var(--brand);color:var(--brand-ink);font-size:12px;font-weight:700;
  display:none;align-items:center;justify-content:center;line-height:1;
}


/* A clip's thumbnail is its poster frame and is otherwise indistinguishable
   from a photograph — the badge is the only thing saying there is something to
   play. Bottom-left, away from the selection tick and the zoom corner. */
.pthumb .vbadge{position:absolute;left:6px;bottom:6px;z-index:2;
  background:rgba(0,0,0,.66);color:#fff;border-radius:50%;
  width:22px;height:22px;display:flex;align-items:center;justify-content:center;
  font-size:10px;line-height:1;padding-left:2px;pointer-events:none}.pthumb.sel .tick{display:flex}
.pthumb .zoom{
  position:absolute;bottom:3px;right:3px;width:22px;height:22px;padding:0;
  border-radius:5px;border:0;background:rgba(6,9,13,.72);color:#e8edf4;
  font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;
  opacity:0;transition:opacity .12s;
}
.pthumb:hover .zoom{opacity:1}
@media(hover:none){.pthumb .zoom{opacity:.85}}

/* row-level photo cell */
.row-photos{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:190px}
.row-photos .pthumb{width:52px;height:40px;border-width:2px}
.row-photos .pthumb .tick{width:15px;height:15px;font-size:9px;top:2px;left:2px}
.row-photos .pthumb .zoom{display:none}
.row-photos.drop-over{outline:2px dashed var(--brand);outline-offset:3px;border-radius:6px}
.place-btn{
  border:1px dashed var(--brand);background:rgba(var(--brand-rgb),.12);color:var(--brand2);
  border-radius:6px;padding:5px 10px;font-size:.76rem;font-weight:600;cursor:pointer;
  min-height:34px;white-space:nowrap;font-family:inherit;
}
.place-btn:hover{background:rgba(var(--brand-rgb),.24)}

/* floating selection tray */
.photo-tray{
  position:fixed;left:50%;bottom:20px;transform:translateX(-50%);z-index:300;
  display:flex;align-items:center;gap:9px;flex-wrap:wrap;justify-content:center;
  background:var(--panel2);border:1px solid var(--line2);border-radius:12px;
  padding:11px 15px;box-shadow:0 14px 40px rgba(0,0,0,.55);max-width:min(94vw,860px);
  animation:trayin .18s ease-out;
}
@keyframes trayin{from{opacity:0;transform:translate(-50%,12px)}}
.photo-tray .count{font-weight:600;font-size:.9rem;color:var(--brand2);white-space:nowrap}
.photo-tray .hint-inline{font-size:.82rem;color:var(--ink3);white-space:nowrap}
@media(max-width:620px){
  .photo-tray{left:10px;right:10px;transform:none;max-width:none}
  @keyframes trayin{from{opacity:0;transform:translateY(12px)}}
  .photo-tray .hint-inline{display:none}
}

/* ---------- Sortable photo strip (per vehicle) ---------- */
.psort{display:flex;flex-wrap:wrap;gap:10px}
.psort-item{
  position:relative;margin:0;width:132px;border-radius:8px;overflow:hidden;
  border:1px solid var(--line2);background:var(--bg2);
  transition:box-shadow .12s,transform .08s;
}
.psort-item img{width:100%;height:96px;object-fit:cover;display:block}
.psort-item.lifted{
  box-shadow:0 10px 26px rgba(0,0,0,.6);transform:scale(1.04);
  border-color:var(--brand);z-index:5;
}
.psort.sorting .psort-item:not(.lifted){opacity:.72}

.psort-item .cover-tag{
  position:absolute;top:5px;left:5px;background:var(--brand);color:var(--brand-ink);
  font-size:.63rem;font-weight:700;letter-spacing:.5px;text-transform:uppercase;
  padding:2px 7px;border-radius:4px;
}
.psort-item > .x{
  position:absolute;top:4px;right:4px;width:22px;height:22px;border-radius:50%;
  background:rgba(138,59,52,.92);color:#fff;border:1px solid #b45047;
  font-size:13px;line-height:1;cursor:pointer;padding:0;
  display:flex;align-items:center;justify-content:center;
}
.psort-item > .x:hover{background:#a5453c}

.psort-bar{
  display:flex;align-items:center;justify-content:space-between;
  padding:3px 4px;background:var(--panel2);border-top:1px solid var(--line);
}
.psort-bar .nudge{
  width:30px;height:30px;padding:0;border:0;border-radius:5px;cursor:pointer;
  background:transparent;color:var(--ink2);font-size:1.05rem;line-height:1;
  font-family:inherit;
}
.psort-bar .nudge:hover:not(:disabled){background:var(--line);color:var(--ink)}
.psort-bar .nudge:disabled{opacity:.25;cursor:default}
.psort-bar .grip{
  flex:1;text-align:center;color:var(--ink3);cursor:grab;font-size:1.05rem;
  line-height:1;padding:6px 0;user-select:none;
  /* the browser must not steal the gesture for scrolling mid-drag */
  touch-action:none;
}
.psort-bar .grip:active{cursor:grabbing;color:var(--brand2)}
.psort-item.lifted .grip{color:var(--brand2)}

/* ---------- Cover thumbnails beside a name ---------- */
.cover-thumb{
  width:56px;height:42px;border-radius:6px;object-fit:cover;flex:none;
  border:1px solid var(--line2);background:var(--bg2);display:block;
}
.cover-thumb.empty{
  display:flex;align-items:center;justify-content:center;
  font-size:1.05rem;opacity:.4;
}

.picker-row{
  width:100%;justify-content:flex-start;margin-bottom:6px;gap:11px;
  text-align:left;padding:8px 11px;height:auto;
}
.picker-name{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}
.picker-name > span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.picker-sub{font-size:.78rem;color:var(--ink3);font-weight:400}
.picker-row .badge{margin-left:auto;flex:none}

/* compact row density */
.photo-count{
  border:1px solid var(--line2);background:var(--bg2);color:var(--ink2);
  border-radius:6px;padding:5px 10px;font-size:.82rem;cursor:pointer;
  font-family:inherit;min-height:32px;white-space:nowrap;
}
.photo-count:hover{border-color:var(--brand);color:var(--ink)}

/* ---------- Bin sort headings + bin drag handles ---------- */
.bin-sort{
  display:flex;align-items:center;gap:4px;flex-wrap:wrap;
  margin:0 0 12px;padding-bottom:9px;border-bottom:1px solid var(--line);
}
.bin-sort-label{
  font-size:.72rem;text-transform:uppercase;letter-spacing:.8px;
  color:var(--ink3);margin-right:5px;font-weight:600;
}
.sort-head{
  background:none;border:1px solid transparent;border-radius:6px;
  color:var(--ink2);font:inherit;font-size:.82rem;padding:6px 11px;
  cursor:pointer;min-height:34px;display:inline-flex;align-items:center;gap:5px;
}
.sort-head:hover{background:var(--panel2);color:var(--ink)}
.sort-head.active{background:var(--panel2);border-color:var(--line2);color:var(--brand2);font-weight:600}
.sort-head .arrow{font-size:.62rem;opacity:.85}

.pthumb .pgrip{
  position:absolute;bottom:3px;left:3px;width:22px;height:22px;
  border-radius:5px;background:rgba(6,9,13,.72);color:#cfd8e3;
  font-size:11px;line-height:1;display:flex;align-items:center;justify-content:center;
  cursor:grab;opacity:0;transition:opacity .12s;
  /* the browser must not steal the gesture for scrolling mid-drag */
  touch-action:none;
}
.pthumb:hover .pgrip{opacity:1}
.pthumb .pgrip:active{cursor:grabbing;color:var(--brand2)}
.pthumb.lifted{
  box-shadow:0 10px 26px rgba(0,0,0,.6);transform:scale(1.06);
  border-color:var(--brand);z-index:5;
}
.pgrid.sorting .pthumb:not(.lifted){opacity:.72}
@media(hover:none){.pthumb .pgrip{opacity:.85}}

/* ---------- Sample fixtures ---------- */
/* Stamped in the interface, not merely named on disk, so a fixture can never be
   mistaken for the client's own photography. Removal: see DEPLOY.md. */
.sample-tag{
  position:absolute;bottom:3px;left:3px;z-index:2;
  background:rgba(208,85,75,.92);color:#fff;
  font-size:.52rem;font-weight:700;letter-spacing:.7px;
  padding:2px 5px;border-radius:3px;pointer-events:none;line-height:1;
}
.psort-item .sample-tag{bottom:auto;top:5px;left:5px;font-size:.56rem;padding:2px 6px}
.psort-item .cover-tag + .sample-tag{top:5px;left:auto;right:32px}

/* --- states shared by the dashboard and the data pages ------------------- */
/* Shown when a load failed or the database is unreachable. Sits above the
   figures, not beside them, because it changes what all of them mean. */
.banner{
  padding:12px 15px;border-radius:9px;margin-bottom:14px;font-size:.9rem;
  line-height:1.55;border:1px solid rgba(208,85,75,.38);
  background:rgba(208,85,75,.11);color:#eda49d;
}
.banner strong{color:#f3c0ba}
/*
 * The same shape, saying something rather than warning about it.
 *
 * `.banner` on its own is the RED one — every use of it so far has been a
 * fault: the database is unreachable, the figures could not be loaded. The
 * Enterprise notice is neither. It says which records the numbers below cover,
 * which is a fact about a correct page, and drawing it in the colour reserved
 * for outages would teach people to ignore that colour.
 *
 * It follows the accent, so a skinned company gets its own.
 */
.banner.info{
  border-color:rgba(var(--brand-rgb),.40);
  background:rgba(var(--brand-rgb),.10);
  color:var(--ink2);
}
.banner.info strong{color:var(--brand2)}
/* A figure that could not be fetched. Rendered as an em dash, never as zero —
   "0 vehicles" and "we could not ask" are different statements. */
.stat-unknown{opacity:.6}
.stat-unknown .v{color:var(--ink3)}
/* A figure describing this browser rather than the business. */
.stat-local{position:relative;opacity:.82}
.stat-local .v{color:var(--ink2)}
.stat-local::after{
  content:'this device';position:absolute;top:7px;right:9px;
  font-size:.6rem;letter-spacing:.5px;text-transform:uppercase;
  color:#eda49d;background:rgba(208,85,75,.16);padding:2px 5px;border-radius:3px;
}
.cover-thumb-none{display:flex;align-items:center;justify-content:center;
                  background:var(--panel2);color:var(--ink3);border-radius:7px}

/* ==========================================================================
   Touch devices — iPad, iPhone, Android
   --------------------------------------------------------------------------
   Last in the file deliberately: these are corrections to rules set above, and
   putting them here means they win without anyone having to reason about
   specificity. Everything is scoped so desktop is untouched.
   ========================================================================== */

/* Safari zooms the entire page when a form control smaller than 16px takes
   focus, and it does not zoom back out. The appraiser is then panned into a
   half-visible form, one-handed, at a kerbside. Nothing else on the page needs
   to change — 16px on the control is the whole fix.

   pointer:coarse rather than a width query, because an iPad in Split View is
   narrow but still touch, and a small desktop window is narrow but is not. */
@media (pointer: coarse) {
  input[type=text],input[type=email],input[type=password],input[type=number],
  input[type=url],input[type=tel],
  input[type=date],input[type=search],select,textarea{
    font-size:16px;
  }
}

/* viewport-fit=cover is on every page, which lets the layout run under the
   rounded corners, the notch and the home indicator. That is only correct if
   something then pads it back — otherwise a landscape iPhone clips the first
   and last characters of every row, and the home indicator sits on top of
   whatever is at the bottom of the page.

   max() keeps the existing 18px on any device with no inset, and the 0px
   fallback inside env() keeps the declaration valid on browsers that have
   never heard of it. */
.topbar{
  padding-left:max(18px, env(safe-area-inset-left, 0px));
  padding-right:max(18px, env(safe-area-inset-right, 0px));
}
.wrap{
  padding-left:max(18px, env(safe-area-inset-left, 0px));
  padding-right:max(18px, env(safe-area-inset-right, 0px));
  padding-bottom:max(80px, calc(56px + env(safe-area-inset-bottom, 0px)));
}

/* Touch feedback. The default is a grey box that looks like a rendering fault
   on a dark theme; this is the brand amber at low opacity, so a tap reads as
   deliberate. */
a,button,.btn,summary,[role=button],.tile,.ref-btn{
  -webkit-tap-highlight-color:rgba(240,192,90,.18);
}

/* dvh does not shrink for the on-screen keyboard, so pin the footer as well —
   otherwise typing in a long form still pushes Save out of reach. The radius
   is .modal's 14px less its 1px border. Do NOT "tidy" the corners with
   overflow:hidden on .modal: that makes .modal the sticky element's scroll
   container and silently kills the pin. */
.modal footer{
  position:sticky;bottom:0;z-index:1;
  background:var(--panel);
  border-radius:0 0 13px 13px;
}

/* ---------- max-width:900px — the top bar stops wrapping ------------------
   .topbar is a nowrap flex line: brand plus six nav links. It needs ~830px to
   stay on one row. Below that the wordmark stacks into three ragged lines and
   nav (which IS flex-wrap:wrap) breaks into 2 rows on an iPad portrait and 3-4
   on a phone — up to 27% of the screen pinned on every page, and every anchor
   offset invalidated. Six tap-sized links cannot fit 360px at any padding, so
   the row scrolls instead of stacking. */
@media (max-width:900px){
  .topbar{gap:10px}

  /* The mark is the home link and stays. The 23-character wordmark beside it is
     the 200px that forces the wrap. Clipped rather than display:none, because
     .mark has no text of its own — hiding the wordmark outright would leave the
     home link with no accessible name. */
  /* Was `span:not(.mark)`, which now also matches .brand-stack — the WRAPPER —
     and clipping a flex column to one pixel takes the identity with it whether
     or not that was intended. Named exactly, so each line is its own decision:
     the wordmark goes (it is the 200px that forces the wrap) and the identity
     stays (it is the only answer on screen to "who is signed in here"). */
  .topbar .brand .brand-words{
    position:absolute;width:1px;height:1px;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;
  }
  /*
   * UNLESS THE NAME IS THE SWITCH. Clipping the wordmark is how the bar makes
   * room below 900px, and it is right for an ordinary account. For a Sys Admin
   * the wordmark is now the ONLY way into a company, and
   * clipping it leaves them on an iPad in portrait looking at empty lists with
   * nothing on screen to explain why — which is the exact failure the old
   * dropdown carried a rule against.
   */
  .topbar .brand .brand-words.brand-pick{
    position:static;width:auto;height:auto;clip-path:none;
    max-width:11rem;overflow:hidden;text-overflow:ellipsis;
  }
  .topbar .firm{max-width:11rem}

  /* One scrolling row instead of three stacked ones. The fade is load-bearing,
     not decoration: at 360px the last links start off the right edge and it is
     the only cue that the strip scrolls. Don't hide the scrollbar as well —
     that removes the second cue. */
  .topbar nav{
    flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;
    -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 26px),transparent);
            mask-image:linear-gradient(90deg,#000 calc(100% - 26px),transparent);
  }
  .topbar nav a{flex:none;white-space:nowrap;padding:9px 11px}
}

/* ---------- the vehicle table's row actions -------------------------------
   min-width:900px on table.data is a floor, not a width. Eleven columns with a
   photo cell and an unbreakable 17-character VIN give a min-content around
   1,290px, so Actions — Appraisal, Research, Edit, Delete, the reason the page
   exists — starts off-screen in BOTH iPad orientations. Pinning the column
   keeps it reachable without hunting sideways.

   Scoped to #tableHost because table.data is shared with users.js, whose
   Actions column is not last in the same way; an unscoped rule would reach
   into users.html. */
/*
 * THE TABLE SCROLLS, NOT THE PAGE. This is the one that made the rest visible.
 *
 * Eleven columns are wider than any window, and with nothing here to contain
 * them the overflow went to the DOCUMENT: the whole page slid sideways, so the
 * top bar, the sign-out link and the right-hand end of the stat row went off
 * screen together. It reads as the application being broken rather than a
 * table being wide, and the pinned Actions column below could not work at all,
 * because `position:sticky` needs a scrolling ancestor and the page was it.
 */
#tableHost{overflow-x:auto;overflow-y:visible}

/*
 * AND IT HAS TO LOOK LIKE AN OVERLAY, or it looks like a broken column.
 *
 * The pinned cell is opaque and sits ON TOP of whatever is underneath, so at
 * rest it clips its neighbour part-way through a word. On the Vehicle DB the
 * header row read "VIN | C | ACTIONS" — the "C" being all that showed of
 * "Comp Low". Nothing was wrong and everything was reachable by scrolling, but
 * a heading truncated to one letter reads as a rendering fault, and somebody
 * seeing it reports a bug rather than scrolling.
 *
 * The soft shadow alone was not enough: -11px 0 13px -9px is a gradient with no
 * edge, and against a dark panel it is nearly invisible. A hard 1px rule is
 * what says "this is in front of something" at a glance; the shadow stays,
 * because the two do different jobs — the line marks the boundary, the shadow
 * gives it depth so it does not read as just another column divider.
 */
#tableHost table.data th:last-child,
#tableHost table.data td.actions{
  position:sticky;right:0;z-index:2;
  background:var(--panel);
  border-left:1px solid var(--line2);
  box-shadow:-11px 0 15px -7px rgba(0,0,0,.75);
}
#tableHost table.data th:last-child{background:var(--panel2);z-index:3}
#tableHost table.data tbody tr:hover td.actions{background:#1f2733}

/*
 * FOUR BUTTONS ON ONE LINE, ALWAYS.
 *
 * .btn-row wraps by default, which is right everywhere else on the site and
 * wrong in a table cell: the cell is as narrow as the layout can make it, so
 * the four buttons stacked into a column and every row grew to four button
 * heights. A list of sixteen cars became a page and a half of buttons.
 *
 * td.actions already had white-space:nowrap, which governs TEXT and says
 * nothing about a flex container's wrapping — the two look like the same rule
 * and are not.
 */
#tableHost table.data td.actions .btn-row{flex-wrap:nowrap}

/*
 * A FLOOR UNDER THE SQUEEZABLE COLUMNS, so the table degrades by scrolling
 * rather than by crushing one column to nothing.
 *
 * Notes is the only cell with no nowrap and no natural minimum, so it is where
 * every other column's greed is paid for: at eleven columns it was handed
 * about a character and a half of width and wrapped one letter per line down
 * the row. A minimum means the overflow shows up as a scrollbar — which is
 * legible, and which the pinned Actions column is built for.
 */
#tableHost table.data td.notes{min-width:200px;max-width:340px}
#tableHost table.data .notes-text{
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;
  -webkit-box-orient:vertical;overflow:hidden;
}

/* The words that can be long. Truncated rather than wrapped: a model name is
   scanned down a column, and one row growing to two lines breaks the rhythm of
   the fifteen around it. The full value is in the edit form, and the title
   attribute is not needed because the cell is never the only place it appears.
   Named, not counted — the same reason as .comp and .notes. */
#tableHost table.data td.text{
  max-width:190px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* One height for every row whether it needs it or not, the same reasoning the
   users grid gives: a grid where some rows are one line and some are two has
   no rhythm to run your eye down. `height` on a row is a minimum. */
#tableHost table.data tbody tr{height:66px}

@media (max-width:1180px){
  /* Comp Low, Comp High and Notes are read at a desk, not decided kerbside,
     and all three remain on the vehicle's edit form. Dropping them takes
     roughly 310px out of the horizontal scroll. Named rather than counted —
     see the COLUMNS array. */
  #tableHost table.data th.comp,  #tableHost table.data td.comp,
  #tableHost table.data th.notes, #tableHost table.data td.notes{
    display:none;
  }
}

/* ---------- pointer:coarse — the photo work ------------------------------
   This file sets --tap:46px and then ships its photo controls at 22px, under
   half of it. On a 104x78 thumbnail that is a 22px drag grip in one corner, a
   22px zoom in the other and 50px of "tap to select" between them, all under
   one fingertip — on the exact task the tool exists for.
   pointer:coarse rather than hover:none, matching the reasoning above and
   because print media reports hover:none, which would paint dark squares on
   every printed thumbnail. */
@media (pointer: coarse){
  /* The bin is already two-per-row at 104px, so a larger tile costs no density. */
  .pthumb{width:128px;height:96px}

  .pthumb .zoom,
  .pthumb .pgrip{opacity:1;width:44px;height:44px;bottom:0;font-size:15px}
  .pthumb .zoom {right:0;border-radius:7px 0 5px 0}
  .pthumb .pgrip{left:0; border-radius:0 7px 0 5px}

  /* The sample badge sits in the grip's corner with a higher z-index and wins.
     Move it out of the way rather than fight it. */
  .pthumb .sample-tag{bottom:auto;top:3px;left:3px}

  /* .btn-sm is 36px, and the four vehicle-row actions are .btn-sm eight pixels
     apart with Delete beside Edit. */
  .btn-sm{min-height:44px;padding:9px 13px}
}

/* ---------- report drop zone ----------------------------------------------
   Deliberately quieter than the photo bin's dropzone: adding a whole report is
   a rarer, heavier action than adding a picture, and it sits above the vehicle
   table where it must not compete with it. */
.report-drop{padding:20px 16px;text-align:center}
.report-drop .small{max-width:46ch;margin:0 auto;color:var(--ink3)}

/* ---------- bulletin board ------------------------------------------------
   A short shared message list. Grid rather than <table> so the message column
   can wrap freely while From and When stay in their lanes — a table would
   need a min-width and start scrolling sideways on a phone, which is exactly
   what the vehicle table has to do and what this must not. */
/* ---------- Picking between existing appraisals on a vehicle ---------- */
.pick-list{display:flex;flex-direction:column;gap:8px}
.pick-row{
  display:flex;align-items:center;gap:12px;padding:11px 13px;text-decoration:none;
  border:1px solid var(--line);border-radius:9px;background:var(--panel2);color:var(--ink);
}
.pick-row:hover{border-color:var(--brand);background:#1f2733}
.pick-row .badge{flex:none}

/* The vehicle cell on the Appraisals table: thumbnail beside the name. */
.ap-name{display:flex;align-items:center;gap:10px;min-width:0}
.ap-name > span:last-child{overflow-wrap:anywhere}
.ap-vehicle{min-width:220px}

/* ---------- the Appraisals table's stacked columns ------------------------
   TWO LINES OF SMALL PRINT INSTEAD OF TWO MORE COLUMNS.
   Above the companies this table reads across every firm, and the company
   name and the second date were the columns that pushed Actions into
   wrapping. The trade is not symmetrical: a wrapped button row costs a whole
   button height on every row of every page, in every firm; a second line of
   text costs a few pixels once, on the rows that have something to say.

   A pair is never split by a media query. Hiding the cell line while the
   header line stays is a column of blanks nobody can account for — so if
   these ever need to go, both halves go together. */

/* The clickable thing is the button inside, not the cell — see th-stack in
   appraisals.js. Without this the padding around the label keeps the
   pointer cursor it inherits from table.data th and does nothing when hit. */
table.data th.th-stack{cursor:default;padding-top:7px;padding-bottom:7px}

/* A <button> that has to look like no button at all. `font:inherit` is the
   one doing the work: a button does not inherit the font, so without it the
   header quietly drops out of the uppercase letter-spaced set into the
   browser's own control font — on every header of the page at once. */
.th-sort{
  display:block;font:inherit;color:inherit;letter-spacing:inherit;
  text-transform:inherit;background:none;border:0;padding:0;margin:0;
  text-align:left;white-space:nowrap;cursor:pointer;
}
.th-sort:hover{color:var(--ink)}
.th-sort:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:3px}
.th-sort.th-sub{font-size:.72rem;font-weight:500;opacity:.7;margin-top:3px}

/* The cell's second line: quieter and below, so the eye runs down the primary
   answer and picks up the secondary only where it stops. Truncated rather
   than wrapped, for the reason td.text gives — one row growing to three lines
   breaks the rhythm of the fifteen around it. */
.cell-sub{
  font-size:.78rem;color:var(--ink3);margin-top:3px;max-width:24ch;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* Floors under the stacked columns, so the table degrades by scrolling rather
   than by crushing one of them — the same reasoning as td.notes above. */
/* Wide enough that an ordinary double-barrelled name stays on one line, so
   the common row is the two the layout is designed around rather than three.
   A name that still does not fit WRAPS — the second line is small print by
   choice, and a client's name truncated with an ellipsis is not. */
#tableHost table.data td.ap-who,
#tableHost table.data th.ap-who{min-width:190px}
#tableHost table.data td.ap-when,
#tableHost table.data th.ap-when{white-space:nowrap;width:1%}

/* min-width:900px is a floor set for the ELEVEN-column vehicle table, and
   #tableHost is both pages, so appraisals inherited a horizontal scrollbar it
   no longer earns — stacking took it to seven columns, and the floors above
   are the honest minimum. Named on the table rather than the host for exactly
   that reason: the two pages share the host and not the shape. */
table.data.ap-table{min-width:0}

/* ---------- Condition rows the current form has no place for ----------
   Read-only, and styled to look it: no controls, muted, folded away. It is a
   record of what a finished report said, not a grid to work in. */
.offform{
  margin-top:18px;border:1px solid var(--line);border-radius:9px;padding:0 12px;
  background:var(--panel2);
}
.offform summary{
  cursor:pointer;padding:11px 0;color:var(--ink2);font-size:.88rem;font-weight:600;
}
.offform summary:hover{color:var(--ink)}
.offform-list{padding-bottom:10px}
.offform-row{
  display:flex;gap:12px;align-items:baseline;padding:5px 0;
  border-bottom:1px dotted var(--line);font-size:.86rem;
}
.offform-row:last-child{border-bottom:0}
.offform-where{flex:1;min-width:0;color:var(--ink2)}
.offform-grade{flex:none;font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums}

/* Unread notice. Shares the amber of the car's headlight, because they are
   saying the same thing at the same moment. */
.bb-unread{
  display:flex;align-items:center;gap:9px;margin-bottom:12px;padding:9px 12px;
  border:1px solid rgba(217,151,58,.4);border-radius:9px;
  background:rgba(217,151,58,.12);color:#e8bd76;font-size:.88rem;font-weight:600;
}
.bb-unread .btn{margin-left:auto}
.bb-unread-dot{
  width:9px;height:9px;border-radius:50%;background:#e8bd76;flex:none;
  box-shadow:0 0 0 0 rgba(232,189,118,.6);animation:bbUnreadDot 1.9s ease-out infinite;
}
@keyframes bbUnreadDot{
  0%  {box-shadow:0 0 0 0 rgba(232,189,118,.55)}
  70% {box-shadow:0 0 0 9px rgba(232,189,118,0)}
  100%{box-shadow:0 0 0 0 rgba(232,189,118,0)}
}
@media (prefers-reduced-motion:reduce){
  .bb-unread-dot{animation:none}
}

.bb-compose{display:flex;gap:10px;align-items:flex-start;margin-bottom:12px}
.bb-input{flex:1;min-height:44px;resize:vertical}
.bb-compose .btn{flex:none}

/* Email notification switch, under the compose box. Quiet by design: it is a
   preference, not an action, and it should not compete with Post. The row
   keeps a 40px touch target through .check while reading as one small line. */
.bb-notify{display:flex;align-items:center;gap:8px;margin:-6px 0 12px;flex-wrap:wrap}
.bb-notify .check{padding:6px 8px;font-size:.85rem;min-height:36px;align-items:center}
.bb-notify .check input{width:17px;height:17px;margin:0}
.bb-notify .check span{padding-top:0}
.bb-notify .muted{font-size:.8rem}
.bb-notify .check input:disabled{cursor:not-allowed;opacity:.55}
/* Clear board sits at the far right of the same row, away from Post. It is
   deliberately a ghost button: it lives on the dashboard permanently, and a
   red button that is always on screen stops being read as a warning. The
   danger styling is on the confirm dialog, where the decision is made. */
.bb-notify .bb-clear{margin-left:auto}

.bb-row{
  display:grid;
  /* From, When, the message, and the Read tick. The tick is last and fixed:
     it is the one column whose width does not depend on its content, and
     putting it at the end keeps the message the thing that grows. */
  grid-template-columns:minmax(0,9rem) minmax(0,8rem) minmax(0,1fr) 4rem;
  gap:12px;align-items:start;padding:9px 12px;border-bottom:1px solid var(--line);
}
.bb-row:last-child{border-bottom:0}
.bb-scroll .bb-row:hover{background:#1f2733}

.bb-head{
  position:sticky;top:0;z-index:1;padding:0;
  background:var(--panel2);border-bottom:1px solid var(--line2);
  border-radius:var(--radius) var(--radius) 0 0;
}
.bb-head .bb-cell{
  background:none;border:0;color:var(--ink2);font:inherit;font-size:.74rem;
  font-weight:600;text-transform:uppercase;letter-spacing:.8px;text-align:left;
  padding:10px 0;cursor:pointer;min-height:var(--tap);display:flex;
  align-items:center;gap:5px;
}
.bb-head .bb-cell:hover{color:var(--ink)}
.bb-head .bb-cell.active{color:var(--brand2)}
.bb-arrow{font-size:.62rem;opacity:.75}

.bb-scroll{
  max-height:340px;overflow-y:auto;-webkit-overflow-scrolling:touch;
  border:1px solid var(--line);border-top:0;
  border-radius:0 0 var(--radius) var(--radius);
}
.bb-scroll:focus-visible{outline:2px solid var(--brand2);outline-offset:-2px}

.bb-who{font-weight:600;overflow-wrap:anywhere}
/* Whose board a post is on, shown only above the companies. Quieter than the
   author and above it: the author is who to talk to, the firm is which
   conversation you are reading. */
/* A composer with nothing to post to. Dimmed rather than hidden, for the same
   reason the read-only sharing switches are: a firm reading every board should
   still see where a note WOULD go. */
.bb-compose-off{opacity:.6}

.bb-firm{
  display:block;font-weight:500;font-size:.74rem;color:var(--brand2);
  text-transform:uppercase;letter-spacing:.6px;margin-bottom:2px;
}
.bb-when{color:var(--ink3);font-size:.85rem;font-variant-numeric:tabular-nums;white-space:nowrap}
.bb-msg{display:flex;gap:8px;align-items:flex-start}
.bb-body{flex:1;min-width:0;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.5}

.bb-x{
  flex:none;width:26px;height:26px;border:0;border-radius:6px;cursor:pointer;
  background:none;color:var(--ink3);font-size:17px;line-height:1;
  opacity:0;transition:opacity .12s,color .12s;
}
.bb-row:hover .bb-x,.bb-x:focus-visible{opacity:1}
.bb-x:hover{color:var(--bad);background:rgba(208,85,75,.12)}
@media (pointer: coarse){
  /* No hover to reveal it, and 26px is under half the tap target. */
  .bb-x{opacity:.7;width:44px;height:44px;font-size:20px}
}

.bb-empty{padding:26px 14px;text-align:center;color:var(--ink3);font-size:.9rem}

/* ---------------------------------------------------------------------------
   THE READ TICK.

   A board is a list of things somebody has to deal with, and the useful state
   is the one that still needs attention. So UNREAD is the plain row and read is
   the faded one — the other way round would leave a brand new board looking
   uniformly greyed, which says the opposite of what it means.

   Dimmed rather than hidden, because unticking is half the feature: a row you
   cannot see is a row you cannot put back on the list.
   --------------------------------------------------------------------------- */
.bb-read{display:flex;align-items:center;justify-content:center;
  min-height:var(--tap);cursor:pointer;margin:0}
.bb-read input{width:20px;height:20px;margin:0;flex:none;accent-color:var(--brand)}
.bb-read-own{color:var(--ink3);font-size:.85rem;user-select:none}

.bb-row.bb-is-read .bb-who,
.bb-row.bb-is-read .bb-when,
.bb-row.bb-is-read .bb-body{opacity:.55}
/* Not the tick itself: a faded checkbox on a faded row is how somebody
   concludes it cannot be pressed, which is exactly the mistake the sharing
   switches on the Companies screen had to be rescued from. */
.bb-row.bb-is-read .bb-read{opacity:1}

@media (max-width:620px){
  /* Three columns cannot hold a sentence at 360px. Stack the byline above the
     message and let From and When share one line. */
  /* From and the tick share the top line; the message drops below them. The
     tick stays on that first line ON PURPOSE — it is the control, and it must
     be reachable with a thumb without reading the whole post first. */
  .bb-row{grid-template-columns:1fr auto;gap:4px 10px}
  .bb-msg{grid-column:1 / -1;grid-row:3}
  .bb-when{grid-row:2}
  .bb-read{grid-column:2;grid-row:1;justify-content:flex-end}

  /* The header stops being column labels and becomes what it always also was:
     the sort controls. Hiding it here would take sorting away on the device
     the board is most likely to be read on. */
  .bb-head{display:flex;gap:18px;padding:0 12px}
  .bb-head .bb-cell{padding:8px 0}
}

/* ---------- device preview ------------------------------------------------
   A desktop-only tool: look at any page at a real tablet or phone viewport.
   The frame is a genuine iframe at genuine device pixels, so every media
   query fires exactly as it will on the device. */
.devsel{
  min-height:34px;padding:5px 8px;width:auto;
  background:var(--bg2);border:1px solid var(--line2);border-radius:7px;
  color:var(--ink2);font:inherit;font-size:.82rem;align-self:center;
}
.devsel:hover{color:var(--ink);border-color:#4d5c72}
/* On the two pages with no top bar — sign-in and password reset — the picker
   floats instead. Low opacity until approached, because the sign-in screen is
   deliberately just a car in a dark room and this must not become the most
   interesting thing on it. */
.devsel-float{
  position:fixed;right:14px;bottom:14px;z-index:60;
  opacity:.35;transition:opacity .16s;
}
.devsel-float:hover,.devsel-float:focus{opacity:1}

.devprev{
  position:fixed;inset:0;z-index:200;
  background:rgba(6,9,13,.93);
  display:flex;flex-direction:column;
}
.devprev-bar{
  display:flex;align-items:center;gap:12px;
  padding:10px 16px;border-bottom:1px solid var(--line);background:var(--panel);
}
.devprev-size{
  flex:1;color:var(--ink2);font-size:.85rem;
  font-variant-numeric:tabular-nums;letter-spacing:.3px;
}
.devprev-stage{
  flex:1;display:flex;align-items:center;justify-content:center;
  overflow:hidden;padding:16px;
}
/* transform-origin centre so a scaled frame stays put; the border is the
   device bezel, and the white background stops a transparent page from
   showing the backdrop through it. */
.devprev-frame{
  border:0;background:#fff;border-radius:14px;
  box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 1px var(--line2);
  transform-origin:center center;flex:none;
}

/* The picker is for a desk. On the device itself it is noise, and the top bar
   is already tight enough to need a scrolling row down here. */
@media (max-width:900px){ .devsel{display:none} }
@media (pointer: coarse){ .devsel{display:none} }

/* The pinned Actions column would otherwise print as a dark stripe with a drop
   shadow down the right edge of every page. The existing print block hides
   .btn but knows nothing about this. */
@media print{
  #tableHost table.data th:last-child,
  #tableHost table.data td.actions{
    position:static;background:transparent;box-shadow:none;
  }
}

/* --- The office database is not answering --------------------------------
   Deliberately loud and not dismissable. This is the one fault where every
   page still renders perfectly and none of it is real — see showServerFault()
   in ui.js for what it cost to learn that a small badge is not enough. */
.server-fault {
  background: #7f1d1d;
  color: #fff;
  padding: 14px 18px;
  font-size: 14px;
  line-height: 1.45;
  border-bottom: 3px solid #ef4444;
}
.server-fault strong { display: block; font-size: 15px; margin-bottom: 2px; }
.server-fault-hint { margin-top: 6px; opacity: .85; font-size: 13px; }

/* --- The Tier 1 companies console ----------------------------------------
   Only the pieces that had no equivalent already: a row of accounts as chips,
   and one transfer decision with its four detail switches beside it. */
.chip-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.chip{display:inline-flex;align-items:center;gap:6px;
  background:var(--panel);border:1px solid var(--line);border-radius:999px;
  padding:4px 6px 4px 12px;font-size:.82rem}
.chip-x{background:none;border:0;cursor:pointer;color:var(--ink3);
  font-size:.95rem;line-height:1;padding:3px 6px;border-radius:999px}
.chip-x:hover{background:var(--line);color:var(--ink)}

/* The switch reads first, its detail flags second, and what just happened
   last — so a saved change is reported beside the control that made it and
   not in a corner of the page. */
.flow-row{display:grid;grid-template-columns:minmax(220px,22rem) 1fr auto;
  gap:10px 16px;align-items:center;padding:10px 0;border-top:1px solid var(--line)}
.flow-row:first-of-type{border-top:0}
.flow-row .check{margin:0}
@media(max-width:760px){
  .flow-row{grid-template-columns:1fr;gap:6px}
}

/* ---------------------------------------------------------------------------
   A SWITCH SOMEBODY CANNOT MOVE MUST NOT LOOK LIKE ONE THEY CAN.

   A Company Administrator is shown what their firm contributes and receives
   and is not allowed to change it — that decision is the installation's, and
   showing the switches anyway is deliberate: "we send nothing" and "we send
   everything but names" are different settings, and a firm is entitled to see
   which one is written down.

   The `disabled` attribute alone did not carry that. On this dark palette the
   browser's own dimming of a checkbox is nearly invisible, and the LABEL beside
   it stayed full-strength white — so twenty controls read as operable, and
   clicking them did nothing at all. Reported as "buttons on the company screen
   don't seem to work", which is exactly what it looks like from the outside.

   So the whole row is dimmed and the cursor says no. It is still readable —
   that is the entire point of not hiding it — but nobody now spends a click
   finding out.
   --------------------------------------------------------------------------- */
.flow-row.readonly{opacity:.66}
.flow-row.readonly .check{cursor:not-allowed}
.flow-row.readonly .check:hover{background:transparent}
.flow-row.readonly .badge{opacity:.8}

/* A saved switch and a refused one must not look alike. */
.ok-text{color:var(--ok)}
.bad-text{color:#eda49d}

/* Whose records this page is showing. Quiet, and always there — see topbar()
   in ui.js for what a scoped list that does not say so costs. */
/*
 * NO LONGER A CHIP. It sits under the wordmark now, as the second line of the
 * brand block, so the pill border and the horizontal padding were drawing a
 * box around a caption. What it needs instead is to be quieter than the name
 * above it and to not stretch the bar: an email is long, the row is precious,
 * and an ellipsis is the honest way to run out of room.
 *
 * font-weight:400 explicitly, because .brand sets 600 and a bold email under a
 * bold company name reads as two headings rather than as a name and a caption.
 */
.topbar .firm{
  font-size:.72rem;font-weight:400;color:var(--ink3);line-height:1.25;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:22rem}
/*
 * IT STAYS ON A PHONE NOW, because it finally fits.
 *
 * This was display:none below 700px, and that was the right call while the
 * badge read "Bayside Valuations · Company Administrator" — 42 characters
 * beside a wordmark and a switcher already naming the same firm. Hiding it
 * removed a duplicate.
 *
 * It carries the ROLE ALONE now — Admin, Sys Admin, Appraiser — so there is
 * nothing duplicated to hide, and what was being lost on every phone was the
 * only answer on screen to "what am I here". A four-character chip is cheaper
 * than that question.
 */

/*
 * THE COMPANY NAME IS THE SWITCHER, for a Sys Admin with somewhere
 * to switch to. There is no separate dropdown: the bar used to carry the
 * firm's name twice, once as the wordmark and once as a <select> beside it,
 * and a reader had to work out that the second was the one that could change.
 *
 * .brand-pick is added by ui.js ONLY when there is more than one place to go.
 * Without it the name is text and looks like text — a control that opens a
 * list of one is a promise the bar cannot keep.
 */
.topbar .brand-words.brand-pick{
  cursor:pointer;border-radius:6px;padding:1px 5px;margin-left:-5px;
  text-decoration:underline;text-decoration-style:dotted;
  text-decoration-color:var(--ink3);text-underline-offset:3px;
}
.topbar .brand-words.brand-pick:hover{background:var(--panel);text-decoration-color:var(--ink)}
.topbar .brand-words.brand-pick:focus-visible{
  outline:2px solid var(--brand);outline-offset:2px;text-decoration:none}
/* Working somewhere other than your own company: the name itself goes amber,
   because it is the thing naming whose records are on screen. */
.topbar .brand-words.firm-elsewhere{color:var(--warn)}

/* ---- the picker itself ---- */
.pick-list{display:flex;flex-direction:column;gap:6px;margin-bottom:4px}
.pick-head{
  font-size:.72rem;letter-spacing:.09em;text-transform:uppercase;color:var(--ink3);
  margin:10px 2px 2px}
/* Each choice says what it MEANS, which is the whole reason this is a dialog
   and not a dropdown: a <select> gives one line per option and no room to say
   that Enterprise is not a company, or that an independent appraiser in the
   list is a person rather than a practice. */
.pick-row{
  display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  width:100%;text-align:left;padding:11px 13px;border-radius:10px;
  border:1px solid var(--line);background:var(--panel);color:var(--ink);
  font:inherit;cursor:pointer}
.pick-row:hover:not(:disabled){border-color:var(--brand);background:var(--panel2)}
.pick-row:disabled{opacity:.55;cursor:default}
.pick-row:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.pick-name{font-weight:600}
.pick-what{font-size:.78rem;color:var(--ink3);line-height:1.35}
.pick-row.pick-now{border-color:var(--brand);background:var(--panel2)}
.pick-here{
  font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--brand2);
  margin-top:3px}
.pick-row.pick-busy .pick-name::after{content:' …';color:var(--ink3)}
/* Working somewhere other than your own company is a state worth seeing at a
   glance — every list on the page belongs to somebody else. */
/* Working in somebody else's company: the colour survives the loss of the
   border it used to share with the chip, which was carrying half the warning. */
.topbar .firm.firm-elsewhere{color:var(--warn);font-weight:600}
/*
 * THE WAY INTO A COMPANY IS NEVER HIDDEN, at any width.
 *
 * The old dropdown had a rule saying so, and the rule mattered: .firm is a
 * LABEL and a narrow bar can do without one, but this is the only way a Sys Admin
 * reaches a company's records. Hiding it on a narrow window or
 * an iPad in portrait left them looking at empty lists with nothing on screen
 * to explain why.
 *
 * It is the wordmark now, and the narrow-width rule DOES clip the wordmark —
 * which would take the switch with it. So the clip is cancelled exactly when
 * the name is a control, and only then: an ordinary account still loses the
 * wordmark to make room for the navigation.
 */
@media(max-width:700px){
  /* The badge shrinks rather than leaving, for the reason above. */
  .topbar .firm{font-size:.72rem;max-width:9rem}
}
/* `.flow-row.pending` lived here — a switch recorded but read by nothing, dimmed
   and captioned. The two rows it existed for were the appraisal ones, and they
   are deleted rather than dimmed: no appraisal crosses a firm boundary, so
   there is no switch to explain. The style goes with them, because a style for
   a state nothing is in is how the state comes back. */







/* ---------------------------------------------------------------------------
   APPEARANCE — a company's own colours, typeface and logo.

   Two halves on the company card. The FIELDS are what is saved; the FINDINGS
   are what the website turned out to contain. They look different on purpose:
   a saved value is a field with a label, and a candidate is a chip you press.
   Anything that made the two look alike would be a screen where it is not
   obvious which colour the firm is actually using.
   --------------------------------------------------------------------------- */

.skin-grid{
  display:grid;gap:12px;margin:.6rem 0 0;
  grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
}
.skin-grid .field{margin:0}

/* A colour field: the input and a square of the colour itself, because a hex
   value is not something anybody can read as a colour. */
.swatch-field{display:flex;align-items:center;gap:8px}
.swatch-field input[type=text]{flex:1;min-width:0}
.swatch{
  width:var(--tap);height:var(--tap);flex:none;border-radius:8px;
  border:1px solid var(--line2);background:var(--panel2);
  display:inline-block;cursor:default;
}

/* A candidate from the scan. Pressable, and it says what it is when hovered. */
.pick-swatch{
  min-height:var(--tap);display:inline-flex;align-items:center;gap:8px;
  padding:5px 10px 5px 5px;border-radius:8px;
  border:1px solid var(--line2);background:var(--panel2);
  color:var(--ink);font:inherit;font-size:.85rem;cursor:pointer;
}
.pick-swatch:hover{border-color:var(--brand);background:#2b3543}
.pick-swatch .dot{
  width:26px;height:26px;flex:none;border-radius:6px;
  border:1px solid rgba(255,255,255,.18);
}
.pick-swatch .n{color:var(--ink3);font-variant-numeric:tabular-nums}

/* A logo candidate. Checkerboard behind it, because half the logos on the
   internet are transparent PNGs that are invisible on a dark panel — and
   "nothing appeared" would read as a broken image rather than a white mark. */
.pick-logo{
  min-height:var(--tap);padding:6px;border-radius:8px;cursor:pointer;
  border:1px solid var(--line2);
  background:
    linear-gradient(45deg,#39404c 25%,transparent 25%,transparent 75%,#39404c 75%) 0 0/16px 16px,
    linear-gradient(45deg,#39404c 25%,transparent 25%,transparent 75%,#39404c 75%) 8px 8px/16px 16px,
    #262d38;
}
.pick-logo:hover{border-color:var(--brand)}
.pick-logo img{display:block;height:34px;width:auto;max-width:170px;object-fit:contain}

/* What the scan says. Neutral by default; the amber border is for the row that
   says a saved value is no longer on the website, which is the one line on this
   panel somebody has to act on. */
.scan-note{
  border:1px solid var(--line2);border-radius:8px;padding:10px 12px;
  background:var(--panel2);margin:.6rem 0;
}
.scan-note.stale{border-color:rgba(217,151,58,.55)}
.scan-note ul{margin:.35rem 0 0;padding-left:1.15rem}
.scan-note li{margin:.15rem 0}

/* The live preview strip: the tokens applied to a few real controls, so a
   colour is judged where it will be used rather than as a square. */
.skin-preview{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  border:1px solid var(--line2);border-radius:8px;padding:10px 12px;
  background:var(--bg2);margin:.6rem 0;
}
.skin-preview .pv-mark{width:30px;height:30px;flex:none;border-radius:6px;background-size:contain;background-repeat:no-repeat;background-position:center}
.skin-preview .pv-btn{
  display:inline-flex;align-items:center;min-height:34px;padding:6px 14px;
  border-radius:8px;font-weight:600;font-size:.9rem;
}
.skin-preview .pv-link{font-weight:600;font-size:.92rem}

/* The top bar's mark, when a firm has supplied one of its own. The product's
   wheel carries an amber glow that belongs to the product's own colour; a
   customer's logo must not inherit it. */
.topbar .brand .mark.mark-custom{filter:none;background-color:transparent}

/* ===========================================================================
   THE DECK — the dashboard beside the car, when it is asked for.

   HIDDEN UNTIL THE DOOR HANDLE IS PRESSED, and that is the whole design. The
   dashboard was removed outright once, because a page that opens with figures
   nobody reads is a page paying for them on every load. Behind the handle it
   costs nothing until somebody wants it — the panel is not laid out, and
   app.html does not even fetch the numbers until the first time it opens.

   When it is open the panel takes a fixed left column and the car takes what
   is left.

   BELOW THE BREAKPOINT IT STACKS, deliberately. A side panel needs horizontal
   room an iPad in portrait does not have, so the deck is a plain column there
   and the car comes first exactly as it always did. The panel is
   also never a side panel there: it simply stacks above the car when the
   handle asks for it, which is the honest thing for a column that has nowhere
   to sit beside.
   =========================================================================== */
.deck{display:flex;flex-direction:column}
/* Source order is the panel first — see the note in app.html — so the car is
   put back on top here rather than the panel being moved down there. */
.deck > .hero{order:-1}
.deck-panel{min-width:0}
.deck-panel-head{display:flex;align-items:center;gap:10px;margin-bottom:2px}
.deck-panel-head .page-title{margin:0}
.sr-only{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap}

@media (min-width:1000px){
  /*
   * ONE COLUMN UNTIL THE HANDLE IS PULLED. The panel is hidden by default, and
   * a two-column grid with an empty first column would leave the car sitting
   * in the right-hand two thirds of the page with a gap beside it. The grid
   * only becomes a grid when there is something to put in it.
   */
  .deck{
    display:grid;
    grid-template-columns:minmax(0,1fr);
    gap:22px;align-items:start;
    /* The transition is on the COLUMNS, so collapsing slides rather than
       jumping — and the car reflows with it instead of being redrawn once at
       the end. */
    transition:grid-template-columns .22s ease;
  }
  .deck.deck-open{grid-template-columns:minmax(210px,250px) minmax(0,1fr)}
  .deck > .hero{order:0;margin-bottom:0}
  
}

/* The board moves into the space the dashboard used to have, so it wants the
   breathing room the dashboard had above it. */
.deck + .card{margin-top:22px}


/* ---- the skin tuner's other three surfaces -------------------------------
   A colour is chosen on this dark page and also lands on white paper, in an
   email and on a tablet in either appearance. These panes are drawn on their
   OWN grounds, not on the page's, because a swatch shown on the wrong ground
   is exactly the mistake they exist to prevent. */
.skin-surfaces{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:12px;margin:.6rem 0 .2rem}
.pv-cap{font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink2);margin:0 0 5px}
.pv-paper{background:#fff;color:#111;border:1px solid #d7dad4;border-radius:7px;
  padding:11px 12px;font-family:Georgia,'Times New Roman',serif}
.pv-firm{font-size:13px;font-weight:700;margin:0 0 7px}
.pv-rule{height:2px;width:52px;background:#333;margin:0 0 8px;border-radius:1px}
.pv-body{font-size:11.5px;color:#444;line-height:1.45}
.pv-mailbtn{display:inline-block;background:#333;color:#fff;font-size:11px;
  font-weight:700;padding:6px 10px;border-radius:3px;
  font-family:var(--font,system-ui,sans-serif)}
.pv-pads{display:flex;gap:8px}
.pv-pad{flex:1;height:54px;border-radius:9px;border:1px solid #d7dad4}
.pv-pad-light{box-shadow:inset 0 0 0 10px #f7f7f5}
.pv-pad-dark{box-shadow:inset 0 0 0 10px #101215;border-color:#2a2f36}
.pv-note{font-size:11px;color:var(--ink2);margin-top:5px;min-height:1.2em}
.pv-note.moved{color:var(--warn,#c9962e)}