zurich_kotak/src/screens/screens.css
Yashas 009153fc0c console: a type scale, so size can carry meaning again
The app had FIFTY-FOUR distinct font sizes and forty of them sat between
0.58rem and 0.95rem — a three-pixel band. The most-used size was 0.78rem, so
body copy was 12.5px. That is not a hierarchy, it is a fog of small grey text,
and it is the main reason the screen read as a decade old: nothing could be
emphasised because everything was already the same size.

Nine steps now, with real jumps. 12px is the floor and is reserved for
uppercase eyebrows — every one of the 53 declarations sitting there that was
not an eyebrow has been lifted off it. The surfaces people actually read — the
trail's entries, WhatsApp bubbles, field values, table cells — are at body size
rather than meta size.

The hairline cages are gone with them. The lead metrics and the overview KPIs
were seven equal cells in a 1px grid, every value the same size as its label,
which left the reader to do the prioritising themselves on every lead. They are
separated by whitespace now, values at 22px against 12px labels, with premium
and commission larger again: size does the prioritising instead.

Panels breathe at 32px rather than 24, radii went 10/14 -> 12/16/20, borders
dropped to the softer line, and the stage rail, the queue table and the cards
were rebuilt on the same spacing rhythm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 23:36:21 +05:30

1647 lines
46 KiB
CSS

/* ---- page header ---- */
.page__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
margin-bottom: 24px;
}
.page__title {
margin: 0;
font-size: var(--fs-2xl);
font-weight: var(--fw-semi);
letter-spacing: -0.022em;
line-height: 1.2;
color: var(--zk-ink);
}
.page__sub {
margin: 6px 0 0;
max-width: 72ch;
font-size: var(--fs-xs);
line-height: 1.5;
color: var(--zk-muted);
}
/* The queue depth, as a stat tile rather than a loose number. */
.page__count {
flex: none;
display: flex;
flex-direction: column;
align-items: center;
gap: 1px;
min-width: 84px;
padding: 10px 18px;
border-radius: var(--r-lg);
background: var(--zk-white);
border: 1px solid var(--zk-line);
box-shadow: var(--sh-sm);
}
.page__count strong {
font-size: var(--fs-2xl);
font-weight: 500;
line-height: 1.1;
color: var(--zk-blue);
font-variant-numeric: tabular-nums;
}
.page__count span {
font-size: var(--fs-3xs);
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--zk-grey);
}
.empty {
margin: 0;
color: var(--zk-muted);
font-size: var(--fs-xs);
text-align: center;
padding: 44px 24px;
border-radius: var(--r-lg);
border: 1px dashed var(--zk-line);
background: linear-gradient(180deg, var(--zk-white) 0%, var(--zk-tint) 100%);
}
.notice {
background: var(--zk-white);
border: 1px solid var(--zk-line);
border-left: 3px solid var(--zk-blue);
border-radius: var(--r-xs) var(--r-lg) var(--r-lg) var(--r-xs);
box-shadow: var(--sh-sm);
padding: 18px 22px;
max-width: 64ch;
}
.notice strong {
display: block;
font-weight: 500;
margin-bottom: 7px;
}
.notice p {
margin: 0 0 6px;
font-size: var(--fs-xs);
color: var(--zk-muted);
line-height: 1.55;
}
.notice__detail {
font-size: var(--fs-2xs) !important;
color: var(--zk-grey) !important;
}
.notice code {
font-family: var(--font-mono);
font-size: 0.8em;
background: var(--zk-tint);
border: 1px solid var(--zk-line-soft);
border-radius: var(--r-xs);
padding: 1px 5px;
}
/* ---- queue table ---- */
.gridwrap {
background: var(--zk-white);
border: 1px solid var(--zk-line);
border-radius: var(--r-lg);
box-shadow: var(--sh-sm);
overflow: hidden;
overflow-x: auto;
}
.grid {
width: 100%;
/* Eight columns of operational data do not compress: below this the card
scrolls sideways rather than squeezing every cell into two words a line. */
min-width: 1000px;
border-collapse: separate;
border-spacing: 0;
font-size: var(--fs-sm);
}
.grid th {
position: sticky;
top: 0;
z-index: 1;
text-align: left;
font-size: var(--fs-3xs);
font-weight: var(--fw-semi);
letter-spacing: 0.09em;
text-transform: uppercase;
color: var(--zk-grey);
padding: 12px 16px;
background: var(--zk-tint);
border-bottom: 1px solid var(--zk-line);
white-space: nowrap;
}
.grid td {
padding: var(--sp-4) var(--sp-5);
border-bottom: 1px solid var(--zk-line-soft);
vertical-align: middle;
}
.grid tbody tr {
transition: background var(--t-fast);
}
.grid tbody tr:last-child td {
border-bottom: none;
}
.grid tbody tr:hover {
background: var(--zk-tint);
}
/* An accent that arrives on hover, drawn inside the cell so it never disturbs
the table's own borders. */
.grid tbody tr:hover td:first-child {
box-shadow: inset 3px 0 0 var(--zk-blue);
}
.grid .num {
text-align: right;
font-variant-numeric: tabular-nums;
}
/* The lead reference is the thing an operator quotes on a call. */
.grid__ref {
font-variant-numeric: tabular-nums;
white-space: nowrap;
color: var(--zk-muted);
}
.grid__sub {
font-size: var(--fs-2xs);
color: var(--zk-muted);
margin-top: 3px;
}
.grid__link {
color: var(--zk-blue);
text-decoration: none;
font-weight: 500;
border-bottom: 1px solid transparent;
transition: border-color var(--t-fast);
}
.grid__link:hover {
border-bottom-color: currentColor;
}
/* ---- loading skeleton ---- */
.skel {
display: flex;
flex-direction: column;
gap: 1px;
padding: 14px 0;
background: var(--zk-white);
border: 1px solid var(--zk-line);
border-radius: var(--r-lg);
box-shadow: var(--sh-sm);
overflow: hidden;
}
.skel__row {
height: 18px;
margin: 9px 16px;
border-radius: var(--r-sm);
background: linear-gradient(90deg, var(--zk-line-soft) 0%, var(--zk-tint) 40%, var(--zk-line-soft) 80%);
background-size: 420px 100%;
animation: zk-shimmer 1.25s linear infinite;
}
.skel__row:nth-child(odd) { width: calc(100% - 32px); }
.skel__row:nth-child(3n) { width: 72%; }
.skel__row:nth-child(3n + 2) { width: 88%; }
/* ---- chips ---- */
.chip {
display: inline-flex;
align-items: center;
font-size: var(--fs-2xs);
font-weight: 500;
padding: 3px 10px;
border-radius: var(--r-pill);
background: var(--zk-tint-blue);
color: var(--zk-blue-dark);
border: 1px solid var(--zk-blue-light);
}
.chip--warn {
background: var(--zk-amber-tint);
color: var(--zk-amber-ink);
border-color: var(--zk-amber-line);
}
/* ---- doors ---- */
.doors {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
gap: 18px;
}
.door {
position: relative;
font: inherit;
text-align: left;
cursor: pointer;
background: var(--zk-white);
border: 1px solid var(--zk-line);
border-radius: var(--r-lg);
box-shadow: var(--sh-sm);
padding: 22px 22px 20px;
display: flex;
flex-direction: column;
gap: 10px;
overflow: hidden;
transition: box-shadow var(--t), transform var(--t-fast), border-color var(--t-fast);
}
/* The accent bar is the door's handle: 3px at rest, the full brand gradient
once the pointer is on it. */
.door::before {
content: '';
position: absolute;
inset: 0 0 auto;
height: 3px;
background: var(--grad-blue);
transform-origin: left;
transition: height var(--t);
}
.door:hover {
border-color: var(--zk-blue-light);
box-shadow: var(--sh-md);
transform: translateY(-2px);
}
.door:hover::before {
height: 5px;
}
.door:focus-visible {
border-color: var(--zk-blue);
box-shadow: var(--ring);
}
.door strong {
font-size: var(--fs-md);
font-weight: 500;
letter-spacing: -0.01em;
color: var(--zk-ink);
}
.door p {
margin: 0;
font-size: var(--fs-2xs);
color: var(--zk-muted);
line-height: 1.55;
}
.door .chip {
align-self: flex-start;
}
/* ---- panel ---- */
.panel {
background: var(--zk-white);
border: 1px solid var(--zk-line-soft);
border-radius: var(--r-lg);
box-shadow: var(--sh-sm);
padding: var(--sp-7);
margin-bottom: var(--sp-5);
}
.panel__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--sp-5);
margin-bottom: var(--sp-6);
}
.panel__title {
margin: 0;
font-size: var(--fs-lg);
font-weight: var(--fw-semi);
letter-spacing: -0.018em;
line-height: var(--lh-tight);
}
.panel__sub {
margin: 5px 0 0;
font-size: var(--fs-2xs);
color: var(--zk-muted);
max-width: 62ch;
line-height: 1.55;
}
/* ---- actions ---- */
.acts {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.act {
font: inherit;
cursor: pointer;
text-align: left;
background: var(--zk-white);
border: 1px solid var(--zk-line);
border-radius: var(--r-md);
padding: 11px 16px;
display: flex;
flex-direction: column;
gap: 3px;
min-width: 196px;
transition: border-color var(--t-fast), background var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
}
.act:hover {
border-color: var(--zk-blue-light);
background: var(--zk-tint);
box-shadow: var(--sh-sm);
transform: translateY(-1px);
}
.act.is-open {
border-color: var(--zk-blue);
background: var(--zk-tint-blue);
box-shadow: var(--ring);
transform: none;
}
.act strong {
font-size: var(--fs-xs);
font-weight: 500;
}
.act__by {
font-size: var(--fs-2xs);
color: var(--zk-muted);
}
.acts__form {
margin-top: 22px;
padding-top: 22px;
border-top: 1px solid var(--zk-line-soft);
animation: zk-rise 0.24s var(--ease) both;
}
/* ---- what the machine is doing ----
Shown where an empty action bar used to be. Most stages are carried by an AI
employee that takes a minute or two, and a screen that says nothing for two
minutes reads as broken. */
.doing {
display: flex;
align-items: flex-start;
gap: 14px;
margin-bottom: 22px;
padding: 16px 20px;
border-radius: var(--r-lg);
border: 1px solid var(--zk-blue-light);
background: var(--zk-tint-blue);
}
.doing div {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
}
.doing strong {
font-size: var(--fs-sm);
font-weight: 500;
color: var(--zk-blue-dark);
}
.doing span {
font-size: var(--fs-2xs);
line-height: 1.5;
color: var(--zk-muted);
}
.doing__pulse {
flex: none;
margin-top: 5px;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--zk-blue);
box-shadow: 0 0 0 0 rgba(33, 103, 174, 0.5);
animation: zk-pulse 1.9s var(--ease) infinite;
}
@keyframes zk-pulse {
70% { box-shadow: 0 0 0 9px rgba(33, 103, 174, 0); }
100% { box-shadow: 0 0 0 0 rgba(33, 103, 174, 0); }
}
/* ---- and when it has stopped ----
Same strip, amber, and NOT pulsing: the animation is what says "something is
happening", so it is the first thing that has to go when nothing is. The
agents refuse rather than invent — the rating engine will not price without
an IDV — and that refusal used to be visible only as a lead that never moved
again. */
.doing--blocked {
border-color: var(--zk-amber-line);
background: var(--zk-amber-tint);
align-items: flex-start;
}
.doing--blocked strong { color: var(--zk-amber-ink); }
.doing__stop {
flex: none;
margin-top: 5px;
width: 10px;
height: 10px;
border-radius: 2px;
background: var(--zk-amber);
}
.doing__fix {
align-self: flex-start;
margin-top: 9px;
font: inherit;
font-size: var(--fs-2xs);
font-weight: 500;
cursor: pointer;
padding: 7px 15px;
border-radius: var(--r-pill);
color: var(--zk-amber-ink);
background: var(--zk-white);
border: 1px solid var(--zk-amber-line);
transition: background var(--t-fast), border-color var(--t-fast);
}
.doing__fix:hover { background: #fff8f1; border-color: var(--zk-amber); }
.doing__fix:focus-visible { outline: 2px solid var(--zk-amber); outline-offset: 1px; }
/* ---- what the workflow said ----
The message the API returns per activity, shown verbatim: it is the workflow
telling the operator what it just set in motion. */
.said {
display: flex;
align-items: flex-start;
gap: 14px;
margin-bottom: 22px;
padding: 14px 16px 14px 20px;
border-radius: var(--r-lg);
border: 1px solid var(--zk-line);
border-left: 3px solid var(--zk-blue);
background: var(--zk-white);
box-shadow: var(--sh-sm);
animation: zk-rise 0.24s var(--ease) both;
}
.said p {
flex: 1;
margin: 0;
font-size: var(--fs-xs);
line-height: 1.5;
color: var(--zk-ink);
}
.said button {
flex: none;
font: inherit;
font-size: var(--fs-lg);
line-height: 1;
cursor: pointer;
padding: 2px 6px;
border: 0;
border-radius: var(--r-sm);
background: none;
color: var(--zk-grey);
transition: background var(--t-fast), color var(--t-fast);
}
.said button:hover {
background: var(--zk-tint);
color: var(--zk-ink);
}
/* The stage a queue named by need actually maps to. */
.page__stage {
display: block;
margin-top: 4px;
font-size: var(--fs-2xs);
color: var(--zk-grey);
}
/* ---- back ----
An icon, not a sentence: the way out of a record is a known shape and does not
need naming twice on the page. It carries a title and an aria-label, so the
name is still there for a pointer that hovers and for a screen reader. */
.backbtn {
flex: none;
margin-top: 4px;
width: 34px;
height: 34px;
display: grid;
place-items: center;
cursor: pointer;
border-radius: var(--r-md);
border: 1px solid var(--zk-line);
background: var(--zk-white);
color: var(--zk-muted);
box-shadow: var(--sh-xs);
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast), box-shadow var(--t-fast);
}
.backbtn svg {
width: 16px;
height: 16px;
transition: transform var(--t-fast);
}
.backbtn:hover {
border-color: var(--zk-blue-light);
background: var(--zk-tint-blue);
color: var(--zk-blue-dark);
box-shadow: var(--sh-sm);
}
.backbtn:hover svg {
transform: translateX(-2px);
}
.backbtn:focus-visible {
outline: none;
border-color: var(--zk-blue);
box-shadow: var(--ring);
}
.page__lead {
display: flex;
align-items: flex-start;
gap: 14px;
min-width: 0;
}
/* ---- lead: the file beside the story ---- */
.lead {
display: grid;
/* Wider than it was. At 384px the trail was a column of two-line entries
beside a full-width file, which read as a margin note rather than as the
other half of the page — and it is the half that says what happened. */
grid-template-columns: minmax(0, 1fr) 440px;
gap: 20px;
align-items: start;
}
.lead__main {
min-width: 0;
}
/* The trail stays in view while the file is read against it. */
.lead__side {
position: sticky;
top: 90px;
min-width: 0;
}
.panel--rail {
padding: 0;
margin-bottom: 0;
overflow: hidden;
}
.panel--rail .panel__head {
padding: 20px 22px 0;
margin-bottom: 14px;
}
.lead__feed {
max-height: calc(100vh - 240px);
overflow-y: auto;
overscroll-behavior: contain;
padding: 0 22px 20px;
}
/* One column below the width where a 384px rail stops paying for itself. */
@media (max-width: 1180px) {
.lead {
grid-template-columns: minmax(0, 1fr);
}
.lead__side {
position: static;
}
.lead__feed {
max-height: 560px;
}
}
/* ---- tabs ----
Underline rather than pills: these swap the whole panel below them, which is
what a tab is for; a pill segment reads as a filter on shared content. The
2px rule lives on the button, so a strip that wraps to two rows still marks
the active one correctly. */
.props {
margin: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
gap: 0 36px;
}
.prop {
display: grid;
grid-template-columns: minmax(96px, 33%) minmax(0, 1fr);
gap: 16px;
align-items: baseline;
padding: 11px 10px;
margin: 0 -10px;
border-radius: var(--r-md);
border-bottom: 1px solid var(--zk-line-soft);
transition: background var(--t-fast);
}
.prop:hover {
background: var(--zk-tint);
}
.prop dt {
font-size: var(--fs-2xs);
color: var(--zk-muted);
line-height: 1.45;
}
.prop dd {
margin: 0;
font-size: var(--fs-xs);
color: var(--zk-ink);
line-height: 1.45;
overflow-wrap: anywhere;
}
.prop__num {
font-variant-numeric: tabular-nums;
font-weight: 500;
}
/* Prose is not a property. It takes the full width, is labelled above rather
than beside, and is folded by ClampText. */
.prop--note {
grid-column: 1 / -1;
display: block;
margin: 10px -10px;
padding: 14px 16px;
border-bottom: 0;
border: 1px solid var(--zk-line-soft);
border-left: 3px solid var(--zk-blue-light);
border-radius: 0 var(--r-md) var(--r-md) 0;
background: var(--zk-tint);
}
.prop--note:hover {
background: var(--zk-tint);
border-color: var(--zk-blue-light);
}
.prop--note dt {
margin-bottom: 7px;
font-size: var(--fs-3xs);
font-weight: 500;
letter-spacing: 0.09em;
text-transform: uppercase;
color: var(--zk-blue-dark);
}
.prop--note dd {
margin: 0;
}
/* ---- lead file ---- */
.lead__stage {
flex: none;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 5px;
text-align: right;
}
.lead__stagelabel {
display: block;
font-size: var(--fs-3xs);
font-weight: 500;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--zk-grey);
}
.lead__closed {
font-size: var(--fs-2xs);
color: var(--zk-grey);
}
.lead__stage strong {
font-size: var(--fs-sm);
font-weight: 500;
color: var(--zk-blue-dark);
padding: 6px 14px;
border-radius: var(--r-pill);
background: var(--zk-tint-blue);
border: 1px solid var(--zk-blue-light);
}
/* A closed lead is not a live stage; it should not wear the live stage's blue. */
.lead__stage strong.is-closed {
color: var(--zk-muted);
background: var(--zk-tint);
border-color: var(--zk-line);
}
/* ---- renewal countdown ---- */
.due {
display: inline-flex;
align-items: center;
font-size: var(--fs-2xs);
font-weight: 500;
padding: 3px 10px;
border-radius: var(--r-pill);
white-space: nowrap;
}
.due--lapsed {
background: var(--zk-danger-tint);
color: var(--zk-danger-ink);
border: 1px solid var(--zk-danger-line);
}
.due--urgent {
background: var(--zk-amber-tint);
color: var(--zk-amber-ink);
border: 1px solid var(--zk-amber-line);
}
.due--soon {
background: var(--zk-tint-blue);
color: var(--zk-blue-dark);
border: 1px solid var(--zk-blue-light);
}
.due--later {
background: var(--zk-tint);
color: var(--zk-muted);
border: 1px solid var(--zk-line);
}
/* ---- lead header meta ---- */
.lead__meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 0 0 22px;
}
.lead__meta div {
display: flex;
flex-direction: column;
gap: 3px;
padding: 9px 16px;
border-radius: var(--r-md);
background: var(--zk-white);
border: 1px solid var(--zk-line);
box-shadow: var(--sh-xs);
}
.lead__meta dt {
font-size: var(--fs-3xs);
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--zk-grey);
}
.lead__meta dd {
margin: 0;
font-size: var(--fs-xs);
color: var(--zk-ink);
}
.lead__meta--money {
border-color: var(--zk-blue-light) !important;
background: var(--zk-tint-blue) !important;
}
.lead__meta--money dd {
font-size: var(--fs-md);
font-weight: 500;
color: var(--zk-blue-dark);
font-variant-numeric: tabular-nums;
}
/* ── Portfolio overview ──────────────────────────────────────────────────
An operations dashboard: numbers first, prose nowhere. Every measure on
this page is derived from the lead list in the browser, so nothing here is
a figure the reader cannot get back to by opening a queue. */
.stamp {
flex: none;
align-self: center;
font-size: var(--fs-3xs);
letter-spacing: .04em;
text-transform: uppercase;
color: var(--zk-grey);
font-variant-numeric: tabular-nums;
}
/* KPI strip. Whitespace rather than rules — see .lmetrics for why the cage
went. Tabular figures so the row does not jitter as it refreshes. */
.kpis {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(176px, 1fr));
gap: var(--sp-5) var(--sp-7);
padding: var(--sp-6) var(--sp-7);
background: var(--zk-white);
border: 1px solid var(--zk-line-soft);
border-radius: var(--r-lg);
box-shadow: var(--sh-sm);
margin-bottom: var(--sp-6);
}
.kpi { min-width: 0; }
.kpi__l {
display: block;
margin-bottom: var(--sp-2);
font-size: var(--fs-3xs);
font-weight: var(--fw-semi);
letter-spacing: .08em;
text-transform: uppercase;
color: var(--zk-grey);
}
.kpi__v {
display: block;
margin-bottom: var(--sp-1);
font-size: var(--fs-3xl);
font-weight: var(--fw-semi);
line-height: var(--lh-tight);
color: var(--zk-navy);
font-variant-numeric: tabular-nums;
letter-spacing: -.028em;
}
.kpi__s {
display: block;
font-size: var(--fs-2xs);
line-height: var(--lh-snug);
color: var(--zk-muted);
}
/* Two columns on a desk, one on anything narrower. The distribution is the
half that survives being pushed below the fold. */
.split { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: 30px; align-items: start; }
.sec {
display: flex;
align-items: baseline;
gap: 10px;
margin: 0 0 12px;
font-size: var(--fs-3xs);
font-weight: 500;
letter-spacing: .07em;
text-transform: uppercase;
color: var(--zk-muted);
}
.split > div > .sec:not(:first-child) { margin-top: 30px; }
.sec__hint { font-size: var(--fs-2xs); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--zk-grey); }
/* Work queues. A row rather than a card: five of them as cards is a wall. */
.qlist { display: flex; flex-direction: column; gap: 6px; }
.q {
display: grid;
grid-template-columns: 44px 1fr auto auto;
align-items: center;
gap: 12px;
padding: 11px 14px;
border: 1px solid var(--zk-line-soft);
border-radius: var(--r-sm);
background: var(--zk-white);
text-decoration: none;
color: inherit;
transition: border-color .12s, background .12s;
}
.q:hover { border-color: var(--zk-blue-light); background: var(--zk-tint); }
.q__n { font-size: var(--fs-xl); font-weight: 500; color: var(--zk-grey); font-variant-numeric: tabular-nums; text-align: right; }
.q__t { font-size: var(--fs-xs); color: var(--zk-ink); }
.q__t em { display: block; font-style: normal; font-size: var(--fs-2xs); color: var(--zk-muted); margin-top: 1px; }
.q__age { font-size: var(--fs-2xs); color: var(--zk-grey); font-variant-numeric: tabular-nums; }
.q__ro {
font-size: var(--fs-3xs); letter-spacing: .06em; text-transform: uppercase;
color: var(--zk-grey); border: 1px solid var(--zk-line); border-radius: 3px; padding: 1px 5px;
}
/* Only a queue with work in it earns weight. The eye should land on the
number that needs clearing, not read five identical rows. */
.q.is-live { border-color: var(--zk-line); }
.q.is-live .q__n { color: var(--zk-navy); }
.buckets { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.bucket {
padding: 13px 15px;
border: 1px solid var(--zk-line);
border-left-width: 3px;
border-radius: var(--r-sm);
background: var(--zk-white);
}
.bucket strong { display: block; font-size: var(--fs-xl); font-weight: 500; line-height: 1.1; font-variant-numeric: tabular-nums; }
.bucket span { display: block; margin-top: 2px; font-size: var(--fs-2xs); color: var(--zk-muted); }
.bucket--lapsed { border-left-color: var(--zk-danger); }
.bucket--lapsed strong { color: var(--zk-danger-ink); }
.bucket--urgent { border-left-color: var(--zk-amber); }
.bucket--urgent strong { color: var(--zk-amber-ink); }
.bucket--soon { border-left-color: var(--zk-blue-mid); }
.bucket--soon strong { color: var(--zk-blue-dark); }
/* Stage distribution. A bar per stage, scaled to the largest — enough to see
where the book is banked without pulling in a charting library. */
.dist { display: flex; flex-direction: column; gap: 3px; }
.dist__r {
display: grid;
grid-template-columns: 1fr 74px 26px;
align-items: center;
gap: 10px;
padding: 5px 8px;
border-radius: var(--r-xs);
text-decoration: none;
color: inherit;
}
.dist__r:hover { background: var(--zk-tint); }
.dist__l { font-size: var(--fs-2xs); color: var(--zk-ink); }
.dist__bar { height: 6px; border-radius: 3px; background: var(--zk-line-soft); overflow: hidden; }
.dist__f { display: block; height: 100%; border-radius: 3px; background: var(--zk-blue-mid); min-width: 0; }
.dist__f--needs { background: var(--zk-amber); }
.dist__f--customer { background: var(--zk-blue); }
.dist__f--auto { background: var(--zk-blue-light); }
.dist__f--waiting { background: var(--zk-grey); }
.dist__f--end { background: var(--zk-line); }
.dist__n { font-size: var(--fs-2xs); text-align: right; color: var(--zk-muted); font-variant-numeric: tabular-nums; }
.dist__r.is-zero .dist__l, .dist__r.is-zero .dist__n { color: var(--zk-grey); }
.grid--tight th, .grid--tight td { padding-top: 8px; padding-bottom: 8px; }
@media (max-width: 1080px) {
.split { grid-template-columns: 1fr; gap: 26px; }
}
@media (max-width: 640px) {
.buckets { grid-template-columns: 1fr; }
}
/* ── Lead measures ───────────────────────────────────────────────────────
The same strip as the portfolio KPIs, at lead scale. Age and dwell time are
computed rather than stored, and they are the two figures that answer "is
this moving?" — a question the record itself cannot answer. */
/* ── The numbers that matter ──────────────────────────────────────────────
This was a 1px-gap hairline grid — seven equal cells in a cage, every
value the same size as every label. The cage is a 2015 device and the
equality was the real fault: if nothing is bigger, the reader has to do
the prioritising themselves, seven times, on every lead.
Whitespace separates the cells now instead of rules, and the values are
22px against 12px labels. Premium and commission — the two numbers
anyone actually came for — are bigger again. */
.lmetrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
gap: var(--sp-5) var(--sp-7);
padding: var(--sp-6) var(--sp-7);
background: var(--zk-white);
border: 1px solid var(--zk-line-soft);
border-radius: var(--r-lg);
box-shadow: var(--sh-sm);
margin-bottom: var(--sp-5);
}
.lm { min-width: 0; }
.lm__l {
display: block;
margin-bottom: var(--sp-2);
font-size: var(--fs-3xs);
font-weight: var(--fw-semi);
letter-spacing: .08em;
text-transform: uppercase;
color: var(--zk-grey);
}
.lm__v {
display: block;
margin-bottom: var(--sp-1);
font-size: var(--fs-xl);
font-weight: var(--fw-semi);
line-height: var(--lh-tight);
color: var(--zk-navy);
font-variant-numeric: tabular-nums;
letter-spacing: -.02em;
}
/* Money leads. Size does the prioritising so the reader does not have to. */
.lm--hero .lm__v { font-size: var(--fs-2xl); }
.lm__v--sm { font-size: var(--fs-md); font-weight: var(--fw-medium); }
.lm__s {
display: block;
font-size: var(--fs-2xs);
line-height: var(--lh-snug);
color: var(--zk-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* The countdown carries its own tone here, the same one the queues use, so a
lapsed renewal reads the same wherever it appears. */
.lm__v.due--lapsed { color: var(--zk-danger-ink); }
.lm__v.due--urgent { color: var(--zk-amber-ink); }
/* ── Next step ───────────────────────────────────────────────────────────
One decision, sized like one. The stage's own step is the only thing at
full weight; loops sit under it, and an AI's own work is folded away
entirely — it is recovery, not an outstanding task. */
.step { display: flex; flex-wrap: wrap; gap: 10px; }
.step__btn {
flex: 1 1 220px;
display: block;
padding: 14px 18px;
border: 1px solid var(--zk-blue);
border-radius: var(--r-md);
background: var(--zk-blue);
color: var(--zk-white);
text-align: left;
cursor: pointer;
transition: background .12s, border-color .12s, box-shadow .12s;
}
.step__btn:hover { background: var(--zk-blue-dark); border-color: var(--zk-blue-dark); box-shadow: 0 2px 10px rgba(33,103,174,.25); }
.step__btn strong { display: block; font-size: var(--fs-sm); font-weight: 500; }
.step__btn span { display: block; margin-top: 2px; font-size: var(--fs-2xs); opacity: .85; }
.step__btn.is-open { background: var(--zk-blue-deep); border-color: var(--zk-blue-deep); }
/* Decline sits beside Clear as an equal — an underwriter is choosing between
two decisions, not deciding whether to act. It is outlined rather than
filled so the pair does not read as two identical recommendations. */
.step__btn--no {
background: var(--zk-white);
color: var(--zk-danger-ink);
border-color: var(--zk-danger-line);
}
.step__btn--no:hover { background: var(--zk-danger-tint); border-color: var(--zk-danger); box-shadow: none; }
.step__btn--no.is-open { background: var(--zk-danger-tint); border-color: var(--zk-danger); }
/* Loops. Real, bounded, and never the answer to "what now". */
.alt { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.alt__btn {
padding: 8px 13px;
border: 1px solid var(--zk-line);
border-radius: var(--r-sm);
background: var(--zk-white);
color: var(--zk-ink);
font-size: var(--fs-2xs);
cursor: pointer;
transition: border-color .12s, background .12s;
}
.alt__btn:hover { border-color: var(--zk-blue-light); background: var(--zk-tint); }
.alt__btn.is-open { border-color: var(--zk-blue); background: var(--zk-tint-blue); }
.alt__btn em { font-style: normal; margin-left: 7px; font-size: var(--fs-2xs); color: var(--zk-grey); }
.alt__btn--exit { color: var(--zk-danger-ink); }
.alt__btn--exit:hover { border-color: var(--zk-danger-line); background: var(--zk-danger-tint); }
/* Recovery, folded. Needed on the day an agent stalls and never otherwise. */
.stuck { margin-top: 18px; border-top: 1px solid var(--zk-line-soft); padding-top: 14px; }
.stuck > summary {
cursor: pointer;
font-size: var(--fs-2xs);
color: var(--zk-muted);
list-style: none;
display: inline-flex;
align-items: center;
gap: 6px;
}
.stuck > summary::-webkit-details-marker { display: none; }
.stuck > summary::before { content: '▸'; font-size: var(--fs-2xs); color: var(--zk-grey); }
.stuck[open] > summary::before { content: '▾'; }
.stuck > summary:hover { color: var(--zk-blue-dark); }
.stuck__why { margin: 10px 0 0; font-size: var(--fs-2xs); color: var(--zk-muted); max-width: 64ch; }
/* ── The row IS the control ───────────────────────────────────────────────
An "Open" button was a ~90px target inside a 1000px row that already reads
as one object, and it put a second tab stop on every line. The row carries
the click, the keyboard and the affordance; the button is gone.
role="link" + tabIndex on a <tr> rather than wrapping cells in <a>: an
anchor cannot span table cells without breaking the table layout, and
display:block on a <td> collapses the column alignment the queue depends on. */
.grid__row { cursor: pointer; transition: background .11s, box-shadow .11s; }
.grid__row:hover { background: var(--zk-tint); }
.grid__row:hover .grid__name { color: var(--zk-blue-dark); }
.grid__row:focus-visible {
outline: 2px solid var(--zk-blue);
outline-offset: -2px;
background: var(--zk-tint);
}
.grid__name { font-weight: 500; color: var(--zk-ink); }
/* A left edge that says who holds the lead, readable before any text is.
Only the states worth acting on are marked — colouring every row would
make the marking mean nothing. */
.grid__row.is-stalled td:first-child { box-shadow: inset 3px 0 0 var(--zk-danger); }
.grid__row.is-working td:first-child { box-shadow: inset 3px 0 0 var(--zk-blue-mid); }
/* ── What the agent is doing ──────────────────────────────────────────────
Agent-status disclosure: an automated stage is either moving or stuck, and
after an hour those look identical without this. `working` pulses because
something is happening right now; `waiting` is steady because it is normal;
`stalled` is red because a lead three hours into a two-minute step is the
thing this console exists to catch. */
.run { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-2xs); line-height: 1.3; }
.run__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.run--working { color: var(--zk-blue-dark); }
.run--working .run__dot { background: var(--zk-blue); animation: runPulse 1.6s ease-in-out infinite; }
.run--waiting { color: var(--zk-muted); }
.run--waiting .run__dot { background: var(--zk-blue-light); }
.run--stalled { color: var(--zk-danger-ink); font-weight: 500; }
.run--stalled .run__dot { background: var(--zk-danger); }
@keyframes runPulse {
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(33,103,174,.45); }
50% { opacity: .55; box-shadow: 0 0 0 5px rgba(33,103,174,0); }
}
/* Respect a reader who has asked the interface to stop moving. */
@media (prefers-reduced-motion: reduce) {
.run--working .run__dot { animation: none; }
}
/* ── An attached document is a thing to open ──────────────────────────────
The record printed a filename as text, so the only way to see what an
agent had actually uploaded was to perform the activity again. */
.prop__files { display: flex; flex-wrap: wrap; gap: 6px; }
.prop__file {
display: inline-flex;
align-items: center;
gap: 5px;
max-width: 100%;
font-size: var(--fs-2xs);
text-decoration: none;
padding: 3px 10px;
border-radius: var(--r-pill, 999px);
background: var(--zk-tint-blue);
border: 1px solid transparent;
color: var(--zk-blue-dark);
overflow-wrap: anywhere;
}
.prop__file:hover { border-color: var(--zk-blue-light); }
.prop__file:focus-visible { outline: 2px solid var(--zk-blue); outline-offset: 1px; }
/* The part of a queue that is not anyone's task. Reported rather than hidden:
the leads are in that state, they are just being carried by an agent, and an
operator who counts the queue by hand should find the same total. */
.q__auto {
display: block;
font-weight: 400;
font-size: var(--fs-2xs);
color: var(--zk-muted);
margin-top: 2px;
}
/* ---- waiting on someone outside the business ----
Neither an agent working nor a task of yours. Grey rather than blue, and
no pulse: nothing is happening, and that is the correct state. */
.doing--waiting {
border-color: var(--zk-line);
background: var(--zk-tint);
}
.doing--waiting strong { color: var(--zk-ink); }
.doing__wait {
flex: none;
margin-top: 5px;
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid var(--zk-grey);
background: transparent;
}
/* ---- panel action ----
A secondary control on a panel heading. Quiet: it opens a view, it does not
change anything. */
.panel__act {
display: inline-flex;
align-items: center;
gap: 7px;
flex: none;
font: inherit;
font-size: var(--fs-2xs);
font-weight: 500;
cursor: pointer;
padding: 6px 12px;
border-radius: var(--r-pill);
color: var(--zk-blue-dark);
background: var(--zk-white);
border: 1px solid var(--zk-line);
transition: background var(--t-fast), border-color var(--t-fast);
}
.panel__act svg { width: 13px; height: 13px; opacity: .75; }
.panel__act b {
font-size: var(--fs-2xs);
font-weight: 600;
font-variant-numeric: tabular-nums;
padding: 1px 6px;
border-radius: var(--r-pill);
background: var(--zk-tint-blue);
}
.panel__act:hover { background: var(--zk-tint); border-color: var(--zk-blue-light); }
.panel__act:focus-visible { outline: 2px solid var(--zk-blue); outline-offset: 1px; }
/* ---- who worked this lead ----
Five agents carry a lead and their names appeared only inside individual
entries. The team is a fact about the lead, so it sits above the trail. */
.crew {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
padding: 10px 18px 12px;
border-bottom: 1px solid var(--zk-line-soft);
}
.crew__l {
font-size: var(--fs-3xs);
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--zk-grey);
}
/* ---- a sign-off, not a task ----
Confirming a premium is the only step in this workflow that is a person's
approval rather than a piece of work, and the only one locked to one role.
It sat fourth in a list of six queues, which is the wrong weight for the
decision that starts somebody's cover. Shown only to the role that owns it. */
.appr {
display: flex;
align-items: center;
gap: 18px;
margin-bottom: 22px;
padding: 16px 22px;
border-radius: var(--r-lg);
text-decoration: none;
border: 1px solid var(--zk-line);
background: var(--zk-white);
transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
/* Amber only when something is actually waiting. A permanent alert colour on
an empty queue teaches people to stop seeing it. */
.appr.is-live {
border-color: var(--zk-amber-line);
background: var(--zk-amber-tint);
}
.appr:hover { box-shadow: var(--sh-sm); border-color: var(--zk-blue-light); }
.appr:focus-visible { outline: 2px solid var(--zk-blue); outline-offset: 2px; }
.appr__n {
flex: none;
min-width: 46px;
font-size: var(--fs-2xl);
font-weight: 300;
line-height: 1;
letter-spacing: -0.02em;
color: var(--zk-grey);
font-variant-numeric: tabular-nums;
}
.appr.is-live .appr__n { color: var(--zk-amber-ink); }
.appr__t {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
flex: 1;
font-size: var(--fs-sm);
font-weight: 500;
color: var(--zk-ink);
}
.appr__t em {
font-style: normal;
font-size: var(--fs-2xs);
font-weight: 400;
line-height: 1.45;
color: var(--zk-muted);
}
.appr__go {
flex: none;
font-size: var(--fs-2xs);
color: var(--zk-muted);
white-space: nowrap;
}
/* ---- an agent that has not come back ----
Amber like `blocked`, because both need a person — but worded and coloured
apart from it: blocked means the workflow refused for a stated reason and
the fix is known; stalled means nobody knows, and the honest offer is to try
again. No pulse either way. */
.doing--stalled {
border-color: var(--zk-amber-line);
background: var(--zk-amber-tint);
}
.doing--stalled strong { color: var(--zk-amber-ink); }
/* ---- header right-hand cluster ---- */
.page__right { display: flex; align-items: center; gap: 18px; flex: none; }
.toggle {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: var(--fs-2xs);
color: var(--zk-muted);
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.toggle input { accent-color: var(--zk-blue); cursor: pointer; }
.toggle:hover { color: var(--zk-ink); }
/* A finished lead, still listed. Dimmed rather than removed: "where did it
go?" is the question this page exists to answer, and a lead that ended is
still a lead. Hover restores it — it is muted, not disabled. */
.grid__row.is-done { color: var(--zk-grey); }
.grid__row.is-done .grid__name { color: var(--zk-muted); font-weight: 400; }
.grid__row.is-done:hover { color: var(--zk-ink); }
.grid__row.is-done:hover .grid__name { color: var(--zk-blue-dark); }
/* ---- file a lead ----
Moved off the sidebar. A nav is for moving between places; this adds
something, and it now sits beside the lists it adds to. */
.newlead {
display: inline-flex;
align-items: center;
gap: 8px;
flex: none;
padding: 9px 16px;
border-radius: var(--r-pill);
text-decoration: none;
font-size: var(--fs-xs);
font-weight: 500;
color: var(--zk-white);
background: var(--grad-blue);
box-shadow: var(--sh-sm);
transition: box-shadow var(--t), transform var(--t-fast), background var(--t);
}
.newlead svg { width: 14px; height: 14px; }
.newlead:hover { background: var(--grad-blue-hover); box-shadow: var(--sh-md); transform: translateY(-1px); }
.newlead:focus-visible { outline: 2px solid var(--zk-blue); outline-offset: 2px; }
/* ── the lead file, all of it ─────────────────────────────────────────────
This was thirteen tabs with one group visible at a time, so "what do we
know about this lead?" took thirteen clicks and a good memory. Then it was
thirteen groups in CSS columns, which was worse in a different way: column
flow reads top-to-bottom-then-across, so a file whose groups run Source,
Customer, Intake landed on the page in an order nobody could predict, and
the groups are wildly uneven — Motor risk has twelve fields, Proposal has
two — so the columns tore raggedly.
A GRID OF CARDS instead. Reading order is left-to-right like every other
list on this screen, each group is bounded so its fields cannot be mistaken
for the next group's, and an uneven card leaves a clean gap under itself
rather than dragging the next heading up into the previous group's fields. */
.file360 {
padding: 4px 22px 22px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 14px;
align-items: start;
}
.fgroup {
padding: var(--sp-5);
border: 1px solid var(--zk-line-soft);
border-radius: var(--r-md);
background: var(--zk-white);
}
.fgroup h3 {
display: flex;
align-items: center;
gap: var(--sp-2);
margin: 0 0 var(--sp-4);
padding-bottom: var(--sp-3);
border-bottom: 1px solid var(--zk-line-soft);
font-size: var(--fs-3xs);
font-weight: var(--fw-semi);
letter-spacing: 0.09em;
text-transform: uppercase;
color: var(--zk-blue-dark);
}
.fgroup h3 span {
margin-left: auto;
font-size: var(--fs-2xs);
font-weight: 500;
letter-spacing: 0;
color: var(--zk-grey);
font-variant-numeric: tabular-nums;
}
/* Label and value on ONE line. Stacked pairs doubled the height of every fact
in the file for no gain — these are two-word labels against short values,
and a label gutter is what makes a column of them scannable. */
.file360 .props { display: block; padding: 0; }
.file360 .prop {
display: grid;
grid-template-columns: minmax(0, 40%) minmax(0, 1fr);
gap: var(--sp-4);
align-items: baseline;
margin: 0;
padding: var(--sp-2) 0;
border: 0;
border-radius: 0;
}
.file360 .prop:hover { background: transparent; }
.file360 .prop dt {
font-size: var(--fs-2xs);
line-height: 1.5;
color: var(--zk-grey);
margin: 0;
}
.file360 .prop dd {
font-size: var(--fs-2xs);
line-height: 1.5;
color: var(--zk-ink);
overflow-wrap: anywhere;
}
/* ── the prose, folded ────────────────────────────────────────────────────
The AI paragraphs used to render inline with the same weight the audit
trail gives them — a tinted block, a bolded finding, a "Read the reasoning"
button — and with seven of them the file read as a wall of quotations with
the facts hidden between. Here they are a caption and two lines of grey.
Nothing is lost: the full text is one click away in the same dialog, and
the trail still shows each paragraph against the step that wrote it. */
.fnotes {
margin-top: 11px;
padding-top: 10px;
border-top: 1px dashed var(--zk-line);
display: grid;
gap: 10px;
}
.fnote__l {
display: block;
margin-bottom: 2px;
font-size: var(--fs-3xs);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--zk-grey);
}
/* The finding is emphasised in the trail, where it is the entry. Here it is
one fact among fifty, so it drops to body weight and two lines. */
.file360 .clamp__lead,
.file360 .clamp__text {
font-size: var(--fs-2xs);
font-weight: 400;
line-height: 1.5;
color: var(--zk-muted);
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.file360 .clamp__more { margin-top: 3px; font-size: var(--fs-2xs); }
/* ── at a glance ──────────────────────────────────────────────────────────
The same card as the full file uses, so opening the drawer feels like more
of the thing you were already reading rather than a different screen. Four
sections at most, so a fixed auto-fit grid never leaves a lone card on a
row of its own the way the thirteen-group version did. */
.glance {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 14px;
padding: 4px 22px 18px;
align-items: start;
}
.glance .props { display: block; padding: 0; }
.glance .prop {
display: grid;
grid-template-columns: minmax(0, 40%) minmax(0, 1fr);
gap: var(--sp-4);
align-items: baseline;
margin: 0;
padding: var(--sp-2) 0;
border: 0;
border-radius: 0;
}
.glance .prop:hover { background: transparent; }
.glance .prop dt { font-size: var(--fs-2xs); line-height: 1.5; color: var(--zk-grey); margin: 0; }
.glance .prop dd { font-size: var(--fs-2xs); line-height: 1.5; color: var(--zk-ink); overflow-wrap: anywhere; }
/* Same de-weighting as the full file: at a glance a reason is one fact among
a dozen, not the entry it is in the trail. */
.glance .clamp__lead,
.glance .clamp__text {
font-size: var(--fs-2xs);
font-weight: 400;
line-height: 1.5;
color: var(--zk-muted);
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.glance .clamp__more { margin-top: 3px; font-size: var(--fs-2xs); }
/* An action that belongs to one card, not to the panel. Quiet until hovered:
the notes above it are the answer most of the time, and the transcript is
the follow-up question. */
.glance__act {
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 10px;
padding: 5px 10px 5px 8px;
border: 1px solid var(--zk-line);
border-radius: var(--r-pill);
background: var(--zk-white);
cursor: pointer;
font: inherit;
font-size: var(--fs-2xs);
font-weight: 500;
color: var(--zk-blue-dark);
transition: background var(--t-fast), border-color var(--t-fast);
}
.glance__act:hover { background: var(--zk-tint-blue); border-color: var(--zk-blue-light); }
.glance__act:focus-visible { outline: 2px solid var(--zk-blue); outline-offset: 1px; }
.glance__act svg { width: 13px; height: 13px; flex: none; }
/* HIDING IS ONLY SAFE WHILE IT STAYS FINDABLE, so this names the number it is
holding rather than saying "more". Full width and on the panel's own floor:
a link tucked into the header would be the discoverability problem that
sinks progressive disclosure. */
.glance__all {
display: flex;
align-items: center;
gap: 9px;
width: 100%;
padding: 13px 22px;
border: 0;
border-top: 1px solid var(--zk-line-soft);
border-radius: 0 0 var(--r-lg) var(--r-lg);
background: none;
cursor: pointer;
font: inherit;
font-size: var(--fs-2xs);
font-weight: 500;
color: var(--zk-blue-dark);
transition: background var(--t-fast);
}
.glance__all:hover { background: var(--zk-tint); }
.glance__all:focus-visible { outline: 2px solid var(--zk-blue); outline-offset: -2px; }
.glance__all svg { width: 15px; height: 15px; flex: none; }
.glance__all b {
margin-left: auto;
font-weight: 400;
font-size: var(--fs-2xs);
color: var(--zk-grey);
font-variant-numeric: tabular-nums;
}
.panel__meta {
flex: none;
font-size: var(--fs-2xs);
color: var(--zk-grey);
font-variant-numeric: tabular-nums;
}