Three problems, and the first one is the reason the other two were hard to see. THERE WAS NO DASHBOARD. "/" redirected to the underwriting queue — one stage, usually empty, and belonging to somebody else. Every screen in the app answered "show me this queue"; none answered "how is the book doing, and is anything waiting on me?", which is the only question a person has before they have picked a queue. So: a Today page. Three blocks, in the order somebody cares about them. What is waiting on a person, as three cards that colour only when they have something in them. Renewals running out, worst first, capped at 30 days because a renewal six months away is not a thing to look at today. Then every stage with its count, zeroes included — a stage quietly receiving nothing is only visible if its zero is on screen. It is one list call, counted in the browser. That is honest at this size and it is the same call the queues already make. If the book outgrows it the answer is a counts endpoint, not a bigger page. THE WORDS WERE WRITTEN FOR WHOEVER BUILT THE WORKFLOW. "What happens next" was followed by "these are the only activities this state allows — but the workflow decides, server-side, whether you may". A failed list said "this queue has no record view yet". Panels were called "Flow details". A closed lead read "nothing runs from here". None of that is wrong; all of it is addressed to the wrong reader. Now: "What you can do", "Lead details", "History", "This queue could not be loaded". Queue names say what they want — "Documents needed", "Underwriter to review", "Payment to confirm". The sidebar group called "Not yet" says "Not due yet", and "Running by itself" says "The agents have it", which is the actual claim being made. THE QUEUE HAD NINE COLUMNS AND LED WITH THE WRONG ONE. Two were internal vocabulary: attribution status renders "clear" or "contested" and means nothing to an operator, and the channel is background rather than something anyone scans a queue for. Both fold into a subtitle under the customer. That leaves six columns and puts the renewal countdown — the number that decides whether to act today — second instead of fifth. Also: the timeline printed a raw slug when a step came back without a name. It is the screen this product is demonstrated on, so a "zk-act-doc-reminder" in the middle of an otherwise readable story is expensive. It now reads as English.
985 lines
21 KiB
CSS
985 lines
21 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: 1.7rem;
|
|
font-weight: 500;
|
|
letter-spacing: -0.022em;
|
|
line-height: 1.2;
|
|
color: var(--zk-ink);
|
|
}
|
|
|
|
.page__sub {
|
|
margin: 6px 0 0;
|
|
max-width: 72ch;
|
|
font-size: 0.855rem;
|
|
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: 1.6rem;
|
|
font-weight: 500;
|
|
line-height: 1.1;
|
|
color: var(--zk-blue);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.page__count span {
|
|
font-size: 0.6rem;
|
|
font-weight: 500;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--zk-grey);
|
|
}
|
|
|
|
.empty {
|
|
margin: 0;
|
|
color: var(--zk-muted);
|
|
font-size: 0.88rem;
|
|
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: 0.86rem;
|
|
color: var(--zk-muted);
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.notice__detail {
|
|
font-size: 0.76rem !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: 0.86rem;
|
|
}
|
|
|
|
.grid th {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
text-align: left;
|
|
font-size: 0.65rem;
|
|
font-weight: 500;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--zk-muted);
|
|
padding: 12px 16px;
|
|
background: var(--zk-tint);
|
|
border-bottom: 1px solid var(--zk-line);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.grid td {
|
|
padding: 13px 16px;
|
|
border-bottom: 1px solid var(--zk-line-soft);
|
|
vertical-align: top;
|
|
}
|
|
|
|
.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: 0.76rem;
|
|
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;
|
|
}
|
|
|
|
/* Row action. Renders as a button; stays an anchor so an operator can open a
|
|
lead in a new tab from a queue.
|
|
NOT `.act` — that class belongs to the Lead page's activity buttons and is
|
|
display:flex, which stretched this anchor to fill the cell. */
|
|
.grid__act {
|
|
text-align: right;
|
|
white-space: nowrap;
|
|
width: 1%;
|
|
}
|
|
|
|
.grid__btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
font-size: 0.78rem;
|
|
font-weight: 500;
|
|
padding: 6px 14px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--zk-white);
|
|
color: var(--zk-blue);
|
|
border: 1px solid var(--zk-line);
|
|
transition: background var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast);
|
|
}
|
|
|
|
.grid__btn::after {
|
|
content: '→';
|
|
font-size: 0.85em;
|
|
transition: transform var(--t-fast);
|
|
}
|
|
|
|
.grid__btn:hover {
|
|
background: var(--zk-tint-blue);
|
|
border-color: var(--zk-blue-light);
|
|
}
|
|
|
|
.grid__btn:hover::after {
|
|
transform: translateX(2px);
|
|
}
|
|
|
|
.grid__btn:focus-visible {
|
|
outline: none;
|
|
border-color: var(--zk-blue);
|
|
box-shadow: var(--ring);
|
|
}
|
|
|
|
/* ---- 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: 0.72rem;
|
|
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: 1.02rem;
|
|
font-weight: 500;
|
|
letter-spacing: -0.01em;
|
|
color: var(--zk-ink);
|
|
}
|
|
|
|
.door p {
|
|
margin: 0;
|
|
font-size: 0.82rem;
|
|
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);
|
|
border-radius: var(--r-lg);
|
|
box-shadow: var(--sh-sm);
|
|
padding: 24px 26px;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
.panel__head {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 20px;
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.panel__title {
|
|
margin: 0;
|
|
font-size: 1.06rem;
|
|
font-weight: 500;
|
|
letter-spacing: -0.012em;
|
|
}
|
|
|
|
.panel__sub {
|
|
margin: 5px 0 0;
|
|
font-size: 0.82rem;
|
|
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: 0.885rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.act__by {
|
|
font-size: 0.72rem;
|
|
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: 0.95rem;
|
|
font-weight: 500;
|
|
color: var(--zk-blue-dark);
|
|
}
|
|
|
|
.doing span {
|
|
font-size: 0.82rem;
|
|
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); }
|
|
}
|
|
|
|
/* ---- 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: 0.88rem;
|
|
line-height: 1.5;
|
|
color: var(--zk-ink);
|
|
}
|
|
|
|
.said button {
|
|
flex: none;
|
|
font: inherit;
|
|
font-size: 1.1rem;
|
|
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: 0.72rem;
|
|
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;
|
|
grid-template-columns: minmax(0, 1fr) 384px;
|
|
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 - 210px);
|
|
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. */
|
|
.tabs {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 2px;
|
|
margin-bottom: 20px;
|
|
border-bottom: 1px solid var(--zk-line);
|
|
}
|
|
|
|
.tab {
|
|
font: inherit;
|
|
font-size: 0.82rem;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
padding: 9px 13px;
|
|
background: none;
|
|
border: 0;
|
|
border-bottom: 2px solid transparent;
|
|
border-radius: var(--r-sm) var(--r-sm) 0 0;
|
|
color: var(--zk-muted);
|
|
transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
|
|
}
|
|
|
|
.tab:hover {
|
|
background: var(--zk-tint);
|
|
color: var(--zk-ink);
|
|
}
|
|
|
|
.tab.is-on {
|
|
color: var(--zk-blue-dark);
|
|
font-weight: 500;
|
|
border-bottom-color: var(--zk-blue);
|
|
}
|
|
|
|
.tab__n {
|
|
font-size: 0.66rem;
|
|
font-variant-numeric: tabular-nums;
|
|
min-width: 18px;
|
|
padding: 1px 6px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--zk-line-soft);
|
|
color: var(--zk-muted);
|
|
text-align: center;
|
|
}
|
|
|
|
.tab.is-on .tab__n {
|
|
background: var(--zk-tint-blue);
|
|
color: var(--zk-blue);
|
|
}
|
|
|
|
/* ---- property rows ----
|
|
Facts read as label/value pairs on hairlines rather than as a wall of filled
|
|
boxes: the tint was carrying no information and made every field shout as
|
|
loudly as every other. */
|
|
.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: 0.78rem;
|
|
color: var(--zk-muted);
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.prop dd {
|
|
margin: 0;
|
|
font-size: 0.875rem;
|
|
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: 0.66rem;
|
|
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: 0.6rem;
|
|
font-weight: 500;
|
|
letter-spacing: 0.12em;
|
|
text-transform: uppercase;
|
|
color: var(--zk-grey);
|
|
}
|
|
|
|
.lead__closed {
|
|
font-size: 0.7rem;
|
|
color: var(--zk-grey);
|
|
}
|
|
|
|
.lead__stage strong {
|
|
font-size: 0.9rem;
|
|
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: 0.75rem;
|
|
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: 0.6rem;
|
|
font-weight: 500;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--zk-grey);
|
|
}
|
|
|
|
.lead__meta dd {
|
|
margin: 0;
|
|
font-size: 0.85rem;
|
|
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: 1rem;
|
|
font-weight: 500;
|
|
color: var(--zk-blue-dark);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* ── Overview ────────────────────────────────────────────────────────────
|
|
The morning page. Three blocks, in the order a person cares: what is
|
|
waiting on me, what is running out of time, and how the book looks.
|
|
Nothing here is a chart — a demo book is small enough that a number and a
|
|
name beat any visualisation of them. */
|
|
|
|
.sec {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 10px;
|
|
margin: 28px 0 12px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
letter-spacing: .02em;
|
|
text-transform: uppercase;
|
|
color: var(--zk-muted);
|
|
}
|
|
.sec:first-of-type { margin-top: 8px; }
|
|
.sec__hint {
|
|
font-size: 12px;
|
|
font-weight: 400;
|
|
letter-spacing: 0;
|
|
text-transform: none;
|
|
color: var(--zk-grey);
|
|
}
|
|
|
|
/* Three cards, never more. These are the only queues a person acts in, and
|
|
keeping the row short is what makes it readable at a glance. */
|
|
.cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
.card {
|
|
display: block;
|
|
padding: 18px 18px 16px;
|
|
border: 1px solid var(--zk-line);
|
|
border-radius: var(--r-md);
|
|
background: var(--zk-white);
|
|
text-decoration: none;
|
|
color: inherit;
|
|
transition: border-color .12s, box-shadow .12s, transform .12s;
|
|
}
|
|
.card:hover { border-color: var(--zk-blue-light); box-shadow: 0 2px 10px rgba(35,54,111,.07); transform: translateY(-1px); }
|
|
.card__n { display: block; font-size: 34px; line-height: 1; font-weight: 300; color: var(--zk-grey); }
|
|
.card__t { display: block; margin-top: 10px; font-size: 15px; font-weight: 500; color: var(--zk-ink); }
|
|
.card__w { display: block; margin-top: 3px; font-size: 12.5px; color: var(--zk-muted); }
|
|
|
|
/* A queue with something in it earns colour. One with nothing stays quiet —
|
|
the point of the row is that the eye lands on the number that matters. */
|
|
.card.is-live { border-color: var(--zk-amber-line); background: var(--zk-amber-tint); }
|
|
.card.is-live .card__n { color: var(--zk-amber-ink); font-weight: 400; }
|
|
|
|
/* Every stage, small. Zeroes are shown rather than hidden: a stage that has
|
|
quietly stopped receiving leads is only visible if its zero is on screen. */
|
|
.tally {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(124px, 1fr));
|
|
gap: 8px;
|
|
}
|
|
.tally__i {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
padding: 10px 12px;
|
|
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;
|
|
}
|
|
.tally__i:hover { border-color: var(--zk-blue-light); background: var(--zk-tint); }
|
|
.tally__n { font-size: 18px; font-weight: 500; color: var(--zk-ink); min-width: 1.2em; }
|
|
.tally__l { font-size: 12.5px; color: var(--zk-muted); line-height: 1.25; }
|
|
.tally__i.is-zero { background: transparent; }
|
|
.tally__i.is-zero .tally__n { color: var(--zk-grey); font-weight: 300; }
|
|
.tally__i.is-zero .tally__l { color: var(--zk-grey); }
|
|
|
|
@media (max-width: 720px) {
|
|
.cards { grid-template-columns: 1fr; }
|
|
}
|