canon extract caesar
CAESAR WEB CANON — verbatim extraction from products/caesar/screens/dashboard.html
Section titled “CAESAR WEB CANON — verbatim extraction from products/caesar/screens/dashboard.html”Source: /Users/Ridwan/Pictures/Claude/Rechaj 2.0/rechaj-design-system/products/caesar/screens/dashboard.html (6,260 lines: CSS lines 8–707, static markup 709–1161, JS 1162–6258). All CSS read in full; markup snippets are copied verbatim from the file (SVG path data occasionally elided with … for length — never elide when building real screens, copy the icon from the file).
File header comment (the self-declared intent, verbatim):
/* ═══════════════════════════════════════ CAESAR — Dashboard. Zoho Books FEATURES & FLOWS, redesigned in the Rechaj design system: collapsible/expandable sidebar (.sb-*), our topbar, KPI stat cards, charts, line tabs, circular tinted icons. Brand green #01E46F, General Sans, shadow-only cards (no strokes), 4px grid. Prefix: cz-*. Dropped per finance walkthrough: Items, Time Tracking, Tax Returns, Configure Features list, Projects widget, Account Watchlist, Getting Started.═══════════════════════════════════════ */Font load: <link href="https://api.fontshare.com/v2/css?f[]=general-sans@400,500,600,700&display=swap" rel="stylesheet"> — General Sans, weights 400/500/600/700 only.
1. Tokens
Section titled “1. Tokens”The full :root block, verbatim:
:root { --brand:#01E46F; --brand-dim:rgba(1,228,111,.10); --brand-dim-h:rgba(1,228,111,.16); --g-dark:#05853E; --blue:#0090FF; --blue-dim:rgba(0,144,255,.12); --amber:#FFC53D; --amber-text:#9A5B00; --amber-dim:rgba(255,197,61,.18); --red:#E32627; --red-dim:rgba(227,38,39,.10); --purple:#7C3AED; --purple-dim:rgba(124,58,237,.12); --ink:#0F1D13; --bg:#F5F6FA; --surface:#fff; --hover:#F0F1F4; --grey1:#FAFAFA; --grey2:#F4F4F5; --border:#E8E8EC; --border-soft:#EFEFF2; --sh-card:2px 3px 16px 0 rgba(193,197,210,.18); --r-sm:6px; --r-md:8px; --r-lg:12px; --r-xl:16px; --r-f:999px; --side-w:248px; --side-w-collapsed:68px; --topbar-h:60px; --input-bg:#fff;}(note: --text-1:#111; --text-2:#555; --text-3:#888; sit on the --ink line group in the file — full line order above is verbatim; the text tokens appear between --ink and --bg:)
--ink:#0F1D13; --bg:#F5F6FA; --surface:#fff; --hover:#F0F1F4; --grey1:#FAFAFA; --grey2:#F4F4F5; --border:#E8E8EC; --border-soft:#EFEFF2; --text-1:#111; --text-2:#555; --text-3:#888;Dark-mode token overrides (verbatim — every component adapts automatically because it uses var(--*)):
body.dark { color-scheme:dark; --ink:#EEF1F5; --bg:#0F1116; --surface:#181B21; --hover:#262B34; --grey1:#1F232B; --grey2:#262B34; --border:#2D323C; --border-soft:#252932; --text-1:#EAECF0; --text-2:#A6AEBA; --text-3:#7E8591; --sh-card:0 2px 14px 0 rgba(0,0,0,.45); --g-dark:#35D982; --brand-dim:rgba(1,228,111,.15); --brand-dim-h:rgba(1,228,111,.24); --blue:#3BA5FF; --blue-dim:rgba(0,144,255,.20); --amber:#FFD166; --amber-text:#F0B94A; --amber-dim:rgba(255,197,61,.16); --red:#FF5B5D; --red-dim:rgba(255,91,93,.16); --purple:#A78BFA; --purple-dim:rgba(167,139,250,.18); --input-bg:#20242C;}body.dark input, body.dark select, body.dark textarea { background:var(--input-bg); color:var(--text-1); border-color:var(--border); }body.dark .dlg { background:var(--surface); }body.dark .dlg-overlay { background:rgba(0,0,0,.6); }body.dark .sb-tooltip, body.dark .chart-tip, body.dark .inc-bar .inc-tip { background:#2C313B; color:#fff; }body.dark .alert-toast--default { background:rgba(0,144,255,.16); } body.dark .alert-toast--default .alert-toast-badge, body.dark .alert-toast--default .alert-toast-text { color:#6FB8FF; }body.dark .alert-toast--gray { background:rgba(255,255,255,.10); } body.dark .alert-toast--gray .alert-toast-badge, body.dark .alert-toast--gray .alert-toast-text { color:#C4CBD6; }body.dark .alert-toast--error { background:rgba(255,91,93,.16); } body.dark .alert-toast--error .alert-toast-badge, body.dark .alert-toast--error .alert-toast-text { color:#FF8E90; }body.dark .alert-toast--warning { background:rgba(255,197,61,.16); } body.dark .alert-toast--warning .alert-toast-badge, body.dark .alert-toast--warning .alert-toast-text { color:#F0C24A; }body.dark .alert-toast--success { background:rgba(1,228,111,.15); } body.dark .alert-toast--success .alert-toast-badge, body.dark .alert-toast--success .alert-toast-text { color:#4ADE80; }/* theme-toggle icon swap (moon in light, sun in dark) */body:not(.dark) #theme-toggle .ico-sun { display:none; }body.dark #theme-toggle .ico-moon { display:none; }Base reset + typography (verbatim):
*,*::before,*::after { box-sizing:border-box; margin:0; padding:0; }html { font-size:14px; -webkit-font-smoothing:antialiased; overflow-y:scroll; scrollbar-gutter:stable; }body { font-family:'General Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; font-size:14px; color:var(--text-1); background:var(--bg); line-height:1.5; }a { text-decoration:none; color:inherit; }b,strong { font-weight:600; }button { cursor:pointer; border:none; background:none; font-family:inherit; }Key facts: base font-size 14px; b/strong are remapped to 600 (bold text is never 700 by default); html forces a scrollbar with stable gutter so screen swaps never shift layout.
2. Shell
Section titled “2. Shell”Structure (ASCII)
Section titled “Structure (ASCII)”<body> ← scrolls (the ONLY vertical scroller for content)├── <aside class="sidebar"> ← position:fixed, top/left/bottom 0, width 248px (68px collapsed)│ ├── .sb-logo ← 60px tall (=topbar-h), brand SVG + "Caesar" wordmark│ ├── <nav class="sb-body"> ← flex:1, its own overflow-y:auto (5px scrollbar)│ │ .sb-item / .sb-item.has-sub + .sb-sub > .sb-subitem…│ └── .sb-foot ← Settings, pinned bottom, border-top├── <div class="main"> ← margin-left:248px (68px collapsed), min-height:100vh, flex column│ ├── <header class="topbar"> ← position:sticky top:0, 60px, z-40│ └── <div class="page app-screen show" id="scr-dashboard"> … </div>│ <div class="page app-screen" id="scr-banking"> … </div>│ <div class="page app-screen" id="scr-…"> (≈50 empty screen shells, filled by JS) </div>├── .qc-overlay#qc ← Quick-Create mega menu (fixed, z-200)└── <div id="modal-root"> ← .dlg dialogs injected here (z-400) #toast-container ← created lazily by toast() (fixed top-center, z-9999)Scroll model
Section titled “Scroll model”- The page body scrolls;
.topbaris sticky within.main;.sidebaris fixed and has its own internal scroll on.sb-body. - Every “page” is a
.page.app-screendiv; navigation swaps screens by toggling.show(display:none↔block) and callswindow.scrollTo(0,0)— a single-file SPA, no URL routing. - Modal open sets
document.body.style.overflow='hidden'; the.dlg-overlayitself isoverflow-y:autoso tall dialogs scroll inside the overlay. - Wide tables scroll horizontally inside
.tscroll { overflow-x:auto; }— never the page.
Shell CSS (verbatim)
Section titled “Shell CSS (verbatim)”/* ── SIDEBAR ── */.sidebar { position:fixed; top:0; left:0; bottom:0; width:var(--side-w); z-index:50; background:var(--surface); border-right:1px solid var(--border); display:flex; flex-direction:column; transition:width .18s ease; }.sb-logo { display:flex; align-items:center; gap:10px; height:var(--topbar-h); padding:0 20px; border-bottom:1px solid var(--border-soft); flex-shrink:0; }.sb-logo-name { font-size:18px; font-weight:700; letter-spacing:-.02em; color:var(--ink); white-space:nowrap; }.sb-body { flex:1; overflow-y:auto; overflow-x:hidden; padding:10px 12px; }.sb-body::-webkit-scrollbar { width:5px; } .sb-body::-webkit-scrollbar-thumb { background:var(--border); border-radius:4px; }
/* ── MAIN ── */.main { margin-left:var(--side-w); transition:margin-left .18s ease; min-height:100vh; display:flex; flex-direction:column; }body.sb-collapsed .main { margin-left:var(--side-w-collapsed); }
.topbar { position:sticky; top:0; z-index:40; height:var(--topbar-h); background:var(--surface); border-bottom:1px solid var(--border); display:flex; align-items:center; gap:16px; padding:0 24px; }
/* ── PAGE ── */.page { max-width:1320px; width:100%; margin:0 auto; padding:24px 32px 48px; }
/* ── App screens ── */.app-screen { display:none; } .app-screen.show { display:block; }Key dimensions: sidebar 248 / 68 collapsed; topbar 60; page column max-width 1320px, padding 24px 32px 48px, centered. Z-index ladder: topbar 40 < sidebar 50 < gsearch panel 60 < dd-menu 120 < quick-create 200 < dlg-overlay 400 < toasts/tooltips 9999.
3. Atoms
Section titled “3. Atoms”3.1 Sidebar
Section titled “3.1 Sidebar”Canonical selectors: .sidebar, .sb-logo, .sb-logo-name, .sb-body, .sb-item (+.active, .has-sub, .open), .sb-label, .sb-chev, .sb-sub (+.show), .sb-subitem (+.active), .sb-foot, .sb-badge, body.sb-collapsed, .sb-tooltip.
.sb-item { display:flex; align-items:center; gap:12px; height:38px; padding:0 12px; border-radius:var(--r-md); font-size:13.5px; color:var(--text-2); cursor:pointer; user-select:none; transition:background .12s,color .12s; white-space:nowrap; position:relative; }.sb-item:hover { background:var(--hover); color:var(--text-1); }.sb-item.active { background:var(--brand-dim); color:var(--g-dark); font-weight:500; }.sb-item svg.lead { flex-shrink:0; }.sb-label { flex:1; }.sb-chev { transition:transform .18s; color:var(--text-3); }.sb-item.open .sb-chev { transform:rotate(90deg); }.sb-sub { display:none; margin:2px 0 4px; }.sb-sub.show { display:block; }.sb-subitem { display:flex; align-items:center; height:34px; padding:0 12px 0 46px; border-radius:var(--r-md); font-size:13px; color:var(--text-2); cursor:pointer; white-space:nowrap; }.sb-subitem:hover { background:var(--hover); color:var(--text-1); }.sb-subitem.active { color:var(--g-dark); font-weight:500; }.sb-subitem .sb-badge { margin-left:auto; }.sb-foot { border-top:1px solid var(--border-soft); padding:10px 12px; flex-shrink:0; }.sb-badge { font-size:11px; font-weight:600; color:var(--amber-text); background:var(--amber-dim); border-radius:var(--r-f); padding:2px 8px; }.sb-item .sb-badge { margin-left:auto; }
/* collapsed rail */body.sb-collapsed .sidebar { width:var(--side-w-collapsed); }body.sb-collapsed .sb-logo-name,body.sb-collapsed .sb-label,body.sb-collapsed .sb-chev,body.sb-collapsed .sb-item .sb-badge { display:none; }body.sb-collapsed .sb-logo { justify-content:center; padding:0; }body.sb-collapsed .sb-item { justify-content:center; padding:0; }body.sb-collapsed .sb-sub { display:none !important; }/* collapsed hover tooltip — Atlas .sb-tooltip (fixed pill, escapes sidebar overflow) */.sb-tooltip { position:fixed; display:none; background:var(--ink); color:#fff; padding:6px 10px; border-radius:6px; font-size:12.5px; font-weight:500; white-space:nowrap; transform:translateY(-50%); z-index:9999; pointer-events:none; box-shadow:0 4px 12px rgba(0,0,0,.15); }/* collapse/expand toggle icon swaps with state */body.sb-collapsed .ico-expanded, body:not(.sb-collapsed) .ico-collapsed { display:none; }Markup (verbatim, one plain item, one expandable group, and the pinned foot):
<aside class="sidebar"> <div class="sb-logo"> <svg width="28" height="28" viewBox="0 0 57 57" fill="none" …aria-label="Rechaj" style="flex-shrink:0"> <path d="M39.6964 8.40009…" fill="#0F1D0F"/> <path d="M17.597 48.0272…" fill="#01E46F"/> </svg> <span class="sb-logo-name">Caesar</span> </div> <nav class="sb-body"> <div class="sb-item active" data-tip="Dashboard" onclick="navTo('dashboard',this)"><svg class="lead" width="18" height="18" …></svg><span class="sb-label">Dashboard</span></div>
<div class="sb-item has-sub" data-tip="Sales" onclick="toggleSub(this)"><svg class="lead" width="18" height="18" …></svg><span class="sb-label">Sales</span><svg class="sb-chev" width="14" height="14" …><polyline points="9 18 15 12 9 6"/></svg></div> <div class="sb-sub"> <div class="sb-subitem" data-screen onclick="openCustomers()">Customers</div><div class="sb-subitem" data-screen onclick="openEstimates()">Estimates</div>… </div> </nav> <div class="sb-foot"> <div class="sb-item" data-tip="Settings" onclick="openSettings('organization')">…<span class="sb-label">Settings</span></div> </div></aside>Usage notes: nav icons are 18×18 stroke 1.9 lucide-style inline SVGs with class lead; group chevron is 14×14 rotating 90° when .open. Sub-menus are an accordion — only one open at a time (toggleSub). Active top item = brand-dim bg + g-dark text at weight 500; active subitem is text-color only (no bg). data-tip drives both the collapsed-rail hover tooltip and active-state lookup. Sub-items without a built screen keep no data-screen attribute and fall through to a “… is not in this prototype” toast — never a dead click. The sidebar auto-collapses when navigating two levels deep (detail/form pages), stays expanded on list pages; expanding again re-opens the active section’s submenu.
3.2 Topbar / header
Section titled “3.2 Topbar / header”Selectors: .topbar, .crumb-toggle, .crumbs > .crumb-item (+.crumb-current, .link) + .crumb-sep, .tb-search-wrap > .tb-search + .gsearch-panel, .tb-spacer, .ds-btn-primary, .icon-action (+.dot), .user-pill, .avatar, .user-name, .user-role.
.crumb-toggle { width:34px; height:34px; border-radius:var(--r-md); display:flex; align-items:center; justify-content:center; color:var(--text-2); }.crumb-toggle:hover { background:var(--hover); color:var(--text-1); }.crumb { font-size:15px; font-weight:600; color:var(--text-1); }.crumbs { display:flex; align-items:center; gap:8px; font-size:15px; min-width:0; }.crumb-item { font-weight:500; color:var(--text-3); white-space:nowrap; }.crumb-item.crumb-current { font-weight:600; color:var(--text-1); }.crumb-item.link { color:var(--text-3); font-weight:500; cursor:pointer; }.crumb-item.link:hover { color:var(--g-dark); }.crumb-sep { color:#C9CED6; flex-shrink:0; }.tb-search-wrap { flex:1; max-width:420px; position:relative; }.tb-search { height:38px; display:flex; align-items:center; gap:8px; padding:0 12px; background:var(--bg); border-radius:var(--r-md); color:var(--text-3); font-size:13px; }.tb-search input { flex:1; border:none; background:none; outline:none; font-family:inherit; font-size:13px; color:var(--text-1); }.gsearch-panel { display:none; position:absolute; top:46px; left:0; right:0; background:var(--surface); border-radius:var(--r-md); box-shadow:var(--sh-pop,0 8px 28px rgba(15,23,42,.16)); padding:6px; max-height:420px; overflow-y:auto; z-index:60; }.gsearch-panel.open { display:block; }.gs-group-t { font-size:11px; font-weight:600; color:var(--text-3); letter-spacing:.02em; padding:8px 10px 4px; }.gs-item { display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:var(--r-sm,8px); cursor:pointer; }.gs-item:hover, .gs-item.gs-cur { background:var(--grey1); }.gs-ic { width:28px; height:28px; border-radius:50%; flex-shrink:0; display:flex; align-items:center; justify-content:center; background:var(--grey1); color:var(--text-2); }.gs-item:hover .gs-ic, .gs-item.gs-cur .gs-ic { background:var(--surface); }.gs-main { flex:1; min-width:0; }.gs-t { font-size:13px; color:var(--text-1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.gs-t b { font-weight:600; }.gs-m { font-size:11.5px; color:var(--text-3); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.gs-amt { font-size:12.5px; font-weight:600; color:var(--text-2); flex-shrink:0; }.gs-empty { padding:18px 12px; text-align:center; font-size:12.5px; color:var(--text-3); }.tb-spacer { flex:1; }.ds-btn-primary { display:inline-flex; align-items:center; justify-content:center; width:38px; height:38px; border-radius:var(--r-md); background:var(--brand); color:#111; }.ds-btn-primary:hover { background:#00c45b; }.icon-action { width:38px; height:38px; border-radius:var(--r-md); border:1px solid var(--border); background:var(--surface); display:flex; align-items:center; justify-content:center; color:var(--text-2); cursor:pointer; position:relative; }.icon-action:hover { background:var(--hover); color:var(--text-1); }.icon-action .dot { position:absolute; top:7px; right:8px; width:7px; height:7px; border-radius:50%; background:var(--red); border:1.5px solid #fff; }.user-pill { display:flex; align-items:center; gap:8px; height:42px; padding:4px 12px 4px 4px; border-radius:var(--r-f); background:var(--bg); cursor:pointer; }.avatar { width:32px; height:32px; border-radius:50%; background:var(--brand-dim); color:var(--g-dark); display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:600; flex-shrink:0; }.user-name { font-size:13px; font-weight:600; color:var(--text-1); line-height:1.1; }.user-role { font-size:11px; color:var(--text-3); }Markup (verbatim, condensed SVGs):
<header class="topbar"> <div class="crumb-toggle" onclick="toggleSidebar()" title="Toggle sidebar"> <svg class="ico-expanded" width="20" height="20" …></svg> <svg class="ico-collapsed" width="20" height="20" …></svg> </div> <nav class="crumbs" id="crumbs"><span class="crumb-item crumb-current">Dashboard</span></nav> <div class="tb-search-wrap"> <div class="tb-search"> <svg width="16" height="16" …search icon…></svg> <input id="gsearch" autocomplete="off" placeholder="Search invoices, bills, contacts, accounts ( / )" oninput="globalSearch(this.value)" onfocus="globalSearch(this.value)" onkeydown="gsearchKey(event)"> </div> <div class="gsearch-panel" id="gsearch-panel"></div> </div> <div class="tb-spacer"></div> <button class="ds-btn-primary" title="Create new" onclick="toggleQC(event)"><svg width="18" height="18" …plus…></svg></button> <button class="icon-action" id="theme-toggle" title="Toggle dark mode" onclick="toggleTheme()">…moon/sun svgs…</button> <div class="dd"> <div class="icon-action dd-trigger"><svg …bell…></svg><span class="dot" id="notif-dot"></span></div> <div class="dd-menu" style="min-width:312px"> <div class="dd-head"><span class="nm">Notifications</span><span class="link" onclick="notifMarkAll(event)">Mark all read</span></div> <div class="dd-divider"></div> <div class="dd-note" onclick="closeAllDD();openBills()"><span class="nt-dot"></span><div><div class="nt-t">Bill <b>BILL-0312</b> from Telpo awaits approval</div><div class="nt-m">10 minutes ago</div></div></div> … </div> </div> <div class="dd"> <div class="user-pill dd-trigger"> <div class="avatar">FO</div> <div><div class="user-name">Francis Ogunlaja</div><div class="user-role">Finance Lead</div></div> <svg class="dd-cv" width="15" height="15" …chevron…></svg> </div> <div class="dd-menu" style="min-width:236px"> <div class="dd-head" style="display:block"><div class="nm">Francis Ogunlaja</div><div class="em">francis.ogunlaja@mocx.co</div></div> <div class="dd-divider"></div> <div class="dd-opt" onclick="openSettings('profile')">…My profile</div> <div class="dd-opt" onclick="openSettings('organization')">…Settings</div> <div class="dd-divider"></div> <div class="dd-opt danger" onclick="location.href='login.html'">…Sign out</div> </div> </div></header>Usage notes: topbar order is fixed — sidebar toggle → breadcrumbs → global search (max 420px) → spacer → green square + button (quick-create) → theme toggle → bell w/ red dot → user pill. Breadcrumb trail is rendered by renderCrumbs(name, extra) from CRUMB_TRAILS; earlier crumbs are clickable .crumb-item.link, last is .crumb-current. / focuses the global search from anywhere; results are grouped (Contacts / Transactions / Accounts) with keyboard ↑↓/Enter support.
3.3 Page head
Section titled “3.3 Page head”Selectors: .pg-head, .pg-title, .pg-sub, .pg-actions, .period.
.pg-head { display:flex; align-items:flex-end; justify-content:space-between; gap:20px; margin-bottom:24px; }.pg-title { font-size:23px; font-weight:700; letter-spacing:-.02em; color:var(--ink); }.pg-sub { font-size:13px; color:var(--text-3); margin-top:4px; }.period { display:inline-flex; align-items:center; gap:8px; height:38px; padding:0 16px; border-radius:var(--r-md); background:var(--surface); box-shadow:var(--sh-card); font-size:13px; font-weight:500; cursor:pointer; }.period svg { color:var(--text-3); }.pg-actions { display:flex; gap:10px; flex-wrap:wrap; justify-content:flex-end; }.form-actions { display:flex; gap:10px; flex-wrap:wrap; }Markup (verbatim, dashboard variant with period dropdown; list variant uses .pg-actions with buttons):
<div class="pg-head"> <div> <div class="pg-title">Hello, Francis</div> <div class="pg-sub">Financial position for MocX Group · as of 18 Jun 2026</div> </div> <div class="dd" data-dd="period"> <div class="period dd-trigger"><span class="dd-label">This Fiscal Year</span> <svg class="dd-cv" width="14" height="14" …chevron…></svg></div> </div></div><div class="pg-head"> <div><div class="pg-title">Banking</div><div class="pg-sub">Accounts, balances and reconciliation · MocX Group</div></div> <div class="pg-actions"> <button class="btn-sec" onclick="importStmtModal()">…Import Statement</button> <button class="btn-pri" onclick="addAccountModal()">…Add Bank / Credit Card</button> </div></div>Usage notes: title 23/700 (display exception), sub 13/text-3 joined with the DS middot · (never em dashes). Detail pages reuse the head as .cust-detail-head with a 21px title, inline status badge, and .txn-actions button row. On -form screens the .form-actions row is moved by showScreen() into the .pg-head as pg-actions (actions sit top-right of a form page).
3.4 Tabs & segmented controls — every variant
Section titled “3.4 Tabs & segmented controls — every variant”(a) .seg — pill segmented control (view switcher inside a card; e.g. Account Summary ↔ Transactions, Users ↔ Roles, detail-page Overview/Comments/Transactions/Statement tabs):
.seg { display:flex; gap:2px; background:var(--bg); border-radius:var(--r-md); padding:4px; }.seg button { font-size:12.5px; font-weight:500; color:var(--text-2); padding:4px 12px; border-radius:6px; }.seg button.on { background:var(--surface); color:var(--g-dark); box-shadow:var(--sh-card); }Markup (from txnScreenHTML / customerDetailHTML — note the detail-page usage overrides the track to --hover):
<div class="seg" id="txn-viewseg"><button onclick="bankView('gtbank','summary')">Account Summary</button><button class="on" onclick="bankView('gtbank','txns')">Transactions</button></div>
<div class="seg" id="cust-tabs" style="width:fit-content;margin-bottom:16px;background:var(--hover)"> <button class="on" data-ct="Overview" onclick="custDetailTab('Overview')">Overview</button>…</div>(b) .txn-tab — line tabs with count pills (status filter over a table: All / Uncategorized n / Categorized):
.txn-tabs { display:flex; gap:2px; }.txn-tab { font-size:13px; font-weight:500; color:var(--text-2); height:38px; padding:0 14px; cursor:pointer; display:inline-flex; align-items:center; gap:8px; position:relative; white-space:nowrap; transition:color .13s; }.txn-tab::after { content:''; position:absolute; left:0; right:0; bottom:-1px; height:2px; background:var(--brand); border-radius:2px 2px 0 0; opacity:0; transition:opacity .13s; }.txn-tab:hover { color:var(--text-1); }.txn-tab.on { color:var(--g-dark); font-weight:600; }.txn-tab.on::after { opacity:1; }.txn-tab .ct { font-size:11px; font-weight:600; background:var(--grey2); color:var(--text-3); border-radius:var(--r-f); padding:1px 7px; }.txn-tab.on .ct { background:var(--brand-dim); color:var(--g-dark); }<div class="txn-tabs" id="txn-tabs"><div class="txn-tab on" data-st="all" onclick="bankTab('gtbank','all')">All</div><div class="txn-tab" data-st="uncat" onclick="bankTab('gtbank','uncat')">Uncategorized <span class="ct">4</span></div><div class="txn-tab" data-st="cat" onclick="bankTab('gtbank','cat')">Categorized</div></div>(c) .form-tab — underlined form-section tabs (multi-section forms, e.g. customer form Other Details / Address / Contact Persons / Remarks):
.form-tabs { display:flex; gap:2px; border-bottom:1px solid var(--border-soft); margin:8px 0 20px; flex-wrap:wrap; }.form-tab { font-size:13px; font-weight:500; color:var(--text-2); padding:8px 16px; cursor:pointer; border-bottom:2px solid transparent; margin-bottom:-1px; }.form-tab:hover { color:var(--text-1); }.form-tab.on { color:var(--g-dark); border-bottom-color:var(--brand); font-weight:600; }.form-sec { display:none; } .form-sec.on { display:block; }(d) .sn-item — settings vertical nav (sticky left rail inside the Settings page):
.settings-nav { display:flex; flex-direction:column; gap:2px; position:sticky; top:88px; }.sn-item { display:flex; align-items:center; gap:10px; padding:9px 12px; border-radius:var(--r-md); font-size:13.5px; font-weight:500; color:var(--text-2); cursor:pointer; transition:background .12s,color .12s; }.sn-item svg { color:var(--text-3); flex-shrink:0; }.sn-item:hover { background:var(--hover); color:var(--text-1); }.sn-item.on { background:var(--brand-dim); color:var(--g-dark); font-weight:600; }.sn-item.on svg { color:var(--g-dark); }(e) .rpt-cat — category rail (Reports Center / Documents left nav, scroll-to or filter):
.rpt-cat { font-size:13px; color:var(--text-2); padding:8px 12px; border-radius:var(--r-md); cursor:pointer; }.rpt-cat:hover { background:var(--hover); color:var(--text-1); } .rpt-cat.on { background:var(--brand-dim); color:var(--g-dark); font-weight:600; }When to use which: .seg = switching views of the same data (2–4 options, inside a card head or detail page); .txn-tabs = status filters above a table (with live count pills); .form-tabs = sections within a form card; .sn-item/.rpt-cat = page-level left rail on settings/reports/documents. The active state is always brand-dim bg + --g-dark text (or brand underline for line tabs) — never a solid green fill.
3.5 Table / list system
Section titled “3.5 Table / list system”Selectors: .tbl-card, .tbl-card-head, .tscroll, .tbl, th.sortable + .sort-ic, .acct/.acct-ic/.acct-nm/.acct-ty, .amt, .muted, .row-arrow, .tbl-foot, .txn-search, .bulk-bar, .row-menu-btn, .st-dot, tr.inactive, .cust-nm-cell, .txn-link-row, .doc-link.
.tbl { width:100%; border-collapse:separate; border-spacing:0; font-size:13.5px; }.tbl th { height:48px; padding:0 16px; font-size:12px; font-weight:500; text-transform:uppercase; letter-spacing:.06em; color:var(--text-2); text-align:left; white-space:nowrap; background:var(--grey2); }.tbl th:first-child { border-radius:8px 0 0 8px; } .tbl th:last-child { border-radius:0 8px 8px 0; }.tbl th.r, .tbl td.r { text-align:right; }.tbl tbody tr { transition:background .1s; } .tbl tbody tr:hover td { background:var(--hover); }.tbl tbody tr:has([onclick*="open"]) { cursor:pointer; }.tbl td { padding:12px 16px; font-size:13.5px; color:var(--text-1); border-bottom:1px solid var(--border-soft); vertical-align:middle; }.tbl tbody tr:last-child td { border-bottom:none; }.acct { display:flex; align-items:center; gap:12px; }.acct-ic { width:38px; height:38px; border-radius:50%; display:flex; align-items:center; justify-content:center; flex-shrink:0; }.acct-nm { font-size:14px; font-weight:600; color:var(--text-1); } .acct-ty { font-size:11.5px; color:var(--text-3); }.amt { font-weight:600; color:var(--ink); } .muted { color:var(--text-3); }.row-arrow { color:var(--text-3); } .tbl tbody tr:hover .row-arrow { color:var(--g-dark); }.tbl-foot { display:flex; align-items:center; justify-content:space-between; padding:16px; border-top:1.5px solid var(--border); }.tbl-foot .lbl { font-size:12.5px; color:var(--text-3); } .tbl-foot .val { font-size:16px; font-weight:700; color:var(--ink); }.tbl-card { background:var(--surface); border-radius:var(--r-xl); padding:16px; }.tbl-card-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; }.tscroll { overflow-x:auto; }.st-dot { width:7px; height:7px; border-radius:50%; background:#30A46C; display:inline-block; margin-right:8px; vertical-align:middle; }.tbl tbody tr.inactive { opacity:.5; }.tbl tbody tr.inactive .st-dot { background:#C9CED6; }.row-menu-btn { width:30px; height:30px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; color:var(--text-3); cursor:pointer; }.row-menu-btn:hover { background:rgba(15,29,19,.09); color:var(--text-1); }.dd.open .row-menu-btn { background:rgba(15,29,19,.09); color:var(--text-1); }.txn-search { display:inline-flex; align-items:center; gap:8px; height:34px; padding:0 12px; background:var(--bg); border-radius:var(--r-md); color:var(--text-3); }.txn-search input { border:none; background:none; outline:none; font:inherit; font-size:13px; color:var(--text-1); width:160px; }.txn-search input::placeholder { color:var(--text-3); }.tbl thead th.sortable { cursor:pointer; user-select:none; white-space:nowrap; }.tbl thead th.sortable:hover { color:var(--text-1); }.sort-ic { display:inline-flex; vertical-align:middle; margin-left:4px; color:var(--text-3); }.sort-ic.on { color:var(--g-dark); }.bulk-bar { display:none; align-items:center; gap:16px; padding:10px 16px; background:var(--brand-dim); border-radius:var(--r-md); margin-bottom:12px; font-size:13px; font-weight:600; color:var(--g-dark); }.bulk-bar.show { display:flex; }.bulk-bar .ba { font-weight:500; color:var(--text-2); cursor:pointer; }.bulk-bar .ba:hover { color:var(--g-dark); }.bulk-bar .ba.danger { color:var(--red); } .bulk-bar .ba.danger:hover { color:var(--red); }.bulk-bar .x { margin-left:auto; cursor:pointer; color:var(--text-3); display:inline-flex; }.chk { width:16px; height:16px; accent-color:var(--brand); cursor:pointer; vertical-align:middle; }.cust-nm-cell { display:flex; align-items:center; gap:12px; cursor:pointer; }.txn-link-row { cursor:pointer; transition:background .12s; }.txn-link-row:hover { background:var(--grey1); }.doc-link { color:var(--g-dark); font-weight:600; cursor:pointer; }.doc-link:hover { text-decoration:underline; }Canonical list-card composition (verbatim skeleton from customersListHTML()):
<div class="tbl-card"> <div class="tbl-card-head"> <div class="dd" data-dd="custview"><div class="card-title dd-trigger" style="display:inline-flex;align-items:center;gap:6px;cursor:pointer">All Customers <svg…chev…></div><div class="dd-menu" style="min-width:210px">…view options…</div></div> <div style="display:flex;align-items:center;gap:12px"> <div class="txn-search"><svg…search…><input id="cust-search" placeholder="Search customers" oninput="custSearch(this.value)"></div> <div class="dd" data-dd="custmore"><div class="gear-btn dd-trigger" title="List options">…hamburger…</div><div class="dd-menu" style="min-width:200px;right:0"> <div class="dd-flt">Sort by</div>…sort options… <div class="dd-divider"></div> <div class="dd-opt" onclick="importModalAuto()"><span>Import Customers</span></div> <div class="dd-opt" onclick="exportCSVAuto()"><span>Export Customers</span></div> <div class="dd-opt" onclick="custRefresh()"><span>Refresh List</span></div> </div></div> </div> </div> <div class="bulk-bar" id="cust-bulk"><span id="cust-bulk-n">0 selected</span><span class="ba" onclick="custBulk('inactive')">Mark as inactive</span><span class="ba" onclick="custBulk('merge')">Merge</span><span class="ba" onclick="custBulk('email')">Send email</span><span class="ba danger" onclick="custBulk('delete')">Delete</span><span class="x" onclick="custClearSel()">…x…</span></div> <div class="tscroll"><table class="tbl"><thead><tr> <th style="width:34px"><input type="checkbox" class="chk" onclick="custToggleAll(this)"></th> <th class="sortable" onclick="custSort('name')">Name <span id="sic-name"></span></th> … <th class="r" style="width:40px"></th> </tr></thead><tbody id="cust-rows"></tbody></table></div></div>Row markup (verbatim from custRenderRows() — checkbox, avatar+dot+name cell, kebab menu in last cell):
<tr> <td><input type="checkbox" class="chk cust-cb" value="c1" onclick="custRowToggle(event)"></td> <td><div class="cust-nm-cell" onclick="openCustomer('c1')"><span class="cust-avatar" style="width:30px;height:30px;font-size:11px">AO</span><span class="st-dot" style="background:#30A46C"></span><span style="font-weight:600;color:var(--text-1)">Acme Ltd</span></div></td> <td>Acme Nigeria</td><td>ops@acme.ng</td><td><span class="muted">—</span></td> <td class="r amt">₦12,400,000</td><td class="r amt"><span class="muted">—</span></td> <td class="r"><div class="dd" style="display:inline-block"><div class="row-menu-btn dd-trigger">…kebab…</div><div class="dd-menu" style="min-width:200px">…Edit / View details / ─ / Mark as inactive…</div></div></td></tr>Usage notes: header row is a grey2 strip with pill-rounded outer corners (8px), uppercase 12/500 labels. Money cells: class="r amt" (right-aligned, 600, ink); absent values render <span class="muted">—</span>. Whole-row click opens the detail via a global delegated click handler that finds the first open* (non-Form) onclick inside the row and clicks it (rows inside #scr-banking/#scr-bank-txns wire their own handlers). Selecting checkboxes reveals the .bulk-bar (brand-dim strip with actions, danger action in red). Table footer .tbl-foot carries an aggregate (e.g. “Group cash position” + 16/700 value). Sort = th.sortable + injected .sort-ic span (green when active, direction arrow).
3.6 Buttons — every variant
Section titled “3.6 Buttons — every variant”.btn-pri { display:inline-flex; align-items:center; gap:8px; height:38px; padding:0 16px; border-radius:var(--r-md); background:var(--brand); color:#111; font-size:13.5px; font-weight:600; }.btn-pri:hover { background:#00c45b; }.btn-sec { display:inline-flex; align-items:center; gap:8px; height:38px; padding:0 16px; border-radius:var(--r-md); background:transparent; border:1px solid var(--border); color:var(--text-1); font-size:13.5px; font-weight:500; }.btn-sec:hover { background:var(--hover); border-color:var(--text-3); }.ds-btn-primary { display:inline-flex; align-items:center; justify-content:center; width:38px; height:38px; border-radius:var(--r-md); background:var(--brand); color:#111; }.ds-btn-primary:hover { background:#00c45b; }.icon-action { width:38px; height:38px; border-radius:var(--r-md); border:1px solid var(--border); background:var(--surface); display:flex; align-items:center; justify-content:center; color:var(--text-2); cursor:pointer; position:relative; }.gear-btn { width:38px; height:38px; border-radius:var(--r-md); display:inline-flex; align-items:center; justify-content:center; color:var(--text-2); background:transparent; border:1px solid var(--border); cursor:pointer; }.gear-btn:hover { color:var(--g-dark); background:var(--hover); border-color:var(--text-3); }.row-menu-btn { width:30px; height:30px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; color:var(--text-3); cursor:pointer; }.card-link { display:inline-flex; align-items:center; gap:4px; font-size:12.5px; font-weight:600; color:var(--g-dark); padding:6px 12px; border-radius:var(--r-f); background:var(--brand-dim); cursor:pointer; transition:background .12s; }.card-link:hover { background:var(--brand-dim-h); }.card-link svg { transition:transform .14s; }.card-link:hover svg { transform:translateX(2px); }.link-add { display:inline-flex; align-items:center; gap:4px; font-size:12.5px; font-weight:600; color:var(--g-dark); cursor:pointer; margin-top:4px; }.ext-link { color:var(--g-dark); text-decoration:none; font-weight:500; }.ext-link:hover { text-decoration:underline; }Markup examples (verbatim):
<button class="btn-pri" onclick="addAccountModal()"><svg width="16" height="16" …plus…></svg> Add Bank / Credit Card</button><button class="btn-sec" onclick="importStmtModal()"><svg width="16" height="16" …upload…></svg> Import Statement</button><div class="card-link" onclick="navTo('banking')">Go to Banking <svg width="13" height="13" …arrow-right…></svg></div><span class="link-add" onclick="invAddLine()">…plus… Add another line</span>Variant inventory & exact specs:
- Primary
.btn-pri: 38px tall, radius 8, brand green bg + dark #111 text (never white-on-green), 13.5/600, hover#00c45b. Icons 15–16px. - Secondary
.btn-sec: 38px, radius 8, transparent bg + 1px--borderoutline, 13.5/500, hover fills--hover. - Destructive: there is no dedicated
.btn-dangerclass — confirm dialogs restyle the primary inline:<button class="btn-pri" style="background:var(--red);color:#fff" …>(fromconfirmModal(…, danger=true)). Row/menu deletes use.dd-opt.danger(red text menu option). - Icon primary
.ds-btn-primary: 38×38 square radius-8 brand button (topbar quick-create “+”). - Icon neutral
.icon-action(38×38, bordered, white bg — topbar) and.gear-btn(38×38, bordered, transparent — page-level “more”/gear triggers). - Row kebab
.row-menu-btn: 30×30 circular ghost. - Tertiary/inline links:
.card-link(brand-dim pill, arrow slides on hover),.link-add(green + plus),.ext-link,.doc-link(green 600, underline on hover),.unc-link,.sum-viewmore,.go-txn,.acc-add. - Ghost dashed tile
.bank-add(add-account slot): transparent,1.5px dashed var(--border), hover turns brand.
.bank-add { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px; border-radius:var(--r-lg); background:transparent; box-shadow:none; border:1.5px dashed var(--border); color:var(--text-3); font-size:13px; font-weight:500; cursor:pointer; min-height:128px; }.bank-add:hover { color:var(--g-dark); border-color:var(--brand); }3.7 Badges & status pills
Section titled “3.7 Badges & status pills”(a) .badge — the standard badge (12/500, 6px radius, no uppercase, letter-spacing −0.01em; 20px tall):
.badge { display:inline-flex; align-items:center; gap:4px; height:20px; padding:0 8px; border-radius:var(--r-sm); font-size:12px; font-weight:500; letter-spacing:-.01em; }.b-red { background:var(--red-dim); color:var(--red); } .b-amber { background:var(--amber-dim); color:var(--amber-text); }.b-green { background:var(--brand-dim); color:var(--g-dark); } .b-blue { background:var(--blue-dim); color:var(--blue); }.b-grey { background:var(--grey2); color:var(--text-3); }.up { color:var(--g-dark); font-weight:600; } .down { color:var(--red); font-weight:600; }<span class="badge b-amber">₦304.5M overdue</span><span class="badge b-green">Reconciled</span><td class="r"><span class="badge b-blue">2</span></td>Document-status → color map (verbatim JS, use for every doc lifecycle):
var ST_BADGE={Paid:'b-green',Approved:'b-green',Accepted:'b-green',Invoiced:'b-green',Active:'b-green',Published:'b-green',Reimbursed:'b-blue',Open:'b-blue',Sent:'b-blue','Client Viewed':'b-blue','Pending Approval':'b-amber',Pending:'b-amber','Partially Paid':'b-amber',Expired:'b-grey',Billable:'b-amber',Unbilled:'b-grey','Non-Billable':'b-grey',Draft:'b-grey',Void:'b-grey','Written Off':'b-grey',Stopped:'b-grey',Closed:'b-grey',Overdue:'b-red',Declined:'b-red'};function stBadge(s){ return s?'<span class="badge '+(ST_BADGE[s]||'b-grey')+'">'+s+'</span>':'<span class="muted">—</span>'; }(b) .st-pill — compact transaction-state pill (11/600, used in banking txn tables):
.st-pill { font-size:11px; font-weight:600; border-radius:var(--r-sm); padding:2px 8px; letter-spacing:-.01em; }.st-cat { background:var(--brand-dim); color:var(--g-dark); } .st-unc { background:var(--amber-dim); color:var(--amber-text); }.st-man { background:var(--blue-dim); color:var(--blue); } .st-mat { background:rgba(48,164,108,.14); color:var(--g-dark); }<span class="st-pill st-cat">Categorized</span>(c) .feed-tag — fully-round pill on uncategorized feed rows: tag-match (green “Match found”) / tag-recog (blue “Recognized”):
.feed-tag { font-size:11px; font-weight:600; border-radius:var(--r-f); padding:2px 8px; white-space:nowrap; }.tag-match { background:rgba(48,164,108,.14); color:var(--g-dark); } .tag-recog { background:var(--blue-dim); color:var(--blue); }(d) .st-dot — 7px status dot before names (green #30A46C active / #C9CED6 inactive), CSS above in §3.5. (e) .sb-badge — sidebar count pill (amber, fully round). (f) .dep/.wd (green/red 600 amounts), .up/.down deltas, .acc-cnt (count pill in accordion titles: font-size:11px; font-weight:600; color:var(--text-3); background:var(--grey2); border-radius:20px; padding:1px 8px;).
3.8 KPI / stat cards
Section titled “3.8 KPI / stat cards”(a) Dashboard KPI .kpi:
.kpi-row { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:16px; }.kpi { background:var(--surface); border-radius:var(--r-lg); padding:16px 20px; display:flex; flex-direction:column; }.kpi-top { display:flex; align-items:flex-start; justify-content:space-between; margin-bottom:8px; }.kpi-lbl { font-size:12.5px; font-weight:500; color:var(--text-3); }.kpi-ic { width:38px; height:38px; border-radius:50%; display:flex; align-items:center; justify-content:center; flex-shrink:0; }.kpi-val { font-size:25px; font-weight:700; letter-spacing:-.02em; color:var(--ink); white-space:nowrap; }.kpi-bar { height:7px; border-radius:var(--r-f); background:#EDEFF2; display:flex; overflow:hidden; margin-top:12px; }.kpi-bar .seg-cur { background:var(--blue); cursor:pointer; } .kpi-bar .seg-od { background:var(--amber); cursor:pointer; }.kpi-foot { margin-top:auto; padding-top:12px; font-size:12px; color:var(--text-3); display:flex; align-items:center; gap:8px; }Tonal icon tints (shared across ALL stat/acct icons):
.ic-green { background:var(--brand-dim); color:var(--g-dark); }.ic-amber { background:var(--amber-dim); color:var(--amber-text); }.ic-blue { background:var(--blue-dim); color:var(--blue); }.ic-ink { background:rgba(15,29,19,.07); color:var(--ink); }.ic-grey { background:var(--grey2); color:var(--text-2); }.ic-purple { background:var(--purple-dim); color:var(--purple); }Markup (verbatim):
<div class="kpi"> <div class="kpi-top"><div class="kpi-lbl">Total Receivables</div><div class="kpi-ic ic-green"><svg width="19" height="19" …></svg></div></div> <div class="kpi-val">₦372.58M</div> <div class="kpi-bar"><div class="seg-cur" style="width:18%" data-tip-l="Current" data-tip-v="₦68.04M"></div><div class="seg-od" style="width:82%" data-tip-l="Overdue" data-tip-v="₦304.54M"></div></div> <div class="kpi-foot"><span class="badge b-amber">₦304.5M overdue</span> Current ₦68.04M</div></div>(b) Section stat tiles — .bk-stat (page-level, with shadow), .bk-s (grey tile inside a white overview card), .txn-s (account-detail summary):
.bk-stats { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:16px; }.bk-stat { background:var(--surface); border-radius:var(--r-lg); box-shadow:var(--sh-card); padding:16px 20px; }.bk-stat .l { font-size:12px; color:var(--text-3); display:flex; align-items:center; gap:8px; }.bk-stat .l .i { width:30px; height:30px; border-radius:50%; display:flex; align-items:center; justify-content:center; }.bk-stat .v { font-size:23px; font-weight:700; letter-spacing:-.02em; color:var(--ink); margin-top:10px; }.bk-stat .s { font-size:11.5px; color:var(--text-3); margin-top:4px; }.bk-statrow { display:grid; grid-template-columns:repeat(5,1fr); gap:16px; padding:16px 20px; }.bk-s { background:var(--grey1); border-radius:var(--r-lg); padding:16px 16px; display:flex; align-items:center; gap:12px; min-width:0; }.bk-s .i { width:38px; height:38px; border-radius:50%; display:flex; align-items:center; justify-content:center; flex-shrink:0; }.bk-s .meta { min-width:0; }.bk-s .l { font-size:12px; color:var(--text-3); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.bk-s .v { font-size:20px; font-weight:700; letter-spacing:-.02em; color:var(--ink); margin-top:4px; white-space:nowrap; }.bk-s.neg .v { color:var(--red); }.bk-s-unc { background:var(--amber-dim); }.bk-s-unc .l { white-space:normal; line-height:1.25; }.unc-n { min-width:44px; height:40px; padding:0 12px; border-radius:var(--r-md); background:rgba(255,197,61,.32); color:var(--amber-text); font-size:18px; font-weight:700; letter-spacing:-.02em; display:flex; align-items:center; justify-content:center; flex-shrink:0; }.unc-link { display:inline-flex; align-items:center; gap:4px; font-size:12px; font-weight:600; color:var(--g-dark); margin-top:4px; cursor:pointer; }.txn-summary { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:16px; }.txn-s { background:var(--surface); border-radius:var(--r-lg); padding:16px 16px; }.txn-s .l { font-size:12px; color:var(--text-3); } .txn-s .v { font-size:19px; font-weight:700; letter-spacing:-.02em; color:var(--ink); margin-top:4px; }.txn-s .v.neg { color:var(--red); }Usage notes: value sizes step down with hierarchy — 25 (dashboard KPI) → 23 (bk-stat) → 20 (bk-s) → 19 (txn-s/cf-v) → 18 (sum-stat). All values are 700 with −0.02em tracking (the sanctioned 700 usage). Icon is always a circular tonal chip (tinted bg + colored glyph, never solid). Negative amounts get .neg → red; negative sign uses U+2212 −, not hyphen.
3.9 Cards / panels
Section titled “3.9 Cards / panels”.card { background:var(--surface); border-radius:var(--r-lg); padding:20px 24px; }.card-h { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; }.card-title { font-size:15px; font-weight:600; color:var(--text-1); }.mini-period { display:inline-flex; align-items:center; gap:6px; font-size:12.5px; color:var(--text-2); padding:4px 10px; border-radius:var(--r-md); background:var(--bg); cursor:pointer; }.bank-card { background:var(--surface); border-radius:var(--r-lg); padding:16px 20px; }.bank-top { display:flex; align-items:center; gap:12px; margin-bottom:16px; }.bank-ic { width:40px; height:40px; border-radius:50%; display:flex; align-items:center; justify-content:center; flex-shrink:0; }.bank-nm { font-size:14px; font-weight:600; color:var(--text-1); } .bank-ty { font-size:11.5px; color:var(--text-3); }.bank-bal { font-size:22px; font-weight:700; letter-spacing:-.02em; color:var(--ink); }.bank-foot { display:flex; align-items:center; justify-content:space-between; margin-top:12px; font-size:12px; color:var(--text-3); }.sum-card { background:var(--surface); border-radius:var(--r-lg); padding:16px 20px; margin-bottom:16px; }.sum-card-t { font-size:13px; font-weight:600; color:var(--ink); margin-bottom:12px; display:flex; align-items:center; justify-content:space-between; }.txn-card { background:var(--surface); border-radius:var(--r-lg); padding:16px 20px; }.txn-card.flat { background:none; padding:0; }.cust-form-card { background:var(--surface); border-radius:var(--r-lg); padding:24px 24px; max-width:900px; }.est-doc { background:var(--surface); border-radius:var(--r-lg); padding:24px 28px; }.set-panel { background:var(--surface); border-radius:var(--r-lg); overflow:hidden; }.bk-overview { background:var(--surface); border-radius:var(--r-lg); margin-bottom:16px; }Card header pattern (verbatim):
<div class="card" style="margin-bottom:16px"> <div class="card-h"><div class="card-title">Cash Flow</div><div class="dd" data-dd="period"><div class="mini-period dd-trigger"><span class="dd-label">This Fiscal Year</span> <svg class="dd-cv" width="13" height="13" …chevron…></svg></div></div></div> …body…</div>Borders: NO card in this file has a border. Radius is --r-lg (12px) for content cards, --r-xl (16px) for .tbl-card and the quick-create panel. Cards are white --surface sitting on the grey --bg page and most carry no shadow at all (separation by contrast); shadow var(--sh-card) appears only on .bk-stat, .acct-card, .period and the active .seg button.on. Internal separations are border-top/bottom:1px solid var(--border-soft) on rows — never on the card box. The only dashed outline is the ghost .bank-add add-tile and .drop-zone (intentional affordances). Nested cards inside a white container flip to --grey1 (#txn-body .sum-card { background:var(--grey1); } — comment in file: “house each section as a grey1 card inside the white container (stat tiles above stay white to pop)”). Where a white card would sit on white, the wrapper goes transparent (.txn-card.flat).
3.10 Overlays: modal / mega-menu (no drawers, no bottom sheets on web)
Section titled “3.10 Overlays: modal / mega-menu (no drawers, no bottom sheets on web)”(a) .dlg centered web modal — THE confirmation/task surface on web:
.dlg-overlay { position:fixed; inset:0; background:rgba(15,29,19,.34); z-index:400; display:flex; align-items:flex-start; justify-content:center; padding:46px 20px; overflow-y:auto; opacity:0; pointer-events:none; transition:opacity .15s; }.dlg-overlay.show { opacity:1; pointer-events:auto; }.dlg { width:100%; max-width:540px; background:var(--surface); border-radius:var(--r-lg); box-shadow:0 24px 64px rgba(0,0,0,.24); overflow:hidden; transform:translateY(10px); transition:transform .15s; margin:auto 0; }.dlg-overlay.show .dlg { transform:none; }.dlg.dlg-md { max-width:600px; } .dlg.dlg-lg { max-width:760px; }.dlg.lg { max-width:780px; }.dlg-head { display:flex; align-items:flex-start; justify-content:space-between; padding:16px 24px; border-bottom:1px solid var(--border-soft); }.dlg-title { font-size:16px; font-weight:600; color:var(--ink); }.dlg-sub { font-size:12.5px; color:var(--text-3); margin-top:4px; }.dlg-close { width:32px; height:32px; border-radius:50%; display:flex; align-items:center; justify-content:center; color:var(--text-2); cursor:pointer; flex-shrink:0; }.dlg-close:hover { background:var(--grey2); color:var(--text-1); }.dlg-body { padding:20px 24px; max-height:62vh; overflow-y:auto; }.dlg-foot { display:flex; align-items:center; justify-content:flex-end; gap:10px; padding:16px 24px; border-top:1px solid var(--border-soft); background:var(--grey1); }.dlg-foot.between { justify-content:space-between; }Composition (assembled by JS — the canonical shape):
<div id="modal-root"> <div class="dlg-overlay" onclick="if(event.target===this)closeModal()"> <div class="dlg dlg-md"> <div class="dlg-head"><div><div class="dlg-title">Record Payment</div><div class="dlg-sub">for INV-2231 · Acme Ltd</div></div><div class="dlg-close" onclick="closeModal()">…x…</div></div> <div class="dlg-body">…fields…</div> <div class="dlg-foot"><button class="btn-sec" onclick="closeModal()">Cancel</button><button class="btn-pri" onclick="applyPayment('inv1')">Record Payment</button></div> </div> </div></div>Footer button placement: right-aligned; Cancel (.btn-sec) first, then the primary (.btn-pri). Footer sits on a --grey1 strip with a soft top border. .dlg-foot.between splits (e.g. rules modal: “+ New rule” link left, “Done” right). Destructive confirm = confirmModal(title, body, confirmLabel, onConfirm, danger) which renders the primary with inline background:var(--red);color:#fff. Sizes: default 540, dlg-md 600, dlg-lg 760, lg 780. Overlay click (backdrop only) and Escape both close. Used for: confirms, record payment/refund, add/edit account, import stepper, transaction rules, reconcile, compose email, invite user/role/tax/location, new tax.
(b) .qc-overlay Quick-Create mega menu (topbar “+” — an anchored panel, not a modal):
.qc-overlay { position:fixed; inset:0; z-index:200; display:none; }.qc-overlay.show { display:block; }.qc-backdrop { position:absolute; inset:0; background:rgba(15,29,19,.10); }.qc-panel { position:absolute; top:66px; right:24px; width:748px; background:var(--surface); border-radius:16px; box-shadow:0 20px 56px rgba(15,29,19,.18), 0 6px 16px rgba(0,0,0,.06); padding:20px 24px 24px; transform-origin:top right; animation:qcIn .16s cubic-bezier(.2,.8,.2,1); }@keyframes qcIn { from { opacity:0; transform:scale(.97) translateY(-6px); } to { opacity:1; transform:none; } }.qc-head { display:flex; align-items:center; justify-content:space-between; padding-bottom:16px; border-bottom:1px solid var(--border-soft); }.qc-title { font-size:15px; font-weight:600; color:var(--ink); }.qc-close { width:30px; height:30px; border-radius:50%; background:var(--grey2); color:var(--text-2); display:flex; align-items:center; justify-content:center; cursor:pointer; }.qc-close:hover { background:var(--hover); color:var(--text-1); }.qc-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-top:16px; }.qc-col-h { display:flex; align-items:center; gap:8px; font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.06em; color:var(--text-3); padding:0 8px 6px; }.qc-col-h .dot { width:7px; height:7px; border-radius:50%; }.qc-item { display:flex; align-items:center; gap:10px; padding:8px 8px; border-radius:var(--r-md); font-size:13px; color:var(--text-1); cursor:pointer; }.qc-item:hover { background:var(--grey1); }.qc-item:hover .qc-ic { background:var(--brand-dim); color:var(--g-dark); }.qc-ic { width:30px; height:30px; border-radius:50%; background:var(--grey2); color:var(--text-2); display:flex; align-items:center; justify-content:center; flex-shrink:0; transition:background .12s,color .12s; }<div class="qc-overlay" id="qc"> <div class="qc-backdrop" onclick="closeQC()"></div> <div class="qc-panel"> <div class="qc-head"><div class="qc-title">Create new</div><div class="qc-close" onclick="closeQC()">…x…</div></div> <div class="qc-grid"> <div> <div class="qc-col-h"><span class="dot" style="background:var(--ink)"></span>General</div> <div class="qc-item" onclick="closeQC();openJournalForm()"><span class="qc-ic">…svg…</span>Journal Entry</div> … </div> <div><div class="qc-col-h"><span class="dot" style="background:var(--brand)"></span>Sales</div>…</div> <div><div class="qc-col-h"><span class="dot" style="background:var(--blue)"></span>Purchases</div>…</div> <div><div class="qc-col-h"><span class="dot" style="background:var(--amber)"></span>Banking</div>…</div> </div> </div></div>Column headers are color-dot-coded per module (ink=General, brand=Sales, blue=Purchases, amber=Banking). There is no drawer and no bottom sheet anywhere in the file — detail views are full screen swaps, tasks are .dlg modals.
3.11 Toast
Section titled “3.11 Toast”CSS (verbatim — canonical .alert-toast pill, top-center):
#toast-container { position:fixed; top:24px; left:50%; transform:translateX(-50%); display:flex; flex-direction:column; align-items:center; gap:10px; z-index:9999; pointer-events:none; }.toast-live { display:inline-flex; align-items:center; gap:10px; border-radius:16px; padding:6px 16px 6px 6px; pointer-events:all; opacity:0; transform:translateY(-16px); transition:opacity .25s ease, transform .25s ease; box-shadow:0 4px 24px rgba(0,0,0,.13); max-width:90vw; }.toast-live.toast-in { opacity:1; transform:translateY(0); }.toast-live.toast-out { opacity:0; transform:translateY(-10px); }.toast-live .alert-toast-badge { display:flex; align-items:center; justify-content:center; background:rgba(255,255,255,.85); border-radius:12px; padding:4px 10px; font-size:13px; font-weight:500; line-height:20px; white-space:nowrap; flex-shrink:0; }.toast-live .alert-toast-text { font-size:13px; font-weight:500; line-height:20px; }.toast-live .toast-close { background:none; border:none; cursor:pointer; padding:0 2px; opacity:.55; display:flex; align-items:center; color:currentColor; margin-left:4px; flex-shrink:0; }.toast-live .toast-close:hover { opacity:1; }.alert-toast--default { background:#e6f4fe; } .alert-toast--default .alert-toast-badge, .alert-toast--default .alert-toast-text { color:#0d74ce; }.alert-toast--gray { background:#f0f0f0; } .alert-toast--gray .alert-toast-badge, .alert-toast--gray .alert-toast-text { color:#646464; }.alert-toast--error { background:#ffebe8; } .alert-toast--error .alert-toast-badge, .alert-toast--error .alert-toast-text { color:#d8121c; }.alert-toast--warning { background:var(--input-bg)7c2; } .alert-toast--warning .alert-toast-badge, .alert-toast--warning .alert-toast-text { color:#9e6c00; }.alert-toast--success { background:#e6f6eb; } .alert-toast--success .alert-toast-badge, .alert-toast--success .alert-toast-text { color:#05853e; }⚠ In-file bug: .alert-toast--warning { background:var(--input-bg)7c2; } is a corrupted value — the canonical DS value is a pale yellow (#fff7c2). Do not copy the bug.
JS helper (verbatim signature + auto-type inference + badge words):
var TOAST_BADGE = { success:'Done', default:'Info', gray:'Note', warning:'Heads up', error:'Error' };function toast(msg, type){ if(!type){ if(/prototype|copied|not in this/i.test(msg)) type='default'; else if(/must be|cannot|unable|failed|error|invalid/i.test(msg)) type='warning'; else if(/deleted|excluded|reconcile later|marked inactive/i.test(msg)) type='gray'; else type='success'; } … el.className = 'toast-live alert-toast alert-toast--' + type; … '<div class="alert-toast-badge">' + (TOAST_BADGE[type]||'Info') + '</div><span class="alert-toast-text">' + msg + '</span>' + '<button class="toast-close" aria-label="Dismiss" onclick="this.closest(\'.toast-live\').remove()">…12px x…</button>'; … auto-dismiss after 3600ms (toast-in → toast-out, remove at +300ms)}Structure: badge word chip (white 85% pill) + message + close ×. Top-center, stacked with 10px gap. Never a custom black bottom pill.
3.12 Inputs & forms
Section titled “3.12 Inputs & forms”.fld { margin-bottom:16px; } .fld:last-child { margin-bottom:0; }.fld > label { display:block; font-size:12px; font-weight:500; color:var(--text-2); margin-bottom:6px; }.fld .req { color:var(--red); margin-left:1px; }.fld input[type=text],.fld input[type=number],.fld input[type=date],.fld select,.fld textarea,.dlg input[type=text],.dlg input[type=number],.dlg input[type=date] { width:100%; height:42px; padding:0 12px; border:1px solid var(--border); border-radius:var(--r-md); font:inherit; font-size:13.5px; color:var(--text-1); background:var(--input-bg); outline:none; transition:border-color .12s, box-shadow .12s; }.fld textarea { height:auto; min-height:66px; }.dlg input:focus { border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-dim); }/* native selects in dialogs: custom chevron with room from the right edge */.fld select, .dlg select { width:100%; height:42px; border:1px solid var(--border); border-radius:var(--r-md); font:inherit; font-size:13.5px; color:var(--text-1); outline:none; transition:border-color .12s, box-shadow .12s; appearance:none; -webkit-appearance:none; -moz-appearance:none; padding:0 36px 0 12px; background-color:var(--input-bg); background-repeat:no-repeat; background-position:right 12px center; background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23888888' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>"); }.fld select:focus, .dlg select:focus { border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-dim); }.fld textarea { height:auto; min-height:62px; padding:8px 12px; resize:vertical; }.fld input:focus,.fld select:focus,.fld textarea:focus { border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-dim); }.fld-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; }.fld-hint { font-size:11.5px; color:var(--text-3); margin-top:4px; }.radio-row { display:flex; gap:10px; flex-wrap:wrap; }.radio-pill { font-size:13px; font-weight:500; color:var(--text-2); padding:8px 16px; border:1px solid var(--border); border-radius:var(--r-md); cursor:pointer; }.radio-pill.on { border-color:var(--brand); background:var(--brand-dim); color:var(--g-dark); font-weight:600; }.dlg-section-t { font-size:12px; font-weight:600; color:var(--text-3); text-transform:uppercase; letter-spacing:.04em; margin:16px 0 10px; }.dlg-section-t:first-child { margin-top:0; }.chk { width:17px; height:17px; accent-color:var(--brand); cursor:pointer; }.fld-3 { display:grid; grid-template-columns:140px 1fr 1fr; gap:16px; }Settings-row controls (40px variant) + settings scaffolding:
.set-wrap { max-width:1080px; margin:0 auto; }.settings-layout { display:grid; grid-template-columns:208px 1fr; gap:20px; align-items:start; }.set-sec { padding:24px 28px; }.set-sec-h { font-size:15px; font-weight:600; color:var(--ink); }.set-sec-s { font-size:13px; color:var(--text-3); margin-top:4px; margin-bottom:16px; line-height:1.6; }.set-row { display:flex; align-items:center; justify-content:space-between; gap:24px; padding:14px 0; border-top:1px solid var(--border-soft); }.set-row:first-of-type { border-top:none; }.set-row-info { flex:1; min-width:0; }.set-row-label { font-size:13.5px; font-weight:500; color:var(--text-1); }.set-row-sub { font-size:12px; color:var(--text-3); margin-top:2px; }.set-ctl { width:320px; flex-shrink:0; display:flex; justify-content:flex-end; }.set-ctl input, .set-ctl select { width:100%; height:40px; padding:0 12px; border:1px solid var(--border); border-radius:var(--r-md); font:inherit; font-size:13.5px; color:var(--text-1); background:var(--input-bg); outline:none; transition:border-color .12s, box-shadow .12s; }.set-ctl input:focus, .set-ctl select:focus { border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-dim); }.set-foot { padding:16px 28px; border-top:1px solid var(--border-soft); background:var(--grey1); display:flex; justify-content:flex-end; gap:10px; }.set-avatar { width:64px; height:64px; border-radius:50%; background:var(--brand-dim); color:var(--g-dark); display:flex; align-items:center; justify-content:center; font-size:22px; font-weight:700; flex-shrink:0; }Line-item table inputs (40px), import stepper, drop-zone, file control:
.li-table { width:100%; border-collapse:collapse; margin-bottom:12px; }.li-table th { font-size:11px; text-transform:uppercase; letter-spacing:.04em; color:var(--text-3); text-align:left; padding:0 8px 8px; font-weight:600; }.li-table th.r, .li-table td.r { text-align:right; }.li-table td { padding:4px 8px; vertical-align:middle; }.li-table input { width:100%; height:40px; border:1px solid var(--border); border-radius:var(--r-md); padding:0 12px; font:inherit; font-size:13.5px; color:var(--text-1); background:var(--input-bg); outline:none; transition:border-color .12s, box-shadow .12s; }.li-table input:focus { border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-dim); }.li-table input.r { text-align:right; }.li-table select { …40px, same chevron data-URI, padding:0 32px 0 12px… }.li-table input[type="checkbox"], .li-table input.chk { width:17px; height:17px; min-width:0; padding:0; accent-color:var(--brand); cursor:pointer; }/* line-item "Item Details": item name + description side by side, not stacked */.li-table input[class$="-name"] { display:inline-block; width:56%; vertical-align:top; }.li-table input[class$="-desc"] { display:inline-block; width:calc(44% - 6px); margin-left:6px !important; margin-top:0 !important; vertical-align:top; font-size:13px !important; color:var(--text-1) !important; }.li-amt { font-weight:600; color:var(--ink); white-space:nowrap; }.li-x { color:var(--text-3); cursor:pointer; display:inline-flex; } .li-x:hover { color:var(--red); }.est-form-totals { max-width:330px; margin-left:auto; margin-top:6px; }.est-form-totals .row { display:flex; justify-content:space-between; align-items:center; gap:12px; padding:8px 0; font-size:13px; color:var(--text-2); }.est-form-totals .row input { width:130px; height:36px; border:1px solid var(--border); border-radius:var(--r-md); padding:0 10px; text-align:right; font:inherit; font-size:13px; outline:none; }.est-form-totals .row.grand { border-top:1px solid var(--border-soft); margin-top:4px; padding-top:12px; font-size:15px; font-weight:700; color:var(--ink); }.stepper { display:flex; align-items:center; gap:8px; margin-bottom:16px; }.step { display:flex; align-items:center; gap:8px; font-size:12.5px; font-weight:500; color:var(--text-3); }.step .n { width:22px; height:22px; border-radius:50%; background:var(--grey2); color:var(--text-3); display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:600; }.step.on { color:var(--g-dark); } .step.on .n { background:var(--brand); color:var(--ink); }.step.done .n { background:var(--brand-dim); color:var(--g-dark); }.step-line { flex:1; height:1px; background:var(--border); min-width:18px; }.drop-zone { border:1.5px dashed var(--border); border-radius:var(--r-lg); padding:32px; text-align:center; color:var(--text-3); cursor:pointer; }.drop-zone:hover { border-color:var(--brand); color:var(--g-dark); background:var(--brand-dim); }.file-drop { display:inline-flex; align-items:center; gap:8px; font-size:13px; font-weight:500; color:var(--g-dark); background:var(--grey1); border-radius:var(--r-md); padding:8px 16px; cursor:pointer; }.file-drop:hover { background:var(--hover); }Markup (verbatim field + row from a modal):
<div class="fld-row"> <div class="fld"><label>Amount Received</label><input id="rp-amt" type="text" value="372580000"></div> <div class="fld"><label>Payment Date</label><input id="rp-date" type="text" value="19 Jun 2026"></div></div><div class="fld"><label>Deposit To</label><select id="rp-acct"><option>GTBank · Operations</option><option>Sterling · Collections</option><option>Petty Cash</option></select></div><div class="fld"><label>Tax Name <span class="req">*</span></label><input id="nt-name" type="text" placeholder="e.g. VAT"></div>Usage notes: input heights — 42px in dialogs/forms, 40px in settings rows and line-item tables, 36px in the totals column, 34px in inline search. Focus ring is always border-color:var(--brand) + box-shadow:0 0 0 3px var(--brand-dim). Required marker = red * in .req. Selects are appearance:none with a grey (#888) chevron data-URI. Validation: on failed save the field gets style.borderColor='var(--red)' + focus + a warning toast — no inline error text pattern in this file.
3.13 Dropdown menus
Section titled “3.13 Dropdown menus”.dd { position:relative; }.dd-menu { position:absolute; top:calc(100% + 8px); right:0; min-width:208px; background:var(--surface); border-radius:12px; box-shadow:0 14px 40px rgba(15,29,19,.16), 0 4px 10px rgba(0,0,0,.05); padding:6px; z-index:120; display:none; animation:ddIn .14s ease; }.dd.open > .dd-menu { display:block; }@keyframes ddIn { from { opacity:0; transform:translateY(-5px); } to { opacity:1; transform:none; } }.dd-cv { transition:transform .16s; }.dd.open .dd-cv { transform:rotate(180deg); }.dd-opt { display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:var(--r-md); font-size:13px; color:var(--text-1); cursor:pointer; white-space:nowrap; }.dd-opt:hover { background:var(--grey1); }.dd-opt.sel { color:var(--g-dark); font-weight:500; }.dd-opt .ck { margin-left:auto; color:var(--g-dark); }.dd-opt svg.oi { color:var(--text-3); flex-shrink:0; }.dd-opt.danger { color:var(--red); } .dd-opt.danger svg.oi { color:var(--red); }.dd-divider { height:1px; background:var(--border-soft); margin:6px 4px; }.dd-flt { font-size:10.5px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--text-3); padding:8px 12px 4px; }.dd-head { padding:8px 10px 4px; display:flex; align-items:center; justify-content:space-between; }.dd-head .nm { font-size:13px; font-weight:600; color:var(--text-1); }.dd-head .em { font-size:11.5px; color:var(--text-3); margin-top:1px; }.dd-head .link { font-size:11.5px; font-weight:500; color:var(--g-dark); cursor:pointer; }.dd-note { display:flex; gap:10px; padding:8px 10px; border-radius:var(--r-md); cursor:pointer; }.dd-note:hover { background:var(--grey1); }.dd-note .nt-dot { width:7px; height:7px; border-radius:50%; background:var(--brand); margin-top:4px; flex-shrink:0; }.dd-note.read .nt-dot { background:var(--border); }.dd-note .nt-t { font-size:12.5px; color:var(--text-1); }.dd-note .nt-m { font-size:11.5px; color:var(--text-3); margin-top:1px; }Wiring (verbatim — one global delegated handler, no per-dd JS):
// Generic dropdown open/close + selectiondocument.addEventListener('click', function(e){ var trig = e.target.closest('.dd-trigger'); if (trig){ var dd = trig.closest('.dd'); var wasOpen = dd.classList.contains('open'); closeAllDD(); closeQC(); if (!wasOpen) dd.classList.add('open'); e.stopPropagation(); return; } var opt = e.target.closest('.dd-opt'); if (opt){ var dd = opt.closest('.dd'); if (opt.hasAttribute('data-val')){ var lbl = dd.querySelector('.dd-label'); if (lbl) lbl.textContent = opt.getAttribute('data-val'); dd.querySelectorAll('.dd-opt').forEach(function(o){ o.classList.remove('sel'); var c=o.querySelector('.ck'); if(c) c.remove(); }); opt.classList.add('sel'); opt.insertAdjacentHTML('beforeend', CK); if (dd.getAttribute('data-dd') === 'accounts') filterCfAccount(opt.getAttribute('data-val')); } dd.classList.remove('open'); return; } if (!e.target.closest('.dd-menu')) closeAllDD();});Pattern: any trigger is <element class="… dd-trigger"> inside <div class="dd">, followed by .dd-menu. Selected option = .sel + appended CK checkmark SVG; the trigger’s .dd-label text is swapped. Trigger chevron uses class dd-cv (rotates 180° open). data-dd="period|range|accounts|…" menus are auto-built from DD_OPTS. Variants: selection menu (.sel+.ck), action menu (.dd-opt + svg.oi icon + .danger), grouped menu (.dd-flt uppercase group labels), notifications (.dd-head + .dd-note), account switcher (.sw-ic circular chip + .sw-nm + .sw-cur), and the mega add-transaction menu (.dd-menu containing .tt-grid):
.tt-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; }.tt-col-h { font-size:12px; font-weight:600; color:var(--text-3); text-transform:uppercase; letter-spacing:.04em; margin-bottom:8px; display:flex; align-items:center; gap:8px; }.tt-item { display:flex; align-items:center; gap:8px; font-size:13.5px; color:var(--text-1); padding:8px 10px; border-radius:var(--r-md); cursor:pointer; }.tt-item:hover { background:var(--brand-dim); color:var(--g-dark); }.tt-item .d { width:7px; height:7px; border-radius:50%; flex-shrink:0; }3.14 Empty states
Section titled “3.14 Empty states”.txn-empty { text-align:center; padding:48px 20px; color:var(--text-3); }.txn-empty .ic { width:54px; height:54px; border-radius:50%; background:var(--grey2); color:var(--text-3); display:flex; align-items:center; justify-content:center; margin:0 auto 16px; }.txn-empty .t { font-size:14px; font-weight:600; color:var(--text-1); margin-bottom:4px; }.txn-empty .m { font-size:13px; max-width:420px; margin:0 auto 16px; line-height:1.5; }.gs-empty { padding:18px 12px; text-align:center; font-size:12.5px; color:var(--text-3); }.sum-empty { font-size:12.5px; color:var(--text-3); padding:8px 0; }Markup (verbatim from emptyTxns() and a filtered-list empty):
<div class="txn-empty"><div class="ic">…bank svg 24…</div><div class="t">No transactions yet</div><div class="m">There are no transactions recorded for this account. Begin by adding new transactions or simply import your latest account statement.</div></div>
<tr><td colspan="8"><div class="txn-empty" style="padding:32px"><div class="t">No customers</div><div class="m">No customers match this view.</div></div></td></tr>Notes: full empty state (icon + title + message) only when a container is truly empty; filtered-empty inside a table drops the icon and shrinks padding to 32px. Search-empty echoes the query: No matches … match "q".
3.15 Icon-container convention
Section titled “3.15 Icon-container convention”All icon containers are circular (border-radius:50%) and tonal — tinted background (*-dim token) + colored glyph, never a solid fill, never a rounded square. Established sizes in this file:
| container | size | context |
|---|---|---|
.txn-acct .i |
46px | account detail header |
.bank-ic |
40px | dashboard bank card |
.kpi-ic, .acct-ic, .bk-s .i |
38px | KPI / table rows / stat tiles |
.avatar, .cmt .av, .dlg-close |
32px | user/comment avatars, dialog close |
.bk-stat .l .i, .qc-ic, .qc-close, .row-menu-btn |
30px | inline stat, quick-create, kebab |
.gs-ic |
28px | global-search result |
.sw-ic |
26px | account-switcher menu |
.step .n |
22px | stepper number |
.txn-empty .ic |
54px | empty state |
.set-avatar |
64px | settings profile |
.cust-avatar (42px, .lg 56px) |
avatars | initials chips, brand-dim/g-dark |
Tint classes: ic-green / ic-amber / ic-blue / ic-ink / ic-grey / ic-purple (§3.8). Avatars = brand-dim bg + --g-dark initials at 600. Glyphs are inline lucide-style SVGs, stroke-width 1.8–1.9 (2.2–2.4 for small chevrons/arrows), generated via svg(path, size) with default stroke-width 1.85. Small square swatches (chart legends: .legend i 10px, .exp-row .sw 10px, .chart-tip .tr i 8px) use 2–3px radius — swatches, not icon containers. One rectangular exception: .unc-n (uncategorized count, 40px tall, --r-md) and .sum-side-tile .n — these are number tiles, not icons.
3.16 Charts & tooltips (supporting atoms)
Section titled “3.16 Charts & tooltips (supporting atoms)”.cf-wrap { display:grid; grid-template-columns:1fr 286px; gap:28px; align-items:center; }.cf-chart { width:100%; height:236px; }.ax { font-size:10px; fill:#b9bdc4; font-family:inherit; }.cf-sum > div { padding:12px 0; }.cf-sum > div + div { border-top:1px solid var(--border-soft); }.cf-k { font-size:12px; color:var(--text-3); margin-bottom:4px; }.cf-v { font-size:19px; font-weight:700; letter-spacing:-.02em; color:var(--ink); display:flex; align-items:center; justify-content:space-between; }.cf-v .op { font-size:16px; color:var(--text-3); font-weight:500; }.cf-in { color:var(--g-dark); } .cf-out { color:var(--red); }.bars { display:flex; align-items:flex-end; gap:16px; height:188px; padding-top:10px; }.bar-grp { flex:1; display:flex; align-items:flex-end; justify-content:center; gap:4px; height:100%; }.bar { width:13px; border-radius:4px 4px 0 0; }.bar.inc { background:var(--brand); } .bar.exp { background:#D7DBE0; }.bars-x { display:flex; gap:16px; margin:8px 0 16px; }.bars-x span { flex:1; text-align:center; font-size:11px; color:var(--text-3); }.legend { display:flex; gap:16px; }.legend span { display:inline-flex; align-items:center; gap:8px; font-size:12px; color:var(--text-2); }.legend i { width:10px; height:10px; border-radius:3px; }.chart-tip { position:fixed; transform:translate(-50%,-100%); background:var(--ink); color:#fff; padding:8px 12px; border-radius:8px; font-size:12px; pointer-events:none; opacity:0; transition:opacity .12s; z-index:9999; white-space:nowrap; box-shadow:0 6px 16px rgba(0,0,0,.18); text-align:center; }.chart-tip.show { opacity:1; }.chart-tip .l { display:block; font-size:10.5px; color:#9fb0a4; margin-bottom:4px; }.chart-tip .v { font-weight:700; }.chart-tip .rows { display:block; text-align:left; }.chart-tip .tr { display:flex; align-items:center; gap:8px; font-size:11.5px; padding:2px 0; white-space:nowrap; }.chart-tip .tr i { width:8px; height:8px; border-radius:2px; flex-shrink:0; }.chart-tip .tr .tk { color:#c2cdc6; }.chart-tip .tr b { margin-left:auto; padding-left:16px; color:#fff; font-weight:600; }Charts are hand-authored inline SVG (line/area with preserveAspectRatio="none", gradient fill #01E46F .20→0, gridlines #EDEFF2, axis text .ax 10px #b9bdc4), hoverable via invisible hit targets carrying data-tip-l/data-tip-v (single value) or data-tip-rows="Name|#color|value;…" (multi-series). A single global .chart-tip (dark --ink pill) follows the cursor for multi-row tips or anchors above the target for single tips. Donut = stacked <circle stroke-dasharray> at r=15.9155 in a 42-viewBox, stroke 5.5, with .exp-row legend list. The multi-account Banking chart rebases each series to index 100 at period start (per Caesar chart conventions; absolute balances live in the tooltip), with clickable .legend span[data-acct] filtering series opacity (1 vs .14).
Other minor atoms present: .kv key/value rows, .sum-acc accordions (chevron rotates 180°, .sum-acc-b collapses), .cmt comment threads, .est-banner status banner (dot + text, colored by status), .est-doc printable document card, .est-hist-item history feed (9px brand dot + text + timestamp), .stepper (Configure → Map fields → Preview), .rec-bal reconcile balance trio, .rule-item/.crit-row rules builder, .feed-row uncategorized bank feed with expandable .feed-panel, .bulk-bar, .inc-chart mini bar chart with .inc-tip hover, .rpt-grid/.rpt-nav/.rpt-link/.rpt-net report scaffolding, .settings-layout.
4. Page anatomy
Section titled “4. Page anatomy”Every page lives in <div class="page app-screen" id="scr-…">; the shell (sidebar/topbar) never re-renders. Screen ids: list scr-invoices, detail scr-invoice, form scr-invoice-form.
4.1 Dashboard (#scr-dashboard)
Section titled “4.1 Dashboard (#scr-dashboard)”Top-to-bottom: .pg-head (greeting “Hello, Francis” + sub + .period fiscal-year dropdown) → .kpi-row (4 KPI cards) → “Bank and Credit Cards” section (.card-h header w/ .card-link “Go to Banking” + .row.row-3 of .bank-cards) → Cash Flow .card (.cf-wrap: SVG line chart + .cf-sum right column: opening / incoming(+) / outgoing(−) / closing(=)) → .row.row-2 with Income & Expense .card (grouped bars + .bars-x + .legend) and Top Expenses .card (donut + .exp-list). Grids: .row { display:grid; gap:16px; margin-bottom:16px; } .row-2 {1fr 1fr} .row-3 {1fr 1fr 1fr}. Every card head carries its own .mini-period dropdown. Bank cards click through to the account’s Summary view.
4.2 Banking hub (#scr-banking)
Section titled “4.2 Banking hub (#scr-banking)”.pg-head w/ .pg-actions (Import Statement .btn-sec, Add Bank / Credit Card .btn-pri) → .bk-overview white card: .bk-ov-head (account-switch dd + range dd) / .bk-statrow (5 grey .bk-s tiles, last one amber .bk-s-unc w/ count + “Categorize now”) / collapsible .bk-cf-head+.bk-cf-body (multi-series indexed chart, Hide/Show chart toggle) → .tbl-card Accounts table (view dropdown in head; rows injected with st-dots + kebab menus; whole row → openAccount(id,'summary')) → .tbl-foot group cash position.
4.3 Account transactions (#scr-bank-txns) — detail, 2 levels deep (sidebar auto-collapses)
Section titled “4.3 Account transactions (#scr-bank-txns) — detail, 2 levels deep (sidebar auto-collapses)”.pg-head variant: .txn-acct identity block that is itself an account-switcher dropdown (46px tonal icon + 19px name + chevron + meta) + .txn-actions (Add Transaction .btn-pri w/ mega dd, Import Statement .btn-sec, gear dd: import / rules / reconcile / edit) → .txn-summary (4 .txn-s tiles) → .txn-card containing .txn-bar (left: .seg Account Summary | Transactions; right .txn-tools: .txn-search + .txn-tabs All / Uncategorized n / Categorized) → #txn-body (sortable .tbl, or .feed list for uncategorized with checkboxes, expandable categorize/match panels and bulk actions, or the Summary view: .sum-grid of grey1 .sum-cards + .sum-side-tiles + accordions). Tools hide in Summary view.
4.4 List pages (Customers, Invoices, Bills, … — all identical skeleton)
Section titled “4.4 List pages (Customers, Invoices, Bills, … — all identical skeleton)”.pg-head (title + sub, right: single .btn-pri “New X”) → .tbl-card: .tbl-card-head (left: view-filter dropdown styled as the card title with chevron; right: .txn-search + .gear-btn hamburger dd w/ Sort by group, Import/Export/Refresh) → .bulk-bar (hidden until checkboxes selected) → .tscroll > table.tbl (checkbox col 34px, sortable headers, status via stBadge(), right-aligned .amt money, kebab dd last col 40px). Row click (delegated) opens detail; checkbox/menu/input clicks are excluded. Empty → in-table .txn-empty.
4.5 Record detail (Invoice/Bill/Estimate/… #scr-invoice)
Section titled “4.5 Record detail (Invoice/Bill/Estimate/… #scr-invoice)”.cust-detail-head (21px doc number + stBadge inline + .pg-sub meta; right .txn-actions: Comments & History .btn-sec, Edit, Send, PDF/Print, Journal, status-dependent primary e.g. Record Payment, kebab dd w/ Mark as Sent / Clone / Credit Note / Write Off / Void / ─ / Delete) → .est-banner status banner (colored dot + sentence) → .est-doc printable document (org block, .est-doc-title “INVOICE”, .est-meta, .est-billto, table.tbl line items, .est-tot-rows totals with .grand rows, Notes / Attachments / Terms blocks) → comments & history section (toggleable). Contact detail (Customer/Vendor) instead uses: avatar .cust-avatar.lg + name + active pill + .txn-summary 3-tile strip + .seg pill tabs (Overview / Comments / Transactions / Statement) over #cust-det-body; Overview = .sum-grid of .sum-cards (kv rows, addresses, income mini-chart).
4.6 Form pages (#scr-*-form)
Section titled “4.6 Form pages (#scr-*-form)”.pg-head (title “New Invoice” / “Edit Invoice” + sub) — showScreen() relocates the trailing .form-actions row into the head as pg-actions → .cust-form-card (white, max-width 900 (1040 for invoice), fields in .fld-row 2-col grids, .dlg-section-t uppercase section labels, .li-table line-item editor with “+ Add another line” .link-add, .est-form-totals right-aligned totals, notes/terms textareas, .file-drop attach) → actions: primary first (“Save and Send” .btn-pri), then secondaries (“Save as Draft”, “Make Recurring”, “Cancel”). Cancel returns to detail (edit) or list (new). Validation = red border + warning toast.
4.7 Settings (#scr-settings)
Section titled “4.7 Settings (#scr-settings)”.set-wrap (max 1080) → .pg-head → .settings-layout (208px sticky .settings-nav of .sn-items | .set-panel white card) → .set-sec (15px heading + 13px sub) with stacked .set-rows (label+sub left, 320px .set-ctl right) → .set-foot grey strip, right-aligned Save .btn-pri. Team section nests a .seg (Users/Roles) + .tbl. Radio-card group (journal approvals) = full-width labels w/ brand-dim bg when selected.
4.8 Reports (#scr-reports → #scr-report) & Documents (#scr-documents)
Section titled “4.8 Reports (#scr-reports → #scr-report) & Documents (#scr-documents)”Reports Center: .pg-head (+ Custom Report .btn-sec) → .rpt-grid (220px .rpt-nav category rail, scroll-to-section; right: .sum-card per category with .rpt-link rows). Report viewer: .cust-detail-head (name + range dd + Export/Print) → .est-doc with centered report header + .tbl.rpt-tbl + .rpt-net grand-total strip (brand-dim, red .neg variant); sidebar collapses; account/customer names linkify to their details. Documents: same .rpt-grid scaffold with folder rail + files table.
Interaction rules (canon)
Section titled “Interaction rules (canon)”- Row click = open detail. Detail = full-screen swap (screen div + crumbs +
window.scrollTo(0,0)), never a modal/drawer. Back = clickable breadcrumb. - Modals (
.dlg) are for tasks and confirmations only (record payment, import, rules, reconcile, invite, confirm delete/void). Confirmations on web are centered.dlg— never bottom sheets. - Default selections: banking rows open on the Summary view; account screen defaults to Transactions/All when opened from elsewhere; settings defaults to
profile(topbar) ororganization(sidebar); first.segoption is preselected. - Search lives per-list (
.txn-searchinside.tbl-card-head) + one global search in the topbar (/shortcut, grouped results, ↑↓/Enter). - Destructive actions confirm via
confirmModal(…, danger)(red primary) for void/write-off; simple deletes act immediately + toast (prototype behavior). - Every control works — unbuilt destinations toast “… is not in this prototype” (type
default), never a dead click. - Sidebar depth rule: list page = expanded + section open + subitem active; detail/form (2 levels) =
body.sb-collapsed(forward-only; expanding is user-controlled). - Escape closes: search panel, quick-create, dropdowns, modal. Backdrop click closes modal & quick-create.
- Mutations always: update the data array →
toast(…)→ re-render the affected screen (openX(id)again) and keep counts in sync (syncAcctBadges()).
5. JS contracts
Section titled “5. JS contracts”Navigation & shell:
navTo(name, el)— show#scr-<name>, set sidebar active, close submenus,renderCrumbs(name), scroll top.showScreen(id)— bare screen swap + form-action relocation +closeAllDD(); closeQC(); window.scrollTo(0,0).renderCrumbs(name, extra)— builds topbar trail fromCRUMB_TRAILS[name], appends optionalextraleaf.crumbNav(to)— crumb click router →openX()functions.topActive(tip)/navActive(menuTip, label)+salesActive(label),purchasesActive(label),accountantActive(label)— sidebar state; navActive collapses sidebar when not on a list screen.toggleSub(el)— sidebar accordion;toggleSidebar()— collapse/expand rail;toggleTheme()— dark mode (+localStoragecaesar-theme).toggleQC(e)/closeQC()— quick-create;closeAllDD()— close all dropdowns.activeScreen()→ current.app-screen.showid.
Per-entity screen contract (repeated for customers, estimates, invoices, paymentsR/M, recInv, creditNotes, vendors, bills, expenses, recBills, recExpenses, vendorCredits, COA, journals, recJournals, bulk, curradj):
openXs()(list) /openX(id)(detail) /openXForm(id?)(create/edit form) — e.g.openInvoices(),openInvoice(id),openInvoiceForm(id).xsListHTML()/xDetailHTML(rec)/xFormHTML(rec);xRenderRows(),xFiltered(),xSearch(v),xSort(col),xFilter(val, el),xRefresh(),xBulk(type),xToggleAll(cb),xRowToggle(e),xUpdateBulk(),xClearSel(),xAction(type, id),saveX(mode).- Banking:
openAccount(id, view)(view: 'summary'|'txns'),bankView(id, which),bankTab(id, status)(all|uncat|cat),bankSearch(v),bankSort(col),txnScreenHTML(a,id),txnBodyHTML(a,id),summaryHTML(a,id),feedListHTML(a,id),addTxnForm(id, type, dir),saveTxn(),bankCategorize(idx),bankMatch(idx),bankExclude(idx).
Overlays & feedback:
toast(msg, type)— type ∈success|default|gray|warning|error, auto-inferred from msg when omitted (§3.11).openModal(html, cls)/closeModal()— cls ∈''|'dlg-md'|'dlg-lg'|'lg';dlgHead(title, sub)builds the header;confirmModal(title, body, confirmLabel, onConfirm, danger).- Task modals:
recordPaymentModal(id),cnRefundModal(id),composeEmailModal(to, subject, onSend),importStmtModal(id)(+impGo(n),impDone()stepper),rulesModal(id),reconcileModal(id),addAccountModal(),editAccountModal(id),newTaxModal(sel, recalc),inviteUserModal(email),newRoleModal(id),locationModal(id),editTaxModal(i),mergeModal(kind),portalModal(kind),glModal(title, sub, lines).
Tabs & switches: custDetailTab(tab) / vendDetailTab(tab) (pill-seg detail tabs), bankView/bankTab (above), setTeamTab(t), settingsNav(sec), rptScrollCat(id, el), sumToggle(el) (accordions), toggleCf(btn) (chart collapse), pillPick(el) (radio pills).
Search: globalSearch(raw), gsearchKey(e), gsearchGo(i), gsearchClose(); / hotkey focuses.
Utilities: svg(path, sz=16) (stroke 1.85 icon factory), usd(n) → '₦'+toLocaleString, money(n, cur), curSym(cur), abbrevMoney(n), parseAmt/parseMoney/parseDate, isoToDisp/dispToIso/dateDef/dateOpt (dates shown as 18 Jun 2026, APP_TODAY='2026-06-18'), fv(id) (field value), initials(nm), stBadge(s), docLink(num) / docOpen(num) / linkifyDocs(s) (auto-link INV|BILL|PAY|PMT|EXP|EST|CN|VC-\d+), downloadCSV(filename, headers, rows), exportTableCSV(scope, filename), exportCSVAuto(), emailAuto(), cloneAuto(), printDoc(), syncAcctBadges(), filterCfAccount(name).
Data globals: BANK (keyed accounts w/ txns, feed, rules), CUSTOMERS, VENDORS, INVOICES, BILLS, ESTIMATES, CREDITNOTES, VENDORCREDITS, PAYMENTS, PAYMENTSMADE, EXPENSES, JOURNALS, COA, TAXES, USERS, ROLES, LOCATIONS, DOCUMENTS; UI state in CUR_* / *_FILTER / *_SORT / *_SEARCH globals. A boot IIFE multiplies seeded amounts ≥100 by 1000 so figures read at company scale.
6. Naming
Section titled “6. Naming”- Class prefixes (all lowercase-hyphen): shell
sb-(sidebar),tb-(topbar),crumb-; pagepg-; cardscard/tbl-card/sum-/bank-/kpi-; bankingbk-,txn-,cf-,feed-,rec-,unc-; salescust-,est-,inv(ids only),cn/vc/pmt(JS); dropdownsdd-; quick-createqc-; dialogsdlg-; formsfld,li-,tt-,crit-,cp-; settingsset-,sn-; reportsrpt-; global searchgs-; toastsalert-toast--*/toast-*. (The header comment claims prefixcz-*but nocz-class exists — the real convention is the semantic prefixes above.) - Screen ids:
scr-<entity>list,scr-<entity-singular>detail,scr-<entity>-formform (the[id$="-form"]CSS selector relies on this). - State classes:
.show(screens/subs/overlays/bulk-bar),.open(dd/sb-item/accordion),.on(tabs/seg/pills/settings nav),.active(sidebar),.sel(dd options),.inactive(rows),.neg(negative values),.read(notifications),.danger(red options/actions). - Personas / org: Francis Ogunlaja — Finance Lead (avatar
FO, francis.ogunlaja@mocx.co); Deborah Egbele — Accountant; Phebe Idowu — AP Clerk; Aisha Bello — salesperson. Org: MocX Group, Lagos/Abuja/PHC locations, FIRS tax agencies. Base currency NGN (₦), multi-currency USD/GBP/EUR…. - ID formats: documents
INV-2231,BILL-0312,EST-…,CN-…,VC-…,PAY-…,PMT-…,EXP-218,JV-001,TRF-0098,WIRE-0461; account codes1001–2001; masked numbers0124 •••• 4821(monospace.mono); record ids in JS are slugs (gtbank,c1,inv1,loc-hq,role-…). - Copy conventions: middot
·joins meta fragments (“Guaranty Trust · NGN”, “10 minutes ago · GTBank Operations”); minus is−(U+2212); “uncategorised/uncategorized” both appear (UI labels prefer -ized in tabs, -ised in notifications); datesDD Mon YYYY.
7. Weight & spacing discipline
Section titled “7. Weight & spacing discipline”Where 700 appears (sanctioned — display numerals & splash-grade display text):
.kpi-val 25px, .bk-stat .v 23, .pg-title 23, .bank-bal 22, .set-avatar 22 (initials), .bk-s .v 20, .est-doc-title 20, .cf-v/.txn-s .v 19, .unc-n/.sum-stat .n 18, .sb-logo-name 18 (wordmark), .rec-b .v 17, .tbl-foot .val 16, .est-tot-rows .tr.grand / .est-form-totals .row.grand / .rpt-net 15, .chart-tip .v 12. Everything else — names, buttons, section titles, card titles, badges, tab labels, b/strong — is 600 or lighter (600 remap of b,strong enforces this).
Borderline/violations noticed within the file itself (do NOT propagate):
.alert-toast--warning { background:var(--input-bg)7c2; }— corrupted color value (should be the pale-yellow#fff7c2canonical toast tint).- Report tables inject inline
font-weight:700on text labels (“Total”, “Assets”, “Net Profit” row label inside.rpt-net) — 700 on words, not numerals; canon says those should be 600. .est-doc-title(“INVOICE”/“ESTIMATE” wordmark) and.sb-logo-nameare 700 display text — accepted as display-grade, but they are not numerals..gsearch-panelusesvar(--sh-pop, fallback)—--sh-popis never defined in:root(fallback always used)..chkis defined twice (17px then 16px — the later 16px wins for the bare class;.li-tablere-forces 17px)..fld textareadefined twice (min-height 66 then 62 — 62 wins).- Duplicated
toggleAccountActivedefinition (first stub is overwritten later) — harmless but present.
Spacing rhythm (4px grid):
- Page:
padding:24px 32px 48px, column max 1320,.pg-headmargin-bottom 24. - Grids/cards: 16px is the universal gap (
.kpi-row,.row,.fld-row, stat rows,margin-bottom:16pxbetween stacked cards); card padding 20×24 (.card), 16×20 (.kpi,.bank-card,.sum-card,.txn-card), 16 all-round (.tbl-card), 24×24/24×28 (.cust-form-card,.est-doc,.set-sec). - Controls: heights 38 (buttons, sb-item, tb-search, tabs, period), 42 (dialog/form inputs, user-pill), 40 (settings/line-item inputs), 34 (subitems, inline search, crumb-toggle), 30 (kebab, qc icons); dropdown menu padding 6, option padding 8×10; dialog paddings 16×24 (head/foot) & 20×24 (body); table cells 12×16, header 48 tall.
- Micro-gaps: 4/6/8/10/12 used deliberately (icon-text 8–12, label-input 6, badge gap 4).
- Off-grid values that exist in the file: 13.5/12.5/11.5px type sizes (half-pixel type scale — intentional), 7px dots & progress-bar height, 9px
.sn-itemvertical padding, 46px.txn-acct .i,top:46pxsearch panel,top:66pxqc panel, 22px stepper dots, 1.5px dashed/tbl-footborders. These are the established exceptions; do not invent new ones.
Radius scale: 6 (--r-sm: badges), 8 (--r-md: buttons/inputs/nav items), 12 (--r-lg: cards/dialogs/dd-menus), 16 (--r-xl: tbl-card/qc-panel/toast pill), 999 (--r-f: pills/avatars/bars). Icon containers 50%.
Color discipline: brand #01E46F is used only for fills of primary buttons, active-tab underline, chart primary series, progress, stepper-active, and selection accents; text on brand is always #111; green text is always --g-dark (#05853E), never --brand. Tinted (*-dim) backgrounds always pair with their dark text partner (--amber-text for amber, base hue otherwise).