/* L&M 360 — demo build. Same visual system as the prototype. */
:root{
  --ground:#EFF3F7; --surface:#FFF; --surface-2:#F5F8FB; --surface-3:#E9EFF5;
  --ink:#121A22; --ink-2:#44525F; --ink-3:#6E7C8A;
  --rule:#DAE2EA; --rule-2:#E9EFF4;
  --accent:#B84C1B; --accent-soft:rgba(184,76,27,.10);
  --good:#0C7A3E; --good-soft:rgba(12,163,12,.12);
  --warn:#8A5A0B; --warn-soft:rgba(250,178,25,.20);
  --crit:#B3322F; --crit-soft:rgba(208,59,59,.13);
  --c1:#86b6ef; --c3:#2a78d6;
  --rail:#141C25; --rail-ink:#AAB8C5; --rail-ink-2:#6C7C8B; --rail-rule:#232E3A;
  --shadow:0 1px 2px rgba(18,26,34,.06),0 10px 28px -18px rgba(18,26,34,.35);
  color-scheme:light;
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
  --ground:#0C1116; --surface:#161B21; --surface-2:#1B222A; --surface-3:#232C35;
  --ink:#E3E9EF; --ink-2:#AEBAC6; --ink-3:#77838F;
  --rule:#28313A; --rule-2:#1F272F;
  --accent:#E8834F; --accent-soft:rgba(232,131,79,.14);
  --good:#4FBF6A; --good-soft:rgba(12,163,12,.18);
  --warn:#E0AC4A; --warn-soft:rgba(250,178,25,.14);
  --crit:#E06B68; --crit-soft:rgba(208,59,59,.18);
  --rail:#10161C; --rail-rule:#1E262F;
  --shadow:0 1px 2px rgba(0,0,0,.5),0 10px 28px -18px rgba(0,0,0,.8);
  color-scheme:dark;
}}
*{box-sizing:border-box}
body{margin:0;background:var(--ground);color:var(--ink);
  font-family:"IBM Plex Sans","Segoe UI",system-ui,-apple-system,sans-serif;font-size:14px;line-height:1.5;
  -webkit-font-smoothing:antialiased}
a{color:var(--accent)}
button,input,select,textarea{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
.mono{font-family:"IBM Plex Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums}
.cond{font-family:"IBM Plex Sans Condensed","IBM Plex Sans",sans-serif}

.app{display:grid;grid-template-columns:230px minmax(0,1fr);min-height:100vh;align-items:start}
/* The menu is its own column, the height of the window, and it stays there while the
   page scrolls. Without a bounded height the rail simply grew and took the whole page
   with it, so "the menu scrolls" was really "everything scrolls and the menu goes up
   with it" — which is why the top of it disappeared on a long page. */
.rail{background:var(--rail);border-right:1px solid var(--rail-rule);display:flex;flex-direction:column;
  position:sticky;top:0;height:100vh}
.rail__brand{padding:16px;border-bottom:1px solid var(--rail-rule);display:flex;gap:10px;
  align-items:center;text-decoration:none}
.rail__brand:hover .rail__name{color:var(--accent)}
/* The logo is a transparent circle whose wordmark is black, and the menu is dark, so it
   needs a light ground under it. A white disc, sized to the artwork's own circle. */
.rail__logo{width:40px;height:40px;border-radius:50%;background:#fff;flex:0 0 auto;
  display:block;box-shadow:0 0 0 1px rgba(255,255,255,.15)}
.rail__name{color:#fff;font-weight:700;font-size:16px;font-family:"IBM Plex Sans Condensed",sans-serif;
  line-height:1.1;display:block}
.rail__org{color:var(--rail-ink-2);font-size:10.5px;display:block}
.rail__nav{flex:1;padding:10px 10px;overflow-y:auto}
/* Tightened until the whole menu fits 1080px without scrolling, which it now does with
   about forty pixels to spare. Everything below that height scrolls the menu itself
   rather than the page, which is what the bounded rail above is for. */
/* Six groups now, so the heading costs six times whatever it is given. Measured at
   1920x1080 with the menu at full length; see the note above about what the rail's
   bounded height is for. */
.navgroup{font-family:"IBM Plex Mono",monospace;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--rail-ink-2);padding:8px 10px 3px}
/* 22 rows in six groups at 1920x1080. Measured, not guessed: a pixel either way here is
   the difference between a menu and a menu with a scrollbar. */
.navbtn{display:block;padding:5px 10px;border-radius:6px;color:var(--rail-ink);text-decoration:none;font-size:13.5px;
  font-family:"IBM Plex Sans Condensed",sans-serif;font-weight:500}
.navbtn:hover{background:rgba(255,255,255,.05);color:#fff}
.navbtn.on{background:var(--accent);color:#fff}
.rail__foot{padding:12px 16px;border-top:1px solid var(--rail-rule);color:var(--rail-ink);font-size:12px}
.rail__foot a{color:var(--rail-ink-2);font-size:11px}

.main{min-width:0;display:flex;flex-direction:column}
.topbar{height:56px;display:flex;align-items:center;gap:12px;padding:0 22px;background:var(--surface);
  border-bottom:1px solid var(--rule)}
.topbar h1{margin:0;font-size:19px;font-weight:700;font-family:"IBM Plex Sans Condensed",sans-serif}
.topbar .sub{font-size:12.5px;color:var(--ink-3)}
/* The search box lives in the header of every page. It takes the space between the page
   title and the alerts bell, and gives most of it back below 1100px — where the title
   and the bell are what matter and a box can be reached from the page it belongs to. */
.gsearch{margin-left:auto;min-width:0}
.gsearch input{width:clamp(180px,26vw,340px);padding:6px 10px;font-size:13px;
  border:1px solid var(--rule);border-radius:16px;background:var(--surface-2);color:inherit}
.gsearch input:focus{background:var(--surface);border-color:var(--accent)}
@media(max-width:1100px){.gsearch input{width:150px}}
@media(max-width:760px){.gsearch{display:none}}
/* A result: one line you can hit with a mouse, the detail under it. */
.srch{display:block;padding:9px 14px;border-bottom:1px solid var(--rule-2);text-decoration:none;color:inherit}
.srch:hover{background:var(--surface-2)}
.srch__t{font-size:13px;font-weight:500}
.srch__s{display:block;font-size:11.5px;color:var(--ink-3)}
/* The second thing a client's name on an enquiry can mean. Quiet, because the name
   itself is the answer to the question most people are asking. */
.enqmore{font-size:10.5px;color:var(--ink-3);text-decoration:none;border-bottom:1px dotted var(--rule)}
.enqmore:hover{color:var(--accent);border-color:var(--accent)}
.view{padding:20px 24px 80px;max-width:1320px}
/* The schedule board is the one page that wants every pixel — a reading measure is
   the wrong constraint for a resource grid. */
.view--wide{max-width:none}

h2.sec{font-family:"IBM Plex Sans Condensed",sans-serif;font-size:15px;margin:26px 0 10px}
.lede{color:var(--ink-2);font-size:13.5px;max-width:74ch;margin:0 0 16px}

.card{background:var(--surface);border:1px solid var(--rule);border-radius:8px;box-shadow:var(--shadow);margin-bottom:16px}
.card__head{padding:13px 16px;border-bottom:1px solid var(--rule-2);display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
.card__head h3{margin:0;font-size:13.5px;font-family:"IBM Plex Sans Condensed",sans-serif;font-weight:600;white-space:nowrap}
.card__head p{margin:0;font-size:11.5px;color:var(--ink-3)}
.card__head .right{margin-left:auto}
.card__body{padding:16px}
.grid{display:grid;gap:16px}
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
.g21{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}
.g4{grid-template-columns:repeat(4,minmax(0,1fr))}

/* The dashboard grid. Three columns; a widget spans one, two or three of them, which is
   the whole of "resize" — a free-form pixel width would mean a layout that only works at
   the width it was arranged at, on the one machine it was arranged on. Below 1000px
   everything is full width, because a third of a phone is not a panel. */
.wgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;align-items:start}
.wg{margin:0}
.wg--1{grid-column:span 1}
.wg--2{grid-column:span 2}
.wg--3{grid-column:span 3}
@media(max-width:1000px){.wgrid{grid-template-columns:minmax(0,1fr)}
  .wg--1,.wg--2,.wg--3{grid-column:span 1}}
.wg--edit{outline:1px dashed var(--rule);outline-offset:3px}
.wgctls{display:flex;gap:4px;align-items:center}
.wgctl{font:inherit;font-size:13px;line-height:1;min-width:26px;height:24px;padding:0 6px;
  border:1px solid var(--rule);border-radius:5px;background:var(--surface);color:var(--ink-2);cursor:pointer}
.wgctl:hover{border-color:var(--accent);color:var(--accent)}
.wgctl--off{opacity:.32;cursor:default;display:inline-flex;align-items:center;justify-content:center}
@media(max-width:1000px){.g2,.g3,.g21{grid-template-columns:minmax(0,1fr)}
  .g4{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:560px){.g4{grid-template-columns:minmax(0,1fr)}}

.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:18px}
.tile{background:var(--surface);border:1px solid var(--rule);border-radius:8px;padding:13px 15px;box-shadow:var(--shadow)}
.tile b{display:block;font-size:25px;font-weight:600;line-height:1.1}
.tile span{font-size:11.5px;color:var(--ink-3)}

table.tbl{border-collapse:collapse;width:100%;font-size:13px}
.tablewrap{overflow-x:auto}
.tbl thead th{text-align:left;font-family:"IBM Plex Sans Condensed",sans-serif;font-weight:600;font-size:10.5px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);padding:9px 13px;background:var(--surface-2);
  border-bottom:1px solid var(--rule);white-space:nowrap}
.tbl tbody td{padding:9px 13px;border-bottom:1px solid var(--rule-2);color:var(--ink-2);vertical-align:middle}
.tbl tbody tr:last-child td{border-bottom:0}
.tbl td.s{color:var(--ink);font-weight:500}
.tbl .r{text-align:right;font-variant-numeric:tabular-nums}
.tbl tbody tr:hover td{background:var(--surface-2)}

.pill{display:inline-flex;align-items:center;gap:5px;padding:2px 8px;border-radius:20px;font-size:11px;
  font-family:"IBM Plex Sans Condensed",sans-serif;font-weight:500;white-space:nowrap;line-height:1.7}
.pill--neutral{background:var(--surface-3);color:var(--ink-2)}
.pill--good{background:var(--good-soft);color:var(--good)}
.pill--warn{background:var(--warn-soft);color:var(--warn)}
.pill--crit{background:var(--crit-soft);color:var(--crit)}
.pill--accent{background:var(--accent-soft);color:var(--accent)}
.dot{width:7px;height:7px;border-radius:50%;background:currentColor}
.tag{display:inline-block;padding:1px 6px;border-radius:3px;font-size:10px;font-family:"IBM Plex Mono",monospace;
  background:var(--surface-3);color:var(--ink-3)}

.btn{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--rule);background:var(--surface);
  color:var(--ink);border-radius:6px;padding:7px 13px;font-size:13px;font-weight:500;cursor:pointer;text-decoration:none;
  font-family:"IBM Plex Sans Condensed",sans-serif}
.btn:hover{background:var(--surface-2)}
.btn--primary{background:var(--accent);border-color:var(--accent);color:#fff}
.btn--primary:hover{filter:brightness(1.08);background:var(--accent)}
.btn--danger{border-color:var(--crit);color:var(--crit)}
.btn--sm{padding:4px 9px;font-size:12px}
.btnrow{display:flex;gap:8px;flex-wrap:wrap;align-items:center}

.fld{margin-bottom:12px}
.fld label{display:block;font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);
  margin-bottom:4px;font-family:"IBM Plex Sans Condensed",sans-serif}
.fld input[type=text],.fld input[type=email],.fld input[type=password],.fld input[type=date],.fld input[type=time],
.fld input[type=search],.fld input[type=number],.fld select,.fld textarea{
  width:100%;padding:8px 10px;border:1px solid var(--rule);border-radius:6px;background:var(--surface);font-size:13.5px}
.fld textarea{min-height:70px;resize:vertical}
/* A permission is a tick box with the sentence that says what it does beside it. A row
   of bare labels is a page nobody can audit — the note is the difference between "ticked
   because it sounded right" and a decision. */
.permrow{display:flex;gap:10px;align-items:flex-start;padding:8px 0;border-bottom:1px solid var(--rule)}
.permrow:last-of-type{border-bottom:0}
.permrow input{margin-top:3px;flex:0 0 auto}
.permrow > span{display:flex;flex-direction:column;gap:2px;font-size:13px;line-height:1.45}
.permrow > span > b{font-weight:600}
.permrow > span > span{color:var(--ink-3);font-size:12px}
.permrow--off{opacity:.55}
.permrow--off input{cursor:not-allowed}

/* A placeholder at the same weight as a value reads as a filled-in field, and the add-a-
   vehicle form is a page of them. Grey it, in both themes. */
input::placeholder,textarea::placeholder{color:var(--ink-3);opacity:.8}
.fld .hint{font-size:11px;color:var(--ink-3);margin-top:4px}
/* A tick box with a sentence beside it, inside a field. The field's own label is a
   small-caps heading, and a list of people's names rendered in that is unreadable —
   so a row inside a field says it is a row rather than a heading. */
.optrow{display:flex;gap:8px;align-items:baseline;padding:3px 0;font-size:13px;
  text-transform:none;letter-spacing:0;color:var(--ink);font-weight:400}
.fld label.optrow{display:flex;font-size:13px;text-transform:none;letter-spacing:0;
  color:var(--ink);margin:0}
.optrow em{font-style:normal;color:var(--ink-3);font-size:12px}
.frow{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:0 14px}
label.chk{display:flex;gap:8px;align-items:center;font-size:13px;color:var(--ink-2);margin-bottom:8px;text-transform:none;letter-spacing:0}

.note{font-size:12px;color:var(--ink-2);background:var(--surface-2);border-left:2px solid var(--accent);
  padding:9px 12px;border-radius:0 5px 5px 0;margin:12px 0 0}
.note--crit{border-left-color:var(--crit)}
.note--warn{border-left-color:var(--warn)}
.flash{padding:10px 14px;border-radius:6px;margin-bottom:16px;font-size:13px}
.flash--ok{background:var(--good-soft);color:var(--good)}
.flash--err{background:var(--crit-soft);color:var(--crit)}
.flash--warn{background:var(--warn-soft);color:var(--warn)}

.kv{display:grid;grid-template-columns:auto 1fr;gap:6px 16px;font-size:13px;align-items:baseline;margin:0}
.kv dt{color:var(--ink-3);font-size:11.5px;white-space:nowrap}
.kv dd{margin:0}
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--rule);margin-bottom:16px;overflow-x:auto}
.tabs a{padding:9px 13px;font-size:13px;color:var(--ink-3);text-decoration:none;border-bottom:2px solid transparent;
  font-family:"IBM Plex Sans Condensed",sans-serif;white-space:nowrap}
.tabs a.on{color:var(--ink);border-bottom-color:var(--accent)}

/* schedule board */
/* Schedule board.
   The grid sizes itself to the window rather than to a fixed column width: the day
   columns share whatever is left after the resource column, down to --daymin, below
   which the board scrolls instead of squeezing text to nothing. --days and --daymin
   are set on the table by the view, so one rule serves 7, 14 and 28 columns. */
.board{overflow:auto;max-height:calc(100vh - 300px);min-height:340px;
  overscroll-behavior:contain;scrollbar-gutter:stable}
.board table{border-collapse:separate;border-spacing:0;width:100%;table-layout:fixed;
  min-width:calc(var(--rescol,210px) + (var(--days,7) * var(--daymin,120px)))}
.board th{background:var(--surface-2);border-bottom:1px solid var(--rule);padding:8px 10px;text-align:left;
  font-family:"IBM Plex Sans Condensed",sans-serif;font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3)}
/* Both axes stay put: day names when scrolling down 60 people, names when scrolling
   across four weeks. The corner cell has to outrank both. */
.board thead th{position:sticky;top:0;z-index:3}
.board thead th.res{z-index:5}
.board th.res{position:sticky;left:0;width:var(--rescol,210px);border-right:1px solid var(--rule)}
.board tbody th{position:sticky;left:0;z-index:2;background:var(--surface);border-right:1px solid var(--rule);
  border-bottom:1px solid var(--rule-2);text-transform:none;letter-spacing:0;font-size:12.5px;color:var(--ink);
  width:var(--rescol,210px)}
.board tbody tr.grp th,.board tbody tr.grp td{background:var(--surface-3);color:var(--ink-3);
  font-family:"IBM Plex Sans Condensed",sans-serif;font-size:10.5px;letter-spacing:.09em;text-transform:uppercase}
.board td.cell{border-bottom:1px solid var(--rule-2);border-right:1px solid var(--rule-2);padding:4px;height:46px;
  vertical-align:top;background:var(--surface)}
.board td.cell.over{background:var(--accent-soft);box-shadow:inset 0 0 0 2px var(--accent)}
/* The coloured edge is a custom property so a continuation chip can switch it off from
   its own rule. Setting the width directly meant the density rules — which are more
   specific — kept re-drawing an edge mid-run and the joined bar showed seams. */
.bk{border-radius:5px;padding:4px 7px;font-size:11.5px;line-height:1.3;
  border-left:var(--bkbar,3px) solid var(--c3);
  background:color-mix(in srgb,var(--c3) 12%,var(--surface));cursor:grab;display:block;text-decoration:none;color:var(--ink)}
/* Narrow columns truncate rather than wrap or overflow; the full text is on the title. */
.bk strong,.bk small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bk small{color:var(--ink-3);font-size:10.5px}
.bk strong{font-family:"IBM Plex Mono",monospace;font-size:11px}
.bk.prov{border-left-style:dashed;background:var(--surface-2)}
.bk.rail{border-left-color:var(--crit)}
.resname small{display:block;font-size:10.5px;color:var(--ink-3);font-family:"IBM Plex Mono",monospace}

/* Weekends are shaded rather than hidden — rail possessions land on them. */
.board td.we{background:var(--surface-2)}
.board th.we{color:var(--ink-3)}
/* Consecutive days on one job draw as a single bar: the continuation chip drops its
   rounded end, its coloured edge and its label, and reaches across the cell padding so
   there is no gap to read as a break. --bkbar is a custom property precisely so this
   one-class rule can switch the edge off without out-specifying the density rules. */
.bk--from{border-top-left-radius:0;border-bottom-left-radius:0;--bkbar:0px;
  margin-left:-5px;padding-left:12px}
.bk--to{border-top-right-radius:0;border-bottom-right-radius:0;margin-right:-5px}
/* The span buttons choose how many columns the grid has. Below the breakpoint there is
   no grid, so they would be four buttons that do nothing. */
@media (max-width:760px){ .spanctl{display:none !important} }

/* Density tiers. More days in the same width means less room per chip, so the chip
   gets smaller rather than the column getting a scrollbar. */
.board table.d2{--daymin:96px}
.board table.d4{--daymin:74px}
.board table.d2 th,.board table.d4 th{padding:7px 6px}
.board table.d2 td.cell,.board table.d4 td.cell{padding:3px}
.board table.d2 .bk{padding:3px 5px;font-size:11px}
.board table.d4 .bk{padding:2px 4px;font-size:10.5px}
.board table.d4 .bk strong{font-size:10px}
.board table.d4 .bk small{font-size:9.5px}
.board table.d4 td.cell{height:38px}

/* Less room for the resource column, and a tighter floor per day, as the window
   narrows — a laptop should fit a week without scrolling sideways. */
@media (max-width:1400px){ .board{--rescol:176px} .board table{--daymin:100px} }
@media (max-width:1100px){ .board{--rescol:160px} .board table{--daymin:94px} .resname small{display:none} }
@media (max-width:900px){ .board{--rescol:132px;max-height:calc(100vh - 240px)} }

/* Below this a matrix is not a usable shape, so the same bookings are shown as a list
   of days instead. Both are rendered; only one is ever visible. */
.boarddays{display:none}
@media (max-width:760px){
  .board{display:none}
  .boarddays{display:block}
}
.boarddays .bday{border-bottom:1px solid var(--rule-2);padding:10px 16px}
.boarddays .bday:last-child{border-bottom:0}
.boarddays .bday.we{background:var(--surface-2)}
.boarddays .bday h4{margin:0 0 8px;font-size:12.5px;font-family:"IBM Plex Sans Condensed",sans-serif;
  letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3)}
.boarddays .brow{display:flex;gap:10px;align-items:baseline;padding:5px 0;border-top:1px solid var(--rule-2)}
.boarddays .brow:first-of-type{border-top:0}
.boarddays .bwho{flex:0 0 42%;font-size:12.5px}
.boarddays .bwho small{display:block;color:var(--ink-3);font-size:10.5px}
.boarddays .bjob{flex:1;font-size:12.5px;text-decoration:none;color:var(--ink)}
.boarddays .bjob small{display:block;color:var(--ink-3);font-size:10.5px}
.boarddays .bjob b{font-family:"IBM Plex Mono",monospace;font-size:11.5px}

/* login */
.loginwrap{min-height:100vh;display:grid;place-items:center;padding:20px;background:var(--ground)}
.loginbox{width:min(400px,100%);background:var(--surface);border:1px solid var(--rule);border-radius:10px;
  box-shadow:var(--shadow);padding:26px}
.loginbox{text-align:center}
.loginbox .fld,.loginbox label{text-align:left}
/* Same white disc as the menu: the artwork's wordmark is black and this card is dark
   grey in dark mode. */
.loginlogo{display:block;margin:0 auto 14px;width:76px;height:76px;border-radius:50%;
  background:#fff;box-shadow:0 0 0 1px var(--rule)}
.loginbox h1{margin:0 0 4px;font-size:22px;font-family:"IBM Plex Sans Condensed",sans-serif}
.loginbox p.sub{margin:0 0 20px;color:var(--ink-3);font-size:13px}
/* The way out of the card: forgotten password, back to signing in. Quiet, because it is
   the second thing anybody wants from this page and never the first. */
.loginalt{margin:16px 0 0;font-size:12.5px;color:var(--ink-3);text-align:center}
.loginalt a{color:var(--ink-2)}

/* field app */
.fieldwrap{max-width:560px;margin:0 auto;padding:0 14px 90px}
.fieldbar{position:sticky;top:0;z-index:10;background:var(--rail);color:#fff;padding:11px 14px;display:flex;
  align-items:center;gap:10px;margin:0 -14px 14px}
.fieldbar a{color:var(--rail-ink);text-decoration:none;font-size:13px}
.fieldbar h1{margin:0;font-size:16px;font-family:"IBM Plex Sans Condensed",sans-serif;font-weight:600}
.fieldnav{position:fixed;bottom:0;left:0;right:0;background:var(--surface);border-top:1px solid var(--rule);
  display:flex;z-index:20}
.fieldnav a{flex:1;text-align:center;padding:10px 4px;font-size:11px;color:var(--ink-3);text-decoration:none;
  font-family:"IBM Plex Sans Condensed",sans-serif}
.fieldnav a.on{color:var(--accent);font-weight:600}
.vcard{display:block;border:1px solid var(--rule);border-radius:8px;padding:12px;margin-bottom:10px;
  background:var(--surface);text-decoration:none;color:var(--ink)}
.vcard h5{margin:0 0 3px;font-size:14px;font-family:"IBM Plex Sans Condensed",sans-serif}
.vcard p{margin:0;font-size:12px;color:var(--ink-3)}
@media(max-width:860px){
  /* minmax(0,1fr), not 1fr: a plain 1fr track is sized by its content, so the widest
     thing inside decides the page width instead of the viewport. */
  .app{grid-template-columns:minmax(0,1fr)}
  .rail{position:static;flex-direction:row;flex-wrap:wrap;align-items:center;min-width:0}
  /* Brand and account on one row, the whole menu scrolling on a second. Wrapping the
     menu stacked fifteen buttons and pushed the page most of a screen down; letting it
     scroll without min-width:0 made the menu, not the viewport, decide the page width
     and sent every page into horizontal scroll. */
  .rail__brand{flex:1 1 auto;min-width:0;padding:10px 12px}
  .rail__foot{flex:0 0 auto;order:2;margin-left:auto;border-top:0;padding:8px 12px}
  .rail__nav{flex:1 1 100%;order:3;min-width:0;display:flex;flex-wrap:nowrap;gap:4px;padding:6px 8px;
    overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;
    border-top:1px solid var(--rail-rule)}
  .rail__nav::-webkit-scrollbar{display:none}
  .navbtn{white-space:nowrap;flex:0 0 auto}
  .navgroup{display:none}
  .view{padding:14px}
}

/* Drop zone for job files and emails */
.dropzone{border:2px dashed var(--rule);border-radius:8px;background:var(--surface-2);
  padding:22px 18px;text-align:center;transition:background .12s,border-color .12s}
.dropzone.over{border-color:var(--accent);background:var(--accent-soft)}
.dropzone__inner strong{display:block;font-size:14px;font-family:"IBM Plex Sans Condensed",sans-serif}
.dropzone__inner span{display:block;color:var(--ink-3);font-size:12.5px;margin:4px 0 12px}
.dropzone input[type=file]{font-size:12.5px}

/* Leave reads as a booking, because that is what it is competing with for the day —
   the eye should not have to switch modes to see that Tuesday is spoken for. Same
   chip, same run-joining, red instead of blue, and no drag handle: leave is moved on
   the holiday page, not by dropping it somewhere. */
.bk--off{background:color-mix(in srgb,var(--crit) 14%,var(--surface));
  border-left-color:var(--crit);color:var(--crit);cursor:default}
.bk--off strong{font-family:inherit;font-weight:600;color:inherit}

/* ---------- Holiday planner ----------
   A year at a glance means roughly 260 cells per person, so each one is a few pixels
   and carries no text: the pattern is the information. Weekends are left out rather
   than shaded, which buys back two sevenths of the width and loses nothing — nobody
   books leave on a Sunday. */
.hol{border-collapse:separate;border-spacing:0;width:100%;table-layout:fixed}
.hol th,.hol td{padding:0;border:0}
.hol__who{position:sticky;left:0;z-index:2;background:var(--surface);text-align:left;
  width:170px;min-width:170px;padding:3px 10px 3px 12px;border-bottom:1px solid var(--line)}
.hol thead .hol__who{z-index:3}
.hol__wk{font:600 10px/1.6 "IBM Plex Mono",monospace;color:var(--ink-3);text-align:left;
  padding-left:2px;border-bottom:1px solid var(--line);white-space:nowrap}
.hol__d{height:19px;border-right:1px solid var(--surface);border-bottom:1px solid var(--surface);
  background:var(--surface-2);cursor:pointer}
.hol__d:hover{outline:1px solid var(--accent);outline-offset:-1px}
.hol__d.sel{outline:2px solid var(--accent);outline-offset:-2px}
.hol__d.d--out{background:transparent;cursor:default}
.hol__d.d--hol{background:var(--crit)}
.hol__d.d--sick{background:var(--warn)}
.hol__d.d--trn{background:var(--accent)}
.hol__d.d--oth{background:var(--ink-3)}
.hol__sum{width:52px;text-align:right;padding:0 10px;font-size:12px;color:var(--ink-2);
  border-bottom:1px solid var(--line);background:var(--surface);position:sticky;right:0}
.hol__sum.over{color:var(--crit);font-weight:600}
.hkey{display:inline-block;width:9px;height:9px;border-radius:2px;vertical-align:-1px;margin:0 2px 0 8px}
.hkey--hol{background:var(--crit)} .hkey--sick{background:var(--warn)}
.hkey--trn{background:var(--accent)} .hkey--oth{background:var(--ink-3)}

/* ---------- Appearance ----------
   The dark tokens appear twice on purpose: once for the OS preference and once for an
   explicit choice, because a media query and an attribute selector cannot be combined
   into one rule. They MUST declare the same set of properties — asserted by the test
   suite, which compares the two blocks property by property. */
:root[data-theme="dark"]{
  --ground:#0C1116; --surface:#161B21; --surface-2:#1B222A; --surface-3:#232C35;
  --ink:#E3E9EF; --ink-2:#AEBAC6; --ink-3:#77838F;
  --rule:#28313A; --rule-2:#1F272F;
  --accent:#E8834F; --accent-soft:rgba(232,131,79,.14);
  --good:#4FBF6A; --good-soft:rgba(12,163,12,.18);
  --warn:#E0AC4A; --warn-soft:rgba(250,178,25,.14);
  --crit:#E06B68; --crit-soft:rgba(208,59,59,.18);
  --rail:#10161C; --rail-rule:#1E262F;
  --shadow:0 1px 2px rgba(0,0,0,.5),0 10px 28px -18px rgba(0,0,0,.8);
  color-scheme:dark;
}
.themepick{display:flex;gap:2px;margin-bottom:10px;background:var(--rail-rule);
  border-radius:6px;padding:2px}
.themepick__b{flex:1;border:0;background:none;color:var(--rail-ink-2);cursor:pointer;
  font:500 10.5px/1 "IBM Plex Sans",sans-serif;letter-spacing:.02em;padding:6px 2px;
  border-radius:4px;text-align:center;text-decoration:none}
.themepick__b:hover{color:var(--rail-ink)}
.themepick__b.on{background:var(--rail);color:var(--rail-ink);box-shadow:0 1px 2px rgba(0,0,0,.3)}
.rail__me{display:block;color:var(--rail-ink);text-decoration:none;font-weight:500;
  padding:6px 8px;margin:0 -8px 4px;border-radius:5px}
.rail__me:hover{background:var(--rail-rule);color:#fff}
.rail__me span{display:block;color:var(--rail-ink-2);font-size:11px;font-weight:400}

/* ---------- The planner ----------
   Two panes: what is not booked on the left, a fortnight of days on the right. The rail
   is sticky because the days scroll and the thing being dragged must stay reachable. */
.plan{display:grid;grid-template-columns:290px minmax(0,1fr);gap:14px;align-items:start}
.plan__rail{position:sticky;top:14px}
.plan__list{max-height:60vh;overflow-y:auto;border-top:1px solid var(--rule-2)}
.plan__job,.plan__res{display:block;padding:10px 13px;border-bottom:1px solid var(--rule-2);background:var(--surface)}
.plan__job{cursor:grab}
/* The filter hides rows with the hidden attribute, and these have a display of their own
   that would otherwise win. Stated once, next to the rule it has to beat. */
.plan__job[hidden],.plan__res[hidden]{display:none}
.plan__job.is-focus{background:var(--accent-soft);box-shadow:inset 3px 0 0 var(--accent)}
.plan__job:active{cursor:grabbing}
.plan__jobno{font-weight:600;font-size:13px;font-family:"IBM Plex Mono",monospace}
.plan__jobtitle{font-size:12.5px;color:var(--ink-2);margin-top:2px}
.plan__jobclient{font-size:11.5px;color:var(--ink-3)}
.plan__jobbook{display:flex;gap:6px;margin-top:7px}
.plan__jobbook select{flex:1;min-width:0;padding:4px 6px;font-size:12px;border:1px solid var(--rule);
  border-radius:5px;background:var(--surface);color:inherit}
/* The search box sits between the heading and the list, inside the same card, so it
   reads as belonging to that list and not to the page. */
.plan__search{display:flex;gap:6px;padding:9px 13px;border-top:1px solid var(--rule-2);
  background:var(--surface-2)}
.plan__search input[type=search]{flex:1;min-width:0;padding:5px 8px;font-size:12.5px;
  border:1px solid var(--rule);border-radius:5px;background:var(--surface);color:inherit}
.plan__nomatch{padding:12px 13px;font-size:12.5px;color:var(--ink-3);border-top:1px solid var(--rule-2)}
.plan__res{display:flex;gap:8px;align-items:center;cursor:grab}
.plan__res.is-on{cursor:default;background:var(--good-soft)}
.plan__res.is-blocked{background:var(--crit-soft)}
.plan__resmain{flex:1;min-width:0}
.plan__reslabel{font-size:13px;font-weight:500}
.plan__ressub{font-size:11.5px;color:var(--ink-3)}
.plan__reswhy{font-size:11.5px;color:var(--crit);margin-top:2px}
.plan__resbusy{font-size:11.5px;color:var(--warn);margin-top:2px}
.plan__hire{border-top:1px solid var(--rule-2)}
.plan__hire summary{padding:11px 14px;font-size:12.5px;cursor:pointer;color:var(--accent)}

.plan__days{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:8px}
.plan__day{background:var(--surface);border:1px solid var(--rule);border-radius:8px;min-height:118px;
  display:flex;flex-direction:column}
.plan__day.is-weekend{background:var(--surface-2)}
.plan__day.is-today{border-color:var(--accent)}
.plan__day.is-target{outline:2px solid var(--accent);outline-offset:2px}
.plan__day.is-over{background:var(--accent-soft);border-color:var(--accent)}
/* A day that has already happened. Quiet rather than hidden — what was booked on Tuesday
   is still worth reading, it is just not the planner's to change any more. The contrast
   stays above the point where the text stops being legible; greying out a column is a
   way of saying "not here", not a way of throwing it away. */
.plan__day.is-past{background:var(--surface-3);border-style:dashed;border-color:var(--rule-2)}
.plan__day.is-past .plan__dayhead{color:var(--ink-3)}
.plan__day.is-past .plan__visit{opacity:.72}
.plan__gone{float:right;font-size:9.5px;letter-spacing:.08em;color:var(--ink-3);
  text-transform:uppercase;font-weight:500}
/* The offer to book a day that has gone anyway. Deliberately the colour of a refusal. */
.plan__past{padding:11px 14px;border-top:1px solid var(--rule-2);background:var(--crit-soft);
  font-size:12.5px;line-height:1.45}
.plan__dayhead{padding:7px 9px;border-bottom:1px solid var(--rule-2);font-size:11px;
  font-family:"IBM Plex Sans Condensed",sans-serif;font-weight:600;text-transform:uppercase;letter-spacing:.06em}
.plan__dayhead span{color:var(--ink-3);font-weight:400;margin-left:5px;text-transform:none;letter-spacing:0}
.plan__drop{flex:1;padding:7px;display:flex;flex-direction:column;gap:6px}
.plan__empty{flex:1;min-height:34px;border:1px dashed var(--rule);border-radius:6px}
.plan__visit{border:1px solid var(--rule);border-radius:6px;padding:7px 8px;background:var(--surface)}
.plan__visit.is-sel{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
.plan__visit.is-prov{border-left:3px solid var(--warn)}
.plan__vno{font-family:"IBM Plex Mono",monospace;font-size:12px;font-weight:600}
.plan__vtitle{font-size:11.5px;color:var(--ink-2);margin-top:1px;overflow-wrap:anywhere}
.plan__vcrew{font-size:11px;color:var(--ink-3);margin-top:3px}
.plan__vnone{color:var(--warn)}
.plan__vhire{font-size:11px;color:var(--warn);margin-top:3px}
.plan__vfoot{display:flex;gap:5px;align-items:center;margin-top:6px;flex-wrap:wrap}
@media(max-width:1200px){.plan__days{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media(max-width:900px){.plan{grid-template-columns:minmax(0,1fr)}
  .plan__rail{position:static}
  .plan__days{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* A drop zone for files, over a form that already works without one. */
.dropzone{border:2px dashed var(--rule);border-radius:8px;padding:18px;text-align:center;
  color:var(--ink-3);font-size:13px;background:var(--surface-2)}
.dropzone.is-over{border-color:var(--accent);background:var(--accent-soft);color:var(--accent)}

/* The walkaround, done in gloves in a yard: two big targets a row, and OK preselected so
   a check with nothing wrong is one press. */
.checkrow{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:11px 0;border-bottom:1px solid var(--rule-2);font-size:15px}
.checkpick{display:flex;gap:6px;flex:0 0 auto}
.checkpick label{display:inline-flex;align-items:center;gap:5px;padding:7px 12px;border:1px solid var(--rule);
  border-radius:7px;font-size:14px;background:var(--surface)}
.checkpick input{margin:0}
.checkpick label:has(input:checked){border-color:var(--accent);background:var(--accent-soft);color:var(--accent)}

/* ---------- The notification area ----------
   A <details> in the top bar: it opens with no script, which matters for the one control
   that is meant to tell you something is wrong. */
.topbar .alerts{position:relative;margin-left:auto}
.alerts__bell{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:6px;
  padding:5px 11px;border:1px solid var(--rule);border-radius:20px;font-size:12.5px;color:var(--ink-3);
  background:var(--surface);white-space:nowrap}
.alerts__bell::-webkit-details-marker{display:none}
.alerts__bell:hover{border-color:var(--accent);color:var(--accent)}
.alerts__bell.is-warn{border-color:var(--warn);color:var(--warn)}
.alerts__bell.is-crit{border-color:var(--crit);color:var(--crit)}
.alerts__n{font-weight:700;font-variant-numeric:tabular-nums}
.alerts__panel{position:absolute;right:0;top:calc(100% + 8px);width:min(460px,88vw);z-index:40;
  background:var(--surface);border:1px solid var(--rule);border-radius:10px;box-shadow:var(--shadow);
  max-height:min(70vh,540px);overflow-y:auto}
.alerts__row{display:flex;gap:8px;align-items:flex-start;padding:9px 12px;border-bottom:1px solid var(--rule-2);
  font-size:13px}
.alerts__row a{flex:1;color:var(--ink-2);text-decoration:none}
.alerts__row a:hover{color:var(--accent)}
.alerts__row--crit{border-left:3px solid var(--crit)}
.alerts__row--warn{border-left:3px solid var(--warn)}
.alerts__row--info{border-left:3px solid var(--rule)}
.alerts__none{padding:14px 12px;font-size:12.5px;color:var(--ink-3)}
.alerts__all{display:block;padding:10px 12px;font-size:12.5px;border-top:1px solid var(--rule-2)}
@media(max-width:700px){.alerts__word{display:none}}

/* ---------- Kanban ----------
   Columns scroll sideways as a unit; each column scrolls on its own vertically, so a
   board with sixty cards in one column does not make the page a mile long. */
.kb{display:flex;gap:12px;overflow-x:auto;padding-bottom:6px;align-items:flex-start;margin-bottom:16px}
.kb__col{flex:0 0 268px;background:var(--surface-2);border:1px solid var(--rule);border-radius:8px;
  display:flex;flex-direction:column;max-height:72vh}
.kb__head{display:flex;align-items:center;gap:8px;padding:9px 12px;border-bottom:1px solid var(--rule);
  font-family:"IBM Plex Sans Condensed",sans-serif;font-weight:600;font-size:12.5px;
  border-top:2px solid var(--rule);border-radius:8px 8px 0 0}
.kb__head span{flex:1}
.kb__head b{font-variant-numeric:tabular-nums;color:var(--ink-3);font-weight:600}
.kb__head--good{border-top-color:var(--good)}
.kb__head--warn{border-top-color:var(--warn)}
.kb__head--crit{border-top-color:var(--crit)}
.kb__head--neutral{border-top-color:var(--rule)}
.kb__drop{padding:10px;display:flex;flex-direction:column;gap:9px;overflow-y:auto;flex:1;min-height:58px}
.kb__drop.over{background:var(--accent-soft);outline:2px dashed var(--accent);outline-offset:-4px;border-radius:6px}
.kb__card{background:var(--surface);border:1px solid var(--rule);border-radius:6px;padding:9px 10px;
  box-shadow:var(--shadow);cursor:grab}
.kb__card:active{cursor:grabbing}
.kb__card.dragging{opacity:.45}
.kb__title{font-size:13px;font-weight:600;line-height:1.35}
.kb__sub{font-size:11.5px;color:var(--ink-3);margin-top:2px;line-height:1.35}
.kb__meta{display:flex;flex-wrap:wrap;gap:4px;margin-top:7px}
.kb__tag{font-size:10.5px;padding:1px 6px;border-radius:9px;background:var(--surface-3);color:var(--ink-2)}
.kb__tag--warn{background:var(--warn-soft);color:var(--warn)}
.kb__tag--crit{background:var(--crit-soft);color:var(--crit)}
.kb__move{display:flex;gap:4px;margin-top:8px}
.kb__move select{flex:1;min-width:0;padding:3px 5px;font-size:11.5px;border:1px solid var(--rule);
  border-radius:5px;background:var(--surface)}
.kb__links{display:flex;gap:8px;align-items:center;margin-top:6px;font-size:11.5px}
.kb__empty{color:var(--ink-3);font-size:12px;text-align:center;padding:14px 4px}
.kb__edit{margin-top:7px}
.kb__edit summary{font-size:11.5px;color:var(--ink-3);cursor:pointer}
.kb__edit form{display:flex;flex-direction:column;gap:6px;margin-top:7px}
.kb__edit input[type=text],.kb__edit input[type=date],.kb__edit input[type=number],.kb__edit select{
  width:100%;padding:4px 7px;font-size:12px;border:1px solid var(--rule);border-radius:5px;background:var(--surface)}
.kb__edit label{font-size:10.5px;color:var(--ink-3);display:flex;flex-direction:column;gap:2px}
.kb__edit label.chk{flex-direction:row;align-items:center;gap:6px}
.linkbtn{background:none;border:0;padding:0;color:var(--accent);font-size:11.5px;cursor:pointer;text-decoration:underline}

/* ---------- Gantt ----------
   A grid of days with bars positioned across it. No library: there is no build step to
   add one to, and a chart that needs a CDN is a chart that is blank the day it is blocked.
   It prints, and it works at any width. */
.gantt{overflow-x:auto;border-top:1px solid var(--rule-2)}
.gantt__head,.gantt__row{display:flex;align-items:stretch;min-width:760px}
.gantt__head{border-bottom:1px solid var(--rule);background:var(--surface-2);position:sticky;top:0;z-index:1}
.gantt__labels{flex:0 0 230px;padding:7px 12px;font-size:12px;line-height:1.3;border-right:1px solid var(--rule-2)}
.gantt__labels a{font-weight:600}
.gantt__labels em{display:block;font-style:normal;color:var(--ink-3);font-size:11px}
.gantt__months{flex:1;display:flex}
.gantt__months span{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);
  padding:7px 6px;border-left:1px solid var(--rule-2);white-space:nowrap;overflow:hidden}
.gantt__rows{}
.gantt__row{border-bottom:1px solid var(--rule-2)}
.gantt__row:last-child{border-bottom:0}
.gantt__track{flex:1;position:relative;margin:7px 0;min-height:20px}
.gantt__bar{position:absolute;top:2px;height:16px;border-radius:4px;background:var(--c1);
  display:block;overflow:hidden;min-width:3px}
.gantt__bar i{display:block;height:100%;background:rgba(0,0,0,.22)}
.gantt__bar--accent{background:var(--c3)}
.gantt__bar--good{background:var(--good)}
.gantt__bar--warn{background:var(--warn)}
.gantt__bar--crit{background:var(--crit)}
.gantt__bar--neutral{background:var(--ink-3)}
.gantt__ms{position:absolute;top:3px;width:13px;height:13px;margin-left:-6px;transform:rotate(45deg);
  background:var(--ink-3);border-radius:2px}
.gantt__ms--crit{background:var(--crit)}
.gantt__ms--warn{background:var(--warn)}
.gantt__ms--good{background:var(--good)}
.gantt__ms--accent{background:var(--c3)}
.gantt__today{position:absolute;top:-7px;bottom:-7px;width:2px;background:var(--accent);opacity:.55}
.gantt__key{display:flex;flex-wrap:wrap;gap:14px;font-size:11.5px;color:var(--ink-3)}
.gantt__key span{display:flex;align-items:center;gap:5px}
.gantt__sw{width:14px;height:9px;border-radius:3px;display:inline-block;background:var(--ink-3)}
.gantt__sw--accent{background:var(--c3)}
.gantt__sw--good{background:var(--good)}
.gantt__sw--warn{background:var(--warn)}
.gantt__sw--crit{background:var(--crit)}
.gantt__todaykey{width:2px;height:12px;background:var(--accent);display:inline-block}
@media print{.kb__move,.kb__edit,.btnrow{display:none}.gantt{overflow:visible}}
