canon extract inv mgr
CANON EXTRACT — Rechaj Inventory Manager (tablet reference app)
Section titled “CANON EXTRACT — Rechaj Inventory Manager (tablet reference app)”Source of truth: /Users/Ridwan/Pictures/Claude/Rechaj 2.0/rechaj-design-system/products/rechaj-inventory-app/flows/inventory-manager/index.html (2,968 lines; CSS lines 17–551, markup 553–1654, JS 1656–2966). Extraction is verbatim — every CSS rule in the file appears in this document exactly once.
File header comment (design intent, verbatim):
Inventory Management — Inventory Manager (iPad Pro 11" portrait, 834×1194)
Mirrors the Procurement Manager tablet shell exactly (same .tablet, .sb,.rsm-page-head, .rsm-body, .rsm-nav scaffolding) but uses the im-* prefixfor IM-specific components per the design-system HARD RULE on prefixownership. PRD §1 owner: single owner of custody from receiving throughworkstation assignment + bundle assignment to Team Lead.Font load: <link href="https://api.fontshare.com/v2/css?f[]=general-sans@500,600,700&display=swap" rel="stylesheet"> — General Sans, weights 500/600/700 only. No 400 loaded (a few rules declare font-weight: 400; they render at the browser-synthesized nearest weight).
1. Tokens
Section titled “1. Tokens”Verbatim :root block:
:root { /* Same token surface as Procurement Manager — see ../../DESIGN.md. */ --brand: #01E46F; --brand-dark: #013F1F; --g-primary: #01E46F; --g-dark: #05853E; --g-2: #DCFBE2; --g-3: #B5F1C2; --grey1: #F7F7F8; --grey2: #F0F0F2; --grey3: #E8E8EA; --grey4: #E0E0E2; --grey5: #E0E0E0; --grey6: #D9D9D9; --grey7: #B0B0B0; --grey9: #A0A0A0; --grey10: #888888; --grey11: #555555; --grey12: #202020; --red9: #E32627; --amber9: #B45309; --white: #FFFFFF; --text-main: #202020; --sh-card: 2.08px 2.78px 16px 0px rgba(193,197,210,0.03); --sh-sheet: 0 -8px 28px rgba(0, 0, 0, 0.18);}Non-token literals used repeatedly (canonical tonal palette — hard-coded, not vars):
| Tone | Tinted background | Glyph/text color |
|---|---|---|
| blue | rgba(30, 64, 175, 0.04) (card) / 0.06 (link row) / 0.08–0.12 (icon circle) |
#1E40AF |
| amber | rgba(180, 83, 9, 0.045) (card) / 0.04–0.06 (banner) / 0.08–0.12 (icon/chip) / #FEF3C7 (pill/avatar) |
var(--amber9) = #B45309 (pill text sometimes #92400E in toast) |
| red | rgba(185, 28, 28, 0.035) (card) / 0.04 (banner) / 0.08–0.10 (icon/chip) / #FEE2E2 (pill) |
#B91C1C |
| green | rgba(5, 133, 62, 0.04)–0.045 (card) / 0.06 (banner) / 0.10–0.12 (icon) / --g-2/--g-3 (pills) |
var(--g-dark) = #05853E |
| grey/neutral | var(--grey1) (row bg) / var(--grey2) (icon circle) / var(--grey3) (pill) |
var(--grey11) / var(--grey10) |
| info (toast/pill only) | #DBEAFE (pill) / #e6f4fe (toast) |
#1E40AF / #0d74ce |
Button hover green: #00C45B. Prototype backdrop: #0E0F12. Tab-screen page bg: #F5F5F7. Sheet scrim: rgba(14,15,18,0.45).
2. Shell
Section titled “2. Shell”iPad Pro 11” portrait, fixed 834×1194, radius 28, on a dark #0E0F12 centered body. One .tablet device, many absolutely-stacked .screens toggled with .active.
ASCII sketch
Section titled “ASCII sketch”body (dark #0E0F12, flex-centered, min-height 100vh)└── .tablet (834×1194, white, radius 28, overflow hidden, flex column) ├── .screen [.active] [.tab-screen] ← absolute inset:0, flex column; one per page │ ├── .im-scroll ← flex:1, overflow-y auto — EVERYTHING above nav scrolls │ │ ├── .sb / .sb--dark ← status bar (52px) — scrolls away with content │ │ ├── header .rsm-page-head ← title row (or .im-hero on dashboard) │ │ └── .rsm-body [.rsm-body--split]← page content │ ├── .im-detail-cta ← OPTIONAL sticky CTA strip (detail screens only), outside scroll │ ├── nav .rsm-nav ← 5-tab bottom nav, pinned outside scroll (z-index 4) │ └── .home-ind ← home indicator (absolute, z-index 3) ├── .im-sheet-overlay #sheet-* ← bottom sheets, absolute inset:0, z-index 60 (×3) └── .alert-toast #toast ← top-center toast, z-index 80Scroll model
Section titled “Scroll model”- The status bar, page head/hero, and body all live inside
.im-scrolland scroll together (comment in file: “body + hero scroll together; nav stays pinned”). .rsm-nav(bottom nav) and.im-detail-cta(detail-screen CTA strip) are siblings of.im-scroll— pinned, never scroll.goTo()resets.im-scrollscrollTop to 0 on every navigation.- Auth screens (splash/onboarding/welcome) have no
.im-scrolland no nav. - A standalone-page fit script scales
.tabletdown (transform: scale) when the window is smaller than 834×1194 so the host page never scrolls.
Verbatim shell CSS
Section titled “Verbatim shell CSS”* { box-sizing: border-box; margin: 0; padding: 0; }html, body { height: 100%; }body { font-family: 'General Sans', system-ui, -apple-system, sans-serif; background: #0E0F12; color: var(--grey12); letter-spacing: -0.01em; -webkit-font-smoothing: antialiased; display: flex; align-items: center; justify-content: center; min-height: 100vh;}
/* Tablet shell — iPad Pro 11" portrait. */.tablet { position: relative; width: 834px; height: 1194px; background: var(--white); overflow: hidden; border-radius: 28px; display: flex; flex-direction: column;}
.screen { display: none; flex-direction: column; position: absolute; inset: 0; background: var(--white); }.screen.active { display: flex; }.tab-screen { background: #F5F5F7; overflow: hidden; }
/* Inner scroll wrapper — body + hero scroll together; nav stays pinned. */.im-scroll { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }.im-scroll::-webkit-scrollbar { display: none; }
/* Home indicator. */.home-ind { position: absolute; bottom: 8px; left: 50%; width: 158px; height: 5px; border-radius: 100px; transform: translateX(-50%); background: var(--text-main); z-index: 3; }.home-ind--light { background: rgba(255,255,255,0.5); }
/* Status bar — taller on tablet. */.sb { height: 52px; padding: 18px 32px 0; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; position: relative; z-index: 2; }.sb-time { font-size: 15px; font-weight: 700; letter-spacing: -0.4px; color: var(--grey12); }.sb-icons { display: flex; align-items: center; gap: 6px; }.sb-icons svg { display: block; }.sb--dark .sb-time { color: var(--white); }.sb--dark .sb-icons svg path, .sb--dark .sb-icons svg rect, .sb--dark .sb-icons svg line { color: var(--white); fill: var(--white); }/* Page head + body — shared rsm-* scaffolding. */.rsm-page-head { background: transparent; padding: 0 24px 16px; flex-shrink: 0; }.rsm-page-head-row { position: relative; height: 64px; display: flex; align-items: center; justify-content: center; padding: 0 28px; margin: 0 -24px; }.rsm-page-title { font-size: 22px; font-weight: 600; color: var(--grey12); letter-spacing: -0.3px; flex: 1; text-align: center; }.rsm-page-back { position: absolute; left: 28px; width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--grey6); background: var(--white); display: flex; align-items: center; justify-content: center; color: var(--grey12); cursor: pointer; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; }.rsm-page-back:hover { background: var(--grey2); }.rsm-page-back svg { width: 20px; height: 20px; }
.rsm-body { padding: 0 24px 24px; display: flex; flex-direction: column; gap: 18px; }.rsm-body > * { flex-shrink: 0; }
.rsm-body--split { display: grid; grid-template-columns: 1.2fr 1fr; gap: 18px; align-content: start; }.rsm-body--split > * { min-width: 0; }
.rsm-section-head { display: flex; align-items: center; justify-content: space-between; padding: 4px 0; }.rsm-section-title { font-size: 16px; font-weight: 600; letter-spacing: -0.2px; }.rsm-section-meta { font-size: 12.5px; font-weight: 500; color: var(--grey11); }.rsm-section-link { font-size: 12.5px; font-weight: 600; color: var(--g-dark); text-decoration: none; cursor: pointer; background: none; border: 0; padding: 0; font-family: inherit; }[hidden] { display: none !important; }Status bar markup (verbatim, light variant — dark variant swaps every #202020 for white and adds .sb--dark):
<div class="sb"> <span class="sb-time">9:41</span> <div class="sb-icons"> <svg width="17" height="11" viewBox="0 0 17 11" fill="none"><rect x="0" y="5" width="3" height="6" rx="1" fill="#202020"></rect><rect x="4.5" y="3" width="3" height="8" rx="1" fill="#202020"></rect><rect x="9" y="1" width="3" height="10" rx="1" fill="#202020"></rect><rect x="13.5" y="0" width="3" height="11" rx="1" fill="#202020" opacity="0.35"></rect></svg> <svg width="16" height="12" viewBox="0 0 16 12" fill="none"><path d="M8 9.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3z" fill="#202020"></path><path d="M4.6 6.9a4.8 4.8 0 0 1 6.8 0" stroke="#202020" stroke-width="1.5" fill="none" stroke-linecap="round"></path><path d="M1.5 3.8a8.5 8.5 0 0 1 13 0" stroke="#202020" stroke-width="1.5" fill="none" stroke-linecap="round"></path></svg> <svg width="25" height="12" viewBox="0 0 25 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="3.5" stroke="#202020" stroke-opacity="0.35"></rect><rect x="2" y="2" width="17" height="8" rx="2" fill="#202020"></rect><path d="M23 4v4a2 2 0 0 0 0-4z" fill="#202020" opacity="0.4"></path></svg> </div></div>Viewport-fit script (verbatim, second <script> block):
/* Tablet viewport-fit — scales the .tablet shell down when the window is smaller than 834×1194 so the standalone page never scrolls the body. Identical pattern to Procurement Manager. */(function () { var el = document.querySelector('.tablet'); if (!el) return; function fit() { var sw = window.innerWidth; var sh = window.innerHeight; var scale = Math.min(sw / 834, sh / 1194, 1); if (scale < 1) { el.style.transform = 'scale(' + scale + ')'; el.style.transformOrigin = 'top center'; el.style.marginTop = '0'; document.body.style.alignItems = 'flex-start'; document.body.style.paddingTop = Math.max(0, (sh - 1194 * scale) / 2) + 'px'; document.body.style.minHeight = (1194 * scale) + 'px'; } else { el.style.transform = ''; el.style.transformOrigin = ''; el.style.marginTop = ''; document.body.style.alignItems = ''; document.body.style.paddingTop = ''; document.body.style.minHeight = ''; } } fit(); window.addEventListener('resize', fit);})();3. Atoms
Section titled “3. Atoms”3.1 Bottom nav (.rsm-nav) — the tablet “sidebar” equivalent
Section titled “3.1 Bottom nav (.rsm-nav) — the tablet “sidebar” equivalent”Selectors: .rsm-nav, .rsm-nav-tab, .rsm-nav-tab.is-active, .rsm-nav-badge
/* Bottom nav — 5-col grid, pinned outside the scroll wrapper. */.rsm-nav { display: grid; grid-template-columns: repeat(5, 1fr); background: var(--white); border-top: 1px solid var(--grey4); padding: 10px 32px 12px; flex-shrink: 0; position: relative; z-index: 4; }.rsm-nav-tab { display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 6px 4px 4px; background: transparent; border: 0; cursor: pointer; font-family: inherit; font-size: 11.5px; font-weight: 500; color: var(--grey10); -webkit-appearance: none; appearance: none; transition: color 0.18s ease; position: relative; }.rsm-nav-tab svg { width: 24px; height: 24px; }.rsm-nav-tab.is-active { color: var(--g-dark); font-weight: 600; }.rsm-nav-tab.is-active svg { color: var(--g-dark); }.rsm-nav-badge { position: absolute; top: 4px; right: calc(50% - 22px); min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: var(--red9); color: var(--white); font-size: 10.5px; font-weight: 700; display: flex; align-items: center; justify-content: center; line-height: 1; }Markup (verbatim; identical block repeated on every tab screen with is-active moved):
<nav class="rsm-nav" role="tablist"> <button class="rsm-nav-tab is-active" type="button" onclick="goTo('s-dashboard')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12 12 3l9 9"/><path d="M5 10v10a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1V10"/></svg><span>Home</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-requests')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg><span>Requests</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-bundles')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg><span>Bundles</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-custody')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg><span>Custody</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-profile')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg><span>Profile</span></button></nav>Usage: exactly 5 tabs (Home / Requests / Bundles / Custody / Profile), 24px feather-style stroked icons, active = --g-dark green + weight 600. .rsm-nav-badge (red count bubble) is defined but not used in this file’s markup. There is no sidebar, no group labels, no collapse — the nav is duplicated verbatim per screen and stays present on detail screens (parent tab kept is-active). The nav sits below .im-scroll in the DOM, so it never scrolls.
3.2 Topbar / header
Section titled “3.2 Topbar / header”There is no persistent topbar. Two patterns cover it:
(a) Status bar .sb / .sb--dark — see Shell section (52px, time 15px/700, three SVG glyphs). Dark variant used on brand-dark surfaces (splash-adjacent screens + dashboard hero).
(b) Dashboard hero (.im-hero) — dark brand header holding the status bar, avatar, name, role pill, and meta line. This is the only place a “user pill / avatar” appears in a header:
/* ── Dashboard hero (custody floor) ── */.im-hero { position: relative; background: var(--brand-dark); padding: 0 32px 80px; flex-shrink: 0; }.im-hero-bg { position: absolute; top: 0; right: 0; width: 360px; height: 100%; pointer-events: none; opacity: 0.12; display: flex; align-items: center; justify-content: flex-end; }.im-hero-bg svg { width: 100%; height: auto; }.im-hero-row { position: relative; display: flex; align-items: center; padding: 12px 0 20px; }.im-hero-id { display: flex; align-items: center; gap: 14px; min-width: 0; }.im-hero-avatar { width: 52px; height: 52px; border-radius: 50%; background: var(--brand); display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; color: var(--text-main); letter-spacing: -0.4px; flex-shrink: 0; }.im-hero-text { min-width: 0; }.im-hero-name { font-size: 20px; font-weight: 600; color: var(--white); letter-spacing: -0.3px; line-height: 26px; }.im-hero-meta { font-size: 13px; font-weight: 400; color: rgba(255,255,255,0.6); line-height: 18px; margin-top: 2px; }.im-hero-role { display: inline-block; font-size: 11px; font-weight: 600; padding: 3px 10px; border-radius: 100px; background: rgba(1,228,111,0.16); color: var(--g-primary); letter-spacing: 0.02em; margin: 4px 0 2px; }<header class="im-hero"> <div class="sb sb--dark"> … </div> <div class="im-hero-bg"> <svg viewBox="0 0 360 460" xmlns="http://www.w3.org/2000/svg"> <path d="M180 30L40 150v40l140-120 140 120v-40L180 30z" fill="#FFFFFF"/> <path d="M180 220L40 340v40l140-120 140 120v-40L180 220z" fill="#FFFFFF"/> </svg> </div> <div class="im-hero-row"> <div class="im-hero-id"> <span class="im-hero-avatar">OA</span> <div class="im-hero-text"> <p class="im-hero-name">Olufunke Adesina</p> <span class="im-hero-role">Inventory Manager</span> <p class="im-hero-meta">Custody floor · Wed, 19 May 2026</p> </div> </div> </div></header>Usage: hero has padding-bottom: 80px so the KPI strip can overlap it with margin-top: -64px. Ghost brand chevrons at 12% opacity on the right. No search anywhere in this app.
3.3 Page head (title + actions row)
Section titled “3.3 Page head (title + actions row)”Two co-existing shapes:
Centered title + round back button (detail screens) — uses shared rsm- classes .rsm-page-head-row, .rsm-page-title, .rsm-page-back (CSS in Shell section):
<header class="rsm-page-head"> <div class="rsm-page-head-row"> <button class="rsm-page-back" type="button" aria-label="Back" onclick="goTo('s-bundles')"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg> </button> <h1 class="rsm-page-title" id="bun-detail-page-title">PUSH-190526-001</h1> </div></header>Left-aligned title + optional right icon button (top-level tab screens) — im- variant:
/* Page-head row + icon button — shared shape used across IM screens. */.im-page-head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0; height: 64px; margin: 0; }.im-page-title { font-size: 22px; font-weight: 600; color: var(--grey12); letter-spacing: -0.3px; }.im-page-icon-btn { width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--grey6); background: var(--white); display: flex; align-items: center; justify-content: center; color: var(--grey12); cursor: pointer; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; flex-shrink: 0; }.im-page-icon-btn:hover { background: var(--grey2); }.im-page-icon-btn svg { width: 18px; height: 18px; }<header class="rsm-page-head"><div class="im-page-head-row"><h1 class="im-page-title">Requests</h1></div></header>With action (Profile screen):
<header class="rsm-page-head"> <div class="im-page-head-row"> <h1 class="im-page-title">Profile</h1> <button class="im-page-icon-btn" type="button" aria-label="Settings" onclick="showToast('Settings', 'Coming soon', 'info')"> <svg viewBox="0 0 24 24" …>…gear path…</svg> </button> </div></header>Usage: title is 22px/600 (never 700). No subtitle line under page titles in this app; section subs live inside cards. Section headers within a body use .rsm-section-head (.rsm-section-title 16px/600 + .rsm-section-meta 12.5px/500 grey or .rsm-section-link “View all” 12.5px/600 green).
3.4 Tabs & segmented controls
Section titled “3.4 Tabs & segmented controls”Only ONE tab variant exists: the pill segmented control .im-tabs (grey track, white raised active pill, count badges). No line tabs, no underline tabs. Used as the list-filter on Requests (2 tabs), Bundles (3), Custody (4), Holds (2). Auto-collapses its grid to the number of tabs via :has().
/* Tab strip — segmented control. Default 4 cols; collapses to N when only N tabs are present (matches the PM :has() trick). */.im-tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; padding: 4px; background: var(--grey2); border-radius: 12px; }.im-tabs:has(> .im-tab:nth-child(2):last-child) { grid-template-columns: repeat(2, 1fr); }.im-tabs:has(> .im-tab:nth-child(3):last-child) { grid-template-columns: repeat(3, 1fr); }.im-tab { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 9px 6px; background: transparent; border: 0; border-radius: 8px; cursor: pointer; font-family: inherit; font-size: 13px; font-weight: 500; color: var(--grey11); -webkit-appearance: none; appearance: none; transition: background 0.15s ease, color 0.15s ease; }.im-tab:hover { color: var(--grey12); }.im-tab[aria-pressed="true"] { background: var(--white); color: var(--g-dark); font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,0.06); }.im-tab-count { display: inline-flex; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; background: var(--grey3); color: var(--grey11); font-size: 11.5px; font-weight: 600; font-variant-numeric: tabular-nums; }.im-tab[aria-pressed="true"] .im-tab-count { background: var(--g-3); color: var(--g-dark); }<div class="im-tabs" role="tablist"> <button class="im-tab" type="button" aria-pressed="true" data-bun-tab="active" onclick="setBundleTab(this)">Active <span class="im-tab-count" id="im-bun-count-active">4</span></button> <button class="im-tab" type="button" aria-pressed="false" data-bun-tab="completed" onclick="setBundleTab(this)">Completed <span class="im-tab-count" id="im-bun-count-completed">3</span></button> <button class="im-tab" type="button" aria-pressed="false" data-bun-tab="returns" onclick="setBundleTab(this)">Returns <span class="im-tab-count" id="im-bun-count-returns">2</span></button></div>Selected state = aria-pressed="true" (not a class). Each tab pairs with a pane div[data-*-pane] where the inactive panes carry the hidden attribute; the JS toggles both hidden and style.display (flex/none). Panes always use display:flex; flex-direction:column; gap:10px inline.
The bottom nav (.rsm-nav-tab, above) is the other “tab” control; there is no other segmented variant.
3.5 List / queue card system (tablet equivalent of the table)
Section titled “3.5 List / queue card system (tablet equivalent of the table)”There is no <table> anywhere. Lists are stacks of full-width tappable cards at gap: 10px. Four sibling species, all the same skeleton (mark circle + id/sub + right pill, then a meta row, optionally actions):
(a) .im-list-card — the canonical list row (Requests + Bundles). Tone-driven background; white+shadow by default, tinted flat (shadow removed) when toned:
/* List row card — request + bundle list rows. Tone-driven background. */.im-list-card { background: var(--white); border-radius: 14px; box-shadow: var(--sh-card); padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; border: 0; width: 100%; text-align: left; font-family: inherit; cursor: pointer; -webkit-appearance: none; appearance: none; transition: filter 0.15s ease, transform 0.1s ease; }.im-list-card:hover { filter: brightness(0.99); }.im-list-card:active { transform: scale(0.998); }.im-list-card[data-tone="blue"] { background: rgba(30, 64, 175, 0.04); box-shadow: none; }.im-list-card[data-tone="amber"] { background: rgba(180, 83, 9, 0.045); box-shadow: none; }.im-list-card[data-tone="red"] { background: rgba(185, 28, 28, 0.035); box-shadow: none; }.im-list-card[data-tone="muted"] { background: var(--grey1); box-shadow: none; }.im-list-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }.im-list-id-block { display: flex; align-items: center; gap: 12px; min-width: 0; }.im-list-mark { width: 42px; height: 42px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: var(--grey2); color: var(--grey11); }.im-list-mark[data-tone="blue"] { background: rgba(30, 64, 175, 0.10); color: #1E40AF; }.im-list-mark[data-tone="amber"] { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.im-list-mark[data-tone="red"] { background: rgba(185, 28, 28, 0.10); color: #B91C1C; }.im-list-mark[data-tone="green"] { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.im-list-mark[data-tone="grey"] { background: var(--grey2); color: var(--grey11); }.im-list-mark svg { width: 20px; height: 20px; }.im-list-id { font-size: 15px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; font-variant-numeric: tabular-nums; }.im-list-sub { font-size: 12.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.im-list-meta { display: flex; flex-wrap: wrap; gap: 8px 14px; font-size: 12px; font-weight: 500; color: var(--grey11); }.im-list-meta-cell { display: inline-flex; align-items: center; gap: 6px; }.im-list-meta-cell svg { width: 14px; height: 14px; color: var(--grey9); }.im-list-actions { display: flex; gap: 8px; padding-top: 4px; }.im-list-actions button { height: 36px; padding: 0 14px; font-size: 13px; width: auto; flex: 1; border: 0; border-radius: 10px; cursor: pointer; font-family: inherit; font-weight: 600; -webkit-appearance: none; appearance: none; letter-spacing: -0.05em; transition: filter 0.15s ease; }.im-list-actions button:hover { filter: brightness(0.96); }.im-list-assign-btn { background: var(--brand); color: var(--text-main); }.im-list-decline-btn { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.im-list-confirm-btn { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }Markup as rendered by requestRowHtml() (JS template, representative):
<div class="im-list-card" data-tone="blue" onclick="openAssignSheet('REQ-190526-003')"> <div class="im-list-head"> <div class="im-list-id-block"> <span class="im-list-mark" data-tone="blue"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/></svg> </span> <div> <p class="im-list-id">REQ-190526-003</p> <p class="im-list-sub">→ TL Adekunle · WO-190526-021 · Olamide Ogundeji</p> </div> </div> <span class="im-pill" data-state="pending">Pending · 23 min</span> </div> <div class="im-list-meta"> <span class="im-list-meta-cell"><svg …>…</svg>kit-K-88301-WS-0016</span> <span class="im-list-meta-cell"><svg …>…</svg>RB-009116</span> <span class="im-list-meta-cell"><svg …>…</svg>5 auto-mech parts</span> </div> <div class="im-list-actions" onclick="event.stopPropagation()"> <button type="button" class="im-list-decline-btn" onclick="declineRequest('REQ-190526-003')">Decline</button> <button type="button" class="im-list-assign-btn" onclick="openAssignSheet('REQ-190526-003')">Assign bundle</button> </div></div>(b) .im-bundle-card — dashboard variant of the same row (static markup on the dashboard “Today’s bundle pushes” rail; always white; radius 16, gap 14):
/* ── Bundle / push activity card (left rail) ── */.im-bundle-card { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 16px 18px; display: flex; flex-direction: column; gap: 14px; border: 0; text-align: left; font-family: inherit; width: 100%; cursor: pointer; -webkit-appearance: none; appearance: none; transition: transform 0.1s; }.im-bundle-card:active { transform: scale(0.998); }.im-bundle-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }.im-bundle-id-block { display: flex; align-items: center; gap: 12px; min-width: 0; }.im-bundle-mark { width: 42px; height: 42px; border-radius: 50%; background: rgba(30, 64, 175, 0.08); color: #1E40AF; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.im-bundle-mark[data-tone="green"] { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.im-bundle-mark[data-tone="amber"] { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.im-bundle-mark[data-tone="red"] { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.im-bundle-mark[data-tone="grey"] { background: var(--grey2); color: var(--grey11); }.im-bundle-mark svg { width: 20px; height: 20px; }.im-bundle-id { font-size: 15px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; font-variant-numeric: tabular-nums; }.im-bundle-sub { font-size: 12.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.im-bundle-meta { display: flex; flex-wrap: wrap; gap: 8px 14px; font-size: 12px; font-weight: 500; color: var(--grey11); }.im-bundle-meta-cell { display: inline-flex; align-items: center; gap: 6px; }.im-bundle-meta-cell svg { width: 14px; height: 14px; color: var(--grey9); }<button class="im-bundle-card" type="button" onclick="openBundle('PUSH-190526-001')"> <div class="im-bundle-head"> <div class="im-bundle-id-block"> <span class="im-bundle-mark" data-tone="blue"> <svg viewBox="0 0 24 24" …>…package icon…</svg> </span> <div> <p class="im-bundle-id">PUSH-190526-001</p> <p class="im-bundle-sub">→ Team Lead Adekunle · WO-190526-014</p> </div> </div> <span class="im-pill" data-state="assigned">Assigned</span> </div> <div class="im-bundle-meta"> <span class="im-bundle-meta-cell"><svg …>…</svg>kit-K-88301-WS-0014</span> <span class="im-bundle-meta-cell"><svg …>…</svg>RB-009114</span> <span class="im-bundle-meta-cell"><svg …>…</svg>4 auto-mech parts</span> <span class="im-bundle-meta-cell"><svg …>…</svg>Assigned 8:42 AM</span> </div></button>(c) .im-custody-row — stock summary card with breakdown grid + big ATP number:
/* Custody row — item-type summary card with breakdown grid. */.im-custody-row { background: var(--white); border-radius: 14px; box-shadow: var(--sh-card); padding: 16px 18px; display: flex; flex-direction: column; gap: 14px; border: 0; width: 100%; text-align: left; font-family: inherit; cursor: pointer; -webkit-appearance: none; appearance: none; transition: filter 0.15s ease, transform 0.1s ease; }.im-custody-row:hover { filter: brightness(0.99); }.im-custody-row:active { transform: scale(0.998); }.im-custody-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }.im-custody-id-block { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }.im-custody-mark { width: 40px; height: 40px; border-radius: 50%; background: var(--grey2); color: var(--grey11); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.im-custody-mark[data-tone="green"] { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.im-custody-mark[data-tone="amber"] { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.im-custody-mark[data-tone="red"] { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.im-custody-mark svg { width: 18px; height: 18px; }.im-custody-name { font-size: 15px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; }.im-custody-sub { font-size: 12.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.im-custody-atp { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }.im-custody-atp-num { font-size: 22px; font-weight: 700; color: var(--grey12); letter-spacing: -0.4px; line-height: 1; font-variant-numeric: tabular-nums; }.im-custody-atp[data-tone="amber"] .im-custody-atp-num { color: var(--amber9); }.im-custody-atp[data-tone="red"] .im-custody-atp-num { color: #B91C1C; }.im-custody-atp-label { font-size: 11px; font-weight: 600; color: var(--grey10); letter-spacing: 0.04em; text-transform: uppercase; }
/* 4-cell breakdown row for in-list view; 7-cell expands in detail. */.im-breakdown { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px 14px; padding: 12px 0 0; border-top: 1px solid var(--grey3); }.im-breakdown--full { grid-template-columns: repeat(7, 1fr); gap: 14px 10px; padding: 0; border-top: 0; }.im-breakdown-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }.im-breakdown-label { font-size: 10.5px; font-weight: 600; color: var(--grey10); letter-spacing: 0.04em; text-transform: uppercase; }.im-breakdown-val { font-size: 17px; font-weight: 700; color: var(--grey12); letter-spacing: -0.2px; font-variant-numeric: tabular-nums; line-height: 1; }.im-breakdown-cell[data-tone="amber"] .im-breakdown-val { color: var(--amber9); }.im-breakdown-cell[data-tone="red"] .im-breakdown-val { color: #B91C1C; }.im-breakdown-cell[data-tone="green"] .im-breakdown-val { color: var(--g-dark); }.im-breakdown-cell[data-tone="muted"] .im-breakdown-val { color: var(--grey10); }
/* Alert chips for non-zero quarantined / missing on the custody row. */.im-custody-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }.im-custody-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 100px; letter-spacing: -0.02em; }.im-custody-chip[data-tone="amber"] { background: rgba(180, 83, 9, 0.08); color: var(--amber9); }.im-custody-chip[data-tone="red"] { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.im-custody-chip svg { width: 11px; height: 11px; }Rendered markup (custodyRowHtml()):
<button class="im-custody-row" type="button" onclick="openCustodyItem('wiring-harness')"> <div class="im-custody-head"> <div class="im-custody-id-block"> <span class="im-custody-mark" data-tone="green"><svg …>…package…</svg></span> <div> <p class="im-custody-name">Wiring harness</p> <p class="im-custody-sub">WH-A1 · Apapa Components</p> </div> </div> <div class="im-custody-atp" data-tone="amber"> <span class="im-custody-atp-num">36</span> <span class="im-custody-atp-label">ATP</span> </div> </div> <div class="im-breakdown"> <div class="im-breakdown-cell"><span class="im-breakdown-label">Held</span><span class="im-breakdown-val">12</span></div> <div class="im-breakdown-cell"><span class="im-breakdown-label">Assigned</span><span class="im-breakdown-val">8</span></div> <div class="im-breakdown-cell"><span class="im-breakdown-label">Consumed</span><span class="im-breakdown-val">184</span></div> <div class="im-breakdown-cell" data-tone="amber"><span class="im-breakdown-label">ATP</span><span class="im-breakdown-val">36</span></div> </div> <div class="im-custody-chips"><span class="im-custody-chip" data-tone="amber"><svg …>…</svg>2 quarantined</span><span class="im-custody-chip" data-tone="red"><svg …>…</svg>1 missing</span></div></button>Tone logic (JS): ATP days-of-cover = atp / dailyBurn; red < 3 days, amber < 5, else neutral.
(d) .im-hold-row — rider-bound hold card (NOT clickable as a whole; contains an inner tappable .im-hold-link):
/* Rider-bound hold row (s-holds). */.im-hold-row { background: var(--white); border-radius: 14px; box-shadow: var(--sh-card); padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }.im-hold-row[data-tone="amber"] { background: rgba(180, 83, 9, 0.04); box-shadow: none; }.im-hold-row[data-tone="red"] { background: rgba(185, 28, 28, 0.035); box-shadow: none; }.im-hold-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }.im-hold-rider { font-size: 14.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }.im-hold-meta { font-size: 12px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.im-hold-components { display: flex; flex-direction: column; gap: 6px; }.im-hold-component { display: grid; grid-template-columns: 24px 1fr auto; gap: 10px; align-items: center; padding: 8px 12px; background: var(--grey1); border-radius: 10px; }.im-hold-row[data-tone="amber"] .im-hold-component,.im-hold-row[data-tone="red"] .im-hold-component { background: rgba(255,255,255,0.6); }.im-hold-component-ic { width: 24px; height: 24px; border-radius: 50%; background: var(--grey2); color: var(--grey11); display: flex; align-items: center; justify-content: center; }.im-hold-component-ic svg { width: 13px; height: 13px; }.im-hold-component-ic[data-state="ok"] { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.im-hold-component-ic[data-state="waiting"] { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.im-hold-component-name { font-size: 13px; font-weight: 600; color: var(--grey12); letter-spacing: -0.02em; }.im-hold-component-sub { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 1px; }.im-hold-component-status { font-size: 11px; font-weight: 600; color: var(--grey11); letter-spacing: -0.02em; flex-shrink: 0; }.im-hold-component-status[data-state="waiting"] { color: var(--amber9); }
/* Footer rows on a hold card. Either an informational line (ready / preserved) or a tappable link to the pending bundle request so the IM can jump straight into the assign sheet. */.im-hold-foot { font-size: 11.5px; font-weight: 500; color: var(--grey10); letter-spacing: -0.02em; padding-top: 4px; }.im-hold-link { display: grid; grid-template-columns: 24px 1fr auto; gap: 10px; align-items: center; padding: 10px 12px; background: rgba(30, 64, 175, 0.06); border: 0; border-radius: 10px; cursor: pointer; width: 100%; text-align: left; font-family: inherit; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; }.im-hold-link:hover { background: rgba(30, 64, 175, 0.10); }.im-hold-link-ic { width: 24px; height: 24px; border-radius: 50%; background: rgba(30, 64, 175, 0.12); color: #1E40AF; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.im-hold-link-ic svg { width: 13px; height: 13px; }.im-hold-link-text { font-size: 12.5px; font-weight: 500; color: #1E40AF; letter-spacing: -0.02em; min-width: 0; }.im-hold-link-text strong { font-weight: 600; }.im-hold-link-chev { width: 14px; height: 14px; color: #1E40AF; flex-shrink: 0; }Rendered markup (holdRowHtml()):
<div class="im-hold-row" data-tone="amber"> <div class="im-hold-head"> <div> <p class="im-hold-rider">Olamide Ogundeji · FST559AB</p> <p class="im-hold-meta">HOLD-7392 · 4 days held · Waiting on Battery BAT-44 — 1.8d cover</p> </div> <span class="im-pill" data-state="pending">Waiting · 4 days</span> </div> <div class="im-hold-components"> <div class="im-hold-component"> <span class="im-hold-component-ic" data-state="ok"><svg …>check</svg></span> <div> <p class="im-hold-component-name">kit-K-88301-WS-0014</p> <p class="im-hold-component-sub">Held</p> </div> <span class="im-hold-component-status" data-state="ok">Available</span> </div> <div class="im-hold-component"> <span class="im-hold-component-ic" data-state="waiting"><svg …>clock</svg></span> <div> <p class="im-hold-component-name">Battery BAT-44 hold</p> <p class="im-hold-component-sub">Waiting on ATP (1.8d cover)</p> </div> <span class="im-hold-component-status" data-state="waiting">Waiting</span> </div> </div> <button class="im-hold-link" type="button" onclick="openAssignSheet('REQ-190526-001')"> <span class="im-hold-link-ic"><svg …>…</svg></span> <span class="im-hold-link-text">TL submitted bundle request <strong>REQ-190526-001</strong> — open to assign</span> <svg class="im-hold-link-chev" …><polyline points="9 18 15 12 9 6"/></svg> </button></div>(e) Compact row atoms inside cards — alert row, activity row, log row, movement row, parts row, serial row:
/* ── Alert row (used inside side cards for requests, holds, activity) ── */.im-alert-row { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: 12px; border: 0; width: 100%; text-align: left; font-family: inherit; cursor: pointer; -webkit-appearance: none; appearance: none; background: var(--grey1); transition: filter 0.15s ease; }.im-alert-row:hover { filter: brightness(0.97); }.im-alert-row[data-tone="amber"] { background: rgba(180, 83, 9, 0.045); }.im-alert-row[data-tone="red"] { background: rgba(185, 28, 28, 0.035); }.im-alert-row[data-tone="blue"] { background: rgba(30, 64, 175, 0.04); }.im-alert-row[data-tone="green"] { background: rgba(5, 133, 62, 0.045); }.im-alert-row[data-tone="grey"] { background: var(--grey1); }.im-alert-ic { width: 32px; height: 32px; border-radius: 50%; background: rgba(30, 64, 175, 0.10); color: #1E40AF; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.im-alert-ic svg { width: 16px; height: 16px; }.im-alert-row[data-tone="amber"] .im-alert-ic { background: rgba(180, 83, 9, 0.12); color: var(--amber9); }.im-alert-row[data-tone="red"] .im-alert-ic { background: rgba(185, 28, 28, 0.10); color: #B91C1C; }.im-alert-row[data-tone="green"] .im-alert-ic { background: rgba(5, 133, 62, 0.12); color: var(--g-dark); }.im-alert-text { flex: 1; min-width: 0; }.im-alert-title { font-size: 13px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }.im-alert-row[data-tone="amber"] .im-alert-title { color: var(--amber9); }.im-alert-row[data-tone="red"] .im-alert-title { color: #B91C1C; }.im-alert-row[data-tone="blue"] .im-alert-title { color: #1E40AF; }.im-alert-row[data-tone="green"] .im-alert-title { color: var(--g-dark); }.im-alert-sub { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.im-alert-chev { width: 16px; height: 16px; color: var(--grey9); flex-shrink: 0; }/* ── Activity stream (right-rail bottom card) ── */.im-activity-row { display: grid; grid-template-columns: 32px 1fr auto; gap: 10px; align-items: flex-start; }.im-activity-avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--g-3); color: var(--g-dark); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; letter-spacing: -0.2px; flex-shrink: 0; }.im-activity-avatar[data-tone="blue"] { background: rgba(30, 64, 175, 0.12); color: #1E40AF; }.im-activity-avatar[data-tone="amber"] { background: #FEF3C7; color: var(--amber9); }.im-activity-text { flex: 1; min-width: 0; font-size: 12.5px; line-height: 18px; color: var(--grey12); }.im-activity-text strong { font-weight: 600; }.im-activity-time { font-size: 11px; font-weight: 500; color: var(--grey10); white-space: nowrap; flex-shrink: 0; padding-top: 1px; }/* Composite event log (PRD §13). */.im-log-row { display: grid; grid-template-columns: 32px 1fr; gap: 10px; align-items: flex-start; }.im-log-avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--g-3); color: var(--g-dark); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; letter-spacing: -0.2px; flex-shrink: 0; }.im-log-avatar[data-tone="blue"] { background: rgba(30, 64, 175, 0.12); color: #1E40AF; }.im-log-avatar[data-tone="amber"] { background: #FEF3C7; color: var(--amber9); }.im-log-avatar[data-tone="red"] { background: rgba(185, 28, 28, 0.10); color: #B91C1C; }.im-log-text { font-size: 13px; font-weight: 500; color: var(--grey12); line-height: 18px; }.im-log-text strong { font-weight: 600; }.im-log-time { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.im-log-event-id { display: inline-block; font-size: 10.5px; font-weight: 600; padding: 1px 6px; border-radius: 100px; background: var(--grey2); color: var(--grey10); margin-left: 6px; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }/* Next-event preview row inside the composite event log card. Production: this slot is fed by inbound webhooks (e.g. the Team Lead app firing battery.installed when they scan in at the workstation). Prototype: includes a "Simulate" affordance that fires the same state mutation so demos can close out the lifecycle without a real TL app present. */.im-log-pending-row { margin-top: 4px; padding: 12px 14px; border: 1px dashed var(--grey5); border-radius: 12px; background: var(--grey1); display: grid; grid-template-columns: 24px 1fr auto; gap: 10px; align-items: center; }.im-log-pending-ic { width: 24px; height: 24px; border-radius: 50%; background: var(--grey2); color: var(--grey10); display: flex; align-items: center; justify-content: center; }.im-log-pending-ic svg { width: 13px; height: 13px; }.im-log-pending-text { min-width: 0; }.im-log-pending-title { font-size: 12.5px; font-weight: 500; color: var(--grey11); line-height: 18px; }.im-log-pending-title strong { font-weight: 600; color: var(--grey12); }.im-log-pending-meta { font-size: 10.5px; font-family: 'SF Mono', Menlo, monospace; color: var(--grey10); margin-top: 2px; }.im-log-sim-btn { padding: 6px 12px; border-radius: 100px; background: transparent; border: 1px dashed var(--grey6); color: var(--grey11); font-family: inherit; font-size: 11px; font-weight: 600; cursor: pointer; -webkit-appearance: none; appearance: none; letter-spacing: 0.02em; text-transform: uppercase; flex-shrink: 0; transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; }.im-log-sim-btn:hover { background: var(--grey2); color: var(--grey12); border-color: var(--grey9); }/* Bundle composition list — read-only inventory of what's in the bundle. The TL scans the bundle as a single unit (one barcode → resolves to its contents), so per-item state would mislead. The overall scan status lives on the bundle (hero pill + log). */.im-parts-row { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; padding: 10px 0; }.im-parts-row + .im-parts-row { border-top: 1px solid var(--grey3); }.im-parts-name { font-size: 13.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }.im-parts-sub { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 1px; }.im-parts-qty { font-size: 13px; font-weight: 600; color: var(--grey11); font-variant-numeric: tabular-nums; flex-shrink: 0; }/* Movement timeline row (used in custody detail). */.im-movement-row { display: grid; grid-template-columns: 32px 1fr auto; gap: 10px; align-items: flex-start; padding: 4px 0; }.im-movement-ic { width: 32px; height: 32px; border-radius: 50%; background: var(--grey2); color: var(--grey11); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.im-movement-ic[data-tone="green"] { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.im-movement-ic[data-tone="blue"] { background: rgba(30, 64, 175, 0.10); color: #1E40AF; }.im-movement-ic[data-tone="amber"] { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.im-movement-ic svg { width: 14px; height: 14px; }.im-movement-text { font-size: 13px; font-weight: 500; color: var(--grey12); line-height: 18px; }.im-movement-text strong { font-weight: 600; }.im-movement-meta { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.im-movement-qty { font-size: 13px; font-weight: 600; color: var(--grey11); font-variant-numeric: tabular-nums; flex-shrink: 0; padding-top: 4px; }/* Serial-assignment list — preview of auto-generated serials. */.im-serial-row { display: grid; grid-template-columns: 28px 1fr auto; gap: 12px; align-items: center; padding: 10px 12px; background: var(--grey1); border-radius: 10px; }.im-serial-ic { width: 24px; height: 24px; border-radius: 50%; background: rgba(5, 133, 62, 0.12); color: var(--g-dark); display: flex; align-items: center; justify-content: center; }.im-serial-ic svg { width: 12px; height: 12px; }.im-serial-id { font-size: 13px; font-weight: 600; color: var(--grey12); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }.im-serial-sub { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 1px; }.im-serial-tag { font-size: 11px; font-weight: 600; color: var(--g-dark); flex-shrink: 0; }Alert-row markup (dashboard side cards):
<button class="im-alert-row" type="button" data-tone="amber" onclick="goTo('s-requests')"> <span class="im-alert-ic"><svg viewBox="0 0 24 24" …><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></span> <div class="im-alert-text"> <p class="im-alert-title">REQ-190526-007 · Brake pot × 2</p> <p class="im-alert-sub">Auto Mech Yusuf · WO-190526-014 · 18m ago</p> </div> <svg class="im-alert-chev" viewBox="0 0 24 24" …><polyline points="9 18 15 12 9 6"/></svg></button>Usage notes: list rows are <button> (or <div onclick> when they contain nested buttons — the actions row wraps in onclick="event.stopPropagation()"). Empty pane = inline-styled centered grey text (see 3.14). Row hover = filter: brightness(0.99), press = transform: scale(0.998). There is no pagination and no table footer; lists show everything.
3.6 Buttons
Section titled “3.6 Buttons”/* ── Buttons (shared) ── */.btn-primary { display: flex; align-items: center; justify-content: center; width: 100%; height: 52px; background: var(--brand); border: 1.5px solid var(--brand); border-radius: 10px; font-family: inherit; font-size: 16px; font-weight: 600; color: var(--text-main); letter-spacing: -0.2px; cursor: pointer; transition: background 0.18s ease, border-color 0.18s ease, transform 0.1s; -webkit-appearance: none; appearance: none; }.btn-primary:hover { background: #00C45B; border-color: #00C45B; }.btn-primary:active { transform: scale(0.98); }.btn-primary.is-disabled { background: var(--grey3); border-color: var(--grey3); color: var(--grey9); cursor: not-allowed; }.btn-primary--lg { height: 56px; font-size: 17px; }.btn-secondary { display: flex; align-items: center; justify-content: center; width: 100%; height: 52px; background: transparent; border: 1.5px solid var(--grey5); border-radius: 10px; font-family: inherit; font-size: 16px; font-weight: 600; color: var(--grey12); letter-spacing: -0.2px; cursor: pointer; transition: background 0.18s ease, border-color 0.18s ease, transform 0.1s; -webkit-appearance: none; appearance: none; }.btn-secondary:hover { background: var(--grey2); }.btn-secondary:active { transform: scale(0.98); }.btn-secondary--lg { height: 56px; font-size: 17px; }Small inline-action buttons (inside list cards; CSS in 3.5a): height 36, radius 10, 13px/600 —
.im-list-assign-btn (brand green bg + dark text), .im-list-decline-btn (red 8% tint + red text), .im-list-confirm-btn (green 10% tint + green text).
Pill-shaped micro buttons:
.im-unmarked-btn { padding: 8px 14px; border-radius: 100px; background: var(--amber9); color: var(--white); border: 0; font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; -webkit-appearance: none; appearance: none; letter-spacing: -0.02em; flex-shrink: 0; transition: filter 0.15s ease; }.im-unmarked-btn:hover { filter: brightness(0.95); }(.im-log-sim-btn — dashed prototype-only pill, see 3.5e.)
Icon buttons (44×44 circle, 1px --grey6 border, white bg): .rsm-page-back (Shell) and .im-page-icon-btn (3.3).
Markup:
<button class="btn-primary btn-primary--lg" onclick="goTo('s-welcome')">Get started</button><button class="btn-primary btn-primary--lg is-disabled" id="welcome-login-btn" disabled onclick="attemptLogin()">Sign in</button><button class="btn-secondary btn-secondary--lg" type="button" onclick="closeAssignSheet()">Cancel</button>Usage: primary = brand green #01E46F with DARK text var(--text-main) (#202020) — never white-on-green. Buttons are full-width by default (auth screens, sheet CTAs); in .im-detail-cta they are overridden to height: 48px; flex: 1; width: auto. There is NO solid-red destructive button — destructive actions use the secondary outline (labelled e.g. “Cancel push”) or the red-tinted .im-list-decline-btn. Disabled = both disabled attr and .is-disabled class. JS-disabled assign button uses inline disabled style="opacity:0.55; cursor:not-allowed;".
3.7 Badges & status pills
Section titled “3.7 Badges & status pills”State pill .im-pill — the canonical status badge, data-state driven:
/* ── State pill ── */.im-pill { display: inline-flex; align-items: center; font-size: 10.5px; font-weight: 600; padding: 3px 9px; border-radius: 100px; letter-spacing: 0.02em; background: var(--g-3); color: var(--g-dark); white-space: nowrap; flex-shrink: 0; }.im-pill[data-state="pending"] { background: #FEF3C7; color: var(--amber9); }.im-pill[data-state="assigned"] { background: #DBEAFE; color: #1E40AF; }.im-pill[data-state="consumed"] { background: var(--g-3); color: var(--g-dark); }.im-pill[data-state="returned"] { background: var(--grey3); color: var(--grey11); }.im-pill[data-state="overdue"] { background: #FEE2E2; color: #B91C1C; }.im-pill[data-state="held"] { background: var(--g-3); color: var(--g-dark); }.im-pill[data-state="in-stock"] { background: var(--g-2); color: var(--g-dark); }<span class="im-pill" data-state="overdue">Overdue · 3h 12m</span>Pill labels seen: Pending, Pending · {age}, Mid-repair · {age}, Assigned, Assigned · {age}, Scanned in, Overdue · {age}, Return · {age}, Waiting · {age}, Ready · {age}, In stock, Below threshold, Stockout risk, 8 unmarked. Pills often carry the age after a middot.
Other badge species: role pills .im-hero-role / .rsm-profile-role-pill (11px/600, radius 100px, green tint), tab count .im-tab-count, nav badge .rsm-nav-badge (red, 700), alert chips .im-custody-chip, event-id chip .im-log-event-id (grey mono-ish tabular pill). There is no status-dot pattern in this file — state is always a filled tinted pill or a tinted icon circle, never a dot.
3.8 KPI / stat cards
Section titled “3.8 KPI / stat cards”/* ── KPI strip — 6 tiles in 3+3 wrap. */.im-kpi-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: -64px 24px 0; position: relative; z-index: 2; }.im-kpi { background: var(--white); border-radius: 18px; box-shadow: var(--sh-card); padding: 18px 18px 16px; display: flex; flex-direction: column; gap: 8px; min-width: 0; border: 0; text-align: left; font-family: inherit; -webkit-appearance: none; appearance: none; }button.im-kpi { cursor: pointer; transition: filter 0.15s ease, transform 0.1s ease; }button.im-kpi:hover { filter: brightness(0.99); }button.im-kpi:active { transform: scale(0.998); }.im-kpi-head { display: flex; align-items: center; gap: 10px; }.im-kpi-ic { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.im-kpi-ic svg { width: 18px; height: 18px; }.im-kpi[data-tone="blue"] .im-kpi-ic { background: rgba(30, 64, 175, 0.10); color: #1E40AF; }.im-kpi[data-tone="amber"] .im-kpi-ic { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.im-kpi[data-tone="red"] .im-kpi-ic { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.im-kpi[data-tone="neutral"] .im-kpi-ic { background: var(--grey2); color: var(--grey11); }.im-kpi-label { font-size: 13px; font-weight: 500; color: var(--grey11); letter-spacing: -0.05em; }.im-kpi-num { font-size: 28px; font-weight: 700; letter-spacing: -0.4px; line-height: 32px; color: var(--grey12); font-variant-numeric: tabular-nums; }.im-kpi-foot { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: -2px; }<div class="im-kpi-strip" role="group" aria-label="Today overview"> <button class="im-kpi" data-tone="amber" type="button" onclick="goTo('s-requests')"> <div class="im-kpi-head"> <span class="im-kpi-ic"><svg viewBox="0 0 24 24" …>…</svg></span> <span class="im-kpi-label">Pending requests</span> </div> <p class="im-kpi-num" id="dash-req-count">5</p> <p class="im-kpi-foot" id="dash-req-sub">3 bundles · 2 mid-repair</p> </button> …</div>Usage: 6 tiles in a 3-column grid overlapping the dark hero (margin: -64px 24px 0). Structure = icon circle (36px, tonal tint) + 13px/500 label on one row, then 28px/700 tabular number, then 11.5px grey foot. Tappable tiles are <button> and route to the relevant screen; non-tappable ones are <div> (no cursor/hover). Card stays WHITE regardless of tone — only the icon circle is tinted. Numbers are hydrated live by renderDashKpis().
3.9 Cards / panels
Section titled “3.9 Cards / panels”NO card in this file has a border. Card containers use box-shadow: var(--sh-card) on white, or a flat tinted/grey background with box-shadow: none. (The only bordered boxes are buttons — .rsm-page-back, .im-page-icon-btn, .btn-secondary — plus the intentionally-dashed prototype slot .im-log-pending-row/.im-log-sim-btn, the 1px structural dividers border-top on .rsm-nav, .im-detail-cta, .im-breakdown, .im-parts-row + .im-parts-row, .rsm-profile-row + .rsm-profile-row::before, and the input field.)
Panel species and specs:
| Class | Radius | Padding | Surface |
|---|---|---|---|
.im-kpi |
18 | 18/18/16 | white + --sh-card |
.im-side-card |
16 | 18 | white + --sh-card |
.im-detail-card |
16 | 16 18 | white + --sh-card |
.im-detail-hero |
16 | 20 22 | tinted (tone), no shadow |
.im-bundle-card |
16 | 16 18 | white + --sh-card |
.im-list-card |
14 | 14 16 | white + shadow, or tinted flat |
.im-custody-row / .im-hold-row |
14 | 16 18 / 14 16 | white + shadow, or tinted flat |
.im-holds-link-card |
16 | 14 18 | white + --sh-card |
.im-unmarked-card |
16 | 14 18 | amber tint, no shadow |
.rsm-profile-user |
18 | 20 | white + --sh-card |
.rsm-profile-section |
16 | 4 0 | white + --sh-card |
/* ── Side cards (right rail) ── */.im-side-card { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 18px; display: flex; flex-direction: column; gap: 14px; }.im-side-head { display: flex; align-items: center; justify-content: space-between; }.im-side-title { font-size: 15px; font-weight: 600; color: var(--grey12); letter-spacing: -0.15px; }.im-side-link { font-size: 12.5px; font-weight: 600; color: var(--g-dark); cursor: pointer; background: none; border: 0; padding: 0; font-family: inherit; }.im-side-body { display: flex; flex-direction: column; gap: 12px; }/* Bundle detail layout. */.im-detail-card { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }.im-detail-hero { background: rgba(30, 64, 175, 0.04); border-radius: 16px; padding: 20px 22px; display: flex; flex-direction: column; gap: 14px; }.im-detail-hero[data-tone="amber"] { background: rgba(180, 83, 9, 0.045); }.im-detail-hero[data-tone="red"] { background: rgba(185, 28, 28, 0.035); }.im-detail-hero[data-tone="green"] { background: rgba(5, 133, 62, 0.04); }.im-detail-hero[data-tone="muted"] { background: var(--grey1); }.im-detail-hero-top { display: flex; align-items: center; gap: 10px; }.im-detail-hero-id { font-size: 12.5px; font-weight: 600; color: var(--grey11); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }.im-detail-hero-name { font-size: 22px; font-weight: 600; color: var(--grey12); letter-spacing: -0.4px; line-height: 28px; }.im-detail-hero-meta { font-size: 13px; font-weight: 500; color: var(--grey11); line-height: 18px; }
.im-meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 18px; }.im-meta-cell { display: flex; flex-direction: column; gap: 3px; min-width: 0; }.im-meta-label { font-size: 11px; font-weight: 500; color: var(--grey10); letter-spacing: 0.04em; text-transform: uppercase; }.im-meta-val { font-size: 13.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.im-meta-val--link { color: var(--g-dark); cursor: pointer; background: none; border: 0; padding: 0; font-family: inherit; text-align: left; font-variant-numeric: tabular-nums; }/* Holds-preserved entry card on the Custody screen (link to s-holds). */.im-holds-link-card { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 14px 18px; display: flex; align-items: center; justify-content: space-between; gap: 14px; cursor: pointer; border: 0; width: 100%; text-align: left; font-family: inherit; -webkit-appearance: none; appearance: none; transition: filter 0.15s ease; }.im-holds-link-card:hover { filter: brightness(0.98); }.im-holds-link-left { display: flex; align-items: center; gap: 12px; min-width: 0; }.im-holds-link-ic { width: 40px; height: 40px; border-radius: 50%; background: var(--grey2); color: var(--grey11); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.im-holds-link-ic svg { width: 18px; height: 18px; }.im-holds-link-title { font-size: 14.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }.im-holds-link-sub { font-size: 12.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.im-holds-link-arrow { color: var(--grey9); flex-shrink: 0; }.im-holds-link-arrow svg { width: 16px; height: 16px; }/* Unmarked-units card (Custody detail, Rechaj-native items only). */.im-unmarked-card { background: rgba(180, 83, 9, 0.045); border-radius: 16px; padding: 14px 18px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }.im-unmarked-left { display: flex; align-items: center; gap: 12px; min-width: 0; }.im-unmarked-ic { width: 36px; height: 36px; border-radius: 50%; background: rgba(180, 83, 9, 0.12); color: var(--amber9); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.im-unmarked-ic svg { width: 16px; height: 16px; }.im-unmarked-title { font-size: 14px; font-weight: 600; color: var(--amber9); letter-spacing: -0.05em; }.im-unmarked-sub { font-size: 12px; font-weight: 500; color: var(--grey11); margin-top: 2px; }Identity/status banner (subtle tinted strip, per the “no flooded cards” rule):
/* Identity check banner — flags battery custody/identity divergence per PRD §11 + §13. Subtle treatment, no flooded cards. */.im-identity-banner { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border-radius: 12px; background: rgba(5, 133, 62, 0.06); font-size: 12.5px; line-height: 18px; color: var(--g-dark); }.im-identity-banner[data-tone="amber"] { background: rgba(180, 83, 9, 0.06); color: var(--amber9); }.im-identity-banner[data-tone="red"] { background: rgba(185, 28, 28, 0.04); color: #B91C1C; }.im-identity-banner-ic { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; }.im-identity-banner-text strong { font-weight: 600; }Card header pattern: .rsm-section-head row (h3.rsm-section-title + right .rsm-section-meta or link) sits as the first child of a .im-detail-card / above a list. Side cards use .im-side-head (h3.im-side-title + .im-side-link “View all”).
Sticky detail CTA strip:
/* Sticky CTA strip — bottom of bundle detail. */.im-detail-cta { background: var(--white); border-top: 1px solid var(--grey3); padding: 14px 24px 8px; display: flex; gap: 10px; flex-shrink: 0; }.im-detail-cta .btn-primary,.im-detail-cta .btn-secondary { height: 48px; flex: 1; width: auto; }3.10 Overlays — bottom sheet only
Section titled “3.10 Overlays — bottom sheet only”There are NO modals, dialogs, or side drawers in this file. All confirmations are bottom sheets (three instances: #sheet-assign-bundle, #sheet-dispatch-parts, #sheet-assign-serials), consistent with the DS rule “confirmations on mobile + tablet use bottom sheets”.
/* Sheet primitives — bottom sheet shell. */.im-sheet-overlay { position: absolute; inset: 0; z-index: 60; pointer-events: none; }.im-sheet-overlay.is-open { pointer-events: auto; }.im-sheet-backdrop { position: absolute; inset: 0; background: rgba(14,15,18,0.45); opacity: 0; transition: opacity 0.22s ease; }.im-sheet-overlay.is-open .im-sheet-backdrop { opacity: 1; }.im-sheet { position: absolute; left: 0; right: 0; bottom: 0; background: var(--white); border-radius: 24px 24px 0 0; padding: 0 32px; transform: translateY(100%); transition: transform 0.28s cubic-bezier(0.32,0.72,0,1); box-shadow: var(--sh-sheet); max-height: 80%; display: flex; flex-direction: column; }.im-sheet-overlay.is-open .im-sheet { transform: translateY(0); }.im-sheet-handle-row { display: flex; align-items: center; justify-content: center; padding: 12px 0 0; flex-shrink: 0; }.im-sheet-handle { width: 44px; height: 4px; background: var(--grey4); border-radius: 100px; }.im-sheet-head { padding: 18px 0 12px; flex-shrink: 0; }.im-sheet-title { font-size: 18px; font-weight: 600; color: var(--grey12); letter-spacing: -0.2px; }.im-sheet-sub { font-size: 13px; font-weight: 500; color: var(--grey11); margin-top: 6px; line-height: 18px; }.im-sheet-body { display: flex; flex-direction: column; gap: 18px; padding: 0 0 8px; overflow-y: auto; }.im-sheet-cta { display: flex; flex-direction: column; gap: 10px; padding: 16px 0 28px; flex-shrink: 0; }Markup (verbatim, assign-bundle sheet):
<div class="im-sheet-overlay" id="sheet-assign-bundle"> <div class="im-sheet-backdrop" onclick="closeAssignSheet()"></div> <div class="im-sheet"> <div class="im-sheet-handle-row"><span class="im-sheet-handle"></span></div> <div class="im-sheet-head"> <p class="im-sheet-title" id="assign-sheet-title">Assign bundle</p> <p class="im-sheet-sub" id="assign-sheet-sub">Pushes the kit, parts and battery to the Team Lead. Identity-side preconditions are checked before the push lands.</p> </div> <div class="im-sheet-body"> <div class="im-detail-hero" data-tone="blue" style="padding: 14px 16px; gap: 6px;"> … summary hero … </div> <div class="im-identity-banner" id="assign-sheet-identity"> … </div> <div class="im-detail-card" style="padding: 14px 16px; box-shadow: none; background: var(--grey1);"> … custody trail meta-grid … </div> </div> <div class="im-sheet-cta"> <button class="btn-primary btn-primary--lg" type="button" onclick="confirmAssign()">Confirm assignment</button> <button class="btn-secondary btn-secondary--lg" type="button" onclick="closeAssignSheet()">Cancel</button> </div> </div></div>Usage: sheet overlays are siblings of the screens inside .tablet (absolute inset 0, z-60), toggled by adding/removing .is-open on the overlay. Footer buttons are STACKED vertically (primary on top, Cancel below), both --lg (56px). Backdrop click closes. Sheet bodies compose the same atoms as pages (a compacted .im-detail-hero, identity banner, grey flattened .im-detail-card).
3.11 Toast
Section titled “3.11 Toast”/* ── Toast ── */.alert-toast { position: absolute; top: 60px; left: 50%; transform: translate(-50%, -16px); z-index: 80; display: inline-flex; align-items: center; gap: 8px; border-radius: 999px; padding: 4px 14px 4px 4px; white-space: nowrap; box-shadow: 0 4px 20px rgba(0,0,0,0.12); opacity: 0; pointer-events: none; transition: opacity 0.25s ease, transform 0.25s ease; }.alert-toast.is-visible { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }.alert-toast-badge { display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.85); border-radius: 12px; padding: 3px 10px; font-size: 13px; font-weight: 500; line-height: 20px; }.alert-toast-text { font-size: 13px; font-weight: 500; line-height: 20px; }.alert-toast--success { background: #dcfbe2; color: #05853e; }.alert-toast--info { background: #e6f4fe; color: #0d74ce; }.alert-toast--warning { background: #fef3c7; color: #92400e; }.alert-toast--error { background: #fee2e2; color: #b91c1c; }Host element: <div class="alert-toast" id="toast"></div> (last child of .tablet).
JS helper (verbatim):
/* ──────── Toast ──────── */let toastT;function showToast(msg, badge, variant = 'success') { const el = document.getElementById('toast'); if (!el) return; el.className = 'alert-toast is-visible alert-toast--' + variant; el.innerHTML = (badge ? `<span class="alert-toast-badge">${badge}</span>` : '') + `<span class="alert-toast-text">${msg}</span>`; clearTimeout(toastT); toastT = setTimeout(hideToast, 3200);}function hideToast() { const el = document.getElementById('toast'); if (el) el.classList.remove('is-visible');}Usage: top-center pill, badge-first (showToast(msg, badge, variant) — note the badge renders BEFORE the message text; badge is the short label, msg the sentence). Variants: success / info / warning / error. Auto-hides after 3200ms. Every unbuilt destination fires showToast('X', 'Coming soon', 'info') — never a dead click. NOTE: this toast has no close button in this file (the canonical DS .alert-toast elsewhere includes one; here it’s badge + text only).
3.12 Inputs & forms
Section titled “3.12 Inputs & forms”.field-group { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }.field-label { font-size: 14px; font-weight: 500; color: var(--grey12); line-height: 20px; }.input { display: flex; align-items: center; gap: 8px; height: 52px; padding: 0 16px; background: var(--white); border: 1px solid var(--grey6); border-radius: 10px; box-shadow: 0 1px 2px rgba(16,24,40,0.05); transition: border-color 0.18s ease, box-shadow 0.18s ease; }.input:focus-within { border-color: var(--brand); box-shadow: 0 0 0 4px rgba(1,228,111,0.12); }.input input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; font-family: inherit; font-size: 16px; line-height: 24px; color: var(--grey12); }.input input::placeholder { color: var(--grey9); }.input-icon { width: 20px; height: 20px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: transparent; border: 0; padding: 0; outline: 0; -webkit-appearance: none; appearance: none; color: var(--grey10); cursor: pointer; }.input-icon svg { width: 100%; height: 100%; display: block; }.field-aside { text-align: right; margin-top: 6px; }.text-link { font-size: 14px; font-weight: 600; color: var(--g-dark); text-decoration: none; cursor: pointer; }.welcome-cta-wrap { margin-top: auto; padding-top: 24px; display: flex; flex-direction: column; gap: 16px; align-items: center; }<div class="field-group"> <label class="field-label" for="welcome-password">Password</label> <div class="input"> <input id="welcome-password" type="password" placeholder="Enter password" oninput="onWelcomeInput()"> <button class="input-icon" type="button" onclick="togglePw('welcome-password', this)" aria-label="Show password"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg> </button> </div> <div class="field-aside"><a class="text-link">Forgot password?</a></div></div>Usage: 52px field, radius 10, --grey6 border, focus ring = brand border + 4px green 12% glow. Trailing icon buttons (eye toggle) live inside .input. Only used on the Welcome/login screen — the app proper has no free-text inputs, no search field, and no <select> (choices are made through tappable cards/sheets).
Toggle switch (Preferences):
.rsm-toggle { position: relative; width: 40px; height: 24px; background: var(--grey5); border-radius: 100px; cursor: pointer; transition: background 0.2s ease; border: 0; -webkit-appearance: none; appearance: none; flex-shrink: 0; }.rsm-toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; background: var(--white); border-radius: 50%; transition: transform 0.2s ease; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }.rsm-toggle[aria-pressed="true"] { background: var(--g-primary); }.rsm-toggle[aria-pressed="true"]::after { transform: translateX(16px); }<button class="rsm-toggle" type="button" aria-pressed="true" aria-label="Push notifications" onclick="this.setAttribute('aria-pressed', this.getAttribute('aria-pressed') === 'true' ? 'false' : 'true')"></button>3.13 Dropdown menus
Section titled “3.13 Dropdown menus”None present. No dropdown/popover menu exists in this file; selection and actions route through bottom sheets, tabs, or inline card actions.
3.14 Empty states
Section titled “3.14 Empty states”Two forms:
(a) Inline list-pane empty state (JS-generated, inline styles — the live pattern):
<div style="padding: 32px 8px; text-align: center; color: var(--grey10); font-size: 13px;">Nothing in this view.</div><!-- Requests variant: --><div style="padding: 32px 8px; text-align: center; color: var(--grey10); font-size: 13px;">No pending requests.</div><!-- Card-scoped variants (movements/holds in custody detail): --><div style="padding: 12px 4px; color: var(--grey10); font-size: 12.5px;">No movements logged in the last 24h.</div><div style="padding: 12px 4px; color: var(--grey10); font-size: 12.5px;">No active holds.</div>(b) .im-placeholder — a full-screen placeholder pattern, defined in CSS but NOT used in this file’s markup (carried from the shared scaffold):
/* ── Placeholder ── */.im-placeholder { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 32px; text-align: center; gap: 12px; color: var(--grey11); }.im-placeholder-ic { width: 56px; height: 56px; border-radius: 50%; background: var(--grey2); color: var(--grey10); display: flex; align-items: center; justify-content: center; margin-bottom: 8px; }.im-placeholder-ic svg { width: 24px; height: 24px; }.im-placeholder h2 { font-size: 18px; font-weight: 600; color: var(--grey12); }.im-placeholder p { font-size: 13px; color: var(--grey11); max-width: 380px; line-height: 1.5; }3.15 Icon-container convention
Section titled “3.15 Icon-container convention”Every icon container is a circle (border-radius: 50%) — zero rounded-square icon containers in this file. The tonal treatment is always tinted background + colored stroke glyph (never a solid fill with white glyph, with two exceptions: the brand-green .im-hero-avatar/.rsm-profile-avatar with dark initials, and the solid-amber .im-unmarked-btn). Icons are feather-style: fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" (check glyphs sometimes stroke-width="2.5").
Circle size ladder (container → glyph):
| Container | Size | Glyph | Used by |
|---|---|---|---|
.im-hold-component-ic, .im-log-pending-ic, .im-hold-link-ic, .im-serial-ic |
24 | 12–13 | in-card micro rows |
.im-alert-ic, .im-activity-avatar, .im-log-avatar, .im-movement-ic, .rsm-profile-chev |
32 | 14–16 | side-card rows, logs |
.im-kpi-ic, .im-unmarked-ic, .rsm-profile-row-ic |
36 | 16–18 | KPI, banners, profile rows |
.im-custody-mark, .im-holds-link-ic |
40 | 18 | custody rows |
.im-list-mark, .im-bundle-mark |
42 | 20 | list cards |
.rsm-page-back, .im-page-icon-btn |
44 | 18–20 | header icon buttons |
.im-hero-avatar |
52 | (initials 18px) | dashboard hero |
.im-placeholder-ic |
56 | 24 | empty screen |
.rsm-profile-avatar |
64 | (initials 22px) | profile card |
Tint recipe per tone: bg = tone color at 8–12% alpha, glyph = full-strength tone color (see token table in §1). Neutral = --grey2 bg + --grey11/--grey12 glyph.
3.16 Profile list system (rsm-profile-* — shared persona shape)
Section titled “3.16 Profile list system (rsm-profile-* — shared persona shape)”/* ── BATCH 5 — Profile (Workspace / Account / Preferences / Support) Same rsm-profile-* shape used by every other persona. */.rsm-profile-user { background: var(--white); border-radius: 18px; box-shadow: var(--sh-card); padding: 20px; display: flex; align-items: center; gap: 14px; cursor: pointer; border: 0; text-align: left; font-family: inherit; width: 100%; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; }.rsm-profile-user:hover { background: var(--grey1); }.rsm-profile-avatar { width: 64px; height: 64px; border-radius: 50%; background: var(--brand); display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 600; color: var(--text-main); letter-spacing: -0.4px; flex-shrink: 0; }.rsm-profile-text { flex: 1; min-width: 0; }.rsm-profile-name { font-size: 17px; font-weight: 600; color: var(--grey12); letter-spacing: -0.2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.rsm-profile-role-pill { display: inline-block; font-size: 11px; font-weight: 600; padding: 3px 10px; border-radius: 100px; background: var(--g-2); color: var(--g-dark); letter-spacing: 0.02em; margin: 6px 0 2px; }.rsm-profile-meta { font-size: 12.5px; color: var(--grey11); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.rsm-profile-chev { width: 32px; height: 32px; border-radius: 50%; background: var(--grey2); display: flex; align-items: center; justify-content: center; color: var(--grey10); flex-shrink: 0; }.rsm-profile-chev svg { width: 16px; height: 16px; }
.rsm-profile-section-title { font-size: 12px; font-weight: 600; color: var(--grey10); letter-spacing: 0.06em; text-transform: uppercase; padding: 0 4px 6px; }.rsm-profile-section { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 4px 0; }.rsm-profile-row { display: flex; align-items: center; gap: 14px; width: 100%; padding: 14px 18px; background: transparent; border: 0; text-align: left; cursor: pointer; font-family: inherit; -webkit-appearance: none; appearance: none; position: relative; transition: background 0.15s ease; }.rsm-profile-row:hover { background: var(--grey1); }.rsm-profile-row + .rsm-profile-row::before { content: ''; position: absolute; left: 64px; right: 18px; top: 0; border-top: 1px solid var(--grey3); }.rsm-profile-row-ic { width: 36px; height: 36px; border-radius: 50%; background: var(--grey2); display: flex; align-items: center; justify-content: center; color: var(--grey12); flex-shrink: 0; }.rsm-profile-row-ic svg { width: 18px; height: 18px; }.rsm-profile-row-label { flex: 1; font-size: 14.5px; font-weight: 500; color: var(--grey12); letter-spacing: -0.1px; }.rsm-profile-row-sub { display: block; font-size: 12px; font-weight: 500; color: var(--grey10); margin-top: 2px; letter-spacing: 0; }.rsm-profile-row-tail { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--grey10); flex-shrink: 0; }.rsm-profile-row-tail svg { width: 16px; height: 16px; color: var(--grey10); }.rsm-profile-row--danger .rsm-profile-row-ic { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.rsm-profile-row--danger .rsm-profile-row-label { color: #B91C1C; }.rsm-profile-row--danger .rsm-profile-row-tail svg { color: #B91C1C; opacity: 0.7; }<div> <p class="rsm-profile-section-title">Account</p> <div class="rsm-profile-section"> <button class="rsm-profile-row" type="button" onclick="showToast('Two-factor', 'Coming soon', 'info')"> <span class="rsm-profile-row-ic"><svg …>shield</svg></span> <span class="rsm-profile-row-label">Two-factor authentication</span> <span class="rsm-profile-row-tail">On <svg …><polyline points="9 18 15 12 9 6"/></svg></span> </button> </div></div>Usage: sectioned iOS-settings shape — uppercase 12px section eyebrow OUTSIDE the card, rows divided by an inset ::before hairline starting at left: 64px. Danger row (Sign out) = red-tinted icon circle + red label. Rows can host a .rsm-toggle instead of the chevron tail. Note: in THIS file .rsm-profile-row-ic is a circle (50%); the Kit Assembler 10px-rounded-square exception documented in CLAUDE.md does not appear here.
4. Page anatomy
Section titled “4. Page anatomy”11 screens, in the SCREENS array order: s-splash, s-onboarding, s-welcome, s-dashboard, s-requests, s-bundles, s-bundle-detail, s-custody, s-custody-detail, s-holds, s-profile.
4.0 Auth screens (splash → onboarding → welcome)
Section titled “4.0 Auth screens (splash → onboarding → welcome)”Composition and verbatim CSS:
/* ── Splash / Onboarding / Welcome (auth) ── */#s-splash { background: var(--brand-dark); align-items: center; justify-content: center; cursor: pointer; }.splash-mark { display: flex; align-items: center; gap: 14px; }.splash-mark svg { width: 56px; height: 56px; }.splash-mark-text { font-size: 34px; font-weight: 700; color: var(--white); letter-spacing: -1.2px; }
#s-onboarding { background: var(--brand-dark); padding: 0; }.ob-content { flex: 1; display: flex; flex-direction: column; padding: 64px 56px 48px; gap: 56px; }.ob-brand { display: flex; align-items: center; gap: 10px; }.ob-brand svg { width: 32px; height: 32px; }.ob-brand-name { font-size: 22px; font-weight: 700; color: var(--white); letter-spacing: -0.5px; }.ob-hero-card { width: 100%; max-width: 440px; aspect-ratio: 1; border-radius: 32px; background: linear-gradient(160deg, #06311B 0%, #013F1F 60%, #021F11 100%); box-shadow: 0 30px 80px rgba(0,0,0,0.4); position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; align-self: center; }.ob-hero-mark { width: 184px; height: 184px; }.ob-hero-caption { position: absolute; bottom: 24px; left: 24px; right: 24px; font-size: 12.5px; font-weight: 500; color: rgba(255,255,255,0.55); letter-spacing: 0.04em; text-transform: uppercase; text-align: center; }.ob-headline { font-size: 36px; font-weight: 700; color: var(--white); letter-spacing: -0.8px; line-height: 44px; }.ob-headline .accent { color: var(--g-primary); }.ob-tag { font-size: 15px; font-weight: 500; color: rgba(255,255,255,0.7); line-height: 22px; max-width: 540px; }.ob-cta { padding: 0 56px 48px; }
/* Welcome / Login — vertical stack: brand-dark top, white sheet bottom. Matches the Procurement Manager pattern. */#s-welcome { background: var(--brand-dark); padding: 0; flex-direction: column; }.welcome-left { flex-shrink: 0; min-height: 560px; padding: 80px 56px 64px; display: flex; flex-direction: column; align-items: center; gap: 18px; color: var(--white); position: relative; overflow: hidden; text-align: center; }.welcome-left::before { content: ''; position: absolute; top: -160px; left: 50%; transform: translateX(-50%); width: 520px; height: 520px; background: radial-gradient(circle at center, rgba(1,228,111,0.18), rgba(1,228,111,0)); pointer-events: none; }.welcome-left-mark { display: flex; align-items: center; gap: 12px; position: relative; z-index: 1; }.welcome-left-mark svg { width: 36px; height: 36px; }.welcome-left-mark span { font-size: 22px; font-weight: 700; color: var(--white); letter-spacing: -0.5px; }.welcome-left-headline { font-size: 32px; font-weight: 700; color: var(--white); line-height: 40px; letter-spacing: -0.6px; max-width: 560px; margin-top: 8px; position: relative; z-index: 1; }.welcome-left-headline .accent { color: var(--brand); }.welcome-left-tag { font-size: 14px; color: rgba(255,255,255,0.7); line-height: 22px; max-width: 520px; position: relative; z-index: 1; }
.welcome-right { width: 100%; flex: 1; min-height: 0; background: var(--white); border-radius: 28px 28px 0 0; padding: 40px 56px 56px; display: flex; flex-direction: column; align-items: center; box-shadow: 0 -8px 28px rgba(0,0,0,0.16); }.welcome-right-head { width: 100%; max-width: 440px; margin-bottom: 28px; text-align: center; }.welcome-right-eyebrow { font-size: 12.5px; font-weight: 600; color: var(--g-dark); letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: 10px; }.welcome-title { font-size: 28px; font-weight: 700; color: var(--grey12); letter-spacing: -0.5px; line-height: 34px; margin-bottom: 6px; }.welcome-sub { font-size: 14px; font-weight: 400; color: var(--grey11); line-height: 22px; }.welcome-right .field-group { width: 100%; max-width: 440px; }.welcome-right .welcome-cta-wrap { width: 100%; max-width: 440px; margin-top: auto; }- Splash (
#s-splash): brand-dark, centered two-tone chevron mark + “Rechaj” 34px/700. Click anywhere or 2.5s timeout → onboarding. - Onboarding: dark
.sb--dark, brand row, square gradient hero card with 184px mark + uppercase caption (“INVENTORY MANAGEMENT · CUSTODY”), 36px/700 headline with green.accentspan, tag paragraph, full-widthGet startedbtn-primary--lg. - Welcome/login: vertical stack — dark hero (radial green glow, brand mark, 32px/700 headline w/ accent, tag) over a white 28px-top-radius sheet holding eyebrow (“Inventory Manager”, uppercase green), 28px/700 “Sign in to continue”, sub “Inventory Management System · Tablet”, Email + Password fields (email pre-filled
olufunke.adesina@rechaj.ng), forgot-password link, disabled-until-filled Sign in button pinned viamargin-top: auto. Login =attemptLogin()→ dashboard, no validation beyond non-empty.
4.1 Dashboard (s-dashboard)
Section titled “4.1 Dashboard (s-dashboard)”Top-to-bottom inside .im-scroll:
.im-hero(brand-dark): dark status bar → avatar OA + name + role pill + meta date. Ghost brand chevrons right at 12% opacity..im-kpi-strip— 6 KPI tiles (Pending requests / Active pushes / Push overdue > 2h / Returns to check in / Holds preserved / ATP signals) overlapping the hero by −64px. 5 are buttons routing to Requests/Bundles/Holds; ATP signals is a static div..rsm-body.rsm-body--split(1.2fr / 1fr,style="margin-top: 20px;"):- LEFT: section head “Today’s bundle pushes” + “View all” link → 3 static
.im-bundle-cards (assigned/overdue/scanned-in); then.im-side-card“Mid-repair requests” with amber.im-alert-rows. - RIGHT:
.im-side-card“Pending bundles” (blue/amber alert rows) →.im-side-card“Holds preserved” (grey alert rows) →.im-side-card“Recent activity” (.im-activity-rowstream, initials avatars + time).
- LEFT: section head “Today’s bundle pushes” + “View all” link → 3 static
- Pinned
.rsm-nav+.home-ind.
Interactions: KPI tile → goTo(...); bundle card → openBundle(id) (detail screen); every alert row → goTo(...) to its list screen. KPI numbers/subs re-derived from live data by renderDashKpis() after every mutation.
4.2 List pages (Requests / Bundles / Custody / Holds)
Section titled “4.2 List pages (Requests / Bundles / Custody / Holds)”Shared composition order:
- Light
.sb .rsm-page-head→.im-page-head-rowwith left title (Requests, Bundles, Custody) — Holds uses the centered.rsm-page-head-row+ back button because it is a sub-page of Custody..rsm-body:- (Custody only)
.im-holds-link-cardabove the tabs, linking tos-holds. .im-tabssegmented control with live counts.- One pane
<div data-*-pane … style="display:flex; flex-direction:column; gap:10px">per tab; inactive panes carryhidden. Panes are hydrated byrender*Pane()at init and after every mutation.
- (Custody only)
- Pinned
.rsm-nav+.home-ind.
Interaction rules:
- First tab is selected by default (
aria-pressed="true"in markup). - Requests row click → bundle kind opens the assign-bundle sheet (blocked with a warning toast if
identityOkis false); mid-repair kind opens the dispatch sheet. In-cardDecline/Assign bundle/Dispatch partsbuttons are wrapped inonclick="event.stopPropagation()". - Bundles row click →
openBundle(id)→ full-screen detail screen (not modal, not drawer). - Custody row click →
openCustodyItem(slug)→ full-screen detail. - Holds rows are NOT clickable; only the inner blue
.im-hold-link(jump to assign sheet) is. - Confirmations are always bottom sheets with stacked Confirm (primary) / Cancel (secondary) buttons. Destructive/irreversible one-tap actions (decline, cancel push, escalate) fire immediately + toast, no confirm.
- Search does not exist on any list page.
4.3 Detail pages (Bundle detail / Custody detail)
Section titled “4.3 Detail pages (Bundle detail / Custody detail)”Composition order:
- Light
.sb - Centered page head:
.rsm-page-back(round, left) +.rsm-page-title= record id or item name. Back goes to the parent LIST (s-bundles/s-custody), not history. .rsm-body.rsm-body--split:- LEFT column:
.im-detail-hero(tinted by state; pill + id on top row; 22px name; meta line; then a.im-meta-gridof 4 labeled values on bundle detail, or the 7-cell.im-breakdown--fullon custody detail) →.im-identity-banner(bundle) or.im-unmarked-card(custody, conditional) →.im-detail-card(Bundle composition.im-parts-rowlist / Identifiers.im-meta-grid). - RIGHT column:
.im-detail-cardComposite event log (.im-log-rows + optional dashed.im-log-pending-rowwith prototypeSimulatebutton) →.im-detail-cardCustody trail (.im-meta-grid); custody detail instead: Recent movements (.im-movement-row) → Active holds (.im-alert-rows →goTo('s-holds')).
- LEFT column:
- (Bundle detail only) pinned
.im-detail-ctastrip ABOVE the nav; content varies by stage:- active → single primary
Cancel push - overdue → secondary
Cancel push+ primaryEscalate - returns → single primary
Restore to ATP - completed → strip hidden entirely.
- active → single primary
- Pinned
.rsm-nav(parent tab staysis-active) +.home-ind.
Detail screens are singletons re-rendered by renderBundleDetail(b) / renderCustodyDetail(i) from the data model; deep links hydrate the first record by default.
4.4 Profile (s-profile)
Section titled “4.4 Profile (s-profile)”- Light
.sb→ left title “Profile” + settings.im-page-icon-btn. .rsm-body:.rsm-profile-usercard (64px avatar, name, role pill, email, chevron circle) → four titled sections (Workspace / Account / Preferences / Support) of.rsm-profile-sectioncards with.rsm-profile-rows. Preferences rows host.rsm-toggles. Support ends with the danger Sign out row →goTo('s-welcome').- Pinned nav + home indicator. All unbuilt rows fire
showToast('X', 'Coming soon', 'info').
4.5 Global interaction rules
Section titled “4.5 Global interaction rules”- Navigation is instant class-swap (
.screen.active), no transitions; scroll resets to top. - Everything clickable is a real
<button>(or<a class="text-link">); every unbuilt destination toasts “Coming soon” — never a dead click. - Mutations (assign, dispatch, decline, cancel, escalate, return, serialise, simulate scan-in) update the in-memory model, re-render EVERY affected pane + counts + dashboard KPIs, toast the outcome, and navigate to the most useful next screen (e.g.
confirmAssign()→ straight into the new push’s detail). - Deep-linking:
?screen=s-…on the URL skips splash and lands on the screen, hydrating a default record for detail screens.
5. JS contracts
Section titled “5. JS contracts”All plain global functions, wired via inline onclick/oninput. Data lives in three global object maps keyed by id: REQUESTS, BUNDLES, CUSTODY_ITEMS, plus HOLDS. Cursor state: currentBundleId, currentRequestId, currentDispatchId, currentCustodySlug, serialPreview, toastT.
Navigation:
const SCREENS = ['s-splash','s-onboarding','s-welcome','s-dashboard','s-requests','s-bundles','s-bundle-detail','s-custody','s-custody-detail','s-holds','s-profile']goTo(id)— guards against unknown ids, swaps.active, resets.im-scrollscrollTop.
Auth: onWelcomeInput(), togglePw(inputId, btn), attemptLogin().
Tab switching (one per screen, identical shape — flips aria-pressed on [data-*-tab] and hidden+style.display on [data-*-pane]):
setRequestTab(btn)·setBundleTab(btn)·setCustodyTab(btn)·setHoldTab(btn)
List rendering (pane hydrators + row template + count updaters):
requestsByKind(kind)/renderRequestPane(kind)/requestRowHtml(r)/renderRequestCounts()— kinds:'bundle' | 'mid-repair'bundlesByStage(stage)/renderBundlePane(stage)/bundleRowHtml(b)/renderBundleCounts()— stages:'active' | 'completed' | 'returns'custodyByCategory(category)/renderCustodyPane(category)/custodyRowHtml(i)/renderCustodyCounts()— categories:'all' | 'components' | 'parts' | 'batteries'holdsByStage(stage)/renderHoldPane(stage)/holdRowHtml(h)/renderHoldCounts()— stages:'waiting' | 'ready'renderDashKpis()— derives all 6 dashboard KPI values from live data.
Detail:
openBundle(id)— looks up, sets cursor,renderBundleDetail(b),goTo('s-bundle-detail'). Unknown id → error toast.renderBundleDetail(b)— sets hero tone, pill, meta grid, identity banner, parts list, custody trail, event log (+ pending row w/simulateTlScanIn()), and rebinds CTA strip per stage.openCustodyItem(slug)/renderCustodyDetail(i)— same shape for custody.
Sheets (open/close/confirm triads; open = classList.add('is-open') on the overlay):
openAssignSheet(reqId)/closeAssignSheet()/confirmAssign()— confirm mintsPUSH-{yymmdd}-{seq}, moves the request intoBUNDLES, deletes it fromREQUESTS, re-renders, toasts, thenopenBundle(pushId).dispatchMidRepair(reqId)(back-compat wrapper) →openDispatchSheet(reqId)/closeDispatchSheet()/confirmDispatch()openAssignSerialsSheet()/closeAssignSerialsSheet()/confirmAssignSerials()— operates oncurrentCustodySlug; only forserialType === 'Rechaj-native'withunmarked > 0.
Row/CTA actions:
declineRequest(reqId)·cancelPush()·escalatePush()·confirmReturn(id)·simulateTlScanIn()(prototype-only: fakes the TL app’s scan-in webhook; the IM never marks scan-in themselves per PRD §5).
Toast: showToast(msg, badge, variant = 'success') / hideToast() — variants success | info | warning | error; 3200ms auto-hide.
Init IIFE: hydrates every pane + counts + KPIs first, then handles ?screen= deep link (with default-record hydration for the two detail screens), else 2500ms splash auto-advance; ends with onWelcomeInput().
Fit IIFE (second script tag): scales .tablet to the window (see §2).
6. Naming
Section titled “6. Naming”Class prefixes (prefix-ownership is a stated HARD RULE in the file header):
im-*— Inventory-Manager-specific components (hero, kpi, pill, tabs, list/bundle/custody/hold cards, detail, sheet, log, movement, serial, placeholder, scroll wrapper).rsm-*— shared persona scaffolding inherited from the RSM/Procurement Manager tablet shell:rsm-page-head,rsm-page-head-row,rsm-page-title,rsm-page-back,rsm-body,rsm-body--split,rsm-section-*,rsm-nav,rsm-nav-tab,rsm-nav-badge,rsm-profile-*,rsm-toggle.- Unprefixed shared atoms:
.tablet,.screen,.tab-screen,.sb*,.home-ind,.btn-primary/.btn-secondary,.field-group/.field-label/.input/.input-icon/.field-aside/.text-link,.splash-*,.ob-*,.welcome-*,.alert-toast*. - State conventions:
.is-active(nav),.is-open(sheet),.is-visible(toast),.is-disabled(button),aria-pressed(tabs + toggles),[hidden](panes),data-tone(color tinting),data-state(pill/component semantics). - Screen ids:
s-<name>. Element ids: kebab, feature-scoped (bun-detail-*,cust-detail-*,assign-sheet-*,dispatch-sheet-*,serial-sheet-*,dash-*,im-req-pane-*,im-bun-count-*, …). Sheets:sheet-<action>.
Personas / names: Olufunke Adesina (Inventory Manager, avatar “OA”, olufunke.adesina@rechaj.ng / @rechaj.com, Apapa receiving bay, Lagos depot); Team Leads Adekunle, Okafor; Auto Mechs Yusuf, Ogundele; linked Procurement Manager Tobi Akinwale; riders Olamide Ogundeji, Bukola Adekanmi, Tunde Adeyemi, Yusuf Adebayo, Babatunde Salami, Femi Akinwale, Kazeem Lawal, Funke Adewale, Ngozi Nwosu, Aisha Bello, Emeka Nwosu, Ifeoma Nnamdi. Workstations A/B/C + Auto-mech bay. Suppliers: Apapa Components, Lekki Hardware Co, Trans-Niger Parts.
ID formats (comment in file: “Cross-system ID conventions (sync with Kit Production + Retrofit Team Lead apps)”):
- Requests:
REQ-190526-003—REQ-DDMMYY-NNN - Pushes:
PUSH-190526-001—PUSH-YYMMDD-NNNper the comment; note a real internal inconsistency: existing data ids read as DDMMYY (190526 = 19 May 2026) whileconfirmAssign()generates true YYMMDD (String(year).slice(2)+MM+DD→ 260519 for the same date) - Returns:
RTN-190526-001; Receipts:RCV-180526-001; Quarantine:QTN-180526-003 - Work orders:
WO-190526-014; Holds:HOLD-7392; Events:EVT-A8421/EVT-SCAN - Kits:
kit-K-88301-WS-0014(matches kit-production handoff); Batteries:RB-009114(RB-NNNNNN); bundle crate barcodeRB-{kit}-WSNN-{check} - Part numbers:
WH-A1,MTR-3K,MCU-6K,VCU-N1,VDU-7N,BC-48-12,DIFF-7B,CV-J-09,BMK-1,BAT-44; generated serialsWH-A1-30041 - Plates:
FST559AB; Battery identity states (uppercase snake):INTAKE_PENDING,REGISTERED_UNASSIGNED,ASSIGNED_PENDING_INSTALL,IN_FIELD - Time strings:
9:41,Today · 8:42 AM,Yesterday · 4:12 PM,Just now,28 min,3h 12m overdue,Wed, 19 May 2026.
7. Weight & spacing discipline
Section titled “7. Weight & spacing discipline”Where 700 appears (complete list)
Section titled “Where 700 appears (complete list)”Compliant with “700 only for KPI digits + display titles + iOS clock”:
.sb-time(15px — iOS clock) ✓.splash-mark-text34px,.ob-brand-name22px,.ob-headline36px,.welcome-left-mark span22px,.welcome-left-headline32px,.welcome-title28px — all splash/onboarding/welcome display titles ✓.im-kpi-num28px,.im-custody-atp-num22px,.im-breakdown-val17px — KPI/stat digits ✓.rsm-nav-badge10.5px/700 — numeric count badge (digits; borderline but numeric-only)
Everything else is 500 or 600: page titles 600, card titles 600, ids/names 600, buttons 600, pills/chips 600, body/meta 500. font-weight: 400 is declared on .im-hero-meta and .welcome-sub even though the 400 weight is not loaded from Fontshare.
Spacing rhythm
Section titled “Spacing rhythm”- Page gutter: 24px (
.rsm-bodypadding, KPI strip margin); hero/status gutter 32px; auth gutter 56px. - Section gap: 18px (
.rsm-bodygap, split-grid gap, sheet-body gap); card-internal gap: 12–14px; list stack gap: 10px; micro gaps: 2/3/4/6/8. - Card padding: 14–20px (14 16 / 16 18 / 18 / 20 22).
- Radii ladder: 8 (tab pill) · 10 (buttons, inputs, micro rows) · 12 (tab track, alert rows, banners) · 14 (list cards) · 16 (detail/side cards, sheets’ inner cards) · 18 (KPI, profile user) · 24 (sheet top) · 28 (tablet, welcome sheet) · 32 (ob hero) · 100px/999px (pills, toggles, toast).
- The file works on a 2px sub-grid rather than a strict 4px grid: 14px and 18px gaps/paddings and half-pixel type sizes (10.5/11.5/12.5/13.5/14.5) are pervasive and canonical here. The forbidden 7/11/15px values do not appear as spacing (11px appears only as a font-size).
- Letter-spacing: negative on nearly all text (−0.01em body default; −0.05em on compact labels; px values −0.1 to −1.2 on headings); positive +0.02–0.06em ONLY on uppercase micro-labels and pills.
font-variant-numeric: tabular-numson every id/number (.im-kpi-num,.im-list-id,.im-bundle-id,.im-custody-atp-num,.im-breakdown-val,.im-meta-val--link,.im-parts-qty,.im-movement-qty,.im-serial-id,.im-log-event-id,.im-tab-count).
Violations noticed within the file itself
Section titled “Violations noticed within the file itself”- Em dashes in copy (violates the “no em dashes in UI copy” rule): line 1273
"Rechaj-native — assign serials on intake"; dashboard activity"still pending — escalation due"; JS strings"Battery ${state} — Identity check pending","Push overdue. Custody / Identity divergence risk — reconcile.","flagged 1 unit — firmware mismatch","TL submitted bundle request <strong>…</strong> — open to assign",HOLDSsub"INTAKE_PENDING — Identity check pending",waitingOn: 'Battery BAT-44 — 1.8d cover'. Elsewhere the file correctly uses·middots. - PUSH-id date-order inconsistency — seeded ids are DDMMYY,
confirmAssign()generates YYMMDD (see §6). - Inline styles as layout glue — list panes, split-column wrappers, sheet-hero compaction (
style="padding: 14px 16px; gap: 6px;"), flattened cards (style="… box-shadow: none; background: var(--grey1);"), the overdue red<strong style="color:#B91C1C;">, and JS-disabled button styling are inline rather than classed. Canonical when reproducing this file’s look, but they are unnamed patterns. .rsm-nav-badgeand.im-placeholderare dead CSS in this file (defined, never instantiated)..im-parts-qty,.im-list-confirm-btn,.im-pill[data-state="held"],.im-list-card[data-tone="muted"],.im-detail-hero[data-tone="muted"],.im-meta-val--link,.home-ind--light,.im-hold-row[data-tone="red"]are defined but unused or only reachable via JS branches (data-tone="white"is emitted bybundleRowHtmlfor completed rows — an attribute with no CSS match, deliberately falling through to the white default).- Uppercase text-transform appears on micro-labels (
.im-meta-label,.im-breakdown-label,.im-custody-atp-label,.rsm-profile-section-title,.ob-hero-caption,.welcome-right-eyebrow,.im-log-sim-btn) — allowed (they are labels/eyebrows, not.ds-badgebadges), but note the global DS badge rule says badges themselves are never uppercase, and indeed no pill here is uppercase.