/* Kwusen Asana time reporting.
 *
 * The visual model is an accounting worksheet, because that is what the
 * numbers are for: billing a contract and defending the figure. Hence banded
 * rows, figures on a shared decimal axis, and a double rule under a final
 * total — the accounting convention for a footed column.
 *
 * Colour is drawn from ledger paper rather than from a dashboard palette:
 * ink blue on warm paper, pale green-grey rules, and red reserved strictly
 * for variance. Red never means "error styling"; it means the numbers do not
 * agree, which is the one thing a reader must never miss.
 */

:root {
  --ink: #1b2a32;
  --ink-soft: #46595f;
  --muted: #6b7b80;
  --paper: #fbfbf7;
  --surface: #ffffff;
  --rule: #d7ded9;
  --rule-strong: #9fb0aa;
  --band: #f1f5f0;
  --tied: #1f6f5c;
  --variance: #a6321e;
  --focus: #2f6f8f;
  /* The cell being examined, in highlighter amber. Orange is the one colour on
   * this paper that is neither ink nor variance red, so it can be loud without
   * being read as a number that disagrees — a marker stroke, not a finding. */
  --open: #ffbf5c;
  --open-edge: #b35c0a;

  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  --gap: 1rem;
  --radius: 3px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e6ece9;
    --ink-soft: #b3c1bd;
    --muted: #8b9a9c;
    --paper: #12191c;
    --surface: #182126;
    --rule: #2c3b40;
    --rule-strong: #4d6167;
    --band: #1c262a;
    --tied: #5fbfa3;
    --variance: #e08268;
    --focus: #79b6d6;
    --open: #8a4b0f;
    --open-edge: #e79a45;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
}

/* Every figure in the interface sits on a shared decimal axis. Tabular
 * numerals are not decoration here — a column of costs that does not align
 * cannot be scanned. */
.num, td.num, th.num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

a { color: var(--focus); }
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

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

.masthead {
  display: flex;
  align-items: baseline;
  gap: var(--gap);
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--rule);
  background: var(--surface);
}
.masthead h1 {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 650;
  letter-spacing: 0.02em;
}
.masthead .who { margin-left: auto; color: var(--muted); font-size: 0.85rem; }

.shell {
  display: grid;
  grid-template-columns: minmax(230px, 300px) 1fr;
  gap: 0;
  min-height: calc(100vh - 49px);
}
.rail {
  border-right: 1px solid var(--rule);
  padding: 1.25rem;
  background: var(--surface);
}
.main { padding: 1.25rem 1.5rem 4rem; min-width: 0; }

@media (max-width: 860px) {
  .shell { grid-template-columns: 1fr; }
  .rail { border-right: 0; border-bottom: 1px solid var(--rule); }
}

/* ---- forms ---- */

label.field { display: block; font-size: 0.8rem; color: var(--muted); margin-bottom: 0.3rem; }

input[type="search"], input[type="date"], select {
  width: 100%;
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
}
input[type="date"] { font-family: var(--mono); font-size: 0.85rem; }

.controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: end;
  margin: 0 0 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--rule);
}
.controls > div { min-width: 8.5rem; }

button, .btn {
  font: inherit;
  padding: 0.45rem 0.8rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
button:hover, .btn:hover { border-color: var(--ink-soft); }

/* ---- project rail ---- */

.results { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.results li { border-bottom: 1px solid var(--rule); }
.results a {
  display: block;
  padding: 0.4rem 0.2rem;
  color: var(--ink);
  text-decoration: none;
}
.results a:hover, .results a:focus { background: var(--band); }
.results .team { display: block; font-size: 0.78rem; color: var(--muted); }
.results mark { background: #ffe9a8; color: inherit; }
@media (prefers-color-scheme: dark) { .results mark { background: #6a5a1f; } }

/* The rail's other entry points, set apart from the typeahead above by a rule
 * rather than by spacing alone: they answer a different question, not a
 * narrower version of the same one. */

.rail-nav {
  margin-top: 1.25rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
}
.rail-nav a {
  display: block;
  padding: 0.4rem 0.5rem;
  margin: 0 -0.5rem;
  border-radius: var(--radius);
  color: var(--ink);
  text-decoration: none;
}
.rail-nav a:hover, .rail-nav a:focus { background: var(--band); }
.rail-nav a[aria-current="page"] {
  background: var(--band);
  box-shadow: inset 2px 0 0 var(--ink);
}
.rail-nav .team { display: block; font-size: 0.78rem; color: var(--muted); }

.hint { color: var(--muted); font-size: 0.85rem; }

/* ---- reconciliation strip: the signature element ----
 *
 * Computed total, Asana's own rollup and the budget share one baseline, so
 * the eye compares them without moving. The rule beneath carries the verdict:
 * the figures tie out, or they do not. */

.recon {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
  align-items: baseline;
  padding: 0.85rem 0 0.7rem;
  border-bottom: 3px double var(--rule-strong);
  margin-bottom: 1.1rem;
}
.recon .figure { display: flex; flex-direction: column; gap: 0.15rem; }
.recon .figure .k {
  font-size: 0.72rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}
.recon .figure .v { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 1.35rem; }
.recon .verdict { margin-left: auto; align-self: center; font-size: 0.85rem; }
.recon .verdict .mark { font-family: var(--mono); font-size: 1.05rem; margin-right: 0.35rem; }
.tied { color: var(--tied); }
.variance { color: var(--variance); }

.project-title { margin: 0 0 0.15rem; font-size: 1.3rem; font-weight: 650; letter-spacing: -0.01em; }
.project-meta { margin: 0 0 0.6rem; color: var(--muted); font-size: 0.85rem; }

/* ---- notices ---- */

.notice {
  border-left: 3px solid var(--rule-strong);
  padding: 0.55rem 0.75rem;
  margin: 0 0 1rem;
  background: var(--band);
  font-size: 0.88rem;
}
.notice.warn { border-left-color: var(--variance); }
.notice p { margin: 0.2rem 0; }

/* ---- the worksheet ----
 *
 * The sheet is sized to its contents rather than to the pane. A pivot of two
 * columns should read as a narrow table, not as a pair of figures flung to
 * opposite edges of the screen with a gulf between a label and its number —
 * stretching it that way is what makes a worksheet hard to scan.
 *
 * A wide pivot therefore overflows instead of compressing, and .tablewrap
 * scrolls it sideways. The row labels and the header row stay pinned while it
 * moves, so a figure in the far right column can still be read off against the
 * row and column it belongs to. */

.tablewrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
}

table.sheet { border-collapse: collapse; width: auto; font-size: 0.85rem; }

/* Compact, and no wrapping: a cell that wraps costs a row of height and puts
 * the figures on different baselines, which defeats scanning a column. Long
 * labels widen the table and it scrolls, rather than reflowing it. */
table.sheet th, table.sheet td {
  padding: 0.28rem 0.6rem;
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}

table.sheet thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  border-bottom: 1px solid var(--rule-strong);
  text-align: right;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
  font-weight: 600;
}

table.sheet tbody tr:nth-child(even) { background: var(--band); }
table.sheet tbody th {
  text-align: left;
  font-weight: 500;
  background: inherit;
  /* A task name can run to a paragraph. Past this the label is clipped rather
   * than allowed to push every figure off the screen; the full text stays
   * available as the cell's tooltip. */
  max-width: 24rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
table.sheet tbody tr:nth-child(odd) th { background: var(--surface); }

/* The label column stays put under horizontal scroll, so the row a figure
 * belongs to is never off-screen. The rule between it and the figures is drawn
 * as a shadow because a collapsed border is painted by the table, not the
 * cell, and so does not travel with a sticky column. */
table.sheet thead th:first-child,
table.sheet tbody th,
table.sheet tfoot th {
  position: sticky;
  left: 0;
  text-align: left;
  box-shadow: 1px 0 0 var(--rule);
}
/* The header corner overlaps both a sticky row and a sticky column, so it sits
 * above everything else. The footer corner does too, but its z-index is set
 * with the rest of the footer below, after the footer's own rule. */
table.sheet thead th:first-child { z-index: 3; }
table.sheet tbody th { z-index: 1; }

/* Both kinds of link in the sheet are marked the same way — a dotted rule, the
 * colour of the surrounding ink — because both are the same offer: there is
 * more behind this. A figure opens its entries below; a row label opens the
 * task or the person in Asana. Neither is loud enough to compete with the
 * numbers, which are what the table is for. */
table.sheet td a,
table.sheet th a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted var(--rule-strong);
}
table.sheet td a:hover,
table.sheet th a:hover { border-bottom-color: var(--focus); color: var(--focus); }

/* The footed total: a double rule, as an accountant would draw it. */
table.sheet tfoot th, table.sheet tfoot td {
  border-top: 3px double var(--rule-strong);
  border-bottom: 0;
  font-weight: 650;
  background: var(--surface);
  position: sticky;
  bottom: 0;
  z-index: 2;
}
table.sheet tfoot th { z-index: 3; }
/* The table closes in both quantities, a column each, so the totals band is
 * where the report stops being in one measure. A rule down its left edge says
 * where that happens — the figures to the right of it are not more of the same
 * column, they are the row answered a second way. */
table.sheet .rowtotal { font-weight: 650; }
table.sheet tbody .rowtotal:first-of-type,
table.sheet tfoot .rowtotal:first-of-type,
table.sheet thead .rowtotal:first-of-type {
  border-left: 1px solid var(--rule-strong);
}
table.sheet td.empty { color: var(--rule-strong); }

/* The cell whose entries are listed below, struck through with a marker the way
 * a reader checking a figure against its backup would mark it. The detail can
 * sit a long way down the page — or arrive with a shared link, before anyone
 * has clicked anything — so this has to be findable at a glance across a table
 * of a few hundred figures, not merely visible once you know where to look.
 *
 * Drawn as an inset shadow rather than a border because a border would shift
 * the collapsed grid by a pixel each time the mark moved from cell to cell. */
table.sheet tbody td.open,
table.sheet tfoot td.open {
  background: var(--open);
  box-shadow: inset 0 0 0 2px var(--open-edge);
}
table.sheet tbody td.open a,
table.sheet tfoot td.open a {
  color: var(--ink);
  font-weight: 700;
  border-bottom-color: transparent;
}

/* The row label is marked too, in the same amber. A pivot can be wider than the
 * screen, and the cell itself scrolls away; the label column does not. Without
 * this a reader who has scrolled sideways is looking at a list of entries with
 * nothing on screen saying whose they are. */
table.sheet tbody tr.openrow th {
  box-shadow: 1px 0 0 var(--rule), inset 4px 0 0 var(--open-edge);
  font-weight: 650;
}

/* ---- attribution ----
 *
 * Offered only when the totals disagree, and answered only when asked, because
 * the answer costs one request per person in the workspace. It sits directly
 * under the strip that raised the doubt. */

button.link {
  border: 0;
  background: none;
  padding: 0;
  margin-left: 0.5rem;
  font: inherit;
  font-size: 0.85rem;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
button.link:hover { text-decoration-thickness: 2px; }

.attribution {
  margin: 0 0 1.1rem;
  padding: 0.8rem 1rem 0.9rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
}
.attribution h3 {
  margin: 0 0 0.15rem;
  font-size: 0.95rem;
  font-weight: 650;
}
.attribution h3 .mark { font-family: var(--mono); margin-right: 0.35rem; }
.attribution .sub { color: var(--muted); font-size: 0.85rem; margin: 0 0 0.6rem; }
.attribution .sub.variance { color: var(--variance); margin-top: 0.6rem; }
.attribution table { margin-top: 0.4rem; }

/* ---- non-billable time ---- */

.unbillable { margin: 2rem 0 0; }
.unbillable h3 { margin: 0 0 0.15rem; font-size: 0.95rem; font-weight: 650; }
.unbillable .sub { color: var(--muted); font-size: 0.85rem; margin: 0 0 0.6rem; }

/* ---- people with no rate ----
 *
 * A missing rate is the one gap the cost column cannot show. The hours are
 * present, the money is simply absent, and no figure above says whose. So the
 * people are named, with the time each of them accounts for, in red — this is
 * precisely the case red is reserved for here: the numbers do not agree with
 * the work until somebody sets these rates. */

.unpriced {
  margin: 1.5rem 0 0;
  padding: 0.8rem 1rem 0.9rem;
  border: 1px solid var(--variance);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--surface);
}
.unpriced h3 {
  margin: 0 0 0.15rem;
  font-size: 0.95rem;
  font-weight: 650;
  color: var(--variance);
}
.unpriced h3 .mark { font-family: var(--mono); margin-right: 0.35rem; }
.unpriced .sub { margin: 0 0 0.6rem; color: var(--muted); font-size: 0.85rem; }

/* Name, hours, entry count on one baseline, so the list can be read down the
 * hours column to find the rate worth setting first. */
.unpriced ul { list-style: none; margin: 0; padding: 0; max-width: 34rem; }
.unpriced li {
  display: grid;
  grid-template-columns: 1fr auto 6rem;
  gap: 0 1rem;
  align-items: baseline;
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--rule);
}
.unpriced li:last-child { border-bottom: 0; }
.unpriced .who { font-weight: 600; }
.unpriced .num { color: var(--variance); }
.unpriced li .hint { text-align: right; }

/* ---- drill-down ---- */

/* The same amber as the cell above it, so the two read as one thing across
 * whatever distance the worksheet puts between them. */
.detail { margin-top: 1.25rem; border-top: 2px solid var(--open-edge); padding-top: 1rem; }
.detail h3 { margin: 0 0 0.15rem; font-size: 1rem; }
.detail .sub { color: var(--muted); font-size: 0.85rem; margin: 0 0 0.6rem; }
.entries { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.entries th, .entries td { text-align: left; padding: 0.35rem 0.6rem; border-bottom: 1px solid var(--rule); }
.entries th { font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.entries td.num, .entries th.num { text-align: right; }

/* Striped by day, not by row. A day's entries share a band and the next day's
 * take the other, so the eye finds where one day ends without reading the
 * dates. The rule above each day's first row is what carries that when the two
 * bands are hard to tell apart — a printed timesheet, or a screen in bright
 * sun. The date itself is printed once per band, on that first row. */
.entries tbody tr.band { background: var(--band); }
.entries tbody tr.dayfirst td { border-top: 1px solid var(--rule-strong); }
.entries tbody tr.dayfirst:first-child td { border-top: 0; }
.entries td.day { white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Footed like the worksheet, with the same double rule, so the listing visibly
 * adds up to the figure that opened it. */
.entries tfoot th, .entries tfoot td {
  border-top: 3px double var(--rule-strong);
  border-bottom: 0;
  font-weight: 650;
}

/* The one control that spends a request per person in the workspace, so it is
 * the one that looks like a commitment rather than a preference. */
button.go {
  border-color: var(--ink-soft);
  font-weight: 600;
  padding-inline: 1.4rem;
}
button.go:hover { background: var(--band); }

/* ---- waiting for a report ----
 *
 * A report is seconds of paced Asana requests, and the staff sweep is one
 * request per person in the workspace. What fills that time has to say it is
 * working: a page that has gone quiet and a page that has stopped look the
 * same. The bar carries no percentage because there is no honest one — how long
 * Asana will take is Asana's to know. */

.loading {
  max-width: 46rem;
  padding: 2rem 0 0;
}
.loading strong { display: block; font-size: 1.05rem; margin-bottom: 0.3rem; }
.loading .hint { margin: 0 0 1.1rem; line-height: 1.5; }
.loading .bar {
  height: 3px;
  border-radius: 2px;
  background: var(--rule);
  overflow: hidden;
}
/* Full width by default, so that a reader who has asked for no motion still
 * meets a bar rather than an empty track. The animation overrides it. */
.loading .bar span {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--ink-soft);
  animation: kar-sweep 1.5s ease-in-out infinite;
}
@keyframes kar-sweep {
  0%   { transform: translateX(-100%) scaleX(0.4); }
  50%  { transform: translateX(0) scaleX(0.6); }
  100% { transform: translateX(100%) scaleX(0.4); }
}

/* ---- feedback ---- */

.htmx-indicator { opacity: 0; }
.htmx-request .htmx-indicator { opacity: 1; }

/* A refetch leaves the previous figures on screen, which is right — they are
 * still the last true answer — but they must not look live while a new one is
 * in flight. Dimmed and inert, so a stale cell cannot be clicked and the wait
 * is visible where the reader is looking rather than only in the corner. */
#report.htmx-request {
  opacity: 0.4;
  pointer-events: none;
}

/* The staff sweep's waiting block. Display rather than opacity, so it takes no
 * room until there is something to wait for — and outside #report, so what it
 * says stays legible while the figures behind it are dimmed. */
#busy {
  display: none;
  max-width: 46rem;
  padding: 0.4rem 0 1.2rem;
}
#busy.htmx-request { display: block; }
#busy strong { display: block; margin-bottom: 0.3rem; }
#busy .hint { margin: 0 0 0.9rem; line-height: 1.5; }
#busy .bar {
  height: 3px;
  border-radius: 2px;
  background: var(--rule);
  overflow: hidden;
}
#busy .bar span {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--ink-soft);
  animation: kar-sweep 1.5s ease-in-out infinite;
}

/* The period and breakdown, stated before anything has been fetched. */
.prompt strong { display: block; margin-bottom: 0.35rem; }
.htmx-swapping { opacity: 0.55; }
.fade { transition: opacity 120ms ease-in; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.toolbar { display: flex; gap: 0.5rem; align-items: center; margin: 0.85rem 0 0; }
.toolbar .said { color: var(--tied); font-size: 0.85rem; }

.empty-state { padding: 3rem 1rem; text-align: center; color: var(--muted); }
.empty-state strong { display: block; color: var(--ink); font-size: 1.05rem; margin-bottom: 0.3rem; }
