/* Generated by scripts/compile-theme-tokens.mjs. Do not edit. */
:root, [data-ez-theme="precision"] {
  --ez-ground: #FFFFFF;
  --ez-surface: #F5F6F7;
  --ez-border: #E3E5E8;
  --ez-ink: #14161A;
  --ez-muted: #676C74;
  --ez-action: #16794A;
  --ez-action-fg: #FFFFFF;
  --ez-live: #E8590C;
  --ez-data: #2F5AE0;
  --ez-money: #FDF7EE;
  --ez-status-bg: #EDF1F5;
  --ez-status-fg: #676C74;
  --ez-ink-2: #3A3D42;
  --ez-orange-tint: #FDEDE3;
  --ez-blue-light: #A8BCF5;
  --ez-red: #C92A2A;
  --ez-amber: #F1B34A;
  --ez-amber-bg: #FEF6E4;
  --ez-live-text: #BA470A;
  --ez-surface-2: #EBEDEF;
  --ez-row-hover: #E1ECFB;
  --ez-font-display: "IBM Plex Sans";
  --ez-font-body: "IBM Plex Sans";
  --ez-font-mono: "IBM Plex Mono";
  --ez-radius: 6px;
}
* {
  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;
}

/*
 * 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;
}
[data-text-size='large'] {
  zoom: 1.125;
}
[data-text-size='larger'] {
  zoom: 1.25;
}

/*
 * 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)` monospace, 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-mono), monospace;
  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));
  max-height: calc(100vh - 32px);
  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-mono), monospace;
}
.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-mono), monospace;
}
.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: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.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);
}
.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-mono), monospace;
  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-live);
}

/* 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: 11px;
  font-weight: 700;
  text-align: left;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.report-time-table tfoot th,
.report-time-table tfoot td {
  font-weight: 700;
}

.invoice-generation {
  width: min(880px, 100%);
  margin-inline: auto;
}
.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;
}
.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-mono), monospace;
  text-transform: uppercase;
}
.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-ink) 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 {
  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);
}
.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: 0 14px 36px color-mix(in srgb, var(--ez-ink) 14%, transparent);
}
/* 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 button,
.invoice-line-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-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;
}
.invoice-line-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
}
.invoice-line-actions button {
  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-mono), monospace;
  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 {
  display: grid;
  gap: 18px;
  margin-top: 24px;
}
.invoice-generation-form input:not([type='checkbox']),
.invoice-generation-form select,
.invoice-generation-form button {
  min-height: 44px;
}
.invoice-generation-form input:not([type='checkbox']),
.invoice-generation-form select {
  width: 100%;
}
.invoice-generation-form fieldset,
.invoice-generation-success {
  display: grid;
  gap: 14px;
  margin: 0;
  padding: 20px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-surface);
}
.invoice-payment-amount-fields {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(110px, 0.35fr);
  gap: 12px;
}
.invoice-line-dialog {
  width: min(680px, calc(100vw - 32px));
}
.invoice-line-number-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.invoice-line-tax-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin: 0;
  padding: 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.invoice-line-tax-fields legend {
  padding-inline: 4px;
  color: var(--ez-muted);
  font-size: 13px;
}
.invoice-line-tax-fields label {
  display: flex;
  align-items: center;
  min-height: 44px;
  gap: 8px;
}
.invoice-line-tax-fields input {
  width: auto;
  min-height: auto;
}
.invoice-line-preview {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  margin: 0;
  padding: 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  font-variant-numeric: tabular-nums;
}
.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));
}
.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;
}
.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;
}
.invoice-generation-form legend {
  padding-inline: 6px;
  font-weight: 700;
}
/* One control now, where two labelled date fields used to sit side by side.
   The control lays out its own row and its own custom pair. */
.invoice-period {
  display: grid;
  min-width: 0;
}
.invoice-projects {
  display: grid;
  gap: 8px;
}
.invoice-projects label {
  display: grid;
  grid-template-columns: auto 1fr;
  justify-content: start;
  align-items: center;
  min-height: 44px;
}
.invoice-projects input {
  width: auto;
}
.invoice-generation-success {
  margin-top: 24px;
  border-color: var(--ez-action);
}

.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-mono), monospace;
  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 dark action
 * colour, 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 action ink 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-action) 10%, var(--ez-ground));
  color: var(--ez-action);
}
.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-mono), monospace;
  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-live);
}
.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-mono), monospace;
  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. `live` gives white only 3.58:1; `live-text` is the
   same hue stepped down until it clears, at 5.26:1. The destructive weight here
   is carried by the fill being the loudest thing on the row, not by the fill
   being the brightest orange available. */
.danger-action {
  border-color: var(--ez-live-text) !important;
  background: var(--ez-live-text) !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-mono), monospace;
  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-mono), monospace;
  font-size: 12px;
}
.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);
  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);
  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);
  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;
  }
  .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 button,
  .invoice-payment-dialog-actions button,
  .retainer-dialog-actions button {
    width: 100%;
  }
  .invoice-payment-amount-fields {
    grid-template-columns: 1fr;
  }
  .invoice-line-number-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 {
    justify-content: stretch;
    padding-top: 0 !important;
  }
  .invoice-line-actions button {
    flex: 1;
    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: 100vh;
  background: var(--ez-ground);
}
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-ink);
  color: var(--ez-ground);
}
.brand {
  display: grid;
  place-items: center;
  color: var(--ez-ground);
  font:
    600 20px/1 var(--ez-font-mono),
    monospace;
  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-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-ground);
  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;
}
.timer-chip [data-timer-label] {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.timer-chip [data-timer-elapsed] {
  font-family: var(--ez-font-mono), monospace;
  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-mono), monospace;
}
/*
 * 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'] {
  /* The border carries the live hue, which is certified as an indicator at 3:1.
     The label cannot: `live` fails AA as text, and `live_text` is the variant
     that exists for exactly the two places the colour has to be read rather
     than noticed. */
  border-color: var(--ez-live);
  color: var(--ez-live-text);
}
/*
 * 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-live);
}
/*
 * 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, orange tint and an orange 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 orange one. */
  position: relative;
  background: var(--ez-orange-tint);
  border-color: var(--ez-live);
  box-shadow: none;
}
.app-content {
  padding: 28px 32px 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: clamp(32px, 4vw, 40px);
  line-height: 1.1;
}
/* 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:
    500 13px/1.4 var(--ez-font-mono),
    monospace;
}
.primary-action {
  min-height: 36px;
  padding: 0 16px;
  border: 1px solid var(--ez-action);
  border-radius: var(--ez-radius);
  background: var(--ez-action);
  color: var(--ez-action-fg);
  font-weight: 600;
}
.authenticated-shell[hidden],
.auth-gateway[hidden],
.session-check-overlay[hidden] {
  display: none;
}
.authenticated-shell {
  min-height: 100vh;
}
.session-check-overlay {
  position: fixed;
  z-index: 1000;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 24px;
  background: color-mix(in srgb, var(--ez-ink) 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-ink) 24%, transparent);
  color: var(--ez-ink);
}
.session-check-card p {
  margin: 0;
  font: 500 14px/1.4 var(--ez-font-mono), monospace;
}
.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: 100vh;
  min-height: 100svh;
  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-ink);
  color: var(--ez-ground);
}
.auth-splash::before {
  content: '';
  position: absolute;
  z-index: -2;
  inset: 0;
  opacity: 0.16;
  background-image:
    linear-gradient(to right, var(--ez-ground) 1px, transparent 1px),
    linear-gradient(to bottom, var(--ez-ground) 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-live);
  border-radius: 50%;
  opacity: 0.9;
}
.auth-wordmark {
  width: fit-content;
  position: relative;
  color: var(--ez-ground);
  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-live);
}
.auth-splash-copy h1 {
  max-width: 9ch;
  margin: 12px 0 20px;
  color: var(--ez-ground);
  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-ground) 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-ground) 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-ink) 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-action);
  color: var(--ez-action);
}
.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;
}
.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);
}
.sign-in-form label {
  display: grid;
  gap: 6px;
  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-ink, so --ez-muted would be unreadable directly against it. */
.account {
  align-self: center;
}
.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;
  text-transform: capitalize;
}
.identity-meta {
  color: var(--ez-muted);
  font:
    500 12px/1.3 var(--ez-font-mono),
    monospace;
}
/* 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-action);
  color: var(--ez-action);
}

.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-ink);
}
.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);
}

/* 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);
}

.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 a top bar whose contents come
 * to 1325px laid out on one row. Everything from 721px to 1324px, which is to
 * say most laptops and every tablet in landscape, drew the account on top of
 * itself and pushed the page into a sideways scroll.
 *
 * Two steps close it, and the widths are what the parts measure rather than
 * round numbers:
 *
 *   >= 1340px  everything, as designed (needs 1325)
 *   1140-1339  the full nav on tighter padding; the search chip goes and the
 *              account keeps only its avatar (1075)
 *   721-1139   the nav folds into the Menu drawer that phone width already
 *              uses -- same links, same dialog (639)
 *
 * Each band has room to spare at the width it starts at, so the bar is never
 * asked to lay out in less space than it needs. The account gives up its name
 * below 1340 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: 1339px) {
  .command-trigger {
    display: none;
  }
  .identity-text {
    display: none;
  }
}
@media (min-width: 721px) and (max-width: 1139px) {
  .primary-nav {
    display: none;
  }
  .menu-trigger {
    display: block;
  }
}
@media (min-width: 1140px) and (max-width: 1339px) {
  .primary-nav a {
    padding: 0 10px;
  }
}

/* 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;
  }
}
.primary-action:disabled,
button[data-auth-action]:disabled {
  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);
}
.week-surface {
  margin-inline: -32px;
  border: 1px solid var(--ez-border);
  border-inline: 0;
  border-radius: var(--ez-radius);
  overflow: hidden;
}
.week-toolbar {
  min-height: 64px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 10px 32px;
  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 32px;
  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 to be read, not a state marker, and it
   sits on the status band's tint. `live` clears only the 3:1 non-text bar
   against that tint, so the reading colour is `live-text`: the same hue taken
   down to where it clears the 4.5:1 body-text bar. */
.timesheet-status [data-timesheet-rejection-reason] {
  color: var(--ez-live-text);
  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-mono), monospace;
  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);
  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-live);
  outline-offset: -2px;
}

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

.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-mono), monospace;
  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-action);
  color: var(--ez-action);
}

/* 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-action);
  color: var(--ez-action);
}

@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 32px;
  background: var(--ez-ground);
  text-align: left;
}
.week-grid-table thead th:first-child {
  z-index: 2;
  background: var(--ez-surface);
}
.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-mono), monospace;
  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 32px;
  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-live);
}

.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-mono), monospace;
  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-live);
}
/* 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);
}
.week-hint {
  margin: 0 0 12px;
  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;
}
.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-mono),
    monospace;
  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-live-text);
}
.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));
  padding: 0;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
  background: var(--ez-ground);
  color: var(--ez-ink);
}
dialog::backdrop {
  background: color-mix(in srgb, var(--ez-ink) 72%, transparent);
}
dialog form {
  display: grid;
  gap: 14px;
  padding: 20px;
}
dialog header {
  display: flex;
  justify-content: space-between;
  gap: 20px;
}
dialog header button {
  border: 0;
  background: transparent;
  color: var(--ez-muted);
  font-size: 24px;
}
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: 10vh auto;
    max-height: calc(90vh - 24px);
  }
}
/*
 * 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: 6px 12px 2px;
  font-size: 12px;
}
.command-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 40px;
  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 tint the tables give the row under the
 * pointer, 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-row-hover);
  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,
.command-option[aria-selected='true']::after {
  flex: none;
  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;
}
.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 nav {
  display: grid;
  gap: 2px;
  padding: 8px;
}
.menu-dialog nav + nav {
  border-top: 1px solid var(--ez-border);
}
.menu-dialog nav a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  border-radius: var(--ez-radius);
  color: var(--ez-ink);
  font-weight: 500;
  text-decoration: none;
}
.menu-dialog nav a:hover,
.menu-dialog nav a:focus-visible {
  background: var(--ez-surface-2);
}
.menu-dialog nav a[aria-current="page"] {
  background: var(--ez-status-bg);
  box-shadow: inset 3px 0 0 var(--ez-live);
  font-weight: 600;
}
.document-shell {
  min-height: 100vh;
  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-live);
  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;
  }
  .timer-chip {
    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;
  }
  .context-row h1 {
    font-size: 32px;
  }
  .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: 100vw;
    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;
}

/* 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: 0 8px 24px rgb(20 22 26 / 12%);
  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: 600 12px/1.3 var(--ez-font-body), sans-serif;
  letter-spacing: 0.04em;
  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-mono), monospace;
  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-ink, 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,
  .topbar,
  .tabstrip,
  .build-stamp,
  .data-quality,
  .session-check-overlay,
  dialog {
    display: none !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-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);
  }
}

/*
 * 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:
   orange-tint fill under an orange border, ink text on top of it. */
.retainer-toolbar button[aria-pressed='true'] {
  border-color: var(--ez-live);
  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 by the live accent the timer uses rather than by an error
   colour, and the word carries the meaning either way. */
.invoice-state[data-recurring-due='overdue'] {
  border-color: var(--ez-live);
  background: var(--ez-orange-tint);
}
.recurring-confirm-dialog::backdrop {
  background: color-mix(in srgb, var(--ez-ink) 45%, transparent);
}
.recurring-confirm-dialog form {
  display: grid;
  gap: 16px;
  min-width: min(420px, 90vw);
}
.recurring-confirm-dialog header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.recurring-confirm-dialog footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.recurring-confirm-dialog button {
  min-height: 44px;
  padding: 8px 16px;
}
.recurring-detail-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.recurring-detail-actions button {
  min-height: 44px;
  padding: 8px 16px;
}
/* The editor. A definition is one instruction with a schedule attached, so the
   scheduling half sits on a grid that collapses to one column and the wording
   half runs full width underneath it — the fields a person reads across are the
   ones they set together. */
.recurring-editor-dialog::backdrop {
  background: color-mix(in srgb, var(--ez-ink) 45%, transparent);
}
.recurring-editor-form {
  display: grid;
  gap: 16px;
  width: min(760px, 92vw);
  max-height: 80vh;
  overflow-y: auto;
}
.recurring-editor-form header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.recurring-editor-form footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.recurring-editor-form label {
  display: grid;
  gap: 4px;
  font-size: 12px;
  color: var(--ez-muted);
}
.recurring-editor-form input,
.recurring-editor-form select,
.recurring-editor-form textarea,
.recurring-editor-form 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;
}
.recurring-editor-form textarea {
  min-height: 72px;
}
.recurring-editor-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: 12px 16px;
}
/* A checkbox is not a field with a value below its name, so it opts out of the
   label grid rather than sitting in a 44px-tall box of its own. */
.recurring-editor-form label.recurring-editor-check {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ez-ink);
  font-size: inherit;
}
.recurring-editor-form .recurring-editor-check input {
  min-height: 0;
  padding: 0;
}
.recurring-editor-line {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: 12px 16px;
  margin: 0 0 12px;
  padding: 12px;
  border: 1px solid var(--ez-border);
  border-radius: var(--ez-radius);
}
.recurring-editor-line legend {
  padding: 0 4px;
  color: var(--ez-muted);
  font-size: 12px;
}
.recurring-editor-lines .hint,
.recurring-editor-import .hint {
  max-width: 72ch;
}
[data-recurring-editor-result][data-outcome='error'],
[data-recurring-delete-result][data-outcome='error'] {
  color: var(--ez-red);
}
[data-recurring-editor-result][data-outcome='pending'],
[data-recurring-delete-result][data-outcome='pending'] {
  color: var(--ez-muted);
}

/* 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-live);
  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;
}
