/* Inter, self-hosted.
 *
 * The same files the Trificta site serves, so the console and the company site
 * render identically, and no request leaves the origin: the CSP is
 * default-src 'none' and there is no font-src for a CDN.
 *
 * Three weights, not six. Each is ~110KB and the difference between 500 and 600
 * is not worth a third of a megabyte on every cold load.
 */
@font-face{font-family:Inter;src:url(/staff/static/fonts/Inter-Regular.woff2) format('woff2');
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:Inter;src:url(/staff/static/fonts/Inter-SemiBold.woff2) format('woff2');
  font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:Inter;src:url(/staff/static/fonts/Inter-Bold.woff2) format('woff2');
  font-weight:700;font-style:normal;font-display:swap}

/* Trificta SupportCore console.
 *
 * Palette lifted from www.trificta.com.au so the tool looks like it belongs to
 * the company that made it: #F47521 orange on near-black, Inter.
 *
 * Layout follows Toggl's shape - a grouped left rail, a persistent timer top
 * right, content that fills whatever width the screen has. Nothing here is
 * centred in a fixed column: a burn-down table with eleven numeric columns is
 * unreadable squeezed into 1200px on a 34" monitor.
 */

:root{
  --orange:#F47521; --orange-dark:#d4611a; --orange-light:#f9a870;
  --orange-a08:rgba(244,117,33,.08);
  /* An OPAQUE orange for planned blocks. A translucent fill let the hour
     gridlines show straight through the block, which read as lines drawn
     across it. */
  --planned:#5e3616;
  /* The wash behind a planned block, and the border on it. Both pulled well
     back from full orange so the orange TEXT on top stays legible. */
  --planned-soft:rgba(94,54,22,.55); --orange-soft:rgba(244,117,33,.45);
  --orange-a10:rgba(244,117,33,.10); --orange-a18:rgba(244,117,33,.18);

  /* --panel3 is the shade under a hovered row and inside a chip, so every
     colour above has to clear 4.5:1 against it too. A shade lighter than this
     put --dim2 back under the floor on exactly the rows somebody is pointing
     at. */
  --bg:#0d0f12; --panel:#15181d; --panel2:#1c2027; --panel3:#1f242c;
  --line:#282d36; --line2:#343b45;
  /* Text colours, chosen against the WCAG AA floor of 4.5:1 rather than by
     eye.

     --dim2 was #666e7c, which is 3.46:1 on a panel and 2.88:1 on the shade
     under a hovered row. It carries the 11px meta text - dates, "logged by",
     the second line of a name - so the smallest type on the console was also
     the hardest to read, and on a laptop screen in daylight several people
     simply cannot. Raised to clear 4.5:1 everywhere it appears, while staying
     a clear step below --dim so the hierarchy the two exist for survives.

     --red was 3.88:1 on that same shade. It is the colour of "over cap" and
     "failed", which is the last thing that should be hard to make out. */
  --fg:#e9ecf1; --dim:#8b93a1; --dim2:#848c9a;

  /* Two reds, because one cannot do both jobs.
     --red is TEXT on a dark background, so it has to be light: 5.92:1 on a
     panel. --red-fill is a solid button behind WHITE text, so it has to be
     dark: white on the light one is 3.01:1, which is why "Delete permanently"
     was hard to read. Lightening a single --red for the sake of red text
     silently degraded every filled danger button, and the two requirements
     pull in opposite directions. */
  --red:#ef6b6b; --red-fill:#c92f2f; --red-a12:rgba(239,107,107,.12);
  --amber:#e8a33d; --green:#3fbf7f; --blue:#4c8dff;
  --radius:6px; --radius-lg:10px;
  --side:236px; --side-min:60px; --top:60px;
  /* Height of one hour row. THE source of truth for calendar geometry: the
     template positions blocks in calc() against it and the script measures it
     back off the DOM, so changing it here changes everything in step. */
  --hr:48px;
  --font:'Inter','Segoe UI',system-ui,-apple-system,Roboto,Arial,sans-serif;
  /* Figures line up in columns and in a clock. */
  --mono:'Roboto Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
}

*{box-sizing:border-box}

/* The hidden attribute wins, always.
 *
 * [hidden]{display:none} lives in the UA stylesheet, where ANY class selector
 * outranks it. So a component styled with display:flex or display:grid stays
 * visible while its markup says hidden and its JavaScript sets .hidden = true,
 * and nothing in the code looks wrong.
 *
 * Every popover, menu, modal and sheet here carried its own .x[hidden] rule to
 * work around that, and the "New activity" pill was added without one and was
 * therefore permanently on screen. One global rule removes the whole trap
 * rather than waiting for the next component to fall into it.
 */
[hidden]{display:none !important}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--fg);
  font:500 14px/1.5 var(--font);letter-spacing:-.006em;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
a{color:var(--orange);text-decoration:none}
a:hover{text-decoration:underline}

/* ---------- shell ---------- */

.shell{display:flex;min-height:100vh}
.main{flex:1;min-width:0;display:flex;flex-direction:column}

/* ---------- sidebar ---------- */

.side{width:var(--side);flex:0 0 var(--side);background:#0a0c0f;
  border-right:1px solid var(--line);display:flex;flex-direction:column;
  position:sticky;top:0;height:100vh;transition:width .16s ease,flex-basis .16s ease}

.brandbar{display:flex;align-items:center;gap:10px;height:var(--top);padding:0 14px;
  border-bottom:1px solid var(--line);flex:0 0 auto}
/* The wordmark, and the square that stands in for it when the sidebar is
   collapsed to icons. Only ever one of the two is shown. */
.brand{display:flex;align-items:center;min-width:0;text-decoration:none}
.brand:hover{text-decoration:none}
.logo{height:19px;width:auto;display:block}
.mark{display:none;width:22px;height:22px;flex:0 0 22px;border-radius:5px;
  background:linear-gradient(135deg,var(--orange),var(--orange-dark))}
.side.mini .logo{display:none}
.side.mini .mark{display:block}
.bname{font-weight:700;letter-spacing:.2px;white-space:nowrap;overflow:hidden}
.collapse{margin-left:auto;background:none;border:none;color:var(--dim2);cursor:pointer;
  padding:4px 6px;border-radius:4px;line-height:1;font-size:16px}
.collapse:hover{color:var(--fg);background:var(--panel2)}

.side nav{flex:1;overflow-y:auto;padding:12px 8px}
/* Trificta orange. The groups are how the sidebar is read - you look for
   "Commercial" and then for the item - and in grey they were quieter than the
   items they were meant to be organising. */
.side .sec{font-size:10px;font-weight:700;letter-spacing:1.1px;text-transform:uppercase;
  color:var(--orange);padding:14px 10px 6px;white-space:nowrap}
.side nav a{display:flex;align-items:center;gap:11px;padding:8px 10px;border-radius:var(--radius);
  color:var(--dim);font-size:13.5px;font-weight:500;white-space:nowrap;overflow:hidden}
.side nav a:hover{background:var(--panel2);color:var(--fg);text-decoration:none}
.side nav a.on{background:var(--orange-a18);color:var(--orange)}
.side nav a.on .ic{background:var(--orange)}

/* The count of policies waiting on this person.

   Pushed to the trailing edge with margin-left:auto rather than a fixed gap,
   because the link is a flex row and the label between the icon and the number
   is a different width on every destination.

   IT SURVIVES THE COLLAPSED SIDEBAR. The rule that hides the labels in .mini is
   written `nav a span`, and this badge is a span: left alone it would vanish
   exactly when the sidebar is narrowest and the icon alone says least. Collapsed
   it becomes a dot on the corner of the icon, which is the same signal in the
   space available. Absolutely positioned, so it does not widen a rail whose
   width is the whole point of that state. */
/* Dark on orange, not white. White is 2.83:1 against this orange and the a11y
   gate refuses it; the same pairing is why .btnlike is #12140f rather than
   white, so this matches that rather than inventing a second answer to a
   question the design system already settled. */
.side nav a .navcount{margin-left:auto;flex:0 0 auto;min-width:18px;height:18px;
  padding:0 5px;border-radius:999px;background:var(--orange);color:#12140f;
  font-size:11px;font-weight:700;line-height:18px;text-align:center;
  font-variant-numeric:tabular-nums}
.side.mini nav a{position:relative}
.side.mini nav a .navcount{display:block;position:absolute;top:3px;right:3px;
  min-width:0;width:8px;height:8px;padding:0;font-size:0;line-height:0;
  border:2px solid #0a0c0f}

/* Icons are CSS masks over a single inline SVG each: no icon font, no sprite
   request, and they take the colour of the item they sit in. */
/* inline-block, not inline: width and height are ignored on an inline box, and
   outside a flex container these icons collapsed to nothing. */
.ic{display:inline-block;width:16px;height:16px;flex:0 0 16px;background:var(--dim);
  -webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center}
.side nav a:hover .ic{background:var(--fg)}
.ic-home{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z'/><path d='M9 22V12h6v10'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z'/><path d='M9 22V12h6v10'/></svg>")}
.ic-clock{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><circle cx='12' cy='12' r='9'/><path d='M12 7v5l3 2'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><circle cx='12' cy='12' r='9'/><path d='M12 7v5l3 2'/></svg>")}
.ic-ticket{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M3 8a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 4v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-4z'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M3 8a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 4v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-4z'/></svg>")}
.ic-grid{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'><rect x='3' y='3' width='7' height='7' rx='1'/><rect x='14' y='3' width='7' height='7' rx='1'/><rect x='3' y='14' width='7' height='7' rx='1'/><rect x='14' y='14' width='7' height='7' rx='1'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'><rect x='3' y='3' width='7' height='7' rx='1'/><rect x='14' y='3' width='7' height='7' rx='1'/><rect x='3' y='14' width='7' height='7' rx='1'/><rect x='14' y='14' width='7' height='7' rx='1'/></svg>")}
.ic-user{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><circle cx='12' cy='8' r='4'/><path d='M4 21c0-4 3.6-6 8-6s8 2 8 6'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><circle cx='12' cy='8' r='4'/><path d='M4 21c0-4 3.6-6 8-6s8 2 8 6'/></svg>")}
.ic-box{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M21 8l-9-5-9 5v8l9 5 9-5z'/><path d='M3 8l9 5 9-5M12 13v8'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M21 8l-9-5-9 5v8l9 5 9-5z'/><path d='M3 8l9 5 9-5M12 13v8'/></svg>")}
.ic-doc{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z'/><path d='M14 3v5h5M9 13h6M9 17h6'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z'/><path d='M14 3v5h5M9 13h6M9 17h6'/></svg>")}
.ic-check{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='9'/><path d='M8 12.5l2.5 2.5 5-5'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='9'/><path d='M8 12.5l2.5 2.5 5-5'/></svg>")}
.ic-cash{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'><rect x='2' y='6' width='20' height='12' rx='2'/><circle cx='12' cy='12' r='2.5'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'><rect x='2' y='6' width='20' height='12' rx='2'/><circle cx='12' cy='12' r='2.5'/></svg>")}
.ic-chart{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M4 20V10M10 20V4M16 20v-7M22 20H2'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M4 20V10M10 20V4M16 20v-7M22 20H2'/></svg>")}
.ic-trend{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 17l6-6 4 4 8-8'/><path d='M15 7h6v6'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 17l6-6 4 4 8-8'/><path d='M15 7h6v6'/></svg>")}
.ic-people{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><circle cx='9' cy='8' r='3.5'/><path d='M2 20c0-3.5 3-5.5 7-5.5s7 2 7 5.5'/><path d='M17 8.5a3 3 0 0 1 0 5M18 14.5c2.5.6 4 2.4 4 5'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><circle cx='9' cy='8' r='3.5'/><path d='M2 20c0-3.5 3-5.5 7-5.5s7 2 7 5.5'/><path d='M17 8.5a3 3 0 0 1 0 5M18 14.5c2.5.6 4 2.4 4 5'/></svg>")}
.ic-plug{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M9 2v6M15 2v6M6 8h12v4a6 6 0 0 1-12 0z'/><path d='M12 18v4'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M9 2v6M15 2v6M6 8h12v4a6 6 0 0 1-12 0z'/><path d='M12 18v4'/></svg>")}

.whoami{display:flex;align-items:center;gap:9px;padding:10px 14px;
  border-top:1px solid var(--line);flex:0 0 auto;font-size:12.5px}
.avatar{width:26px;height:26px;flex:0 0 26px;border-radius:50%;background:var(--orange);
  color:#12140f;font-weight:700;display:grid;place-items:center;font-size:12px}
.wname{color:var(--fg);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:1.25}
.wname em{display:block;font-style:normal;color:var(--dim2);font-size:11px}
/* The build, phone only: the desktop footer already says it, and twice is once
   too many. Two classes, because `.wname em` above would otherwise out-specify
   a bare .sidever and the line would show on a desktop as well. */
.wname .sidever{display:none}
.whoami a{margin-left:auto;display:grid;place-items:center;width:30px;height:30px;
  border-radius:var(--radius);border:1px solid transparent;color:var(--dim2)}
.whoami a:hover{color:var(--red);border-color:rgba(224,82,82,.4);
  background:rgba(224,82,82,.10);text-decoration:none}
.whoami a .ic{width:17px;height:17px;flex:0 0 17px;background:currentColor}
.side.mini .whoami a{margin:0}

/* Collapsed: an icon rail, the way Toggl's does it. Labels disappear, the
   active highlight does not - you must still be able to see where you are. */
.side.mini{width:var(--side-min);flex-basis:var(--side-min)}
.side.mini .bname,.side.mini .sec,.side.mini nav a span,.side.mini .wname{display:none}
.side.mini .brandbar,.side.mini .whoami{justify-content:center;padding:0 8px}
.side.mini .whoami{padding:10px 8px}
.side.mini nav a{justify-content:center;padding:9px 0}
.side.mini .collapse{transform:rotate(180deg);margin:0}
.side.mini .whoami a{margin-left:0}

/* ---------- top bar and timer ---------- */

.top{height:var(--top);flex:0 0 auto;display:flex;align-items:center;gap:20px;
  padding:0 20px;background:var(--panel);border-bottom:1px solid var(--line);
  position:sticky;top:0;z-index:20}
.timerbar{display:flex;align-items:center;gap:10px;flex:1;width:100%}
.tdesc{flex:1;min-width:0;background:transparent;border:1px solid transparent;
  color:var(--fg);border-radius:var(--radius);padding:8px 10px;font-size:14px}
.tdesc:hover{border-color:var(--line2)}
.tdesc:focus{outline:none;border-color:var(--orange);background:var(--panel2)}
.tdesc::placeholder{color:var(--dim2)}
.tproj{background:var(--panel2);border:1px solid var(--line);color:var(--dim);
  border-radius:var(--radius);padding:7px 8px;font-size:12.5px;max-width:190px}
.telapsed{font-variant-numeric:tabular-nums;font-size:15px;color:var(--dim);
  min-width:66px;text-align:right;letter-spacing:.3px}
.timerbar[data-running="1"] .telapsed{color:var(--orange);font-weight:600}

/* The round start button, Toggl's most recognisable control. */
.tgo{width:38px;height:38px;flex:0 0 38px;border-radius:50%;border:none;cursor:pointer;
  background:var(--orange);padding:0;position:relative;transition:filter .12s}
.tgo:hover{filter:brightness(1.12)}
.tgo::after{content:'';position:absolute;inset:0;margin:auto;width:0;height:0;
  border-left:12px solid #12140f;border-top:8px solid transparent;border-bottom:8px solid transparent;
  transform:translateX(2px)}
.tgo.stop{background:var(--red)}
.tgo.stop::after{border:none;width:12px;height:12px;background:#fff;border-radius:2px;transform:none}

main{padding:22px 24px 40px;flex:1;min-width:0}

/* ---------- typography and primitives ---------- */

/* A heading that carries pills.
   flex with align-items:center, because .tag is inline-block and was sitting
   on the TEXT BASELINE of 19px type: an 11px pill hung visibly low beside it.
   Baseline is right for words in a sentence and wrong for a badge, which is a
   block that should centre against the line it sits on. wrap, so a long
   subject on a narrow screen takes the pills with it rather than pushing them
   off the edge. */
h1{font-size:19px;margin:0 0 18px;font-weight:600;
  display:flex;align-items:center;flex-wrap:wrap;gap:8px}
h2{font-size:12px;margin:0 0 12px;color:var(--dim);text-transform:uppercase;letter-spacing:.7px}
.dim{color:var(--dim)}
.nowrap{white-space:nowrap}
.mono{font-family:var(--mono);font-size:12px;letter-spacing:0}

.grid{display:grid;gap:16px}
.cards{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-lg);padding:16px}
.card .n{font-size:26px;font-weight:600;line-height:1.15}
.card .l{color:var(--dim);font-size:11px;text-transform:uppercase;letter-spacing:.6px;margin-top:6px}
.card.warn .n{color:var(--amber)}
/* Already lapsed, as opposed to about to. Amber is a warning; a certificate
   that has expired is not a warning, it is a fact somebody has to act on. */
.card.bad .n{color:var(--red)}

/* A card that is a link. It must look the same at rest as one that is not,
   because the dashboard reads as a row of figures and an underlined blue one
   would read as different in kind rather than merely clickable. The
   affordance is the hover and the focus ring. */
a.card{display:block;color:inherit;text-decoration:none;
  transition:border-color .12s ease,background .12s ease}
a.card:hover{border-color:var(--orange);background:var(--orange-a08)}
a.card:focus-visible{outline:2px solid var(--orange);outline-offset:2px}

/* A visible focus ring on everything that can be reached from a keyboard.
   The default ring disappears against a dark panel, and a focus you cannot
   see is the same as no focus at all: you tab, something is selected, and
   nothing on screen says what. */
.blk:focus-visible,.tlbar:focus-visible{outline:2px solid var(--orange);
  outline-offset:1px;z-index:5}
button:focus-visible,a:focus-visible,summary:focus-visible,
input:focus-visible,select:focus-visible,textarea:focus-visible{
  outline:2px solid var(--orange);outline-offset:2px}
@media (prefers-reduced-motion: reduce){a.card{transition:none}}

.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-lg);
  overflow:hidden;margin-bottom:18px}
.panel>.hd{padding:12px 16px;border-bottom:1px solid var(--line);font-weight:600;
  display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.panel>.bd{padding:16px}

/* Wide tables scroll inside a WRAPPER, never by making the table itself a
   block. display:block on a <table> stops width:100% meaning anything: the
   table shrinks to its content, which left the reports table occupying about a
   tenth of the panel it sat in on a wide screen. */
.tablewrap{overflow-x:auto}
.tablewrap>table{min-width:100%}
table{width:100%;border-collapse:collapse}
th{text-align:left;font-size:10.5px;text-transform:uppercase;letter-spacing:.6px;color:var(--dim2);
  padding:10px 12px;border-bottom:1px solid var(--line);font-weight:700;white-space:nowrap}
td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
tr:last-child td{border-bottom:none}
tbody tr:hover{background:var(--panel2)}

.tag{display:inline-block;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;
  background:var(--panel2);border:1px solid var(--line);white-space:nowrap}
/* P1..P5. A rung on a scale, so it is drawn as one: a coloured bar and a
   label, not a pill. Five pills of five colours in a table column is bunting.
   The bar carries the severity and the text stays plain, which means the
   column can be read down at a glance without reading a single word. */
.prio{display:inline-flex;align-items:center;gap:7px;font-weight:600;
  font-size:12px;white-space:nowrap;font-variant-numeric:tabular-nums}
.prio::before{content:'';width:3px;height:13px;border-radius:2px;
  background:var(--line2);flex:0 0 3px}
.p-p1::before{background:var(--red-fill)}
.p-p2::before{background:var(--red)}
.p-p3::before{background:var(--amber)}
.p-p4::before{background:var(--dim2)}
.p-p5::before{background:var(--line2)}
.p-p1{color:var(--red)}
.p-p2{color:var(--red)}
.p-p4,.p-p5{color:var(--dim)}
.s-new,.s-triage{color:var(--amber);border-color:var(--amber)}
.s-open{color:var(--blue);border-color:var(--blue)}
.s-resolved,.s-closed{color:var(--green);border-color:var(--green)}
.sev-ok{color:var(--green);border-color:var(--green)}
.sev-warn{color:var(--amber);border-color:var(--amber)}
.sev-critical{color:#12140f;background:var(--amber);border-color:var(--amber)}
.sev-over{color:#fff;background:var(--red-fill);border-color:var(--red-fill)}
.sev-info{color:var(--blue);border-color:var(--blue)}

/* Money columns line up on the decimal point or they cannot be scanned. */
th.num,td.num{text-align:right;font-family:var(--mono);white-space:nowrap}
/* A whole row that navigates. The anchor inside stays the real target so the
   keyboard and a middle click still work; the row click is a convenience. */
table.linky tbody tr{cursor:pointer}

/* A file input styled like a button. The native control cannot be styled, so
   it is hidden inside a label that can be. */
.filepick{position:relative;display:inline-flex}
.filepick input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%}
.filepick span{display:inline-flex;align-items:center;gap:7px;padding:8px 12px;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--panel2);
  font-size:13px;white-space:nowrap}
.filepick:hover span{border-color:var(--orange);color:var(--orange)}
.filepick.has span{border-color:var(--orange);color:var(--orange)}

/* Pagination, so a capped list says so instead of pretending to be complete. */
.pager{display:flex;align-items:center;gap:14px;justify-content:center;
  padding:12px 16px;border-top:1px solid var(--line)}
.pager a{padding:6px 12px;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--panel2);text-decoration:none}
.pager a:hover{border-color:var(--orange);color:var(--orange)}
.pager .off{padding:6px 12px;color:var(--dim);opacity:.45}

/* Summary rows: a label, a proportional bar, and the numbers. The bar is what
   makes "where did the week go" answerable at a glance rather than by reading
   a column of decimals. */
.sumrow{display:grid;grid-template-columns:minmax(140px,22%) 1fr 76px 52px 96px;
  align-items:center;gap:14px;padding:7px 0}
.sumrow + .sumrow{border-top:1px solid var(--line)}
.sumkey{display:flex;align-items:center;gap:8px;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sumbar{height:10px;border-radius:999px;background:var(--panel2);overflow:hidden}
.sumbar i{display:block;height:100%;border-radius:999px;min-width:2px}
.sumnum{text-align:right;font-variant-numeric:tabular-nums;font-size:13px}
@media(max-width:820px){
  .sumrow{grid-template-columns:1fr 70px 90px}
  .sumbar{grid-column:1/-1;order:3}
  .sumnum:nth-of-type(2){display:none}
}

/* The list view: a table you can act on rather than read. */
/* The flag cell's contents used to be separated by the newlines between them
   in the template. Those are trimmed now, so that a row with nothing to flag
   leaves the cell genuinely empty and the phone layout can drop it, and the
   space they were providing has to be asked for. */
table.entrylist td.eflags span + span{margin-left:5px}
table.entrylist tbody tr.picked{background:rgba(244,117,33,.10)}
table.entrylist tbody tr[data-locked] td{opacity:.72}
table.entrylist .linkedit{opacity:0;transition:opacity .12s}
table.entrylist tr:hover .linkedit,table.entrylist .linkedit:focus{opacity:1}
/* ONE DEFINITION, because there were two and the second won everywhere.
   This said 8px and border-radius:50%, a small circle, and the project row's
   copy further down said 10px and border-radius:3px. Nothing rendered as a
   circle anywhere: every dot took the square from the later rule and the margin
   and alignment from this one, so the shipped dot was a mongrel of the two and
   the circle was unreachable. These are exactly the values that render today,
   merged into one rule, so the page looks identical and only the duplicate is
   gone. Whether a project dot SHOULD be an 8px circle is a separate question
   for somebody who wants to answer it. */
.pdot{display:inline-block;width:10px;height:10px;border-radius:3px;
  vertical-align:middle;margin-right:6px;flex:0 0 10px}

/* The bulk bar appears only when something is selected, so it never occupies
   space while nobody is editing. */
.bulkbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:10px 16px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  background:rgba(244,117,33,.08)}
.bulkbar select,.bulkbar button{font-size:12.5px;padding:6px 10px}
.bulkbar b{color:var(--orange)}

/* Tag picking inside the entry editor. */
.tagpick{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.tagopt{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;
  border:1px solid var(--line);border-radius:999px;font-size:12.5px;cursor:pointer}
.tagopt input{width:auto;margin:0}
.tagopt:has(input:checked){border-color:var(--orange);color:var(--orange)}
.tagpick input[type=text],.tagpick #ee-newtag{font-size:12.5px;padding:5px 10px;
  border-radius:999px;max-width:140px}

/* The keyboard shortcut list. */
table.keys td{padding:5px 10px;border:none}
table.keys td:first-child{width:110px;white-space:nowrap}
kbd{display:inline-block;padding:2px 7px;border:1px solid var(--line);border-bottom-width:2px;
  border-radius:5px;background:var(--panel2);font-family:var(--mono);font-size:11.5px}

/* The calendar and the timeline are viewport-height views: the whole week has
   to be on screen, with the grid scrolling inside itself rather than the page
   scrolling underneath a header. Everything else scrolls normally.
   
   These were deleted by accident when the timesheet was restyled, and the
   symptom was the calendar growing to its full 24-hour height and the PAGE
   scrolling instead of the grid. */
body.fills main{display:flex;flex-direction:column;min-height:0;overflow:hidden}
body.fills .cal,body.fills .tl{flex:1;min-height:0}

/* The calendar runs to the edges, meeting the sidebar with no gutter, as
   Toggl's does. A card floating in a padded page wastes the width on a view
   whose whole job is to show seven full days, and the seam reads as a border
   the grid does not need. The toolbar above it keeps its padding. */
body.fills main{padding:0}
body.fills main>.calbar,body.fills main>p.dim{padding-left:20px;padding-right:20px}
body.fills main>.calbar{padding-top:14px}
body.fills .cal{border-radius:0;border-left:none;border-right:none;border-bottom:none}
body.fills .shell{height:100vh;overflow:hidden}
body.fills .main{min-height:0}
body.fills main>p.dim{flex:0 0 auto}

/* ---------- customer portal ---------- */

/* A deliberately plain shell. The console's sidebar names every customer in
   the system, so the portal shares the stylesheet and nothing else. */
body.portal{background:var(--bg)}
.pshell{max-width:980px;margin:0 auto;padding:0 20px;min-height:100vh;
  display:flex;flex-direction:column}
.phead{display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:18px 0;border-bottom:1px solid var(--line)}
.pbrand{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--fg);
  font-weight:600}
.pmain{flex:1;padding:26px 0}
.pfoot{padding:18px 0;border-top:1px solid var(--line);color:var(--dim);font-size:12.5px}
.pcard{max-width:440px;margin:8vh auto;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--radius-lg);padding:28px}
.pcard h1{font-size:20px;margin-bottom:16px}
.pmsg + .pmsg{margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}
.pmsghd{display:flex;gap:10px;align-items:baseline;margin-bottom:6px;font-size:13px}

/* A panel for something irreversible. Deliberately loud: this is the only
   place in the console that destroys history. */
.panel.danger{border-color:var(--red)}
.panel.danger .hd{color:#ffb3b3;background:var(--red-a12)}
ul.impact{margin:8px 0 0;padding-left:20px;color:var(--dim);font-size:13px}
ul.impact li{margin-bottom:3px}

/* A form inside a dropdown menu, for the custom date range. */
.ddform{display:flex;gap:6px;padding:6px 10px 10px;flex-wrap:wrap}
.ddform input{flex:1;min-width:118px;font-size:12.5px;padding:6px 8px}

/* The service level line on a ticket: promised, met, missed. */
.slaline{display:flex;flex-wrap:wrap;gap:8px 22px;margin-top:14px;padding-top:12px;
  border-top:1px solid var(--line);font-size:12.5px}
.slaline span{display:flex;flex-direction:column;gap:2px}
.slaline em{font-style:normal;color:var(--dim);font-size:10.5px;
  text-transform:uppercase;letter-spacing:.6px}
.unattributed{color:var(--amber);font-weight:600}

input,select,textarea,button{font:inherit;color:var(--fg);background:var(--panel2);
  border:1px solid var(--line);border-radius:var(--radius);padding:8px 10px}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--orange)}
textarea{width:100%;min-height:90px;resize:vertical}
button{background:var(--orange);border-color:var(--orange);color:#12140f;font-weight:600;cursor:pointer}
button:hover{filter:brightness(1.1)}
button.sec{background:var(--panel2);border-color:var(--line);color:var(--fg)}
button.danger{background:var(--red-fill);border-color:var(--red-fill);color:#fff}
/* A row of controls that sit beside each other.
   NOT form-scoped. It was `form.inline`, and two places in the templates put
   the class on a <div>: the add-comment controls on a ticket and the domain
   row on a customer. Neither got any of this, so the select, the attach
   button and the submit stacked at their default sizes and lined up with
   nothing. A layout class that only works on one element type is a class that
   silently does nothing on the others. */
.inline{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
/* A bare select in such a row sizes to its content rather than filling the
   row. .field select is width:100%, which is right inside a labelled field and
   wrong here. */
.inline > select{width:auto;min-width:0}
/* A LABELLED field inside an inline form.
   .field select is width:100%, which resolves against whatever the flex
   algorithm hands the field. With no basis set that was the whole panel, so
   the two Merge dropdowns stretched edge to edge and the row stopped reading
   as a row at all. A basis gives them a sane size and they still shrink on a
   narrow screen. */
form.inline .field{flex:0 1 260px;min-width:150px}
/* Bottom-aligned only where there are labels to align under: centring a
   label-plus-control against a bare button puts the control half a label
   higher than the thing it sits next to. Every other inline form has no
   labels and wants the centring it already has. */
form.inline:has(.field){align-items:flex-end}

.row{display:grid;gap:14px;grid-template-columns:1fr 1fr}
.two{display:grid;gap:18px;grid-template-columns:2fr 1fr;align-items:start}
label{display:block;font-size:11px;color:var(--dim);margin-bottom:5px;
  text-transform:uppercase;letter-spacing:.5px}
.field{margin-bottom:14px}
.field input,.field select{width:100%}

.event{padding:12px 0;border-bottom:1px solid var(--line)}
.event:last-child{border-bottom:none}
.event .meta{font-size:12px;color:var(--dim);margin-bottom:5px}
.event .body{white-space:pre-wrap}
.event.public{border-left:3px solid var(--green);padding-left:12px}
.event.internal{border-left:3px solid var(--line);padding-left:12px}

/* An email rendered in its own markup.
 *
 * Everything here is containment. The sanitiser has already dropped the tags
 * and attributes that could execute, but a message is still free to declare a
 * 900px table or a white background, and the console has to survive that:
 *
 *   - contain:content plus a new stacking context, so nothing inside can
 *     paint over the page around it.
 *   - overflow-x:auto, so a wide table scrolls inside its own box rather than
 *     pushing the whole layout sideways.
 *   - white-space:normal, undoing .body's pre-wrap: the markup carries its own
 *     line breaks and pre-wrap would double every one of them.
 *   - Colours reset to ours at the root. A message written for a white
 *     background is black-on-black here, and its own inline colours still win
 *     wherever it set them deliberately.
 */
.emailhtml{white-space:normal;overflow-x:auto;contain:content;isolation:isolate;
  color:var(--fg);font-size:13.5px;line-height:1.55;max-height:70vh;overflow-y:auto}
.emailhtml *{max-width:100%}
.emailhtml img{height:auto;border-radius:var(--radius)}
.emailhtml a{color:var(--orange)}
.emailhtml table{border-collapse:collapse;margin:10px 0}
.emailhtml td,.emailhtml th{border:1px solid var(--line);padding:5px 8px;
  text-align:left;vertical-align:top}
.emailhtml blockquote{margin:10px 0;padding-left:12px;border-left:2px solid var(--line2);
  color:var(--dim)}
.emailhtml pre{white-space:pre-wrap;background:var(--panel2);padding:9px;
  border-radius:var(--radius);overflow-x:auto}
.emailhtml h1,.emailhtml h2,.emailhtml h3{font-size:15px;margin:12px 0 6px}
.emailhtml p{margin:0 0 9px}
.emailhtml hr{border:none;border-top:1px solid var(--line);margin:12px 0}

.err{background:rgba(224,82,82,.12);border:1px solid var(--red);color:#ff9d9d;
  padding:10px 14px;border-radius:var(--radius);margin-bottom:16px}
.empty{padding:40px;text-align:center;color:var(--dim2)}
.banner{background:var(--orange-a10);border:1px solid rgba(244,117,33,.35);
  padding:12px 16px;border-radius:var(--radius-lg);margin-bottom:18px}

/* ---------- week calendar ---------- */

.calbar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px}
.calbar .nav{display:flex;align-items:center;gap:2px;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}
.calbar .nav a,.calbar .nav span{padding:7px 12px;color:var(--dim);font-size:13px;white-space:nowrap}
.calbar .nav a:hover{background:var(--panel2);color:var(--fg);text-decoration:none}
.calbar .nav .cur{color:var(--fg);font-weight:600}
.calbar .tot{color:var(--dim);font-size:13px}
.calbar .tot b{color:var(--fg);font-variant-numeric:tabular-nums}
/* The ends are rounded and the dividers are not, so the control reads as one
   pill rather than three buttons pushed together. The children carry their own
   radius as well as the parent: overflow:hidden clips the background, but the
   active segment's inset box-shadow follows the element's OWN corners, so a
   square child draws a square outline inside a curved parent. */
.seg{display:flex;border:1px solid var(--line);border-radius:var(--radius-lg);
  overflow:hidden;margin-left:auto}
.seg a{padding:7px 14px;font-size:13px;color:var(--dim);border-right:1px solid var(--line)}
.seg a:first-child{border-top-left-radius:calc(var(--radius-lg) - 1px);
  border-bottom-left-radius:calc(var(--radius-lg) - 1px)}
.seg a:last-child{border-right:none;
  border-top-right-radius:calc(var(--radius-lg) - 1px);
  border-bottom-right-radius:calc(var(--radius-lg) - 1px)}
.seg a:hover{background:var(--panel2);color:var(--fg);text-decoration:none}
.seg a.on{background:var(--orange-a18);color:var(--orange);font-weight:700;
  box-shadow:inset 0 0 0 1px var(--orange)}

.cal{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-lg);
  overflow:hidden;display:flex;flex-direction:column;min-height:0}
.calhead{display:grid;grid-template-columns:70px repeat(7,1fr);border-bottom:1px solid var(--line);
  background:var(--panel);flex:0 0 auto}
.calhead .gut{border-right:1px solid var(--line)}
.calhead .dh{padding:9px 10px 0;border-right:1px solid var(--line);
  display:flex;align-items:flex-start;gap:8px;flex-wrap:wrap;position:relative}
.calhead .dh:last-child{border-right:none}
.dnum{font-size:21px;font-weight:700;line-height:1;color:var(--fg);letter-spacing:-.02em;
  padding:3px 7px;border-radius:999px;margin:-3px -3px 0}
.dnum:hover{background:var(--panel3);text-decoration:none}
.calhead .dh.today .dnum{background:var(--orange);color:#12140f}
.dmeta{display:flex;flex-direction:column;gap:1px;padding-top:1px}
/* The day name reads with the number rather than under it: same white, close
   to the same weight. Ten pixels of dim grey beside a twenty-one pixel white
   numeral made the number the only thing on the row anybody could see. */
.calhead .dh .dow{font-size:12.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:.6px;color:var(--fg)}
/* The day's total, in the UI font rather than monospace. Toggl sets these in
   its interface face and it reads as a label, not a data field. */
.calhead .dh .tot{font-size:12px;color:var(--dim);font-family:var(--font);
  font-weight:400;font-variant-numeric:tabular-nums}

/* Utilisation under each day. Toggl draws this and it is the fastest way to
   see a thin day without reading a single number. */
.util{position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--line)}
.util i{display:block;height:100%;background:var(--orange)}

/* Unscheduled strip: entries with no start time. They are real work, they just
   do not know when they happened - so they sit above the grid until dragged in
   rather than being placed at an invented hour. */
.unsched{display:grid;grid-template-columns:70px repeat(7,1fr);border-bottom:1px solid var(--line);
  min-height:34px;background:var(--panel2)}
.unsched .lab{font-size:10px;text-transform:uppercase;letter-spacing:.6px;color:var(--dim2);
  padding:9px 6px;border-right:1px solid var(--line);text-align:right}
.unsched .ucell{border-right:1px solid var(--line);padding:4px;display:flex;flex-wrap:wrap;gap:4px}
.unsched .ucell:last-child{border-right:none}

/* The grid takes whatever height is left and FITS inside it.
   
   It used to scroll, which meant the bottom of the day was off screen and the
   week you were looking at was never the whole week. The hour height is now
   computed from the space actually available, so the full range is visible with
   no scrollbar at any window size. overflow stays auto rather than hidden: if
   the fit ever fails, a scrollbar is honest and clipped entries are not. */
/* Where the week went, by project. Toggl draws this under the date navigation
   and it answers the question before you have read a number: one glance says
   whether the week was one job or five.
   
   Each label sits directly above its own segment, so the bar needs no legend.
   A narrow segment truncates its label rather than pushing the others out of
   alignment. */
.weekalloc{display:flex;align-items:flex-end;gap:2px;padding:0 20px 8px;flex:0 0 auto}
.wa{min-width:0;display:flex;flex-direction:column;gap:4px}
.walabel{display:block;font-size:10px;font-weight:700;text-transform:uppercase;
  letter-spacing:.7px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  color:var(--orange)}
.walabel em{font-style:normal;font-weight:600;color:var(--dim2)}
.walabel i{font-style:normal;font-weight:600;color:var(--dim2);
  font-family:var(--mono);letter-spacing:0;margin-left:5px}
/* The planned figure, marked as an addition rather than a total. */
.walabel u{text-decoration:none;font-weight:600;color:var(--orange);
  font-family:var(--mono);letter-spacing:0;margin-left:4px;opacity:.85}

/* The bar splits within each project: full strength for time that HAPPENED,
   faded for time that is only planned. Keeping them apart matters more than
   the split by project does - one is a record and the other is an intention,
   and a single solid bar would read as a week that is already full.
   
   FADED rather than hatched on purpose: a diagonal hatch already means
   non-billable on a calendar block, and one pattern with two meanings is worse
   than either choice alone. */
.wabar{display:flex;height:3px;border-radius:999px;overflow:hidden;gap:1px;
  background:transparent}
.wabar i{display:block;height:100%;border-radius:999px}

/* An empty week keeps the strip's height and says so quietly. Drawn as an
   empty track rather than blank space, so it reads as "nothing here yet"
   instead of as a bar that failed to render. */
.weekalloc.empty .walabel{color:var(--dim2);font-weight:600}
.weekalloc.empty .wabar{background:var(--line)}
.wabar .lg{background:var(--orange)}
.wabar .pl{background-color:var(--pc,var(--orange));opacity:.32}

/* Whose week this is, in the toolbar rather than a heading above it. */
.whose{font-weight:600;font-size:13px;color:var(--dim);white-space:nowrap}
.calbar .tot.planned b{color:var(--orange)}

.calbody{position:relative;overflow-y:auto;flex:1;min-height:0;
  scrollbar-width:thin;scrollbar-color:var(--orange) transparent}
/* Toggl's scrollbar: a thin line in the brand colour rather than the platform
   slab, which on a dark panel reads as a seam down the side of the page. */
.calbody::-webkit-scrollbar{width:6px}
.calbody::-webkit-scrollbar-track{background:transparent}
.calbody::-webkit-scrollbar-thumb{background:var(--orange);border-radius:999px}
.calbody::-webkit-scrollbar-thumb:hover{background:var(--orange-light)}
@media(min-height:900px){:root{--hr:54px}}
@media(min-height:1200px){:root{--hr:60px}}
.calgrid{display:grid;grid-template-columns:70px repeat(7,1fr);position:relative}
.gutter{border-right:1px solid var(--line);position:relative}
.gutter .hr{height:var(--hr);position:relative}
.gutter .hr span{position:absolute;top:-7px;right:7px;font-size:11px;color:var(--dim2);
  font-variant-numeric:tabular-nums}
.daycol{border-right:1px solid var(--line);position:relative;cursor:crosshair}
.daycol:last-child{border-right:none}
.daycol .hr{height:var(--hr);border-bottom:1px solid var(--line)}
.daycol .hr.half{border-bottom-style:dotted;border-bottom-color:var(--line)}
.daycol.weekend{background:rgba(255,255,255,.012)}

/* The now line, as Toggl draws it. */
.nowline{position:absolute;left:0;right:0;height:0;border-top:2px solid var(--orange);z-index:4;
  pointer-events:none}
.nowline::before{content:'';position:absolute;left:-4px;top:-4px;width:7px;height:7px;
  border-radius:50%;background:var(--orange)}

.blk{position:absolute;left:3px;width:calc(100% - 6px);right:auto;border-radius:5px;padding:4px 7px 15px;overflow:hidden;
  font-size:11.5px;line-height:1.3;cursor:grab;user-select:none;
  background-color:var(--orange-a18);border:1px solid var(--orange);color:var(--fg);
  border-left:3px solid var(--pc,var(--orange))}
/* Logged time is dark grey; PLANNED time carries the project colour.
   
   That is the opposite of the obvious assignment, and it is the right way
   round: the calendar is mostly a record of what happened, so the record is
   the calm background and the plan is the thing that stands out and asks to be
   dealt with. The colour is the project's, so a week's plan reads by project
   at a glance. */
.blk.logged{background-color:var(--panel3);border-color:var(--line2);color:var(--fg);
  border-left-color:var(--pc,var(--dim2))}
.blk.logged .p{color:var(--dim)}
/* Planned: solid orange, all four edges. Not dashed - a dotted outline reads
   as unsaved or invalid, and a plan is neither. */
/* A plan is orange, but quietly. The fill was strong enough that the orange
   project name on top of it had almost no contrast left, which is the one
   thing on the block worth reading. Toggl does the same: a muted wash, and the
   text keeps its colour. */
.blk.planned{background-color:var(--planned-soft);border-color:var(--orange-soft);
  border-left-color:var(--orange)}
.blk.planned .p{color:var(--dim)}
.blk.planned .p .pn{color:var(--orange-light)}
.blk.planned .t,.blk.planned .d{color:var(--fg)}
/* No hatch on a plan. Billability is not settled for work that has not
   happened, so marking it non-billable is noise, and the diagonal lines read
   as something being wrong with the block. Just orange. */
.blk.planned.nonbill{background-image:none}
/* PLANNED sits bottom RIGHT, opposite the duration, not top right.
   
   It is absolutely positioned, so it reserves no space, and the title line runs
   the full width underneath it. On a wide block that is invisible; on a block
   squeezed by two others sharing the hour it is not, because the ellipsis falls
   at the block's edge and the tail of the customer name ends up printed
   underneath the badge. Moving it down gives the whole top line back to the
   title and pairs it with the duration on one baseline. */
.blk.planned::before{content:'PLANNED';position:absolute;bottom:3px;right:6px;
  font-size:8.5px;font-weight:700;letter-spacing:.9px;color:var(--orange-light);opacity:.85}
.blk.planned.tiny::before{display:none}
/* Below about 104px the badge and the duration would meet in the middle, which
   is the same collision one corner down. The badge goes rather than the
   duration: the block is already orange and already sits in the future, so
   PLANNED is the most redundant thing on it, while the duration is the number
   somebody came to the calendar to read. */
.blk.planned.narrow::before{display:none}
/* A chip is position:static, so the badge - which is position:absolute - does
   not anchor to it at all. It escapes to whichever ancestor happens to be
   positioned and prints PLANNED in the corner of the strip instead of on the
   chip that owns it. The chip keeps the orange wash, which is the same tell
   the grid uses and the only one that fits on a pill this size. */
.blk.chip.planned::before{display:none}

.blk:hover{filter:brightness(1.18);z-index:3}
.blk.dragging{opacity:.75;cursor:grabbing;z-index:6}
/* The description WRAPS, up to half the block, then ellipsises.
   
   One nowrap line meant a description said "Replaced the level transmitter
   and re-ran the..." and stopped, on a block with four empty lines under it.
   Wrapping without a limit is the opposite mistake: a long note would push the
   project and the customer out of a short block entirely, and those are what
   identify it.
   
   Half is the compromise, and it is half of THIS block rather than a fixed
   number of lines, so it scales from a 45-minute entry to an 8-hour one. The
   line count is set on the element by markBlockFit, which is already measuring
   the box; CSS cannot divide a percentage height by a line height on its own.
   
   overflow-wrap keeps a pasted URL or a long part number inside the block
   instead of letting it run out through the side. */
.blk .t{font-weight:600;overflow:hidden;white-space:normal;overflow-wrap:anywhere;
  display:-webkit-box;-webkit-box-orient:vertical;
  -webkit-line-clamp:var(--tlines,1);line-clamp:var(--tlines,1)}
.blk .p{color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.blk .p .pn{color:var(--pc,var(--orange));font-weight:600}
/* Who the work is for. Dimmer than the project, because the project is what
   you are looking for and the customer is what confirms it. */
.blk .p .cn{color:var(--dim)}
.blk .p .cn::before{content:"\00b7\00a0"}

/* On its own line once the block is sharing its hour, or is narrow enough that
   one line cannot hold both.
   
   Side by side, "PLQ Resource - Humanised Group" has a third of a column to
   live in and clips to "PLQ Reso... Humani...", which names neither. Stacked,
   each gets the full width of the block and both read. The middot goes with
   it: it joined two things on one line and there is only one line now.
   
   BOTH get the block treatment, not just the customer. text-overflow on .p
   only reaches its own inline content, and a block-level child wraps what is
   around it in anonymous boxes the ellipsis no longer applies to, so the
   project would clip mid-glyph while the customer under it ellipsised
   properly.
   
   .shared comes from the LANE COUNT and .cramped from a measured width, and
   both are wanted. Two boxes beside each other is the case somebody sees on a
   busy day; a single block on a phone is narrow without sharing with anybody. */
.blk.shared .p .pn,
.blk.cramped .p .pn,
.blk.shared .p .cn,
.blk.cramped .p .cn{display:block;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.blk.shared .p .cn::before,
.blk.cramped .p .cn::before{content:none}
/* The duration, pinned to the bottom of the block in white. It is the number
   being read off a calendar; the start and finish are already obvious from
   where the block sits, and spelling them out was the least useful line in it. */
.blk .d{position:absolute;left:7px;bottom:3px;color:#fff;font-weight:600;
  font-variant-numeric:tabular-nums;text-shadow:0 1px 2px rgba(0,0,0,.45)}
/* A short block has no room for three lines; the duration wins. */
.blk.tiny{padding-bottom:4px}
.blk.tiny .p,.blk.tiny .t{display:none}
.blk.tiny .d{position:static}
/* No project needs no rule of its own: .blk.logged falls back to grey when
   --pc is unset, and a PLANNED block keeps its orange edge either way. The
   rule that used to live here sat after .blk.planned and repainted it grey. */
/* Not billable: a hatch LAID OVER whatever the state colour is, rather than
   replacing it, so a planned non-billable block still reads as planned. */
.blk.nonbill{background-image:repeating-linear-gradient(135deg,
  rgba(255,255,255,.06) 0 6px, rgba(255,255,255,0) 6px 12px)}
.blk.locked{cursor:not-allowed;opacity:.85}
/* Bottom right, for the same reason PLANNED is: pinned top right it reserved
   no space, the title ran underneath it, and the ellipsis landed exactly
   there. Approved and planned are different statuses and never appear on one
   block, so they can share the corner. */
.blk.locked::after{content:'✓';position:absolute;bottom:3px;right:6px;font-size:10px;color:var(--green)}
.blk.running{border-style:dashed}
.blk .grip{position:absolute;left:0;right:0;bottom:0;height:6px;cursor:ns-resize}
.blk.locked .grip{display:none}
.blk.chip{position:static;display:inline-flex;gap:6px;align-items:center;padding:3px 8px;
  border-radius:999px;font-size:11px;cursor:grab}

/* Ghost drawn while dragging out a new entry. Solid, like the block it is
   about to become: a dashed outline mid-drag reads as an error state. */
.ghost{position:absolute;left:3px;width:calc(100% - 6px);right:auto;border-radius:5px;pointer-events:none;z-index:5;
  background:var(--planned);border:1px solid var(--orange);
  font-size:11px;color:#fff;padding:3px 6px;font-variant-numeric:tabular-nums}

/* The popover header, in Toggl's arrangement: the state on the left, then the
   timer, the pencil and the overflow menu, then close. */
.pop .pophd .pstate{font-size:10px;font-weight:700;letter-spacing:1.1px;
  color:var(--dim2);padding-left:2px}
.pop .pophd .pstate.is-planned{color:var(--orange)}
.pop .pophd .rowbtn.play{background:var(--orange);border-color:var(--orange)}
.pop .pophd .rowbtn.play i{background:#12140f}
.pop .pophd .rowbtn.play:hover{filter:brightness(1.1)}
.pop .times .pday{font-size:12.5px;color:var(--dim);white-space:nowrap;flex:0 0 auto;
  padding:6px 8px;background:var(--panel3);border-radius:var(--radius)}
.pop .popft{display:flex;gap:8px;justify-content:flex-end;margin-top:12px}

/* ---------- entry editor ---------- */

.sheet{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:50;display:grid;place-items:center;
  padding:20px}
.sheetbox{background:var(--panel);border:1px solid var(--line2);border-radius:var(--radius-lg);
  width:min(460px,100%);max-height:90vh;overflow-y:auto;box-shadow:0 24px 60px rgba(0,0,0,.6)}
.sheetbox .hd{padding:14px 18px;border-bottom:1px solid var(--line);font-weight:600;
  display:flex;align-items:center;gap:10px}
.sheetbox .hd button{margin-left:auto;background:none;border:none;color:var(--dim);
  font-size:20px;line-height:1;padding:0 4px;cursor:pointer}
.sheetbox .bd{padding:18px}
.sheetbox .ft{padding:14px 18px;border-top:1px solid var(--line);display:flex;gap:10px;align-items:center}
.sheetbox .ft .sp{margin-left:auto}
.chk{display:flex;align-items:center;gap:8px;text-transform:none;font-size:13px;color:var(--fg)}
.chk input{width:auto;margin:0}

/* The attendance list on a minute. A column per name would run off the page at
   sixteen staff and a single row would wrap into an unreadable ribbon, so it is
   a grid that fits as many as the width allows. */
.whowas{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));
  gap:6px 14px;margin-top:4px}

.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:60;
  background:var(--panel3);border:1px solid var(--line2);border-radius:var(--radius);
  padding:10px 16px;font-size:13px;box-shadow:0 8px 24px rgba(0,0,0,.5);max-width:min(560px,90vw)}
.toast.bad{border-color:var(--red);color:#ff9d9d}

/* ---------- responsive ---------- */

@media(max-width:1100px){.two,.row{grid-template-columns:1fr}}
@media(max-width:900px){
  .side{width:var(--side-min);flex-basis:var(--side-min)}
  .side .bname,.side .sec,.side nav a span,.side .wname,.side .collapse{display:none}
  .side .brandbar,.side .whoami{justify-content:center;padding:0 8px}
  .side nav a{justify-content:center;padding:9px 0}
  .tproj{display:none}
  main{padding:16px}
}
@media(max-width:640px){
  /* Seven days will not fit on a phone, so the calendar scrolls sideways as
     ONE piece.

     It used to scroll in pieces, which is worse than not scrolling at all:
     overflow-x was put on .calbody and on .calwrap, a class that exists in no
     template, so the grid scrolled and the day headers above it did not. The
     columns then said Monday while the blocks under them were Thursday's, and
     the "no time" row did not move either. A calendar whose headings do not
     match its contents is not a slow calendar, it is a wrong one.

     .cal is the scroller now, so the headings, the unscheduled row and the
     grid move together and stay lined up. The 700px floor is what seven
     columns plus a gutter need to stay readable; below that the columns become
     too narrow to hold a block's label. */
  .cal{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}
  .calhead,.calgrid,.unsched{grid-template-columns:62px repeat(7,minmax(90px,1fr));
    min-width:700px}
  .calbody{overflow-x:hidden;min-width:700px}
  /* One day at a time is the better phone answer, and the day numbers already
     link to it. Made obvious rather than left as a hint nobody would guess:
     tap a date, get that day full width. */
  .dnum{text-decoration:underline;text-decoration-color:var(--orange);
    text-underline-offset:3px}

  /* ---------- tables become cards ----------

     THIRTY-SIX TABLES CARRIED THIS CLASS AND NOTHING IMPLEMENTED IT. The
     markup was right - class="stack" on the table, data-label on every cell -
     and only portal.css ever turned it into anything, so on the staff console
     those tables scrolled sideways on a phone exactly as if the class were not
     there. Inert markup is the worst kind of wrong: it reads as done in every
     template and does nothing in every browser.

     The mobile gate passed too, because it checked that a stacked table
     LABELS its cells and never that anything stacks. It checks both now.

     A row becomes a card and each cell becomes a line with its heading in
     front of it, taken from data-label. The thead is hidden rather than
     deleted: it is still the accessible header for the cells, and on a wide
     screen it is still the table's headings. */
  table.stack,
  table.stack tbody,
  table.stack tr,
  table.stack td{display:block;width:100%}
  table.stack thead{display:none}
  table.stack tr{border:1px solid var(--line);border-radius:var(--radius);
    margin:0 0 10px;padding:4px 0;background:var(--panel2)}
  table.stack tr:last-child{margin-bottom:0}
  table.stack td{border:none;padding:7px 12px;white-space:normal;
    display:flex;align-items:baseline;gap:10px}
  table.stack td::before{content:attr(data-label);flex:0 0 84px;
    color:var(--dim2);font-size:10.5px;font-weight:700;text-transform:uppercase;
    letter-spacing:.5px;line-height:1.7}
  /* A cell with no label is a control or a continuation, not a field: giving
     it an empty 84px gutter would indent it under nothing. */
  table.stack td:not([data-label])::before,
  table.stack td[data-label=""]::before{display:none}
  /* A number is read against its label, not against the numbers above it, so
     the right alignment that makes a column scan goes away with the column. */
  table.stack td.num{text-align:left;justify-content:flex-start}
  /* The wrapper exists to let a wide table scroll. Nothing is wide any more. */
  .tablewrap:has(table.stack){overflow:visible}
}

/* ---------- settings sub-navigation ---------- */

.subnav{display:flex;gap:4px;margin:-6px 0 20px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.subnav a{padding:9px 14px;color:var(--dim);font-size:13.5px;border-bottom:2px solid transparent;
  margin-bottom:-1px}
.subnav a:hover{color:var(--fg);text-decoration:none}
.subnav a.on{color:var(--orange);border-bottom-color:var(--orange);font-weight:600}
/* The risk matrix. Colour carries the band, and so does the number in the
   cell: a grid that means nothing in greyscale or to a colourblind reader is a
   grid that means nothing to some of the people who have to use it. */
.matrix{border-collapse:separate;border-spacing:3px;min-width:520px}
.matrix th{font-size:12px;color:var(--dim);font-weight:600;padding:6px 8px;text-align:center}
.matrix thead th{vertical-align:bottom}
.matrix tbody th{text-align:right;white-space:nowrap}
.matrix .mcorner{text-align:left;color:var(--dim2)}
.matrix td{text-align:center;font-weight:700;font-size:15px;padding:12px 8px;
  border-radius:var(--radius);font-variant-numeric:tabular-nums}
.m-low{background:var(--panel2);color:var(--dim)}
.m-med{background:rgba(74,163,110,.16);color:var(--green)}
.m-high{background:rgba(214,158,46,.18);color:var(--amber)}
.m-ext{background:var(--red-fill);color:#fff}

/* The second level, on a page that has both. Same control, drawn lighter: two
   strips at the same weight read as one confused row rather than as a
   hierarchy. Sits tight under the first, with no rule of its own, so the pair
   reads as a heading and its contents. */
.subnav-2{margin:-14px 0 20px;border-bottom:none;gap:2px}
.subnav-2 a{padding:7px 12px;font-size:13px;border-bottom-width:2px}
.subnav-2 a.on{font-weight:600}

/* ---------- pickers, popovers, context menus ---------- */

/* A popover, not a modal. Dimming the page to ask which project three hours
   belong to hides the calendar you need in order to answer. */
.pop{position:fixed;z-index:60;width:min(380px,92vw);background:var(--panel);
  border:1px solid var(--line2);border-radius:var(--radius-lg);
  box-shadow:0 18px 48px rgba(0,0,0,.55)}
.pophd{display:flex;align-items:center;gap:10px;padding:11px 14px;
  border-bottom:1px solid var(--line);font-weight:600;font-size:13.5px}
/* Only the FIRST button takes the auto margin. Putting it on every button
   distributes the free space between them, which spreads the play/edit/more/
   close run right across the header instead of grouping it on the right. */
.pophd button{background:none;border:none;color:var(--dim);cursor:pointer;
  font-size:18px;line-height:1;padding:0 2px}
.pophd > button:first-of-type{margin-left:auto}
.popbd{padding:14px}
.popbd .when{margin:0 0 12px;font-size:12px}
.popbd .hint{margin:10px 0 0;font-size:12px;color:var(--amber);min-height:1em}
.popft{padding:11px 14px;border-top:1px solid var(--line);display:flex;gap:8px;align-items:center}
.linkedit{background:none;border:none;cursor:pointer;font-size:13px;color:var(--dim);
  padding:3px 8px;border-radius:var(--radius)}
.linkedit:hover{background:var(--panel2);color:var(--orange)}
/* Editing a task is a real action, not an afterthought.
   It used to be a .linkedit: grey text that appeared only on row hover, which
   is right for a control you use once in a while and wrong for the one an
   operator reaches for constantly on a project. Same orange as the primary
   button elsewhere, sized down so a column of them does not shout. */
.taskedit{background:var(--orange);border:1px solid var(--orange);color:#12140f;
  font-weight:600;cursor:pointer;font-size:12px;line-height:1.2;
  padding:5px 13px;border-radius:var(--radius)}
.taskedit:hover{filter:brightness(1.1)}
.modalbox.wide{max-width:720px}
/* A list row with an edit affordance that only appears on hover, so the panel
   stays readable when nobody is editing. */
.editrow{display:flex;align-items:flex-start;gap:10px;margin-bottom:10px}
.editrow > div{flex:1;min-width:0}
.editrow .linkedit{opacity:0;transition:opacity .12s}
.editrow:hover .linkedit,.editrow .linkedit:focus{opacity:1}
.linkdel{margin-left:auto;background:none;border:none;cursor:pointer;font-size:13px;
  color:#ff9d9d;padding:6px 8px;border-radius:var(--radius)}
.linkdel:hover{background:rgba(224,82,82,.12);color:#ffb3b3}

/* ===========================================================================
   A record that is read, with the form that changes it folded away.
   ===========================================================================

   A customer's page was five open forms stacked on top of each other and three
   thousand pixels tall, on a page opened almost entirely to LOOK SOMETHING UP.
   The account panel alone was eleven hundred pixels of input boxes. An input box
   is not a fact: it is a fact plus an invitation to change it, and stacking a
   page of invitations buries the record underneath them.

   So the facts are rendered as facts and every form sits behind one line the
   reader can open. No JavaScript: <details> has done this natively for years and
   a fold that depends on a script is a fold that fails silently. */
.factlist{display:grid;grid-template-columns:auto 1fr;gap:6px 14px;margin:0;
  font-size:13.5px;align-items:baseline}
.factlist dt{color:var(--dim2);font-size:11.5px;font-weight:700;
  letter-spacing:.5px;text-transform:uppercase;white-space:nowrap}
.factlist dd{margin:0;min-width:0;overflow-wrap:anywhere}

/* The fold itself. The summary is the panel's own header when it is standing in
   for one, and a quieter bar when it sits under a body that is already there. */
.panelfold > summary{cursor:pointer;list-style:none}
.panelfold > summary::-webkit-details-marker{display:none}
.panelfold > summary::before{content:"\203A";display:inline-block;margin-right:8px;
  color:var(--dim2);transition:transform .12s;transform-origin:50% 50%}
.panelfold[open] > summary::before{transform:rotate(90deg)}
.panelfold > summary:focus-visible{outline:2px solid var(--orange);outline-offset:-2px}
.foldbar{padding:11px 16px;border-top:1px solid var(--line);color:var(--dim);
  font-size:12.5px;font-weight:600}
.foldbar:hover{background:var(--panel2);color:var(--fg)}

/* ===========================================================================
   Set-up: what is not running by itself yet.
   ===========================================================================

   Three columns rather than prose, because the three questions are always the
   same ones and in the same order: what is missing, what that costs, and can the
   system fix it. On a narrow screen they stack in that order too, which is why
   the fix is last in the markup rather than pinned to the right by a float. */
.gaps .bd{display:flex;flex-direction:column;gap:0}
.gap{display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap;
  padding:11px 0;border-top:1px solid var(--line)}
.gap:first-child{border-top:none;padding-top:0}
.gapwhat{flex:0 0 auto}
.gapwhy{flex:1 1 300px;min-width:0;font-size:13px;color:var(--dim)}
.gapfix{flex:0 0 auto;margin-left:auto}
@media (max-width:640px){.gapfix{margin-left:0}}

/* A remaining figure that has gone through the cap. */
td .over{color:var(--red);font-weight:600}

.pickers{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.picker{display:inline-flex;align-items:center;gap:7px;background:var(--panel2);
  border:1px solid var(--line);color:var(--dim);border-radius:var(--radius);
  padding:7px 11px;font-size:12.5px;cursor:pointer;max-width:100%}
.picker span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.picker:hover:not(:disabled){border-color:var(--line2);color:var(--fg)}
.picker.set{color:var(--orange);border-color:rgba(244,117,33,.45);background:var(--orange-a10)}
.picker.set .ic{background:var(--orange)}
.picker:disabled{opacity:.45;cursor:not-allowed}
.picker .ic{width:15px;height:15px;flex:0 0 15px}

/* Solid folder, the glyph Toggl uses for its project picker. */
.ic-folder{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'><path d='M4 4h5.2l2 2.4H20a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'><path d='M4 4h5.2l2 2.4H20a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z'/></svg>")}
/* A plain check box for the task picker. The label beside it says what it is,
   so the glyph does not also need a tick in it. */
.ic-task{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><rect x='4' y='4' width='16' height='16' rx='3'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><rect x='4' y='4' width='16' height='16' rx='3'/></svg>")}
.ic-gear{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'><circle cx='12' cy='12' r='3'/><path d='M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 1 1-4 0v-.1A1.6 1.6 0 0 0 7.9 19.4l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.6 1.6 0 0 0 3 14.6a2 2 0 1 1 0-4h.1A1.6 1.6 0 0 0 4.6 7.9l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1A1.6 1.6 0 0 0 10 3.1V3a2 2 0 1 1 4 0v.1a1.6 1.6 0 0 0 2.7 1.1l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0 1.1 2.7H21a2 2 0 1 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1.3z'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'><circle cx='12' cy='12' r='3'/><path d='M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 1 1-4 0v-.1A1.6 1.6 0 0 0 7.9 19.4l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.6 1.6 0 0 0 3 14.6a2 2 0 1 1 0-4h.1A1.6 1.6 0 0 0 4.6 7.9l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1A1.6 1.6 0 0 0 10 3.1V3a2 2 0 1 1 4 0v.1a1.6 1.6 0 0 0 2.7 1.1l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0 1.1 2.7H21a2 2 0 1 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1.3z'/></svg>")}
.ic-cal{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><rect x='3' y='5' width='18' height='16' rx='2'/><path d='M3 10h18M8 3v4M16 3v4'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><rect x='3' y='5' width='18' height='16' rx='2'/><path d='M3 10h18M8 3v4M16 3v4'/></svg>")}

.finder{position:fixed;z-index:70;width:min(320px,92vw);background:var(--panel);
  border:1px solid var(--line2);border-radius:var(--radius);overflow:hidden;
  box-shadow:0 14px 36px rgba(0,0,0,.55)}
.finder input{width:100%;border:none;border-bottom:1px solid var(--line);border-radius:0;
  background:var(--panel2);padding:10px 12px}
.finder input:focus{outline:none;border-bottom-color:var(--orange)}
.flist{max-height:260px;overflow-y:auto;padding:4px}
.fitem{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;
  border:none;color:var(--fg);padding:8px 10px;border-radius:var(--radius);cursor:pointer;
  font-size:13px}
.fitem span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fitem em{margin-left:auto;font-style:normal;color:var(--dim2);font-size:11.5px;white-space:nowrap}
.fitem:hover,.fitem.sel{background:var(--orange-a18);color:var(--orange)}
.fitem .dot{width:8px;height:8px;border-radius:50%;flex:0 0 8px}
.fnone{padding:14px;color:var(--dim2);font-size:12.5px;text-align:center}

.menu{position:fixed;z-index:75;min-width:170px;background:var(--panel);
  border:1px solid var(--line2);border-radius:var(--radius);padding:4px;
  box-shadow:0 14px 36px rgba(0,0,0,.55)}
.mitem{display:block;width:100%;text-align:left;background:none;border:none;color:var(--fg);
  padding:8px 11px;border-radius:4px;cursor:pointer;font-size:13px}
.mitem:hover{background:var(--panel3)}
/* A menu row, not a filled button.
   `.mitem` sets background:none at one class of specificity and button.danger
   sets a red fill at one class plus an element, so the fill won and the row
   came out solid red with the light pink text meant for a dark background:
   #ff9d9d on #ef6b6b is 1.51:1, which is what a screenshot of it looks like.
   The hover rule below gives it away - a 12% translucent red only makes sense
   over a transparent base. Neutralised here, where the specificity wins. */
.mitem.danger{background:none;border-color:transparent;color:#ff9d9d}
.mitem.danger:hover{background:var(--red-a12);color:#ffb3b3}
.msep{height:1px;background:var(--line);margin:4px 6px}

/* A block drawn but not yet saved. Solid, matching the ghost it grew out of;
   the lighter fill is what says it is still landing. */
/* Drawn but not yet saved. Opaque for the same reason as the ghost: a
   translucent fill showed the gridlines through it, which read as stripes. */
.blk.provisional{background:var(--planned);border:1px solid var(--orange);
  border-left:3px solid var(--orange);color:var(--fg);cursor:default}
/* Bottom left, which is where .blk .d puts the duration on every saved block.
   It used to sit top right while drawing and then jump to the bottom left the
   moment the box was released, so the one number you are watching moved corner
   to corner at the exact moment you stopped watching it. */
.ghost{font-weight:600;display:flex;align-items:flex-end;justify-content:flex-start}

/* The same ghost, carrying an entry that already knows how long it is.
   Two lines rather than one: what it is at the top, when it would start and
   finish at the bottom, on the edge you are actually lining up against the
   hour rules. The title is clipped rather than wrapped, because a box being
   dragged must not change shape as it passes over a narrow column. */
.ghost.carry{flex-direction:column;align-items:stretch;justify-content:space-between;
  padding:4px 7px;gap:2px}
.ghost.carry .gt{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ghost.carry .gd{font-weight:600;white-space:nowrap;overflow:hidden}

/* Sidebar icons added so no two destinations share a silhouette. On a phone
   the sidebar becomes a scrolling bottom bar with a 10.5px label, so the
   icon is what people actually navigate by. Purchase orders and Engagements
   both drew a document; Invoicing and Payroll both drew a banknote. */
/* Engagements: a briefcase. Contracted work, as opposed to the purchase
   order document beside it. */
.ic-case{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><rect x='2' y='7' width='20' height='13' rx='2'/><path d='M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2'/><path d='M2 13h20'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><rect x='2' y='7' width='20' height='13' rx='2'/><path d='M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2'/><path d='M2 13h20'/></svg>")}
/* Payroll: a wallet. Money going out to people, which is the opposite
   direction to the invoicing note. */
.ic-wallet{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M3 7a2 2 0 0 1 2-2h12v4'/><rect x='3' y='7' width='18' height='13' rx='2'/><path d='M16 13.5h3'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M3 7a2 2 0 0 1 2-2h12v4'/><rect x='3' y='7' width='18' height='13' rx='2'/><path d='M16 13.5h3'/></svg>")}
/* Invoicing: a receipt, torn along the bottom. Distinct at 20px from both
   the plain document and the banknote. */
.ic-invoice{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M5 3h14v18l-3-2-2 2-2-2-2 2-2-2-3 2z'/><path d='M9 8h6M9 12h6'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M5 3h14v18l-3-2-2 2-2-2-2 2-2-2-3 2z'/><path d='M9 8h6M9 12h6'/></svg>")}
/* Timeline: bars of different lengths at different offsets, which is what
   the page actually draws. A calendar grid read as another square next to
   the four squares of Projects. */
.ic-gantt{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M4 6h10M7 12h13M4 18h8'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M4 6h10M7 12h13M4 18h8'/></svg>")}
/* Approvals: a clipboard with a tick. The old circled tick was a circle
   with a mark in it, and so is the clock on My time: at 20px they were the
   same shape twice. */
.ic-shield{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3l7 3v5.5c0 4.4-2.9 8.3-7 9.5-4.1-1.2-7-5.1-7-9.5V6z'/><path d='M9 12l2 2 4-4'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3l7 3v5.5c0 4.4-2.9 8.3-7 9.5-4.1-1.2-7-5.1-7-9.5V6z'/><path d='M9 12l2 2 4-4'/></svg>")}
/* Safety: a hard hat. Not the shield, which the compliance registers own,
   and not a warning triangle, which reads as an error state rather than
   as a place to go. */
.ic-hardhat{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 18h18'/><path d='M5 18v-2a7 7 0 0 1 14 0v2'/><path d='M10 4.6A2 2 0 0 1 12 3a2 2 0 0 1 2 1.6l.6 4.2'/><path d='M9.4 8.8L10 4.6'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 18h18'/><path d='M5 18v-2a7 7 0 0 1 14 0v2'/><path d='M10 4.6A2 2 0 0 1 12 3a2 2 0 0 1 2 1.6l.6 4.2'/><path d='M9.4 8.8L10 4.6'/></svg>")}
/* Policies: a bound document, distinct from the shield the compliance
   registers use and from the loose page of ic-doc. */
.ic-book{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 5a2 2 0 0 1 2-2h12v18H6a2 2 0 0 1-2-2z'/><path d='M8 7h7'/><path d='M8 11h7'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 5a2 2 0 0 1 2-2h12v18H6a2 2 0 0 1-2-2z'/><path d='M8 7h7'/><path d='M8 11h7'/></svg>")}
.ic-clipcheck{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M9 4H7a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2'/><rect x='9' y='2' width='6' height='4' rx='1'/><path d='M9 13l2 2 4-4'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M9 4H7a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2'/><rect x='9' y='2' width='6' height='4' rx='1'/><path d='M9 13l2 2 4-4'/></svg>")}
/* Customers: companies, not people. The person glyph is what Settings uses
   for staff, and the two lists are not the same list. */
.ic-building{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M3 21h18M5 21V5a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v16'/><path d='M15 9h4a2 2 0 0 1 2 2v10'/><path d='M8 7h4M8 11h4M8 15h4'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M3 21h18M5 21V5a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v16'/><path d='M15 9h4a2 2 0 0 1 2 2v10'/><path d='M8 7h4M8 11h4M8 15h4'/></svg>")}

/* ---------- timeline ---------- */

.tl{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-lg);
  overflow:hidden;display:flex;flex-direction:column;min-height:0}
.tlhead{display:flex;border-bottom:1px solid var(--line);background:var(--panel);
  flex:0 0 auto}
.tlname{width:210px;flex:0 0 210px;border-right:1px solid var(--line);padding:8px 12px}
.tlgrid{flex:1;display:grid;grid-template-columns:repeat(14,minmax(0,1fr));position:relative}
.tld{padding:6px 8px;border-right:1px solid var(--line);text-align:center;font-size:11px}
.tld:last-child{border-right:none}
.tld b{display:block;font-size:14px;font-weight:600}
.tld span{color:var(--dim2);text-transform:uppercase;letter-spacing:.5px;font-size:9.5px}
.tld.today b{color:var(--orange)}
.tld.weekend{background:rgba(255,255,255,.015)}

.tlbody{overflow-y:auto;flex:1;min-height:0}
.tlrow{display:flex;border-bottom:1px solid var(--line);min-height:56px}
.tlrow:last-child{border-bottom:none}
.tlrow .tlname{display:flex;flex-direction:column;justify-content:center;gap:5px}
.tlrow .n{font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tlrow .sub{font-size:11.5px}
.cap{display:flex;flex-direction:column;gap:3px;font-size:11px}
.cap .bar{display:block;height:4px;background:var(--panel3);border-radius:999px;overflow:hidden}
.cap .bar i{display:block;height:100%;background:var(--green);border-radius:999px}
.cap .bar i.over{background:var(--red)}
.overtxt{color:var(--red);font-weight:600}

.lanes{position:relative;align-content:start;padding:6px 0;row-gap:4px}
.tlcell{position:absolute;top:0;bottom:0;border-right:1px solid var(--line);pointer-events:none}
.lanes .tlcell{position:static;grid-row:1;height:0;border-right:1px solid var(--line);
  align-self:stretch;pointer-events:none}
.tlcell.weekend{background:rgba(255,255,255,.015)}
.tlcell.today{background:var(--orange-a10)}

.tlbar{grid-row:auto;margin:0 2px;background:var(--orange-a18);border:1px solid var(--orange);
  border-left:3px solid var(--orange);border-radius:5px;padding:5px 8px;font-size:11.5px;
  display:flex;align-items:center;gap:8px;cursor:grab;user-select:none;min-width:0;
  position:relative;z-index:2}
.tlbar .t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
.tlbar .h{margin-left:auto;color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap}
.tlbar:hover{filter:brightness(1.15)}
.tlbar.dragging{opacity:.75;cursor:grabbing;z-index:6}
.tlbar.unassigned{background:var(--panel3);border-color:var(--line2);border-left-color:var(--dim2)}
.tlbar.ghostbar{background:var(--orange-a10);border:1px dashed var(--orange);color:var(--orange);
  justify-content:center;pointer-events:none}
.calbar .tot.over{color:var(--red);font-weight:600}

@media(max-width:900px){
  .tlname{width:140px;flex-basis:140px}
  .tlbody,.tlhead{overflow-x:auto}
}

/* ---------- list toolbar ---------- */

.toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:16px}
.dd{position:relative}
.ddbtn{display:inline-flex;align-items:center;gap:7px;background:var(--panel);
  border:1px solid var(--line);color:var(--fg);border-radius:var(--radius);
  padding:8px 13px;font-size:13px;cursor:pointer;list-style:none;white-space:nowrap}
.ddbtn::-webkit-details-marker{display:none}
.ddbtn:hover{border-color:var(--line2);background:var(--panel2);text-decoration:none}
.ddbtn.active{border-color:rgba(244,117,33,.45);background:var(--orange-a10);color:var(--orange)}
.ddbtn.active .ic{background:var(--orange)}
.ddbtn .ic{width:14px;height:14px;flex:0 0 14px}
.dd[open] .ddbtn{border-color:var(--line2);background:var(--panel2)}
/* max-width is the floor under .flip below: nothing may open wider than the
   window, for a menu whose button is itself close enough to the edge that
   neither side fits. It lived in its own second .ddmenu rule further down so it
   could be read after the flip it backs up; it is here now, because a selector
   written twice in one file is how the .pdot and .paydue tangles started. */
.ddmenu{position:absolute;top:calc(100% + 6px);left:0;z-index:40;min-width:210px;
  max-width:calc(100vw - 24px);
  max-height:340px;overflow-y:auto;background:var(--panel);border:1px solid var(--line2);
  border-radius:var(--radius);padding:5px;box-shadow:0 14px 36px rgba(0,0,0,.55)}
.ddmenu.wide{min-width:260px}
/* Opened from the button's right edge instead, for a dropdown near the side of
   the window. Set by measurement on open, because which side has room is a
   fact about the window rather than about the markup. */
.ddmenu.flip{left:auto;right:0}
.ddsec{font-size:10px;font-weight:700;letter-spacing:1px;text-transform:uppercase;
  color:var(--dim2);padding:9px 10px 5px}
.ddmenu a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:4px;
  color:var(--fg);font-size:13px}
.ddmenu a em{margin-left:auto;font-style:normal;color:var(--dim2);font-size:11.5px}
.ddmenu a:hover{background:var(--panel3);text-decoration:none}
.ddmenu a.on{background:var(--orange-a18);color:var(--orange);font-weight:600}
.ddmenu a.on em{color:var(--orange)}
.clearf{font-size:12.5px;color:var(--dim)}

.ic-list{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01'/></svg>")}
.ic-filter{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M3 4h18l-7 8v6l-4 2v-8z'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><path d='M3 4h18l-7 8v6l-4 2v-8z'/></svg>")}
.ic-group{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'><rect x='3' y='4' width='18' height='6' rx='1'/><rect x='3' y='14' width='18' height='6' rx='1'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2'><rect x='3' y='4' width='18' height='6' rx='1'/><rect x='3' y='14' width='18' height='6' rx='1'/></svg>")}
.ic-sort{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M7 4v16M7 20l-3-3M7 20l3-3M17 20V4M17 4l-3 3M17 4l3 3'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M7 4v16M7 20l-3-3M7 20l3-3M17 20V4M17 4l-3 3M17 4l3 3'/></svg>")}
.ic-paperclip{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48'/></svg>")}
.ic-plus{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M12 5v14M5 12h14'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M12 5v14M5 12h14'/></svg>")}
.ic-out{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M15 17l5-5-5-5M20 12H9M12 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M15 17l5-5-5-5M20 12H9M12 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6'/></svg>")}

/* ---------- project list ---------- */

/* Rows, not a spreadsheet. Eight equal-width columns of mostly dashes reads as
   a database dump; the name earns the space and the numbers sit where the eye
   already goes for them. */
.grouphd{display:flex;align-items:baseline;gap:12px;padding:14px 4px 6px;font-size:12.5px}
.grouphd .gname{font-weight:700;font-size:13.5px}
/* One line per project rather than two.
   The name sat above the customer and the kind, which made every row two lines
   tall for two pieces of text that read perfectly well side by side, and a
   list of twenty projects needed scrolling to see ten. */
.plist{display:flex;flex-direction:column;gap:5px}
.prow{display:flex;align-items:center;gap:16px;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--radius);padding:8px 14px;
  color:var(--fg);position:relative;overflow:hidden}
.prow:hover{border-color:var(--line2);background:var(--panel2);text-decoration:none}
/* The project dot is the shared .pdot near the top of this file. It used to be
   redefined here, which is what made it a mongrel; see the note up there. */
/* RENAMED FROM .pmain, WHICH THE CUSTOMER PORTAL ALSO USED for its main
   column. Two unrelated components shared the name in this one file, and this
   definition comes later, so it won for both: every portal page's <main> became
   a baseline-aligned flex row and laid its top level blocks out side by side.
   The filter form, the export link and the empty state all ended up on one line
   with the form squeezed. Named for its parent .prow so the relationship is
   visible and the collision cannot come back by accident. */
.prowmain{flex:1;min-width:0;display:flex;align-items:baseline;gap:10px;
  flex-wrap:wrap;row-gap:2px}
.pname{font-size:13.5px;font-weight:600;display:flex;align-items:center;gap:7px;
  flex-wrap:wrap;min-width:0}
.psub{font-size:12px;color:var(--dim);display:flex;align-items:center;gap:9px;
  flex-wrap:wrap;min-width:0}
/* The figure and its label side by side, for the same reason. */
.pstat{flex:0 0 118px;display:flex;align-items:baseline;justify-content:flex-end;
  gap:5px;text-align:right}
.pstat.wide{flex-basis:158px}
.pstat b{font-size:13.5px;font-weight:600;font-variant-numeric:tabular-nums}
.pstat em{font-style:normal;font-size:10px;color:var(--dim2);text-transform:uppercase;
  letter-spacing:.5px;white-space:nowrap}
.pbar{position:absolute;left:0;right:0;bottom:0;height:2px;background:transparent}
.pbar i{display:block;height:100%;background:var(--orange);max-width:100%}
.pbar i.over{background:var(--red)}

@media(max-width:1000px){
  .prow{flex-wrap:wrap;gap:6px 16px}
  /* Renamed with its sibling above: under 1000px this was also reshaping the
     portal's main column, on exactly the widths a customer is most likely to
     be reading on. */
  .prowmain{flex-basis:100%}
  .pstat{flex-basis:auto;text-align:left;justify-content:flex-start}
}

/* ---------- footer version ---------- */

.verfoot{padding:10px 24px 18px;text-align:right;color:var(--dim2);font-size:11px;
  font-variant-numeric:tabular-nums}
.verfoot a{color:var(--dim2)}
.verfoot a:hover{color:var(--dim)}

/* Not on a phone.
   Measured at 390x844: the strip is 45px, and the shell came to 890 against an
   844 viewport, so saying the build number WAS the entire reason the dashboard
   scrolled. A screen this size cannot spend a bar on a fact nobody reads.
   The build moves into the menu sheet instead, one tap away and costing the
   page nothing. Kept here rather than in the phone block below so that
   deleting the footer takes its phone rule with it. */
@media(max-width:720px){ .verfoot{display:none} }

/* ---------- flash toast ---------- */

/* Floats. An inline banner pushed the whole page down on every save, so the
   thing you were looking at moved the moment you confirmed you were done with
   it. Green means it worked and red means it did not, because a single colour
   for both teaches people to ignore the colour. */
.flash{position:fixed;top:calc(var(--top) + 14px);right:20px;z-index:80;
  max-width:min(460px,calc(100vw - 40px));padding:12px 16px;border-radius:var(--radius);
  font-size:13.5px;line-height:1.45;box-shadow:0 14px 36px rgba(0,0,0,.5);
  animation:flashin .18s ease-out}
.flash.ok{background:rgba(63,191,127,.14);border:1px solid var(--green);color:#9ff0c4}
.flash.bad{background:rgba(224,82,82,.14);border:1px solid var(--red);color:#ffb3b3}
.flash.hiding{animation:flashout .25s ease-in forwards}
/* An error copies itself on click, so it says so and leaves room for the
   dismiss button rather than putting it on top of the last line of text. */
.flash.copyable{cursor:copy;padding-right:34px}
.flashx{position:absolute;top:6px;right:8px;width:20px;height:20px;padding:0;
  line-height:1;font-size:16px;background:none;border:none;color:inherit;
  opacity:.65;cursor:pointer}
.flashx:hover{opacity:1;background:none}
.flashcopied{display:block;margin-top:6px;font-size:11px;font-weight:600;
  letter-spacing:.4px;text-transform:uppercase;opacity:0;
  transition:opacity .12s ease-out}
.flashcopied.show{opacity:.9}
.flashcopied.bad{color:#ffd9a1}
@keyframes flashin{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
@keyframes flashout{to{opacity:0;transform:translateY(-8px)}}
@media(prefers-reduced-motion:reduce){
  .flash,.flash.hiding{animation:none}
}

/* ---------- live refresh pill ---------- */

/* Bottom centre, deliberately: the top right is where the flash toast and the
   timer live, and a permanent element there would compete with both. */
.freshpill{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);z-index:75;
  display:flex;align-items:center;gap:9px;padding:9px 16px;border-radius:999px;
  background:var(--panel);border:1px solid var(--orange);color:var(--fg);
  font-size:13px;font-weight:600;cursor:pointer;
  box-shadow:0 12px 30px rgba(0,0,0,.45);animation:pillin .22s ease-out}
.freshpill:hover{background:var(--panel2)}
.freshpill .fdot{width:8px;height:8px;border-radius:50%;background:var(--orange);
  animation:pillpulse 1.8s ease-in-out infinite}
@keyframes pillin{from{opacity:0;transform:translate(-50%,10px)}
  to{opacity:1;transform:translate(-50%,0)}}
@keyframes pillpulse{0%,100%{opacity:1}50%{opacity:.35}}
@media(prefers-reduced-motion:reduce){
  .freshpill{animation:none}
  .freshpill .fdot{animation:none}
}

/* ---------- modal dialog ---------- */

.modal{position:fixed;inset:0;z-index:70;background:rgba(0,0,0,.55);display:grid;
  place-items:center;padding:20px}
.modalbox{background:var(--panel);border:1px solid var(--line2);border-radius:var(--radius-lg);
  width:min(660px,100%);max-height:90vh;display:flex;flex-direction:column;
  box-shadow:0 24px 60px rgba(0,0,0,.6)}
.modalhd{display:flex;align-items:center;gap:10px;padding:14px 18px;
  border-bottom:1px solid var(--line);font-weight:600}
.modalhd button{margin-left:auto;background:none;border:none;color:var(--dim);
  font-size:20px;line-height:1;padding:0 4px;cursor:pointer}
.modalbd{padding:18px;overflow-y:auto}
.modalft{padding:14px 18px;border-top:1px solid var(--line);display:flex;gap:10px;
  align-items:center;flex-wrap:wrap}

/* A whole row that behaves like a link. The pointer and the hover are what say
   so, since a <tr> cannot be an <a>. */
.clickrow{cursor:pointer}
.clickrow:hover{background:var(--panel3)}
.clickrow:focus-visible{outline:2px solid var(--orange);outline-offset:-2px}

/* ---------- entry editor, Toggl's layout ---------- */

.entrypop{width:min(430px,94vw)}
.entrypop .pophd{border-bottom:none;padding:10px 12px 0;gap:4px}
.rowbtn{display:inline-flex;align-items:center;background:none;border:none;
  color:var(--dim2);cursor:pointer;padding:6px;border-radius:var(--radius);line-height:0}
.rowbtn:hover{background:var(--panel3);color:var(--fg)}
.rowbtn .ic{width:16px;height:16px;background:currentColor}
.entrypop .popbd{padding:6px 16px 16px}
.pdesc{width:100%;background:transparent;border:none;color:var(--fg);font-size:18px;
  font-weight:500;letter-spacing:-.012em;padding:8px 0 12px}
.pdesc:focus{outline:none}
.pdesc::placeholder{color:var(--dim2);font-weight:500}
/* One row, same as Toggl. The project pill is the only one allowed to shrink,
   so a long project name ellipsises rather than pushing the task and billable
   controls onto a second line. */
.entrypop .pickers{margin-bottom:14px;flex-wrap:nowrap}
.entrypop .picker{flex:0 0 auto}
.entrypop #p-project{flex:0 1 auto;min-width:0}
.picker.icon-only{padding:7px 9px}
.picker.icon-only span{display:none}

/* Toggl sets the times and the duration in its UI face, not a monospace one.
   Tabular figures keep the columns from jittering as the digits change, which
   is the only thing the monospace was buying. nowrap because the duration
   belongs on the same line as the times it describes: at 8px gaps the row
   came to 399px inside a 396px body and wrapped by three pixels. */
.times{display:flex;align-items:center;gap:7px;flex-wrap:nowrap}
.tin{background:var(--panel2);border:1px solid var(--line);color:var(--fg);
  border-radius:var(--radius);padding:7px 9px;font-size:13px;
  font-variant-numeric:tabular-nums;width:112px}
.tin:focus{outline:none;border-color:var(--orange)}
.times .arrow{color:var(--dim2);flex:0 0 auto}
.times .dur{font-size:14px;color:var(--dim);font-variant-numeric:tabular-nums;
  white-space:nowrap;min-width:60px;text-align:right}
.times button{margin-left:auto;padding:9px 22px}

/* Toggl grows the panel from its centre. Decorative, and skipped for anyone
   who has asked for less motion. */
@keyframes popin{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
.popin{animation:popin .13s cubic-bezier(.2,.8,.3,1)}
@media(prefers-reduced-motion:reduce){.popin{animation:none}}

.ic-play{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'><path d='M8 5v14l11-7z'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'><path d='M8 5v14l11-7z'/></svg>")}
.ic-pencil{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 20h9'/><path d='M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 20h9'/><path d='M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z'/></svg>")}
.ic-dots{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'><circle cx='12' cy='5' r='2'/><circle cx='12' cy='12' r='2'/><circle cx='12' cy='19' r='2'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000'><circle cx='12' cy='5' r='2'/><circle cx='12' cy='12' r='2'/><circle cx='12' cy='19' r='2'/></svg>")}
.ic-copy{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><rect x='9' y='9' width='12' height='12' rx='2'/><path d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'><rect x='9' y='9' width='12' height='12' rx='2'/><path d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/></svg>")}
.ic-bin{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3'/></svg>")}
.ic-dollar{-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M12 2v20M17 6.5c0-2-2.2-3.5-5-3.5S7 4.5 7 6.5 9 10 12 10.5s5 1.5 5 3.5-2.2 3.5-5 3.5-5-1.5-5-3.5'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'><path d='M12 2v20M17 6.5c0-2-2.2-3.5-5-3.5S7 4.5 7 6.5 9 10 12 10.5s5 1.5 5 3.5-2.2 3.5-5 3.5-5-1.5-5-3.5'/></svg>")}

/* ---------- timesheet ---------- */

/* The timesheet grid, in Toggl's shape.
 *
 * A week of rows against seven day columns, every cell an editable duration.
 * The important bits are the ones that make it scannable rather than a
 * spreadsheet: today's whole column is marked, not just its header; an empty
 * cell is BLANK rather than 0:00, so the eye lands on the days with work; and
 * the row and column totals sit in their own weight so they read as answers
 * instead of more data. */
table.ts{border-collapse:separate;border-spacing:0}
table.ts th,table.ts td{border-bottom:1px solid var(--line)}

table.ts th.num,table.ts td.num{text-align:right;font-family:var(--mono);white-space:nowrap}
table.ts thead th{padding:10px 8px;vertical-align:bottom;background:var(--panel)}
table.ts th.tsproj{text-align:left;min-width:220px}
table.ts .tsdow{display:block;font-size:10px;font-weight:700;text-transform:uppercase;
  letter-spacing:.9px;color:var(--dim2);font-family:var(--font)}
table.ts .tsdate{display:block;font-size:17px;font-weight:700;line-height:1.25;
  font-family:var(--font);letter-spacing:-.01em}

/* Today, through the whole column. Toggl marks the column rather than the
   header, and it is the difference between finding today and hunting for it. */
table.ts th.today .tsdate{display:inline-block;background:var(--orange);color:#12140f;
  border-radius:999px;padding:1px 8px;margin-right:-2px}
table.ts td.today{background:var(--orange-a08)}
table.ts tfoot td.today{background:var(--orange-a18)}

table.ts td{padding:6px 8px;vertical-align:middle}
table.ts td.tsproj{text-align:left}
table.ts .tsname{display:inline-block;vertical-align:middle;line-height:1.3}
table.ts .tsname em{display:block;font-style:normal;font-size:11.5px;color:var(--dim2)}
table.ts .pdot{display:inline-block;margin-right:8px;vertical-align:middle}

/* A cell reads as text until you touch it. A grid of forty visible input boxes
   is a form; this is a timesheet. */
input.tscell{width:100%;min-width:62px;text-align:right;font-family:var(--mono);
  font-size:13.5px;padding:7px 8px;background:transparent;border:1px solid transparent;
  border-radius:var(--radius)}
input.tscell::placeholder{color:transparent}
input.tscell:hover:not([readonly]){border-color:var(--line2);background:var(--panel2)}
input.tscell:hover:not([readonly])::placeholder{color:var(--dim2)}
input.tscell:focus{border-color:var(--orange);background:var(--panel2);outline:none;
  box-shadow:0 0 0 2px var(--orange-a18)}
input.tscell:focus::placeholder{color:var(--dim2)}
input.tscell[readonly]{color:var(--dim);cursor:not-allowed}

table.ts td.tstotal,table.ts th.tstotal{border-left:1px solid var(--line2)}
table.ts .rowtotal{font-family:var(--mono)}
table.ts tfoot td{border-top:1px solid var(--line2);border-bottom:none;font-weight:700;
  font-family:var(--mono);background:var(--panel2)}
table.ts tfoot td.tsproj{font-family:var(--font)}

/* The clear-row control appears on hover, so forty of them are not competing
   with the numbers. */
table.ts .tsactions{width:34px;padding:0 6px}
table.ts .tsdel{opacity:0;transition:opacity .12s;font-size:16px;line-height:1;
  padding:2px 7px}
table.ts tr:hover .tsdel,table.ts .tsdel:focus{opacity:1}

.tsweek{font-family:var(--mono);font-size:17px;font-weight:700;text-align:right;
  line-height:1.15}
.tsweek em{display:block;font-style:normal;font-family:var(--font);font-size:10px;
  font-weight:700;text-transform:uppercase;letter-spacing:.9px;color:var(--dim2)}

.dim2{color:var(--dim2)}
.tsadd td{padding:12px 12px}
.tsadd .picker{font-size:12.5px}

/* ===========================================================================
   Phones.
   ===========================================================================

   Everything above assumes a mouse and a wide screen. Below 720px neither is
   true, and the three things that actually break are the shell, the top bar
   and every table.

   The breakpoint is 720px rather than 640: a landscape phone and a small
   tablet both want the phone layout, and the desktop shell is unusable long
   before 640px, where the sidebar alone is a fifth of the screen.

   Kept as one block at the end of the file, so what a phone gets is readable
   in one place instead of scattered through a dozen components.
   ------------------------------------------------------------------------ */

/* ---------- phone menu ---------- */

/* Neither exists above the phone breakpoint. Kept next to the block that
   builds them rather than filed under the settings sub-navigation, where
   deleting that section would have taken this reset with it and put the bar on
   every desktop page. */
.mobbar,.mobscrim{display:none}

@media(max-width:720px){
  /* --top is NOT overridden: .flash positions itself with calc(var(--top) +
     14px), and calc(auto + 14px) is invalid, which drops the declaration and
     drops the flash message into the corner of the page. The top bar sets its
     own height below instead. */
  :root{ --mobnav:56px }

  /* The shell stacks, and the page keeps clear of the menu bar at the bottom. */
  .shell{display:block;min-height:100vh;padding-bottom:calc(var(--mobnav) + env(safe-area-inset-bottom,0px))}

  /* The bar itself: a button that says Menu, where you already are, and a way
     out. Three things, all of them readable, instead of sixteen icons behind a
     horizontal scroll. */
  /* z-index ABOVE the sheet. The sheet is bottom:0 and paints its background
     over the whole bar, so at a lower z-index the Menu button is covered by the
     thing it opened and tapping it again does nothing. That is the affordance
     everybody reaches for first. The sheet reserves the bar's height as padding
     so nothing is hidden underneath. */
  .mobbar{display:flex;align-items:center;gap:10px;
    position:fixed;left:0;right:0;bottom:0;z-index:48;
    height:calc(var(--mobnav) + env(safe-area-inset-bottom,0px));
    padding:0 12px env(safe-area-inset-bottom,0px);
    background:var(--panel);border-top:1px solid var(--line)}
  .mobmenu{display:inline-flex;align-items:center;gap:9px;
    min-height:42px;padding:0 15px 0 13px;border-radius:var(--radius);
    background:var(--panel2);border:1px solid var(--line);color:var(--fg);
    font-size:14.5px;font-weight:600;cursor:pointer}
  .mobmenu:active{background:var(--orange-a08)}
  /* Three bars, drawn rather than shipped as an icon, because it is the one
     glyph every phone user already reads without a label. */
  .burger{width:16px;height:12px;flex:0 0 auto;position:relative}
  .burger,.burger::before,.burger::after{
    border-top:2px solid currentColor;box-sizing:border-box}
  .burger::before,.burger::after{content:"";position:absolute;left:0;right:0}
  .burger::before{top:4px}
  .burger::after{top:9px}
  /* Where you are. Truncated rather than wrapped: the bar is a fixed height and
     a two-line title would push the button off it. */
  .mobwhere{flex:1 1 auto;min-width:0;color:var(--dim);font-size:13px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mobout{display:inline-flex;align-items:center;justify-content:center;
    width:42px;height:42px;flex:0 0 auto;color:var(--dim)}

  .mobscrim{position:fixed;inset:0;z-index:46;background:rgba(0,0,0,.45)}

  /* The sheet. Slides up from the bar, stops short of the top so the page
     behind stays visible and it reads as a panel rather than a new page. */
  .side{position:fixed;left:0;right:0;bottom:0;top:auto;
    width:auto;flex-basis:auto;height:auto;max-height:85vh;
    flex-direction:column;align-items:stretch;
    border-right:none;border-top:1px solid var(--line);
    border-radius:var(--radius-lg) var(--radius-lg) 0 0;
    padding-bottom:calc(var(--mobnav) + env(safe-area-inset-bottom,0px));
    /* No overflow here: the nav below is the scrolling element, and a second
       scroll container that never engages is one a future editor sets a scroll
       position on by mistake. */
    z-index:47;transform:translateY(101%);transition:transform .18s ease}
  .side.open{transform:translateY(0)}
  @media(prefers-reduced-motion:reduce){.side{transition:none}}

  /* The brand bar becomes the sheet's header, and the collapse control becomes
     its close. Same element doing the job the shape now calls for. */
  .side .brandbar{display:flex;padding:12px 14px 10px;border-bottom:1px solid var(--line)}
  .side .collapse{display:inline-flex;transform:rotate(90deg);font-size:22px}
  .side .mark{display:none}

  /* A vertical list of NAMES. The section headings come back, because in a list
     of sixteen they are the difference between scanning and reading. */
  .side nav{display:flex;flex-direction:column;gap:1px;padding:8px;
    -webkit-overflow-scrolling:touch;scrollbar-width:none}
  .side nav::-webkit-scrollbar{display:none}
  .side .sec{display:block;padding:12px 12px 4px}
  .side nav a{flex:0 0 auto;flex-direction:row;gap:12px;justify-content:flex-start;
    min-height:46px;padding:0 12px;font-size:15px;text-align:left;border-radius:var(--radius)}
  /* display, explicitly. The tablet rule at 900px turns the sidebar into a
     narrow icon rail and hides these; setting white-space on them here does not
     bring them back, and the sheet renders as a column of unlabelled glyphs,
     which is the exact problem this whole change exists to fix. */
  .side nav a span{display:inline;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .side nav a .ic{flex:0 0 auto}

  /* justify-content too, for the same reason: the 900px rule centres it for a
     rail, and a sheet reads as a list left to right.
     `.side` in the selector, because that is what the 900px rule uses and a
     bare `.whoami` loses to it. It was losing: measured on a 390px sheet the
     row came out centred at padding 0 8px, which is the rail shape this rule
     was written to undo. */
  .side .whoami,.side.mini .whoami{border-top:1px solid var(--line);border-left:none;
    padding:12px 14px;justify-content:flex-start}
  .whoami .wname{display:block}
  /* Where the version went. Under the name, in a sheet that is only on screen
     while somebody is holding it open, rather than a permanent strip above the
     bar on every page. */
  .wname .sidever{display:block;font-variant-numeric:tabular-nums}

  /* The collapse toggle's .mini class is meaningless for a sheet and must not
     shrink it.
     It must not do the REST of its job either, and that half was missing. The
     class is applied from localStorage on load whatever the screen width is,
     so collapsing the sidebar on a laptop and then opening the same account on
     a phone brought every .mini rule in the base block with it, each one
     out-specifying the phone rules above. Measured with .mini forced on at
     390px: no section headings, no username and therefore no version, the list
     of names centred with no left padding, the brand bar stripped to 0 8px and
     the close chevron rotated 180 instead of 90. Selector for selector,
     because anything shorter silently loses again. */
  .side.mini{width:auto;flex-basis:auto}
  .side.mini nav a span{display:block}
  .side.mini .wname,.side.mini .sec{display:block}
  .side.mini nav a{justify-content:flex-start;padding:0 12px}
  .side.mini .brandbar{padding:12px 14px 10px}
  .side.mini .collapse{transform:rotate(90deg)}
  .side.mini .whoami a{margin-left:auto}

  /* Nothing scrolls behind an open sheet. */
  body.navopen{overflow:hidden}

  /* The timer, on two rows: the description gets a whole one, the controls
     share the next. Squeezed onto one row the description ends up 40px wide
     and unusable, which is the fastest way to stop somebody tracking time. */
  .top{height:auto;min-height:var(--top);padding:8px 12px;gap:8px}
  .timerbar{flex-wrap:wrap;gap:8px}
  .tdesc{flex:1 0 100%;font-size:16px;border-color:var(--line);background:var(--panel2)}
  .tproj{display:inline-flex;flex:1 1 auto;min-width:0}
  .tproj span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .telapsed{margin-left:auto}

  main{padding:14px 12px 28px}
  h1{font-size:19px}

  /* Anything that was two or three columns becomes one. */
  .two,.row{grid-template-columns:1fr}
  .grid{gap:12px}

  /* Cards stay TWO across. They were one, which put eleven full-width tiles
     between the top of the dashboard and the first thing you can act on: a
     screen and a half of scrolling to reach the ticket list. The content of a
     card is a short number and a short label, so half a phone is ample. */
  .cards{grid-template-columns:1fr 1fr;gap:8px}
  .card{padding:11px 12px}
  .card .l{font-size:10px;letter-spacing:.4px;margin-top:3px}

  /* Not on a phone.
     Applied to the parts of the dashboard that are for reading at a desk
     rather than acting on in a corridor: the rings, which are a chart with a
     legend and unreadable at this width, and the scheduled-jobs table, which
     is four columns of operational detail that already alerts to Discord on
     its own and gets read every morning by the triage routine.
     Nothing here is the only copy of anything. */
  .deskonly{display:none !important}

  /* Tables scroll inside their own box. Without this the widest table on the
     page sets the width of the document and every other page element gets
     dragged sideways with it. */
  .tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
  table{font-size:12.5px}
  th,td{padding:8px 9px;white-space:nowrap}
  /* Except the columns that are prose, which should wrap rather than run. */
  td.wrap,td .body,.event .body{white-space:normal}

  /* ---------- My Time: the timesheet, stood up vertically ----------

     Measured at 390x844 before this: the grid was 860px inside a 388px box,
     so 472px of it was off the screen and reachable only by dragging the
     table sideways. Seven day columns plus a project name plus two totals is
     never going to fit, and shrinking the columns to make it fit would leave
     an entry field about 30px wide.

     So the grid turns. Each PROJECT becomes a card and each of its seven days
     becomes a labelled line down it, which is the same information read in
     the other direction. The markup does not change: the table is still a
     table, and every one of the script's row/cell lookups still resolves,
     because display:block hides a structure rather than removing it. */
  table.ts,table.ts tbody,table.ts tr,table.ts td{display:block;width:auto}
  /* The column headings are hidden, not deleted: repaintTotals() counts
     thead th.num to know how many days there are, and an empty thead would
     silently make every column total zero. */
  table.ts thead{display:none}
  /* And the footer goes, because its column totals are columns. The week total
     it also carries is already in the panel heading above, in bigger type. */
  table.ts tfoot{display:none}
  .tspanel .tablewrap{overflow:visible}

  table.ts tbody tr[data-project]{position:relative;
    border:1px solid var(--line);border-radius:var(--radius-lg);
    margin:0 0 10px;padding:2px 0;background:var(--panel2)}

  /* The card's heading: the project, its customer, and the row's clear button
     pinned to the corner. On a desktop that button appears on hover, which a
     phone does not have, so here it is simply always there. */
  table.ts td.tsproj{border-bottom:1px solid var(--line);
    padding:9px 44px 9px 12px;font-size:14.5px}
  table.ts td.tsactions{position:absolute;top:4px;right:4px;
    width:auto;padding:0;border:none}
  table.ts .tsdel{opacity:1;font-size:20px;padding:6px 10px;min-height:36px}

  /* A day. Label on the left, box on the right, the way it reads aloud. */
  table.ts tbody td.num{display:flex;align-items:center;gap:10px;
    border-bottom:none;padding:3px 10px;text-align:left}
  table.ts tbody td.num::before{content:attr(data-label);
    flex:1 1 auto;min-width:0;font-family:var(--font);font-size:13px;
    color:var(--dim);white-space:nowrap}
  /* The five days you are most likely to be filling in should not have to
     compete with the two you are not. */
  table.ts tbody td.tswknd::before{color:var(--dim2)}
  table.ts tbody td.today::before{color:var(--orange);font-weight:700}
  /* Visible edges. On a desktop a cell reads as text until you touch it,
     which works because the grid lines say where the boxes are; standing up,
     there is no grid left to imply them. */
  table.ts tbody td.num input.tscell{flex:0 0 116px;width:116px;min-width:0;
    border-color:var(--line);background:var(--panel);padding:9px 10px}
  /* The placeholder stays hidden. A desktop shows 0:00 on hover as a hint at
     what to type; a phone has no hover, so making it permanent instead would
     put a 0:00 on all seven lines of every card and lose the thing the blank
     cell is for, which is that the eye lands on the days with work. */
  /* Today's tint belongs on the input, not the whole line: across the full
     width of a card it reads as a selected row rather than as today. */
  table.ts tbody td.today{background:none}
  table.ts tbody td.today input.tscell{background:var(--orange-a08)}

  table.ts tbody td.tstotal{display:flex;align-items:center;justify-content:space-between;
    gap:10px;border-left:none;border-top:1px solid var(--line);
    margin-top:3px;padding:9px 12px;text-align:left}
  table.ts tbody td.tstotal::before{content:attr(data-label);
    font-family:var(--font);font-size:13px;color:var(--dim)}
  table.ts .rowtotal{font-size:15px}

  /* The add-a-row line spans nothing now, so it is just a block.
     white-space, explicitly: the phone rule above sets nowrap on every cell,
     and the sentence in here is 567px long. It was the last thing on the page
     still dragging the timesheet sideways after the grid was stood up, and it
     is invisible as a cause because the text is clipped rather than seen. */
  table.ts tr.tsadd{display:block;border:none;padding:0}
  table.ts tr.tsadd td{display:block;padding:0;border:none;white-space:normal}
  table.ts tr.tsadd .dim{display:block;margin:8px 2px 0 !important;font-size:12px;
    white-space:normal}

  /* ---------- My Time: the entry list, as cards ----------

     891px of table inside a 388px box, so 503px of every row was off the
     screen: the description was readable and the duration, the project and
     the edit button were not. Eight columns do not compress into 390px, and
     the ones that would have to give are the ones the list exists for.

     One card per entry instead. The description leads, because that is what
     the eye is looking for; everything else follows as labelled lines in the
     order you would ask for them. */
  table.entrylist,table.entrylist tbody,table.entrylist tr,
  table.entrylist td{display:block;width:auto}
  table.entrylist thead{display:none}
  #listpanel .tablewrap{overflow:visible}

  /* The card is a flex column, and the cells are placed with order rather
     than left in the order the columns happen to be in. The description is
     the third column and has to be the first line: it is the thing being
     scanned for, and a card that opens with the word DATE buries it. */
  table.entrylist tbody tr{position:relative;
    display:flex;flex-wrap:wrap;align-items:baseline;gap:0 10px;
    border:1px solid var(--line);border-radius:var(--radius-lg);
    margin:0 0 10px;padding:10px 12px 9px;background:var(--panel2)}
  table.entrylist tbody tr.picked{background:var(--orange-a08);border-color:var(--orange)}
  table.entrylist tbody td{border:none;padding:0;white-space:normal;text-align:left}
  /* Nothing renders for a value that is not there. */
  table.entrylist tbody td:empty{display:none}

  /* Room on the right for the select box, which is pinned to the corner so it
     lines up down a list of cards rather than moving with the description. */
  table.entrylist td.emain{order:1;flex:1 1 100%;
    font-size:15px;line-height:1.35;color:var(--fg);
    padding-right:26px;margin-bottom:5px}
  table.entrylist td.epick{position:absolute;top:9px;right:10px;padding:0}
  table.entrylist td.epick input{width:20px;height:20px}

  /* When, and how long, sharing one line. No captions in front of either: a
     date reads as a date and a duration reads as a duration, and an uppercase
     label gutter would take more width than the values it introduces. */
  table.entrylist td.eday{order:2;flex:0 0 auto;font-size:12.5px;color:var(--dim2)}
  table.entrylist td.etime{order:3;flex:0 0 auto;margin-left:auto;
    font-size:14.5px;font-weight:700;color:var(--fg)}

  /* The project shares its line with the flags and the buttons, which saves a
     whole row on every card in a list that is mostly rows. min-width:0 so a
     long project name gives way to the buttons rather than pushing them off;
     tags, when there are any, take the width they need and everything after
     them wraps under, which is the right shape for the uncommon case. */
  table.entrylist td.eproj{order:4;flex:1 1 auto;min-width:0;margin-top:6px;
    font-size:13px;color:var(--dim)}
  table.entrylist td.etags{order:5;flex:1 1 100%;margin-top:5px}
  table.entrylist td.eflags{order:6;flex:0 0 auto;margin-top:6px;font-size:12px}
  table.entrylist td.eacts{order:7;flex:0 0 auto;display:flex;
    justify-content:flex-end;gap:8px;margin-top:4px;margin-left:auto}
  /* The buttons are revealed on hover on a desktop, which on a phone means
     never revealed at all, so here they are always there and thumb-sized. */
  table.entrylist td.eacts .linkedit{opacity:1;min-height:36px;padding:0 13px;
    border:1px solid var(--line);border-radius:var(--radius)}

  /* The bulk bar wraps rather than scrolls: it appears only once something is
     selected, and a row of controls you have to drag sideways to reach is a
     row of controls nobody uses. */
  .bulkbar{flex-wrap:wrap;gap:8px}
  .bulkbar select,.bulkbar button{flex:1 1 auto;min-width:0}
  .bulkbar>span[style]{display:none}

  /* 16px or iOS zooms the whole page in on focus and never zooms back out. */
  input,select,textarea{font-size:16px}
  /* Fingers are not cursors. */
  button,.ddbtn,.picker,.seg a,.subnav a{min-height:40px}
  .toolbar{gap:6px}
  .toolbar .ddbtn,.toolbar select,.toolbar input{max-width:100%}
  .subnav{overflow-x:auto;flex-wrap:nowrap;scrollbar-width:none}
  .subnav::-webkit-scrollbar{display:none}
  .subnav a{flex:0 0 auto}

  /* Popovers and menus become sheets: anchoring a 430px panel to a control on
     a 390px screen puts half of it off the edge. */
  .pop,.finder,.menu{left:8px !important;right:8px !important;width:auto !important;
    max-width:none;max-height:80vh;overflow-y:auto}
  .entrypop{width:auto}
  /* .modal is the full-screen backdrop; the dialog inside it is .modalbox. */
  .modal{padding:12px}
  .modalbox{width:100%;max-height:86vh}

  /* The full-height pages keep their own scroll, minus the bottom bar.
     .main carries an explicit height because the shell is no longer a flex
     container here, so its flex:1 does nothing and the calendar would collapse
     to the height of its own content. */
  body.fills .shell{height:100vh;
    padding-bottom:calc(var(--mobnav) + env(safe-area-inset-bottom,0px))}
  body.fills .main{height:100%}
  body.fills main>.calbar,body.fills main>p.dim{padding-left:12px;padding-right:12px}

  /* ---- the calendar page on a phone: the calendar IS the page ----

     Measured before touching it, at 390x844: the controls above the calendar
     were 261px and the calendar itself 245px, of which the actual scrollable
     time grid was 114px. The calendar was 32% of the screen and its body 15%.

     The bar had no phone treatment at all, so its five groups each wrapped onto
     a line of their own. It also has FIVE children, not the three the markup
     suggests at a glance: the week arrows, whose week and the totals, the view
     segment, the timesheets-due pill and the copy-week form.

     Three rows, in the order somebody uses them: where you are and how it is
     shown, then what it adds up to, then the things you press once a week.

     min-width:0 on every child is the part that actually does the work. Grid
     and flex items refuse to shrink below their content by default, so a first
     attempt at this laid out a 525px column inside a 390px screen and moved
     nothing: the bar still wrapped, just inside a grid. */
  /* minmax(0,1fr), not 1fr. A 1fr track carries an implicit minimum of its own
     content, so min-width:0 on the CHILD is not enough on its own: the second
     attempt at this still laid out a 500px bar inside a 390px screen. Both the
     track and the item have to be told they may shrink. */
  body.fills .calbar{display:grid;grid-template-columns:auto minmax(0,1fr);
    gap:6px 8px;margin-bottom:8px;align-items:center}
  body.fills .calbar>*{min-width:0}

  body.fills .calbar>.nav{grid-column:1;grid-row:1;justify-self:start}
  body.fills .calbar .nav a,body.fills .calbar .nav span{padding:7px 8px;font-size:12.5px}

  /* Whose week and the four totals share row one with the arrows, scrolling
     sideways. Wrapped they were four separate rows; this is part of one. */
  body.fills .calbar>.tbmid{grid-column:2;grid-row:1;margin:0;gap:12px;
    flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;justify-content:flex-start}

  /* The view segment gets a ROW OF ITS OWN, and this is the second thing the
     screenshot corrected. Pinned beside the week arrows it held four controls -
     three view links, the settings cog, ask for leave and the span picker - in
     whatever was left of 390px, so it scrolled to its right-hand end and
     "Calendar" and "List view" sat off the left edge underneath the arrows.
     A control you cannot see is not a control. */
  body.fills .calbar>.tbright{grid-column:1 / -1;grid-row:2;justify-self:stretch;
    margin-left:0;gap:6px;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
  body.fills .calbar>.tbright::-webkit-scrollbar{display:none}
  /* Each control keeps its natural width and the ROW scrolls, the same way the
     compliance strip and the settings sub-navigation already behave at this
     width. Allowed to shrink, the segment was squeezed narrower than its own
     labels and "Calendar" was clipped inside a box with overflow:hidden, which
     reads as broken rather than as scrollable. */
  body.fills .calbar>.tbright>*{flex:0 0 auto}
  body.fills .calbar .seg a{padding:7px 9px;font-size:12.5px}
  body.fills .calbar>.tbmid::-webkit-scrollbar{display:none}
  body.fills .calbar .tot{font-size:12px;white-space:nowrap;flex:0 0 auto}
  body.fills .calbar .tbmid select{font-size:12.5px;padding:4px 8px;min-height:32px}

  /* Everything else - timesheets due, copy week - flows onto a third row
     rather than being pinned, because which of them exist depends on payroll
     being configured and on which view is open. */
  body.fills .calbar>form,body.fills .calbar>.paydue,
  body.fills .calbar>#subctl{grid-column:1 / -1;justify-self:start}
  body.fills .calbar .paydue{font-size:11.5px}

  /* The day headers were 81px carrying a weekday, a date and a total. The
     totals are in the strip above, so the header keeps what says WHICH DAY. */
  body.fills .calhead .dh{padding:5px 6px 0}
  body.fills .calhead .dh .dow{font-size:11px}
  body.fills .calhead .dh .tot{font-size:11px}

  /* The refresh pill sits above the bottom bar rather than under it. The
     flash is anchored to the top and needs no adjustment. */
  .freshpill{bottom:calc(var(--mobnav) + 12px + env(safe-area-inset-bottom,0px))}
  .flash{right:12px;left:12px}
}

/* Narrow phones: the calendar's seven day columns cannot be made to fit, so
   they scroll, and the week bar's labels are dropped rather than wrapped into
   an unreadable stack. */
@media(max-width:480px){
  .calhead,.calgrid,.unsched{grid-template-columns:52px repeat(7,minmax(84px,1fr))}
  .weekalloc{padding:0 12px 8px}
  .walabel em,.walabel u{display:none}
  .cards .card .n{font-size:22px}
  main{padding:12px 10px 24px}
}

/* The project status control. A row rather than a field in the edit dialog,
   because closing a project has consequences beyond this page and belongs in
   the open where its effect can be stated next to it. */
.statusbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin:-4px 0 18px;padding:9px 12px;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--radius)}
.statusbar .l{font-size:10px;font-weight:700;letter-spacing:1.1px;
  text-transform:uppercase;color:var(--dim2)}
.statusbar select{padding:6px 9px;font-size:13px}
.statusbar .dim{font-size:12.5px}

/* The payroll review table.
   One row per person. There used to be a second "Adjust" row under each one
   holding fourteen boxes that repeated the fourteen figures above them, which
   doubled the height of the table and made two rows that had to be read
   together to mean anything. The figures are now the boxes.
   A day over the cap is the only cell on this page anybody needs to look at
   twice, so it is the only thing that carries colour. */
table.payroll td.num{font-variant-numeric:tabular-nums}
table.payroll th.num,table.payroll td.num{min-width:66px}
/* The person and the dates are what you navigate this table by, and both were
   set in the same small dim grey as the figures they label. */
table.payroll thead th{font-size:12px;color:var(--fg);letter-spacing:.4px}
table.payroll td.who strong{font-size:14.5px;color:var(--fg)}

/* A figure you can type into, that does not announce itself as a form until
   you go near it. A grid of fourteen visible boxes per person reads as a form
   to be filled in; this reads as a table that happens to be correctable, which
   is what it is: most rows are approved without a keystroke. */
table.payroll input.dayin{width:100%;min-width:52px;text-align:right;
  font-variant-numeric:tabular-nums;font-size:13px;padding:4px 6px;
  background:transparent;border:1px solid transparent;border-radius:var(--radius);
  color:inherit;font-weight:inherit}
table.payroll input.dayin:hover{border-color:var(--line)}
table.payroll input.dayin:focus{border-color:var(--orange);background:var(--bg);
  outline:none}
/* No steppers, ever. Chrome and Safari draw them the moment a field is a
   number input, which is fourteen pairs of arrows per person, and on focus
   they sit on top of the worked figure underneath. Nobody nudges a timesheet
   a quarter hour at a time with a mouse; they type the number they were told. */
table.payroll input.dayin::-webkit-outer-spin-button,
table.payroll input.dayin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
table.payroll input.dayin{-moz-appearance:textfield}

/* Over the daily cap. The payable figure is what the totals add up to, so it
   stays the number in the cell; what was actually worked sits under it, which
   is the only place the two ever differ. */
table.payroll td.trimmed input.dayin,table.payroll td.trimmed{color:var(--amber);font-weight:600}
table.payroll .worked{display:block;font-size:10.5px;color:var(--amber);
  opacity:.8;text-align:right;padding-right:7px;margin-top:-2px}
table.payroll .worked::before{content:"worked "}

table.payroll td.pay .trim{display:block;font-size:10.5px;color:var(--amber);opacity:.8}
table.payroll td.who .cap{font-size:11px;color:var(--dim2);margin-top:2px;
  font-variant-numeric:tabular-nums}
table.payroll td{vertical-align:top}
table.payroll .adjnote{width:100%;min-width:170px;text-align:left}

/* The confirmation dialog. Narrower than the general modal: it asks one
   question, and a 660px box for one sentence reads as an error page. */
.modalbox.confirmbox{width:min(430px,100%)}
.confirmbox .modalft{justify-content:flex-end}
.confirmbox #confirm-yes{background:var(--red-fill);border-color:var(--red-fill);color:#fff}
.confirmbox #confirm-yes:hover{filter:brightness(1.1)}
.confirmbox.keep #confirm-yes{background:var(--orange);border-color:var(--orange);color:#12140f}

/* The pay run deadline was defined here as a bordered tag and again further
   down as the quieter thing it was redesigned into, and both survived. It is
   one block now, with the My Time toolbar it belongs to. */

/* Approvals, grouped by person and week. One card per decision, with the days
   inside it: the question is "which day is the odd one", not "what happened on
   the 14th", and a flat list of day rows answers neither. */
.apweek{border-top:1px solid var(--line);padding:14px 20px}
.apweek:first-of-type{border-top:none}
.aphd{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.apwho{font-weight:700;font-size:14.5px}
.approj{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:40ch}
.aptot{font-variant-numeric:tabular-nums;font-weight:700;color:var(--orange)}
/* The pay period a run of weeks belongs to. Deliberately heavier than the
   week headers underneath it: it is the boundary payroll is drawn on, and a
   divider that reads as just another row is not a boundary at all. */
.apcycle{display:flex;gap:10px;align-items:center;flex-wrap:wrap;
  padding:9px 16px;background:var(--panel2);
  border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.apcyclen{font-weight:650;font-size:13px}
.apdays{display:flex;gap:6px;margin:10px 0 12px;flex-wrap:wrap}
.apday{display:flex;flex-direction:column;align-items:center;gap:2px;
  min-width:56px;padding:6px 8px;background:var(--panel2);
  border:1px solid var(--line);border-radius:var(--radius)}
.apday em{font-style:normal;font-size:10px;font-weight:700;letter-spacing:.9px;
  text-transform:uppercase;color:var(--dim2)}
.apday i{font-style:normal;font-size:10.5px;color:var(--dim);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.apday b{font-variant-numeric:tabular-nums;font-size:13px}
.apday.zero{opacity:.45}
.apday.zero b{color:var(--dim2)}
.apact{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.apreject{display:flex;gap:8px;align-items:center;flex:1;min-width:280px}
.apreject input[name="note"]{flex:1;min-width:180px}
/* THE REVERSAL ROW IS A COLUMN, not a sentence with controls after it.
   The line saying who approved the week takes the slack, and the form beside it
   keeps a fixed width, so the reason box and the button start and end on the
   same vertical line in every block. They used to be laid out in flow after the
   text, so a week approved by "auto (approval not required for
   richard.biggs@trificta.com.au)" pushed its controls 190px further right than
   the one above it, and a column of buttons a person has to aim at was
   staggered by nothing more meaningful than the length of an email address. */
.apby{flex:1 1 auto;min-width:0;font-size:12.5px}
.apundo{display:flex;gap:8px;align-items:center;flex:0 1 auto;min-width:0;margin:0}
/* Fixed rather than flexible: two forms that grow by different amounts are the
   staggering again, one level down. Below about 820px the form drops to its own
   line, and from there the REASON BOX gives up the width - the button does not
   shrink, because "Reverse the week" cannot be squeezed below its own text and
   trying makes it push out of the panel instead. Measured down to 300px. */
.apundo input[name="reason"]{width:230px;min-width:0}
.apundo button{flex:0 0 auto}

/* ===========================================================================
   Touch.
   ===========================================================================

   Dragging is pointer-event driven, which covers mouse, touch and pen in one
   path. Two things still have to be said in CSS.

   touch-action:none on the DRAGGABLE things only. Without it the browser
   decides a finger on a block means "scroll the day" and takes the gesture
   before any handler sees it, which is why a block could not be moved on a
   phone at all. It is deliberately NOT on the day columns: a drag across empty
   grid is how somebody scrolls, and claiming that would trade one broken
   gesture for another. Tapping empty grid still creates an entry.
   ------------------------------------------------------------------------ */

.blk, .blk.chip, .tlbar{touch-action:none}

/* A finger is about 9mm across and a mouse pointer is one pixel. Anything you
   are expected to hit deliberately gets a 44px target on a touch screen, which
   is the figure both Apple and Google publish. The icons stay their size; the
   padding around them grows. */
@media (pointer: coarse){
  .rowbtn{padding:12px}
  .rowbtn .ic{width:20px;height:20px}
  .pop .pophd{gap:2px;padding:8px 8px 0}
  .pop .pophd button{min-width:44px;min-height:44px;font-size:22px}
  .pop .pophd .pstate{padding-left:8px}

  /* The menu the three dots opens: rows a thumb can hit without aiming. */
  .mitem{padding:13px 16px;font-size:14.5px}
  .menu{min-width:200px}

  /* Resize grips are 6px for a mouse, which no finger can find. */
  .blk .grip{height:16px}

  /* And the pickers inside the editor. */
  .picker{min-height:40px}
  .tin{min-height:40px}
}

/* A day holding time nobody has submitted yet.
   Underlined rather than recoloured: the colours in this table already mean
   something (orange is a cap that trimmed the day), and a second meaning on
   the same channel makes both harder to read. */
table.payroll td.draftday {
  text-decoration: underline dotted var(--amber);
  text-underline-offset: 3px;
}

/* A day inside the period that has not arrived yet. */
table.payroll td.futureday { color: var(--dim); opacity: .5 }

/* Dashboard donuts.
   Auto-fit so three sit in a row on a desktop and stack on a phone without a
   breakpoint that has to be kept in step with the number of charts. */
.donuts{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
.panel.donut{margin-bottom:0}
.panel.donut>.bd{padding:14px 16px}
.ring{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.ring svg{width:132px;height:132px;flex:none}
.ring .ringn{fill:var(--fg);font-size:20px;font-weight:600;text-anchor:middle;
  font-variant-numeric:tabular-nums}
.ring .ringu{fill:var(--dim);font-size:9px;text-anchor:middle;
  text-transform:uppercase;letter-spacing:.7px}
.ring .key{list-style:none;margin:0;padding:0;flex:1 1 130px;min-width:130px}
.ring .key li{display:flex;align-items:center;gap:8px;padding:2px 0;font-size:12.5px}
.ring .key i{width:9px;height:9px;border-radius:2px;flex:none}
.ring .key .kl{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ring .key .kv{color:var(--dim);font-variant-numeric:tabular-nums}
.ring .key .kp{color:var(--dim2);font-variant-numeric:tabular-nums;
  min-width:34px;text-align:right}

/* A day nobody worked. Fourteen columns of 0.00 at full contrast is most of
   what made this table feel busy, and none of it is what the reader is
   looking for. */
table.payroll td.zero,table.payroll td.zero input.dayin{color:var(--dim2)}

/* A page's explanation, folded away.
   Written once for the person meeting the page for the first time, and in the
   way of everybody who has met it before. Closed by default because the second
   visit is the common one. */
details.help{margin:-6px 0 14px}
details.help>summary{display:inline-flex;align-items:center;gap:6px;cursor:pointer;
  color:var(--dim);font-size:12.5px;list-style:none;padding:2px 0;
  border-bottom:1px dotted var(--line);width:max-content}
details.help>summary::-webkit-details-marker{display:none}
details.help>summary::before{content:"?";font-size:10px;font-weight:700;
  width:14px;height:14px;border-radius:50%;border:1px solid var(--line);
  display:inline-flex;align-items:center;justify-content:center;flex:none}
details.help[open]>summary{color:var(--fg);margin-bottom:8px}
details.help>div{color:var(--dim);font-size:13px;max-width:95ch}
details.help>div p{margin:0 0 8px}

/* Closing a period out as paid elsewhere. The note is the point, so it gets
   the room; the button is secondary because sending to Xero is the norm. */
form.settle input{min-width:230px}

/* An empty weekend column. Hidden rather than removed: the review form pairs
   each person with one figure per day by position, so a cell that is not
   rendered is a figure that is not posted and a save the server refuses. */
table.payroll th.offday,table.payroll td.offday{display:none}

/* A day that looks like two days entered against one. Amber already means "the
   cap trimmed this"; this is a different claim, so it gets its own colour and
   a word rather than a shade, because the operator is being asked to go and
   look at something rather than to notice a number. */
table.payroll td.suspect{color:var(--red);font-weight:600}
table.payroll .mis{display:block;font-size:10px;color:var(--red);text-align:right;
  padding-right:7px;margin-top:-1px;text-transform:uppercase;letter-spacing:.5px;cursor:help}

/* What used to be a page heading, on the two pages that had something worth
   saying beside the name the sidebar already gives. */
.pagenote{display:flex;align-items:center;gap:8px;margin:-4px 0 12px}

/* How much of the week is on screen. Sits beside the Calendar/Timesheet
   control and reads as part of it: a caret and the current mode, the way
   Toggl has it, rather than a fourth thing competing with the three tabs. */
.spanpick{margin-left:8px}
.spanpick>.ddbtn::after{content:"";width:0;height:0;margin-left:2px;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:5px solid currentColor;opacity:.7}
.spanpick[open]>.ddbtn::after{transform:rotate(180deg)}
.spanpick .ddmenu{min-width:150px}

/* A planned row in the list. The same orange the calendar uses for a plan,
   pulled back to a tag so it labels the row rather than colouring it. */
.plantag{color:var(--orange-light);border-color:var(--orange-soft)}

/* A heading over a group of settings panels. Says what the panels below are
   for, in the words somebody would go looking with. */
h2.setsec{font-size:12px;font-weight:700;letter-spacing:1.1px;text-transform:uppercase;
  color:var(--orange);margin:2px 0 10px}
h2.setsec:not(:first-of-type){margin-top:26px}

/* A timeline bar built from entered time rather than from an allocation typed
   on this page. Flatter and not draggable: it is a record of what happened,
   and it is changed on the timesheet where it was made. */
.tlbar.actual{cursor:default;background:var(--panel3)}
.tlbar.actual.plan{background:var(--planned-soft)}
.tlbar.actual .h{opacity:.85}

/* A day off, on the week header. Approved leave is stated; a request still
   waiting is dimmer, because it is not a fact about the week yet. */
.calhead .dh.onleave{background:var(--panel2)}
.lvtag{position:absolute;right:8px;top:9px;font-size:9px;font-weight:700;
  letter-spacing:.6px;text-transform:uppercase;color:var(--blue)}
.calhead .dh.requested .lvtag{color:var(--dim);font-style:italic}
details.leaveask{margin:0 0 12px}
details.leaveask>.ddmenu{display:none}
.lvform{margin-top:8px;flex-wrap:wrap;gap:8px}

/* Somebody else's week. Deliberately loud: every drag and every edit on this
   page writes to their timesheet under their name, and a page that looks
   exactly like your own is how that gets forgotten. */
.banner.othertime{border-color:var(--blue);background:rgba(76,141,255,.08)}
.banner.othertime a{margin-left:8px}

/* The little cross that takes somebody off a project team. */
.tag button.lnk{background:none;border:none;color:inherit;cursor:pointer;
  padding:0 0 0 5px;font-size:13px;line-height:1;opacity:.7}
.tag button.lnk:hover{opacity:1}

/* The calendar settings cog, with CSS-only tabs. Radio inputs rather than a
   listener: the content security policy allows no inline script and a tab
   strip is not worth wiring up. */
.cogset .cogmenu{min-width:290px;padding:0}
.cogmenu>input[type=radio]{position:absolute;opacity:0;pointer-events:none}
.cogtabs{display:flex;border-bottom:1px solid var(--line)}
.cogtabs label{flex:1;text-align:center;padding:10px 8px;font-size:12.5px;
  color:var(--dim);cursor:pointer;border-bottom:2px solid transparent}
#cog-display:checked~.cogtabs label[for=cog-display],
#cog-sync:checked~.cogtabs label[for=cog-sync]{color:var(--fg);border-bottom-color:var(--orange)}
.cogpane{display:none;padding:12px 14px}
#cog-display:checked~.pane-display,#cog-sync:checked~.pane-sync{display:block}
.cogrow{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:7px 0;font-size:13px}
.cogrow select{min-width:150px}
.cogfoot{display:flex;justify-content:flex-end;padding-top:10px;
  border-top:1px solid var(--line);margin-top:6px}

/* The end of the My Time toolbar.
   Three separate things had piled up on the right - the payroll deadline, the
   submitted count and Unsubmit - each with its own border and its own padding,
   reading as three unrelated alarms. The deadline is quieter than a tag now,
   because it is a date rather than something to act on, and the count sits
   against the button it belongs to instead of floating beside it. */
.paydue{display:inline-flex;align-items:baseline;gap:6px;font-size:11.5px;
  color:var(--dim);background:none;border:none;padding:0;white-space:nowrap}
.paydue em{font-style:normal;text-transform:uppercase;letter-spacing:.7px;
  font-size:9.5px;font-weight:700;color:var(--dim2)}
.paydue b{color:var(--fg);font-weight:600}
/* Quiet until the run is close: a permanent warning is one people stop reading
   before it ever matters. The whole line goes amber, not just the date - that
   is what the two rules added up to and what people are used to seeing. */
.paydue.soon{color:var(--amber)}
.paydue.soon b,.paydue.soon em{color:var(--amber)}
#subctl{display:inline-flex;align-items:center;gap:0}
#subctl .subwait{border-top-right-radius:0;border-bottom-right-radius:0;
  border-right:none;margin-right:0}
#subctl #unsubform button{border-top-left-radius:0;border-bottom-left-radius:0}

/* A day either side of the pay period. The window is whole weeks so it reads
   Monday first; these are the days that belong to the neighbouring run. Shown
   rather than cropped, because a page about hand-offs should not hide the
   hand-off, and dimmed so the period is readable at a glance. */
.tld.outside{opacity:.42}
.tld.outside b{font-weight:500}

/* The My Time toolbar, in three groups.
   Where you are on the left, whose week and how many hours in the middle, how
   it is displayed on the right. Auto margins either side of the middle rather
   than spacer spans, which drifted whenever a total appeared or somebody had
   no person picker. */
.calbar>.tbmid{display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  margin-left:auto;margin-right:auto}
.calbar>.tbright{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin-left:auto}
.calbar>.tbright .spanpick{margin-left:0}

/* Unsubmit stands the same height as the pill it is joined to. They read as
   one control: a count and the thing you do about it. */
#subctl .subwait{display:inline-flex;align-items:center;gap:5px;height:30px;padding:0 10px;
  margin:0;line-height:1}
#subctl #unsubform button{height:30px;padding:0 12px;line-height:1}
@media(max-width:900px){
  .calbar>.tbmid,.calbar>.tbright{margin-left:0;margin-right:0}
}

/* A day off, behind the grid. Green because it is the one thing on this
   calendar that is not work: the eye should read "not here" without having to
   parse a label. Underneath the entries, because somebody who worked part of a
   day off should still see the hours they worked on top of it. */
.leaveblk{position:absolute;inset:0;z-index:0;pointer-events:none;
  background:rgba(63,191,127,.13);border-left:3px solid var(--green);
  display:flex;justify-content:center;padding-top:6px}
.leaveblk span{font-size:10px;font-weight:700;letter-spacing:.7px;
  text-transform:uppercase;color:var(--green);opacity:.9}
/* Asked for but not yet granted: the same green, said quietly. */
.leaveblk.asked{background:rgba(63,191,127,.07);border-left-style:dashed}
.leaveblk.asked span{opacity:.65;font-style:italic}

/* The week label holds its width as the weeks change.
   "3 to 9 Aug 2026" and "27 Jul to 2 Aug 2026" are different lengths, so the
   Today button slid left and right underneath the pointer and paging twice
   quickly landed on it by accident. Fixed width, centred, so nothing after it
   moves. */
.calbar .nav .cur{display:inline-block;min-width:15.5em;text-align:center}

/* The pay run header, in two groups that keep their places.
   Every status pill goes left and every action right, and the left group is
   allowed to shrink. Without that, a period carrying one more pill widened the
   left side until the buttons wrapped to a second line, so the page changed
   shape depending on how far through Xero that particular run happened to be
   and nothing sat where it had been on the period before. */
.runhd{flex-wrap:nowrap;align-items:flex-start}
.runwho{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0;flex:1 1 auto}
.runacts{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;
  flex:0 0 auto;margin-left:auto}
/* Narrow enough that side by side stops being readable: stack, rather than
   squeezing the buttons to nothing. */
@media(max-width:820px){
  .runhd{flex-wrap:wrap}
  .runacts{margin-left:0;width:100%;justify-content:flex-start}
}

/* ---------- people pills ---------- */

/* Who is on a task, in the space a table cell has.
   A task can carry several people, and a full name each turns a row into a
   paragraph. Initials fit; the colour is what makes them scannable, because a
   column of grey two-letter badges is not readable at a glance and a column of
   coloured ones is.

   Eight fixed colours rather than a colour computed at render time, so every
   colour a badge can be has been through the contrast gate.

   WHICH slot a person gets is allocated once and stored on their staff record,
   against everybody who already has one. It used to be a hash of the username,
   which was stable and spread nothing: five real accounts landed on three
   colours with three of them sharing one, because a hash spreads values that
   have no relationship to each other and the requirement here is precisely a
   relationship. Stored rather than positional, so a colour is a fact about a
   person and does not move when somebody joins whose name sorts earlier.

   The count is stated in three places - here, model.PersonPalette, and the
   staff_allocate_hue trigger - and TestEveryPaletteSlotHasAStylesheetClass
   fails if they drift, because a pill whose class does not exist is invisible
   rather than merely the wrong colour. */
.pills{display:inline-flex;flex-wrap:wrap;gap:4px;vertical-align:middle}
.pill{display:inline-grid;place-items:center;
  min-width:26px;height:22px;padding:0 6px;border-radius:999px;
  font-family:var(--font);font-size:11px;font-weight:700;letter-spacing:.04em;
  font-variant-numeric:tabular-nums;
  border:1px solid transparent;cursor:default;user-select:none}

/* The palette. Each pair is a tinted ground with a text colour from the same
   family, dark enough on it to read: a saturated fill with white text looks
   like a button, and these are labels rather than controls. */
.pill.hue0{background:rgba(244,117,33,.16);color:#f08a3c;border-color:rgba(244,117,33,.34)}
.pill.hue1{background:rgba(84,160,255,.16);color:#77b4ff;border-color:rgba(84,160,255,.34)}
.pill.hue2{background:rgba(96,193,132,.16);color:#7ccb9a;border-color:rgba(96,193,132,.34)}
.pill.hue3{background:rgba(198,130,232,.16);color:#c894ea;border-color:rgba(198,130,232,.34)}
.pill.hue4{background:rgba(232,182,74,.16);color:#e2bd67;border-color:rgba(232,182,74,.34)}
.pill.hue5{background:rgba(88,197,206,.16);color:#79ccd4;border-color:rgba(88,197,206,.34)}
.pill.hue6{background:rgba(233,124,142,.16);color:#e78e9f;border-color:rgba(233,124,142,.34)}
.pill.hue7{background:rgba(150,166,196,.16);color:#a7b4cc;border-color:rgba(150,166,196,.34)}

/* The money on a task, under the hours. Tabular so a column of them lines up,
   and dimmer than the hours because hours are what most tasks are read by. */
.money{font-family:var(--mono);font-size:12px;color:var(--dim);
  font-variant-numeric:tabular-nums}

/* ---------- ticket quick actions ---------- */

/* Moving a ticket along should be one tap, not a form.

   What this replaced: six dropdowns and a Save button, for a screen whose two
   commonest actions are "it is in progress now" and "this one is mine". The
   cost of that shape is measurable rather than theoretical: of the first ten
   tickets raised in this system, all ten were unassigned and seven had no
   customer. */
.quick{display:flex;flex-direction:column;gap:10px}
.qrow{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.qlabel{flex:0 0 62px;font-size:11px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--dim2)}
.qbtns{display:flex;flex-wrap:wrap;gap:5px;flex:1 1 auto;min-width:0}
.qbtns form{margin:0}
.qwho{font-size:13px;color:var(--fg);align-self:center}

.qbtn{padding:5px 11px;font-size:12.5px;font-weight:600;border-radius:999px;
  background:var(--panel2);border:1px solid var(--line);color:var(--dim);
  cursor:pointer;white-space:nowrap;min-height:30px}
.qbtn:hover{border-color:var(--line2);color:var(--fg)}
/* The current one is stated, not merely styled: aria-current carries it for a
   screen reader, and this is what carries it for everybody else. */
.qbtn.on{background:var(--orange-a18);border-color:var(--orange);color:var(--orange)}
.qbtn.primary{background:var(--orange);border-color:var(--orange);color:#12140f}
.qbtn.primary:hover{color:#12140f;filter:brightness(1.08)}
/* Priority codes are two characters, so they need a fixed width or the row
   reflows every time one is selected. */
.qbtn[class*="p-p"]{min-width:38px;text-align:center;font-family:var(--mono)}

/* The rest, closed. Set once when a ticket is filed and rarely again, and
   mostly filled in automatically now. */
details.more{margin-top:14px;border-top:1px solid var(--line);padding-top:10px}
details.more > summary{cursor:pointer;font-size:12.5px;color:var(--dim);
  list-style:none;display:flex;align-items:center;gap:6px;min-height:32px}
details.more > summary::-webkit-details-marker{display:none}
details.more > summary::before{content:"+";font-family:var(--mono);color:var(--dim2)}
details.more[open] > summary::before{content:"-"}
details.more > summary:hover{color:var(--fg)}
details.more > form{margin-top:10px}

/* What a task is waiting on. Amber rather than red: blocked is a normal state
   in a plan, not a fault, and colouring it as a fault makes a Gantt of ordinary
   work look like an incident board. */
.waiting{display:inline-block;margin-top:3px;font-size:11.5px;color:var(--amber)}
/* What a task is made up of. pre-line so the line items stay lines:
   the breakdown is written one item per line and an inline span
   collapsed all of it into a single run-on sentence. */
.tasklines{white-space:pre-line;margin-top:3px;font-size:12px;color:var(--dim)}
/* A subtask row. Indented by a marker in the cell rather than by padding on
   the row, so the indent travels with the name and cannot be undone by the
   table's own cell padding. Quieter than its parent, because the parent is the
   deliverable and the child is one step of it. */
/* A DELIVERABLE IS NOT A STEP, and the table has to say so at a glance.
   The two were separated by a slightly darker cell and a two point drop in
   size, which nobody reads as a hierarchy: on a project with 189 steps under
   16 deliverables the table looked like one flat list of 205 things. A
   deliverable is what gets invoiced and what carries the budget; a step is
   work inside it. So the deliverable is named in the page's own weight, and
   its steps hang off a rail underneath it. */
.tasktop>td:first-child{font-weight:600}
.tasktop>td:first-child .tname{color:var(--fg)}
.subtask>td{background:var(--panel2)}
.subtask>td:first-child{font-size:12.5px;position:relative;padding-left:38px}
/* The rail runs the full height of every step, so a run of them reads as one
   group hanging off the deliverable rather than as separate rows. */
.subtask>td:first-child::before{content:"";position:absolute;left:19px;top:0;bottom:0;
  border-left:2px solid var(--line2)}
.subtask>td:first-child .tname{color:var(--dim2)}
/* The arm of the L, joining the rail to the name. */
/* The role switcher, above the name in the sidebar. Only one account has it.
   It is deliberately quiet until it is doing something, then loud: a console
   silently showing you less than you have is worse than no switcher at all. */
.viewas{padding:8px 12px 0;display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.viewas label{flex:0 0 100%;font-size:11px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--dim)}
.viewas select{flex:1;min-width:0;padding:4px 6px;font-size:12px}
.viewas button{padding:4px 9px;font-size:12px}
.viewas p{flex:0 0 100%;margin:2px 0 0;font-size:11.5px;line-height:1.35;color:var(--amber)}
.viewas.on{background:rgba(232,163,61,.10);border-left:3px solid var(--amber);
  padding-left:9px;margin:0 0 4px}
.viewas.on label{color:var(--amber)}

.subtick{display:inline-block;width:11px;margin-right:7px;border-bottom:2px solid var(--line2);
  vertical-align:middle;margin-bottom:3px}
/* The control that folds a task's steps away. A triangle drawn in CSS rather
   than a glyph, so it turns rather than swapping to a second character. */
.twist{width:16px;height:16px;padding:0;margin-right:6px;vertical-align:-3px;
  border:0;background:transparent;cursor:pointer;position:relative}
.twist::before{content:"";position:absolute;top:4px;left:5px;
  border-left:5px solid var(--dim);border-top:4px solid transparent;
  border-bottom:4px solid transparent;transition:transform .12s ease;
  transform-origin:2px 4px}
.twist[aria-expanded="true"]::before{transform:rotate(90deg)}
.twist:hover::before{border-left-color:var(--fg)}
.twist:focus-visible{outline:2px solid var(--orange);outline-offset:1px;border-radius:3px}
/* Keeps a task with no steps aligned with one that has them. */
.twistgap{display:inline-block;width:16px;margin-right:6px}
@media (prefers-reduced-motion:reduce){.twist::before{transition:none}}
/* How many of a task's subtasks are finished. Counted, not measured in hours. */
.steps{display:inline-block;margin-left:8px;padding:1px 7px;border-radius:9px;
  background:var(--panel2);border:1px solid var(--line2);font-size:11.5px;color:var(--dim)}

/* The project timeline.
   Positions are percentages worked out in Go, so nothing here computes
   anything: this file only has to make them visible. The chart is a fixed-width
   block inside .tablewrap so a long programme scrolls sideways in its own
   container rather than dragging the page with it.

   Three weights, and they mean three different things. A DELIVERABLE is drawn
   solid and dark because it is what the customer is buying. The work inside it
   is orange. A MILESTONE is a diamond, not a bar, because a bar for something
   with no duration misrepresents how long it takes. */
.gantt{min-width:940px;padding:6px 0 4px;--gcol:330px;--ggap:12px}
.grows{position:relative}
/* Everything drawn ACROSS the chart rather than in one row lives in here.
   It exists so a percentage resolves against the track and nothing else.
   The band and the month rules were absolutely positioned inside .grows,
   which spans the label column as well, and then pushed right again by a
   margin: 85% of the whole width PLUS 270px put the February shutdown off
   the end of a chart that finishes in March. */
.goverlay{position:absolute;top:0;bottom:0;right:0;
  left:calc(var(--gcol) + var(--ggap));pointer-events:none}

/* Milestones, above everything, with the label over the marker. */
.gmarks{position:relative;height:52px;margin-bottom:2px;
  margin-left:calc(var(--gcol) + var(--ggap))}
.gmark{position:absolute;bottom:0;transform:translateX(-50%);text-align:center;width:130px}
.gmlabel{display:block;font-size:11px;line-height:1.3;color:var(--dim);margin-bottom:4px}
.gmlabel b{color:var(--fg);font-weight:600}
.gmdot{display:inline-block;width:9px;height:9px;background:var(--fg);transform:rotate(45deg)}

.grow{display:grid;grid-template-columns:var(--gcol) 1fr;align-items:start;
  gap:var(--ggap);padding:4px 0;position:relative}
/* Right aligned, so every name finishes hard against the chart and the eye has
   one edge to travel down rather than a ragged one. */
/* Names WRAP rather than truncate.
   At 270px a third of these disappeared behind an ellipsis: "3e. AWTP HA
   upgrades, cutovers, failover te...". A chart whose rows cannot be told apart
   is not a chart, and a title attribute does not help on paper. Right aligned
   so a wrapped second line still finishes hard against the track. */
.gtask{font-size:12.5px;text-align:right;line-height:1.35;
  overflow-wrap:anywhere;hyphens:auto}
.gdeliv .gtask{font-weight:700}
.gstep .gtask,.grow:not(.gdeliv):not(.gmilestone) .gtask{color:var(--dim);font-size:12px}
.gmilestone .gtask{font-weight:600}

.gtrack{position:relative;height:17px}
.grule{position:absolute;top:0;bottom:0;width:1px;background:var(--line)}
/* The shaded window sits behind every row: it is a period of time, not a task. */
.gband{position:absolute;top:0;bottom:0;background:rgba(244,117,33,.10)}
.gblabel{position:absolute;top:8px;left:50%;transform:translateX(-50%);
  width:150px;text-align:center;font-size:11.5px;font-weight:700;
  color:var(--orange);line-height:1.3}

.gbase{position:absolute;top:2px;height:13px;border:1px dashed var(--line2);
  border-radius:3px;box-sizing:border-box}
.gbar{position:absolute;top:4px;height:9px;background:var(--orange);
  border-radius:2px;overflow:hidden}
.gdbar{top:2px;height:13px;background:var(--fg);border-radius:2px}
.galong{background:var(--panel2);border:1px solid var(--line2);box-sizing:border-box}
.gslip{background:rgba(232,163,61,.55)}
/* WHAT STATE THE WORK IS IN, on the bar itself.
   The chart drew every bar in the accent, so a finished deliverable, one
   nobody has started, and one that is stuck all looked identical and the only
   way to tell them apart was to go back to the task table. RowClass is
   "st-" plus the task's status and was already computed and thrown away.
   Cancelled is drawn faintest rather than hidden: it stays on the chart
   because a plan that quietly loses a line is a plan nobody can reconcile
   against the one they agreed. */
.gbar.st-open{background:var(--orange-soft)}
.gbar.st-in_progress{background:var(--orange)}
.gbar.st-blocked{background:var(--red)}
.gbar.st-done{background:var(--green)}
.gbar.st-cancelled{background:var(--line2);opacity:.65}
.gbar.st-open .gfill,.gbar.st-in_progress .gfill{background:var(--orange)}
.gbar.st-blocked .gfill{background:var(--red-fill)}
.gbar.st-done .gfill{background:var(--green)}
.gbar.st-cancelled .gfill{background:var(--dim)}
.gdiamond.st-done{background:var(--green)}
.gdiamond.st-blocked{background:var(--red)}
.gdiamond.st-cancelled{background:var(--line2)}
.grow.st-cancelled .gtask{text-decoration:line-through;color:var(--dim)}
/* The key. Without it the colours are a code the reader has to guess. */
.gkey{display:flex;flex-wrap:wrap;gap:14px;align-items:center;
  font-size:12px;color:var(--dim);padding:10px 14px 0}
.gkey span{display:inline-flex;align-items:center;gap:6px}
.gkey i{display:inline-block;width:16px;height:9px;border-radius:2px;font-style:normal}
.gkey .k-open{background:var(--orange-soft)}
.gkey .k-in_progress{background:var(--orange)}
.gkey .k-blocked{background:var(--red)}
.gkey .k-done{background:var(--green)}
.gkey .k-cancelled{background:var(--line2);opacity:.65}
.gfill{display:block;height:100%;background:var(--orange);border-radius:2px}
.gdbar .gfill{background:var(--orange)}
.gdiamond{position:absolute;top:4px;width:9px;height:9px;background:var(--fg);
  transform:translateX(-50%) rotate(45deg)}

/* The dates travel with the bar rather than sitting in a column of their own,
   where a short bar leaves them stranded an inch from the thing they describe. */
.gwhen{position:absolute;top:1px;margin-left:8px;white-space:nowrap;
  font-size:11.5px;color:var(--dim);pointer-events:none}
.gpin{display:inline-block;font-size:10px;font-weight:700;color:var(--orange);
  border:1px solid var(--line2);border-radius:3px;padding:0 3px;margin-right:3px}
.glate{color:var(--amber);margin-left:5px}
/* A pinned deliverable whose steps have moved out from under it. Amber rather
   than red: the dates are stale, not invalid, and somebody has to decide which
   of the two is right. */
.drift{color:var(--amber);font-size:11.5px;white-space:normal}
.gearly{color:var(--green);margin-left:5px}

.gaxis{position:relative;height:20px;margin-top:4px;
  margin-left:calc(var(--gcol) + var(--ggap));border-top:1px solid var(--line)}
.gmonth{position:absolute;top:4px;font-size:11px;color:var(--dim);
  border-left:1px solid var(--line);padding-left:5px;overflow:hidden;white-space:nowrap}

@media (max-width:900px){
  .gantt{--gcol:170px}
  .gmlabel{font-size:10px}
}
}

/* The SLA countdown on a ticket. */
.slaclock{display:inline-flex;align-items:center;gap:6px;
  padding:4px 10px;border-radius:999px;font-family:var(--mono);font-size:12.5px;
  font-variant-numeric:tabular-nums;
  background:var(--panel2);border:1px solid var(--line);color:var(--fg)}
/* Paused reads as deliberately stopped rather than as a fault: waiting on a
   customer is a normal state and colouring it red trains people to ignore red. */
.slaclock.paused{color:var(--dim);border-style:dashed}
.slaclock.breached{color:var(--red);border-color:var(--red);background:rgba(224,82,82,.10)}

/* ---------- attachment thumbnails ---------- */

/* A screenshot on a ticket, shown rather than listed.
   The thumbnail is capped in BOTH directions: a tall narrow phone screenshot
   and a wide desktop one have to sit in the same row without one of them
   setting the height of the table. object-fit keeps the aspect ratio while
   the box stays a fixed size. */
.shot{display:inline-flex;align-items:center;gap:10px;text-decoration:none;color:var(--fg)}
.shot img{width:56px;height:40px;object-fit:cover;border-radius:4px;
  border:1px solid var(--line);background:var(--panel2);display:block}
.shot:hover img{border-color:var(--orange)}
.shot span{text-decoration:underline;text-underline-offset:2px}
.shot:hover span{color:var(--orange)}

/* The lightbox. Its own layer above everything, dismissed by clicking
   anywhere or pressing escape, because a viewer that traps you is worse than
   a download. */
.lightbox{position:fixed;inset:0;z-index:120;display:grid;place-items:center;
  background:rgba(5,7,9,.88);padding:24px;cursor:zoom-out}
.lightbox img{max-width:100%;max-height:100%;object-fit:contain;
  border-radius:var(--radius);box-shadow:0 24px 70px -20px rgba(0,0,0,.9)}
.lightbox .lbname{position:fixed;left:0;right:0;bottom:0;padding:14px 20px;
  text-align:center;color:var(--dim);font-size:12.5px;
  background:linear-gradient(transparent,rgba(5,7,9,.85))}

/* ---------- who reported a ticket ---------- */

/* The sender as a person. The address alone answers "where did this come
   from" and not "who do I ring", which is the question somebody actually has
   in front of a ticket. */
.reporter{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;
  margin-top:14px;padding:9px 12px;font-size:13px;
  background:var(--panel2);border:1px solid var(--line);border-radius:var(--radius)}
.reporter .who{font-weight:650;color:var(--fg)}
.reporter .sep{width:1px;height:14px;background:var(--line2)}
.reporter a{color:var(--dim)}
.reporter a:hover{color:var(--orange)}
.reporter .more{margin-left:auto;color:var(--orange)}
/* A sender who is nobody on file reads as a gap to close, not a fault. */
.reporter.unknown{border-style:dashed}

/* ---------- Statement of Applicability ---------- */

/* A theme folds away. Ninety-three controls is four screens of table, and
   somebody working through the organisational ones does not want the other
   seventy underneath them. */
.soagroup > summary.hd{cursor:pointer;list-style:none;user-select:none;
  border-top:1px solid var(--line)}
.soagroup > summary.hd::-webkit-details-marker{display:none}
.soagroup > summary.hd:hover{background:var(--panel2)}
.soagroup > summary.hd:focus-visible{outline:2px solid var(--orange);outline-offset:-2px}
/* The triangle, drawn rather than borrowed from the default marker so it sits
   on the baseline of the heading and turns with the section. */
.fold{display:inline-block;width:0;height:0;margin-right:9px;
  border-left:5px solid var(--dim);border-top:4px solid transparent;
  border-bottom:4px solid transparent;
  transform:rotate(0deg);transition:transform .12s;vertical-align:middle}
.soagroup[open] > summary.hd .fold{transform:rotate(90deg)}

/* One grid, not four.

   Every theme is its own <table>, so each was sized independently from its own
   contents and no two columns lined up: Owner sat somewhere different in each
   section and the page read as four unrelated tables. The colgroup in the
   template supplies the widths; this is what makes the browser honour them. */
.soatable{table-layout:fixed;width:100%}
.soatable td,.soatable th{overflow-wrap:anywhere}

/* The edit form opens UNDERNEATH the control, across the whole table.

   It used to live inside the last cell, which is the narrowest column on the
   page: it opened as a sliver hanging off the right edge and .tablewrap's
   overflow-x clipped the rest, so on a small screen most of the form could not
   be read at all. Widening it in place could not work either - an absolutely
   positioned panel is clipped by the same scrolling container that was clipping
   the cell.

   So the form is its own row with colspan, INSIDE the table where nothing
   clips it and where it gets the full width. The disclosure in the last cell
   is now empty and carries only the open state; :has() on the row above is
   what reveals this one.

   :has() rather than script, for the same reason the disclosure was a
   <details> to begin with: it needs no JavaScript, it is keyboard operable,
   and the state lives in the DOM rather than in a variable. */
/* NOT SCOPED TO .soatable. It was, and the compliance register and the risk
   matrix both reuse this pattern without that class - so their rows never got
   display:none and every editor on both pages was permanently expanded. The
   page looked like a wall of open forms. A rule scoped to one table silently
   does nothing on the others, which is invisible in a template and obvious in
   a browser. */
tr.ctrledit{display:none}
tr.ctrl:has(.soaedit[open]) + tr.ctrledit{display:table-row}
tr.ctrledit > td{background:var(--panel2);
  border-left:3px solid var(--orange);padding:16px 18px}
/* Room to actually work in. A justification and an evidence note are sentences,
   not words, so they get the width the row now has. */
.soaform{max-width:900px}
.soaform .row{display:flex;gap:14px;flex-wrap:wrap;margin-top:12px}
.soaform .row .field{flex:1;min-width:190px}
.soaform .field input,.soaform .field select{width:100%}

/* The per-row edit control looks like the button it is. It was bare grey text
   that happened to be clickable. */
.soaedit > summary{list-style:none;cursor:pointer}
.soaedit > summary::-webkit-details-marker{display:none}
.btnlike{display:inline-block;background:var(--orange);border:1px solid var(--orange);
  color:#12140f;font-weight:600;font-size:12px;line-height:1.2;
  padding:5px 13px;border-radius:var(--radius)}
/* The quiet variant, matching button.sec. A link styled as a button needs its
   own rule: button.sec is element-scoped and does nothing on an <a>, so a
   "Cancel" beside a "Save" came out as a second orange call to action. */
.btnlike.sec{background:var(--panel2);border-color:var(--line);color:var(--fg)}
.btnlike.sec:hover{background:var(--panel2);filter:brightness(1.2)}
.soaedit > summary:hover .btnlike{filter:brightness(1.1)}
.soaedit > summary:focus-visible{outline:none}
.soaedit > summary:focus-visible .btnlike{outline:2px solid var(--fg);outline-offset:2px}

/* ---------- a policy, rendered for reading ---------- */

/* A controlled document is read straight through, so it gets a measure and
   room to breathe rather than the dense grid the registers use. */
.policydoc{max-width:74ch;font-size:14px;line-height:1.65}
.policydoc h2{font-size:15.5px;font-weight:600;margin:26px 0 10px;
  padding-bottom:6px;border-bottom:1px solid var(--line)}
.policydoc h2:first-child{margin-top:0}
.policydoc h3{font-size:13.5px;font-weight:600;margin:18px 0 8px;color:var(--dim)}
.policydoc p{margin:0 0 12px}
.policydoc ul{margin:0 0 14px;padding-left:20px}
.policydoc li{margin-bottom:6px}
.policydoc strong{color:var(--fg);font-weight:600}
.policydoc table{font-size:13px}
.policydoc .tablewrap{margin:0 0 16px}

/* The compliance item editor, in its full-width row. Same shape as the Annex A
   controls: it used to open inside the narrowest column, which is why the
   attach-a-certificate form in it appeared not to work at all. */
.itemedit{max-width:900px}
.itemactions{display:flex;gap:20px;flex-wrap:wrap;align-items:center;
  margin-top:18px;padding-top:14px;border-top:1px solid var(--line)}
.itemactions form.inline{gap:8px;flex-wrap:wrap}
/* The renewal note sits under the two buttons rather than in a tooltip: which
   one to press is the only decision on this panel worth explaining. */
.itemhint{flex-basis:100%;margin:0;font-size:12px;color:var(--dim)}

/* ---------- what a policy actually covers ---------- */

/* The headline figure, then the limits behind it. One number cannot say which
   section it belongs to, and a customer asking for the professional indemnity
   limit on a combined policy would otherwise be told the wrong one. */
.covermain{font-variant-numeric:tabular-nums;font-weight:600}
.coverlines{margin-top:5px;font-size:11.5px;color:var(--dim);line-height:1.5}
.coverlines .amt{font-variant-numeric:tabular-nums;color:var(--fg)}

.coveredit{margin-top:18px;padding-top:14px;border-top:1px solid var(--line);
  max-width:900px}
.coveredit > .hd{padding:0 0 10px;border:0;background:none;font-size:13px}
.coverbreak{width:auto;margin-bottom:12px;font-size:13px}
.coverbreak td{padding:4px 16px 4px 0;border-bottom:1px solid var(--line)}
.coverbreak .amt{font-variant-numeric:tabular-nums;font-weight:600;
  text-align:right;white-space:nowrap}
.coveredit form.inline{gap:8px;flex-wrap:wrap}
.coveredit form.inline input{min-width:150px}

/* Certificates an item has carried. The superseded ones are the point: a
   claims-made policy is answered by what was in force at the time. */
.certhist{margin-top:18px;padding-top:14px;border-top:1px solid var(--line);
  max-width:900px}
.certhist > .hd{padding:0 0 10px;border:0;background:none;font-size:13px}

/* A checkbox field: the box beside its label (deal.html). */
.field.check { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
.field.check label { margin: 0; }
.field.check > .dim { flex-basis: 100%; }
