Skip to content

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-* prefix
for IM-specific components per the design-system HARD RULE on prefix
ownership. PRD §1 owner: single owner of custody from receiving through
workstation 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).


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).


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.

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 80
  • The status bar, page head/hero, and body all live inside .im-scroll and 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-scroll scrollTop to 0 on every navigation.
  • Auth screens (splash/onboarding/welcome) have no .im-scroll and no nav.
  • A standalone-page fit script scales .tablet down (transform: scale) when the window is smaller than 834×1194 so the host page never scrolls.
* { 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.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.

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.

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).

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.

/* ── 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;".

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.

/* ── 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().

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; }

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).

/* ── 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).

.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>

None present. No dropdown/popover menu exists in this file; selection and actions route through bottom sheets, tabs, or inline card actions.

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; }

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.


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 .accent span, tag paragraph, full-width Get started btn-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 via margin-top: auto. Login = attemptLogin() → dashboard, no validation beyond non-empty.

Top-to-bottom inside .im-scroll:

  1. .im-hero (brand-dark): dark status bar → avatar OA + name + role pill + meta date. Ghost brand chevrons right at 12% opacity.
  2. .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.
  3. .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-row stream, initials avatars + time).
  4. 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:

  1. Light .sb
  2. .rsm-page-head → .im-page-head-row with left title (Requests, Bundles, Custody) — Holds uses the centered .rsm-page-head-row + back button because it is a sub-page of Custody.
  3. .rsm-body:
    • (Custody only) .im-holds-link-card above the tabs, linking to s-holds.
    • .im-tabs segmented control with live counts.
    • One pane <div data-*-pane … style="display:flex; flex-direction:column; gap:10px"> per tab; inactive panes carry hidden. Panes are hydrated by render*Pane() at init and after every mutation.
  4. 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 identityOk is false); mid-repair kind opens the dispatch sheet. In-card Decline / Assign bundle / Dispatch parts buttons are wrapped in onclick="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:

  1. Light .sb
  2. 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.
  3. .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-grid of 4 labeled values on bundle detail, or the 7-cell .im-breakdown--full on custody detail) → .im-identity-banner (bundle) or .im-unmarked-card (custody, conditional) → .im-detail-card (Bundle composition .im-parts-row list / Identifiers .im-meta-grid).
    • RIGHT column: .im-detail-card Composite event log (.im-log-rows + optional dashed .im-log-pending-row with prototype Simulate button) → .im-detail-card Custody trail (.im-meta-grid); custody detail instead: Recent movements (.im-movement-row) → Active holds (.im-alert-rows → goTo('s-holds')).
  4. (Bundle detail only) pinned .im-detail-cta strip ABOVE the nav; content varies by stage:
    • active → single primary Cancel push
    • overdue → secondary Cancel push + primary Escalate
    • returns → single primary Restore to ATP
    • completed → strip hidden entirely.
  5. Pinned .rsm-nav (parent tab stays is-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.

  1. Light .sb → left title “Profile” + settings .im-page-icon-btn.
  2. .rsm-body: .rsm-profile-user card (64px avatar, name, role pill, email, chevron circle) → four titled sections (Workspace / Account / Preferences / Support) of .rsm-profile-section cards with .rsm-profile-rows. Preferences rows host .rsm-toggles. Support ends with the danger Sign out row → goTo('s-welcome').
  3. Pinned nav + home indicator. All unbuilt rows fire showToast('X', 'Coming soon', 'info').
  • 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.

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-scroll scrollTop.

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 mints PUSH-{yymmdd}-{seq}, moves the request into BUNDLES, deletes it from REQUESTS, re-renders, toasts, then openBundle(pushId).
  • dispatchMidRepair(reqId) (back-compat wrapper) → openDispatchSheet(reqId) / closeDispatchSheet() / confirmDispatch()
  • openAssignSerialsSheet() / closeAssignSerialsSheet() / confirmAssignSerials() — operates on currentCustodySlug; only for serialType === 'Rechaj-native' with unmarked > 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).


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-NNN per the comment; note a real internal inconsistency: existing data ids read as DDMMYY (190526 = 19 May 2026) while confirmAssign() 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 barcode RB-{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 serials WH-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.

Compliant with “700 only for KPI digits + display titles + iOS clock”:

  • .sb-time (15px — iOS clock) ✓
  • .splash-mark-text 34px, .ob-brand-name 22px, .ob-headline 36px, .welcome-left-mark span 22px, .welcome-left-headline 32px, .welcome-title 28px — all splash/onboarding/welcome display titles ✓
  • .im-kpi-num 28px, .im-custody-atp-num 22px, .im-breakdown-val 17px — KPI/stat digits ✓
  • .rsm-nav-badge 10.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.

  • Page gutter: 24px (.rsm-body padding, KPI strip margin); hero/status gutter 32px; auth gutter 56px.
  • Section gap: 18px (.rsm-body gap, 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-nums on 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).
  1. 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", HOLDS sub "INTAKE_PENDING — Identity check pending", waitingOn: 'Battery BAT-44 — 1.8d cover'. Elsewhere the file correctly uses · middots.
  2. PUSH-id date-order inconsistency — seeded ids are DDMMYY, confirmAssign() generates YYMMDD (see §6).
  3. 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.
  4. .rsm-nav-badge and .im-placeholder are dead CSS in this file (defined, never instantiated).
  5. .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 by bundleRowHtml for completed rows — an attribute with no CSS match, deliberately falling through to the white default).
  6. 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-badge badges), but note the global DS badge rule says badges themselves are never uppercase, and indeed no pill here is uppercase.