canon extract proc mgr
CANON EXTRACT — Rechaj Inventory App / Procurement Manager (tablet 834×1194)
Section titled “CANON EXTRACT — Rechaj Inventory App / Procurement Manager (tablet 834×1194)”Source of truth: /Users/Ridwan/Pictures/Claude/Rechaj 2.0/rechaj-design-system/products/rechaj-inventory-app/flows/procurement-manager/index.html
(4307 lines: CSS 11–830, markup 832–2332, JS 2333–4305. 100% of the CSS was read; all values below are verbatim.)
File self-description (header comment, verbatim):
Inventory Management — Procurement Manager (iPad Pro 11" portrait, 834×1194)
Mirrors the Kit Production Manager tablet shell exactly (same .tablet,.sb, .rsm-page-head, .rsm-body, .rsm-nav scaffolding) but uses thepm-* prefix for Procurement-specific components. Each persona fileis a self-contained prototype — no cross-file CSS dependencies.Font: 'General Sans', system-ui, -apple-system, sans-serif loaded from Fontshare at weights 400,500,600,700.
1. Tokens
Section titled “1. Tokens”Verbatim :root block:
:root { --brand: #01E46F; --brand-dark: #013F1F; --g-primary: #01E46F; --g-dark: #05853E; --g-2: #DCFBE2; --g-3: #B5F1C2; --grey1: #FAFAFA; --grey2: #F9F9F9; --grey3: #E4E4E4; --grey4: #E8E8E8; --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);}Hard-coded (non-token) colors that recur throughout the file and are part of the canon:
- Blue tone:
#1E40AF(glyph/text) onrgba(30, 64, 175, 0.10)icon tint /rgba(30,64,175,0.04)row tint /rgba(30,64,175,0.08)thumb tint /#DBEAFEpill bg. - Red tone:
#B91C1C(glyph/text) onrgba(185, 28, 28, 0.08)icon tint /rgba(185,28,28,0.035)row tint /#FEE2E2pill bg. (--red9: #E32627exists but is used only for the nav badge and input error ring; status reds are #B91C1C.) - Amber tone:
var(--amber9)= #B45309 onrgba(180, 83, 9, 0.10)icon tint /rgba(180,83,9,0.045)row tint /#FEF3C7pill bg;#F59E0Bfor amber progress fills;#92400Etoast text. - Green tone:
var(--g-dark)= #05853E onrgba(5, 133, 62, 0.10)icon tint /rgba(5,133,62,0.04–0.08)tints;var(--g-3)#B5F1C2 pill bg. - Page/backdrop: prototype body backdrop
#0E0F12; tab-screen page background#F5F5F7; brand button hover#00C45B. - Toast palette: success
#dcfbe2/#05853e, info#e6f4fe/#0d74ce, warning#fef3c7/#92400e, error#fee2e2/#b91c1c.
The tonal tint recipe (stated in file comments): icon circles use rgba tints at ~10% opacity (not Tailwind-100 solids); row/card background tints are even lighter, ~3.5–8% opacity (“Tinted bg communicates the warning state at a glance, but the tint is intentionally light (rgba ~6-8% opacity) so the dashboard reads calm overall”).
2. Shell
Section titled “2. Shell”Dimensions & structure
Section titled “Dimensions & structure”- Device: iPad Pro 11” portrait, 834 × 1194, border-radius 28px, on a
#0E0F12dark backdrop, centered with flex. - Screens are absolutely stacked inside
.tablet; exactly one carries.active. - App screens (post-login) add
.tab-screen→ page bg#F5F5F7. - Each screen has an inner
.pm-scrollwrapper that scrolls; the bottom nav (.rsm-nav), sticky CTA strip (.pm-detail-cta), FAB (.pm-fab), and home indicator live OUTSIDE.pm-scrolland stay pinned. - A JS
fit()scaler (second<script>) scales.tabletdown withtransform: scale()when the viewport is smaller than 834×1194.
ASCII sketch of a standard app screen:
.tablet (834×1194, radius 28, overflow hidden, flex column)└── .screen.tab-screen.active (absolute inset:0, flex column, bg #F5F5F7) ├── .pm-scroll (flex:1, overflow-y:auto — EVERYTHING that scrolls) │ ├── .sb (status bar, 52px: time 9:41 + signal/wifi/battery SVGs) │ │ — dashboard only: .sb sits INSIDE header.rsm-hero (dark variant .sb--dark) │ ├── header.rsm-page-head │ │ └── .rsm-page-head-row (centered title + .rsm-page-back) ← detail screens │ │ or .pm-page-head-row (left title + right icon-btn slot) ← root tab screens │ └── .rsm-body (padding 0 24px 24px, flex column gap 18px) │ or .rsm-body.rsm-body--split (grid 1.2fr 1fr, gap 18px) ├── .pm-fab (optional, pinned bottom-right, above nav) ├── .pm-detail-cta (optional sticky action strip, above nav) ├── nav.rsm-nav (5-col bottom nav, pinned) └── .home-ind (iOS home indicator)Dashboard variant: .pm-scroll starts with header.rsm-hero (brand-dark hero containing the dark status bar + avatar/name/role), then the KPI strip overlaps the hero bottom with a negative top margin (margin: -64px 24px 0), then .rsm-body.rsm-body--split.
Scroll model: hero + page head + body scroll together; bottom nav, FAB, sticky CTA, and home indicator never move. Sheets overlay the whole tablet (position: absolute; inset: 0; z-index: 50). Toast overlays at z-index 80.
Verbatim shell CSS
Section titled “Verbatim shell CSS”* { box-sizing: border-box; margin: 0; padding: 0; }html, body { height: 100%; }body { font-family: 'General Sans', system-ui, -apple-system, sans-serif; background: #0E0F12; color: var(--grey12); letter-spacing: -0.01em; -webkit-font-smoothing: antialiased; display: flex; align-items: center; justify-content: center; min-height: 100vh;}
/* Tablet shell — iPad Pro 11" portrait. */.tablet { position: relative; width: 834px; height: 1194px; background: var(--white); overflow: hidden; border-radius: 28px; display: flex; flex-direction: column;}
.screen { display: none; flex-direction: column; position: absolute; inset: 0; background: var(--white); }.screen.active { display: flex; }.tab-screen { background: #F5F5F7; overflow: hidden; }
/* Inner scroll wrapper — body + hero scroll together; nav stays pinned. */.pm-scroll { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }.pm-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); }
.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; }
[hidden] { display: none !important; }Status bar SVGs: time is always 9:41; icons are signal bars (17×11), wifi (16×12), battery (25×12) drawn inline — #FFFFFF fills on dark screens, #202020 fills on light screens (the light variant hard-codes fill color per-screen rather than using .sb--dark).
Fit scaler (verbatim core):
var scale = Math.min(sw / 834, sh / 1194, 1);if (scale < 1) { el.style.transform = 'scale(' + scale + ')'; el.style.transformOrigin = 'top center'; 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';}3. Atoms
Section titled “3. Atoms”3.1 Bottom nav (tablet has NO sidebar)
Section titled “3.1 Bottom nav (tablet has NO sidebar)”Canonical 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; }<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-receiving')">…<span>Receiving</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-quarantine')">…<span>Quarantine</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-replenishment')">…<span>Replenishment</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-profile')">…<span>Profile</span></button></nav>Usage: exactly 5 tabs (Home / Receiving / Quarantine / Replenishment / Profile). The nav is DUPLICATED per screen (static HTML, no JS re-render); .is-active is baked into the markup of each screen. 24px stroke icons, no fills. .rsm-nav-badge is defined but not used in this file’s markup. Active color is --g-dark (dark green), never bright brand green. No collapse behavior, no group labels — this is a tablet bottom nav, not a sidebar.
3.2 Topbar / header — hero header (dashboard) + status bar
Section titled “3.2 Topbar / header — hero header (dashboard) + status bar”Dashboard uses a brand-dark hero header with avatar + name + role pill (there is NO search box anywhere in this persona). Canonical selectors: .rsm-hero, .rsm-hero-bg, .rsm-hero-row, .rsm-hero-id, .rsm-hero-avatar, .rsm-hero-text, .rsm-hero-name, .rsm-hero-meta, .rsm-hero-role
.rsm-hero { position: relative; background: var(--brand-dark); padding: 0 32px 80px; flex-shrink: 0; }.rsm-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; }.rsm-hero-bg svg { width: 100%; height: auto; }.rsm-hero-row { position: relative; display: flex; align-items: center; justify-content: space-between; padding: 12px 0 20px; }.rsm-hero-id { display: flex; align-items: center; gap: 14px; min-width: 0; }.rsm-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; }.rsm-hero-text { min-width: 0; }.rsm-hero-name { font-size: 20px; font-weight: 600; color: var(--white); letter-spacing: -0.3px; line-height: 26px; }.rsm-hero-meta { font-size: 13px; font-weight: 400; color: rgba(255,255,255,0.6); line-height: 18px; margin-top: 2px; }.rsm-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="rsm-hero"> <div class="sb sb--dark"> … status bar … </div> <span class="rsm-hero-bg" aria-hidden="true"> <svg viewBox="0 0 100 100" fill="none"><path d="M70 15h-10c-1 0-2 .8-2 2v22h14L40 95h22c1 0 2-.4 3-1.2L82 65c.5-.6.5-1.4 0-2L72 17c-.3-.7-1-2-2-2Z" fill="#01E46F"/></svg> </span> <div class="rsm-hero-row"> <div class="rsm-hero-id"> <span class="rsm-hero-avatar">TA</span> <div class="rsm-hero-text"> <p class="rsm-hero-name">Tobi Akinwale</p> <span class="rsm-hero-role">Procurement Manager</span> <p class="rsm-hero-meta">Receiving floor · Wed, 18 May 2026</p> </div> </div> </div></header>Usage: dashboard only. The hero’s 80px bottom padding hosts the overlapping KPI strip (margin: -64px 24px 0). Big ghost brand-mark at right, 12% opacity. Avatar = brand-green circle + dark initials at weight 600. Order inside hero-text: name, then role pill, then meta line.
3.3 Page head (title + sub + actions row)
Section titled “3.3 Page head (title + sub + actions row)”Two variants. Detail screens use the centered rsm-* row with round back button; root tab screens use the left-aligned pm-* row with an optional circular icon button on the right.
Canonical selectors: .rsm-page-head, .rsm-page-head-row, .rsm-page-title, .rsm-page-back, .pm-page-head-row, .pm-page-title, .pm-page-icon-btn
.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; }
/* ── Page-head row variant for screens with left-aligned title + right slot ── */.pm-page-head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0; height: 64px; margin: 0; }.pm-page-title { font-size: 24px; font-weight: 600; color: var(--grey12); letter-spacing: -0.3px; }
/* Circular icon button — for search / filter / settings affordances. */.pm-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; }.pm-page-icon-btn:hover { background: var(--grey2); }.pm-page-icon-btn svg { width: 18px; height: 18px; }<!-- Root tab screen (left title, optional icon button) --><header class="rsm-page-head"> <div class="pm-page-head-row"> <h1 class="pm-page-title">Profile</h1> <button class="pm-page-icon-btn" type="button" aria-label="Settings" onclick="showToast('Settings', 'Coming soon', 'info')"> <svg …gear icon…></svg> </button> </div></header>
<!-- Detail screen (centered title, round back button) --><header class="rsm-page-head"> <div class="rsm-page-head-row"> <button class="rsm-page-back" type="button" aria-label="Back" onclick="goTo('s-quarantine')"> <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="qtn-detail-page-title">QTN-150526-007</h1> </div></header>Usage: also present are section heads inside the body:
.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; }<div class="rsm-section-head"> <h2 class="rsm-section-title">Today's receiving</h2> <button class="rsm-section-link" type="button" onclick="goTo('s-receiving')">View all</button></div>Section titles are 16/600; inside cards they’re often overridden inline to font-size: 13px. “View all”/“Open” links are 12.5/600 in --g-dark.
3.4 Tabs & segmented controls (all variants)
Section titled “3.4 Tabs & segmented controls (all variants)”Variant A — .pm-tabs filter tabs (segmented control on the page body). Used on every list page (Receiving, Quarantine, Replenishment, Missing) to filter the list by lifecycle stage. Grey track, white raised active chip, per-tab count badge. Active state via aria-pressed.
/* Filter tabs — segmented control on the page body. *//* Default: 4 columns (Receiving, Quarantine). Override: when only 2 tabs are present (Replenishment), each takes half the strip so the segmented control fills the whole width. */.pm-tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; padding: 4px; background: var(--grey2); border-radius: 12px; }.pm-tabs:has(> .pm-tab:nth-child(2):last-child) { grid-template-columns: repeat(2, 1fr); }.pm-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; }.pm-tab:hover { color: var(--grey12); }.pm-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); }.pm-tab-count { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: var(--grey3); color: var(--grey11); font-size: 11px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; }.pm-tab[aria-pressed="true"] .pm-tab-count { background: var(--g-3); color: var(--g-dark); }[data-receiving-pane][hidden] { display: none; }<div class="pm-tabs" role="tablist"> <button class="pm-tab" type="button" aria-pressed="true" data-receiving-tab="active" onclick="setReceivingTab(this)">Active <span class="pm-tab-count" id="pm-tab-count-active">1</span></button> <button class="pm-tab" type="button" aria-pressed="false" data-receiving-tab="today" onclick="setReceivingTab(this)">Today <span class="pm-tab-count" id="pm-tab-count-today">2</span></button> <button class="pm-tab" type="button" aria-pressed="false" data-receiving-tab="scheduled" onclick="setReceivingTab(this)">Scheduled <span class="pm-tab-count" id="pm-tab-count-scheduled">3</span></button> <button class="pm-tab" type="button" aria-pressed="false" data-receiving-tab="past" onclick="setReceivingTab(this)">Past <span class="pm-tab-count" id="pm-tab-count-past">3</span></button></div><!-- Panes — only one visible at a time. --><div data-receiving-pane="active" id="pm-rcv-pane-active" style="display: flex; flex-direction: column; gap: 12px;"></div><div data-receiving-pane="today" id="pm-rcv-pane-today" hidden style="flex-direction: column; gap: 12px;"></div>Notes: 3-tab instances (Missing) still use the default 4-col grid (the :has() override only fires at exactly 2 children). Counts hydrate from renderXCounts(). Active tab text is --g-dark, count chip flips to --g-3 bg.
Variant B — .pm-segmented form segmented control (inside sheets). Used for mutually-exclusive form choices (e.g. “Expected arrival”: Arriving now / Today, PM / Tomorrow). Same grey-track/white-chip pattern, but active text is neutral --grey12 (not green), no counts.
.pm-segmented { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; padding: 4px; background: var(--grey2); border-radius: 12px; }.pm-segmented-opt { display: flex; align-items: center; justify-content: center; padding: 10px 14px; background: transparent; border: 0; border-radius: 9px; 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; }.pm-segmented-opt[aria-pressed="true"] { background: var(--white); color: var(--grey12); font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,0.05); }<div class="pm-segmented" role="group"> <button type="button" class="pm-segmented-opt" aria-pressed="true" data-arrival="now" onclick="setNewSessionArrival(this)">Arriving now</button> <button type="button" class="pm-segmented-opt" aria-pressed="false" data-arrival="today-pm" onclick="setNewSessionArrival(this)">Today, PM</button> <button type="button" class="pm-segmented-opt" aria-pressed="false" data-arrival="tomorrow" onclick="setNewSessionArrival(this)">Tomorrow</button></div>There are NO line/underline tabs in this file. The three “tab” levels are: bottom nav (.rsm-nav-tab) → page filter tabs (.pm-tabs) → form segmented (.pm-segmented).
3.5 List system (the tablet equivalent of the table system — there are no <table>s)
Section titled “3.5 List system (the tablet equivalent of the table system — there are no <table>s)”All lists are stacks of tappable <button> cards inside a pane div (display:flex; flex-direction:column; gap:10–12px). Six row species:
a) .pm-receiving-card — rich queue card (dashboard “Today’s receiving” + Receiving list panes). White card, header (rounded-square mark + id/sub + status pill), progress bar row, meta row.
.pm-receiving-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; }.pm-receiving-card:active { transform: scale(0.998); }.pm-receiving-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }.pm-receiving-id-block { display: flex; align-items: center; gap: 12px; min-width: 0; }.pm-receiving-id-mark { width: 42px; height: 42px; border-radius: 12px; background: var(--grey2); color: var(--grey11); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.pm-receiving-id-mark[data-tone="green"] { color: var(--g-dark); }.pm-receiving-id-mark[data-tone="amber"] { color: var(--amber9); }.pm-receiving-id-mark[data-tone="grey"] { color: var(--grey11); }.pm-receiving-id-mark svg { width: 20px; height: 20px; }.pm-receiving-id-text { min-width: 0; }.pm-receiving-id { font-size: 15px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; font-variant-numeric: tabular-nums; }.pm-receiving-sub { font-size: 12.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }
/* Progress row inside a receiving card. */.pm-receiving-progress { display: flex; flex-direction: column; gap: 6px; }.pm-receiving-progress-row { display: flex; align-items: center; justify-content: space-between; font-size: 12.5px; }.pm-receiving-progress-label { color: var(--grey11); }.pm-receiving-progress-val { font-weight: 600; color: var(--grey12); font-variant-numeric: tabular-nums; }.pm-receiving-progress-bar { display: block; height: 6px; border-radius: 100px; background: var(--grey2); overflow: hidden; }.pm-receiving-progress-fill { display: block; height: 100%; background: var(--g-primary); border-radius: 100px; transition: width 0.3s ease; }.pm-receiving-progress-fill[data-tone="amber"] { background: #F59E0B; }.pm-receiving-progress-fill[data-tone="grey"] { background: var(--grey5); }
.pm-receiving-meta-row { display: flex; align-items: center; gap: 16px; font-size: 12px; color: var(--grey11); }.pm-receiving-meta-cell { display: flex; align-items: center; gap: 6px; }.pm-receiving-meta-cell svg { width: 14px; height: 14px; color: var(--grey10); }.pm-receiving-meta-val { font-weight: 600; color: var(--grey12); }<button class="pm-receiving-card" type="button" onclick="openReceivingSession('RCV-180526-002')"> <div class="pm-receiving-head"> <div class="pm-receiving-id-block"> <span class="pm-receiving-id-mark" data-tone="amber"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg> </span> <div class="pm-receiving-id-text"> <p class="pm-receiving-id">RCV-180526-002</p> <p class="pm-receiving-sub">Apapa Components Ltd · PO-2026-00119</p> </div> </div> <span class="pm-pill" data-state="active">Active</span> </div> <div class="pm-receiving-progress"> <div class="pm-receiving-progress-row"> <span class="pm-receiving-progress-label">Items scanned</span> <span class="pm-receiving-progress-val">32 of 48</span> </div> <span class="pm-receiving-progress-bar"><span class="pm-receiving-progress-fill" data-tone="amber" style="width: 67%;"></span></span> </div> <div class="pm-receiving-meta-row"> <span class="pm-receiving-meta-cell"><svg …clock…></svg>Started 8:14 AM</span> <span class="pm-receiving-meta-cell"><svg …></svg>2 quarantined so far</span> </div></button>Design note (file comment, verbatim): “Receiving session mark — uniform neutral grey tile, icon stroke carries the state tone. Status pill on the right does the heavy lifting for state communication; the mark stays calm.”
b) .pm-po-row — compact pipeline row (dashboard “Scheduled receiving”). 40px rounded-square icon + two text lines + right column (pill over date).
.pm-po-list { display: flex; flex-direction: column; gap: 10px; }.pm-po-row { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: var(--white); border-radius: 14px; box-shadow: var(--sh-card); border: 0; text-align: left; font-family: inherit; width: 100%; cursor: pointer; -webkit-appearance: none; appearance: none; transition: transform 0.1s; }.pm-po-row:active { transform: scale(0.998); }.pm-po-row-ic { width: 40px; height: 40px; border-radius: 12px; background: var(--grey2); color: var(--grey11); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.pm-po-row-ic svg { width: 18px; height: 18px; }.pm-po-row-text { flex: 1; min-width: 0; }.pm-po-row-id { font-size: 13.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.pm-po-row-sub { font-size: 12px; font-weight: 500; color: var(--grey10); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.pm-po-row-right { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }.pm-po-row-date { font-size: 11.5px; font-weight: 600; color: var(--grey11); white-space: nowrap; }<button class="pm-po-row" type="button" onclick="openReceivingSession('RCV-220526-001')"> <span class="pm-po-row-ic"><svg …truck…></svg></span> <div class="pm-po-row-text"> <p class="pm-po-row-id">RCV-220526-001 · Lagos Auto Hub</p> <p class="pm-po-row-sub">PO-2026-00121 · 56 items · Wiring harness + MCU lot</p> </div> <div class="pm-po-row-right"> <span class="pm-pill" data-state="scheduled">Scheduled</span> <span class="pm-po-row-date">Thu 22 May · AM</span> </div></button>c) .pm-quarantine-row — dense list row, grid 52px 1fr auto. Tone via data-tone on the row: blue (pending review, tinted, shadow removed), neutral (white), muted (grey1). Thumb is a 52px rounded square with optional photo-count chip.
.pm-quarantine-row { display: grid; grid-template-columns: 52px 1fr auto; gap: 14px; align-items: center; padding: 14px 16px; border-radius: 14px; border: 0; width: 100%; text-align: left; font-family: inherit; cursor: pointer; -webkit-appearance: none; appearance: none; background: var(--white); box-shadow: var(--sh-card); transition: filter 0.15s ease, transform 0.1s ease; }.pm-quarantine-row:hover { filter: brightness(0.99); }.pm-quarantine-row:active { transform: scale(0.998); }.pm-quarantine-row[data-tone="blue"] { background: rgba(30, 64, 175, 0.04); box-shadow: none; }.pm-quarantine-row[data-tone="neutral"] { background: var(--white); }.pm-quarantine-row[data-tone="muted"] { background: var(--grey1); box-shadow: none; }
.pm-quarantine-thumb { width: 52px; height: 52px; border-radius: 12px; background: var(--grey2); display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--grey11); position: relative; }.pm-quarantine-thumb[data-tone="blue"] { background: rgba(30, 64, 175, 0.08); color: #1E40AF; }.pm-quarantine-thumb[data-tone="success"] { background: rgba(5, 133, 62, 0.08); color: var(--g-dark); }.pm-quarantine-thumb[data-tone="muted"] { background: var(--grey2); color: var(--grey10); }.pm-quarantine-thumb svg { width: 22px; height: 22px; }.pm-quarantine-thumb-count { position: absolute; bottom: -4px; right: -4px; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: var(--grey12); color: var(--white); font-size: 10.5px; font-weight: 600; display: flex; align-items: center; justify-content: center; line-height: 1; }
.pm-quarantine-meta { min-width: 0; display: flex; flex-direction: column; gap: 4px; }.pm-quarantine-top { display: flex; align-items: center; gap: 8px; min-width: 0; }.pm-quarantine-id { font-size: 13px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; font-variant-numeric: tabular-nums; flex-shrink: 0; }.pm-quarantine-item { font-size: 14px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.pm-quarantine-sub { font-size: 12px; font-weight: 500; color: var(--grey10); display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; overflow: hidden; }.pm-quarantine-sub .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--grey7); flex-shrink: 0; }.pm-quarantine-reason { color: var(--grey11); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.pm-quarantine-age { color: var(--grey10); flex-shrink: 0; font-variant-numeric: tabular-nums; }.pm-quarantine-chev { color: var(--grey9); flex-shrink: 0; }.pm-quarantine-chev svg { width: 16px; height: 16px; }Markup (generated by quarantineRowHtml(q)):
<button class="pm-quarantine-row" type="button" data-tone="blue" onclick="openQuarantineItem('QTN-150526-007')"> <span class="pm-quarantine-thumb" data-tone="blue"><svg …/><span class="pm-quarantine-thumb-count">4</span></span> <div class="pm-quarantine-meta"> <div class="pm-quarantine-top"> <span class="pm-quarantine-id">QTN-150526-007</span> <span class="pm-quarantine-item">Battery BAT-44-22001</span> </div> <div class="pm-quarantine-sub"> <span>Apapa Components Ltd</span> <span class="dot"></span> <span class="pm-quarantine-reason">Bulged casing</span> <span class="dot"></span> <span class="pm-quarantine-age">3 days</span> </div> </div> <span class="pm-quarantine-chev"><svg …chevron-right…></svg></span></button>Sub-line separators are 3px .dot middots — the canonical separator (never em dashes).
d) .pm-replen-row — three-column grid 44px 1fr 132px with a right-side “cover” column (big number + mini bar). Circular 44px icon (status-tinted). Tones: red, amber, muted.
.pm-replen-row { display: grid; grid-template-columns: 44px 1fr 132px; align-items: center; gap: 14px; padding: 14px 16px; border-radius: 14px; border: 0; width: 100%; text-align: left; font-family: inherit; cursor: pointer; -webkit-appearance: none; appearance: none; background: var(--white); box-shadow: var(--sh-card); transition: filter 0.15s ease, transform 0.1s ease; }.pm-replen-row:hover { filter: brightness(0.99); }.pm-replen-row:active { transform: scale(0.998); }.pm-replen-row[data-tone="red"] { background: rgba(185, 28, 28, 0.035); box-shadow: none; }.pm-replen-row[data-tone="amber"] { background: rgba(180, 83, 9, 0.045); box-shadow: none; }.pm-replen-row[data-tone="muted"] { background: var(--grey1); box-shadow: none; }
.pm-replen-ic { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: var(--grey2); color: var(--grey10); }.pm-replen-row[data-tone="red"] .pm-replen-ic { background: rgba(185, 28, 28, 0.10); color: #B91C1C; }.pm-replen-row[data-tone="amber"] .pm-replen-ic { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.pm-replen-row[data-tone="muted"] .pm-replen-ic { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.pm-replen-ic svg { width: 20px; height: 20px; }
.pm-replen-meta { min-width: 0; display: flex; flex-direction: column; gap: 3px; }.pm-replen-name { font-size: 14.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.pm-replen-sub { font-size: 12px; font-weight: 500; color: var(--grey10); display: flex; align-items: center; gap: 8px; }.pm-replen-sub .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--grey7); flex-shrink: 0; }.pm-replen-row[data-tone="red"] .pm-replen-name { color: #B91C1C; }.pm-replen-row[data-tone="amber"] .pm-replen-name { color: var(--amber9); }
.pm-replen-cover { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; min-width: 0; }.pm-replen-cover-num { font-size: 18px; font-weight: 700; color: var(--grey12); letter-spacing: -0.3px; font-variant-numeric: tabular-nums; line-height: 1; }.pm-replen-cover-unit { font-size: 11px; font-weight: 500; color: var(--grey10); letter-spacing: -0.02em; }.pm-replen-row[data-tone="red"] .pm-replen-cover-num { color: #B91C1C; }.pm-replen-row[data-tone="amber"] .pm-replen-cover-num { color: var(--amber9); }.pm-replen-row[data-tone="muted"] .pm-replen-cover-num { color: var(--g-dark); }.pm-replen-bar { width: 100%; height: 4px; border-radius: 100px; background: var(--grey3); overflow: hidden; position: relative; }.pm-replen-bar-fill { position: absolute; top: 0; left: 0; height: 100%; border-radius: 100px; background: var(--grey9); transition: width 0.3s ease; }.pm-replen-row[data-tone="red"] .pm-replen-bar-fill { background: #B91C1C; }.pm-replen-row[data-tone="amber"] .pm-replen-bar-fill { background: var(--amber9); }.pm-replen-row[data-tone="muted"] .pm-replen-bar-fill { background: var(--g-dark); }Markup (from replenRowHtml(i); bar fill = Math.max(2, Math.min(100, (i.days / 10) * 100)) %):
<button class="pm-replen-row" type="button" data-tone="red" onclick="openReplenishment('wiring-harness')"> <span class="pm-replen-ic"><svg …trending-down…></svg></span> <div class="pm-replen-meta"> <span class="pm-replen-name">Wiring harness</span> <span class="pm-replen-sub"> <span>48 units ATP</span><span class="dot"></span><span>Lead 4d</span><span class="dot"></span><span>Apapa Components</span> </span> </div> <div class="pm-replen-cover"> <div style="display: flex; align-items: baseline; gap: 4px;"> <span class="pm-replen-cover-num">2.4</span> <span class="pm-replen-cover-unit">d cover</span> </div> <div class="pm-replen-bar"><div class="pm-replen-bar-fill" style="width: 24%;"></div></div> </div></button>e) .pm-missing-row — stacked audit card with inline actions (not a button; buttons live inside).
.pm-missing-row { display: flex; flex-direction: column; gap: 10px; padding: 14px 16px; border-radius: 14px; background: var(--white); box-shadow: var(--sh-card); }.pm-missing-row[data-tone="red"] { background: rgba(185, 28, 28, 0.035); box-shadow: none; }.pm-missing-row[data-tone="muted"] { background: var(--grey1); box-shadow: none; }.pm-missing-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }.pm-missing-id { font-size: 12.5px; font-weight: 600; color: var(--grey11); letter-spacing: -0.05em; font-variant-numeric: tabular-nums; }.pm-missing-name { font-size: 14.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; }.pm-missing-row[data-tone="red"] .pm-missing-name { color: #B91C1C; }.pm-missing-sub { font-size: 12px; font-weight: 500; color: var(--grey10); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }.pm-missing-sub .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--grey7); flex-shrink: 0; }.pm-missing-actions { display: flex; gap: 8px; padding-top: 4px; }.pm-missing-actions button { height: 36px; padding: 0 14px; font-size: 13px; width: auto; flex: 1; }.pm-missing-resolve { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.pm-missing-resolve:hover { background: rgba(5, 133, 62, 0.15); }.pm-missing-writeoff { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.pm-missing-writeoff:hover { background: rgba(185, 28, 28, 0.14); }.pm-missing-actions button { border: 0; border-radius: 10px; cursor: pointer; font-family: inherit; font-weight: 600; -webkit-appearance: none; appearance: none; letter-spacing: -0.05em; transition: background 0.15s ease; }.pm-missing-resolution { display: flex; align-items: center; gap: 8px; padding-top: 4px; font-size: 12px; font-weight: 500; color: var(--grey10); }.pm-missing-resolution svg { width: 13px; height: 13px; color: var(--g-dark); flex-shrink: 0; }.pm-missing-row[data-tone="muted"] .pm-missing-resolution svg { color: var(--grey9); }<div class="pm-missing-row" data-tone="red"> <div class="pm-missing-top"> <span class="pm-missing-id">MIS-180526-001 · RCV-170526-008</span> <span class="pm-pill" data-state="missing">Unresolved · 1 day</span> </div> <div> <p class="pm-missing-name">Fasteners × 2</p> <p class="pm-missing-sub"><span>Lekki Hardware Co</span><span class="dot"></span><span>…reason…</span></p> </div> <div class="pm-missing-actions"> <button type="button" class="pm-missing-resolve" onclick="resolveMissing('MIS-180526-001')">Mark resolved</button> <button type="button" class="pm-missing-writeoff" onclick="openMissingWriteOff('MIS-180526-001')">Write off</button> </div></div>f) In-panel micro rows — non-card rows nested inside white panels, sitting on --grey1/--grey2:
/* Per-item reconciliation row. */.pm-item-list { display: flex; flex-direction: column; gap: 8px; }.pm-item-row { display: grid; grid-template-columns: 36px 1fr auto; gap: 12px; align-items: center; padding: 12px 14px; background: var(--grey2); border-radius: 10px; }.pm-item-ic { width: 36px; height: 36px; border-radius: 10px; background: var(--white); color: var(--grey11); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.pm-item-ic[data-tone="green"] { color: var(--g-dark); }.pm-item-ic[data-tone="amber"] { color: var(--amber9); }.pm-item-ic[data-tone="red"] { color: #B91C1C; }.pm-item-ic svg { width: 16px; height: 16px; }.pm-item-text { min-width: 0; }.pm-item-name { font-size: 13.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }.pm-item-meta { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.pm-item-counts { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }.pm-item-count-pill { display: inline-flex; align-items: center; gap: 4px; padding: 4px 9px; border-radius: 100px; background: var(--white); font-size: 11px; font-weight: 600; color: var(--grey11); letter-spacing: -0.02em; }.pm-item-count-pill[data-tone="green"] { background: var(--g-3); color: var(--g-dark); }.pm-item-count-pill[data-tone="amber"] { background: #FEF3C7; color: var(--amber9); }.pm-item-count-pill[data-tone="red"] { background: #FEE2E2; color: #B91C1C; }
/* Linked-items mini-row. */.pm-linked-row { display: grid; grid-template-columns: 32px 1fr auto; align-items: center; gap: 10px; padding: 8px 10px; background: var(--grey1); border-radius: 10px; border: 0; width: 100%; text-align: left; font-family: inherit; cursor: pointer; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; }.pm-linked-row:hover { background: var(--grey2); }.pm-linked-ic { width: 32px; height: 32px; border-radius: 50%; background: var(--grey2); color: var(--grey10); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.pm-linked-ic svg { width: 16px; height: 16px; }.pm-linked-row[data-state="released"] .pm-linked-ic { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.pm-linked-row[data-state="open"] .pm-linked-ic { background: rgba(30, 64, 175, 0.08); color: #1E40AF; }.pm-linked-text { min-width: 0; }.pm-linked-id { font-size: 12.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.pm-linked-sub { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 1px; }
/* At-risk WO row. */.pm-risk-row { display: grid; grid-template-columns: 36px 1fr auto; align-items: center; gap: 10px; padding: 10px 12px; background: var(--grey1); border-radius: 10px; }.pm-risk-ic { width: 36px; height: 36px; border-radius: 50%; background: rgba(185, 28, 28, 0.08); color: #B91C1C; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.pm-risk-ic svg { width: 16px; height: 16px; }.pm-risk-row[data-state="warn"] .pm-risk-ic { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.pm-risk-row[data-state="ok"] .pm-risk-ic { background: rgba(5, 133, 62, 0.10); color: var(--g-dark); }.pm-risk-text { min-width: 0; }.pm-risk-id { font-size: 12.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; font-variant-numeric: tabular-nums; }.pm-risk-sub { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 1px; }Head-row-with-search / footer / pagination: none exist in this file. List “headers” are .rsm-section-head (title + meta or link) or the .pm-tabs strip; lists are short, unpaginated stacks.
Row hover convention across all tappable rows/cards: filter: brightness(0.97–0.99) on hover, transform: scale(0.998) (or 0.97–0.985 on buttons) on :active. No background-color hover swaps on shadowed cards.
3.6 Buttons (every variant)
Section titled “3.6 Buttons (every variant)”/* ── 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:disabled, .btn-primary.is-disabled { background: var(--g-3); border-color: var(--g-3); color: rgba(17,17,17,0.45); cursor: not-allowed; pointer-events: none; }.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); border-color: var(--grey9); }.btn-secondary:active { transform: scale(0.98); }.btn-secondary--lg { height: 56px; font-size: 17px; }
.btn-text-destructive { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; height: 48px; background: transparent; border: 0; border-radius: 10px; font-family: inherit; font-size: 14.5px; font-weight: 600; color: #B91C1C; letter-spacing: -0.1px; cursor: pointer; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; }.btn-text-destructive:hover { background: rgba(185, 28, 28, 0.06); }.btn-text-destructive svg { width: 16px; height: 16px; }
/* FAB — "New receiving session" trigger. Lives outside .pm-scroll so it stays pinned bottom-right while the page scrolls. */.pm-fab { position: absolute; right: 24px; bottom: 120px; z-index: 30; display: inline-flex; align-items: center; gap: 8px; padding: 0 22px 0 18px; height: 56px; border-radius: 100px; background: var(--brand); border: 0; color: var(--text-main); font-family: inherit; font-size: 15px; font-weight: 600; letter-spacing: -0.1px; cursor: pointer; box-shadow: 0 12px 28px rgba(1,228,111,0.32), 0 2px 6px rgba(0,0,0,0.12); -webkit-appearance: none; appearance: none; transition: background 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;}.pm-fab:hover { background: #00C45B; }.pm-fab:active { transform: scale(0.97); }.pm-fab svg { width: 18px; height: 18px; }<button class="btn-primary btn-primary--lg" onclick="goTo('s-welcome')">Get started</button><button class="btn-secondary btn-secondary--lg" type="button" onclick="closeScanSheet()">Done</button><button class="pm-fab" type="button" onclick="openNewSessionSheet()" aria-label="New receiving session"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> New session</button>Summary table:
| Variant | Height | Radius | Fill | Text |
|---|---|---|---|---|
.btn-primary |
52px (–lg 56px) | 10px | --brand #01E46F, 1.5px same-color border |
--text-main #202020, 16/600 (–lg 17px) — dark text on green, never white |
.btn-primary:disabled / .is-disabled |
— | — | --g-3 #B5F1C2 |
rgba(17,17,17,0.45) |
.btn-secondary |
52px (–lg 56px) | 10px | transparent, 1.5px --grey5 border |
--grey12, 16/600 |
.btn-text-destructive |
48px | 10px | transparent (hover rgba red 6%) | #B91C1C, 14.5/600 |
.pm-fab |
56px pill | 100px | --brand + green glow shadow |
dark text 15/600; pinned right:24px; bottom:120px (above nav) |
Icon buttons .rsm-page-back / .pm-page-icon-btn / .pm-photo-nav-btn |
44×44 | 50% | white, 1px --grey6 border |
--grey12 glyph (20px back / 18px others) |
Inline tinted actions .pm-missing-resolve / .pm-missing-writeoff |
36px | 10px | 8–10% rgba tint | tone color, 13/600 |
Inside .pm-detail-cta, .btn-primary/.btn-secondary are overridden to height: 48px; flex: 1; |
3.7 Badges & status pills
Section titled “3.7 Badges & status pills”.pm-pill — the status pill (“same shape as km-wo-pill / rsm-wo-summary-pill” per file comment). All state variants:
.pm-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; }.pm-pill[data-state="active"] { background: #FEF3C7; color: var(--amber9); }.pm-pill[data-state="scheduled"] { background: var(--grey3); color: var(--grey11); }.pm-pill[data-state="closed"] { background: var(--g-3); color: var(--g-dark); }.pm-pill[data-state="quarantined"] { background: #FEF3C7; color: var(--amber9); }.pm-pill[data-state="missing"] { background: #FEE2E2; color: #B91C1C; }.pm-pill[data-state="released"] { background: var(--grey3); color: var(--grey11); }.pm-pill[data-state="held"] { background: var(--g-3); color: var(--g-dark); }.pm-pill[data-state="assigned"] { background: #DBEAFE; color: #1E40AF; }<span class="pm-pill" data-state="active">Active</span><span class="pm-pill" data-state="missing">Stockout in 3 days</span>.pm-check-pill — small icon + label result pill (per “completed state subtle” rule — a small green check, never a flooded green card):
.pm-check-pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px 4px 6px; border-radius: 100px; background: rgba(5, 133, 62, 0.08); color: var(--g-dark); font-size: 11.5px; font-weight: 600; letter-spacing: -0.02em; }.pm-check-pill svg { width: 14px; height: 14px; }.pm-check-pill[data-tone="muted"] { background: var(--grey2); color: var(--grey11); }.pm-check-pill[data-tone="red"] { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.pm-check-pill[data-tone="amber"] { background: #FEF3C7; color: var(--amber9); }<span class="pm-check-pill"><svg …check…></svg>Resolved</span><span class="pm-check-pill" data-tone="red"><svg …x-circle…></svg>Write-off</span>Role pills (hero + profile): .rsm-hero-role (rgba(1,228,111,0.16) bg + --g-primary text, on dark) and .rsm-profile-role-pill (--g-2 bg + --g-dark text, on light) — 11/600, 3px 10px, radius 100px.
Count badges: .pm-tab-count (18px round, grey3/grey11; active g-3/g-dark), .rsm-nav-badge (18px, --red9 bg, white 10.5/700), .pm-quarantine-thumb-count (18px, --grey12 bg, white 10.5/600, overlaps thumb corner).
Status-dot pattern — .pm-activity-dot, 8px circle, tone via data attribute:
.pm-activity-row { display: flex; align-items: flex-start; gap: 10px; padding: 6px 0; }.pm-activity-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--g-primary); flex-shrink: 0; margin-top: 7px; }.pm-activity-dot[data-tone="amber"] { background: var(--amber9); }.pm-activity-dot[data-tone="blue"] { background: #1E40AF; }.pm-activity-dot[data-tone="red"] { background: #B91C1C; }.pm-activity-text { flex: 1; min-width: 0; font-size: 12.5px; line-height: 18px; color: var(--grey12); }.pm-activity-text strong { font-weight: 600; }.pm-activity-time { font-size: 11px; color: var(--grey10); flex-shrink: 0; margin-top: 3px; }<div class="pm-activity-row"> <span class="pm-activity-dot" data-tone="amber"></span> <p class="pm-activity-text"><strong>Wiring harness</strong> ATP fell below 5-day threshold</p> <span class="pm-activity-time">2 h</span></div>NOTE: this tablet file’s pills are 10.5px/600/uppercase-free with full-round (100px) radius — this is the tablet pill convention (distinct from the web .ds-badge 12px/500/6px-radius canon; do not mix them).
3.8 KPI / stat cards
Section titled “3.8 KPI / stat cards”Canonical: .pm-kpi (+ -head, -ic, -label, -num, -foot), in .pm-kpi-strip.
/* ── pm-* KPI strip ── Filled icon circles with subtle rgba tints (10% opacity) instead of Tailwind-100 solid colors. … Foot copy stays neutral grey so the warnings don't feel shouty. */.pm-kpi-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: -64px 24px 0; position: relative; z-index: 2; }.pm-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.pm-kpi { cursor: pointer; transition: filter 0.15s ease, transform 0.1s ease; }button.pm-kpi:hover { filter: brightness(0.99); }button.pm-kpi:active { transform: scale(0.998); }.pm-kpi-head { display: flex; align-items: center; gap: 10px; }.pm-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); }.pm-kpi-ic svg { width: 18px; height: 18px; }.pm-kpi[data-tone="blue"] .pm-kpi-ic { background: rgba(30, 64, 175, 0.10); color: #1E40AF; }.pm-kpi[data-tone="amber"] .pm-kpi-ic { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.pm-kpi[data-tone="red"] .pm-kpi-ic { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.pm-kpi[data-tone="neutral"] .pm-kpi-ic { background: var(--grey2); color: var(--grey11); }.pm-kpi-label { font-size: 13px; font-weight: 500; color: var(--grey11); letter-spacing: -0.05em; }.pm-kpi-num { font-size: 28px; font-weight: 700; letter-spacing: -0.4px; line-height: 32px; color: var(--grey12); font-variant-numeric: tabular-nums; }.pm-kpi-foot { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: -2px; }<div class="pm-kpi-strip" role="group" aria-label="Today overview"> <div class="pm-kpi" data-tone="amber"> <div class="pm-kpi-head"> <span class="pm-kpi-ic"><svg …warning-triangle…></svg></span> <span class="pm-kpi-label">In quarantine</span> </div> <p class="pm-kpi-num">7</p> <p class="pm-kpi-foot">Oldest 5 days</p> </div> <!-- Tappable variant: same classes on a <button> --> <button class="pm-kpi" data-tone="red" type="button" onclick="goTo('s-missing')"> <div class="pm-kpi-head">…</div> <p class="pm-kpi-num" id="dash-missing-count">2</p> <p class="pm-kpi-foot">Awaiting investigation</p> </button></div>Structure order: icon-circle + label on one row → big number → grey foot line. Number is the ONLY 700-weight element. 6 tiles wrap 3+3 in a 3-col grid. Icon circle always tinted-bg + colored stroke glyph (never solid fills). No delta arrows/percent-change chips in this file — the foot line carries the qualifier text (“Oldest 5 days”, “In transit · expected this week”).
Related display-stat atoms on the replenishment detail: .pm-cover-atp (36/700), .pm-cover-days-num (22/700), .pm-rcv-breakdown-val (18/700) with 11px uppercase labels:
.pm-rcv-breakdown { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; padding-top: 14px; border-top: 1px solid var(--grey3); }.pm-rcv-breakdown-cell { display: flex; flex-direction: column; gap: 2px; }.pm-rcv-breakdown-label { font-size: 11px; font-weight: 500; color: var(--grey10); letter-spacing: 0.04em; text-transform: uppercase; }.pm-rcv-breakdown-val { font-size: 18px; font-weight: 700; color: var(--grey12); letter-spacing: -0.2px; font-variant-numeric: tabular-nums; }.pm-rcv-breakdown-val[data-tone="amber"] { color: var(--amber9); }.pm-rcv-breakdown-val[data-tone="red"] { color: #B91C1C; }.pm-rcv-breakdown-val[data-tone="grey"] { color: var(--grey10); }3.9 Cards / panels
Section titled “3.9 Cards / panels”NO card in this file has a border. Every white card uses box-shadow: var(--sh-card) only; tinted/muted variants drop the shadow too (box-shadow: none) and rely on the tint alone. The only borders in the entire file are: 1px structural separators (.rsm-nav border-top grey4; .pm-detail-cta border-top grey3; .pm-rcv-breakdown border-top grey3; .rsm-profile-row + .rsm-profile-row::before divider grey3), input/.btn-secondary control borders, 44px circular icon-button borders (grey6), the dashed .pm-cover-bar-threshold, .pm-scan-corner reticle strokes, and the scan counter’s rgba brand border. None are on cards.
Card catalogue (all verbatim):
/* Side card (dashboard right column) */.pm-side-card { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 18px; display: flex; flex-direction: column; gap: 14px; }.pm-side-head { display: flex; align-items: center; justify-content: space-between; }.pm-side-title { font-size: 14px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; }.pm-side-link { font-size: 12px; font-weight: 600; color: var(--g-dark); background: none; border: 0; cursor: pointer; font-family: inherit; padding: 0; }.pm-side-body { display: flex; flex-direction: column; gap: 12px; }
/* Section panel (Items, Activity). */.pm-panel { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 18px; display: flex; flex-direction: column; gap: 14px; }.pm-panel-head { display: flex; align-items: center; justify-content: space-between; }.pm-panel-title { font-size: 14px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; }.pm-panel-meta { font-size: 12px; font-weight: 500; color: var(--grey11); }
/* Sub-section card. Used for Photos / Notes / Linked items / Activity. */.pm-detail-card { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }
/* Hero card with session-level reconciliation. */.pm-rcv-hero { background: var(--white); border-radius: 18px; box-shadow: var(--sh-card); padding: 22px 24px; display: flex; flex-direction: column; gap: 18px; }
/* Cover summary card (replenishment detail) */.pm-cover-card { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 20px 22px; display: flex; flex-direction: column; gap: 16px; }
/* Detail hero (quarantine) — TINTED, shadowless */.pm-detail-hero { background: rgba(30, 64, 175, 0.04); border-radius: 16px; padding: 20px 22px; display: flex; flex-direction: column; gap: 14px; }.pm-detail-hero[data-tone="success"] { background: rgba(5, 133, 62, 0.04); }.pm-detail-hero[data-tone="muted"] { background: var(--grey1); }.pm-detail-hero-top { display: flex; align-items: center; gap: 10px; }.pm-detail-hero-id { font-size: 12.5px; font-weight: 600; color: var(--grey11); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }.pm-detail-hero-name { font-size: 22px; font-weight: 600; color: var(--grey12); letter-spacing: -0.4px; line-height: 28px; }.pm-detail-hero-meta { font-size: 13px; font-weight: 500; color: var(--grey11); line-height: 18px; }
/* Decision card — full-width tile with stacked action rows. */.pm-decision-card { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 8px; display: flex; flex-direction: column; gap: 4px; }.pm-decision-btn { display: grid; grid-template-columns: 36px 1fr auto; align-items: center; gap: 12px; padding: 12px 14px; border-radius: 12px; border: 0; background: transparent; cursor: pointer; -webkit-appearance: none; appearance: none; text-align: left; font-family: inherit; transition: background 0.15s ease; }.pm-decision-btn:hover { background: var(--grey1); }.pm-decision-btn:active { transform: scale(0.998); }.pm-decision-btn-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); }.pm-decision-btn-ic svg { width: 18px; height: 18px; }.pm-decision-btn[data-tone="amber"] .pm-decision-btn-ic { background: rgba(180, 83, 9, 0.10); color: var(--amber9); }.pm-decision-btn[data-tone="red"] .pm-decision-btn-ic { background: rgba(185, 28, 28, 0.08); color: #B91C1C; }.pm-decision-btn-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }.pm-decision-btn-title { font-size: 14.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; }.pm-decision-btn[data-tone="red"] .pm-decision-btn-title { color: #B91C1C; }.pm-decision-btn-sub { font-size: 12px; font-weight: 500; color: var(--grey10); }.pm-decision-btn-chev { color: var(--grey9); flex-shrink: 0; }.pm-decision-btn-chev svg { width: 16px; height: 16px; }Radius scale: 18px (hero/KPI/profile-user), 16px (standard card/panel), 14px (list rows), 12px (nested tiles/tab track/alert rows), 10px (micro rows, buttons, inputs). Padding: 16–22px on cards; header pattern is a flex space-between head row (title 14/600 + meta or link) followed by the body stack.
Key/value meta grid (used in heroes + identifier cards):
.pm-meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 18px; }.pm-meta-cell { display: flex; flex-direction: column; gap: 3px; min-width: 0; }.pm-meta-label { font-size: 11px; font-weight: 500; color: var(--grey10); letter-spacing: 0.04em; text-transform: uppercase; }.pm-meta-val { font-size: 13.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.pm-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; }<div class="pm-meta-grid"> <div class="pm-meta-cell"> <span class="pm-meta-label">Source session</span> <button class="pm-meta-val pm-meta-val--link" type="button" onclick="openQuarantineRcvLink()">RCV-150526-002</button> </div> <div class="pm-meta-cell"> <span class="pm-meta-label">Supplier</span> <span class="pm-meta-val">Apapa Components Ltd</span> </div></div>Alert row (dashboard side cards) — the tinted tappable notification row:
.pm-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; }.pm-alert-row:hover { filter: brightness(0.97); }.pm-alert-row[data-tone="amber"] { background: rgba(180, 83, 9, 0.045); }.pm-alert-row[data-tone="red"] { background: rgba(185, 28, 28, 0.035); }.pm-alert-row[data-tone="blue"] { background: rgba(30, 64, 175, 0.04); }.pm-alert-row[data-tone="grey"] { background: var(--grey1); }.pm-alert-ic { width: 32px; height: 32px; border-radius: 50%; background: var(--white); display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--grey11); }.pm-alert-row[data-tone="amber"] .pm-alert-ic { color: var(--amber9); }.pm-alert-row[data-tone="red"] .pm-alert-ic { color: #B91C1C; }.pm-alert-row[data-tone="blue"] .pm-alert-ic { color: #1E40AF; }.pm-alert-ic svg { width: 16px; height: 16px; }.pm-alert-text { flex: 1; min-width: 0; }.pm-alert-title { font-size: 13px; font-weight: 600; letter-spacing: -0.05em; color: var(--grey12); }.pm-alert-row[data-tone="amber"] .pm-alert-title { color: var(--amber9); }.pm-alert-row[data-tone="red"] .pm-alert-title { color: #B91C1C; }.pm-alert-row[data-tone="blue"] .pm-alert-title { color: #1E40AF; }.pm-alert-sub { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.pm-alert-chev { width: 16px; height: 16px; color: var(--grey9); flex-shrink: 0; }<button class="pm-alert-row" type="button" data-tone="red" onclick="openReplenishment('wiring-harness')"> <span class="pm-alert-ic"><svg …trending-down…></svg></span> <div class="pm-alert-text"> <p class="pm-alert-title">Wiring harness · 2.4 days of cover</p> <p class="pm-alert-sub">Threshold 5 days · 1 Production WO at risk</p> </div> <svg class="pm-alert-chev" …chevron-right…></svg></button>Tone rule (file comment): “Title carries the tone (clean dark color on a near-white tint — full readability). Sub stays neutral grey… Avoids washed-out tone-on-tone fading.”
3.10 Overlays — bottom sheet ONLY (no modal, no drawer)
Section titled “3.10 Overlays — bottom sheet ONLY (no modal, no drawer)”Every overlay in this file is a bottom sheet (.rsm-sheet-*), used for: create form (New receiving session), confirmations (Close session, Start session), the write-off co-sign form, the mitigation action picker, the scan viewfinder, and the photo preview/lightbox. There are no centered dialogs and no side drawers — this matches the “confirmations on tablet use bottom sheets” hard rule.
/* ── New session sheet ── */.rsm-sheet-overlay { position: absolute; inset: 0; z-index: 50; display: flex; flex-direction: column; justify-content: flex-end; pointer-events: none; overflow: hidden; }.rsm-sheet-overlay.is-open { pointer-events: auto; }.rsm-sheet-backdrop { position: absolute; inset: 0; background: rgba(14,15,18,0.45); opacity: 0; transition: opacity 0.22s ease; }.rsm-sheet-overlay.is-open .rsm-sheet-backdrop { opacity: 1; }.rsm-sheet { position: relative; 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; }.rsm-sheet-overlay.is-open .rsm-sheet { transform: translateY(0); }.rsm-sheet-handle-row { display: flex; align-items: center; justify-content: center; padding: 10px 0 6px; flex-shrink: 0; }.rsm-sheet-handle { width: 40px; height: 4px; border-radius: 100px; background: var(--grey5); }.rsm-sheet-head { padding: 8px 0 16px; flex-shrink: 0; }.rsm-sheet-title { font-size: 20px; font-weight: 600; color: var(--grey12); letter-spacing: -0.3px; margin: 0; }.rsm-sheet-sub { font-size: 13px; color: var(--grey11); line-height: 19px; margin: 4px 0 0; }.rsm-sheet-body { display: flex; flex-direction: column; gap: 18px; padding: 0 0 8px; overflow-y: auto; }.rsm-sheet-body::-webkit-scrollbar { display: none; }.rsm-sheet-cta { display: flex; flex-direction: column; gap: 10px; padding: 16px 0 28px; flex-shrink: 0; }Skeleton markup (all 7 sheets follow it exactly):
<div class="rsm-sheet-overlay" id="sheet-new-session"> <div class="rsm-sheet-backdrop" onclick="closeNewSessionSheet()"></div> <div class="rsm-sheet"> <div class="rsm-sheet-handle-row"><span class="rsm-sheet-handle"></span></div> <div class="rsm-sheet-head"> <p class="rsm-sheet-title">New receiving session</p> <p class="rsm-sheet-sub">Open a session against an inbound shipment. …</p> </div> <div class="rsm-sheet-body"> … fields / summary tile / decision list … </div> <div class="rsm-sheet-cta"> <button class="btn-primary btn-primary--lg" type="button" onclick="submitNewSession()">Create session</button> <button class="btn-secondary btn-secondary--lg" type="button" onclick="closeNewSessionSheet()">Cancel</button> </div> </div></div>Footer button placement: vertically stacked, full-width; PRIMARY ON TOP, Cancel (secondary) below. Sheets whose body rows ARE the actions (mitigation picker, scan sheet) have a single secondary Cancel/Done button. Confirmation sheets embed a compact read-only summary tile — a .pm-detail-hero[data-tone="muted"] with inline padding overrides (padding: 14px 16px; gap: 6px;) + .pm-check-pill state.
The sticky detail CTA strip (not an overlay — pinned between body and nav on detail screens):
.pm-detail-cta { background: var(--white); border-top: 1px solid var(--grey3); padding: 14px 24px 8px; display: flex; gap: 10px; flex-shrink: 0; }.pm-detail-cta .btn-primary,.pm-detail-cta .btn-secondary { height: 48px; flex: 1; }<div class="pm-detail-cta" id="qtn-detail-cta"> <button class="btn-secondary btn-secondary--lg" type="button" onclick="returnToSupplier()">Return to supplier</button> <button class="btn-primary btn-primary--lg" type="button" onclick="releaseQuarantineItem()">Release to stock</button></div>Here (horizontal strip) the order is secondary LEFT, primary RIGHT. It is hidden (display:none / innerHTML=‘’) for closed/resolved records.
Sheet inventory: sheet-new-session, sheet-scan-rcv, sheet-close-rcv, sheet-start-rcv, sheet-writeoff-cosign, sheet-mitigation, sheet-photo-preview.
3.11 Toast
Section titled “3.11 Toast”Canonical DS .alert-toast pill, top-center of the tablet, badge + text; auto-dismisses after 3200ms (no close button in this instance).
/* Toast (overlay) */.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; }<div class="alert-toast" id="toast"></div>JS helper (verbatim):
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');}Call convention: showToast(message, badgeText, variant) — e.g. showToast('Settings', 'Coming soon', 'info') for unbuilt destinations; showToast(${i.name} · Emergency PO drafted, 'Awaiting approval', 'warning'). Note argument order: badge is the SECOND arg but renders FIRST (white pill), message second.
3.12 Inputs & forms
Section titled “3.12 Inputs & forms”Auth-style bordered input (login):
.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.is-error { border-color: var(--red9); box-shadow: 0 0 0 4px rgba(227,38,39,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; }<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 …eye…></svg> </button> </div> <div class="field-aside"><a class="text-link">Forgot password?</a></div></div>Focus ring: brand border + 0 0 0 4px rgba(1,228,111,0.12); error: red9 border + 0 0 0 4px rgba(227,38,39,0.12).
Sheet-style filled fields (all in-app forms — grey2 fill, borderless):
.pm-field { display: flex; flex-direction: column; gap: 8px; }.pm-field-label { font-size: 13px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }.pm-picker-row { display: flex; align-items: center; gap: 12px; height: 52px; padding: 0 16px; background: var(--grey2); border-radius: 10px; cursor: pointer; border: 0; width: 100%; text-align: left; font-family: inherit; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; }.pm-picker-row:hover { background: var(--grey3); }.pm-picker-row svg { width: 18px; height: 18px; color: var(--grey10); flex-shrink: 0; }.pm-picker-row-val { flex: 1; font-size: 14px; font-weight: 500; color: var(--grey12); }.pm-picker-row-val[data-empty="true"] { color: var(--grey9); font-weight: 400; }.pm-picker-row-chev { color: var(--grey9); }
.pm-textarea { width: 100%; box-sizing: border-box; resize: none; border: 0; border-radius: 12px; padding: 12px 14px; background: var(--grey2); font-family: inherit; font-size: 14px; line-height: 20px; color: var(--grey12); -webkit-appearance: none; appearance: none; }.pm-textarea::placeholder { color: var(--grey9); }<div class="pm-field"> <label class="pm-field-label">Supplier</label> <button class="pm-picker-row" type="button" onclick="cycleNewSessionSupplier()"> <svg …leading icon…></svg> <span class="pm-picker-row-val" id="new-session-supplier">Lagos Auto Hub</span> <svg class="pm-picker-row-chev" …chevron-right…></svg> </button></div><div class="pm-field"> <label class="pm-field-label">Notes (optional)</label> <textarea id="new-session-notes" class="pm-textarea" rows="2" placeholder="Anything the receiving bay should know…"></textarea></div>Co-signer picker row (avatar variant of the picker):
.pm-cosigner-row { display: grid; grid-template-columns: 42px 1fr auto; align-items: center; gap: 12px; padding: 10px 14px; background: var(--grey1); border-radius: 12px; border: 0; width: 100%; text-align: left; font-family: inherit; cursor: pointer; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; }.pm-cosigner-row:hover { background: var(--grey2); }.pm-cosigner-avatar { width: 42px; height: 42px; border-radius: 50%; background: var(--g-3); color: var(--g-dark); display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; letter-spacing: -0.2px; flex-shrink: 0; }.pm-cosigner-name { font-size: 14px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; }.pm-cosigner-role { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.pm-cosigner-chev { color: var(--grey9); flex-shrink: 0; }.pm-cosigner-chev svg { width: 16px; height: 16px; }Toggle switch:
/* Toggle — same shape as the retrofit personas. */.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>Search input: DOES NOT EXIST in this persona. No <select> elements either — selection is done with .pm-picker-row (tap to cycle in the prototype) and .pm-segmented.
3.13 Dropdown menus
Section titled “3.13 Dropdown menus”None. There are no floating dropdown/popover menus. Picker rows cycle values via JS (cycleNewSessionSupplier(), cycleNewSessionPo(), cycleWriteOffReason(), cycleWriteOffCosigner()), and action choices live in .pm-decision-card stacks inside sheets. The only expand/collapse affordance is the flag-damaged tray (an inline disclosure, aria-expanded + chevron rotation):
.pm-scan-flag-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 14px; border-radius: 100px; background: var(--grey1); border: 0; width: 100%; font-family: inherit; -webkit-appearance: none; appearance: none; cursor: pointer; transition: background 0.15s ease; }.pm-scan-flag-row:hover { background: var(--grey2); }.pm-scan-flag-label { display: inline-flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 600; color: var(--amber9); letter-spacing: -0.05em; }.pm-scan-flag-label svg { width: 14px; height: 14px; }.pm-scan-flag-chev { color: var(--grey9); transition: transform 0.2s ease; }.pm-scan-flag-chev svg { width: 14px; height: 14px; }.pm-scan-flag-row[aria-expanded="true"] .pm-scan-flag-chev { transform: rotate(180deg); }.pm-scan-flag-tray { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 8px; }.pm-scan-flag-chip { padding: 8px 10px; background: rgba(180, 83, 9, 0.08); color: var(--amber9); border: 0; border-radius: 10px; font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; -webkit-appearance: none; appearance: none; letter-spacing: -0.05em; transition: background 0.15s ease; }.pm-scan-flag-chip:hover { background: rgba(180, 83, 9, 0.16); }3.14 Empty states
Section titled “3.14 Empty states”/* Empty state inside a tab pane (e.g. "No active sessions today"). */.pm-empty { padding: 32px 24px; text-align: center; color: var(--grey10); }.pm-empty-ic { width: 48px; height: 48px; border-radius: 50%; background: var(--grey2); color: var(--grey10); display: inline-flex; align-items: center; justify-content: center; margin-bottom: 10px; }.pm-empty-ic svg { width: 22px; height: 22px; }.pm-empty h3 { font-size: 14px; font-weight: 600; color: var(--grey11); margin-bottom: 4px; }.pm-empty p { font-size: 12.5px; max-width: 320px; margin: 0 auto; }Generated by emptyTabHtml(tab):
<div class="pm-empty"> <span class="pm-empty-ic"><svg …box…></svg></span> <h3>No active sessions</h3> <p>When a shipment arrives, start a session from the FAB.</p></div>Per-tab copy pairs (verbatim): active → “No active sessions” / “When a shipment arrives, start a session from the FAB.”; today → “Nothing closed today yet” / “Closed sessions land here after reconcile.”; scheduled → “No scheduled sessions” / “Schedule a session from the FAB when a supplier confirms a dispatch.”; past → “No past sessions” / “Historical sessions appear here once they age past today.” A second empty state (all-scanned, #scan-sheet-empty) uses a 56px tinted green circle + check + title 15/600 + sub 13 grey10, built with inline styles inside the scan sheet.
3.15 Icon-container convention
Section titled “3.15 Icon-container convention”Two families, strictly separated:
- Circular (
border-radius: 50%) = status/action/person containers. Always tinted background + colored stroke glyph (tint ≈8–10% rgba of the tone color; glyph in the strong tone color). Sizes seen: 32px (.pm-alert-ic — white bg on tinted row, .pm-linked-ic, .rsm-profile-chev, .pm-scan-target-ic, .pm-log-avatar), 36px (.pm-kpi-ic, .pm-decision-btn-ic, .pm-risk-ic, .rsm-profile-row-ic), 42px (.pm-cosigner-avatar), 44px (.pm-replen-ic, .rsm-page-back, .pm-page-icon-btn, .pm-photo-nav-btn), 48px (.pm-empty-ic), 52px (.rsm-hero-avatar), 56px (photo-stage ic / all-scanned check), 64px (.rsm-profile-avatar). Solid fills appear ONLY on brand avatars (brand green bg + dark initials) and the g-3 avatar variant. - Rounded square (10–14px radius) = entity/session/media marks.
.pm-receiving-id-mark42px/r12,.pm-rcv-hero-mark52px/r14,.pm-po-row-ic40px/r12,.pm-quarantine-thumb52px/r12,.pm-item-ic36px/r10,.pm-photo-thumb(media, r12, aspect 1). These stay on neutral--grey2(or a light tone tint) with the icon stroke carrying the tone: “uniform neutral grey tile, icon stroke carries the state tone.”
Photo evidence grid (media exception to the circular rule — square thumbs, file comment says so explicitly):
/* Photo evidence grid — rounded SQUARE thumbs (media exception to the circular-icon rule). 3 columns at this width. */.pm-photo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }.pm-photo-thumb { aspect-ratio: 1; border-radius: 12px; background: linear-gradient(135deg, var(--grey2), var(--grey3)); position: relative; overflow: hidden; cursor: pointer; border: 0; -webkit-appearance: none; appearance: none; transition: transform 0.1s ease; }.pm-photo-thumb:active { transform: scale(0.985); }.pm-photo-thumb .pm-photo-ic { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: var(--grey9); }.pm-photo-thumb .pm-photo-ic svg { width: 28px; height: 28px; }.pm-photo-thumb .pm-photo-label { position: absolute; bottom: 6px; left: 8px; font-size: 11px; font-weight: 600; color: var(--white); letter-spacing: -0.05em; padding: 2px 8px; border-radius: 100px; background: rgba(0,0,0,0.55); }3.16 Additional specialised atoms (present in file, documented for completeness)
Section titled “3.16 Additional specialised atoms (present in file, documented for completeness)”Activity/audit log row (detail screens; avatars are circles “per HARD RULE”):
.pm-log-row { display: grid; grid-template-columns: 32px 1fr; gap: 10px; align-items: flex-start; }.pm-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; }.pm-log-avatar[data-tone="blue"] { background: rgba(30, 64, 175, 0.12); color: #1E40AF; }.pm-log-avatar[data-tone="amber"] { background: #FEF3C7; color: var(--amber9); }.pm-log-text { font-size: 13px; font-weight: 500; color: var(--grey12); line-height: 18px; }.pm-log-text strong { font-weight: 600; }.pm-log-time { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }Progress/quant visuals (replenishment detail cover card):
.pm-cover-top { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }.pm-cover-atp { font-size: 36px; font-weight: 700; color: var(--grey12); letter-spacing: -0.8px; line-height: 1; font-variant-numeric: tabular-nums; }.pm-cover-atp-unit { font-size: 14px; font-weight: 500; color: var(--grey10); margin-top: 6px; }.pm-cover-divider { width: 1px; height: 36px; background: var(--grey4); }.pm-cover-days { display: flex; flex-direction: column; gap: 2px; }.pm-cover-days-num { font-size: 22px; font-weight: 700; color: var(--grey12); letter-spacing: -0.4px; line-height: 1; font-variant-numeric: tabular-nums; }.pm-cover-card[data-tone="red"] .pm-cover-days-num { color: #B91C1C; }.pm-cover-card[data-tone="amber"] .pm-cover-days-num { color: var(--amber9); }.pm-cover-card[data-tone="muted"] .pm-cover-days-num { color: var(--g-dark); }.pm-cover-days-label { font-size: 11px; font-weight: 500; color: var(--grey10); letter-spacing: 0.04em; text-transform: uppercase; }.pm-cover-bar-wrap { position: relative; }.pm-cover-bar { width: 100%; height: 8px; border-radius: 100px; background: var(--grey3); overflow: hidden; position: relative; }.pm-cover-bar-fill { position: absolute; top: 0; left: 0; height: 100%; border-radius: 100px; }.pm-cover-card[data-tone="red"] .pm-cover-bar-fill { background: #B91C1C; }.pm-cover-card[data-tone="amber"] .pm-cover-bar-fill { background: var(--amber9); }.pm-cover-card[data-tone="muted"] .pm-cover-bar-fill { background: var(--g-dark); }.pm-cover-bar-threshold { position: absolute; top: -4px; bottom: -4px; width: 0; border-left: 2px dashed var(--grey9); pointer-events: none; }.pm-cover-bar-legend { display: flex; justify-content: space-between; margin-top: 6px; font-size: 11px; font-weight: 500; color: var(--grey10); }
/* 14-day demand histogram. */.pm-demand-bars { display: grid; grid-template-columns: repeat(14, 1fr); gap: 4px; height: 56px; align-items: end; }.pm-demand-bar { width: 100%; border-radius: 3px; background: var(--grey4); min-height: 2px; }.pm-cover-card[data-tone="red"] .pm-demand-bar { background: rgba(185, 28, 28, 0.22); }.pm-cover-card[data-tone="amber"] .pm-demand-bar { background: rgba(180, 83, 9, 0.20); }.pm-cover-card[data-tone="muted"] .pm-demand-bar { background: rgba(5, 133, 62, 0.22); }.pm-demand-bar.is-today { background: var(--grey9); }.pm-cover-card[data-tone="red"] .pm-demand-bar.is-today { background: #B91C1C; }.pm-cover-card[data-tone="amber"] .pm-demand-bar.is-today { background: var(--amber9); }.pm-cover-card[data-tone="muted"] .pm-demand-bar.is-today { background: var(--g-dark); }
/* Forecast row — next 7 days projected ATP as a horizontal compact list. */.pm-forecast-row { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }.pm-forecast-cell { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 10px 4px; background: var(--grey1); border-radius: 10px; }.pm-forecast-cell[data-state="warn"] { background: rgba(180, 83, 9, 0.06); }.pm-forecast-cell[data-state="danger"] { background: rgba(185, 28, 28, 0.05); }.pm-forecast-day { font-size: 10.5px; font-weight: 600; color: var(--grey10); letter-spacing: 0.04em; text-transform: uppercase; }.pm-forecast-num { font-size: 14px; font-weight: 700; color: var(--grey12); letter-spacing: -0.2px; font-variant-numeric: tabular-nums; line-height: 1; }.pm-forecast-cell[data-state="warn"] .pm-forecast-num { color: var(--amber9); }.pm-forecast-cell[data-state="danger"] .pm-forecast-num { color: #B91C1C; }Scan viewfinder (dark camera mock inside the scan sheet — see lines 722–773 for full CSS): .pm-scan-stage (16/11 aspect, dark gradient, radius 16, green flash ::after via .is-flashing), .pm-scan-reticle + four .pm-scan-corner--tl/tr/bl/br (22px, 2.5px brand borders, 6px corner radii), animated .pm-scan-laser (2px brand gradient line, keyframes pm-scan-laser 2.4s), .pm-scan-target (black 55% pill with icon + name/meta), .pm-scan-stage-hint (“Tap the viewfinder to scan” pill), .pm-scan-stage-foot with .pm-scan-progress-text + .pm-scan-counter (12/700, rgba brand 18% bg, 1px rgba brand 35% border), .pm-scan-remaining-row strip rows (grey1, active row rgba(1,228,111,0.08)).
Photo preview stage (lightbox sheet): .pm-photo-stage (4/3, radius 16, tinted bg from data), .pm-photo-stage-label-text / .pm-photo-stage-counter (black 55% pills), .pm-photo-nav with 44px circular nav buttons (:disabled { opacity: 0.4 }) and dots — active dot stretches to 24px pill:
.pm-photo-nav-dots { display: inline-flex; align-items: center; gap: 6px; }.pm-photo-nav-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--grey4); }.pm-photo-nav-dot.is-active { background: var(--g-primary); width: 24px; border-radius: 100px; }Profile group (.rsm-profile-* — “same rsm-profile-* shape used by Kit Production Manager + the retrofit personas”):
.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; }Note: in THIS file .rsm-profile-row-ic is a 50% circle (the Kit Assembler 10px-rounded-square exception was not carried here).
Auth screens (splash/onboarding/welcome) — brand-dark (--brand-dark #013F1F) full-bleed screens; onboarding = brand row → square hero card (radius 32, dark-green gradient, radial brand glow, caption uppercase) → 36/700 display title with .accent brand-green word → 15/400 tag → full-width primary CTA (max-width 360). Welcome = dark top (headline 32/700 + tag) + white sheet bottom (border-radius: 28px 28px 0 0, shadow 0 -8px 28px rgba(0,0,0,0.16)) holding eyebrow (12.5/600 uppercase --g-dark), title 28/700, sub 14/400, fields, CTA pinned with margin-top: auto. Full CSS at lines 112–167.
4. Page anatomy
Section titled “4. Page anatomy”Screen registry (SCREENS array): s-splash, s-onboarding, s-welcome, s-dashboard, s-receiving, s-receiving-detail, s-quarantine, s-quarantine-detail, s-replenishment, s-replenishment-detail, s-missing, s-profile.
A. Splash (s-splash)
Section titled “A. Splash (s-splash)”Order: dark status bar (absolute) → centered 96px two-tone brand mark → light home indicator. Whole screen is a click target (onclick="goTo('s-onboarding')"); auto-advances after 2500ms.
B. Onboarding (s-onboarding)
Section titled “B. Onboarding (s-onboarding)”Order: dark sb → .ob-col: brand row (mark + “Rechaj”) → hero card → title + tag (“Receive clean. Release ready.”) → primary CTA “Get started” → home-ind. CTA → goTo('s-welcome').
C. Welcome / Login (s-welcome)
Section titled “C. Welcome / Login (s-welcome)”Order: .welcome-left (dark, brand mark row, headline, tag) → .welcome-right (white rounded sheet): eyebrow “Procurement Manager” → “Sign in to continue” → sub → Email field (pre-filled tobi.akinwale@rechaj.ng) → Password field w/ eye toggle + “Forgot password?” → Sign in button (disabled until both fields non-empty via onWelcomeInput()). Login → goTo('s-dashboard').
D. Dashboard (s-dashboard) — the hub
Section titled “D. Dashboard (s-dashboard) — the hub”Composition order inside .pm-scroll:
header.rsm-hero(dark sb + ghost mark + avatar/name/role-pill/meta).pm-kpi-strip— 6 KPI tiles overlapping the hero (margin: -64px 24px 0): Receiving today / In quarantine (amber) / ATP below threshold (red) / Missing unresolved (red, BUTTON →goTo('s-missing')) / Write-offs to co-sign (amber) / Active POs (blue).rsm-body.rsm-body--split(1.2fr / 1fr):- LEFT: section head “Today’s receiving” + “View all” → 3×
.pm-receiving-card→ section head “Scheduled receiving” + “View all” →.pm-po-listof 3.pm-po-row - RIGHT:
.pm-side-card“Replenishment alerts” (+Open link, 2.pm-alert-row) →.pm-side-card“Quarantine queue” (+View all, 2.pm-alert-row) →.pm-side-card“Recent activity” (5.pm-activity-row) Pinned:.rsm-nav(Home active) +.home-ind. Interactions: every card/row/alert is a<button>that deep-opens the matching detail (openReceivingSession(id)/openReplenishment(slug)/openQuarantineItem(id)); section linksgoTo()to list screens. Dashboard also gets a light sb INSIDE the hero (dark variant).
- LEFT: section head “Today’s receiving” + “View all” → 3×
E. List pages (s-receiving, s-quarantine, s-replenishment, s-missing) — one shared template
Section titled “E. List pages (s-receiving, s-quarantine, s-replenishment, s-missing) — one shared template”Order: light .sb → .rsm-page-head with .pm-page-head-row (left title; back button only on s-missing since it’s not a nav tab) → .rsm-body: .pm-tabs filter strip → N pane divs (data-*-pane, one visible, others hidden), each a gap-10/12 stack of row cards → (receiving only) .pm-fab “New session” pinned outside the scroll → .rsm-nav + .home-ind.
- Tab sets: Receiving = Active/Today/Scheduled/Past; Quarantine = Open/In review/Released/Written off; Replenishment = Active/Recovered (2-col tabs); Missing = Unresolved/Resolved/Written off.
- Default selected tab = first (Active/Open/Unresolved),
aria-pressed="true"baked in markup. - Row click →
openX(id): hydrates the detail screen thengoTo('s-x-detail'). Missing rows do NOT drill in — actions are inline buttons on the card (Mark resolved / Write off). - Empty panes get
emptyTabHtml(tab). - Tab switch resets pane scroll to top (
scrollTo({top:0,behavior:'instant'})). - There is no search field on list pages.
F. Detail pages (s-receiving-detail, s-quarantine-detail, s-replenishment-detail)
Section titled “F. Detail pages (s-receiving-detail, s-quarantine-detail, s-replenishment-detail)”Order: light .sb → .rsm-page-head with centered .rsm-page-head-row (round back button + record ID as title) → .rsm-body (receiving: single column, JS-hydrated; quarantine + replenishment: .rsm-body--split two-column) → .pm-detail-cta sticky strip pinned above the nav → .rsm-nav (parent tab stays .is-active) + .home-ind.
- Receiving detail body:
.pm-rcv-hero(mark + id/supplier·PO + status pill; progress bar; 4-cell breakdown Scanned/In stock/Quarantined/Missing) →.pm-panel“Items” with.pm-item-rowreconciliation list. CTA by status: active → [Close session (secondary)] + [Continue scanning (primary)] (primary-only “Close session” when fully scanned); scheduled → [Start session]; closed → CTA hidden. - Quarantine detail: LEFT =
.pm-detail-hero(pill + QTN id, item name, meta,.pm-meta-gridincl.pm-meta-val--linkback to the source RCV) → Evidence.pm-detail-cardw/.pm-photo-grid→ Notes & activity card w/.pm-log-rows. RIGHT = Decision card (.pm-decision-cardoptions injected by state) → Same shipment (.pm-linked-rows) → Identifiers meta-grid. CTA: [Return to supplier (secondary)] + [Release to stock (primary)]; hidden once resolved. - Replenishment detail: LEFT =
.pm-cover-card(name/part + pill; ATP 36/700 + divider + days-of-cover; threshold bar; 14-day demand bars) → Inbound card → Mitigation log. RIGHT = 7-day forecast → At-risk WOs → Item identifiers. CTA: [Open emergency PO (secondary)] + [Open mitigation (primary)]. - Back button returns to the parent LIST (
goTo('s-receiving')etc.), not history.
G. Profile (s-profile)
Section titled “G. Profile (s-profile)”Order: sb → page head (left “Profile” + settings .pm-page-icon-btn) → .rsm-body: .rsm-profile-user card → 4 grouped sections, each = uppercase .rsm-profile-section-title + .rsm-profile-section card of rows: Workspace (receiving bay / suppliers / linked Inventory Manager), Account (details / password / 2FA w/ “On” tail), Preferences (push toggle / dark-appearance toggle / language w/ “English” tail), Support (help / about / Sign out as --danger row → goTo('s-welcome')).
Interaction rules (global)
Section titled “Interaction rules (global)”- Row/card click → hydrate + navigate to a full detail SCREEN. Detail views are never modals or drawers.
- Create/confirm/multi-choice actions → bottom sheets (
.rsm-sheet-overlay.is-open). Backdrop click closes. Confirmations always show a read-only summary tile of what is being acted on. - All destructive/consequential confirmation sheets: primary confirm stacked ABOVE Cancel.
- Every mutation ends with
showToast(...)and re-renders affected panes/counts; unbuilt destinations getshowToast('X', 'Coming soon', 'info')— never a dead click. - Write-off requires a co-signer; battery part numbers (
BAT*) force the Super Admin co-signer label/flow. - Deep-linking:
?screen=s-xURL param jumps past splash; detail screens auto-select their first record so a deep-link never lands blank.
5. JS contracts
Section titled “5. JS contracts”Everything is inline onclick/oninput handlers + global functions. Data lives in plain object maps keyed by ID; “current record” globals hold selection.
Navigation
const SCREENS = [ …12 ids… ]goTo(id)— validates against SCREENS, swaps.active, resets that screen’s.pm-scrollto top.init()IIFE — hydrates ALL panes + counts first, then handles?screen=deep-link (with default-record hydration for detail screens), else auto-advances splash after 2500ms; callsonWelcomeInput().- Second IIFE:
fit()viewport scaler + resize listener.
Auth: onWelcomeInput(), togglePw(inputId, btn), attemptLogin().
Toast: showToast(msg, badge, variant = 'success') / hideToast() — variants success | info | warning | error; auto-hide 3200ms.
Data stores + selection state
RECEIVING_SESSIONSkeyedRCV-DDMMYY-NNN; fields:id, supplier, po, status('active'|'closed'|'scheduled'), statusLabel, startedAt, closedAt, expectedAt, expectedTotal, scanned, inStock, quarantined, missing, items[{name, expected, scanned, inStock, quarantined, missing, serial}];let currentReceivingId = nullQUARANTINE_ITEMSkeyedQTN-DDMMYY-NNN; fields incl.stage('open'|'in-review'|'released'|'written-off'), stageLabel, stagePill, itemName, supplier, reasonText, age, partNumber, photos[], log[], linked[];let currentQuarantineId = nullREPLENISHMENT_ITEMSkeyed by slug ('wiring-harness','differential','battery-bat-44','mcu'…); fields incl.slug, stage('red'|'amber'|'muted'), name, atp, days, leadTime, supplier, demand[], forecast[], inbound[], risk[], log[];let currentReplenSlug = nullMISSING_ITEMSkeyedMIS-DDMMYY-NNN;stage('unresolved'|'resolved'|'written-off'),resolution{actor, at, note},decision{cosigner, cosignerRole, at}let currentWriteOffContext = { type: 'qtn'|'missing', id }— shared write-off sheet context.
Per-domain repeated pattern (×4 domains — receiving/quarantine/replenishment/missing):
- Filter:
receivingByTab(tab),quarantineByStage(stage),replenByStage(stage),missingByStage(stage) - Row template:
receivingCardHtml(s),quarantineRowHtml(q),replenRowHtml(i),missingRowHtml(m),emptyTabHtml(tab) - Pane render:
renderReceivingPane(tab),renderQuarantinePane(stage),renderReplenPane(stage),renderMissingPane(stage) - Count render:
renderReceivingCounts(),renderQuarantineCounts(),renderReplenCounts(),renderMissingCounts() - Tab switch:
setReceivingTab(btn),setQuarantineTab(btn),setReplenTab(btn),setMissingTab(btn)— readbtn.dataset.xTab, fliparia-pressedon siblings, togglehidden+display:flexon panes, scroll pane to top. - Drill-in:
openReceivingSession(id),openQuarantineItem(id),openReplenishment(slug)— guard (showToast(id, 'Not found', 'error')), set current,renderXDetail(),goTo('s-x-detail'). - Detail render:
renderReceivingDetail(),renderQuarantineDetail(q),renderReplenDetail(i)— build innerHTML incl. state-dependent CTA strip.
Sheets — open/close pairs (open = classList.add('is-open') on the overlay, usually preceded by a refresh; close = remove):
openNewSessionSheet()/closeNewSessionSheet(), openScanSheet()/closeScanSheet(), openCloseRcvSheet()/closeCloseSheet(), openStartRcvSheet()/closeStartSheet(), openWriteOffCosignSheet()/closeWriteOffCosignSheet(), openMitigationSheet()/closeMitigationSheet(), openPhotoPreview(idx)/closePhotoPreview().
Sheet internals / actions
- New session:
cycleNewSessionSupplier(),cycleNewSessionPo(),setNewSessionArrival(btn),submitNewSession() - Scan:
refreshScanSheet(),scanCurrentTap(),flashScanStage(),toggleScanFlagTray(),flagDamagedCurrent(reason)(mints a new QTN id:'QTN-' + yymmdd + '-' + seq.padStart(3,'0')),recomputeSessionTotals(s) - Lifecycle confirms:
confirmCloseSession(),confirmStartSession() - Quarantine decisions:
quarantineDecisionOptionsHtml(q),quarantineDecisionResolvedHtml(q, kind),releaseQuarantineItem(),returnToSupplier(),openQuarantineRcvLink() - Write-off:
cycleWriteOffReason(),cycleWriteOffCosigner(),refreshWriteOffSheet(),confirmWriteOff()(branches oncurrentWriteOffContext.type;(q.partNumber || '').startsWith('BAT')→ battery co-sign rules) - Missing:
resolveMissing(id),openMissingWriteOff(id) - Photo preview:
cyclePhotoPreview(step)(±1),refreshPhotoPreview() - Replenishment:
openInboundRef(id),openEmergencyPo(),confirmMitigation(action)where action ∈'substitute' | 'partial-release' | 'park-wo' | 'pull-forward' | 'emergency-po',applyMitigationToItem(i, action)
Tab-switch contract (canonical implementation, verbatim):
function setReceivingTab(btn) { const tab = btn.dataset.receivingTab; if (!tab) return; document.querySelectorAll('#s-receiving .pm-tab').forEach(t => { t.setAttribute('aria-pressed', String(t === btn)); }); document.querySelectorAll('#s-receiving [data-receiving-pane]').forEach(p => { p.hidden = p.dataset.receivingPane !== tab; if (!p.hidden) p.style.display = 'flex'; }); document.querySelector('#s-receiving .pm-scroll')?.scrollTo({ top: 0, behavior: 'instant' });}6. Naming
Section titled “6. Naming”Class prefixes
pm-— Procurement-Manager-specific components (this persona’s namespace). Each persona gets its own prefix; the shell staysrsm-.rsm-— shared tablet scaffolding inherited from the Kit Production Manager shell:rsm-page-head,rsm-page-title,rsm-page-back,rsm-body(--split),rsm-section-*,rsm-nav*,rsm-hero*,rsm-sheet-*,rsm-profile-*,rsm-toggle.sb/sb-*— status bar;ob-*— onboarding;welcome-*— login;btn-*— shared buttons;alert-toast*— toast;home-ind— home indicator;field-*/input*/text-link— auth form.- State classes/attrs:
.is-active(nav),.is-open(sheets),.is-visible(toast),.is-disabled,.is-error,.is-flashing,.is-today,aria-pressed(tabs/segmented/toggles),aria-expanded(disclosure),data-tone(green/amber/red/blue/grey/muted/neutral/success),data-state(pill states; row statesopen/released/warn/ok/danger),data-*-tab/data-*-pane(tab wiring),data-arrival,data-active,data-empty. - ID conventions: screens
s-<name>; sheetssheet-<name>; hydration targets<domain>-detail-<field>(e.g.rcv-detail-title,qtn-detail-supplier,rpl-detail-atp); countspm-tab-count-<tab>/pm-qtn-count-<stage>/pm-rpl-count-<stage>/pm-missing-count-<stage>; panespm-rcv-pane-<tab>etc.
Personas / people
- User: Tobi Akinwale (avatar “TA”), role Procurement Manager,
tobi.akinwale@rechaj.ng(login) /tobi.akinwale@rechaj.com(profile), workspace “Apapa receiving bay”, “Receiving floor · Wed, 18 May 2026”. - Co-signer: Chinedu Adeyemi (“CA”), Inventory Director / Super Admin co-signer.
- Linked Inventory Manager: Olufunke (Funke) Adesina.
- Suppliers: Lagos Auto Hub, Apapa Components Ltd, Trans-Niger Parts, Lekki Hardware Co.
ID formats (visible)
- Receiving session:
RCV-DDMMYY-NNN(e.g.RCV-180526-002= 18 May 2026, seq 002). NOTE: a code comment says “RCV-YYMMDD-NNN” but every actual ID is DDMMYY — follow the data (DDMMYY). - Quarantine:
QTN-DDMMYY-NNN; Missing:MIS-DDMMYY-NNN; Purchase order:PO-2026-00118(PO-YYYY-NNNNN); Lot:LOT-A22-0418; Parts:WH-A1,BAT-44(serialized asBAT-44-22001); reason codes:DMG-CASE; replenishment slugs: kebab-case (wiring-harness,battery-bat-44). - Dates:
Wed 18 May · 8:14 AM/Thu 22 May · AM; middot·is the universal separator in copy (no em dashes).
7. Weight & spacing discipline
Section titled “7. Weight & spacing discipline”Where 700 appears (complete list):
- Display numbers (sanctioned):
.pm-kpi-num28px,.pm-cover-atp36px,.pm-cover-days-num22px,.pm-rcv-breakdown-val18px,.pm-rcv-progress-val17px,.pm-replen-cover-num18px,.pm-forecast-num14px. - Display/auth titles (sanctioned):
.ob-title36px,.welcome-left-headline32px,.welcome-title28px,.ob-brand-name/.welcome-left-mark span22px (brand wordmark). - Clock (sanctioned):
.sb-time15px. - Borderline/violations within the file:
.rsm-nav-badgeis 10.5px/700 and.pm-scan-counteris 12px/700 — small numeric counters at 700 where the system elsewhere uses 600 for counters of the same size (.pm-tab-count,.pm-quarantine-thumb-countare 600). If the rule is read strictly (“700 only for KPI-scale display digits”), these two are the file’s only weight deviations. - Everything else — names, IDs, section titles, buttons, pills, labels — is 600 max; body/meta text is 500; long-form copy 400. This matches the “bold sparingly” canon.
Type scale observed: 10.5 / 11 / 11.5 / 12 / 12.5 / 13 / 13.5 / 14 / 14.5 / 15 / 16 / 17 / 18 / 20 / 22 / 24 / 28 / 32 / 36 px. Half-pixel sizes (10.5–14.5) are canonical for meta/sub text. Negative letter-spacing nearly everywhere (-0.01em body default; -0.05em/-0.1px/-0.2px…-0.8px scaling with size); positive tracking (0.02–0.06em) reserved for uppercase micro-labels and pills. font-variant-numeric: tabular-nums on every numeric ID/count/value.
Spacing rhythm: a 2px-granular scale — 2/3/4/6/8/10/12/14/16/18/20/22/24/28/32. Signature values: page gutter 24px; hero/sb gutter 32px; body section gap 18px; card padding 16–22px; card internal gap 12–14px; list gaps 8/10/12px; icon-text gaps 10–14px; sheet side padding 32px. The file is NOT on a strict 4px grid — 6/10/14/18/22 half-steps are pervasive and deliberate (they appear in the shared rsm- scaffolding too), and odd one-offs exist (pill padding 3px 9px/10px, tab padding 9px 6px, pm-item-count-pill 4px 9px, 3px dots, 5px nav gap, 7px dot margin-top, 9px segmented radius). Treat 24-gutter / 18-section / 14-card-gap / 10-list-gap as the load-bearing rhythm; avoid inventing values outside the observed set. Forbidden 7/11/15px paddings do not appear (7px only as a margin-top optical nudge on .pm-activity-dot).
Radius rhythm: 28 (device) / 24 (sheet top) / 18 (hero cards, KPI, profile user) / 16 (cards/panels/stages) / 14 (list rows) / 12 (nested tiles, tab track, alert rows, thumbs) / 10 (buttons, inputs, micro rows) / 9 (segmented opt) / 8 (tab chip) / 100px|999px|50% (pills, circles).
Tone discipline (restated from file comments — part of the canon): icon-circle tints ~10% rgba; row tints 3.5–8% rgba; completed = neutral card + small green check (.pm-check-pill), never a flooded green card; write-off = muted red TEXT only, never a filled red card; the status pill (not the icon tile) carries state; sub-lines stay neutral grey even when the title takes the tone color.