/* Generated by scripts/compile-theme-tokens.mjs. Do not edit. */
:root, [data-ez-theme="precision"] {
  color-scheme: light;
  --ez-ground: #FFFFFF;
  --ez-surface: #F9FAFB;
  --ez-border: #E5E7EB;
  --ez-ink: #1A1A2E;
  --ez-muted: #5F6673;
  --ez-action: #254FE8;
  --ez-action-fg: #FFFFFF;
  --ez-live: #15803D;
  --ez-data: #254FE8;
  --ez-money: #FDF7EE;
  --ez-status-bg: #F1F5F9;
  --ez-status-fg: #5F6673;
  --ez-ink-2: #374151;
  --ez-orange-tint: #EFF3FF;
  --ez-blue-light: #C7D2FE;
  --ez-red: #B91C1C;
  --ez-amber: #B45309;
  --ez-amber-bg: #FEF3C7;
  --ez-live-text: #15803D;
  --ez-surface-2: #F3F4F6;
  --ez-row-hover: #EFF3FF;
  --ez-band: #1A1A2E;
  --ez-band-ink: #FFFFFF;
  --ez-shadow: #000000;
  --ez-font-display: "Google Sans Flex";
  --ez-font-body: "Google Sans Flex";
  --ez-font-mono: "IBM Plex Mono";
  --ez-radius: 8px;
}
[data-ez-theme="precision"][data-ez-scheme="dark"] {
  color-scheme: dark;
  --ez-ground: #0C111C;
  --ez-surface: #131A27;
  --ez-border: #1E2635;
  --ez-ink: #F6F7F8;
  --ez-muted: #8A929E;
  --ez-action: #3D63F0;
  --ez-action-fg: #FFFFFF;
  --ez-live: #22C55E;
  --ez-data: #6B8CF7;
  --ez-money: #1F1A10;
  --ez-status-bg: #1B2332;
  --ez-status-fg: #A3ABB7;
  --ez-ink-2: #D1D5DB;
  --ez-orange-tint: #1A2547;
  --ez-blue-light: #A5B4FC;
  --ez-red: #F87171;
  --ez-amber: #FBBF24;
  --ez-amber-bg: #2A2110;
  --ez-live-text: #4ADE80;
  --ez-surface-2: #19212F;
  --ez-row-hover: #172036;
  --ez-band: #070A12;
  --ez-band-ink: #F6F7F8;
  --ez-shadow: #000000;
}
[data-ez-theme="precision"][data-ez-scheme="dim"] {
  color-scheme: dark;
  --ez-ground: #2A303C;
  --ez-surface: #313846;
  --ez-border: #434C5C;
  --ez-ink: #F1F3F6;
  --ez-muted: #B0B8C3;
  --ez-action: #3D63F0;
  --ez-action-fg: #FFFFFF;
  --ez-live: #4ADE80;
  --ez-data: #93AAFC;
  --ez-money: #3A3426;
  --ez-status-bg: #3A4252;
  --ez-status-fg: #C5CBD5;
  --ez-ink-2: #D8DCE2;
  --ez-orange-tint: #323E5C;
  --ez-blue-light: #A5B4FC;
  --ez-red: #FCA5A5;
  --ez-amber: #FCD34D;
  --ez-amber-bg: #463C22;
  --ez-live-text: #86EFAC;
  --ez-surface-2: #363E4D;
  --ez-row-hover: #323E5C;
  --ez-band: #1E232C;
  --ez-band-ink: #F1F3F6;
  --ez-shadow: #000000;
}
@media (prefers-color-scheme: dark) {
  [data-ez-theme="precision"][data-ez-scheme="system"]:not([data-ez-palette]) {
    color-scheme: dark;
    --ez-ground: #0C111C;
    --ez-surface: #131A27;
    --ez-border: #1E2635;
    --ez-ink: #F6F7F8;
    --ez-muted: #8A929E;
    --ez-action: #3D63F0;
    --ez-action-fg: #FFFFFF;
    --ez-live: #22C55E;
    --ez-data: #6B8CF7;
    --ez-money: #1F1A10;
    --ez-status-bg: #1B2332;
    --ez-status-fg: #A3ABB7;
    --ez-ink-2: #D1D5DB;
    --ez-orange-tint: #1A2547;
    --ez-blue-light: #A5B4FC;
    --ez-red: #F87171;
    --ez-amber: #FBBF24;
    --ez-amber-bg: #2A2110;
    --ez-live-text: #4ADE80;
    --ez-surface-2: #19212F;
    --ez-row-hover: #172036;
    --ez-band: #070A12;
    --ez-band-ink: #F6F7F8;
    --ez-shadow: #000000;
  }
}
/* Provider branding is fixed, independent of the workspace theme palette. */
:root {
  --ez-google-sign-in-background: #f2f2f2;
  --ez-google-sign-in-foreground: #1f1f1f;
}
* {
  box-sizing: border-box;
}

/*
 * `hidden` is a presentational default, so any `display` rule beats it. Every
 * component that sets `display` therefore un-hides itself, which is why nine
 * separate `.thing[hidden]` rules had accumulated below — each one someone
 * patching this locally. State one rule instead, and let `el.hidden` mean what
 * it says.
 */
[hidden] {
  display: none !important;
}

/*
 * Density and type scale. Old Harvest fitted a working week on one screen and
 * gave every page one number big enough to read from across the room; §4 and §6
 * of the old-UI analysis measure both. These are the tokens the list surfaces
 * need before there is a table to apply them to.
 */
:root {
  --ez-row-h: 40px;
  --ez-row-h-lead: 48px;
  --ez-display: 40px;
  --ez-display-lead: 44px;
  --ez-gutter: 32px;
}

[data-density='compact'] {
  --ez-row-h: 32px;
  --ez-row-h-lead: 40px;
}

/*
 * The tracker design's type and elevation (the 2026-10 reskin). Body copy is
 * 14px; a page title is 20px at the family's heaviest cut, set tight; a label
 * is 11px bold capitals, tracked open. Figures take tabular numerals in the
 * body family rather than a monospace face, so columns still align and a
 * number reads as part of the line it sits in. The shadows are mixed from
 * `shadow`, a colour of their own: mixed from `ink` they turned into a pale
 * halo the moment a dark scheme made the ink light. The primary glow is the
 * action colour's own.
 */
:root {
  --ez-text-size: 14px;
  --ez-title-size: 20px;
  --ez-title-weight: 900;
  --ez-title-tracking: -0.04em;
  --ez-label-size: 11px;
  --ez-label-weight: 700;
  --ez-label-tracking: 0.06em;
  --ez-shadow-popover:
    0 8px 32px color-mix(in srgb, var(--ez-shadow) 14%, transparent),
    0 2px 8px color-mix(in srgb, var(--ez-shadow) 8%, transparent);
  --ez-shadow-menu:
    0 4px 16px color-mix(in srgb, var(--ez-shadow) 12%, transparent),
    0 1px 4px color-mix(in srgb, var(--ez-shadow) 6%, transparent);
  --ez-shadow-primary: 0 2px 8px color-mix(in srgb, var(--ez-action) 35%, transparent);
}
/*
 * The edge of a blue fill: every primary button, and the sidebar's tile for
 * the section you are in. It is the fill itself, except in Dim, whose slate is
 * light enough that the action blue stands only 2.7:1 off it, short of the 3:1
 * a control's boundary owes -- and no blue can both carry a white label at
 * 4.5:1 and clear that slate. So in Dim the label stays white on the blue and
 * the edge is `data`, the reading blue, which the contrast gate holds to 3:1
 * or better on every ground a fill sits on. Declared wherever a theme is, so a
 * document on white paper inside a dim page takes its edges from its own
 * palette.
 */
:root,
[data-ez-theme] {
  --ez-action-edge: var(--ez-action);
}
[data-ez-scheme='dim'] {
  --ez-action-edge: var(--ez-data);
}

/*
 * Text size (issue 800). Every size below is a pixel literal, so the honest way
 * to scale the type is to scale the whole shell: `zoom` does that without
 * touching the literals, keeps layout math intact, and leaves the browser's own
 * zoom for the chrome. Documents a client receives render in their own shell
 * and do not inherit it.
 */
[data-text-size='small'] {
  zoom: 0.9;
  --ez-zoom: 0.9;
}
[data-text-size='large'] {
  zoom: 1.125;
  --ez-zoom: 1.125;
}
[data-text-size='larger'] {
  zoom: 1.25;
  --ez-zoom: 1.25;
}
/*
 * `zoom` scales viewport units with everything else, so under it a box that
 * is 100vh tall is 90%, 112.5% or 125% of the window. Anything that has to be
 * exactly the window divides by the zoom it is under: the sign-in page stopped
 * 100px short of a 1000px window at the Small text size.
 */

/*
 * The icon set. Every icon is a 20px square drawn on a 20-unit grid in the
 * colour of the text beside it, which is why an icon inside a control that
 * changes colour on hover or on disable changes with it, and why none of them
 * needs a second definition per theme. The chevron is drawn once pointing
 * right and turned here; four paths would be four things to keep at one weight.
 */
.ez-icon {
  width: 20px;
  height: 20px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ez-icon[data-direction='up'] {
  transform: rotate(-90deg);
}

.ez-icon[data-direction='down'] {
  transform: rotate(90deg);
}

.ez-icon[data-direction='left'] {
  transform: rotate(180deg);
}

/*
 * A control whose whole label is an icon has to centre it. The inline default
 * puts the icon on the text baseline, leaving descender space under it and the
 * glyph sitting a couple of pixels low inside a 36px button.
 */
.command-trigger,
.week-actions button,
.day-switcher button,
.team-period button,
.data-table-overflow > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

/*
 * Two page surfaces, not nine width caps. Grid surfaces (timesheet, lists,
 * report tables) are full-bleed because rows earn the width; document surfaces
 * (settings, forms) cap the measure because prose does not.
 */
.page--grid {
  max-width: none;
  padding-inline: var(--ez-gutter);
}

.page--document {
  width: min(720px, 100%);
  margin-inline: auto;
}

/* The settings pages carry tables, toggles and fact lists rather than prose,
   and at the document width they wrapped (issue 800). A fixed 1100px then left
   most of a wide window empty (issue 857): the page takes nine tenths of a
   window wider than that, never less than it had, and never more than the
   narrow screen offers. The forms inside keep their own measure. */
.page--settings {
  width: min(max(90vw, 1100px), 100%);
  margin-inline: auto;
}

.team-workspace {
  width: 100%;
}
.team-period,
.team-list-toolbar,
.team-person-tabs,
.team-rate-columns,
.team-form-pair,
.team-summary {
  display: flex;
  gap: 12px;
}
.team-period {
  align-items: center;
  margin-top: 20px;
}
.team-period strong {
  min-width: 180px;
  text-align: center;
}
.team-period button,
.team-list-toolbar button,
.team-person-tabs button,
.team-workspace button,
.team-workspace input,
.team-rate-dialog input,
.team-deactivate-dialog input {
  min-height: 44px;
}
.team-list-toolbar {
  align-items: end;
  justify-content: space-between;
  margin-top: 20px;
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.team-list-toolbar fieldset {
  display: flex;
  margin: 0;
  padding: 0;
  border: 0;
}
.team-list-toolbar label,
.team-person-form label,
.team-person-panel > form > label,
.team-rate-dialog label,
.team-deactivate-dialog label {
  display: grid;
  gap: 6px;
}
.team-list-toolbar input,
.team-person-form input,
.team-person-panel input,
.team-rate-dialog input,
.team-deactivate-dialog input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
/*
 * A 140px floor cannot hold these numbers. They are set in
 * `clamp(28px, 3.2vw, 46px)` tabular figures, so the figure grows with the viewport
 * while the track floor does not, and `328.25h` overran into the column beside
 * it -- the strip read `328.25h284.5h`, two figures printed over one another.
 *
 * Fixed the way `.dashboard-cards` already fixes it for the very same numeral,
 * rather than inventing a second mechanism: a wider floor that still yields on
 * a narrow screen, children that may shrink, and a figure that wraps rather
 * than spilling. `minmax(max-content, 1fr)` was the other candidate and was
 * rejected -- auto-fit cannot size a track below max-content, so a long figure
 * would widen the page into a horizontal scroll instead of wrapping.
 *
 * Now covered, by `e2e/team-summary.browser.ts` (issue 524). It was not, and the
 * reason is worth keeping: the acceptance roster was the signed-in owner alone,
 * so the strip read `1`, `35h`, `0.75h`, `0.75h`, `0h`, `2.1%` -- figures two to
 * five characters wide that fit any track at any width. A collision assertion
 * over those passes with these three declarations reverted, which is exactly
 * what happened. What guards them now is the `team-summary-seed` roster in
 * `apps/web/scripts/browser-worker-harness.mjs`; shrink its hours and the guard
 * goes quiet again rather than failing, which is why that seed asserts its own
 * totals.
 */
.team-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  gap: 16px 24px;
  margin-top: 20px;
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.team-summary > div {
  display: grid;
  min-width: 0;
  gap: 4px;
}
.team-summary span,
.team-person-name p {
  color: var(--ez-muted);
}

/* The old app's KPI numerals are what make this screen readable in one look;
   at body size they are five labels you have to stop and parse. */
.team-summary strong {
  overflow-wrap: anywhere;
  font: 600 clamp(28px, 3.2vw, 46px)/1.05 var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
}

/* The swatch ties the number to its segment in the bar below, so the split is
   readable without matching label text to colour by memory. */
.team-summary > div[data-swatch] span::before {
  display: inline-block;
  width: 10px;
  height: 10px;
  margin-right: 6px;
  border-radius: 2px;
  content: '';
}

.team-summary > div[data-swatch='billable'] span::before {
  background: var(--ez-action);
}

.team-summary > div[data-swatch='nonbillable'] span::before {
  background: var(--ez-border);
}

.team-summary-bar {
  display: flex;
  grid-column: 1 / -1;
  height: 12px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--ez-ground);
}

.team-summary-bar span[data-part='billable'] {
  background: var(--ez-action);
}

.team-summary-bar span[data-part='nonbillable'] {
  background: var(--ez-border);
}
.team-page-status {
  min-height: 24px;
  margin-block: 16px;
}
.team-empty {
  padding: 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.team-person-name {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

/*
 * The ellipsis has to live on the text, not on the cell.
 *
 * `.data-table tbody td` already sets overflow:hidden and text-overflow:ellipsis,
 * but this cell's overflowing box is a flex child two levels down, so the td
 * clipped it without ever drawing the ellipsis — "Avery Featherstone" came out as
 * "Avery Featherston", cut mid-glyph, with nothing to say it had been cut. A flex
 * item also keeps min-width:auto, so the identity column refused to shrink and
 * pushed the clip further out at 100% zoom while looking fine at 90%.
 */
.team-person-name > div {
  min-width: 0;
}
.team-person-name a,
.team-person-name p {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.team-person-name a {
  color: var(--ez-ink);
  font-weight: 700;
}
.team-person-name p {
  margin: 4px 0 0;
  font-size: 13px;
}

/*
 * The numbers need their own width and no more; the name is what should take
 * the slack. Every cell carries max-width:0 so the table shares width by
 * content, which handed "0h" and "0%" as much room as a full name — the reason
 * the roster had dead space in the middle and a clipped column at the edge.
 * `width: 1%` with nowrap is the shrink-to-content idiom for a table cell.
 */
[data-team-list] th:not(:first-child),
[data-team-list] td:not(:first-child) {
  width: 1%;
  white-space: nowrap;
}
.team-avatar {
  display: grid;
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  place-items: center;
  overflow: hidden;
  border-radius: 50%;
  background: var(--ez-action);
  background-position: center;
  background-size: cover;
  color: var(--ez-action-fg);
  font-weight: 700;
}
.team-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.team-utilization {
  display: grid;
  gap: 4px;
  min-width: 120px;
  justify-items: end;
}
.team-utilization progress {
  width: 100%;
  height: 10px;
  overflow: hidden;
  border: 0;
  border-radius: 999px;
  background: var(--ez-ground);
}
.team-utilization progress::-webkit-progress-bar {
  background: var(--ez-ground);
}
.team-utilization progress::-webkit-progress-value,
.team-utilization progress::-moz-progress-bar {
  background: var(--ez-action);
}
.team-person-header {
  align-items: end;
}
.team-person-tabs {
  overflow-x: auto;
  border-bottom: 1px solid var(--ez-border);
  scrollbar-width: thin;
}
.team-person-tabs button {
  flex: 0 0 auto;
  border: 0;
  border-bottom: 3px solid transparent;
  background: transparent;
  color: var(--ez-muted);
}
.team-person-tabs button[aria-selected='true'] {
  border-bottom-color: var(--ez-action);
  color: var(--ez-ink);
  font-weight: 700;
}
.team-person-panel {
  padding-block: 24px;
}
.team-person-panel > header,
.team-rate-columns section > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.team-person-panel h2,
.team-person-panel h3,
.team-status-actions h3 {
  margin-top: 0;
}
.team-person-form,
.team-person-panel > form {
  display: grid;
  gap: 16px;
  max-width: 760px;
}
.team-form-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.team-person-form fieldset,
.team-person-panel fieldset {
  margin: 0;
  padding: 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.team-option-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px 16px;
  margin-top: 10px;
}
.team-check {
  display: flex !important;
  align-items: center;
  gap: 8px !important;
  min-height: 36px;
}
.team-check input {
  width: auto;
  min-height: 20px;
}
/* A scope the acting profile cannot grant stays visible so the list reads the
   same for everyone, and reads as not yours to tick. */
.team-check--unavailable {
  color: var(--ez-muted);
}
.settings-token-dialog .hint,
.settings-token-dialog .form-result {
  margin: 0;
}
.settings-token-reveal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0;
}
.settings-token-reveal code {
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px 10px;
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) / 2);
  background: var(--ez-ground);
  font: 13px/1.6 var(--ez-font-mono), monospace;
  overflow-wrap: anywhere;
}
.settings-token-eye {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 36px;
  min-width: 36px;
  padding: 0;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: none;
  color: inherit;
}
/* One button, two drawings: the eye when the value is masked, the crossed eye
   when it is shown, so the control says what pressing it will do. */
.settings-token-eye [data-icon='eye-off'],
.settings-token-eye[aria-pressed='true'] [data-icon='eye'] {
  display: none;
}
.settings-token-eye[aria-pressed='true'] [data-icon='eye-off'] {
  display: block;
}
.team-status-actions {
  max-width: 760px;
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--ez-border);
}
.settings-payout form,
.settings-payout-current {
  display: grid;
  gap: 12px;
  max-width: 640px;
}
.settings-payout-current {
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.team-payout-current {
  max-width: 760px;
  margin-bottom: 28px;
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.team-payout-current h3 {
  margin-top: 0;
}
.team-rate-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: 20px;
}
.team-rate-columns section {
  min-width: 0;
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.team-rate-columns table {
  width: 100%;
  margin-top: 12px;
  border-collapse: collapse;
}
.team-rate-columns th,
.team-rate-columns td {
  padding: 10px 6px;
  border-bottom: 1px solid var(--ez-border);
  text-align: left;
}
.team-project-assignment-list {
  display: grid;
  gap: 10px;
}
.team-project-assignment {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 16px;
  align-items: center;
  padding: 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.team-profile-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 10px;
}
.team-profile-option {
  display: flex;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.team-profile-option:has(input:checked) {
  border-color: var(--ez-action);
  background: var(--ez-status-bg);
}
.team-profile-option input {
  width: auto;
  min-height: 20px;
}
.team-profile-option span,
.team-profile-option small {
  display: grid;
  gap: 4px;
}
.team-profile-option small {
  color: var(--ez-muted);
  line-height: 1.35;
}
.team-weekdays {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
  margin-top: 10px;
}
.team-weekdays label {
  display: grid;
  min-height: 52px;
  place-items: center;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.team-weekdays input {
  width: auto;
  min-height: 18px;
}
.team-rate-dialog form,
.team-deactivate-dialog form {
  display: grid;
  gap: 16px;
}
.team-rate-dialog header,
.team-deactivate-dialog header,
.team-confirm-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

@media (max-width: 720px) {
  .team-period {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) 44px;
  }
  .team-period strong {
    min-width: 0;
  }
  .team-period [data-team-week-current] {
    grid-column: 1 / -1;
    width: 100%;
  }
  .team-list-toolbar,
  .team-person-panel > header,
  .team-rate-columns section > header {
    align-items: stretch;
    flex-direction: column;
  }
  .team-list-toolbar label,
  .team-list-toolbar input,
  .team-list-toolbar fieldset,
  .team-list-toolbar button,
  .team-person-panel button,
  .team-status-actions button,
  .team-rate-dialog button,
  .team-deactivate-dialog button,
  .team-person-dialog button {
    width: 100%;
  }
  .team-summary,
  .team-form-pair,
  .team-option-grid,
  .team-rate-columns,
  .team-profile-grid {
    grid-template-columns: 1fr;
  }
  .team-summary {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .team-person-tabs {
    margin-inline: -16px;
    padding-inline: 16px;
  }
  .team-person-tabs button {
    min-width: 120px;
  }
  .team-project-assignment {
    grid-template-columns: 1fr;
    gap: 4px;
  }
  .team-weekdays {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .team-confirm-actions {
    align-items: stretch;
    flex-direction: column-reverse;
  }
}

.reports-workspace {
  width: 100%;
  min-width: 0;
}
.reports-intro {
  max-width: 64ch;
  color: var(--ez-muted);
}
.report-filters {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr)) auto;
  align-items: end;
  gap: 12px;
  margin-top: 24px;
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.report-filter-field {
  display: grid;
  gap: 6px;
  min-width: 0;
}
.report-filter-field label {
  color: var(--ez-muted);
  font-size: 12px;
}
.report-filters input,
.report-filters select,
.report-filters button,
[data-report-retry] {
  min-width: 0;
  min-height: 44px;
}
.report-filters input,
.report-filters select {
  width: 100%;
  padding: 0 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.report-filters [hidden] {
  display: none;
}
/*
 * The period takes two of the card's five columns because it carries an arrow,
 * a kind and a label where the fields beside it carry one control each. Two of
 * five is the width the From and To fields it replaced already had, so the card
 * keeps its column count and the filter row stays one band — §6 allows three
 * between the tab strip and the first data row, and reports already spends them
 * on the heading, the intro and this card.
 */
.report-period-field {
  grid-column: span 2;
}
.period-control {
  display: grid;
  gap: 6px;
  min-width: 0;
}
.period-field-label {
  color: var(--ez-muted);
  font-size: 12px;
}
.period-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.period-step {
  display: grid;
  place-items: center;
  flex: none;
  width: 44px;
  padding: 0;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.period-step:hover:not(:disabled),
.period-step:focus-visible:not(:disabled) {
  background: var(--ez-surface-2);
}
.period-step:disabled {
  opacity: 0.5;
}
/* The kind sizes to its own words; the shared `.report-filters select` rule
   would otherwise stretch it across the flex line and leave the label no room. */
.period-row .period-kind {
  flex: none;
  width: auto;
}
/*
 * The label is the control's answer, so it takes whatever the two buttons and
 * the select leave and truncates rather than wrapping: a second line here grows
 * the card, and growing the card is how a filter row turns into two bands.
 */
.period-summary {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  color: var(--ez-ink);
  font-variant-numeric: tabular-nums;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.period-custom {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  min-width: 0;
}
.report-status {
  min-height: 20px;
  margin-top: 16px;
}
[data-report-retry] {
  margin-top: 8px;
  padding-inline: 16px;
}
.report-results {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 20px;
  margin-top: 20px;
}
.report-results[aria-busy='true'] {
  opacity: 0.55;
}
.report-saved-library {
  display: grid;
  gap: 14px;
  margin-top: 20px;
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.report-saved-library[hidden] { display: none; }
.report-saved-library > header,
.report-saved-card {
  display: flex;
  align-items: center;
  gap: 12px;
}
.report-saved-library > header { justify-content: space-between; }
.report-saved-card {
  padding-block: 12px;
  border-top: 1px solid var(--ez-border);
}
.report-saved-card p { flex: 1; margin: 0; color: var(--ez-muted); }
[data-report-builder] {
  width: min(720px, calc((100vw - 32px) / var(--ez-zoom, 1)));
  max-height: calc((100vh - 32px) / var(--ez-zoom, 1));
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-ink);
}
[data-report-builder-form] { display: grid; gap: 14px; }
[data-report-builder-form] > label { display: grid; gap: 6px; }
[data-report-builder-form] select[multiple] { min-height: 120px; }
[data-report-builder-form] header,
[data-report-builder-form] footer { display: flex; justify-content: space-between; gap: 12px; }
.report-result-heading {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 20px;
  padding-bottom: 12px;
  border-bottom: 2px solid var(--ez-ink);
}
.report-result-title {
  margin: 0;
  font: 600 clamp(24px, 4vw, 34px) / 1.15 var(--ez-font-display), sans-serif;
}
.report-result-heading p {
  margin: 0;
  color: var(--ez-muted);
  font: 12px/1.4 var(--ez-font-body), sans-serif;
}
.report-currency-grid,
.report-budget-list,
.report-client-tree {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.report-currency-grid {
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}
.report-currency-card,
.report-budget-card,
.report-client-node,
.report-metadata {
  padding: 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.report-currency-card > header,
.report-budget-card > header,
.report-client-node > header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.report-currency-card h3,
.report-budget-card h3,
.report-client-node h3,
.report-metrics h4 {
  margin: 0;
}
.report-currency-card > header strong {
  font: 600 20px/1.2 var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
}
.report-budget-card > header span,
.report-client-node > header p {
  margin: 0;
  color: var(--ez-muted);
  font-size: 12px;
}
.report-facts,
.report-metadata {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 12px;
  margin-block: 16px 0;
}
.report-metadata {
  margin: 0;
}
.report-facts > div,
.report-metadata > div {
  min-width: 0;
}
.report-facts dt,
.report-metadata dt {
  color: var(--ez-muted);
  font-size: var(--ez-label-size);
  font-weight: var(--ez-label-weight);
  text-transform: uppercase;
  letter-spacing: var(--ez-label-tracking);
}
.report-facts dd,
.report-metadata dd {
  margin: 3px 0 0;
  font-variant-numeric: tabular-nums;
}
.report-warning,
.report-empty {
  margin: 14px 0 0;
  padding: 12px;
  border-radius: var(--ez-radius);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
  font-size: 13px;
}
.report-empty {
  margin: 0;
  padding: 36px 20px;
  text-align: center;
}
.report-client-node {
  margin-left: min(calc(var(--report-depth, 0) * 24px), 20%);
}
.report-rollup-comparison {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  margin-top: 16px;
}
.report-metrics {
  min-width: 0;
  padding: 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.report-table-wrap {
  max-width: 100%;
  margin-top: 14px;
  overflow-x: auto;
}
.report-table {
  width: 100%;
  min-width: 510px;
  border-collapse: collapse;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.report-table th,
.report-table td {
  padding: 8px;
  border-bottom: 1px solid var(--ez-border);
  text-align: right;
  white-space: nowrap;
}
/* The header band, the same one the data table carries. */
.report-table thead th {
  background: var(--ez-surface);
  color: var(--ez-muted);
  font-size: 13px;
  font-weight: 400;
}
.report-table tbody tr:hover {
  background: var(--ez-surface);
}
.report-table th:first-child,
.report-table td:first-child {
  text-align: left;
}
/* One block per currency in the contractor cost report, stacked rather than
   merged into a single table: the costs in two currencies do not add up, and a
   reader should not be able to run their eye down one column and add them. */
.report-cost-currency + .report-cost-currency {
  margin-top: 20px;
}
.report-cost-currency h3 {
  margin: 0;
}
/* The second line in a cell: contractor or employee under a name, and the count
   of entries with no rate under a cost that therefore does not exist. */
.report-cost-note {
  display: block;
  margin-top: 2px;
  color: var(--ez-muted);
  font-size: 11px;
}

/*
 * The detailed report is five text columns and one number, where every other
 * report table here is one label and a row of figures. Right-aligning names
 * would leave the reader tracking a ragged left edge down the longest table on
 * the screen, so the alignment is reversed and Hours opts back in.
 */
.report-detailed-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 20px;
  align-items: start;
}
.report-detailed-summary /* The line that says which hours the card counted. Quiet, because it explains
   a figure rather than competing with it. */
.report-card-note {
  margin: 8px 0 0;
  color: var(--ez-muted);
  font-size: 12px;
}
.report-facts {
  margin-block: 0;
}
.report-detailed-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 12px;
}
.report-detailed-controls select {
  min-width: 0;
  min-height: 44px;
  padding: 0 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.report-detailed-active {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  min-height: 44px;
}
.report-detailed-actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
}
.report-detailed-actions button {
  min-height: 44px;
  padding-inline: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
/* Export opens a panel under its toggle: whose notes, then the three files.
   It floats over the table rather than pushing it down, as the invoice page's
   Export hours menu does, and on a phone it takes a row of its own instead. */
.report-export {
  position: relative;
}
.report-export-menu {
  position: absolute;
  z-index: 5;
  top: calc(100% + 6px);
  right: 0;
  display: grid;
  gap: 2px;
  min-width: 260px;
  padding: 6px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  box-shadow: var(--ez-shadow-menu);
}
.report-export-notes {
  display: grid;
  gap: 2px;
  margin: 0 0 4px;
  padding: 4px 6px 8px;
  border: 0;
  border-bottom: 1px solid var(--ez-border);
}
.report-export-notes legend {
  padding: 4px 0 2px;
  color: var(--ez-muted);
  font: var(--ez-label-weight) var(--ez-label-size) / 1.2 var(--ez-font-body), sans-serif;
  letter-spacing: var(--ez-label-tracking);
  text-transform: uppercase;
}
.report-export-notes label {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  color: var(--ez-ink);
  font-size: 14px;
  cursor: pointer;
}
.report-export-notes input {
  margin: 0;
}
/* The actions row's own buttons are outlined blocks. A file in the list is not
   a control of that weight, so it drops the outline and reads as a list. */
.report-detailed-actions .report-export-item {
  min-height: 40px;
  padding: 0 10px;
  border: 0;
  background: transparent;
  text-align: left;
}
.report-detailed-actions .report-export-item:hover,
.report-detailed-actions .report-export-item:focus-visible {
  background: var(--ez-surface);
}
.report-detailed-table th,
.report-detailed-table td {
  text-align: left;
}
.report-detailed-table .report-numeric {
  text-align: right;
}
.report-detailed-table .report-band-row th,
.report-detailed-table .report-band-row td {
  background: var(--ez-surface);
  font-weight: 600;
}
.report-detailed-table tfoot th,
.report-detailed-table tfoot td {
  font-weight: 600;
}

/* The Time report's summary strip. Same shape as the team roster's, because it
   answers the same shape of question -- how much time, how much of it billable
   -- and two different treatments for one idea is how a product stops reading
   as one product. */
.report-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: 18px;
  padding: 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.report-summary > div {
  display: grid;
  align-content: start;
  gap: 4px;
  min-width: 0;
}
.report-summary span {
  color: var(--ez-muted);
  font-size: 12px;
}
/* The KPI numerals are what make the strip readable in one look; at body size
   they are four labels you have to stop and parse. */
.report-summary strong {
  overflow-wrap: anywhere;
  font: 600 clamp(22px, 2.4vw, 34px) / 1.05 var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
}
/* The swatch ties each figure to its segment in the bar below, so the split is
   readable without matching label text to colour by memory. */
.report-summary > div[data-swatch] span::before {
  display: inline-block;
  width: 10px;
  height: 10px;
  margin-right: 6px;
  border-radius: 2px;
  content: '';
}
.report-summary > div[data-swatch='billable'] span::before {
  background: var(--ez-action);
}
.report-summary > div[data-swatch='nonbillable'] span::before {
  background: var(--ez-border);
}
.report-summary-bar {
  display: flex;
  grid-column: 1 / -1;
  height: 12px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--ez-ground);
}
.report-summary-bar span[data-part='billable'] {
  background: var(--ez-action);
}
.report-summary-bar span[data-part='nonbillable'] {
  background: var(--ez-border);
}

/* The four groupings, as the level-3 strip they are in the report being
   matched. It borrows the shell strip's paint rather than inventing a third
   active-state treatment, but sits inside the results because it selects a fold
   of one answer rather than navigating anywhere. */
.report-subtabs {
  display: flex;
  align-items: stretch;
  gap: 24px;
  height: 40px;
  overflow-x: auto;
  border-bottom: 1px solid var(--ez-border);
}
.report-subtabs a {
  display: grid;
  place-items: center;
  color: var(--ez-muted);
  text-decoration: none;
  white-space: nowrap;
}
.report-subtabs a:hover,
.report-subtabs a:focus-visible {
  color: var(--ez-ink);
}
.report-subtabs a[aria-current='page'] {
  color: var(--ez-ink);
  box-shadow: inset 0 -3px var(--ez-data);
}

/* The bar column carries no number, so it takes whatever width is left after
   the figures have theirs; every other column shrinks to its content. */
.report-time-table .report-bar-cell {
  width: 30%;
  min-width: 90px;
}
.report-time-table progress {
  display: block;
  width: 100%;
  height: 10px;
  overflow: hidden;
  border: 0;
  border-radius: 999px;
  background: var(--ez-ground);
}
.report-time-table progress::-webkit-progress-bar {
  background: var(--ez-ground);
}
.report-time-table progress::-webkit-progress-value,
.report-time-table progress::-moz-progress-bar {
  background: var(--ez-action);
}
/* A currency per line: this product holds no exchange rate, so two currencies
   are two figures and never a sum. */
.report-time-table td span {
  display: block;
}
.report-group-row th {
  background: var(--ez-ground);
  color: var(--ez-muted);
  font-size: var(--ez-label-size);
  font-weight: var(--ez-label-weight);
  text-align: left;
  text-transform: uppercase;
  letter-spacing: var(--ez-label-tracking);
}
.report-time-table tfoot th,
.report-time-table tfoot td {
  font-weight: 700;
}

.invoice-workspace {
  width: 100%;
}
.invoice-create-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  text-decoration: none;
}
.invoice-intro {
  max-width: 64ch;
  color: var(--ez-muted);
}
.invoice-page-status {
  min-height: 20px;
}
.invoice-list {
  margin-top: 24px;
}
/*
 * A date is read whole or not at all. The list's cells take whatever width
 * the table's layout leaves them, and beside the sidebar at 1440px that cut
 * every due date to "Oct 31, 20…". Issued and Due are held at the width of a
 * date -- Due a little wider, for the bold date an overdue invoice leads with,
 * and an ellipsis after it -- and the client's name, which is free text, gives
 * way instead.
 */
.invoice-list .data-table [data-column='issued'] {
  width: 9.5em;
}
.invoice-list .data-table [data-column='due-date'] {
  width: 10.5em;
}
.invoice-state {
  width: fit-content;
  padding: 4px 8px;
  border-radius: 999px;
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
  font: 600 11px/1.2 var(--ez-font-body), sans-serif;
  text-transform: uppercase;
}
/* The four states someone scans the list for, each in a theme slot rather
   than a literal so every theme keeps its own contrast. The tint is the ink
   mixed into the ground, so the word still carries the meaning either way.
   Paid is the exception: it is the green the running timer is said in, on the
   pill's own band, because green mixed into the ground the way the others are
   would sit below 4.5:1. */
.invoice-state[data-tone='sent'] {
  background: color-mix(in srgb, var(--ez-data) 10%, var(--ez-ground));
  color: var(--ez-data);
}
.invoice-state[data-tone='late'] {
  background: color-mix(in srgb, var(--ez-red) 12%, var(--ez-ground));
  color: var(--ez-red);
}
.invoice-state[data-tone='paid'] {
  color: var(--ez-live-text);
}
.invoice-past-due {
  color: var(--ez-status-fg);
  font-weight: 700;
}
.invoice-load-more {
  min-height: 44px;
  margin-top: 16px;
  padding: 0 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.invoice-document {
  padding: clamp(20px, 4vw, 48px);
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  box-shadow: 0 18px 48px color-mix(in srgb, var(--ez-shadow) 8%, transparent);
}
.invoice-document-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  padding-bottom: 24px;
  border-bottom: 2px solid var(--ez-ink);
}
.invoice-document-heading h2 {
  margin: 0;
  font: 600 clamp(26px, 4vw, 40px) / 1.1 var(--ez-font-display), sans-serif;
}
.invoice-document-heading p:last-child {
  margin: 8px 0 0;
  color: var(--ez-muted);
  white-space: pre-wrap;
}
.invoice-document-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
.invoice-document-actions button,
.invoice-document-actions .invoice-edit-link {
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ink);
  color: var(--ez-ground);
}
/* Edit invoice leads to a page, so it is a link, drawn as the header's other
   actions are. Without an href (a closed invoice) it is the disabled state a
   link has, and the title says why. */
.invoice-document-actions .invoice-edit-link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  white-space: nowrap;
}
:is(.invoice-edit-link, .invoice-line-add)[aria-disabled='true'] {
  cursor: not-allowed;
  opacity: 0.55;
}
.invoice-overflow {
  position: relative;
}
.invoice-overflow-menu {
  position: absolute;
  z-index: 5;
  top: calc(100% + 6px);
  right: 0;
  display: grid;
  gap: 2px;
  min-width: 230px;
  padding: 6px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  box-shadow: var(--ez-shadow-menu);
}
/* The header's own buttons are filled ink blocks. A menu row is not a control
   of that weight, so it drops the fill and reads as a list of verbs. */
.invoice-document-actions .invoice-overflow-item {
  min-height: 40px;
  padding: 0 10px;
  border: 0;
  background: transparent;
  color: var(--ez-ink);
  text-align: left;
}
.invoice-document-actions .invoice-overflow-item:hover {
  background: var(--ez-surface);
}
/* Destructive verbs are red *text*, never a red fill. A filled button's label
   is body text over the fill and owes 4.5:1; `red` on the document ground
   scores 5.46:1 and on the hover surface 5.04:1, so both are covered without
   making cancelling an invoice the loudest thing on the page. */
.invoice-document-actions .invoice-overflow-destructive,
.invoice-payment-dialog-actions .invoice-destructive-action {
  color: var(--ez-red);
  font-weight: 600;
}
.invoice-payment-dialog-actions .invoice-destructive-action {
  border-color: var(--ez-red);
  background: transparent;
}
.invoice-transition-dialog p:not(.hint):not(.form-result) {
  margin: 0;
}
.invoice-reminder-line {
  margin: 16px 0 0;
  padding: 10px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
}
.invoice-facts,
.invoice-totals {
  display: grid;
  gap: 12px 24px;
  margin: 0;
}
.invoice-facts {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  padding: 24px 0;
}
.invoice-facts div,
.invoice-totals div {
  display: grid;
  gap: 4px;
}
.invoice-facts dt {
  color: var(--ez-muted);
  font-size: 12px;
}
/* Secondary ink rather than `muted`: these labels sit on the money band, not on
   the page ground, and `muted` is tuned against the ground. The two are not the
   same job -- `muted` de-emphasises metadata on `surface`, `ink_2` carries the
   same weight of text where the ground beneath it is tinted. */
.invoice-totals dt {
  color: var(--ez-ink-2);
  font-size: 12px;
}
.invoice-facts dd,
.invoice-totals dd {
  margin: 0;
}
.invoice-line-wrap {
  overflow-x: auto;
}
.invoice-line-editor {
  margin-top: 8px;
}
.invoice-line-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
}
.invoice-line-heading h3 {
  margin: 0 0 4px;
  font-size: 16px;
}
.invoice-line-heading p {
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.invoice-line-heading .invoice-line-add,
.invoice-line-actions a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  text-decoration: none;
  white-space: nowrap;
}
.invoice-line-editor > .form-result,
.invoice-line-editor > .hint {
  margin-bottom: 12px;
}
.invoice-line-table {
  width: 100%;
  min-width: 620px;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.invoice-line-table th,
.invoice-line-table td {
  padding: 12px 10px;
  border-bottom: 1px solid var(--ez-border);
  text-align: right;
}
.invoice-line-table th:first-child,
.invoice-line-table tbody th {
  text-align: left;
}
.invoice-line-table tbody th strong,
.invoice-line-table tbody th span,
.invoice-line-table tbody th small {
  display: block;
}
.invoice-line-table tbody th span,
.invoice-line-table tbody th small {
  margin-top: 3px;
  color: var(--ez-muted);
  font-weight: 400;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
/* One link now, so the cell stays a table cell: a flex cell drops out of the
   table's own layout and its rule no longer meets the rest of the row's. */
.invoice-line-actions {
  text-align: right;
  white-space: nowrap;
}
.invoice-line-actions a {
  min-height: 36px;
  padding-inline: 10px;
}
.invoice-line-table thead th {
  color: var(--ez-muted);
  font-size: 12px;
  font-weight: 500;
}
.invoice-line-empty {
  color: var(--ez-muted);
  text-align: center !important;
}
/* The money band (issue 299): the totals block reads as money rather than as one
   more list, which is the job `money` has been holding a colour for. */
.invoice-totals {
  width: min(360px, 100%);
  margin: 24px 0 0 auto;
  padding: 16px 18px;
  background: var(--ez-money);
  border-radius: var(--ez-radius);
}
.invoice-totals div {
  grid-template-columns: minmax(0, 1fr) auto;
}
.invoice-totals dd {
  font-family: var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.invoice-totals div:last-child {
  padding-top: 10px;
  border-top: 2px solid var(--ez-ink);
  font-weight: 700;
}
.invoice-notes,
.invoice-history,
.invoice-attachment-section {
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--ez-border);
}
.invoice-attachment-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  max-width: 100%;
}
.invoice-attachment-form button,
.invoice-attachment-form input {
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  min-width: 0;
  max-width: 100%;
}
[data-invoice-attachments] {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 10px;
}
[data-invoice-attachments] li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  min-width: 0;
}
[data-invoice-attachments] a {
  color: var(--ez-ink);
  font-weight: 700;
  text-underline-offset: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.invoice-attachment-empty {
  color: var(--ez-muted);
  font-size: 14px;
}
.invoice-notes h3,
.invoice-history h3 {
  margin: 0 0 12px;
  font-size: 16px;
}
.invoice-payment-heading {
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 16px;
  margin-bottom: 12px;
}
/* Two buttons now, not one, and space-between would have pushed them apart to
   either side of the row. The auto margin belongs to the title instead, so the
   pair stays together at the end and reads as one group of actions. */
.invoice-payment-heading > div {
  margin-inline-end: auto;
}
.invoice-payment-heading h3 {
  margin-bottom: 4px;
}
.invoice-payment-heading p {
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.invoice-payment-heading button,
.invoice-payment-actions button {
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.invoice-payment-heading button {
  flex: 0 0 auto;
}
.invoice-payment-history > .form-result,
.invoice-payment-history > .hint {
  margin-bottom: 12px;
}
.invoice-notes p {
  margin: 0;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.invoice-history ul {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.invoice-history li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 16px;
  padding: 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.invoice-history li > span,
.invoice-history li > small {
  color: var(--ez-muted);
  font-size: 12px;
}
.invoice-history li > p,
.invoice-history li > small {
  grid-column: 1 / -1;
  margin: 0;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.invoice-history .invoice-payment-metadata {
  white-space: normal;
}
.invoice-payment-actions {
  grid-column: 1 / -1;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 4px;
}
.invoice-history-empty {
  display: block !important;
  color: var(--ez-muted);
}
.invoice-generation-form select:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.invoice-generation-form .field-hint {
  margin: -6px 0 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.invoice-payment-amount-fields {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(110px, 0.35fr);
  gap: 12px;
}
.invoice-payment-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.invoice-payment-dialog-actions button {
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.invoice-composer-dialog {
  width: min(920px, calc((100vw - 32px) / var(--ez-zoom, 1)));
}
/* The invoice dialogs share one frame: the title and the actions stay put in
   bands of their own while a long form scrolls between them, so Save is never
   below the fold. */
:is(.invoice-payment-dialog, .invoice-transition-dialog, .invoice-composer-dialog) > form {
  max-height: calc((100dvh - 48px) / var(--ez-zoom, 1));
  padding: 0 24px;
  overflow-y: auto;
}
:is(.invoice-payment-dialog, .invoice-transition-dialog, .invoice-composer-dialog) > form > header {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 -24px;
  padding: 20px 24px 14px;
  border-bottom: 1px solid var(--ez-border);
  background: var(--ez-ground);
}
:is(.invoice-payment-dialog, .invoice-transition-dialog, .invoice-composer-dialog) h2 {
  margin: 0;
}
:is(.invoice-payment-dialog, .invoice-transition-dialog, .invoice-composer-dialog) > form > .invoice-payment-dialog-actions {
  position: sticky;
  bottom: 0;
  margin: 4px -24px 0;
  padding: 14px 24px;
  border-top: 1px solid var(--ez-border);
  background: var(--ez-surface);
}
:is(.invoice-payment-dialog, .invoice-transition-dialog, .invoice-composer-dialog) .hint {
  margin-top: -6px;
  font-size: 13px;
}
.invoice-payment-dialog-actions button:not(.primary-action) {
  background: var(--ez-ground);
}
.invoice-composer-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(230px, 0.8fr);
  gap: 20px;
}
.invoice-composer-fields {
  display: grid;
  gap: 12px;
}
.invoice-composer-check {
  display: flex;
  align-items: center;
  gap: 8px;
}
.invoice-composer-check input {
  width: auto;
  min-height: auto;
}
/* Recording the invoice as sent without emailing it is allowed, and it is the
   exception: said in the amber the shell uses for a warning, not in a hint's
   grey, so it is not mistaken for the send. */
.invoice-composer-no-email {
  margin: 0;
  padding: 8px 12px;
  border: 1px solid var(--ez-amber);
  border-radius: var(--ez-radius);
  background: var(--ez-amber-bg);
  font-size: 13px;
}
/* What goes with the email. Grouped under the delivery switch because it
   only means anything once the email is being sent, and greyed until then. */
.invoice-composer-attachments {
  display: grid;
  gap: 4px;
  min-width: 0;
  margin: 0;
  padding: 10px 12px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.invoice-composer-attachments > legend {
  padding: 0 4px;
  color: var(--ez-muted);
  font-size: 13px;
}
.invoice-composer-attachments .invoice-composer-check {
  min-height: 44px;
  color: var(--ez-ink);
  font-size: 14px;
}
.invoice-composer-attachments .invoice-composer-check:has(input:disabled) {
  color: var(--ez-muted);
}
.invoice-composer-journal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 12px;
}
.invoice-composer-journal select {
  width: auto;
}
.invoice-composer-formats {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0 14px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.invoice-composer-formats label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  color: var(--ez-ink);
  font-size: 14px;
}
.invoice-composer-formats input {
  width: auto;
  min-height: auto;
}
/* The preview: the email exactly as it would go, before it goes. The body is
   the plain text the client reads, so it keeps its line breaks. */
.invoice-composer-preview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.invoice-composer-preview-actions button {
  min-height: 44px;
}
.invoice-composer-preview {
  display: grid;
  gap: 8px;
  min-width: 0;
  padding: 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.invoice-composer-preview h3,
.invoice-composer-preview h4,
.invoice-composer-preview ul {
  margin: 0;
}
.invoice-composer-preview h4 {
  font-size: 13px;
  color: var(--ez-muted);
}
.invoice-composer-preview-headers {
  display: grid;
  gap: 4px;
  margin: 0;
}
.invoice-composer-preview-headers > div {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  gap: 8px;
}
.invoice-composer-preview-headers dt {
  color: var(--ez-muted);
}
.invoice-composer-preview-headers dd {
  margin: 0;
  overflow-wrap: anywhere;
}
.invoice-composer-preview-body {
  max-height: 320px;
  margin: 0;
  overflow: auto;
  padding: 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: inherit;
  font-size: 13px;
}
.invoice-composer-preview a[data-invoice-preview-document] {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.invoice-variable-reference {
  align-self: start;
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.invoice-variable-reference h3,
.invoice-variable-reference p,
.invoice-variable-reference dl {
  margin: 0;
}
.invoice-variable-reference p {
  margin-top: 6px;
  color: var(--ez-muted);
  font-size: 13px;
}
.invoice-variable-reference dl {
  display: grid;
  gap: 12px;
  margin-top: 16px;
}
.invoice-variable-reference dt,
.invoice-variable-reference dd {
  margin: 0;
}
.invoice-variable-reference dd {
  margin-top: 3px;
  color: var(--ez-muted);
  font-size: 12px;
}
/* New invoice (one step, then the draft). A header and a footer band hold
   the title and the actions still while the body scrolls on a short screen. */
.invoice-new-dialog {
  width: min(600px, calc((100vw - 32px) / var(--ez-zoom, 1)));
}
.invoice-new-dialog > form {
  gap: 0;
  padding: 0;
}
.invoice-new-dialog > form > header {
  padding: 20px 24px 16px;
  border-bottom: 1px solid var(--ez-border);
}
.invoice-new-dialog h2 {
  margin: 0;
}
.invoice-new-body {
  display: grid;
  gap: 20px;
  max-height: min(calc(64vh / var(--ez-zoom, 1)), 640px);
  padding: 20px 24px;
  overflow-y: auto;
}
.invoice-new-body > .form-result:empty {
  display: none;
}
.invoice-new-tracked {
  display: grid;
  gap: 20px;
}
.invoice-new-tracked[hidden],
.invoice-period[hidden] {
  display: none;
}
.invoice-new-dialog fieldset {
  display: grid;
  gap: 10px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.invoice-new-dialog legend {
  margin-bottom: 8px;
  padding: 0;
  color: var(--ez-ink);
  font-weight: 600;
  font-size: 13px;
}
.invoice-new-dialog > form > footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 24px;
  border-top: 1px solid var(--ez-border);
  background: var(--ez-surface);
  border-radius: 0 0 calc(var(--ez-radius) * 2) calc(var(--ez-radius) * 2);
}
.invoice-new-dialog > form > footer button {
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
}
.invoice-new-dialog > form > footer .primary-action {
  border-color: var(--ez-action-edge);
  background: var(--ez-action);
  color: var(--ez-action-fg);
}
.invoice-new-dialog > form > footer [data-retry-invoice-catalog] {
  margin-right: auto;
}
/* The types as cards, the chosen one in the action colour. */
.invoice-new-kind {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.invoice-new-kind legend {
  grid-column: 1 / -1;
}
.invoice-new-dialog .invoice-new-kind-card {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) * 1.5);
  background: var(--ez-ground);
  color: var(--ez-ink);
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}
.invoice-new-kind-card:hover {
  border-color: color-mix(in srgb, var(--ez-action) 45%, var(--ez-border));
}
.invoice-new-kind-card:has(input:checked) {
  border-color: var(--ez-action);
  background: color-mix(in srgb, var(--ez-action) 6%, var(--ez-ground));
  box-shadow: 0 0 0 1px var(--ez-action);
}
.invoice-new-kind-card:has(input:focus-visible) {
  outline: 2px solid var(--ez-action);
  outline-offset: 2px;
}
.invoice-new-kind-card span {
  display: grid;
  gap: 2px;
}
.invoice-new-kind-card strong {
  font-size: 14px;
}
.invoice-new-kind-card small {
  color: var(--ez-muted);
  font-size: 12px;
}
.invoice-new-dialog input[type='radio'],
.invoice-new-dialog input[type='checkbox'] {
  width: 16px;
  min-height: 16px;
  height: 16px;
  margin: 2px 0 0;
  padding: 0;
  accent-color: var(--ez-action);
}
/* Projects as one bordered list, each with what it has waiting. */
.invoice-projects {
  display: grid;
  max-height: 232px;
  overflow-y: auto;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.invoice-projects > p {
  margin: 0;
  padding: 12px;
}
.invoice-new-dialog .invoice-projects label {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 12px;
  border-bottom: 1px solid var(--ez-border);
  color: var(--ez-ink);
  font-size: 14px;
  cursor: pointer;
}
.invoice-new-dialog .invoice-projects label:last-child {
  border-bottom: 0;
}
.invoice-new-dialog .invoice-projects label:hover {
  background: var(--ez-row-hover);
}
.invoice-new-dialog .invoice-projects input {
  margin: 0;
}
.invoice-projects small {
  color: var(--ez-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.invoice-new-period-choice {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
}
.invoice-new-dialog .invoice-new-period-choice label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  color: var(--ez-ink);
  font-size: 14px;
  cursor: pointer;
}
.invoice-new-dialog .invoice-new-period-choice input {
  margin: 0;
}
.invoice-new-journal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding-top: 4px;
}
.invoice-new-journal[hidden],
.invoice-new-journal select[hidden] {
  display: none;
}
.invoice-new-dialog .invoice-new-check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  color: var(--ez-ink);
  font-size: 14px;
  cursor: pointer;
}
.invoice-new-dialog .invoice-new-check input {
  margin: 0;
}
.invoice-new-dialog .invoice-new-journal select {
  width: auto;
  min-height: 36px;
  padding: 4px 10px;
  font-size: 14px;
}
.invoice-new-dialog .invoice-new-formats {
  display: inline-flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 4px 14px;
}
.invoice-new-dialog .invoice-new-formats[hidden] {
  display: none;
}
.invoice-new-dialog .invoice-new-formats label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  color: var(--ez-ink);
  font-size: 14px;
  cursor: pointer;
}
.invoice-new-dialog .invoice-new-formats input {
  margin: 0;
}
.invoice-hours-menu-label {
  margin: 0;
  padding: 8px 12px 2px;
  color: var(--ez-muted);
  font: var(--ez-label-weight) var(--ez-label-size) / 1.2 var(--ez-font-body), sans-serif;
  letter-spacing: var(--ez-label-tracking);
  text-transform: uppercase;
}
/* The period control's arrows and fields keep a phone-sized tap target here,
   as they had on the generation page this dialog replaced. */
.invoice-new-dialog .invoice-period button,
.invoice-new-dialog .invoice-period select,
.invoice-new-dialog .invoice-period input {
  min-height: 44px;
}
.invoice-new-dialog .invoice-period .period-step {
  min-width: 44px;
}
/* One control, mounted by the browser; it lays out its own row. */
.invoice-period {
  display: grid;
  min-width: 0;
}
.invoice-new-detail {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
/* The band's card has the longest words, so it takes the row below the two. */
.invoice-new-dialog .invoice-new-kind-wide {
  grid-column: 1 / -1;
}
.invoice-new-tracked-only,
.invoice-new-band {
  display: grid;
  gap: 20px;
}
.invoice-new-tracked-only[hidden],
.invoice-new-band[hidden],
.invoice-new-ceiling[hidden],
.invoice-fee-remove[hidden] {
  display: none;
}
/* The fee, a line at a time: what it is, then how many at what, and its amount. */
.invoice-fee-lines {
  display: grid;
  gap: 16px;
}
.invoice-new-dialog .invoice-fee-line {
  display: grid;
  /* The amount column holds a typical fee's width, so lines stay aligned. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(7.5rem, auto) auto;
  align-items: end;
  gap: 8px 10px;
}
.invoice-new-dialog .invoice-fee-line + .invoice-fee-line {
  padding-top: 16px;
  border-top: 1px solid var(--ez-border);
}
.invoice-new-dialog .invoice-fee-description {
  grid-column: 1 / -1;
}
.invoice-new-dialog .invoice-fee-amount {
  display: grid;
  gap: 6px;
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.invoice-new-dialog .invoice-fee-amount strong,
.invoice-fee-foot strong {
  color: var(--ez-ink);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.invoice-new-dialog .invoice-fee-amount strong {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: 44px;
}
.invoice-fee-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
}
.invoice-fee-foot p {
  margin: 0;
  color: var(--ez-muted);
  font-size: 14px;
}
.invoice-new-dialog .invoice-fee-line button,
.invoice-new-dialog .invoice-fee-foot button {
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.invoice-new-ceiling {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.invoice-new-ceiling .field-hint,
.invoice-new-ceiling .invoice-new-overage {
  grid-column: 1 / -1;
}
.invoice-band-preview {
  margin: 0;
  color: var(--ez-ink);
  font-size: 14px;
}
.invoice-band-preview:empty {
  display: none;
}
@media (max-width: 560px) {
  .invoice-new-kind,
  .invoice-new-detail,
  .invoice-new-ceiling {
    grid-template-columns: minmax(0, 1fr);
  }
  .invoice-new-dialog .invoice-fee-line {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

.client-workspace {
  width: 100%;
}
.client-intro {
  max-width: 64ch;
  color: var(--ez-muted);
}
.client-page-status {
  min-height: 20px;
}
.client-list-toolbar,
.client-header-actions,
.client-confirm-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.client-list-toolbar {
  margin: 24px 0 12px;
}

/*
 * A flex item keeps `min-width: auto` by default, so it refuses to shrink below
 * its own content. The search field these toolbars gained therefore widened the
 * toolbar past the viewport instead of narrowing inside it, and the whole page
 * scrolled sideways on a phone -- which is what expectNoPageOverflow is for.
 *
 * It only failed on Linux CI. The webfont request is blocked there as it is
 * here, and the fallback face is wide enough to push the field past 390px where
 * the one on a Mac was not. The rule was always wrong; the metrics hid it.
 */
.client-list-toolbar > *,
.client-list-toolbar fieldset > *,
.project-list-toolbar > *,
.project-list-toolbar fieldset > *,
.team-list-toolbar > *,
.team-list-toolbar fieldset > *,
.invoice-list-toolbar > *,
.invoice-list-toolbar fieldset > *,
.task-admin-toolbar > *,
.task-admin-toolbar fieldset > * {
  min-width: 0;
}
.client-list-toolbar button,
.client-header-actions button,
.client-contacts header button,
.client-confirm-actions button,
.client-workspace [data-client-create],
[data-client-list-retry],
[data-client-detail-retry] {
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.client-list-toolbar button[aria-pressed='true'] {
  border-color: var(--ez-data);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
}
.client-tree-name {
  display: flex;
  align-items: center;
  gap: 12px;
}
.client-tree-name a {
  color: var(--ez-ink);
  font-weight: 700;
  text-underline-offset: 3px;
}
.client-contacts header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.client-status-pill,
.client-recipient-pill {
  flex: 0 0 auto;
  padding: 4px 8px;
  border-radius: 999px;
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
  font: 600 11px/1.2 var(--ez-font-body), sans-serif;
  text-transform: uppercase;
}
/*
 * Invoice routing is the whole reason the contact carries the field, so the
 * four states the API enumerates have to read apart at a glance. Colour alone
 * would not do it: a colourblind operator and a printed statement both see
 * only the shape, so each state differs in border weight and pattern as well
 * as hue. The recipient carries the heaviest border — it is the one that gets
 * the invoice; CC is a hairline outline, the copy that is visible; BCC is a
 * dashed outline, the copy that is not; and a contact with no invoice role
 * carries no chrome at all, so a column of contacts shows the routing without
 * being read word by word. The transparent border on the base rule keeps every
 * pill the same box whichever state it is in.
 */
.client-recipient-pill {
  border: 1px solid transparent;
}
/*
 * A saturated fill would have been the obvious way to mark the recipient, and
 * it is the one thing that cannot be used here. Browsers omit background-color
 * when "Background graphics" is off — the print default — so a filled pill with
 * --ez-action-fg ink printed as near-white text on white paper: the one contact
 * who actually receives the invoice was the faintest row in the column while
 * the contacts who receive nothing were the boldest. print-color-adjust would
 * force the fill back in Chromium, but it would still be leaning on colour, and
 * a greyscale printer or a colourblind operator would be no better off. So the
 * emphasis is carried in ink instead: a doubled border and the blue ink,
 * both of which are painted whatever the print settings say. The tint only
 * reinforces it on screen, and is weak enough (10% over the ground) that the
 * blue still clears 4.5:1 against it. Trading a pixel of padding for the
 * extra border width keeps the pill the same box as its siblings.
 */
.client-recipient-pill[data-recipient-status='recipient'] {
  padding: 3px 7px;
  border-width: 2px;
  border-color: var(--ez-action);
  background-color: color-mix(in srgb, var(--ez-data) 10%, var(--ez-ground));
  color: var(--ez-data);
}
.client-recipient-pill[data-recipient-status='cc'] {
  border-color: var(--ez-data);
  background-color: transparent;
  color: var(--ez-data);
}
.client-recipient-pill[data-recipient-status='bcc'] {
  border-style: dashed;
  border-color: var(--ez-data);
  background-color: transparent;
  color: var(--ez-data);
}
.client-recipient-pill[data-recipient-status='none'] {
  background-color: transparent;
  color: var(--ez-muted);
}
.client-tree-empty,
.client-related-empty {
  padding: 28px 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  color: var(--ez-muted);
  text-align: center;
}
.client-detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 0.55fr);
  gap: 18px;
}
.client-facts,
.client-projects,
.client-contacts {
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.client-facts {
  grid-row: span 2;
}
.client-facts h2,
.client-projects h2,
.client-contacts h2 {
  margin: 0;
  font-size: 18px;
}
.client-facts dl {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  margin: 22px 0 0;
}
.client-facts dl > div {
  display: grid;
  gap: 4px;
}
.client-facts dt {
  color: var(--ez-muted);
  font-size: 12px;
}
.client-facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}
.client-facts .client-address {
  grid-column: 1 / -1;
}
.client-facts .client-address dd {
  white-space: pre-wrap;
}
.client-projects header,
.client-contacts header {
  margin-bottom: 14px;
}
.client-form-pair,
.client-defaults {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
/* Full width under the facts and the two related lists: the three figures are
   per-currency tables, and a table squeezed into the 280px sidebar column would
   wrap its own amounts. */
.client-360 {
  grid-column: 1 / -1;
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.client-360 > header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}
.client-360 h2 {
  margin: 0;
  font-size: 18px;
}
.client-360 h3 {
  margin: 0 0 6px;
  font-size: 15px;
}
.client-360 .hint {
  font-size: 12px;
}
.client-360-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  margin-top: 16px;
}
.client-360-figures > section {
  display: grid;
  align-content: start;
  gap: 8px;
}

.project-workspace {
  width: 100%;
}
.project-intro,
.project-page-status,
.project-section-status {
  color: var(--ez-muted);
}
.project-page-status,
.project-section-status {
  min-height: 20px;
}
.project-list-toolbar,
.project-list-toolbar fieldset,
.project-header-actions,
.project-confirm-actions,
.project-task-card > div:last-child,
.project-attachments form {
  display: flex;
  align-items: center;
  gap: 8px;
}
.project-list-toolbar {
  justify-content: space-between;
  margin: 24px 0 12px;
}
.project-list-toolbar fieldset {
  margin: 0;
  padding: 0;
  border: 0;
}
.project-list-toolbar label {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ez-muted);
  font-size: 13px;
}
.project-list-toolbar button,
.project-list-toolbar select,
.project-header-actions button,
.project-tasks header button,
.project-task-card button,
.project-confirm-actions button,
.project-attachments button,
.project-attachments input,
.project-workspace [data-project-create],
[data-project-list-retry],
[data-project-detail-retry] {
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.project-list-toolbar button[aria-pressed='true'] {
  border-color: var(--ez-data);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
}
.project-task-card,
.project-tasks ul,
.project-attachments ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
.project-tasks ul,
.project-attachments ul {
  display: grid;
  gap: 10px;
}
.project-task-card,
.project-attachments li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.project-attachments a {
  color: var(--ez-ink);
  font-weight: 700;
  text-underline-offset: 3px;
}
.project-task-card p {
  margin: 5px 0 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.project-status-pill {
  padding: 4px 8px;
  border-radius: 999px;
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
  font: 600 11px/1.2 var(--ez-font-body), sans-serif;
  text-transform: uppercase;
}
.project-list-empty,
.project-related-empty {
  padding: 28px 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  color: var(--ez-muted);
  text-align: center;
}
.project-detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 0.65fr);
  gap: 18px;
}
.project-facts,
.project-tasks,
.project-attachments {
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.project-facts {
  grid-row: span 2;
}
.project-facts h2,
.project-tasks h2,
.project-attachments h2 {
  margin: 0;
  font-size: 18px;
}
.project-facts dl {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  margin: 22px 0 0;
}
.project-facts dl > div {
  display: grid;
  gap: 4px;
}
.project-facts dt {
  color: var(--ez-muted);
  font-size: 12px;
}
.project-facts dd {
  margin: 0;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.project-tasks header,
.project-attachments header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.project-task-card {
  background: var(--ez-ground);
}
.project-task-card > div:last-child {
  flex: 0 0 auto;
}
.project-attachments form {
  align-items: flex-end;
  margin-bottom: 14px;
}
.project-attachments form label {
  display: grid;
  flex: 1;
  gap: 6px;
  color: var(--ez-muted);
  font-size: 13px;
}
.project-attachments form .hint {
  flex-basis: 100%;
}
.project-attachments li span {
  color: var(--ez-muted);
  font-size: 12px;
}
.project-form-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.project-check {
  grid-template-columns: auto 1fr;
  align-items: center;
  justify-content: start;
  min-height: 44px;
}
.project-check input {
  width: auto;
  min-height: auto;
}
.project-assignment-task-name {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
}
.expense-workspace {
  width: 100%;
}
.expense-intro,
.expense-page-status,
.expense-lock-message {
  color: var(--ez-muted);
}
.expense-create-panel,
.expense-filter-form,
.expense-edit-panel,
.expense-attachments,
.expense-module-unavailable {
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.expense-create-panel {
  margin: 24px 0 16px;
}
.expense-module-unavailable {
  margin: 24px 0;
  border-inline-start: 3px solid var(--ez-amber);
}
.expense-module-unavailable h2,
.expense-module-unavailable p {
  margin: 0;
}
.expense-module-unavailable p {
  margin-top: 8px;
  color: var(--ez-muted);
}
.expense-create-panel h2,
.expense-edit-panel h2,
.expense-attachments h2 {
  margin: 0;
  font-size: 18px;
}
.expense-create-panel > header,
.expense-edit-panel > header,
.expense-attachments > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
/*
 * The activity filters shipped without a rule of their own, so three labelled
 * controls laid out as raw inline text: "To" on the end of one line and the
 * date field it labels on the next. Same shape the other filter surfaces use,
 * and `auto-fit` means no breakpoint -- three across where there is room, one
 * per row on a phone, and the tracks never crush below a usable field.
 */
.activity-log-toolbar {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}
.activity-log-toolbar label {
  display: grid;
  gap: 6px;
  min-width: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.activity-log-toolbar input,
.activity-log-toolbar select {
  min-height: 44px;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}

.expense-form,
.expense-filter-form {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}
/*
 * Four across needs 802px; the widest field is Amount, which wants 165px and
 * was being handed 145. Below that the row pairs up rather than clipping the
 * one field on the form where a cut-off value would be read as a real number.
 */
@media (min-width: 721px) and (max-width: 801px) {
  .expense-form,
  .expense-filter-form {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.expense-form label,
.expense-filter-form label,
.expense-attachments form label {
  display: grid;
  gap: 6px;
  color: var(--ez-muted);
  font-size: 13px;
}
.expense-form input,
.expense-form select,
.expense-form textarea,
.expense-filter-form input,
.expense-filter-form select,
.expense-attachments input,
.expense-form button,
.expense-filter-form button,
.expense-attachments button,
[data-expense-load-more],
[data-expense-list-retry],
[data-expense-detail-retry] {
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  /*
   * A control in a grid cell starts at `min-width: auto`, which for a select is
   * its longest option -- "All reimbursement states" is wider than the column it
   * sits in, so the box pushed out of its cell rather than sitting inside it.
   * Zero lets the cell decide, which is what the four-up grid is for.
   */
  min-width: 0;
}
.expense-form textarea {
  resize: vertical;
}
.expense-form .expense-wide-field {
  grid-column: 1 / -1;
}
.expense-form .expense-check {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
}
.expense-form .expense-check input {
  width: auto;
  min-height: auto;
}
.expense-filter-form {
  margin-bottom: 14px;
}
/*
 * Two of the card's four columns, which is exactly what the From and To fields
 * it replaced already had, so the filter card keeps its column count and the
 * filter row stays one band. §6 allows three between the tab strip and the
 * first data row and this screen already spends them on the heading, the intro
 * and the new-expense panel.
 */
.expense-filter-period {
  grid-column: span 2;
}
/* The card pads its own buttons, and it is one class more specific than the
   control's own rule: a chevron centred in a 44px box with 12px of side padding
   is a clipped chevron. */
.expense-filter-form .period-step {
  padding: 0;
}
.expense-filter-actions {
  display: flex;
  align-items: end;
  gap: 8px;
}
.expense-attachments ul {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.expense-attachments li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.expense-attachments a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ez-ink);
  font-weight: 700;
  text-underline-offset: 3px;
}
.expense-status-pill {
  padding: 4px 8px;
  border-radius: 999px;
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
  font: 600 11px/1.2 var(--ez-font-body), sans-serif;
  text-transform: uppercase;
}
.expense-detail {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(300px, 0.7fr);
  gap: 18px;
}
.expense-state-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin: 22px 0 0;
}
.expense-state-facts > div {
  display: grid;
  gap: 4px;
}
.expense-state-facts dt {
  color: var(--ez-muted);
  font-size: 12px;
}
.expense-state-facts dd {
  margin: 0;
}
.expense-lock-message {
  padding: 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-status-bg);
}
.expense-attachments form {
  display: grid;
  gap: 10px;
}
.expense-attachments li span {
  color: var(--ez-muted);
  font-size: 12px;
}
.expense-attachments .expense-attachment-empty {
  color: var(--ez-muted);
}
.client-defaults {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 0;
  padding: 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.client-defaults legend {
  padding-inline: 6px;
  color: var(--ez-muted);
  font-size: 13px;
}
.client-confirm-actions {
  justify-content: flex-end;
}
/* A filled button, so its label is body text over the fill and owes 4.5:1 — not
   the 3:1 an indicator owes. The fill is the red every other destructive verb
   is written in, and the gate holds its label to the text ratio. The
   destructive weight is carried by the fill being the loudest thing on the
   row. */
.danger-action {
  border-color: var(--ez-red) !important;
  background: var(--ez-red) !important;
  color: var(--ez-ground) !important;
}

.timesheet-approvals {
  width: 100%;
}
.approval-intro {
  max-width: 64ch;
  color: var(--ez-muted);
}
.approval-queue {
  display: grid;
  gap: 12px;
  margin-top: 24px;
}
.approval-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 18px 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.approval-card h2,
.approval-card p,
.approval-empty {
  margin: 0;
}
.approval-card h2 {
  font-size: 18px;
}
.approval-card p {
  margin-top: 4px;
  color: var(--ez-muted);
}
.approval-totals {
  font-family: var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
}
.approval-entry-list {
  display: grid;
  gap: 8px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
}
.approval-entry-list li {
  padding: 10px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
}
.approval-entry-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.approval-entry-header span {
  flex: 0 0 auto;
  color: var(--ez-muted);
  font-family: var(--ez-font-body), sans-serif;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.approval-card .approval-entry-note {
  margin-top: 8px;
  color: var(--ez-ink);
  white-space: pre-wrap;
}
.approval-entry-note[data-empty='true'] {
  color: var(--ez-muted);
  font-style: italic;
}
.approval-actions {
  align-items: center;
  display: flex;
  gap: 8px;
}
.approval-select {
  height: 20px;
  width: 20px;
}
.approval-bulk {
  align-items: center;
  display: flex;
  gap: 12px;
  justify-content: flex-end;
}
.approval-bulk-count {
  color: var(--ez-muted);
}
.approval-bulk button {
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--ez-action-edge);
  border-radius: var(--ez-radius);
  background: var(--ez-action);
  color: var(--ez-action-fg);
}
.approval-bulk button:disabled {
  border-color: var(--ez-border);
  background: var(--ez-ground);
  color: var(--ez-muted);
}
.approval-actions button {
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.approval-actions .primary-action {
  border-color: var(--ez-action-edge);
  background: var(--ez-action);
  color: var(--ez-action-fg);
}
.approval-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: end;
  margin-top: 16px;
}
.approval-filters label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--ez-muted);
}
.approval-filters select {
  min-height: 36px;
  padding: 0 8px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-ink);
}
.approval-filters .filter-apply {
  min-height: 36px;
  padding: 0 16px;
  border: 1px solid var(--ez-action-edge);
  border-radius: var(--ez-radius);
  background: var(--ez-action);
  color: var(--ez-action-fg);
  cursor: pointer;
}
.load-more {
  display: block;
  width: 100%;
  margin-top: 12px;
  padding: 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-ink);
  cursor: pointer;
  text-align: center;
}
.load-more[hidden] {
  display: none;
}
.approval-empty {
  padding: 28px 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  color: var(--ez-muted);
  text-align: center;
}
.timesheet-lock-policy {
  display: grid;
  gap: 18px;
  margin-top: 32px;
  padding-top: 28px;
  border-top: 1px solid var(--ez-border);
}
.timesheet-lock-policy[hidden] {
  display: none;
}
.timesheet-lock-policy > header {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 24px;
}
.timesheet-lock-policy > header h2,
.manual-lock-form h3 {
  margin: 0;
  font-family: var(--ez-font-display), sans-serif;
}
.timesheet-lock-policy > header > p {
  max-width: 42ch;
  margin: 0;
  color: var(--ez-muted);
}
.org-timezone-form,
.lock-policy-form,
.manual-lock-form {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: end;
  gap: 12px;
  padding: 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.org-timezone-form label,
.lock-policy-form label,
.manual-lock-form label {
  display: grid;
  gap: 6px;
}
.org-timezone-form select,
.org-timezone-form button,
.lock-policy-form input:not([type='checkbox']),
.lock-policy-form select,
.manual-lock-form input,
.manual-lock-form textarea,
.lock-policy-form button,
.manual-lock-form button {
  min-height: 44px;
}
.lock-policy-form input:not([type='checkbox']),
.lock-policy-form select,
.manual-lock-form input,
.manual-lock-form textarea {
  width: 100%;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.lock-policy-toggle {
  grid-column: 1 / -1;
  grid-template-columns: auto 1fr;
  align-items: center;
  min-height: 44px;
}
.lock-policy-toggle input {
  width: 20px;
  height: 20px;
}
.manual-lock-form > div {
  grid-column: 1 / -1;
}
.manual-lock-form label:nth-of-type(2) {
  grid-column: span 2;
}
.timesheet-lock-list {
  display: grid;
  gap: 10px;
}
.timesheet-lock-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(220px, 0.7fr) auto;
  align-items: end;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.timesheet-lock-card p {
  margin: 3px 0 0;
  color: var(--ez-muted);
}
.timesheet-lock-card label {
  display: grid;
  gap: 4px;
}
.timesheet-lock-card input,
.timesheet-lock-card button {
  min-height: 44px;
}
.timesheet-status-actions {
  display: flex;
  gap: 8px;
}

@media (max-width: 720px) {
  .invoice-facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .invoice-document-heading {
    flex-direction: column-reverse;
  }
  .invoice-document-actions {
    width: 100%;
    justify-content: space-between;
    flex-wrap: wrap;
  }
  /* The actions row wraps here, so a menu anchored to the toggle can end up
     anchored to the left edge of a line and run off the screen. On this
     breakpoint it stops floating and takes a row of its own instead. */
  .invoice-overflow {
    position: static;
    width: 100%;
  }
  .invoice-overflow-menu {
    position: static;
    min-width: 0;
    margin-top: 8px;
  }
  /* Anchored to its toggle, an open panel could run off either edge of a
     phone's screen. Here it takes a line of its own under the toggle instead,
     and the row wraps only while it is open, so a closed one lays out as
     before. */
  .report-detailed-actions:has(.report-export-menu:not([hidden])) {
    flex-wrap: wrap;
  }
  .report-export:has(.report-export-menu:not([hidden])) {
    width: 100%;
  }
  .report-export-menu {
    position: static;
    min-width: 0;
    margin-top: 8px;
  }
  .invoice-composer-layout {
    grid-template-columns: 1fr;
  }
  .invoice-payment-heading,
  .invoice-line-heading,
  .invoice-payment-dialog-actions,
  .retainer-dialog-actions {
    align-items: stretch;
    flex-direction: column;
  }
  .invoice-payment-heading button,
  .invoice-line-heading .invoice-line-add,
  .invoice-payment-dialog-actions button,
  .retainer-dialog-actions button {
    width: 100%;
  }
  .invoice-payment-amount-fields {
    grid-template-columns: 1fr;
  }
  .invoice-line-table {
    min-width: 0;
  }
  .invoice-line-table thead {
    display: none;
  }
  .invoice-line-table tbody {
    display: grid;
    gap: 12px;
  }
  .invoice-line-table tbody tr {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border: 1px solid var(--ez-border);
    border-radius: var(--ez-radius);
  }
  .invoice-line-table tbody th,
  .invoice-line-table tbody td {
    border: 0;
    text-align: left;
  }
  .invoice-line-table tbody th,
  .invoice-line-actions,
  .invoice-line-empty {
    grid-column: 1 / -1;
  }
  .invoice-line-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 3px;
    color: var(--ez-muted);
    font-size: 11px;
  }
  .invoice-line-actions {
    padding-top: 0 !important;
  }
  .invoice-line-actions a {
    display: flex;
    min-height: 44px;
  }
  .invoice-attachment-form {
    flex-wrap: wrap;
  }
  .invoice-attachment-form input,
  .invoice-attachment-form button {
    min-width: 0;
    flex: 1 1 100%;
  }
  .approval-card {
    align-items: stretch;
    flex-direction: column;
  }
  .approval-actions button {
    flex: 1;
  }
  .approval-entry-header {
    align-items: flex-start;
    flex-direction: column;
    gap: 4px;
  }
  .timesheet-lock-policy > header,
  .timesheet-status-actions {
    align-items: stretch;
    flex-direction: column;
  }
  .lock-policy-form,
  .manual-lock-form,
  .timesheet-lock-card {
    grid-template-columns: 1fr;
  }
  .manual-lock-form label:nth-of-type(2) {
    grid-column: auto;
  }
}
html {
  background: var(--ez-ground);
  color: var(--ez-ink);
  font-family: var(--ez-font-body), sans-serif;
}
body {
  margin: 0;
  min-height: calc(100vh / var(--ez-zoom, 1));
  background: var(--ez-ground);
}
/* While the sign-in page is what is showing, the document wears its right
   panel's ground, so whatever lies beyond the page -- a window taller than
   the layout, a scroll bounced past its end -- reads as more of that panel
   rather than as a white strip. */
[data-auth-state='unknown'],
[data-auth-state='signed-out'],
[data-auth-state='unknown'] body,
[data-auth-state='signed-out'] body {
  background: var(--ez-surface);
}
/* The 14px base is the design's desk density, so it applies where there is a
   pointer. A phone keeps the browser's 16px: it zooms the page into any field
   set smaller than that the moment the field takes focus. */
@media (pointer: fine) {
  body {
    font-size: var(--ez-text-size);
  }
}
/* `strong` is `bolder` by default, and bolder than bold is the Black cut the
   page titles load. Emphasis stops at bold, the weight it always rendered at. */
b,
strong {
  font-weight: 700;
}
button,
input,
select,
textarea {
  font: inherit;
}
button {
  cursor: pointer;
}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 3px solid var(--ez-data);
  outline-offset: 2px;
}
button,
a {
  -webkit-tap-highlight-color: transparent;
}
a {
  color: var(--ez-data);
}
.topbar {
  min-height: 56px;
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 0 24px;
  background: var(--ez-band);
  color: var(--ez-band-ink);
}
.brand {
  display: grid;
  place-items: center;
  color: var(--ez-band-ink);
  /* 900, the heaviest weight the font request carries: an 800 here was drawn
     at 900 anyway, by a browser told a weight it had not been given. */
  font: 900 19px/1 var(--ez-font-display);
  letter-spacing: -0.03em;
  text-decoration: none;
  margin-right: 12px;
}
/* An uploaded mark is bounded by the bar it sits in rather than by its own
   pixel size: an operator uploads whatever their designer exported, and a
   2000px wordmark must not decide how tall the topbar is. Width follows so a
   wide lockup and a square glyph both keep their proportions. */
.brand-lockup {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.brand-logo {
  width: 26px;
  height: 26px;
  flex: none;
  fill: currentColor;
}
.brand-mark {
  max-height: 32px;
  width: auto;
  object-fit: contain;
}
.auth-wordmark .brand-mark {
  max-height: clamp(28px, 3vw, 40px);
}
.document-shell .brand-mark {
  max-height: 24px;
}
.primary-nav {
  display: flex;
  align-items: stretch;
}
.primary-nav a {
  display: grid;
  place-items: center;
  padding: 0 14px;
  color: var(--ez-band-ink);
  text-decoration: none;
  font-weight: 500;
}
.primary-nav a[aria-current="page"] {
  background: var(--ez-surface);
  color: var(--ez-ink);
}
.timer-chip,
.money-toggle,
.command-trigger,
.menu-trigger {
  align-self: center;
  min-height: 36px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-ink);
}
.timer-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  padding: 0 12px;
  max-width: 320px;
  /* Its automatic minimum is its whole label -- overflow on the label lets the
     label ellipsize but does not lower the button's own min-content -- so it
     never gave up a pixel and the bar overflowed past it instead. The floor
     keeps the clock, the dot, a sliver of label and the elapsed time. */
  min-width: 150px;
}
.timer-chip [data-timer-label] {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.timer-chip [data-timer-elapsed] {
  font-family: var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
}
.live-dot {
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--ez-muted);
}
.timer-chip[data-state="running"] {
  border-color: var(--ez-live);
}
.timer-chip[data-state="running"] .live-dot {
  background: var(--ez-live);
}
.money-toggle,
.command-trigger,
.menu-trigger {
  margin-left: 0;
  padding: 0 12px;
  font-family: var(--ez-font-body), sans-serif;
}
/*
 * Search looks like the field it opens (the 2026-10 reskin): the magnifier,
 * "Search…" in the secondary ink, and the key that opens it as a chip at the
 * far end. It is still a button -- the field it resembles is the one in the
 * command bar -- so its name is the button's own, not the placeholder.
 */
.command-trigger {
  justify-content: flex-start;
  min-width: 180px;
  padding: 0 6px 0 10px;
  background: var(--ez-ground);
  color: var(--ez-muted);
}
.command-trigger-label {
  margin-right: auto;
  white-space: nowrap;
}
/* One chip style for every key the shell draws: the search trigger's, the
   command bar's footer, and the one on its highlighted row. Secondary ink on
   its own ground, so it reads the same on any row it sits on. */
.command-shortcut,
.command-keys kbd,
.command-option[aria-selected='true']::after {
  flex: none;
  min-width: 26px;
  padding: 1px 6px;
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) - 2px);
  background: var(--ez-ground);
  color: var(--ez-muted);
  font: 600 11px/1.6 var(--ez-font-body), sans-serif;
  text-align: center;
  white-space: nowrap;
}
/*
 * Kept at every width, unlike the command trigger the narrow bands shed. It is
 * one glyph wide, and on a touch screen it is the only way to reach the setting
 * at all -- there is no keyboard there to take the shortcut.
 */
.money-toggle {
  min-width: 36px;
}
/*
 * Pressed means the amounts are off, which is the state worth marking: the
 * whole point of the control is to be able to tell at a glance whether the
 * figures on screen are the real ones.
 */
.money-toggle[aria-pressed='true'] {
  /* Pressed is a selected state, so it wears the selected colour: a blue
     border, and a blue label the gate holds to 4.5:1 on the button's own
     surface. */
  border-color: var(--ez-data);
  color: var(--ez-data);
}
/*
 * Masking, and why it is masking rather than hiding.
 *
 * `visibility: hidden` keeps the element's box, so the row it is in keeps
 * exactly the width it had -- toggling redraws nothing else, and a table does
 * not jump under the reader's eyes, which would be its own kind of attention in
 * the moment someone is standing behind them. It also takes the figure out of
 * the accessibility tree and out of a selection, so the amount is not read
 * aloud or copied while it is masked.
 *
 * The dots are drawn in a pseudo-element pinned over that same box, so their
 * count is fixed at four whatever the amount was. The box's width still tracks
 * the amount's -- someone watching a toggle could infer a figure's digit count
 * from it. That is a real leak and an acceptable one: this is a courtesy
 * against a glance, not a security control, and `canViewMoneyField` is what
 * decides whether the figure was ever sent.
 */
[data-money='hidden'] .money {
  position: relative;
  visibility: hidden;
}
[data-money='hidden'] .money::after {
  content: '••••';
  position: absolute;
  inset: 0;
  visibility: visible;
  color: var(--ez-muted);
  letter-spacing: 0.1em;
  white-space: nowrap;
}
/*
 * Visible at every width since issue 461. Below 1140px it is the whole nav;
 * at desktop it is where the sections folded out of the six live -- Home,
 * Tasks and Team. Leaving it hidden up there would have made "folded into a
 * menu" mean "removed", because the strip is the only other affordance.
 */
.menu-trigger {
  display: block;
}
.tabstrip {
  height: 44px;
  display: flex;
  align-items: stretch;
  gap: 24px;
  padding: 0 32px;
  border-bottom: 1px solid var(--ez-border);
  overflow-x: auto;
}
.tabstrip a {
  display: grid;
  place-items: center;
  color: var(--ez-muted);
  text-decoration: none;
  white-space: nowrap;
}
.tabstrip a:hover,
.tabstrip a:focus-visible {
  color: var(--ez-ink);
}
.tabstrip a[aria-current="page"] {
  color: var(--ez-ink);
  box-shadow: inset 0 -3px var(--ez-data);
}
/*
 * Week and Day are view modes of one screen, not sections of the app. They wore
 * the level-2 underline only because they were the one strip in the shell; now
 * that Invoices and Reports have real level-2 tabs, that underline has to mean
 * exactly one thing, so the view modes take §6's segmented control instead --
 * a joined group, the selected tint and a blue border on the mode you are in. The
 * strip stays a strip: same nav, same links, same aria-current, different paint.
 */
.tabstrip.view-switch {
  align-items: center;
  gap: 0;
}
.tabstrip.view-switch a {
  height: 28px;
  padding: 0 16px;
  border: 1px solid var(--ez-border);
}
/* The segments share their edge so the group reads as one control rather than
   as two buttons that happen to touch. */
.tabstrip.view-switch a + a {
  margin-inline-start: -1px;
}
.tabstrip.view-switch a:first-child {
  border-start-start-radius: var(--ez-radius);
  border-end-start-radius: var(--ez-radius);
}
.tabstrip.view-switch a:last-child {
  border-start-end-radius: var(--ez-radius);
  border-end-end-radius: var(--ez-radius);
}
.tabstrip.view-switch a:hover:not([aria-current="page"]),
.tabstrip.view-switch a:focus-visible:not([aria-current="page"]) {
  background: var(--ez-surface-2);
  color: var(--ez-ink);
}
.tabstrip.view-switch a[aria-current="page"] {
  /* Above its neighbour, so the shared edge is the blue one. */
  position: relative;
  background: var(--ez-orange-tint);
  border-color: var(--ez-data);
  box-shadow: none;
}
.app-content {
  padding: 28px clamp(24px, 3vw, 56px) 48px;
}
.context-row {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 20px;
}
.context-row h1,
.week-surface h2,
dialog h2,
.empty-state h2 {
  margin: 0;
  font-family: var(--ez-font-display), sans-serif;
}
.context-row h1 {
  font-size: var(--ez-title-size);
  font-weight: var(--ez-title-weight);
  letter-spacing: var(--ez-title-tracking);
  line-height: 1.2;
}
/* Used by render.ts for table-header and control labels that must reach a
   screen reader without occupying layout. It had no definition, so those
   labels were rendering visibly. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.eyebrow {
  margin: 0 0 4px;
  color: var(--ez-muted);
  font:
    var(--ez-label-weight) var(--ez-label-size) / 1.4 var(--ez-font-body),
    sans-serif;
  letter-spacing: var(--ez-label-tracking);
  text-transform: uppercase;
}
.primary-action {
  min-height: 36px;
  padding: 0 16px;
  border: 1px solid var(--ez-action-edge);
  border-radius: var(--ez-radius);
  background: var(--ez-action);
  box-shadow: var(--ez-shadow-primary);
  color: var(--ez-action-fg);
  font-weight: 600;
}
.authenticated-shell[hidden],
.auth-gateway[hidden],
.session-check-overlay[hidden] {
  display: none;
}
.authenticated-shell {
  min-height: calc(100vh / var(--ez-zoom, 1));
}
.session-check-overlay {
  position: fixed;
  z-index: 1000;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 24px;
  background: color-mix(in srgb, var(--ez-shadow) 24%, transparent);
  backdrop-filter: blur(2px);
}
.session-check-card {
  min-width: min(320px, 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 18px 22px;
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) * 2);
  background: var(--ez-ground);
  box-shadow: 0 20px 56px color-mix(in srgb, var(--ez-shadow) 24%, transparent);
  color: var(--ez-ink);
}
.session-check-card p {
  margin: 0;
  font: 500 14px/1.4 var(--ez-font-body), sans-serif;
}
.session-check-spinner {
  width: 24px;
  aspect-ratio: 1;
  flex: 0 0 auto;
  border: 3px solid var(--ez-border);
  border-top-color: var(--ez-action);
  border-radius: 50%;
  animation: auth-spin 900ms linear infinite;
}
.auth-gateway {
  min-height: calc(100vh / var(--ez-zoom, 1));
  min-height: calc(100dvh / var(--ez-zoom, 1));
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(380px, 0.85fr);
  overflow: hidden;
  background: var(--ez-surface);
}
.auth-splash {
  min-width: 0;
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
  padding: clamp(32px, 5vw, 72px);
  background: var(--ez-band);
  color: var(--ez-band-ink);
}
.auth-splash::before {
  content: '';
  position: absolute;
  z-index: -2;
  inset: 0;
  opacity: 0.16;
  background-image:
    linear-gradient(to right, var(--ez-band-ink) 1px, transparent 1px),
    linear-gradient(to bottom, var(--ez-band-ink) 1px, transparent 1px);
  background-size: 72px 72px;
  mask-image: linear-gradient(145deg, black, transparent 78%);
}
.auth-splash::after {
  content: '';
  position: absolute;
  z-index: -1;
  width: clamp(180px, 32vw, 480px);
  aspect-ratio: 1;
  right: clamp(-160px, -10vw, -48px);
  bottom: clamp(-190px, -14vw, -80px);
  border: clamp(36px, 5vw, 72px) solid var(--ez-action);
  border-radius: 50%;
  opacity: 0.9;
}
.auth-wordmark {
  width: fit-content;
  position: relative;
  color: var(--ez-band-ink);
  font:
    600 clamp(22px, 2vw, 28px) / 1 var(--ez-font-mono),
    monospace;
  letter-spacing: -0.04em;
  text-decoration: none;
}
.auth-splash-copy {
  width: min(620px, 90%);
  position: relative;
  margin-block: clamp(64px, 15vh, 180px);
}
.auth-splash-copy .eyebrow {
  color: var(--ez-blue-light);
}
.auth-splash-copy h1 {
  max-width: 9ch;
  margin: 12px 0 20px;
  color: var(--ez-band-ink);
  font: 600 clamp(48px, 7vw, 96px) / 0.94 var(--ez-font-display), sans-serif;
  letter-spacing: -0.055em;
}
.auth-splash-copy > p:last-child {
  max-width: 48ch;
  margin: 0;
  color: color-mix(in srgb, var(--ez-band-ink) 72%, transparent);
  font-size: clamp(16px, 1.4vw, 20px);
  line-height: 1.55;
}
.auth-splash-foot {
  position: relative;
  margin: 0;
  color: color-mix(in srgb, var(--ez-band-ink) 58%, transparent);
  font: 12px/1.4 var(--ez-font-mono), monospace;
}
.auth-entry {
  min-width: 0;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 24px;
  padding: clamp(24px, 6vw, 88px);
  background: var(--ez-surface);
}
.auth-card {
  width: min(100%, 440px);
  min-height: 390px;
  display: grid;
  align-items: center;
  padding: clamp(24px, 4vw, 48px);
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) * 2);
  background: var(--ez-ground);
  box-shadow: 0 24px 64px color-mix(in srgb, var(--ez-shadow) 10%, transparent);
}
.auth-checking {
  display: grid;
  justify-items: center;
  gap: 16px;
  color: var(--ez-muted);
  text-align: center;
}
.auth-checking[hidden] {
  display: none;
}
.auth-checking span {
  width: 44px;
  aspect-ratio: 1;
  border: 3px solid var(--ez-border);
  border-top-color: var(--ez-action);
  border-radius: 50%;
  animation: auth-spin 900ms linear infinite;
}
.auth-checking p,
.auth-build-stamp {
  margin: 0;
  font: 12px/1.4 var(--ez-font-mono), monospace;
}
.auth-build-stamp {
  color: var(--ez-muted);
}
@keyframes auth-spin {
  to {
    transform: rotate(360deg);
  }
}
.sign-in-form {
  display: grid;
  gap: 18px;
}
.sign-in-form[hidden] {
  display: none;
}
.demo-credentials {
  display: grid;
  gap: 10px;
  padding: 16px;
  border: 1px dashed var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.demo-credentials-note {
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
  line-height: 1.5;
}
.demo-credentials ul {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.demo-credentials li {
  display: grid;
  gap: 6px;
  justify-items: start;
}
.demo-account-label {
  margin: 0;
  font-weight: 600;
}
.demo-account-describes {
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
  line-height: 1.4;
}
.demo-account-secret {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
}
.demo-account-secret code {
  padding: 2px 6px;
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) / 2);
  background: var(--ez-ground);
  font: 12px/1.6 var(--ez-font-mono), monospace;
  overflow-wrap: anywhere;
}
.demo-account-fill {
  padding: 6px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: inherit;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.demo-account-fill:hover {
  border-color: var(--ez-data);
  color: var(--ez-data);
}
.auth-heading h2 {
  margin: 0 0 8px;
  font: 600 clamp(28px, 3vw, 36px) / 1.1 var(--ez-font-display), sans-serif;
  letter-spacing: -0.03em;
}
.auth-heading > p:last-child {
  margin: 0;
  color: var(--ez-muted);
  line-height: 1.5;
}
.oidc-entry {
  display: grid;
  gap: 14px;
}
.oidc-sign-in {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  border: 1px solid var(--ez-muted);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font-weight: 600;
  text-decoration: none;
}
/* Provider colors stay fixed across instance themes (Google neutral treatment). */
.oidc-sign-in[data-oidc-provider="google"] {
  background: var(--ez-google-sign-in-background);
  color: var(--ez-google-sign-in-foreground);
  border-color: transparent;
  padding: 0 12px 0 2px;
  font-size: 14px;
  font-weight: 500;
  line-height: 20px;
}
.oidc-sign-in[data-oidc-provider="google"] img {
  flex: 0 0 40px;
  object-fit: contain;
}
.auth-divider,
.auth-build-stamp {
  color: var(--ez-muted);
  font-size: 13px;
}
.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  text-align: center;
}
.auth-divider::before,
.auth-divider::after {
  content: '';
  height: 1px;
  flex: 1;
  background: var(--ez-border);
}
/*
 * One vertical rhythm for the form: 16px between field groups, 8px from a
 * label to its field, 12px from the button to the line that reports on it.
 * The fields sat in a wrapper with no layout of its own, so they stacked flush:
 * the focused Email field's ring lay across the Password label, and "Your
 * session ended" sat against the Sign in button.
 */
.password-entry {
  display: grid;
  gap: 16px;
}
.password-entry > .auth-result {
  margin-top: -4px;
}
.sign-in-form label {
  display: grid;
  gap: 8px;
  color: var(--ez-muted);
  font-size: 13px;
}
.sign-in-form input {
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-muted);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.sign-in-form .primary-action {
  min-height: 44px;
}
.sign-in-form .primary-action {
  width: 100%;
}
/* Leaving a half-finished second factor has to be as reachable as finishing
   it: the alternative is a person stuck on a code they cannot produce. */
.sign-in-restart {
  border: 0;
  padding: 0;
  background: none;
  color: var(--ez-muted);
  font: inherit;
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
}
.auth-result {
  min-height: 20px;
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
/* The account control sits in the topbar's trailing corner, where an account
   control is looked for, and is a light pill like its siblings — the bar is
   --ez-band, so --ez-muted would be unreadable directly against it. */
.account {
  align-self: center;
  /* The bar's give is the timer chip's label, which ellipsizes by design; the
     account is shown only in the bands with room for it, whole. */
  flex-shrink: 0;
}
.account[data-state='loading'] {
  display: none;
}
.account-identity {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 36px;
  padding: 0 6px 0 8px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-ink);
}
.account-identity[hidden] {
  display: none;
}
.identity-avatar {
  width: 30px;
  height: 30px;
  padding: 5px;
  border: 1px solid var(--ez-border);
  border-radius: 999px;
  color: var(--ez-muted);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
}
.identity-text {
  display: grid;
  gap: 1px;
  min-width: 0;
  line-height: 1.25;
}
/*
 * Both lines truncate. `identity-text` is `overflow: visible`, so a name with
 * nowhere to go does not stop at its box -- it paints straight over whatever
 * is beside it, which here is the Settings button. An ellipsis is the honest
 * failure: the profile name is decoration, and the two controls next to it are
 * the only route into a person's own account.
 */
.identity-name,
.identity-meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.identity-name {
  font-weight: 600;
}
.identity-meta {
  color: var(--ez-muted);
  font:
    500 12px/1.3 var(--ez-font-body),
    sans-serif;
}
/*
 * The person's name leads once the profile says it; until then, or on a server
 * that does not send one, the role takes the name's place rather than leaving a
 * blank line above it.
 */
.identity-role {
  text-transform: capitalize;
}
.identity-name[hidden] + .identity-role {
  color: var(--ez-ink);
  font: inherit;
  font-weight: 600;
}
/* Settings sits beside Sign out because that is where a person looks for their
   own account, and it is the only route into either half. */
.identity-settings {
  min-height: 28px;
  padding: 0 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font-size: 13px;
  line-height: 26px;
  text-decoration: none;
}

.identity-settings:hover {
  border-color: var(--ez-data);
  color: var(--ez-data);
}

.settings-tabs {
  margin-bottom: 16px;
}

/* The company page's section strip: the same segmented control the calendar
   uses for its view modes, because it is the same thing -- one screen, one
   part of it at a time -- and not a second level of navigation. */
.settings-sections {
  height: auto;
  padding: 0;
  margin-bottom: 8px;
  border-bottom: 0;
  flex-wrap: wrap;
  row-gap: 8px;
}
.settings-sections a {
  min-height: 32px;
}

.settings-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 8px 24px;
  margin: 0;
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}

.settings-facts dt {
  color: var(--ez-muted);
}

.settings-facts dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

/* One section per thing an operator configures. The rules between them are the
   only thing saying where one setting ends and the next begins. */
.settings-section {
  padding: 24px 0;
  border-top: 1px solid var(--ez-border);
}

.settings-section > header {
  margin-bottom: 16px;
}

.settings-section > header h2 {
  margin: 0;
}

.settings-section > header p {
  margin: 4px 0 0;
  max-width: 68ch;
  color: var(--ez-muted);
}

/* A section reads what another system holds, or failed to (issue 809): the
   control that reads it again sits by the heading, and the one that retries a
   failed read sits by the sentence saying it failed. */
.settings-section-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.settings-refresh,
.settings-retry {
  min-height: 28px;
  padding: 0 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-data);
}
.settings-refresh:disabled,
.settings-retry:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

/* The instance palette editor (issue 591). Grouped rather than one long list: the
   contrast rule means a dark palette needs most of the slots set, so the whole
   token set is offered, and the grouping is what keeps that from reading as a
   wall of pickers. */
.settings-theme {
  display: grid;
  gap: 16px;
  margin-bottom: 12px;
}
.theme-group {
  margin: 0;
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.theme-group legend {
  padding: 0 6px;
  font-weight: 600;
}
.theme-slot {
  display: grid;
  grid-template-columns: 1fr auto 10ch;
  align-items: center;
  gap: 8px 12px;
  padding: 6px 0;
}
.theme-slot + .theme-slot {
  border-top: 1px solid var(--ez-border);
}
.theme-slot label {
  grid-column: 1;
}
/* The hint belongs to the row above it, so it spans the whole row rather than
   squeezing into the label column and pushing the controls out of line. */
.theme-slot .field-hint {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 13px;
  color: var(--ez-muted);
}
.theme-slot input[type='color'] {
  width: 40px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: none;
}
/* Tabular figures and a monospace face, because these are read character by
   character when one is compared against a brand document. */
.theme-hex {
  width: 100%;
  padding: 4px 6px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font-family: var(--ez-font-mono);
  font-size: 13px;
}

.settings-brand-slots {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  margin-bottom: 12px;
}
.brand-slot {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.brand-slot h3 {
  margin: 0;
  font-size: 15px;
}
.brand-slot .hint {
  margin: 0;
  max-width: 44ch;
  color: var(--ez-muted);
}
/* The preview commits to the ground the slot's label names. A light-on-
   transparent mark shown on white is invisible, and an operator cannot tell
   that from having uploaded a broken file. */
.brand-slot-preview {
  display: grid;
  place-items: center;
  min-height: 72px;
  padding: 12px;
  border-radius: var(--ez-radius);
}
.brand-slot-preview[data-ground='dark'] {
  background: var(--ez-band);
}
.brand-slot-preview[data-ground='dark'] .hint {
  color: var(--ez-blue-light);
}
.brand-slot-preview[data-ground='light'] {
  background: var(--ez-ground);
  border: 1px solid var(--ez-border);
}
.brand-slot-preview img {
  max-width: 100%;
  max-height: 56px;
  object-fit: contain;
}
.brand-slot-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}
.brand-slot-picker {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.settings-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 16px;
  margin-bottom: 12px;
}

.settings-form label {
  display: grid;
  gap: 4px;
  color: var(--ez-muted);
}

/* Settings › Invoices is a document form, not a row of switches: company
   information, then default values, each field full width with its hint
   beneath it, so the wrapping row above does not apply. */
.settings-form.invoice-defaults-form {
  display: grid;
  gap: 14px;
  align-items: stretch;
  max-width: 720px;
}
.invoice-defaults-form h3 {
  margin: 12px 0 0;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--ez-border);
  font-size: 16px;
}
.invoice-defaults-form h3:first-child {
  margin-top: 0;
}
.invoice-defaults-form label {
  gap: 6px;
  font-size: 13px;
}
.invoice-defaults-form input,
.invoice-defaults-form textarea {
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font: inherit;
}
.invoice-defaults-form textarea {
  resize: vertical;
  line-height: 1.45;
}
.invoice-defaults-form .hint {
  margin: -6px 0 0;
  font-size: 13px;
}
.invoice-defaults-form > button {
  justify-self: start;
  min-height: 44px;
  margin-top: 6px;
}
/* A checkbox row in the invoice settings form: the box beside its words, not
   above them, however the form lays out its other fields. Specific enough to
   hold against the form's own label and input rules. */
.settings-form.invoice-defaults-form label.invoice-defaults-check {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  color: var(--ez-ink);
}
.settings-form.invoice-defaults-form .invoice-defaults-check input {
  flex: none;
  width: 18px;
  height: 18px;
  min-height: 0;
  margin: 0;
  padding: 0;
  accent-color: var(--ez-action);
}
.settings-form.invoice-defaults-form .invoice-defaults-select {
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font: inherit;
}
.settings-form.invoice-defaults-form .invoice-defaults-formats {
  display: flex;
  flex-wrap: wrap;
  gap: 0 16px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.settings-form.invoice-defaults-form .invoice-defaults-formats legend {
  padding: 0;
  color: var(--ez-muted);
  font-size: 13px;
}

/* The BILL delivery switch on a client (issue 542). Its own section rather
   than a row in the facts list: the facts are what the client IS, and this is a
   decision about how they are billed. */
.client-delivery {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.client-delivery-hint {
  margin: 0;
  max-width: 68ch;
  color: var(--ez-muted);
}

/* Who a client's invoices go to and what they carry. In the column beside the
   facts, under the BILL switch, as a form of its own: the lists and the rules
   are one standing decision about this client's invoices, so one Save. */
.client-sending {
  display: grid;
  align-content: start;
  gap: 12px;
  min-width: 0;
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.client-sending h2 {
  margin: 0;
  font-size: 18px;
}
.client-sending .hint {
  font-size: 13px;
  line-height: 1.45;
}
[data-client-sending-retry] {
  justify-self: start;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.client-sending-form {
  display: grid;
  gap: 12px;
  min-width: 0;
}
.client-sending-form fieldset {
  display: grid;
  gap: 10px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.client-sending-form legend {
  margin-bottom: 4px;
  padding: 0;
  font-weight: 600;
}
.client-sending-form label:not(.settings-toggle) {
  display: grid;
  gap: 6px;
  min-width: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.client-sending-form textarea,
.client-sending-form select {
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font: inherit;
  font-size: 15px;
}
.client-sending-form textarea {
  line-height: 1.45;
  resize: vertical;
}
/* Following the contacts, the boxes show who that is, and it is changed on the
   contacts: the second surface says this is read here, not edited. */
.client-sending-form textarea:read-only {
  background: var(--ez-surface-2);
}
.client-sending-form .settings-toggle {
  min-height: 44px;
}
.client-sending-form .settings-toggle input {
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--ez-action);
}
.client-sending-form .client-sending-formats {
  display: flex;
  flex-wrap: wrap;
  gap: 0 16px;
}
.client-sending-form > button {
  justify-self: start;
  min-height: 44px;
}

.settings-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ez-ink);
}

.settings-table {
  margin-bottom: 16px;
  overflow-x: auto;
}

/* The TXT challenge is what an operator copies into a zone file, so the record
   name and its value stack rather than running together into one string. */
.sso-record {
  display: grid;
  gap: 2px;
}

.sso-record code {
  font-family: var(--ez-font-mono), monospace;
  font-size: 12px;
  word-break: break-all;
}

.identity-signout {
  min-height: 28px;
  padding: 0 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font-size: 13px;
}
/* Neither is allowed to shrink or wrap: "Sign out" broken across two lines was
   how the bar announced it had run out of room, and it is a control, not copy. */
.identity-settings,
.identity-signout {
  flex: none;
  white-space: nowrap;
}
/*
 * Between the phone bar and the width the full bar actually needs, there was
 * nothing at all -- one breakpoint at 720px -- and the bands that closed that
 * gap were measured when the bar was six sections in a narrower brand. With
 * eight sections, the brand's mark and a search field, the whole bar needs
 * about 1590px, and the old bands overflowed by 36px at 1340 and 77px at 1140
 * (the 2026-10 reskin). The widths below are what the parts measure for an
 * administrator, who sees every section, with a running timer's chip at its
 * 150px floor:
 *
 *   >= 1700px  everything: the nav, search as a field, the account's name (1592)
 *   1520-1699  the account keeps only its avatar (1489)
 *   1300-1519  and the nav takes tighter padding, and search is its magnifier
 *              alone (1281)
 *   721-1299   the nav folds into the Menu drawer that phone width already
 *              uses -- same links, same dialog (688)
 *
 * Search gives up its field at 1520 rather than at the 1440 the bar could
 * carry it to. Between the two, the field took the room a running timer's
 * chip needed, and at 1440 the chip said "Bro…": the timer's name is what the
 * chip is for, and the magnifier still opens the same bar with the same keys.
 *
 * Each band has room to spare at the width it starts at, and the browser test
 * `[e2e:nav] fits the top bar at every width` measures the bar at each of them
 * with the longest timer label the chip will hold, so a band that stops fitting
 * fails there rather than in a screenshot. The account gives up its name rather
 * than truncating it: the profile and user number are decoration, and Settings
 * -- which stays -- is the page that states both.
 */
/* Phone is excluded: down there the account has a whole row to itself and the
   name fits, which is the one thing the 720px layout already got right. */
@media (min-width: 721px) and (max-width: 1699px) {
  .identity-text {
    display: none;
  }
}
@media (min-width: 1300px) and (max-width: 1519px) {
  .primary-nav a {
    padding: 0 10px;
  }
}
/* Search keeps its name and its shortcut; it loses only the field around them. */
@media (min-width: 721px) and (max-width: 1519px) {
  .command-trigger {
    min-width: 36px;
    width: 36px;
    justify-content: center;
    padding: 0;
  }
  .command-trigger-label,
  .command-shortcut {
    display: none;
  }
}
@media (min-width: 721px) and (max-width: 1299px) {
  .primary-nav {
    display: none;
  }
  .menu-trigger {
    display: block;
  }
}

/* There is no corner to sit in at phone width: brand, timer and menu already
   fill the bar, so the account wraps onto its own row inside the header rather
   than overflowing it. Sign out is a required 44px tap target there. */
@media (max-width: 720px) {
  .topbar {
    flex-wrap: wrap;
    padding-bottom: 8px;
  }
  .account {
    flex-basis: 100%;
  }
  .account-identity {
    min-height: 44px;
  }
  .identity-settings,
  .identity-signout {
    min-height: 44px;
    margin-left: auto;
  }
}

/*
 * Navigation: one header, two layouts (the 2026-10 reskin).
 *
 * Everything above lays the header out as the bar across the top. Someone who
 * keeps the sidebar -- the default -- gets the same elements down the left,
 * from 900px up; below that neither layout fits, and both fold into the Menu
 * drawer through the bar's own narrow bands, which is why every rule here is
 * inside the one media query. Nothing here reorders an element, so the order
 * a keyboard walks the header in is the order it is read in, in both layouts.
 */
:root {
  --ez-sidebar-width: 248px;
  --ez-sidebar-width-collapsed: 72px;
}
.brand-glyph,
.nav-collapse,
.primary-nav .ez-icon,
.identity-settings .ez-icon,
.identity-signout .ez-icon {
  display: none;
}
@media (min-width: 900px) {
  [data-ez-nav='sidebar'] .authenticated-shell {
    padding-left: var(--ez-sidebar-width);
  }
  [data-ez-nav='sidebar'] .topbar {
    position: fixed;
    z-index: 30;
    inset: 0 auto 0 0;
    width: var(--ez-sidebar-width);
    min-height: 0;
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr);
    grid-template-rows: auto auto auto auto auto 1fr;
    align-items: start;
    gap: 8px;
    padding: 12px 10px;
    overflow-y: auto;
    border-right: 1px solid var(--ez-border);
    background: var(--ez-surface);
    color: var(--ez-ink);
  }
  /* The brand keeps its band. The mark an operator uploads for a dark ground
     is drawn on it, and on the light sidebar that mark would disappear. */
  [data-ez-nav='sidebar'] .brand {
    grid-column: 1 / -1;
    min-height: 52px;
    margin: 0 0 4px;
    padding: 0 52px 0 16px;
    place-items: center start;
    border-radius: calc(var(--ez-radius) * 1.5);
    background: var(--ez-band);
    color: var(--ez-band-ink);
  }
  [data-ez-nav='sidebar'] .nav-collapse {
    position: absolute;
    top: 22px;
    right: 20px;
    display: grid;
    width: 32px;
    height: 32px;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: var(--ez-radius);
    background: transparent;
    color: var(--ez-band-ink);
  }
  [data-ez-nav='sidebar'] .primary-nav {
    grid-column: 1 / -1;
    display: grid;
    gap: 2px;
  }
  [data-ez-nav='sidebar'] .primary-nav a {
    grid-template-columns: auto minmax(0, 1fr);
    place-items: center start;
    gap: 12px;
    min-height: 44px;
    padding: 0 6px;
    border-radius: calc(var(--ez-radius) + 2px);
    color: var(--ez-ink);
    font-weight: 600;
  }
  [data-ez-nav='sidebar'] .primary-nav .ez-icon {
    display: block;
    box-sizing: content-box;
    width: 18px;
    height: 18px;
    padding: 6px;
    border-radius: var(--ez-radius);
    color: var(--ez-muted);
  }
  [data-ez-nav='sidebar'] .primary-nav a:hover,
  [data-ez-nav='sidebar'] .primary-nav a:focus-visible {
    background: var(--ez-surface-2);
  }
  /* The section you are in: its row on the nested grey and its mark on a tile
     of the action blue. aria-current is what draws it, so what the eye sees
     and what a screen reader hears are the one fact. */
  [data-ez-nav='sidebar'] .primary-nav a[aria-current='page'] {
    background: var(--ez-surface-2);
    color: var(--ez-ink);
  }
  [data-ez-nav='sidebar'] .primary-nav a[aria-current='page'] .ez-icon {
    background: var(--ez-action);
    box-shadow: inset 0 0 0 1px var(--ez-action-edge);
    color: var(--ez-action-fg);
  }
  [data-ez-nav='sidebar'] .timer-chip,
  [data-ez-nav='sidebar'] .money-toggle,
  [data-ez-nav='sidebar'] .command-trigger,
  [data-ez-nav='sidebar'] .menu-trigger,
  [data-ez-nav='sidebar'] .account-identity {
    background: var(--ez-ground);
  }
  [data-ez-nav='sidebar'] .timer-chip {
    grid-column: 1 / -1;
    width: 100%;
    max-width: none;
    /* The top bar's floor is for a chip that shares a row; in the column it is
       the column's width, and narrowed it is an icon. */
    min-width: 0;
    min-height: 40px;
    margin: 8px 0 0;
  }
  [data-ez-nav='sidebar'] .timer-chip [data-timer-elapsed] {
    margin-left: auto;
  }
  /* Search is a row of its own, the width of the column; $ and Menu share the
     row under it. Squeezed between them, its label ran out of the button. */
  [data-ez-nav='sidebar'] .command-trigger {
    grid-column: 1 / -1;
    width: 100%;
    min-width: 0;
    min-height: 40px;
    justify-content: flex-start;
    padding: 0 6px 0 10px;
  }
  [data-ez-nav='sidebar'] .command-trigger-label,
  [data-ez-nav='sidebar'] .command-shortcut {
    display: inline-block;
  }
  [data-ez-nav='sidebar'] .money-toggle {
    grid-column: 1;
  }
  [data-ez-nav='sidebar'] .menu-trigger {
    grid-column: 2;
    display: block;
  }
  [data-ez-nav='sidebar'] .account {
    grid-column: 1 / -1;
    align-self: end;
  }
  [data-ez-nav='sidebar'] .account-identity {
    flex-wrap: wrap;
    padding: 8px;
  }
  /* The name takes the first row beside the avatar, and the two account
     controls share the second, so neither the name nor a control is squeezed
     to an ellipsis in a column this narrow. */
  [data-ez-nav='sidebar'] .identity-text {
    display: grid;
    flex: 1 1 calc(100% - 48px);
  }
  [data-ez-nav='sidebar'] .identity-settings,
  [data-ez-nav='sidebar'] .identity-signout {
    flex: 1 1 40%;
    text-align: center;
  }

  /* Narrowed to its icons. Every label stays in the document and out of
     sight, so no control changes its name when the sidebar changes width. */
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .authenticated-shell {
    padding-left: var(--ez-sidebar-width-collapsed);
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .topbar {
    width: var(--ez-sidebar-width-collapsed);
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: repeat(7, auto) 1fr;
    justify-items: center;
    padding: 12px;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .brand {
    width: 48px;
    min-height: 48px;
    padding: 0;
    place-items: center;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .brand-wordmark {
    display: none;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .brand-glyph {
    display: grid;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .nav-collapse {
    position: static;
    color: var(--ez-muted);
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .nav-collapse:hover,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .nav-collapse:focus-visible {
    background: var(--ez-surface-2);
    color: var(--ez-ink);
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .primary-nav a {
    grid-template-columns: auto;
    place-items: center;
    padding: 0 6px;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .timer-chip {
    width: auto;
    padding: 0 10px;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .money-toggle,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .command-trigger,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .menu-trigger {
    grid-column: 1;
    width: 48px;
    min-width: 0;
    justify-content: center;
    padding: 0;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .account-identity {
    flex-direction: column;
    gap: 6px;
    padding: 6px;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .identity-settings,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .identity-signout {
    display: grid;
    flex: none;
    width: 36px;
    min-height: 36px;
    place-items: center;
    padding: 0;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .identity-settings .ez-icon,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .identity-signout .ez-icon {
    display: block;
  }
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .nav-label,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .timer-chip [data-timer-label],
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .timer-chip [data-timer-elapsed],
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .command-trigger > span,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .command-trigger > kbd,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .identity-text,
  [data-ez-nav='sidebar'][data-ez-sidebar='collapsed'] .identity-action-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
.primary-action:disabled,
button[data-auth-action]:disabled {
  box-shadow: none;
  cursor: not-allowed;
  opacity: 0.55;
}
@media (prefers-reduced-motion: reduce) {
  .auth-checking span,
  .session-check-spinner {
    animation: none;
  }
}
.session-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
  padding: 10px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
}
.session-status button {
  min-height: 36px;
  padding: 0 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  white-space: nowrap;
}
.session-status[data-state="ready"] {
  border-color: var(--ez-action);
  color: var(--ez-ink);
}
/*
 * A write being sent again after it failed (issue 953). Fixed to the corner, so
 * it reads the same beside the sidebar and under the top bar, and stays in view
 * while the page beneath it scrolls. Below the session check, which covers
 * everything while it runs.
 */
.write-status {
  position: fixed;
  z-index: 950;
  inset-block-end: 16px;
  inset-inline-end: 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: calc(100vw - 32px);
  padding: 10px 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
  box-shadow: var(--ez-shadow-menu);
}
.write-status-spinner {
  width: 14px;
  aspect-ratio: 1;
  flex: 0 0 auto;
  border: 2px solid var(--ez-border);
  border-top-color: var(--ez-action);
  border-radius: 50%;
  animation: auth-spin 900ms linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .write-status-spinner {
    animation: none;
  }
}
.week-surface {
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) * 1.5);
  overflow: hidden;
}
.week-toolbar {
  min-height: 64px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 10px 20px;
  background: var(--ez-surface);
  border-bottom: 1px solid var(--ez-border);
}
.timesheet-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--ez-border);
  background: var(--ez-status-bg);
}
.timesheet-status[hidden] {
  display: none;
}
.timesheet-status p {
  margin: 4px 0 0;
}
/* The reason an approver typed is prose about a refusal, and it sits on the
   status band's tint. It is written in red, which the gate holds to the 4.5:1
   body-text bar on that band. */
.timesheet-status [data-timesheet-rejection-reason] {
  color: var(--ez-red);
  white-space: pre-wrap;
}
.timesheet-status button {
  min-height: 44px;
  flex: 0 0 auto;
}
.week-surface h2 {
  font-size: 18px;
}
/*
 * The period takes the room the "Week of …" heading had and lets its summary
 * ellipsize rather than wrap: the toolbar is one band and a second line here is
 * how a band becomes two.
 */
.week-period {
  flex: 1 1 auto;
  min-width: 0;
}
/*
 * The summary already reads "This week: 24 – 30 Aug 2026", so a "Week" caption
 * stacked above it spends a line of a 64px band repeating the word the value
 * opens with. Hidden rather than dropped: it is what names the output for a
 * reader who cannot see that the two sit together.
 */
.week-period .period-field-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.week-period .period-summary {
  font-size: 18px;
  font-weight: 600;
}
.week-surface [data-week-total] {
  font-family: var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.week-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  flex-wrap: wrap;
}
.week-actions button,
.day-switcher button {
  min-height: 36px;
  padding: 0 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.week-actions [data-add-row-trigger] {
  border-color: var(--ez-action-edge);
  background: var(--ez-action);
  color: var(--ez-action-fg);
  font-weight: 600;
}
/* The week's total is the one number the whole screen is for, and it read at
   the same size as the buttons beside it. §6 of the old-UI analysis puts the
   landmark figure at 40px; nothing in the app reached 20. It clamps so a
   decimal account's wider figure still fits beside the controls. */
.week-actions [data-week-total] {
  min-width: 72px;
  padding-left: 14px;
  font-size: clamp(24px, 3vw, 40px);
  line-height: 1.05;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-align: right;
}
/* The seven-day strip: the week's shape before you read a row. Seven equal
   columns so the eye compares heights, not positions. */
.day-totals {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  margin: 0 0 12px;
  padding: 0;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  overflow: hidden;
  background: var(--ez-border);
  list-style: none;
}

.day-totals li {
  display: grid;
  background: var(--ez-ground);
}

/* The padding moves onto the control so the whole tile is the hit target,
   rather than a word inside a tile that also looks clickable. */
.day-totals li > button {
  display: grid;
  gap: 2px;
  padding: 8px 10px;
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}

.day-totals li > button:hover {
  background: var(--ez-surface);
}

.day-totals li > button:focus-visible {
  outline: 2px solid var(--ez-data);
  outline-offset: -2px;
}

.day-totals li[data-today] {
  background: var(--ez-surface);
  box-shadow: inset 0 -3px var(--ez-data);
}

.day-totals li[data-selected] {
  background: var(--ez-surface);
}

.day-totals span {
  color: var(--ez-muted);
  font-size: 13px;
}

.day-totals strong {
  font: 700 20px/1.2 var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
}

.day-totals strong[data-empty] {
  color: var(--ez-muted);
  font-weight: 400;
}

.day-row-start {
  justify-self: start;
  min-height: 44px;
  margin-top: 8px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}

.day-row-start:hover:not(:disabled) {
  border-color: var(--ez-data);
  color: var(--ez-data);
}

/* The row label is a sticky header cell in a dense grid, so Start sits inline
   with the task and appears on hover or focus rather than adding a control to
   every row at rest. Keyboard reaches it the same way: focus-within. */
.grid-row-start {
  min-height: 28px;
  margin-left: 8px;
  padding: 0 10px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font-size: 12px;
  opacity: 0;
}

.week-grid-table tbody tr:hover .grid-row-start,
.week-grid-table tbody th:focus-within .grid-row-start,
.grid-row-start:focus-visible {
  opacity: 1;
}

.grid-row-start:hover:not(:disabled) {
  border-color: var(--ez-data);
  color: var(--ez-data);
}

@media (hover: none) {
  /* No hover to reveal it, and the phone layout uses the day list anyway. */
  .grid-row-start {
    opacity: 1;
  }
}

.week-grid-wrap {
  overflow-x: auto;
}
.week-grid-table {
  width: 100%;
  min-width: 1100px;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.week-grid-table th,
.week-grid-table td {
  height: 58px;
  border-bottom: 1px solid var(--ez-border);
  text-align: left;
  white-space: nowrap;
}
.week-grid-table thead th {
  height: 44px;
  padding: 0 8px;
  background: var(--ez-surface);
  color: var(--ez-muted);
  font-size: 13px;
  font-weight: 500;
  text-align: center;
}

/* Weekday over date: the eye runs across the weekdays and drops to the date
   only to place the week. On one line the two compete. */
.week-grid-table thead th [data-weekday] {
  display: block;
  color: var(--ez-ink);
  font-weight: 600;
  line-height: 1.2;
}

.week-grid-table thead th [data-date] {
  display: block;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

.week-grid-table thead th:first-child [data-weekday],
.week-grid-table thead th:first-child [data-date] {
  display: inline;
}
.week-grid-table thead th:first-child,
.week-grid-table tbody th,
.week-grid-table tfoot th {
  width: 224px;
  min-width: 224px;
  position: sticky;
  left: 0;
  z-index: 1;
  padding: 0 16px 0 20px;
  background: var(--ez-ground);
  text-align: left;
}
.week-grid-table thead th:first-child {
  z-index: 2;
  background: var(--ez-surface);
}
/* A project's name gives way rather than widening the grid. The cell is
   nowrap, so its longest name was its minimum width: a 30-character project
   pushed the whole week into a sideways scroll at any width. At zero, the
   column's own width is the floor and the name ellipsizes into it. */
.week-grid-table tbody th {
  max-width: 0;
}
.week-grid-table tbody th strong,
.week-grid-table tbody th span,
.day-row-label strong,
.day-row-label span {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}
.week-grid-table tbody th span,
.day-row-label span {
  margin-top: 2px;
  color: var(--ez-muted);
  font-size: 13px;
  font-weight: 400;
}
.week-grid-table tbody td {
  width: 112px;
  min-width: 112px;
  padding: 0;
  border-left: 1px solid var(--ez-border);
}
.week-grid-table tbody tr:last-child th,
.week-grid-table tbody tr:last-child td {
  border-bottom: 0;
}
.week-grid-table tbody tr[data-running="true"] th {
  box-shadow: inset 3px 0 var(--ez-live);
}
.week-grid-table .row-total,
.week-grid-table tfoot td {
  padding: 0 12px;
  color: var(--ez-muted);
  font-family: var(--ez-font-body), sans-serif;
  text-align: right;
}
.week-grid-table tfoot th,
.week-grid-table tfoot td {
  height: 44px;
  border-top: 1px solid var(--ez-border);
  border-bottom: 0;
  background: var(--ez-surface);
  font-weight: 600;
}
.week-grid-table .grid-empty {
  padding: 24px 20px;
  color: var(--ez-muted);
  text-align: center;
}
.week-cell {
  min-height: 57px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 24px;
  grid-template-rows: 36px minmax(12px, auto) minmax(0, auto);
  align-items: center;
  padding: 2px 4px 0;
  background: var(--ez-ground);
}
/* Today's column, marked its full height. The old grid used a rule under the
   header; a tinted column reads at a glance without adding a line to a table
   whose whole discipline is hairlines. */
.week-grid-table th[data-today] {
  box-shadow: inset 0 -3px var(--ez-data);
}

.week-grid-table td[data-today],
.week-grid-table th[data-today] {
  background: var(--ez-surface);
}

.week-cell input {
  width: 100%;
  height: 34px;
  padding: 0 7px;
  /* A resting border: the old grid read as cells you could type into before you
     touched it, rather than revealing itself one cell at a time on hover. */
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: transparent;
  color: var(--ez-ink);
  font-family: var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.week-cell input:hover:not(:disabled) {
  border-color: var(--ez-border);
}
.week-cell input:focus {
  border-color: var(--ez-data);
  background: var(--ez-surface);
}
.week-cell input:disabled {
  color: var(--ez-muted);
  cursor: not-allowed;
}
.week-cell[data-running="true"] input {
  box-shadow: inset 3px 0 var(--ez-live);
}
.week-cell[data-cell-state="saved"] input {
  border-color: var(--ez-action);
}
.week-cell[data-cell-state="retry"] input,
.week-cell[data-cell-state="conflict"] input {
  border-color: var(--ez-red);
}
/* The inset rule beside the input is the indicator; this is the word next to
   it, so it reads as body text and takes `live-text` rather than `live`. */
.week-cell[data-cell-state="running"] .cell-status {
  color: var(--ez-live-text);
}
/*
 * Below 1470px the week is tightened rather than scrolled. The grid needed
 * 1129px, and beside the 248px sidebar inside the page gutter a 1440px window
 * leaves its card 1104px: the Total column sat under the card's edge on a
 * 1440px laptop, and 1366 and 1280 lost two columns. A day keeps room for
 * "23:59" and the note button, the total keeps room for a week's hours, and
 * the label column keeps its width, so the grid needs 912px -- inside its card
 * from a 1236px window beside the sidebar, 1050 beside the narrowed rail, and
 * under 1000 with the top bar. Wider than that, the table spreads the
 * difference across the columns as before. `[e2e:width-fit]` measures it.
 */
@media (max-width: 1469px) {
  .week-grid-table {
    min-width: 0;
  }
  .week-grid-table tbody td {
    width: 88px;
    min-width: 88px;
  }
  .week-grid-table tbody td.row-total {
    width: 72px;
    min-width: 72px;
  }
  .week-grid-table .row-total,
  .week-grid-table tfoot td {
    padding: 0 8px;
  }
  .week-grid-table .week-cell {
    padding: 2px 2px 0;
  }
  .week-grid-table .week-cell input {
    padding: 0 5px;
  }
}
.week-hint {
  margin: 0 0 12px;
  padding: 8px 20px 0;
  color: var(--ez-muted);
  font-size: 12px;
  line-height: 1.6;
}
.week-removed {
  margin: 0 0 12px;
  padding: 8px 12px;
  border-left: 3px solid var(--ez-border);
  background: var(--ez-surface);
  color: var(--ez-data);
  font-size: 12px;
}
/*
 * An administrator's person picker on Time and the band saying whose week is on
 * screen (issue 917). The band uses the status tint and the action colour for its
 * rule: it is the one thing on the page that must not be mistaken for your own
 * week, so it is not the quiet grey of a passing notice.
 */
.time-person {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-left: auto;
  font-size: 13px;
  color: var(--ez-muted);
}
.time-person[hidden],
.time-person-banner[hidden] {
  display: none;
}
.time-person select {
  min-height: 36px;
  padding: 0 8px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-ink);
}
.time-person-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin: 0 0 12px;
  padding: 8px 12px;
  border-left: 3px solid var(--ez-action);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
}
.time-person-banner button {
  min-height: 36px;
  flex: 0 0 auto;
}
.week-hint kbd {
  padding: 1px 5px;
  border: 1px solid var(--ez-border);
  border-radius: 3px;
  background: var(--ez-surface);
  color: var(--ez-data);
  font:
    11px/1 var(--ez-font-mono),
    monospace;
}

/* Reads as a control rather than punctuation (issue 753). Transparent and
   borderless beside a bordered input, a bare glyph looked like decoration --
   the account owner read this screen and concluded the product could not edit
   a note or remove an entry, when both have always been one click away here. */
.cell-note {
  width: 24px;
  height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--ez-muted);
  font-weight: 700;
  cursor: pointer;
}
.cell-note:hover:not(:disabled),
.cell-note:focus-visible {
  border-color: var(--ez-border);
  background: var(--ez-surface);
  color: var(--ez-data);
}
/* A cell that already carries a note says so at rest, so an entry worth
   opening is distinguishable from an empty day without hovering every cell. */
.cell-note[data-has-note="true"] {
  color: var(--ez-data);
}
.cell-note:disabled {
  color: var(--ez-border);
  cursor: default;
}
.cell-status {
  grid-column: 1 / -1;
  min-width: 0;
  overflow: hidden;
  color: var(--ez-muted);
  font:
    10px/1.2 var(--ez-font-body),
    sans-serif;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cell-lock-reason {
  grid-column: 1 / -1;
  min-width: 0;
  overflow: hidden;
  color: var(--ez-muted);
  font-size: 10px;
  line-height: 1.2;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.week-cell[data-cell-state="retry"] .cell-status,
.week-cell[data-cell-state="conflict"] .cell-status {
  color: var(--ez-red);
}
.cell-retry {
  grid-column: 2;
  grid-row: 2;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ez-data);
  font-size: 10px;
  font-weight: 600;
}
.day-list {
  display: none;
}
.day-switcher {
  min-height: 52px;
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
  gap: 8px;
  padding: 4px 12px;
  border-bottom: 1px solid var(--ez-border);
  background: var(--ez-ground);
  text-align: center;
}
.day-row {
  min-height: 72px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 132px;
  align-items: center;
  gap: 12px;
  padding: 6px 16px;
  border-bottom: 1px solid var(--ez-border);
}
.day-row[data-running="true"] {
  box-shadow: inset 3px 0 var(--ez-live);
}
.day-row-label {
  min-width: 0;
}
.day-entry-note {
  margin: 8px 0 0;
  color: var(--ez-ink);
  font-size: 14px;
  line-height: 1.45;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.day-entry-note[data-empty="true"] {
  color: var(--ez-muted);
  font-style: italic;
}
.day-empty {
  margin: 0;
  padding: 28px 16px;
  color: var(--ez-muted);
  text-align: center;
}
html[data-time-view="day"] .week-grid-wrap,
html[data-time-view="calendar"] .week-grid-wrap,
html[data-time-view="calendar"] .day-list {
  display: none;
}
/* The calendar is its own panel rather than a restyled grid: the two answer
   different questions, and one stylesheet trying to be both is how a view ends
   up half-right in each. */
.calendar-week {
  display: none;
}
html[data-time-view="calendar"] .calendar-week {
  display: block;
}
html[data-time-view="day"] .day-list {
  display: block;
}

/* Day view on a desktop is a chosen view, not a fallback. Below 720px the
   phone rules take over and this whole block is irrelevant; above it the same
   markup was being rendered at phone density on a screen with room to spare --
   a 52px switcher, 14px hours, and rows sized for a thumb. */
@media (min-width: 721px) {
  html[data-time-view="day"] .day-switcher {
    min-height: 72px;
    grid-template-columns: 44px 1fr 44px;
    padding: 8px 20px;
  }

  html[data-time-view="day"] .day-switcher [data-day-label] {
    font-size: 28px;
    font-weight: 600;
    line-height: 44px;
  }

  html[data-time-view="day"] .day-row {
    min-height: 88px;
    grid-template-columns: minmax(0, 1fr) 180px;
    padding: 12px 20px;
  }

  /* The hours are the thing you came to read on this screen. */
  html[data-time-view="day"] .day-row .week-cell input {
    height: 48px;
    font-size: 22px;
  }

  html[data-time-view="day"] .day-row-label strong {
    font-size: 16px;
  }
}
.data-quality {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
}
.data-quality a {
  font-weight: 600;
  white-space: nowrap;
}
.empty-state {
  min-height: 180px;
  display: grid;
  place-content: center;
  text-align: center;
  padding: 32px;
  background: var(--ez-surface);
  color: var(--ez-muted);
}
.empty-state p {
  margin: 8px 0 0;
  max-width: 44ch;
}
dialog {
  width: min(640px, calc((100vw - 32px) / var(--ez-zoom, 1)));
  padding: 0;
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) * 2);
  background: var(--ez-ground);
  color: var(--ez-ink);
  box-shadow: var(--ez-shadow-popover);
}
dialog::backdrop {
  background: color-mix(in srgb, var(--ez-shadow) 48%, transparent);
  backdrop-filter: blur(2px);
}
@media (prefers-reduced-motion: no-preference) {
  dialog[open] {
    animation: ez-dialog-in 140ms ease-out;
  }
}
@keyframes ez-dialog-in {
  from {
    opacity: 0;
    /* Translate only: a scale would shrink tap targets below 44px mid-open. */
    transform: translateY(6px);
  }
}
dialog form {
  display: grid;
  gap: 14px;
  padding: 20px;
}
dialog header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
}
dialog header button {
  border: 0;
  background: transparent;
  color: var(--ez-muted);
  font-size: 24px;
}
/* The × alone gets a square hit area; text buttons in a header keep theirs. */
dialog header button[aria-label^='Close'] {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--ez-radius);
  font-size: 22px;
  line-height: 1;
}
dialog header button[aria-label^='Close']:hover {
  background: var(--ez-surface-2);
  color: var(--ez-ink);
}
dialog label {
  display: grid;
  gap: 6px;
  font-size: 13px;
  color: var(--ez-muted);
}
dialog input,
dialog select,
dialog textarea {
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-ink);
}
dialog textarea {
  resize: vertical;
}
.entry-assignment,
.entry-times {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.entry-times[hidden],
[data-entry-duration][hidden],
[data-entry-running][hidden] {
  display: none;
}
.hint,
.form-result {
  margin: 0;
  color: var(--ez-muted);
}
/*
 * The command bar and the Menu hang from the bar they open from (issue 859).
 * A modal dialog centres itself on the viewport, and the command bar's list
 * grows and shrinks with every keystroke -- so its input moved while it was
 * being typed into. Phone keeps the bottom sheet every dialog takes there.
 */
@media (min-width: 721px) {
  .command-dialog,
  .menu-dialog {
    margin-block: calc(10vh / var(--ez-zoom, 1)) auto;
    max-height: calc((90vh - 24px) / var(--ez-zoom, 1));
  }
}
/*
 * The command bar in the reskin's tokens (the 2026-10 reskin): a roomy query
 * with the magnifier inside it, the groups under label-style headings, the
 * highlighted row in the selected tint, and the keys as chips in the footer.
 * Paint only -- the combobox, the listbox, their ids and every handler are the
 * ones the bar already had.
 */
.command-dialog {
  width: min(600px, calc((100vw - 32px) / var(--ez-zoom, 1)));
}
.command-dialog form {
  gap: 16px;
}
.command-query {
  position: relative;
}
.command-query .ez-icon {
  position: absolute;
  top: 50%;
  left: 16px;
  transform: translateY(-50%);
  color: var(--ez-muted);
  pointer-events: none;
}
.command-query input {
  min-height: 52px;
  padding: 0 16px 0 46px;
  border-radius: calc(var(--ez-radius) * 1.5);
  background: var(--ez-ground);
  font-size: 16px;
}
/*
 * The results run edge to edge between two rules rather than sitting in a
 * bordered box inside the dialog's own border (issue 859). The negative margin
 * is the form's padding, so the rows' text still lines up with the heading.
 */
.command-results {
  display: grid;
  gap: 8px;
  max-height: min(46vh, 340px);
  margin: 0 -20px;
  padding: 8px;
  overflow-y: auto;
  border-top: 1px solid var(--ez-border);
  border-bottom: 1px solid var(--ez-border);
}
.command-group {
  display: grid;
  gap: 2px;
}
.command-group > .eyebrow {
  margin: 0;
  padding: 10px 12px 4px;
}
.command-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  padding: 0 12px;
  border-radius: var(--ez-radius);
  color: var(--ez-ink);
  text-decoration: none;
}
.command-option:hover {
  background: var(--ez-surface-2);
}
/*
 * The row Enter acts on. It takes the selected tint, the focus colour as a bar
 * down its edge -- it is the focus, by way of aria-activedescendant -- and the
 * key it answers to at its far end. Hover is a different tint on purpose: the
 * pointer does not move the highlight here, so a hovered row and the
 * highlighted row are two different rows that two different actions reach.
 *
 * The key is generated content with empty alternative text, which keeps it
 * out of the option's accessible name: a reader hears the destination, not
 * "Time Enter". An engine that does not know the syntax drops the declaration
 * and draws no key, which is the same row it drew before.
 */
.command-option[aria-selected='true'] {
  background: var(--ez-orange-tint);
  box-shadow: inset 3px 0 0 var(--ez-data);
}
.command-option[aria-selected='true']::after {
  content: 'Enter' / '';
}
.command-empty {
  margin: 0;
  padding: 10px 12px;
  color: var(--ez-muted);
}
/* A log line the bar understood is an answer, not an absence, so it is not
   painted like "nothing matched". */
.command-empty[data-command-preview] {
  color: var(--ez-ink);
}
/*
 * The footer: the keys on the left, the button the touch screens need on the
 * right. The keys are chips in the week hint's style, one per action, so the
 * line neither wraps at the dialog's width nor reads as a paragraph. Every one
 * of them is handled on the input; none is wired here.
 */
.command-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 16px;
}
.command-footer .primary-action {
  margin-left: auto;
}
.command-keys {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  font-size: 13px;
}
.command-keys span {
  white-space: nowrap;
}
.command-keys kbd + kbd {
  margin-left: 2px;
}
@media (hover: none) {
  /* No keyboard to hint at, on the row or under the list; the button beside
     the legend is the whole footer there. */
  .command-keys,
  .command-option[aria-selected='true']::after {
    display: none;
  }
}
@media (max-width: 720px) {
  .command-footer {
    flex-direction: column;
    align-items: stretch;
  }
  /* An auto margin absorbs the free space a column would otherwise stretch
     the button into, so it goes: full width, like the entry dialog's. */
  .command-footer .primary-action {
    margin-left: 0;
  }
}
.timer-actions {
  display: flex;
  gap: 8px;
}
.timer-actions > button {
  min-height: 36px;
  padding: 0 16px;
  border-radius: var(--ez-radius);
}
/*
 * The Menu (issue 859). Its two navs -- the strip's sections and the ones
 * issue 461 folded out of it -- read as one list with a rule where the second
 * begins, on rows tall enough to tap and evenly spaced whichever gates are
 * hiding links. The section you are in is marked the way the tab strip marks
 * the tab you are on. The timezone form is the dialog's footer, below.
 */
.menu-dialog > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 20px;
  border-bottom: 1px solid var(--ez-border);
}
.menu-dialog > header button {
  border: 0;
  background: transparent;
  color: var(--ez-muted);
  font-size: 24px;
}
.menu-dialog {
  width: min(680px, calc((100vw - 32px) / var(--ez-zoom, 1)));
}
/*
 * The Menu's destinations are cards (2026-10): a mark, the section and one line
 * of what lives there, two to a row, grouped under small headings. They are a
 * component of their own -- `.menu-card`, never `.nav-label` -- so a collapsed
 * sidebar, which hides `.nav-label`, cannot reach into the Menu.
 */
.menu-group {
  padding: 16px 20px 4px;
}
.menu-group + .menu-group {
  border-top: 1px solid var(--ez-border);
}
.menu-group-title {
  margin: 0 0 10px;
  color: var(--ez-muted);
  font-size: var(--ez-label-size);
  font-weight: var(--ez-label-weight);
  letter-spacing: var(--ez-label-tracking);
  text-transform: uppercase;
}
.menu-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  padding-bottom: 12px;
}
.menu-card {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-height: 64px;
  padding: 12px;
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) * 1.5);
  background: var(--ez-surface);
  color: var(--ez-ink);
  text-decoration: none;
}
.menu-card[hidden] {
  display: none;
}
.menu-card:hover {
  border-color: var(--ez-data);
  background: var(--ez-row-hover);
}
.menu-card:focus-visible {
  outline: 2px solid var(--ez-data);
  outline-offset: 2px;
}
.menu-card[aria-current='page'] {
  border-color: var(--ez-data);
  background: var(--ez-orange-tint);
}
.menu-card .ez-icon {
  flex: none;
  margin-top: 2px;
  color: var(--ez-data);
}
.menu-card-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.menu-card-title {
  font-weight: 600;
}
.menu-card-hint {
  color: var(--ez-muted);
  font-size: 13px;
  line-height: 1.35;
}
.menu-layout {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 20px;
  border-top: 1px solid var(--ez-border);
}
.menu-layout-label {
  margin-right: auto;
  color: var(--ez-muted);
  font-size: 13px;
}
.menu-layout button {
  min-height: 36px;
  padding: 0 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.menu-layout button[aria-pressed='true'] {
  border-color: var(--ez-data);
  background: var(--ez-orange-tint);
}
/*
 * Below 900px the Menu is the drawer, for both layouts: it slides in from the
 * left, where the sidebar would be, and takes the full height. It is a modal
 * dialog, so the page behind it is inert and Tab cannot leave it; Escape and
 * the close button both hand focus back to the Menu button.
 */
@media (max-width: 899px) {
  .menu-dialog {
    inset: 0 auto 0 0;
    width: min(340px, calc(88vw / var(--ez-zoom, 1)));
    max-width: none;
    height: 100%;
    max-height: none;
    margin: 0;
    border-width: 0 1px 0 0;
    border-radius: 0;
  }
}
/* In the drawer the cards stack: one column, still a card each. */
@media (max-width: 899px) {
  .menu-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .menu-card {
    min-height: 56px;
  }
}
@media (max-width: 899px) and (prefers-reduced-motion: no-preference) {
  .menu-dialog[open] {
    animation: ez-drawer-in 180ms ease-out;
  }
}
@keyframes ez-drawer-in {
  from {
    transform: translateX(-100%);
  }
}
.document-shell {
  min-height: calc(100vh / var(--ez-zoom, 1));
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.document-shell > header {
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 24px;
  border-bottom: 1px solid var(--ez-border);
}
.document-shell > main {
  width: min(720px, calc(100% - 32px));
  margin: 0 auto;
  padding: 48px 0;
}
.build-stamp {
  padding: 0 32px 24px;
  color: var(--ez-muted);
  font:
    12px/1.4 var(--ez-font-mono),
    monospace;
}
.task-admin-workspace {
  width: 100%;
}
.task-admin-intro,
.task-admin-status {
  color: var(--ez-muted);
}
.task-admin-status {
  min-height: 20px;
}
.task-admin-toolbar,
.task-admin-toolbar fieldset,
.invoice-list-toolbar fieldset,
.task-admin-confirm-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.expense-category-workspace {
  width: 100%;
}
.expense-category-intro,
.expense-category-status {
  color: var(--ez-muted);
}
.expense-category-create,
.expense-category-directory {
  margin-top: 22px;
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.expense-category-create > header,
.expense-category-directory-header,
.expense-category-dialog header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.expense-category-create h2,
.expense-category-directory h2,
.expense-category-dialog h2 {
  margin: 0;
  font-size: 18px;
}
.expense-category-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.expense-category-form label,
.expense-category-unit-fields label {
  display: grid;
  gap: 6px;
  color: var(--ez-muted);
  font-size: 13px;
}
.expense-category-form input,
.expense-category-form select,
.expense-category-form button,
.expense-category-filter button,
.expense-category-list-actions button,
.expense-category-dialog-actions button,
.expense-category-dialog [data-dialog-close] {
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.expense-category-unit-fields {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.expense-category-form > .form-result {
  grid-column: 1 / -1;
}
.expense-category-filter,
.expense-category-list-actions,
.expense-category-dialog-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.task-admin-toolbar {
  margin: 24px 0 12px;
}
.task-admin-toolbar fieldset {
  margin: 0;
  padding: 0;
  border: 0;
}
.invoice-list-toolbar fieldset button,
.task-admin-toolbar button,
.task-admin-confirm-actions button,
.task-admin-workspace [data-task-create],
.task-admin-workspace [data-task-load-more],
.task-admin-workspace [data-task-list-retry] {
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.invoice-list-toolbar fieldset button[aria-pressed='true'],
.task-admin-toolbar button[aria-pressed='true'] {
  border-color: var(--ez-data);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
}
.task-admin-list {
  margin: 0 0 12px;
}
.client-list-toolbar fieldset {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 0;
  border: 0;
}
.invoice-list-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 20px 0 12px;
}
.invoice-list-toolbar fieldset {
  margin: 0;
  padding: 0;
  border: 0;
}
.client-list-toolbar label,
.task-admin-toolbar label,
.invoice-list-toolbar label {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ez-muted);
  font-size: 13px;
}
.client-list-toolbar input,
.task-admin-toolbar input,
.invoice-list-toolbar input,
.project-list-toolbar input {
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.task-admin-empty {
  padding: 28px 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  color: var(--ez-muted);
  text-align: center;
}
.task-admin-form-body {
  display: grid;
  gap: 14px;
}
.task-admin-form-body > label:not(.task-admin-check) {
  display: grid;
  gap: 6px;
}
.task-admin-form-body input:not([type='checkbox']) {
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.task-admin-check {
  display: flex;
  align-items: center;
  min-height: 44px;
  gap: 10px;
}
.task-admin-check input {
  width: 20px;
  height: 20px;
}
.task-admin-confirm-actions {
  justify-content: flex-end;
}
.expense-category-filter button[aria-pressed='true'] {
  border-color: var(--ez-data);
  background: var(--ez-status-bg);
}
.expense-category-empty {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
}
.expense-category-list-actions {
  margin-top: 14px;
}
.expense-category-dialog {
  width: min(580px, calc(100vw - 32px));
}
.expense-category-dialog > form {
  padding: 22px;
}
.expense-category-dialog-actions {
  grid-column: 1 / -1;
  flex-wrap: wrap;
}
@media (max-width: 720px) {
  /*
   * These two toolbars were a single flex row all the way down to 390px, which
   * held only while the status filter was two buttons wide. A third one tips
   * them over, and the thing that overflows is the search field: it is an input
   * inside a label, so the `min-width: 0` sweep above -- which reaches a
   * toolbar's children and a fieldset's -- never touches it, and an input keeps
   * its intrinsic size and pushes the page sideways instead of narrowing.
   *
   * Stacking rather than wrapping, because that is what the neighbours already
   * do at this width: .project-list-toolbar becomes a one-column grid and
   * .team-list-toolbar a column of full-width controls. These two were simply
   * the pair that had no narrow-width treatment at all.
   */
  .client-list-toolbar,
  .task-admin-toolbar {
    align-items: stretch;
    flex-direction: column;
  }
  .client-list-toolbar fieldset,
  .client-list-toolbar label,
  .client-list-toolbar input,
  .task-admin-toolbar fieldset,
  .task-admin-toolbar label,
  .task-admin-toolbar input {
    width: 100%;
    min-width: 0;
  }
  .auth-gateway {
    min-height: 100svh;
    grid-template-columns: 1fr;
    grid-template-rows: minmax(220px, 34svh) 1fr;
    overflow: visible;
  }
  .auth-splash {
    min-height: 220px;
    padding: 24px;
  }
  .auth-splash::before {
    background-size: 48px 48px;
  }
  .auth-splash::after {
    width: 220px;
    right: -104px;
    bottom: -124px;
    border-width: 40px;
  }
  .auth-splash-copy {
    width: min(290px, 82%);
    margin: 32px 0 0;
  }
  .auth-splash-copy h1 {
    margin: 8px 0 0;
    font-size: clamp(38px, 12vw, 52px);
  }
  .auth-splash-copy > p:last-child,
  .auth-splash-foot {
    display: none;
  }
  .auth-entry {
    align-content: start;
    gap: 16px;
    padding: 24px 16px;
  }
  .auth-card {
    min-height: 0;
    padding: 24px;
    border-radius: var(--ez-radius);
    box-shadow: none;
  }
  .auth-heading h2 {
    font-size: 28px;
  }
  .topbar {
    min-height: 52px;
    padding: 0 12px;
    gap: 6px;
  }
  .primary-nav,
  .command-trigger {
    display: none;
  }
  .menu-trigger {
    display: block;
  }
  .brand {
    margin-right: 0;
  }
  /* It takes the width the row has left rather than its content's: the bar
     wraps before anything shrinks, so at 375px a chip sized to its label pushed
     Menu onto a line of its own. The label is what gives, and it ellipsizes. */
  .timer-chip {
    flex: 1 1 0;
    min-width: 0;
    margin-left: auto;
    max-width: min(56vw, 250px);
  }
  .timer-chip [data-timer-label] {
    max-width: 96px;
  }
  .tabstrip {
    padding: 0 16px;
  }
  .app-content {
    padding: 20px 16px 36px;
  }
  .context-row {
    align-items: center;
  }
  .reports-workspace .context-row {
    align-items: stretch;
    flex-direction: column;
  }
  .reports-workspace .context-row .report-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
  }
  .report-filters,
  .report-rollup-comparison {
    grid-template-columns: 1fr;
  }
  .report-filters button {
    width: 100%;
  }
  /* One column here, so a two-column span would invent a second one and push
     the card off the side of the phone. The arrows stay square: the rule above
     stretches the card's own buttons, and a full-width chevron is not a
     stepper. */
  .report-period-field {
    grid-column: auto;
  }
  .report-filters .period-step {
    width: 44px;
  }
  .report-result-heading,
  .report-currency-card > header,
  .report-budget-card > header,
  .report-client-node > header {
    align-items: flex-start;
    flex-direction: column;
  }
  .report-client-node {
    margin-left: min(calc(var(--report-depth, 0) * 12px), 8%);
  }
  .report-metrics {
    padding: 12px;
  }
  .client-header-actions {
    align-items: flex-end;
    flex-direction: column;
  }
  .client-detail,
  .client-facts dl,
  .client-form-pair,
  .client-defaults {
    grid-template-columns: 1fr;
  }
  .client-facts {
    grid-row: auto;
  }
  .project-header-actions {
    align-items: flex-end;
    flex-direction: column;
  }
  .project-list-toolbar,
  .project-detail,
  .project-facts dl,
  .project-form-pair {
    grid-template-columns: 1fr;
  }
  .project-list-toolbar {
    display: grid;
    align-items: stretch;
  }
  .project-list-toolbar label,
  .project-list-toolbar select {
    width: 100%;
  }
  .project-facts {
    grid-row: auto;
  }
  .expense-form,
  .expense-filter-form,
  .expense-detail,
  .expense-state-facts {
    grid-template-columns: 1fr;
  }
  /* One column here, so a two-column span would invent a second one and push
     the card off the side of the phone. */
  .expense-filter-period {
    grid-column: auto;
  }
  .expense-filter-actions,
  .expense-attachments li {
    align-items: stretch;
    flex-direction: column;
  }
  .expense-filter-actions button {
    flex: 1;
  }
  .expense-list-row > div:last-child {
    justify-items: start;
  }
  .expense-category-header,
  .expense-category-directory-header,
  .expense-category-empty {
    align-items: stretch;
    flex-direction: column;
  }
  .expense-category-form,
  .expense-category-unit-fields {
    grid-template-columns: 1fr;
  }
  .expense-category-filter,
  .expense-category-filter button,
  .expense-category-list-actions,
  .expense-category-list-actions button,
  .expense-category-dialog-actions,
  .expense-category-dialog-actions button {
    width: 100%;
  }
  .expense-category-filter button,
  .expense-category-list-actions button,
  .expense-category-dialog-actions button {
    flex: 1;
  }
  .project-task-card,
  .project-attachments li {
    align-items: stretch;
    flex-direction: column;
  }
  .project-task-card > div:last-child,
  .project-attachments form {
    align-items: stretch;
    flex-direction: column;
  }
  .task-admin-header {
    align-items: flex-start;
  }
  .task-admin-workspace [data-task-create],
  .task-admin-workspace [data-task-load-more],
  .task-admin-workspace [data-task-list-retry] {
    width: 100%;
  }
  .week-surface {
    margin-inline: -16px;
    border-inline: 0;
    border-radius: 0;
  }
  .week-toolbar {
    align-items: flex-start;
    flex-wrap: wrap;
    padding: 12px 16px;
  }
  /* Its own row on a phone. Sharing the line with the actions leaves the
     summary about twenty pixels between two 44px arrows and a 220px action
     column, which is a period control that cannot say which period it is on. */
  .week-period {
    flex-basis: 100%;
  }
  .timesheet-status {
    align-items: stretch;
    flex-direction: column;
    padding: 14px 16px;
  }
  .week-actions {
    max-width: 220px;
  }
  .week-actions button,
  .day-switcher button {
    min-height: 44px;
  }
  .week-actions [data-week-total] {
    width: 100%;
    padding: 4px 0 0;
    text-align: right;
  }
  .week-grid-wrap {
    display: none;
  }
  .day-list {
    display: block;
  }
  .day-row .week-cell {
    min-height: 58px;
  }
  dialog {
    width: calc(100vw / var(--ez-zoom, 1));
    max-width: none;
    margin: auto 0 0;
    border-radius: var(--ez-radius) var(--ez-radius) 0 0;
  }
  .timer-actions {
    flex-direction: column;
  }
  .entry-assignment,
  .entry-times {
    grid-template-columns: 1fr;
  }
  .build-stamp {
    padding-inline: 16px;
  }
}

/*
 * The data table. One system, nine sets of columns — see
 * screens/HRVST10/OLD-UI-ANALYSIS.md §2 and §6. No zebra: hairline rules and a
 * hover layer carry the row, which is what let the old UI fit a working week on
 * one screen without it feeling dense.
 */
.data-table-scroll {
  position: relative;
  max-width: 100%;
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.data-table thead th {
  height: var(--ez-row-h);
  padding: 0 12px;
  background: var(--ez-surface);
  border-bottom: 1px solid var(--ez-border);
  color: var(--ez-muted);
  font-size: 13px;
  font-weight: 400;
  text-align: left;
  white-space: nowrap;
}

.data-table tbody td {
  height: var(--ez-row-h);
  max-width: 0;
  padding: 0 12px;
  overflow: hidden;
  border-bottom: 1px solid var(--ez-border);
  color: var(--ez-ink);
  text-overflow: ellipsis;
  /* One row is one line. A free-text column — an expense note, an invoice
     subject — wrapped instead, pushing rows past --ez-row-h and losing the
     density the row height exists to buy. max-width:0 lets the column widths
     come from the table's own layout rather than from the longest note. */
  white-space: nowrap;
}

.data-table-sort {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-height: var(--ez-row-h);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}

.data-table-sort:hover { text-decoration: underline; }
.data-table-sort:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
.data-table .is-numeric .data-table-sort { justify-content: flex-end; }

/* The row's own actions are the exception: the overflow menu is a popup inside
   this cell, and clipping the cell clips the menu. */
.data-table tbody td.data-table-actions {
  max-width: none;
  overflow: visible;
}

/* An icon standing in for a word inside a cell rides the text baseline, which
   leaves it sitting low against the words beside it. */
.data-table tbody td .ez-icon {
  vertical-align: -5px;
}

.data-table .is-numeric {
  text-align: right;
}

/* Grouping is a band, not an indent — the label owns the full width. */
/*
 * A band inside the list, not the list's own header -- which is why it takes
 * the nested surface. On `--ez-surface` it was the same grey as `thead th`
 * directly above it, so a grouped table read as one undifferentiated band and
 * the grouping did no work.
 */
.data-table .data-table-group th {
  height: var(--ez-row-h);
  padding: 0 12px;
  background: var(--ez-surface-2);
  border-bottom: 1px solid var(--ez-border);
  color: var(--ez-ink);
  font-size: 13px;
  font-weight: 600;
  text-align: left;
}

/* The hover tint the old roster used, sampled from it rather than guessed
   (issue 292). It is pointer-and-focus only: no row state -- overdue, locked,
   selected, billable -- is expressed as a row background anywhere, which is why
   the absence of print-color-adjust in this stylesheet costs nothing here. A
   row fill that encoded meaning would be a bug, because it would vanish on
   paper. */
.data-table tbody tr[data-row]:hover,
.data-table tbody tr[data-row]:focus-within {
  background: var(--ez-row-hover);
}

/* A group's total closes its run: same weight as the footer, but it belongs to
   the band above it rather than to the table. */
.data-table tbody tr.data-table-group-total td {
  border-bottom: 1px solid var(--ez-border);
  font-weight: 700;
}

/* Totals sit apart by weight, not by a rule above them. */
.data-table tfoot td {
  height: var(--ez-row-h-lead);
  padding: 0 12px;
  color: var(--ez-ink);
  font-weight: 600;
}

.data-table-empty {
  color: var(--ez-muted);
}

.data-table-actions {
  text-align: right;
  white-space: nowrap;
  width: 1%;
}

/*
 * Actions are present but quiet until the row is under the pointer, and always
 * present to the keyboard — :focus-within is why they cannot be display:none.
 */
.data-table-action,
.data-table-overflow > summary {
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: var(--ez-radius);
  background: none;
  color: var(--ez-data);
  cursor: pointer;
  font: inherit;
  opacity: 0;
}

.data-table tbody tr[data-row]:hover .data-table-action,
.data-table tbody tr[data-row]:hover .data-table-overflow > summary,
.data-table tbody tr[data-row]:focus-within .data-table-action,
.data-table tbody tr[data-row]:focus-within .data-table-overflow > summary,
.data-table-action:focus-visible,
.data-table-overflow[open] > summary {
  opacity: 1;
}

.data-table-action:hover,
.data-table-overflow > summary:hover {
  border-color: var(--ez-border);
}

.data-table-overflow {
  display: inline-block;
  position: relative;
}

.data-table-overflow > summary {
  list-style: none;
}

.data-table-overflow > summary::-webkit-details-marker {
  display: none;
}

.data-table-menu {
  position: absolute;
  right: 0;
  z-index: 2;
  min-width: 160px;
  padding: 4px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  box-shadow: var(--ez-shadow-menu);
  text-align: left;
}

.data-table-menu button {
  display: block;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: calc(var(--ez-radius) - 2px);
  background: none;
  color: var(--ez-ink);
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.data-table-menu button:hover,
.data-table-menu button:focus-visible {
  background: var(--ez-surface);
}

@media (prefers-reduced-motion: no-preference) {
  .data-table-action,
  .data-table-overflow > summary {
    transition: opacity 120ms ease;
  }
}

/* The home screen. Cards, not a report: each one is a single figure, the one
   line that says whether it needs anything, and the way into the section that
   owns it. */
.dashboard-workspace {
  width: 100%;
}

.dashboard-status {
  min-height: 24px;
  margin-block: 16px;
}

/* `minmax(min(100%, 220px), 1fr)` rather than `minmax(220px, 1fr)`: a grid
   track with a fixed floor is the same defect as a flex item keeping
   min-width auto -- at 390px the row stops shrinking and widens the page
   instead. */
.dashboard-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: 16px;
}

.dashboard-card {
  display: grid;
  align-content: start;
  gap: 6px;
  min-width: 0;
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}

.dashboard-card h2 {
  margin: 0;
  color: var(--ez-muted);
  font: var(--ez-label-weight) var(--ez-label-size) / 1.3 var(--ez-font-body), sans-serif;
  letter-spacing: var(--ez-label-tracking);
  text-transform: uppercase;
}

/* The same KPI numeral the team summary reads by: at body size these are four
   labels you have to stop and parse. */
.dashboard-figure {
  overflow-wrap: anywhere;
  font: 600 clamp(28px, 3.2vw, 46px)/1.05 var(--ez-font-body), sans-serif;
  font-variant-numeric: tabular-nums;
}

.dashboard-detail {
  margin: 0;
  color: var(--ez-muted);
}

.dashboard-currencies {
  margin: 0;
  padding: 0;
  list-style: none;
  font-variant-numeric: tabular-nums;
}

/* What needs doing is said in words. Nothing here is carried by a fill, which
   is what stops it from disappearing when the page is printed. */
.dashboard-note {
  margin: 0;
}

.dashboard-note[data-tone='action'] {
  color: var(--ez-red);
}

.dashboard-card-link {
  justify-self: start;
  margin-top: 6px;
}

/*
 * Paper.
 *
 * Cmd-P on an invoice printed the application. The top bar came out as a white
 * band -- it is white text on --ez-band, and a browser omits background-color
 * when "Background graphics" is off, which is the default -- followed by the
 * primary nav, the section tabs, "Loading invoice…", every control the operator
 * uses to change the invoice, the internal message history, and the build
 * stamp, wrapped around a document with no print rules of its own. A client who
 * asked for something to file got the screenshot of an app.
 *
 * What is dropped is what belongs to the application rather than to the
 * invoice: the chrome, the controls, the status lines, the attachment surface,
 * and the send/reminder history, which names internal senders and recipients
 * and is nobody's copy of the bill. What is kept is the document: who it is
 * for, what the lines are, what is owed, the notes, and the payments already
 * applied to it.
 *
 * Nothing here restores a background fill. As with the recipient pill on the
 * contact sheet, anything whose meaning would live in one carries it in ink
 * instead: the state pill trades its fill for a border, so it reads the same
 * whether or not the person printing has background graphics switched on.
 */
@media print {
  @page {
    margin: 16mm;
  }
  /* Application chrome, on every screen: none of it is the page. Print is one
     of the detailed report's two exports, so its controls belong here with the
     rest of the chrome rather than in a print block of their own -- the invoice
     test lifts the FIRST `@media print` it finds as the paper stylesheet, so a
     second block earlier in the file silently became the whole of it. */
  .report-detailed-controls,
  .report-entry-actions,
  .topbar,
  .tabstrip,
  .build-stamp,
  .data-quality,
  .session-check-overlay,
  .write-status,
  dialog {
    display: none !important;
  }
  [data-ez-nav='sidebar'] .authenticated-shell {
    padding-left: 0 !important;
  }
  /*
   * The invoice's own working surfaces. The document is what is being printed;
   * the controls that change it, the states of those controls, and the history
   * of who was sent what are the application around it.
   */
  [data-invoice-detail-page] .context-row,
  .invoice-page-status,
  .invoice-load-more,
  .invoice-reminder-line,
  .invoice-document-actions button,
  .invoice-document-actions .invoice-edit-link,
  .invoice-overflow,
  .invoice-line-editor,
  .invoice-line-actions,
  .invoice-payment-heading button,
  .invoice-payment-actions,
  .invoice-attachment-section,
  .invoice-history:not(.invoice-payment-history):not(.invoice-attachment-section),
  .invoice-workspace .form-result,
  .invoice-workspace .hint,
  .invoice-line-table thead th:last-child {
    display: none !important;
  }
  .app-content {
    padding: 0;
  }
  /* The sheet is the border; the card's own is chrome, and its shadow is ink
     spent on nothing. */
  .invoice-document {
    padding: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  .invoice-document-heading {
    flex-direction: row;
  }
  /*
   * A printed page is about 695px wide at these margins, which is inside the
   * 720px phone breakpoint, so the rules that turn the line table into stacked
   * cards would otherwise apply to paper. An invoice is a table on paper.
   */
  .invoice-line-wrap {
    overflow-x: visible;
  }
  .invoice-line-table {
    min-width: 0;
  }
  .invoice-line-table thead {
    display: table-header-group;
  }
  .invoice-line-table tbody {
    display: table-row-group;
  }
  .invoice-line-table tbody tr {
    display: table-row;
    border: 0;
    /* A line and its amount belong on the same sheet. */
    break-inside: avoid;
  }
  .invoice-line-table tbody th,
  .invoice-line-table tbody td {
    border-bottom: 1px solid var(--ez-border);
    text-align: right;
  }
  .invoice-line-table tbody th {
    text-align: left;
  }
  .invoice-line-table td[data-label]::before {
    content: none;
  }
  .invoice-totals,
  .invoice-notes {
    break-inside: avoid;
  }
  /*
   * The state is the one thing on the document whose meaning was carried by a
   * fill, and a fill is exactly what does not print. It becomes a bordered word
   * in the muted ink it already uses, which clears 4.5:1 on the sheet.
   */
  .invoice-state {
    border: 1px solid var(--ez-status-fg);
    background: transparent;
    color: var(--ez-status-fg);
  }
  .invoice-state[data-tone] {
    background: transparent;
  }
}

/*
 * Retainers.
 *
 * The facts grids and the ledger table are the two things the screen is, and
 * both have to survive a 390px viewport and a mono-colour print. The columns
 * use `minmax(min(180px, 100%), 1fr)` rather than a bare 180px floor: a bare
 * floor is a track that refuses to shrink, which is the same widen-the-page
 * failure `min-width: auto` produces on a flex item and which
 * expectNoPageOverflow catches. Nothing here carries meaning in colour alone —
 * an overdrawn balance reads by its minus sign and by the note beneath the
 * grid, both of which survive losing the ink.
 */
.retainer-toolbar,
.retainer-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.retainer-toolbar {
  margin: 24px 0 12px;
}
.retainer-toolbar > *,
.retainer-actions > *,
.retainer-detail-heading > * {
  min-width: 0;
}
.retainer-toolbar button {
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
/* The same pressed affordance the Week/Day segmented control already ships:
   the selected tint under a blue border, ink text on top of it. */
.retainer-toolbar button[aria-pressed='true'] {
  border-color: var(--ez-data);
  background: var(--ez-orange-tint);
}
.retainer-detail-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.retainer-detail-back {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.retainer-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: 12px 24px;
  margin: 24px 0 0;
  padding: 24px 0;
  border-top: 1px solid var(--ez-border);
}
.retainer-facts div {
  display: grid;
  gap: 4px;
}
.retainer-facts dt {
  color: var(--ez-muted);
  font-size: 12px;
}
.retainer-facts dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}
.retainer-policy dd {
  font-variant-numeric: normal;
}
.retainer-notes {
  margin: 0;
  padding: 12px 16px 12px 32px;
  background: var(--ez-surface);
  color: var(--ez-ink);
}
.retainer-notes li + li {
  margin-top: 8px;
}
.retainer-ledger {
  margin-top: 24px;
}
.retainer-ledger .hint {
  max-width: 64ch;
}
/* The status segmented control and the New retainer button share a line until
   they cannot: `wrap` rather than a breakpoint, because the button's label
   translates and a fixed width for it would be a guess. */
.retainer-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}
.retainer-controls > * {
  min-width: 0;
}
.retainer-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.retainer-dialog-actions button {
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
/* The projected balance is the one number in a write dialog the operator is
   deciding on, so it reads at the ledger's own tabular alignment rather than as
   running prose. */
.retainer-projection {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

/* Recurring. It carries the retainer pane's furniture — the two sit under the
   same strip and a person moves between them without a reload, so a second set
   of spacing rules would read as two screens borrowed from two products. The
   only thing of its own is the due pill, which has to be legible without the
   colour: the word is 'Past due', not a red dot. */
.recurring-toolbar,
.recurring-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.recurring-toolbar {
  margin: 24px 0 12px;
}
.recurring-toolbar > *,
.recurring-actions > *,
.recurring-detail-heading > * {
  min-width: 0;
}
.recurring-toolbar input[type='search'] {
  flex: 1 1 320px;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font: inherit;
}
.recurring-detail-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.recurring-detail-back {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.recurring-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: 12px 24px;
  margin: 24px 0 0;
  padding: 24px 0;
  border-top: 1px solid var(--ez-border);
}
.recurring-facts div {
  display: grid;
  gap: 4px;
}
.recurring-facts dt {
  color: var(--ez-muted);
  font-size: 12px;
}
.recurring-facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}
.recurring-issue,
.recurring-config,
.recurring-templates {
  margin-top: 24px;
}
.recurring-issue .hint,
.recurring-config .hint {
  max-width: 64ch;
}
.recurring-issue .primary-action {
  margin-top: 12px;
}
/* Three tones, because the three answers are three different things. Issued is
   done; not-due and already-issued are the definition working as instructed and
   must not read as failures; only the last is an error. Each is carried by the
   sentence as well as the colour. */
[data-recurring-issue-result][data-outcome='issued'] {
  color: var(--ez-ink);
}
[data-recurring-issue-result][data-outcome='not_due'],
[data-recurring-issue-result][data-outcome='already_generated'] {
  color: var(--ez-muted);
}
[data-recurring-issue-result][data-outcome='error'],
[data-recurring-issue-result][data-outcome='unavailable'] {
  color: var(--ez-red);
}
.recurring-issued-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
/* Past due is an invitation, not an alarm: it is the row you came to act on.
   So it is marked in the warning amber rather than in the error red, and the
   word carries the meaning either way. */
.invoice-state[data-recurring-due='overdue'] {
  border-color: var(--ez-amber);
  background: var(--ez-amber-bg);
}
.recurring-confirm-dialog::backdrop,
.report-confirm-dialog::backdrop {
  background: color-mix(in srgb, var(--ez-shadow) 45%, transparent);
}
.recurring-confirm-dialog form,
.report-confirm-dialog form {
  display: grid;
  gap: 16px;
  min-width: min(420px, 90vw);
}
.recurring-confirm-dialog header,
.report-confirm-dialog header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.recurring-confirm-dialog footer,
.report-confirm-dialog footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.recurring-confirm-dialog button,
.report-confirm-dialog button {
  min-height: 44px;
  padding: 8px 16px;
}
/* Edit and Delete on a Detailed time row, for an administrator (issue 917). */
.report-entry-actions {
  white-space: nowrap;
}
.report-entry-actions button + button {
  margin-left: 6px;
}
.recurring-detail-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.recurring-detail-actions button {
  min-height: 44px;
  padding: 8px 16px;
}
/* The links that open the recurring editor read as the buttons they replaced:
   New is the page's primary action, Edit sits with Delete. */
.recurring-new-link,
.recurring-edit-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  text-decoration: none;
}
.recurring-edit-link {
  padding: 8px 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
[data-recurring-delete-result][data-outcome='error'],
:is([data-recurring-editor-result], [data-invoice-edit-result])[data-outcome='error'] {
  color: var(--ez-red);
}
[data-recurring-delete-result][data-outcome='pending'],
:is([data-recurring-editor-result], [data-invoice-edit-result])[data-outcome='pending'] {
  color: var(--ez-muted);
}

/* A definition's standing files. One row on the detail view and in the editor
   -- the file, its size and which invoices it goes with -- and the editor's
   controls wrap beneath it rather than beside it, so a phone gets a column and
   nothing passes the edge. */
.recurring-files {
  margin-top: 24px;
}
.recurring-files .hint {
  max-width: 64ch;
}
.recurring-attachment-list {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.recurring-attachment {
  display: grid;
  gap: 8px;
  min-width: 0;
  padding: 12px 18px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.recurring-attachment-file {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  min-width: 0;
}
.recurring-attachment-file a {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--ez-ink);
  font-weight: 700;
  text-underline-offset: 3px;
}
.recurring-attachment-file span,
.recurring-attachment-through {
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.recurring-attachment-controls,
.recurring-attachment-upload {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 8px 12px;
  min-width: 0;
}
.recurring-attachment-controls label,
.recurring-attachment-upload label {
  flex: 1 1 200px;
}
.recurring-attachment-empty {
  color: var(--ez-muted);
  font-size: 14px;
}

/*
 * Full-page editors: the recurring definition and the invoice.
 *
 * Both were dialogs, and both outgrew one -- the recurring editor's line table
 * scrolled sideways inside it and clipped its own labels at the left edge, and
 * the invoice's fields were stacked into a column too narrow to read a line
 * item in. A page gives each section the width it needs.
 *
 * One centred column on the settings measure, sections divided by rules as
 * settings sections are, every control 44px tall, and the actions in a band
 * held at the foot of the window so Save is never below the fold of a long
 * invoice -- the frame the invoice dialogs already used, at page scale.
 */
/* The settings measure, held narrower: a form page, where a wide window would
   otherwise stretch a date field across a third of it. */
.page--settings.editor-page {
  width: min(1180px, 100%);
}
.editor-page-heading > div {
  min-width: 0;
}
.editor-back {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  white-space: nowrap;
}
.editor-form {
  display: grid;
  min-width: 0;
}
.editor-section {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: 24px 0 26px;
  border-top: 1px solid var(--ez-border);
}
.editor-section > h2 {
  margin: 0;
  font:
    600 17px/1.3 var(--ez-font-display),
    sans-serif;
}
.editor-section .hint {
  max-width: 76ch;
  font-size: 13px;
  line-height: 1.5;
}
.editor-subsection {
  display: grid;
  gap: 14px;
  min-width: 0;
}
.editor-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: start;
  gap: 16px 20px;
  min-width: 0;
}
.editor-span-2 {
  grid-column: span 2;
}
.editor-span-all {
  grid-column: 1 / -1;
}
/* A field with a hint: the hint describes the control rather than naming it,
   so it sits beside the label instead of inside it. */
.editor-field {
  display: grid;
  align-content: start;
  gap: 6px;
  min-width: 0;
}
.editor-form label,
.editor-fact {
  display: grid;
  gap: 6px;
  min-width: 0;
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.editor-form input:not([type='checkbox']):not([type='radio']),
.editor-form select,
.editor-form textarea {
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
  color: var(--ez-ink);
  font: inherit;
  font-size: 15px;
}
.editor-form select[multiple] {
  padding: 4px;
}
.editor-form select[multiple] option {
  padding: 6px 8px;
  border-radius: calc(var(--ez-radius) - 2px);
}
.editor-form textarea {
  line-height: 1.45;
  resize: vertical;
}
.editor-form .field-hint {
  color: var(--ez-muted);
  font-size: 12px;
  line-height: 1.45;
}
/* Read, not edited: the field's shape so it lines up with the ones beside it,
   on the second surface so it does not invite a click. */
.editor-fact strong {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface-2);
  color: var(--ez-ink);
  font-size: 15px;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.editor-form label.editor-check {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  color: var(--ez-ink);
  font-size: 14px;
  cursor: pointer;
}
.editor-grid > .editor-check {
  align-self: end;
}
.editor-form .editor-check input,
.editor-form .editor-choice-card input {
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--ez-action);
}
/* A choice between two kinds of thing reads as two cards, as New invoice's
   Type does, rather than as a select that hides the second answer. */
.editor-choice {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.editor-choice legend {
  margin-bottom: 8px;
  padding: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.editor-form .editor-choice-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--ez-border);
  border-radius: calc(var(--ez-radius) * 1.5);
  background: var(--ez-ground);
  color: var(--ez-ink);
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}
.editor-form .editor-choice-card input {
  margin-top: 1px;
}
.editor-choice-card:hover {
  border-color: color-mix(in srgb, var(--ez-action) 45%, var(--ez-border));
}
.editor-choice-card:has(input:checked) {
  border-color: var(--ez-action);
  background: color-mix(in srgb, var(--ez-action) 6%, var(--ez-ground));
  box-shadow: 0 0 0 1px var(--ez-action);
}
.editor-choice-card:has(input:focus-visible) {
  outline: 2px solid var(--ez-action);
  outline-offset: 2px;
}
.editor-choice-card span {
  display: grid;
  gap: 3px;
}
.editor-choice-card strong {
  font-size: 14px;
}
.editor-choice-card small {
  color: var(--ez-muted);
  font-size: 12px;
  line-height: 1.4;
}
.editor-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font: inherit;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.editor-secondary:hover {
  background: var(--ez-surface-2);
}
.editor-form .primary-action {
  min-height: 44px;
  padding: 0 20px;
}
.editor-form button:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}
/* The band Save lives in, held at the foot of the window while the form
   scrolls under it, and slightly wider than the column so it reads as the
   page's footing rather than one more section. */
.editor-actions {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px 12px;
  margin: 0 -20px;
  padding: 14px 20px;
  border-top: 1px solid var(--ez-border);
  background: var(--ez-surface);
  box-shadow: 0 -10px 24px -18px color-mix(in srgb, var(--ez-shadow) 45%, transparent);
}
.editor-actions .form-result {
  flex: 1 1 280px;
  min-width: 0;
  font-size: 14px;
}
.editor-actions .form-result:empty {
  display: none;
}

/*
 * The line table. Both editors carry one, and it is the part the dialog could
 * not hold: nine fields a row on the recurring definition, seven on an invoice.
 *
 * On a wide window it is a table -- a numbered row per line, the description
 * taking the width that is left, the figures in narrow right-hand columns and
 * the line's project and taxes on a second row beneath its description, where
 * Harvest kept the linked project. Below that it becomes a card per line with
 * every field labelled, and nothing ever scrolls sideways.
 */
.line-editor {
  --line-columns: 28px minmax(112px, 150px) minmax(0, 1fr) 92px 132px 116px 44px;
  display: grid;
  min-width: 0;
}
.line-editor-head {
  display: none;
}
.line-editor-rows {
  display: grid;
  gap: 12px;
  min-width: 0;
}
.line-editor-row {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-areas:
    'kind description description description'
    'quantity price amount remove'
    'meta meta meta meta'
    'removed removed removed removed';
  align-items: start;
  gap: 12px 16px;
  min-width: 0;
  margin: 0;
  padding: 14px 16px 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
}
.line-editor-row > legend {
  padding: 0 6px;
  color: var(--ez-muted);
  font:
    500 12px/1.4 var(--ez-font-body),
    sans-serif;
}
.line-editor-number {
  display: none;
  grid-area: number;
}
.line-editor-kind {
  grid-area: kind;
}
.line-editor-description {
  grid-area: description;
}
.line-editor-description textarea {
  min-height: 72px;
}
.line-editor-quantity {
  grid-area: quantity;
}
.line-editor-price {
  grid-area: price;
}
.line-editor-quantity input,
.line-editor-price input {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.line-editor-amount {
  display: grid;
  grid-area: amount;
  gap: 6px;
  min-width: 0;
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
}
.line-editor-amount strong {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: 44px;
  color: var(--ez-ink);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.line-editor-remove {
  display: inline-flex;
  grid-area: remove;
  align-items: center;
  justify-content: center;
  gap: 6px;
  align-self: end;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
.line-editor-remove > span[aria-hidden='true'] {
  font-size: 20px;
  line-height: 1;
}
.line-editor-remove:hover {
  border-color: var(--ez-red);
  color: var(--ez-red);
}
.line-editor-meta {
  display: grid;
  grid-area: meta;
  /* The date keeps the width its digits need; below it a date field clips. */
  grid-template-columns: minmax(0, 2fr) minmax(9rem, 1fr) minmax(0, 1fr) auto auto;
  align-items: end;
  gap: 12px 16px;
  min-width: 0;
}
/* An invoice line's second row is the linked project and the two taxes, the
   project held to a select's width rather than the whole row's, as Harvest
   kept it under the description. */
.invoice-line-meta {
  grid-template-columns: minmax(0, 30rem) auto auto;
  justify-content: start;
}
.line-editor-meta label:not(.editor-check) {
  font-size: 12px;
}
/* Marked to go, and still on the page until it saves: dimmed, dashed, and
   saying so beside the one control that keeps it. */
.line-editor-row[data-removed] {
  border-style: dashed;
  background: var(--ez-surface);
}
.line-editor-row[data-removed] > :not(legend):not(.line-editor-removed) {
  opacity: 0.5;
}
.line-editor-row[data-removed] textarea {
  text-decoration: line-through;
}
.line-editor-removed {
  display: flex;
  flex-wrap: wrap;
  grid-area: removed;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  margin: 0;
  color: var(--ez-ink);
  font-size: 14px;
}
.line-editor-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.line-editor-total {
  margin: 0;
  color: var(--ez-muted);
  font-size: 14px;
}
.line-editor-total strong {
  margin-left: 8px;
  color: var(--ez-ink);
  font-size: 17px;
  font-variant-numeric: tabular-nums;
}
@media (min-width: 960px) {
  .line-editor-head {
    display: grid;
    grid-template-columns: var(--line-columns);
    gap: 12px;
    padding: 0 12px 8px;
    border-bottom: 1px solid var(--ez-border);
    color: var(--ez-muted);
    font-size: 12px;
    font-weight: 500;
  }
  .line-editor-numeric {
    text-align: right;
  }
  .line-editor-rows {
    gap: 0;
  }
  .line-editor-row {
    grid-template-columns: var(--line-columns);
    grid-template-areas:
      'number kind description quantity price amount remove'
      '. . meta meta meta meta .'
      '. . removed removed removed removed .';
    gap: 8px 12px;
    padding: 12px;
    border: 0;
    border-bottom: 1px solid var(--ez-border);
    border-radius: 0;
    background: transparent;
  }
  .line-editor-row[data-removed] {
    border-style: solid;
    border-bottom-style: dashed;
  }
  /* The column headings name these, so each control's own label is kept for
     a screen reader and taken off the row. */
  .line-editor-row > legend,
  .line-editor-label,
  .line-editor-remove-text {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .line-editor-number {
    display: flex;
    align-items: center;
    min-height: 44px;
    color: var(--ez-muted);
    font:
      500 12px/1 var(--ez-font-body),
      sans-serif;
  }
  .line-editor-amount {
    gap: 0;
  }
  .line-editor-remove {
    align-self: start;
    width: 44px;
    padding: 0;
  }
}
@media (max-width: 720px) {
  .editor-page-heading {
    align-items: flex-start;
    flex-direction: column;
    gap: 8px;
  }
  .editor-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px 12px;
  }
  .editor-span-2,
  .editor-span-all {
    grid-column: 1 / -1;
  }
  .editor-choice {
    grid-template-columns: minmax(0, 1fr);
  }
  .line-editor-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-areas:
      'kind kind'
      'description description'
      'quantity price'
      'amount amount'
      'meta meta'
      'remove remove'
      'removed removed';
    gap: 12px;
    padding: 12px 12px 14px;
  }
  .line-editor-amount {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }
  .line-editor-amount strong {
    min-height: 0;
  }
  .line-editor-meta,
  .invoice-line-meta {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
  }
  .line-editor-project {
    grid-column: 1 / -1;
  }
  .line-editor-foot {
    align-items: stretch;
    flex-direction: column;
  }
  .editor-actions {
    margin: 0 -16px;
    padding: 12px 16px;
  }
  .editor-actions > a,
  .editor-actions > button {
    flex: 1 1 0;
  }
}

/* Invoice configuration. Two sections on one page, because they answer one
   question between them — what a client sees when an invoice arrives — and an
   operator checking the address is usually about to check the wording. */
.email-senders,
.email-templates {
  margin-top: 32px;
}
.email-senders .hint,
.email-templates .hint {
  max-width: 72ch;
}
.email-config-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}
.email-config-actions > * {
  min-width: 0;
}
.email-config-actions button {
  min-height: 44px;
  padding: 8px 16px;
}
/* The one thing this screen can say that a bounce cannot: mail is about to go
   out as an address the provider will refuse. It is lifted out of the table
   because it is about the default, and the default is the one in use. */
.email-sender-warning {
  padding: 12px 16px;
  border: 1px solid var(--ez-red);
  border-radius: var(--ez-radius);
  color: var(--ez-red);
}
/* Verified takes no colour. It is the state every sender is supposed to be in,
   and marking the ordinary case is what leaves an operator scanning for which
   green means which. Only the two that need attention are marked. */
.invoice-state[data-sender-evidence='pending'] {
  border-color: var(--ez-amber);
  background: var(--ez-amber-bg);
}
.invoice-state[data-sender-evidence='failed'],
.invoice-state[data-sender-evidence='unknown'] {
  border-color: var(--ez-red);
  color: var(--ez-red);
}
.email-template-editor {
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--ez-border);
}
.email-template-editor-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.email-template-close {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.email-template-editor form {
  display: grid;
  gap: 16px;
  margin-top: 16px;
}
.email-template-editor label {
  display: grid;
  gap: 6px;
}
.email-template-editor input,
.email-template-editor textarea {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font: inherit;
}
.email-template-editor textarea {
  font: 13px/1.6 var(--ez-font-mono), monospace;
  resize: vertical;
}
/* A token this template has no variable for. Not an error yet — it is a draft —
   so it is a warning in the amber the rest of the shell uses for the same. */
.email-template-unknown {
  padding: 8px 12px;
  border: 1px solid var(--ez-amber);
  border-radius: var(--ez-radius);
  background: var(--ez-amber-bg);
}
[data-template-result][data-outcome='saved'] {
  color: var(--ez-ink);
}
[data-template-result][data-outcome='stale'] {
  color: var(--ez-amber);
}
[data-template-result][data-outcome='invalid'],
[data-template-result][data-outcome='error'] {
  color: var(--ez-red);
}
.email-template-variables,
.email-template-history {
  margin-top: 24px;
}

/* Backups. The alarm is the section's answer, not a row to be found in the
   table beneath it — so it sits above the facts and takes the red the rest of
   the shell uses for a thing that has actually gone wrong. */
.settings-backup-alarm {
  padding: 12px 16px;
  border: 1px solid var(--ez-red);
  border-radius: var(--ez-radius);
  color: var(--ez-red);
}
.invoice-state[data-backup-status='failed'] {
  border-color: var(--ez-red);
  color: var(--ez-red);
}
.invoice-state[data-backup-status='running'] {
  border-color: var(--ez-amber);
  background: var(--ez-amber-bg);
}

/* Row density. The segmented pair the retainer status filter already uses --
   one control, one pressed state, no third paint to learn. */
.settings-density {
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--ez-border);
}
.settings-density .hint {
  max-width: 64ch;
}
.settings-density-choice {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}
.settings-density-choice button {
  min-height: 44px;
  padding: 8px 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font: inherit;
  cursor: pointer;
}
.settings-density-choice button[aria-pressed='true'] {
  border-color: var(--ez-data);
  background: var(--ez-orange-tint);
}

/* The calendar. Height is minutes: a two-hour block is twice a one-hour block
   in either mode, and only where the column starts differs between them. */
.calendar-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}
.calendar-table caption {
  padding: 0 0 10px;
  color: var(--ez-muted);
  font-size: 13px;
  text-align: left;
}
.calendar-table th,
.calendar-table td {
  padding: 6px;
  border: 1px solid var(--ez-border);
  vertical-align: top;
  min-width: 0;
}
.calendar-column {
  position: relative;
  min-height: 120px;
}
.calendar-block {
  position: absolute;
  top: calc(var(--calendar-offset) * 0.5px);
  left: 0;
  right: 0;
  /* A floor, so a six-minute entry is still a target you can hit rather than a
     hairline nobody can click. */
  min-height: 18px;
  height: calc(var(--calendar-minutes) * 0.5px);
  padding: 2px 4px;
  overflow: hidden;
  border-radius: var(--ez-radius);
  background: var(--ez-status-bg);
  color: var(--ez-status-fg);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.calendar-untimed,
.calendar-total {
  margin: 6px 0 0;
  color: var(--ez-muted);
  font-size: 12px;
}
.calendar-total {
  font-weight: 600;
}

/* Issue 757. The organization timezone is its own setting rather than a field on
   the lock-policy form, and a person can set their own from the global menu. */
.org-timezone-form .field-hint,
.org-timezone-form .form-result {
  grid-column: 1 / -1;
  margin: 0;
}

/* The Menu's footer (issue 859). A band on the surface tint with the field and
   its button on one row and the hint under both, so it reads as the dialog's
   foot rather than as more list. The label is the one thing that names the
   field: the eyebrow that repeated it is gone. Explicit rows, because the hint
   sits between the field and the button in the markup and auto-placement
   would put the button under it. */
.profile-timezone-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 8px 12px;
  padding: 16px 20px 20px;
  border-top: 1px solid var(--ez-border);
  background: var(--ez-surface);
}

.profile-timezone-form label {
  display: grid;
  grid-area: 1 / 1;
  gap: 6px;
}

.profile-timezone-form select {
  background: var(--ez-ground);
}

.profile-timezone-form button {
  grid-area: 1 / 2;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
  font-weight: 500;
  white-space: nowrap;
}

.profile-timezone-form .field-hint,
.profile-timezone-form .form-result {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--ez-muted);
  font-size: 13px;
}

.profile-timezone-form .field-hint {
  grid-row: 2;
}

.profile-timezone-form .form-result {
  grid-row: 3;
}

@media (max-width: 720px) {
  /* One column: the field, its hint, then the button at full width. */
  .profile-timezone-form {
    grid-template-columns: minmax(0, 1fr);
  }
  .profile-timezone-form button {
    grid-area: 3 / 1;
  }
  .profile-timezone-form .form-result {
    grid-row: 4;
  }
}

.profile-timezone-form .form-result:empty,
.org-timezone-form .form-result:empty {
  display: none;
}

/* Staff payment register: amounts stay aligned with the shared table. */
.staff-payment-toolbar { display: flex; flex-wrap: wrap; align-items: end; gap: 12px; margin: 24px 0; }
.staff-payment-toolbar label, .staff-payment-person form > label, .staff-payment-opening form > label { display: grid; gap: 6px; }
.staff-payment-toolbar label.payment-check { display: flex; align-items: center; gap: 8px; min-height: 44px; }
.staff-payments input:not([type='checkbox']), .staff-payments select, .staff-payments textarea, .staff-payments button { min-height: 44px; padding: 8px 12px; border: 1px solid var(--ez-border); border-radius: var(--ez-radius); background: var(--ez-ground); color: var(--ez-ink); font: inherit; }
.staff-payment-summary { padding: 12px 16px; background: var(--ez-status-bg); margin-bottom: 20px; }
.staff-payment-person, .staff-payment-opening { margin-top: 28px; border-top: 1px solid var(--ez-border); padding-top: 20px; }
.staff-payment-person form, .staff-payment-opening form { display: grid; gap: 12px; max-width: 640px; margin: 20px 0; }
.staff-payment-person header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; }
[data-payment-opening-people] { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; }
[data-payment-opening-people] label { display: flex; align-items: center; gap: 8px; }
.staff-payments [hidden] { display: none !important; }
.staff-payments .data-table { min-width: 1000px; table-layout: auto; }
.staff-payments .data-table tbody td { max-width: none; padding-top: 6px; padding-bottom: 6px; }
.staff-payments .data-table [data-column='status'] { min-width: 180px; }
.staff-payments .data-table [data-column='due'] { min-width: 105px; }
.staff-payments .data-table [data-column='note'] { min-width: 260px; max-width: 440px; white-space: normal; overflow-wrap: anywhere; }

/* Every granted scope must be readable without a token detail screen. */
[data-settings-tokens-table] .data-table [data-column='scopes'] {
  white-space: normal;
  overflow-wrap: anywhere;
}

.payment-runs { margin-block: 1.5rem; }
.payment-runs > section, .payment-runs [data-run-history] > section { border: 1px solid var(--ez-border); border-radius: var(--ez-radius); padding: 1rem; margin-block: 1rem; }
.payment-runs .data-table input:not([type='checkbox']) { width: 10rem; }
.payment-runs [data-run-timezone] { align-self: end; padding: 12px; }
[data-payments-report] .staff-payment-toolbar { flex-wrap: wrap; }

/* Payments: one task per panel, with a keyboard-accessible horizontal tab bar. */
.payment-tabs { display: flex; gap: 8px; overflow-x: auto; border-bottom: 1px solid var(--ez-border); scrollbar-width: thin; }
.staff-payments .payment-tabs button { flex: 0 0 auto; border: 0; border-bottom: 3px solid transparent; border-radius: 0; background: transparent; color: var(--ez-muted); }
.staff-payments .payment-tabs button[aria-selected='true'] { border-bottom-color: var(--ez-action); color: var(--ez-ink); font-weight: 700; }
.staff-payments [role='tabpanel'] { padding-block: 20px; }
.staff-payments .primary-action { background: var(--ez-action); color: var(--ez-action-fg); }
