canon extract prod qa
CANON EXTRACT — Rechaj Kit Production · Production QA flow
Section titled “CANON EXTRACT — Rechaj Kit Production · Production QA flow”Source of truth: /Users/Ridwan/Pictures/Claude/Rechaj 2.0/rechaj-design-system/products/rechaj-kit-production-app/flows/production-qa/index.html (1,824 lines; CSS lines 11–496, markup 497–1182, JS 1183–1822). Everything below is copied verbatim from that file unless marked as a note.
1. Tokens
Section titled “1. Tokens”Verbatim :root block (lines 12–21):
: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 used repeatedly in the file — treat these as de-facto tokens of this canon:
| Purpose | Values |
|---|---|
| Blue status (In progress / In QA / KaaG) | bg #DBEAFE, text #1E40AF |
| Amber status (Pending) | bg #FEF3C7, text #B45309 |
| Red status (Defect / alerts) | bg #FEE2E2, text #B91C1C; red tint rgba(227,38,39,0.06); red outline rgba(227,38,39,0.4) |
| Backdrop / device frame dark | #0E0F12 (page body AND camera screen bg) |
| Tab-screen page background | #F5F5F7 |
| Primary button hover | #00C45B |
| Photo-thumb gradient | linear-gradient(135deg, #1f2230 0%, #2c3140 100%) (evidence) and linear-gradient(135deg, #3a3a3a 0%, #555 100%) (camera) |
| Toast fills | success #dcfbe2/#05853e, info #e6f4fe/#0d74ce, error #FEE2E2/#B91C1C |
| Splash logo mark fill | #0F1D13 (dark two-tone on green disc); onboarding brand mark: #FFFFFF + #01E46F |
Font: 'General Sans', system-ui, -apple-system, sans-serif loaded from Fontshare at weights 400,500,600,700. Global letter-spacing: -0.01em on body. theme-color meta = #01E46F. Viewport = width=390.
2. Shell
Section titled “2. Shell”Dimensions & skeleton
Section titled “Dimensions & skeleton”- Device: 390 × 844, radius 28px, white,
overflow:hidden, centered on a#0E0F12page body (flex-centered,min-height:100vh). No external chrome — device only. - Screens are absolutely-stacked full-bleed layers inside
.phone; exactly one has.active. - Each screen is a column flexbox; only
.rsm-bodyscrolls (scrollbar hidden). Status bar, page head / hero, bottom nav and home indicator are all fixed (flex-shrink:0 or absolute). - Bottom sheets + toast live as siblings of the screens, direct children of
.phone, layered above with z-index 50 / 80.
.phone (390×844, r28, overflow hidden, flex column)├── .screen.active [.tab-screen] ← one per app page, position:absolute inset:0│ ├── .sb ← 44px status bar (fixed)│ ├── header.rsm-page-head | header.rsm-hero ← fixed page head OR dark hero│ ├── [.rsm-stats] ← dashboard only, overlaps hero -52px│ ├── .rsm-body ← THE ONLY SCROLL REGION (flex:1, overflow-y:auto)│ ├── nav.rsm-nav ← 3-tab bottom nav (fixed, tab screens only)│ └── .home-ind ← iOS home indicator (absolute bottom 8px)├── .rsm-sheet-overlay#sheet-* ×5 ← bottom sheets (z-index 50)└── .alert-toast#toast ← toast (z-index 80, top 56px)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;}.phone { position: relative; width: 390px; height: 844px; 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; }.screen-enter { animation: slide-in 0.3s ease; }@keyframes slide-in { from { transform: translateX(8px); opacity: 0.5; } to { transform: translateX(0); opacity: 1; } }.tab-screen { background: #F5F5F7; }.home-ind { position: absolute; bottom: 8px; left: 50%; width: 134px; 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 */.sb { height: 44px; padding: 14px 24px 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: 5px; }.sb-icons svg { display: block; }.sb--dark .sb-time, .sb--dark .sb-icons svg path, .sb--dark .sb-icons svg rect { color: var(--white); fill: var(--white); }.rsm-body { flex: 1; overflow-y: auto; padding: 0 16px 24px; display: flex; flex-direction: column; gap: 18px; }.rsm-body::-webkit-scrollbar { display: none; }.rsm-body > * { flex-shrink: 0; }[hidden] { display: none !important; }Scroll model rules:
.rsm-bodyis the single scroll container; children getgap: 18pxrhythm andflex-shrink:0.- App pages (dashboard, queue, detail, gates, profile) use
.screen.tab-screen→ grey#F5F5F7page so white cards separate from the page (never white-on-white). - Auth pages (splash/onboarding/welcome) are non-tab screens on
--brand-dark. - On navigation,
goTo()resets.rsm-bodyscrollTop to 0 and plays.screen-enterslide-in. - Interior pages give
.rsm-bodyinlinestyle="padding-top: 4px;"; the dashboard usesstyle="padding-top: 16px;"(below the overlapping stat cards). - Status bar has two skins: dark screens use
.sb.sb--darkwith white#FFFFFFSVG fills; light screens use plain.sbwith#202020SVG fills. Time is always9:41.
3. Atoms
Section titled “3. Atoms”3.1 Bottom nav (.rsm-nav) — this app has NO sidebar; 3-column fixed bottom tab bar
Section titled “3.1 Bottom nav (.rsm-nav) — this app has NO sidebar; 3-column fixed bottom tab bar”/* Bottom nav — 3-col */.rsm-nav { display: grid; grid-template-columns: repeat(3, 1fr); background: var(--white); border-top: 1px solid var(--grey4); padding: 6px 8px 6px; flex-shrink: 0; position: relative; z-index: 1; }.rsm-nav-tab { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 6px 4px 4px; background: transparent; border: 0; cursor: pointer; font-family: inherit; font-size: 10.5px; font-weight: 500; color: var(--grey10); -webkit-appearance: none; appearance: none; transition: color 0.18s ease; }.rsm-nav-tab svg { width: 22px; height: 22px; }.rsm-nav-tab.is-active { color: var(--g-dark); font-weight: 600; }.rsm-nav-tab.is-active svg { color: var(--g-dark); }<nav class="rsm-nav" aria-label="Primary"> <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 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg><span>Home</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-qa-queue')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M9 14l2 2 4-4"/></svg><span>QA Queue</span></button> <button class="rsm-nav-tab" type="button" onclick="goTo('s-profile')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg><span>Profile</span></button></nav>Usage: appears only on the three top-level tab screens (Home / QA Queue / Profile); drill-in screens (QA detail, G2, G3) have NO bottom nav — they use the back button instead. Active state is is-active (dark-green --g-dark text+icon, weight bumps 500→600). No badges, no group labels, no collapse behavior. Nav is duplicated per screen with the matching tab pre-marked active (state is not JS-toggled).
3.2 Topbar / header — TWO variants
Section titled “3.2 Topbar / header — TWO variants”Variant A — Dashboard hero (.rsm-hero): dark-green brand header with avatar, name, role pill, meta line, faded brand-mark background art, plus (CSS-available) white notification bell.
/* Hero */.rsm-hero { position: relative; background: var(--brand-dark); padding: 4px 16px 64px; flex-shrink: 0; }.rsm-hero-bg { position: absolute; top: 0; right: 0; width: 220px; height: 100%; pointer-events: none; opacity: 0.18; 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: 8px 0 16px; }.rsm-hero-id { display: flex; align-items: center; gap: 12px; min-width: 0; }.rsm-hero-avatar { width: 44px; height: 44px; border-radius: 50%; background: var(--brand); display: flex; align-items: center; justify-content: center; font-size: 16px; 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: 17px; font-weight: 600; color: var(--white); letter-spacing: -0.3px; line-height: 22px; }.rsm-hero-meta { font-size: 12px; font-weight: 400; color: rgba(255,255,255,0.6); line-height: 16px; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.rsm-hero-role { display: inline-block; font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 100px; background: rgba(1,228,111,0.16); color: var(--g-primary); letter-spacing: 0.02em; margin: 4px 0 2px; }.rsm-hero-bell { position: relative; width: 40px; height: 40px; border-radius: 50%; background: var(--white); border: 0; color: var(--text-main); display: flex; align-items: center; justify-content: center; cursor: pointer; -webkit-appearance: none; appearance: none; box-shadow: var(--sh-card); }.rsm-hero-bell svg { width: 18px; height: 18px; }.rsm-hero-bell-dot { position: absolute; top: 8px; right: 9px; width: 8px; height: 8px; border-radius: 50%; background: var(--red9); border: 2px solid var(--white); }<header class="rsm-hero"> <div class="sb sb--dark"> … </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">AN</span> <div class="rsm-hero-text"> <p class="rsm-hero-name">Adaeze</p> <span class="rsm-hero-role">Production QA</span> <p class="rsm-hero-meta">QA Mode · Wed, 14 May 2026</p> </div> </div> </div></header>Usage: dashboard only. padding-bottom: 64px reserves the strip the stat cards overlap into (.rsm-stats pulls up -52px). The bell (.rsm-hero-bell + .rsm-hero-bell-dot) is defined in CSS but not rendered in this persona’s markup.
Variant B — Interior page head (.rsm-page-head): centered title, optional circular back button on the left.
/* Page head */.rsm-page-head { background: transparent; padding: 0 16px 16px; flex-shrink: 0; }.rsm-page-head-row { position: relative; height: 56px; display: flex; align-items: center; justify-content: center; padding: 0 20px; margin: 0 -16px; }.rsm-page-title { font-size: 20px; font-weight: 600; color: var(--grey12); letter-spacing: -0.3px; flex: 1; text-align: center; }.rsm-page-back { position: absolute; left: 20px; width: 40px; height: 40px; 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; }<header class="rsm-page-head"> <div class="rsm-page-head-row"> <button class="rsm-page-back" onclick="goTo(qaReturnScreen)" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg></button> <h1 class="rsm-page-title">QA detail</h1> </div></header>Usage: every non-dashboard app page. Top-level tab pages (QA Queue, Profile) omit the back button; drill-ins (QA detail, G2 Visual, G3 Electrical) include it. There is NO search field and NO user pill in the topbar in this app.
3.3 Page head — section heads inside the body
Section titled “3.3 Page head — section heads inside the body”/* Section heads */.rsm-section-head { display: flex; align-items: center; justify-content: space-between; padding: 4px 0; }.rsm-section-title { font-size: 15px; font-weight: 600; letter-spacing: -0.2px; }.rsm-section-meta { font-size: 12px; font-weight: 500; color: var(--grey11); }<div class="rsm-section-head"> <h2 class="rsm-section-title">Currently inspecting</h2> <span class="rsm-section-meta">G2 in progress</span></div>Plus uppercase day-group heads inside list panes:
/* Day section heads */.rsm-q-day-head { display: flex; align-items: baseline; justify-content: space-between; padding: 12px 0 6px; font-size: 12px; font-weight: 600; color: var(--grey11); letter-spacing: 0.04em; text-transform: uppercase; }.rsm-q-day-head:first-child { padding-top: 4px; }.rsm-q-day-count { font-weight: 500; color: var(--grey10); letter-spacing: 0; text-transform: none; }<div class="rsm-q-day-head"><span>Awaiting QA · Wed 14 May</span><span class="rsm-q-day-count">2 kits</span></div>Usage: .rsm-section-head = title left + meta right, sits directly in .rsm-body between cards (dashboard). .rsm-q-day-head = uppercase date bucket above .rsm-q-list groups (queue panes).
3.4 Tabs & segmented controls — ONE variant only: .km-tabs pill segmented
Section titled “3.4 Tabs & segmented controls — ONE variant only: .km-tabs pill segmented”/* Tabs — PM .km-tabs pattern */.km-tabs { display: flex; gap: 4px; padding: 4px; background: var(--grey2); border-radius: 12px; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }.km-tabs::-webkit-scrollbar { display: none; }.km-tab { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 5px; padding: 8px 12px; background: transparent; border: 0; border-radius: 8px; cursor: pointer; font-family: inherit; font-size: 12px; font-weight: 500; color: var(--grey11); -webkit-appearance: none; appearance: none; transition: background 0.15s ease, color 0.15s ease; white-space: nowrap; }.km-tab:hover { color: var(--grey12); }.km-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); }.km-tab-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; background: var(--grey3); color: var(--grey11); font-size: 10px; font-weight: 600; line-height: 1; }.km-tab[aria-pressed="true"] .km-tab-count { background: var(--g-3); color: var(--g-dark); }[data-qa-pane][hidden] { display: none; }<div class="km-tabs" role="tablist"> <button class="km-tab" type="button" aria-pressed="true" data-qa-tab="pending" onclick="setQaTab(this)">Pending <span class="km-tab-count">2</span></button> <button class="km-tab" type="button" aria-pressed="false" data-qa-tab="in-progress" onclick="setQaTab(this)">In progress <span class="km-tab-count">1</span></button> <button class="km-tab" type="button" aria-pressed="false" data-qa-tab="completed" onclick="setQaTab(this)">Completed <span class="km-tab-count">3</span></button></div>…<div data-qa-pane="pending"> … </div><div data-qa-pane="in-progress" hidden> … </div><div data-qa-pane="completed" hidden> … </div>Usage: status filter at the top of the QA Queue list page. Grey --grey2 track (r12, 4px padding) with white raised active pill (r8, weight 600, green text, subtle shadow) + per-tab count chips. Selection = aria-pressed; panes = sibling div[data-qa-pane] toggled via hidden. No line tabs, no other segmented variant exists in this file.
3.5 List system — queue cards (mobile list-card equivalent)
Section titled “3.5 List system — queue cards (mobile list-card equivalent)”Wrapper + card:
/* Queue list cards */.rsm-q-list { display: flex; flex-direction: column; gap: 14px; }.rsm-q-row { display: flex; flex-direction: column; background: var(--white); border: 0; border-radius: 16px; box-shadow: var(--sh-card); padding: 14px 14px 6px; width: 100%; text-align: left; cursor: pointer; font-family: inherit; -webkit-appearance: none; appearance: none; transition: transform 0.12s ease, box-shadow 0.18s ease; }.rsm-q-row:active { transform: scale(0.995); }.rsm-q-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--grey3); }.rsm-q-card-id-row { display: flex; align-items: center; gap: 10px; min-width: 0; }.rsm-q-card-icon { width: 36px; height: 36px; border-radius: 50%; background: var(--grey3); display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--grey12); }.rsm-q-card-icon svg { width: 16px; height: 16px; }.rsm-q-card-icon--kaag { background: #DBEAFE; color: #1E40AF; font-size: 10px; font-weight: 600; }.rsm-q-card-icon--std { background: var(--g-3); color: var(--g-dark); font-size: 10.5px; font-weight: 600; }.rsm-q-card-id-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }.rsm-q-card-id { font-size: 14px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; line-height: 18px; font-variant-numeric: tabular-nums; }.rsm-q-card-type { font-size: 11.5px; font-weight: 500; color: var(--grey10); line-height: 14px; }.rsm-q-card-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; min-width: 0; }.rsm-q-card-time { font-size: 10.5px; font-weight: 500; color: var(--grey10); line-height: 13px; text-align: right; font-variant-numeric: tabular-nums; }/* Status pill — Pending = amber, In progress = blue, Pass = green, Defect = red */.rsm-q-card-status { font-size: 10px; font-weight: 600; padding: 2px 9px; border-radius: 100px; flex-shrink: 0; }.rsm-q-card-status--pending { background: #FEF3C7; color: #B45309; }.rsm-q-card-status--in-progress { background: #DBEAFE; color: #1E40AF; }.rsm-q-card-status--pass { background: var(--g-3); color: var(--g-dark); }.rsm-q-card-status--defect { background: #FEE2E2; color: #B91C1C; }.rsm-q-card-rows { display: flex; flex-direction: column; padding: 2px 0 4px; }.rsm-q-card-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--grey3); min-width: 0; }.rsm-q-card-row:last-child { border-bottom: 0; }.rsm-q-card-row-label { font-size: 12px; font-weight: 500; color: var(--grey10); flex-shrink: 0; }.rsm-q-card-row-value { font-size: 12.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }<button class="rsm-q-row" type="button" onclick="openQaDetail('q3-0192','s-qa-queue')"> <div class="rsm-q-card-head"> <div class="rsm-q-card-id-row"> <span class="rsm-q-card-icon rsm-q-card-icon--kaag">KaaG</span> <div class="rsm-q-card-id-text"><p class="rsm-q-card-id">Kit 3 of WO-K-2407-0192</p><p class="rsm-q-card-type">KaaG kit · BOM v2 KaaG · from Yusuf</p></div> </div> <div class="rsm-q-card-meta"><span class="rsm-q-card-time">Submitted 11:12 AM</span><span class="rsm-q-card-status rsm-q-card-status--pending">Pending</span></div> </div> <div class="rsm-q-card-rows"><div class="rsm-q-card-row"><span class="rsm-q-card-row-label">Gates</span><span class="rsm-q-card-row-value">G1 passed · awaiting G2 + G3</span></div></div></button>Usage: the entire card is a <button> — the whole row is the tap target; :active compresses to 0.995. Anatomy = head (36px circular type icon + id/type text + right column of timestamp over status pill), hairline --grey3 divider, then key/value rows (grey 500 label left, dark 600 tabular value right). No search, no pagination, no footer — grouping is done by .rsm-q-day-head day buckets and .km-tabs panes. Card body has asymmetric padding 14px 14px 6px because the last kv-row supplies its own bottom padding.
Secondary list rows inside panels (.km-serial-row — grey inset rows, defined for parity with PM/assembler flows):
/* Component serial / summary rows — PM .km-serial-row */.km-serial-list { display: flex; flex-direction: column; gap: 6px; }.km-serial-row { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; padding: 10px 12px; background: var(--grey2); border-radius: 10px; }.km-serial-row-text { min-width: 0; }.km-serial-row-name { font-size: 12.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }.km-serial-row-val { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; font-variant-numeric: tabular-nums; }.km-serial-row-pill { display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border-radius: 100px; background: var(--g-3); color: var(--g-dark); font-size: 10.5px; font-weight: 600; letter-spacing: 0.02em; flex-shrink: 0; }.km-serial-row-pill svg { width: 11px; height: 11px; }And the checklist/step rows (.km-sop-row family) used for gates and sub-checks:
/* Sub-check rows — QA G2/G3 drill-in (PM .km-sop-row family) */.km-sop-list { display: flex; flex-direction: column; gap: 6px; }.km-sop-row { display: grid; grid-template-columns: 28px 1fr auto; gap: 10px; align-items: center; padding: 10px 12px; background: var(--grey2); border-radius: 10px; border: 0; text-align: left; font-family: inherit; width: 100%; -webkit-appearance: none; appearance: none; }.km-sop-row--active { cursor: pointer; outline: 1.5px solid var(--g-3); outline-offset: -1.5px; transition: background 0.15s ease; }.km-sop-row--active:hover { background: var(--grey3); }.km-sop-row--tappable { cursor: pointer; transition: background 0.15s ease, transform 0.1s ease; }.km-sop-row--tappable:hover { background: var(--grey3); }.km-sop-row--tappable:active { transform: scale(0.997); }.km-sop-row--locked { opacity: 0.55; }.km-sop-check { width: 26px; height: 26px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; background: var(--g-3); color: var(--g-dark); font-size: 12px; font-weight: 600; flex-shrink: 0; }.km-sop-check svg { width: 13px; height: 13px; }.km-sop-check--todo { background: var(--grey3); color: var(--grey10); }.km-sop-check--fail { background: #FEE2E2; color: #B91C1C; }.km-sop-text { min-width: 0; }.km-sop-name { font-size: 13px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }.km-sop-sub { font-size: 11.5px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.km-sop-time { font-size: 11.5px; font-weight: 500; color: var(--grey10); font-variant-numeric: tabular-nums; }.km-sop-chev { width: 16px; height: 16px; color: var(--grey9); }Representative markup (JS-generated, renderGate()):
<button class="km-sop-row km-sop-row--tappable" type="button" onclick="openSubcheck(${i})"> <span class="km-sop-check km-sop-check--todo">${i + 1}</span> <div class="km-sop-text"><p class="km-sop-name">${c[0]}</p><p class="km-sop-sub">${c[1]}</p></div> <svg class="km-sop-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg></button>Usage: 3-column grid (26px state disc / text / trailing status word or chevron) on grey --grey2 r10 inset rows. State disc: green check = done, grey number = todo, red X = fail; --locked = 0.55 opacity for gates not yet unlocked. Trailing status text is colored inline: style="color:var(--g-dark);" for Pass, style="color:#B91C1C;" for Fail/Defect.
3.6 Buttons — every variant
Section titled “3.6 Buttons — every variant”Primary (brand green, dark text — NEVER white-on-green):
.btn-primary { display: flex; align-items: center; justify-content: center; width: 100%; height: 48px; background: var(--brand); border: 1.5px solid var(--brand); border-radius: 8px; 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: 52px; font-size: 16px; }Secondary (outline):
.btn-secondary { display: flex; align-items: center; justify-content: center; width: 100%; height: 48px; background: transparent; border: 1.5px solid var(--grey5); border-radius: 8px; 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: 52px; font-size: 16px; }Danger (red outline ghost, NOT filled red):
/* Danger button — log a defect */.btn-danger { display: flex; align-items: center; justify-content: center; width: 100%; height: 48px; background: transparent; border: 1.5px solid rgba(227,38,39,0.4); border-radius: 8px; font-family: inherit; font-size: 16px; font-weight: 600; color: var(--red9); 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-danger:hover { background: rgba(227,38,39,0.06); }.btn-danger:active { transform: scale(0.98); }.btn-danger:disabled, .btn-danger.is-disabled { border-color: var(--grey5); color: var(--grey9); background: transparent; cursor: not-allowed; pointer-events: none; }.btn-danger--lg { height: 52px; }<button class="btn-primary btn-primary--lg is-disabled" id="subcheck-pass-btn" disabled type="button" onclick="markSubcheckPass()">Mark pass</button><button class="btn-danger btn-danger--lg is-disabled" id="subcheck-defect-btn" disabled type="button" onclick="markSubcheckDefect()">Log defect</button><button class="btn-secondary btn-secondary--lg" type="button" onclick="closeDefectSheet()">Cancel</button>Spec summary: full-width, 48px default / 52px --lg, radius 8px, 16px/600 label, 1.5px border on all variants, scale(0.98) on press. Disabled = both disabled attribute AND .is-disabled class (belt-and-braces; JS toggles both). Danger is used for “Log defect” / “Route back to assembler” actions.
Icon buttons (all circular):
| Selector | Size | Skin |
|---|---|---|
.rsm-page-back |
40px | white, 1px --grey6 border, --grey12 glyph |
.rsm-hero-bell |
40px | white on dark hero, --sh-card shadow, red 8px dot |
.input-icon |
20px | transparent, --grey10 glyph (inside inputs) |
.rt-capture-thumb-del |
22px | rgba(0,0,0,0.6) scrim, white glyph, absolute top-right of thumb |
.ka-camera-back |
36px | rgba(255,255,255,0.12) on camera dark |
.ka-shutter |
72px | white disc, 4px rgba(255,255,255,0.22) ring |
.alert-toast-close |
— | borderless, currentColor at 0.55 opacity |
.welcome-faceid |
44px | transparent, --grey12 glyph, hovers to --g-dark |
One rectangular exception: .ka-camera-done (camera confirm) — brand-green, r10, 44px height, min-width: 84px:
.ka-camera-done { background: var(--brand); color: var(--text-main); border: 0; border-radius: 10px; padding: 0 18px; height: 44px; min-width: 84px; font-family: inherit; font-weight: 600; font-size: 14px; cursor: pointer; -webkit-appearance: none; appearance: none; }.ka-camera-done.is-disabled { background: rgba(255,255,255,0.18); color: rgba(255,255,255,0.55); pointer-events: none; }There is also a text link atom:
.field-aside { text-align: right; margin-top: 8px; }.text-link { font-size: 14px; font-weight: 600; color: var(--g-dark); text-decoration: none; cursor: pointer; }3.7 Badges & status pills
Section titled “3.7 Badges & status pills”All status pills in this canon are fully-rounded (border-radius: 100px), 10–11px, weight 600 tonal pills. Four-color status system used consistently everywhere:
| State | Background | Text |
|---|---|---|
| pending | #FEF3C7 |
#B45309 |
| in-progress | #DBEAFE |
#1E40AF |
| pass | var(--g-3) #B5F1C2 |
var(--g-dark) #05853E |
| defect / fail | #FEE2E2 |
#B91C1C |
| completed (neutral) | var(--grey3) |
var(--grey11) |
Variant inventory (all verbatim CSS above/below):
.rsm-q-card-status+--pending/--in-progress/--pass/--defect— 10px/600,padding: 2px 9px(queue cards)..km-wo-pill+[data-state=pending|in-progress|pass|defect|completed]— 10.5px/600,padding: 3px 9px(detail hero):
/* Status pill — PM .km-wo-pill mapping */.km-wo-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; }.km-wo-pill[data-state="pending"] { background: #FEF3C7; color: #B45309; }.km-wo-pill[data-state="in-progress"] { background: #DBEAFE; color: #1E40AF; }.km-wo-pill[data-state="pass"] { background: var(--g-3); color: var(--g-dark); }.km-wo-pill[data-state="defect"] { background: #FEE2E2; color: #B91C1C; }.km-wo-pill[data-state="completed"] { background: var(--grey3); color: var(--grey11); }.rt-active-status(+--running) — active-card pill, default blue#DBEAFE/#1E40AF, running = green..rsm-hero-role— hero role pill,rgba(1,228,111,0.16)bg +--g-primarytext on dark..rsm-profile-role-pill— light-green--g-2bg +--g-dark, 11px/600,padding: 3px 10px..ka-evi-result+[data-state=done|pass|fail]— evidence sheet result pill, 11px/600,padding: 4px 11px..km-serial-row-pill— green pill w/ 11px icon inside serial rows..km-tab-count— 16px-tall count chip inside tabs (r8, not fully round).
Status-dot pattern — .km-gate-dot (G1/G2/G3 gate progress):
/* G-gate dots — PM .km-gate-dot */.km-gate-dots { display: inline-flex; align-items: center; gap: 4px; }.km-gate-dot { width: 20px; height: 20px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 9px; font-weight: 700; background: var(--grey3); color: var(--grey11); }.km-gate-dot[data-state="pass"] { background: var(--g-3); color: var(--g-dark); }.km-gate-dot[data-state="active"] { background: #DBEAFE; color: #1E40AF; }.km-gate-dot[data-state="fail"] { background: #FEE2E2; color: #B91C1C; }.km-gate-dot svg { width: 11px; height: 11px; }JS builder (pass renders a check SVG, fail an X, active/pending the gate label):
function _gateDot(label, state) { if (state === 'pass') return `<span class="km-gate-dot" data-state="pass">${_checkSvg}</span>`; if (state === 'fail') return `<span class="km-gate-dot" data-state="fail">${_xSvg}</span>`; if (state === 'active') return `<span class="km-gate-dot" data-state="active">${label}</span>`; return `<span class="km-gate-dot">${label}</span>`;}Note: this app’s pills deviate from the web .ds-badge spec (12px/500/6px radius) — mobile canon here is fully-round 10–11px/600 pills. Follow THIS file for 390px flows.
3.8 KPI / stat cards
Section titled “3.8 KPI / stat cards”/* Stats — 2 cards overlapping hero by 52px */.rsm-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: -52px 16px 0; position: relative; z-index: 2; }.rsm-stat { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 14px; display: flex; align-items: center; gap: 10px; min-width: 0; }.rsm-stat-ic { width: 36px; height: 36px; border-radius: 50%; background: var(--g-2); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.rsm-stat-ic svg { width: 18px; height: 18px; color: var(--g-primary); }.rsm-stat-ic--alert { background: #FEE2E2; }.rsm-stat-ic--alert svg { color: #B91C1C; }.rsm-stat-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }.rsm-stat-label { font-size: 12px; color: var(--grey11); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.rsm-stat-num { font-size: 20px; font-weight: 700; letter-spacing: -0.4px; line-height: 26px; display: flex; align-items: baseline; gap: 2px; color: var(--grey12); }.rsm-stat-num-of { font-size: 13px; font-weight: 500; color: var(--grey10); }<div class="rsm-stats" role="group" aria-label="Today overview"> <div class="rsm-stat"> <span class="rsm-stat-ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></span> <div class="rsm-stat-text"> <p class="rsm-stat-label">Passed today</p> <p class="rsm-stat-num"><span>5</span><span class="rsm-stat-num-of">kits</span></p> </div> </div> <div class="rsm-stat"> <span class="rsm-stat-ic rsm-stat-ic--alert"><svg …>…</svg></span> <div class="rsm-stat-text"> <p class="rsm-stat-label">Defects today</p> <p class="rsm-stat-num"><span>1</span><span class="rsm-stat-num-of">kit</span></p> </div> </div></div>Usage: exactly 2 cards in a 1fr/1fr grid, pulled up over the hero by margin-top: -52px (hero reserves 64px). Icon = 36px circle, tonal (light tint bg + strong colored stroke glyph). Value = 20px/700 digits with a 13px/500 grey unit suffix. No delta/trend element in this canon.
3.9 Cards / panels
Section titled “3.9 Cards / panels”NO card in this file has a border. Every card = white --white bg + box-shadow: var(--sh-card) + 16/18px radius. The only borders anywhere: bottom-nav border-top: 1px solid var(--grey4) (chrome divider), internal hairline row dividers 1px solid var(--grey3), input borders, the dashed photo-add tile, and 1.5px button outlines — never on a card container.
Radius/padding scale: outer cards r16–r18, padding 16px (queue card 14px 14px 6px); grey inset rows inside cards r10–r12, padding 10–14px, bg --grey2.
Generic panel:
/* Panel — PM .km-panel */.km-panel { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 16px; display: flex; flex-direction: column; gap: 12px; }.km-panel-head { display: flex; align-items: center; justify-content: space-between; }.km-panel-title { font-size: 14px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; }.km-panel-meta { font-size: 12px; font-weight: 500; color: var(--grey11); }<div class="km-panel"> <div class="km-panel-head"> <h2 class="km-panel-title">Assembler evidence</h2> <span class="km-panel-meta">Submitted ${q.submittedAt}</span> </div> <div class="ka-check-row"> … </div></div>Detail hero card:
/* Kit hero card — PM .km-wo-hero-card, tuned for 390px */.km-wo-hero-card { background: var(--white); border-radius: 18px; box-shadow: var(--sh-card); padding: 16px; display: flex; flex-direction: column; gap: 16px; }.km-wo-hero-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }.km-wo-hero-id { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }.km-wo-hero-mark { width: 46px; height: 46px; border-radius: 12px; background: var(--g-3); color: var(--g-dark); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 12px; letter-spacing: -0.05em; flex-shrink: 0; }.km-wo-hero-mark--kaag { background: #DBEAFE; color: #1E40AF; }.km-wo-hero-id-text { min-width: 0; }.km-wo-hero-id-val { font-size: 15px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; font-variant-numeric: tabular-nums; }.km-wo-hero-id-sub { font-size: 12px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.km-wo-hero-progress { display: flex; flex-direction: column; gap: 6px; }.km-wo-hero-progress-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }.km-wo-hero-progress-label { font-size: 12px; color: var(--grey11); }.km-wo-hero-progress-val { font-size: 15px; font-weight: 700; color: var(--grey12); letter-spacing: -0.2px; font-variant-numeric: tabular-nums; }.km-wo-hero-progress-bar { display: block; height: 8px; border-radius: 100px; background: var(--grey2); overflow: hidden; }.km-wo-hero-progress-fill { display: block; height: 100%; background: var(--g-primary); border-radius: 100px; transition: width 0.3s ease; }.km-wo-hero-meta { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }.km-wo-hero-meta-cell { display: flex; flex-direction: column; gap: 2px; }.km-wo-hero-meta-label { font-size: 10px; font-weight: 500; color: var(--grey10); letter-spacing: 0.04em; text-transform: uppercase; }.km-wo-hero-meta-val { font-size: 12.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }Rendered markup (from renderQaDetail()):
<div class="km-wo-hero-card"> <div class="km-wo-hero-head"> <div class="km-wo-hero-id"> <span class="km-wo-hero-mark">STD</span> <div class="km-wo-hero-id-text"> <p class="km-wo-hero-id-val">Kit 5 of WO-K-2407-0188</p> <p class="km-wo-hero-id-sub">Standard kit · BOM v4</p> </div> </div> <span class="km-wo-pill" data-state="in-progress">In QA</span> </div> <div class="km-wo-hero-progress"> <div class="km-wo-hero-progress-row"> <span class="km-wo-hero-progress-label">QA gates</span> <span class="km-gate-dots">…gate dots…</span> </div> </div> <div class="km-wo-hero-meta"> <div class="km-wo-hero-meta-cell"><span class="km-wo-hero-meta-label">Assembler</span><span class="km-wo-hero-meta-val">Muiz Olawale</span></div> <div class="km-wo-hero-meta-cell"><span class="km-wo-hero-meta-label">Submitted</span><span class="km-wo-hero-meta-val">11:48 AM</span></div> <div class="km-wo-hero-meta-cell"><span class="km-wo-hero-meta-label">Components</span><span class="km-wo-hero-meta-val">12 scanned</span></div> </div></div>Dashboard active-work card (whole card = button):
/* Active QA card on dashboard */.rt-active-card { background: var(--white); border-radius: 18px; box-shadow: var(--sh-card); padding: 16px; display: flex; flex-direction: column; gap: 12px; cursor: pointer; border: 0; text-align: left; font-family: inherit; width: 100%; -webkit-appearance: none; appearance: none; }.rt-active-card:active { transform: scale(0.998); }.rt-active-head { display: flex; align-items: center; gap: 12px; }.rt-active-icon { width: 44px; height: 44px; border-radius: 12px; background: var(--g-3); color: var(--g-dark); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 13px; flex-shrink: 0; letter-spacing: -0.2px; }.rt-active-icon--kaag { background: #DBEAFE; color: #1E40AF; }.rt-active-meta { flex: 1; min-width: 0; }.rt-active-id { font-size: 14px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; font-variant-numeric: tabular-nums; }.rt-active-sub { font-size: 12px; font-weight: 500; color: var(--grey10); margin-top: 2px; }.rt-active-status { font-size: 10.5px; font-weight: 600; padding: 3px 10px; border-radius: 100px; background: #DBEAFE; color: #1E40AF; flex-shrink: 0; }.rt-active-status--running { background: var(--g-3); color: var(--g-dark); }.rt-progress-bar { height: 6px; border-radius: 100px; background: var(--grey3); overflow: hidden; }.rt-progress-fill { height: 100%; background: var(--g-primary); border-radius: 100px; transition: width 0.3s ease; }.rt-active-step-meta { display: flex; align-items: center; justify-content: space-between; font-size: 12px; color: var(--grey11); font-weight: 500; }<button class="rt-active-card" type="button" onclick="openQaDetail('q5-0188','s-dashboard')"> <div class="rt-active-head"> <span class="rt-active-icon">STD</span> <div class="rt-active-meta"> <p class="rt-active-id">Kit 5 of WO-K-2407-0188</p> <p class="rt-active-sub">Standard kit · BOM v4 · from Muiz</p> </div> <span class="rt-active-status">In QA</span> </div> <div class="rt-progress-bar"><div class="rt-progress-fill" style="width: 30%;"></div></div> <div class="rt-active-step-meta"> <span>G2 Visual · 1 of 4 sub-checks</span> <span>G3 pending</span> </div></button>Profile grouped-list card:
/* Profile tab */.rsm-profile-user { background: var(--white); border-radius: 16px; box-shadow: var(--sh-card); padding: 16px; display: flex; align-items: center; gap: 12px; 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: 56px; height: 56px; border-radius: 50%; background: var(--brand); display: flex; align-items: center; justify-content: center; font-size: 20px; 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: 15px; font-weight: 600; color: var(--grey12); letter-spacing: -0.2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.rsm-profile-meta { font-size: 12px; color: var(--grey11); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.rsm-profile-chev { width: 28px; height: 28px; 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: 14px; height: 14px; }.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: 12px; width: 100%; padding: 14px 16px; background: transparent; border: 0; text-align: left; cursor: pointer; font-family: inherit; -webkit-appearance: none; appearance: none; position: relative; }.rsm-profile-row + .rsm-profile-row::before { content: ''; position: absolute; left: 60px; right: 16px; top: 0; border-top: 1px solid var(--grey3); }.rsm-profile-row-ic { width: 32px; height: 32px; border-radius: 10px; 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-section-title { font-size: 12px; font-weight: 600; color: var(--grey10); letter-spacing: 0.06em; text-transform: uppercase; padding: 0 4px 6px; }.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-row-tail { display: flex; align-items: center; gap: 6px; font-size: 12px; 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: #FEE2E2; color: #B91C1C; }.rsm-profile-row--danger .rsm-profile-row-label { color: #B91C1C; }<div> <p class="rsm-profile-section-title">Workspace</p> <div class="rsm-profile-section"> <button class="rsm-profile-row" type="button"> <span class="rsm-profile-row-ic"><svg …>…</svg></span> <span class="rsm-profile-row-label">QA Mode <span class="rsm-profile-row-sub">QA bench · Tricycle Hardware · Wed shift</span> </span> <span class="rsm-profile-row-tail"><svg …>chevron</svg></span> </button> … </div></div>Note: .rsm-profile-row-ic (32px, r10 rounded square) is the documented exception to the circular-icon rule (Kit Assembler precedent). Row dividers are inset pseudo-elements (left: 60px), not row borders.
Inline summary/note atoms used inside panels & sheets:
/* Green check summary row */.ka-check-row { display: flex; align-items: center; gap: 10px; padding: 12px; background: var(--grey2); border-radius: 10px; }.ka-check-row-ic { width: 26px; height: 26px; border-radius: 50%; background: var(--g-3); color: var(--g-dark); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.ka-check-row-ic svg { width: 13px; height: 13px; }.ka-check-row-text { font-size: 13px; font-weight: 600; color: var(--grey12); }/* Defect note — red text + red-outline error icon, light tint (no flooded card) */.ka-defect-note { display: flex; gap: 10px; padding: 12px; background: rgba(227,38,39,0.06); border-radius: 10px; }.ka-defect-note-ic { width: 28px; height: 28px; border-radius: 50%; background: var(--white); color: var(--red9); box-shadow: inset 0 0 0 1.5px rgba(227,38,39,0.4); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }.ka-defect-note-ic svg { width: 14px; height: 14px; }.ka-defect-note-text { font-size: 12.5px; color: #B91C1C; line-height: 17px; }/* Instruction block */.ka-instr { display: flex; flex-direction: column; gap: 6px; padding: 14px; background: var(--grey2); border-radius: 12px; }.ka-instr-name { font-size: 13.5px; font-weight: 600; color: var(--grey12); }.ka-instr-desc { font-size: 12.5px; color: var(--grey11); line-height: 18px; }/* Serialized label preview — QA pass confirm sheet */.ka-label-preview { display: flex; align-items: center; gap: 12px; padding: 12px; background: var(--grey2); border-radius: 10px; }.ka-label-qr { width: 48px; height: 48px; border-radius: 8px; background: var(--white); display: flex; align-items: center; justify-content: center; color: var(--grey12); flex-shrink: 0; }.ka-label-qr svg { width: 28px; height: 28px; }.ka-label-text { min-width: 0; }.ka-label-id { font-size: 13px; font-weight: 600; color: var(--grey12); font-variant-numeric: tabular-nums; letter-spacing: -0.05em; }.ka-label-sub { font-size: 11.5px; color: var(--grey10); margin-top: 2px; }.km-cta-hint { font-size: 11.5px; color: var(--grey10); text-align: center; margin-top: 6px; line-height: 16px; }The defect-note pattern embodies the “completed/error state stays subtle” rule: light 6% red tint, white circular icon with a 1.5px red inset-ring, red 12.5px text — never a flooded red card.
3.10 Overlays — bottom sheets ONLY (no modals, no drawers on mobile)
Section titled “3.10 Overlays — bottom sheets ONLY (no modals, no drawers on mobile)”All confirmations, capture steps, and read-only detail overlays are bottom sheets sliding over the current screen. Five instances: #sheet-subcheck (evidence capture), #sheet-defect (defect reason), #sheet-qa-pass (pass confirm), #sheet-defect-summary (batch route-back), #sheet-evidence (read-only evidence viewer).
/* Bottom sheet — PM .rsm-sheet pattern */.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 24px; transform: translateY(100%); transition: transform 0.28s cubic-bezier(0.32,0.72,0,1); box-shadow: var(--sh-sheet); max-height: 82%; 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: 19px; 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: 10px; padding: 0 0 8px; overflow-y: auto; }.rsm-sheet-body::-webkit-scrollbar { display: none; }.rsm-sheet-note { font-size: 12px; color: var(--grey11); line-height: 17px; padding: 2px 2px 0; }.rsm-sheet-cta { display: flex; flex-direction: column; gap: 10px; padding: 16px 0 28px; flex-shrink: 0; }Canonical markup:
<div class="rsm-sheet-overlay" id="sheet-qa-pass"> <div class="rsm-sheet-backdrop" onclick="closeQaPassSheet()"></div> <div class="rsm-sheet" role="dialog" aria-modal="true" aria-labelledby="qapass-sheet-title"> <div class="rsm-sheet-handle-row"><span class="rsm-sheet-handle"></span></div> <div class="rsm-sheet-head"> <h2 class="rsm-sheet-title" id="qapass-sheet-title">Confirm QA pass</h2> <p class="rsm-sheet-sub" id="qapass-sheet-sub">Kit 5 of WO-K-2407-0188 · Standard kit</p> </div> <div class="rsm-sheet-body"> <div class="ka-check-row"> … </div> <div class="ka-label-preview"> … </div> <p class="rsm-sheet-note">Closing the assembly generates the kit-id, prints the serialized label, and moves the kit to the Production Manager's handoff queue.</p> </div> <div class="rsm-sheet-cta"> <button class="btn-primary btn-primary--lg" type="button" onclick="confirmQaPass()">Close assembly & generate kit-id</button> <button class="btn-secondary btn-secondary--lg" type="button" onclick="closeQaPassSheet()">Cancel</button> </div> </div></div>Sheet rules extracted:
- Structure order is fixed: handle-row → head (title+sub) → scrollable body → cta.
- Footer buttons are stacked vertically (never side-by-side): affirmative first (
btn-primary--lgORbtn-danger--lgfor destructive),btn-secondary--lgCancel last. Capture sheet stacks primary “Mark pass” above danger “Log defect” (no cancel; backdrop dismisses). - Backdrop click closes (
onclick="close…Sheet()"on.rsm-sheet-backdrop). - Open/close = toggling
.is-openon the overlay; sheet animatestranslateY(100%) → 0at 0.28scubic-bezier(0.32,0.72,0,1); backdroprgba(14,15,18,0.45)fades 0.22s. role="dialog" aria-modal="true" aria-labelledbyon every sheet.- Sheets are shared singletons at
.phonelevel and re-hydrated by JS per invocation.
Auth static sheet variant (welcome/login screen — a permanently-open sheet over the dark brand backdrop):
.sheet-screen { background: var(--brand-dark); }.sheet { position: absolute; inset: 60px 0 0 0; background: var(--white); border-radius: 24px 24px 0 0; display: flex; flex-direction: column; padding: 0 24px 32px; overflow: hidden; }.sheet-pill { width: 36px; height: 4px; border-radius: 100px; background: var(--grey5); margin: 8px auto 0; flex-shrink: 0; }Read-only evidence sheet content atoms:
/* Read-only evidence sheet — view what was validated per QA sub-check */.ka-evi-result { display: inline-flex; align-items: center; align-self: flex-start; font-size: 11px; font-weight: 600; padding: 4px 11px; border-radius: 100px; letter-spacing: 0.02em; }.ka-evi-result[data-state="done"], .ka-evi-result[data-state="pass"] { background: var(--g-3); color: var(--g-dark); }.ka-evi-result[data-state="fail"] { background: #FEE2E2; color: #B91C1C; }.ka-evi-thumbs { display: flex; gap: 8px; flex-wrap: wrap; }.ka-evi-thumb { width: 76px; height: 76px; border-radius: 12px; background: linear-gradient(135deg, #1f2230 0%, #2c3140 100%); display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,0.45); flex-shrink: 0; }.ka-evi-thumb svg { width: 22px; height: 22px; }.ka-evi-meta { font-size: 11.5px; color: var(--grey10); line-height: 16px; }.ka-evi-note { font-size: 12.5px; color: var(--grey11); line-height: 17px; padding: 10px 12px; background: var(--grey2); border-radius: 10px; }.ka-evi-note--defect { background: rgba(227,38,39,0.06); color: #B91C1C; }3.11 Toast
Section titled “3.11 Toast”/* Toast */.alert-toast { position: absolute; top: 56px; left: 50%; transform: translate(-50%, -16px); z-index: 80; display: inline-flex; align-items: center; gap: 8px; border-radius: 999px; padding: 4px 12px 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-close { background: none; border: 0; cursor: pointer; padding: 0 2px; margin-left: 4px; display: flex; align-items: center; color: currentColor; opacity: 0.55; }.alert-toast-close:hover { opacity: 1; }.alert-toast-close svg { width: 12px; height: 12px; }.alert-toast--success { background: #dcfbe2; color: #05853e; }.alert-toast--info { background: #e6f4fe; color: #0d74ce; }.alert-toast--error { background: #FEE2E2; color: #B91C1C; }<!-- Toast --><div class="alert-toast alert-toast--success" id="toast" role="status" aria-live="polite"> <span class="alert-toast-badge">Saved</span> <span class="alert-toast-text">Done</span> <button class="alert-toast-close" type="button" onclick="hideToast()" aria-label="Dismiss"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg></button></div>JS helper — showToast(message, badge, variant) (variant ∈ 'success' | 'info' | 'error', defaults success; auto-hides after 3500ms) + hideToast():
let _toastTimer = null;function showToast(message, badge, variant) { const t = document.getElementById('toast'); if (!t) return; ['success','info','error'].forEach(v => t.classList.remove('alert-toast--' + v)); t.classList.add('alert-toast--' + (variant || 'success')); if (badge) t.querySelector('.alert-toast-badge').textContent = badge; if (message) t.querySelector('.alert-toast-text').textContent = message; t.classList.add('is-visible'); clearTimeout(_toastTimer); _toastTimer = setTimeout(hideToast, 3500);}function hideToast() { document.getElementById('toast')?.classList.remove('is-visible'); }Usage: THE canonical DS pill toast — top-center (top: 56px, under the status bar), white translucent badge chip + text + close, tonal fills per variant. Example calls in-file: showToast('QA passed — ' + q.kitId, 'Closed', 'success'), showToast(c[0] + ' — defect logged', 'Defect', 'error'), showToast('Edit profile', 'Coming soon', 'info'). Never a black bottom pill.
3.12 Inputs & forms
Section titled “3.12 Inputs & forms”.field-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }.field-label { font-size: 14px; font-weight: 500; color: var(--grey12); line-height: 20px; }.input { display: flex; align-items: center; gap: 8px; height: 48px; padding: 0 14px; background: var(--white); border: 1px solid var(--grey6); border-radius: 8px; box-shadow: 0 1px 2px rgba(16,24,40,0.05); transition: border-color 0.18s ease, box-shadow 0.18s ease; }.input:focus-within { border-color: var(--brand); box-shadow: 0 0 0 4px rgba(1,228,111,0.12); }.input input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; font-family: inherit; font-size: 16px; line-height: 24px; color: var(--grey12); }.input input::placeholder { color: var(--grey9); }.input-icon { width: 20px; height: 20px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: transparent; border: 0; padding: 0; outline: 0; -webkit-appearance: none; appearance: none; color: var(--grey10); cursor: pointer; }.input-icon svg { width: 100%; height: 100%; display: block; }<div class="field-group"> <label class="field-label" for="welcome-email">Email</label> <div class="input"> <input id="welcome-email" type="email" placeholder="e.g adaeze.nwosu@rechaj.com" value="adaeze.nwosu@rechaj.com" oninput="onWelcomeInput()"> </div></div>Textarea (notes):
.rt-note-input { width: 100%; box-sizing: border-box; resize: none; overflow: hidden; border: 1px solid var(--grey3); border-radius: 12px; padding: 10px 12px; font-family: inherit; font-size: 13px; line-height: 18px; color: var(--grey12); background: var(--grey1); -webkit-appearance: none; appearance: none; transition: border-color 0.15s ease, background 0.15s ease; }.rt-note-input::placeholder { color: var(--grey10); }.rt-note-input:focus { outline: none; border-color: var(--g-primary); background: var(--white); }<textarea class="rt-note-input" id="defect-reason" rows="3" placeholder="Describe what failed and what the assembler needs to fix..." oninput="refreshDefectCta()"></textarea>Toggle switch:
.rsm-toggle { position: relative; width: 36px; height: 22px; 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: 18px; height: 18px; 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(14px); }<button class="rsm-toggle" type="button" aria-pressed="true" aria-label="Push notifications" onclick="toggleSwitch(this)"></button>Focus ring convention: brand-green border + 0 0 0 4px rgba(1,228,111,0.12) glow on .input:focus-within; textarea gets green border + white bg only. Input height 48px, radius 8px. No <select> and no search input exist in this file. Password fields use trailing .input-icon eye button + togglePw(). Enable-gating pattern: CTA stays disabled + .is-disabled until validation passes (onWelcomeInput, refreshDefectCta, refreshSubcheckCta).
Photo capture row (form-adjacent atom, retrofit-tech .rt-capture pattern):
/* Inline photo capture row — retrofit-tech .rt-capture pattern */.rt-capture-section { display: flex; flex-direction: column; gap: 10px; }.rt-capture-heading { font-size: 14px; font-weight: 600; color: var(--grey12); letter-spacing: -0.1px; }.rt-capture-row { display: flex; gap: 10px; overflow-x: auto; padding: 2px 2px 6px; -webkit-overflow-scrolling: touch; }.rt-capture-row::-webkit-scrollbar { display: none; }.rt-capture-add { width: 88px; height: 88px; border: 1.5px dashed var(--grey6); border-radius: 14px; background: var(--white); display: flex; align-items: center; justify-content: center; color: var(--grey10); flex-shrink: 0; cursor: pointer; padding: 0; -webkit-appearance: none; appearance: none; transition: background 0.18s ease, border-color 0.18s ease; }.rt-capture-add:hover { background: var(--grey2); border-color: var(--grey9); color: var(--grey12); }.rt-capture-add svg { width: 30px; height: 30px; }.rt-capture-thumb { position: relative; width: 88px; height: 88px; border-radius: 14px; background: linear-gradient(135deg, #1f2230 0%, #2c3140 100%); display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,0.45); flex-shrink: 0; overflow: hidden; }.rt-capture-thumb-ic { width: 26px; height: 26px; }.rt-capture-thumb-del { position: absolute; top: 6px; right: 6px; width: 22px; height: 22px; border-radius: 50%; background: rgba(0,0,0,0.6); color: var(--white); display: flex; align-items: center; justify-content: center; border: 0; padding: 0; cursor: pointer; -webkit-appearance: none; appearance: none; transition: background 0.15s ease; }.rt-capture-thumb-del:hover { background: rgba(0,0,0,0.85); }.rt-capture-thumb-del svg { width: 10px; height: 10px; }.rt-capture-hint { font-size: 11.5px; color: var(--grey10); line-height: 16px; }<div class="rt-capture-section"> <p class="rt-capture-heading">Evidence photo</p> <div class="rt-capture-row" id="subcheck-thumbs"> <button class="rt-capture-add" type="button" onclick="subcheckCapturePhoto()" aria-label="Take photo"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> </button> </div> <p class="rt-capture-hint">Mandatory before pass or defect.</p></div>3.13 Dropdown menus
Section titled “3.13 Dropdown menus”None present. No dropdown/menu/popover component exists in this file. Choices are made via bottom sheets or tabs.
3.14 Empty states
Section titled “3.14 Empty states”CSS defines .rt-placeholder but it is unused in this file’s markup (all panes ship with content). It is the canonical empty/placeholder pattern to reuse:
/* Placeholder */.rt-placeholder { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px; text-align: center; gap: 10px; color: var(--grey11); }.rt-placeholder-ic { width: 56px; height: 56px; border-radius: 50%; background: var(--grey2); color: var(--grey10); display: flex; align-items: center; justify-content: center; margin-bottom: 4px; }.rt-placeholder-ic svg { width: 24px; height: 24px; }.rt-placeholder h2 { font-size: 18px; font-weight: 600; color: var(--grey12); }.rt-placeholder p { font-size: 13px; color: var(--grey11); max-width: 240px; line-height: 1.5; }Anatomy: centered column — 56px grey circle icon, 18px/600 title, 13px grey body capped at 240px.
3.15 Icon-container convention
Section titled “3.15 Icon-container convention”- Circular is the default for all state/identity icons:
.rsm-stat-ic36px,.rsm-q-card-icon36px,.km-sop-check26px,.ka-check-row-ic26px,.km-gate-dot20px,.rsm-hero-avatar44px,.rsm-profile-avatar56px,.rsm-profile-chev28px,.rt-placeholder-ic56px,.ka-defect-note-ic28px, all icon buttons. - Tonal tinting pattern everywhere: light tint background + saturated glyph of the same hue (e.g.
--g-2bg +--g-primaryglyph;#FEE2E2bg +#B91C1Cglyph;#DBEAFEbg +#1E40AFglyph). Solid fills are reserved for the brand avatar (--brandbg + dark text) and the primary button. - Rounded squares (r10–r14) are reserved for: kit-type marks (
.rt-active-icon44px r12,.km-wo-hero-mark46px r12 — lettered “STD”/“KaaG” tiles), photo/media thumbnails (.rt-capture-thumb/.rt-capture-add88px r14,.ka-evi-thumb76px r12,.ka-camera-thumb48px r8,.ka-label-qr48px r8), and the profile row icon.rsm-profile-row-ic(32px r10 — the documented DS exception). - The red-outline style for errors: white circle +
box-shadow: inset 0 0 0 1.5px rgba(227,38,39,0.4)+ red glyph (.ka-defect-note-ic) — never a solid red disc.
3.16 Fullscreen camera pattern (CSS present; screen not instantiated in this file)
Section titled “3.16 Fullscreen camera pattern (CSS present; screen not instantiated in this file)”#s-qa-capture and the .ka-camera-* / .ka-viewfinder-* / .ka-shutter family are fully specified in CSS (lines 410–444) as the shared retrofit camera pattern, but the markup contains no s-qa-capture screen and SCREENS omits it — photo capture is simulated inside the sub-check sheet (subcheckCapturePhoto() appends a .rt-capture-thumb). Keep the CSS as the canonical camera spec:
/* Fullscreen camera — shared retrofit camera pattern */#s-qa-capture { background: #0E0F12; }.ka-camera-shell { flex: 1; display: flex; flex-direction: column; padding-top: 44px; }.ka-camera-head { padding: 12px 16px 4px; display: flex; align-items: center; gap: 12px; }.ka-camera-back { width: 36px; height: 36px; border-radius: 50%; background: rgba(255,255,255,0.12); border: 0; color: var(--white); display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; -webkit-appearance: none; appearance: none; flex-shrink: 0; }.ka-camera-back svg { width: 16px; height: 16px; }.ka-camera-title-wrap { flex: 1; min-width: 0; }.ka-camera-title { font-size: 14.5px; font-weight: 600; color: var(--white); line-height: 18px; }.ka-camera-meta { font-size: 11px; color: rgba(255,255,255,0.5); line-height: 14px; margin-top: 2px; }.ka-viewfinder { flex: 1; margin: 8px 16px 0; border-radius: 18px; background: radial-gradient(120% 80% at 50% 0%, #353535 0%, #161616 70%, #050505 100%); position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; min-height: 200px; cursor: pointer; }.ka-viewfinder-grid { position: absolute; inset: 0; pointer-events: none; }.ka-viewfinder-grid::before, .ka-viewfinder-grid::after { content: ''; position: absolute; }.ka-viewfinder-grid::before { left: 33.33%; right: 33.33%; top: 0; bottom: 0; border-left: 1px solid rgba(255,255,255,0.08); border-right: 1px solid rgba(255,255,255,0.08); }.ka-viewfinder-grid::after { top: 33.33%; bottom: 33.33%; left: 0; right: 0; border-top: 1px solid rgba(255,255,255,0.08); border-bottom: 1px solid rgba(255,255,255,0.08); }.ka-viewfinder-target { width: 56px; height: 56px; border-radius: 50%; position: relative; z-index: 1; background: rgba(255,255,255,0.06); display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,0.4); }.ka-viewfinder-target svg { width: 22px; height: 22px; }.ka-viewfinder-corner { position: absolute; width: 24px; height: 24px; border-color: rgba(255,255,255,0.4); }.ka-viewfinder-corner-tl { top: 14px; left: 14px; border-top: 2px solid; border-left: 2px solid; border-radius: 4px 0 0 0; }.ka-viewfinder-corner-tr { top: 14px; right: 14px; border-top: 2px solid; border-right: 2px solid; border-radius: 0 4px 0 0; }.ka-viewfinder-corner-bl { bottom: 14px; left: 14px; border-bottom: 2px solid; border-left: 2px solid; border-radius: 0 0 0 4px; }.ka-viewfinder-corner-br { bottom: 14px; right: 14px; border-bottom: 2px solid; border-right: 2px solid; border-radius: 0 0 4px 0; }.ka-viewfinder-label { position: absolute; bottom: 14px; left: 14px; font-size: 10.5px; color: rgba(255,255,255,0.5); letter-spacing: 0.08em; text-transform: uppercase; }.ka-camera-thumbs { display: flex; gap: 8px; padding: 12px 16px 0; overflow-x: auto; min-height: 60px; align-items: center; }.ka-camera-thumbs::-webkit-scrollbar { display: none; }.ka-camera-thumbs-empty { font-size: 11.5px; color: rgba(255,255,255,0.45); }.ka-camera-thumb { width: 48px; height: 48px; border-radius: 8px; background: linear-gradient(135deg, #3a3a3a 0%, #555 100%); display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,0.55); flex-shrink: 0; position: relative; }.ka-camera-thumb svg { width: 16px; height: 16px; }.ka-camera-thumb-del { position: absolute; top: -5px; right: -5px; width: 18px; height: 18px; border-radius: 50%; background: var(--white); color: var(--text-main); display: flex; align-items: center; justify-content: center; border: 0; cursor: pointer; padding: 0; -webkit-appearance: none; appearance: none; }.ka-camera-thumb-del svg { width: 10px; height: 10px; }.ka-camera-controls { display: flex; align-items: center; justify-content: space-between; padding: 16px 24px 32px; }.ka-camera-controls-spacer { width: 84px; height: 1px; flex-shrink: 0; }.ka-shutter { width: 72px; height: 72px; border-radius: 50%; background: var(--white); border: 4px solid rgba(255,255,255,0.22); cursor: pointer; padding: 0; -webkit-appearance: none; appearance: none; transition: transform 0.12s ease; box-shadow: 0 0 0 2px rgba(255,255,255,0.06); }.ka-shutter:active { transform: scale(0.92); }3.17 Auth atoms (splash / onboarding / welcome)
Section titled “3.17 Auth atoms (splash / onboarding / welcome)”/* ══ AUTH SCREENS — replicated 1:1 from retrofit personas ══ */#s-splash { background: var(--brand-dark); align-items: center; justify-content: center; cursor: pointer; }#s-splash .splash-logo { width: 80px; height: 80px; background: var(--brand); border-radius: 50%; display: flex; align-items: center; justify-content: center; }#s-splash .splash-logo svg { width: 44px; height: 44px; }
#s-onboarding { background: var(--brand-dark); padding: 0 24px; }.ob-brand { display: flex; align-items: center; gap: 10px; justify-content: center; margin-top: 24px; }.ob-brand-mark { width: 36px; height: 36px; }.ob-brand-name { font-size: 22px; font-weight: 700; color: var(--white); letter-spacing: -0.5px; }.ob-photo { flex: 1; display: flex; align-items: center; justify-content: center; margin-top: 16px; }.ob-photo-img { display: block; width: 280px; height: auto; max-height: 360px; object-fit: contain; }.ob-text { text-align: center; padding: 8px 0 56px; }.ob-title { font-size: 24px; font-weight: 700; color: var(--white); line-height: 32px; letter-spacing: -0.4px; margin-bottom: 12px; }.ob-title .accent { color: var(--brand); }.ob-tag { font-size: 14px; font-weight: 400; color: rgba(255,255,255,0.7); line-height: 22px; max-width: 320px; margin: 0 auto; }.ob-cta-wrap { padding-bottom: 32px; }.welcome-head { text-align: center; padding: 22px 0 28px; }.welcome-title { font-size: 28px; font-weight: 700; color: var(--grey12); letter-spacing: -0.4px; line-height: 36px; margin-bottom: 6px; }.welcome-sub { font-size: 14px; font-weight: 400; color: var(--grey11); line-height: 20px; }.welcome-cta-wrap { margin-top: auto; padding-top: 32px; display: flex; flex-direction: column; gap: 16px; align-items: center; }.welcome-faceid-wrap { display: flex; justify-content: center; padding: 24px 0 0; }.welcome-faceid { width: 44px; height: 44px; background: transparent; border: 0; cursor: pointer; color: var(--grey12); display: flex; align-items: center; justify-content: center; -webkit-appearance: none; appearance: none; transition: color 0.18s ease; }.welcome-faceid svg { width: 32px; height: 32px; }.welcome-faceid:hover { color: var(--g-dark); }Notes: splash = green 80px disc with dark two-tone brand mark (#0F1D13) on --brand-dark, tap or 2.5s auto-advance. Onboarding = brand row (white+green mark, “Rechaj” 22px/700), photo, display title with .accent green word, tagline, primary CTA. Welcome = login sheet (title 28px/700) with email/password, forgot-password .text-link, Face ID button, disabled-until-valid Login. .welcome-cta-wrap is also reused in JS-hydrated bodies as the generic bottom-CTA wrapper (<div class="welcome-cta-wrap" style="padding-top: 4px;">).
4. Page anatomy
Section titled “4. Page anatomy”Nine screens in SCREENS order: s-splash, s-onboarding, s-welcome, s-dashboard, s-qa-queue, s-qa-detail, s-g2-visual, s-g3-electrical, s-profile.
4.1 Splash (#s-splash)
Section titled “4.1 Splash (#s-splash)”.sb.sb--dark(absolute overlay) → 2. centered.splash-logo→ 3..home-ind.home-ind--light. Interaction: whole screenonclick="goTo('s-onboarding')"; auto-advances after 2500ms (initSplash).?screen=query param can deep-link to any screen and skips splash.
4.2 Onboarding (#s-onboarding)
Section titled “4.2 Onboarding (#s-onboarding)”.sb.sb--dark→ 2..ob-brand→ 3..ob-photo(flex:1) → 4..ob-text(title + tag) → 5..ob-cta-wrapwithbtn-primary--lg“Get started” →goTo('s-welcome')→ 6. light home indicator.
4.3 Welcome / Login (#s-welcome, .sheet-screen)
Section titled “4.3 Welcome / Login (#s-welcome, .sheet-screen)”.sb.sb--dark→ 2..sheet(inset 60px top): pill →.welcome-head→ email.field-group→ password.field-group(+eye toggle, forgot link) →.welcome-faceid-wrap→.welcome-cta-wrapLogin button. Interaction: Login disabled until/\S+@\S+\.\S+/email + pw ≥ 4 chars (onWelcomeInput); Face ID and Login bothgoTo('s-dashboard').
4.4 Dashboard (#s-dashboard, tab-screen) — “home” page type
Section titled “4.4 Dashboard (#s-dashboard, tab-screen) — “home” page type”Top-to-bottom composition:
header.rsm-hero(contains.sb.sb--dark,.rsm-hero-bgart, avatar+name+role+meta).rsm-stats(2 KPI cards, overlapping hero −52px).rsm-body(padding-top:16px): a..rsm-section-head“Currently inspecting” + meta “G2 in progress” b..rt-active-card(in-flight work item w/ progress bar) c..rsm-section-head“Awaiting your QA” + meta “2 kits” d..rsm-q-listof.rsm-q-rowpending cardsnav.rsm-nav(Home active) → 5..home-ind. Interaction: every card is a full-card button →openQaDetail(key, 's-dashboard')(return-screen remembered).
4.5 QA Queue (#s-qa-queue, tab-screen) — “list” page type
Section titled “4.5 QA Queue (#s-qa-queue, tab-screen) — “list” page type”.sb(light) → 2..rsm-page-head(centered title, no back) → 3..rsm-body(padding-top:4px): a..km-tabssegmented filter (Pending / In progress / Completed with counts) b. threediv[data-qa-pane]panes, each:.rsm-q-day-headday bucket →.rsm-q-listof.rsm-q-rownav.rsm-nav(QA Queue active) → 5..home-ind. Interaction: tab click =setQaTab(this)(aria-pressed swap, pane hidden-toggle, auto-center of the active tab in the scrollable strip, body scrolled to top). Row click =openQaDetail(key, 's-qa-queue'). Default selected tab: Pending. No search anywhere.
4.6 QA Detail (#s-qa-detail) — “detail” page type, single template hydrated per record
Section titled “4.6 QA Detail (#s-qa-detail) — “detail” page type, single template hydrated per record”.sb→ 2..rsm-page-headwith.rsm-page-back(→goTo(qaReturnScreen)) + title “QA detail” → 3..rsm-body#qa-detail-bodyfully hydrated byrenderQaDetail()in this order: a..km-wo-hero-card— kit mark, id, type·BOM sub,.km-wo-pillstatus, G1/G2/G3.km-gate-dots, 3-col meta grid (Assembler / Submitted / Components) b..km-panel“Assembler evidence” — 2 green.ka-check-rows c..km-panel“QA gates” —.km-sop-listwith G2 and G3 rows (pass → tappable w/ green check; fail → tappable w/ red X; todo → numbered; G3--lockedat 0.55 opacity until G2 completes) d..km-panel“Defects logged / Defects routed back” (only if defects exist) —.ka-defect-noteitems +.km-cta-hinte. Tail CTA — state machine:- g2 pending →
btn-primary“Start G2 Visual” →openGate('g2') - g3 pending →
btn-primary“Start G3 Electrical pre-check” →openGate('g3') - both run + defects →
btn-danger“Send N defect(s) to {FirstName}” →openDefectSummarySheet() - both passed clean →
btn-primary“Confirm QA pass” →openQaPassSheet() - status pass → centered
.km-panelsuccess summary (3 green gate dots, “QA passed — {kit-id}”) — no button - status defect → no tail (defects panel shows routed-back state)
- g2 pending →
.home-ind. No bottom nav on drill-in screens. Interaction: gate rows open the gate screen (if actionable) — detail lives on its own SCREEN, not a modal. Confirmations are bottom sheets.
4.7 G2 Visual / G3 Electrical (#s-g2-visual, #s-g3-electrical) — “task/checklist” page type
Section titled “4.7 G2 Visual / G3 Electrical (#s-g2-visual, #s-g3-electrical) — “task/checklist” page type”.sb→ 2..rsm-page-head+ back (→'s-qa-detail') + title (“G2 Visual” / “G3 Electrical”) → 3..rsm-body#g2-body|#g3-bodyhydrated byrenderGate(): a. compact.km-wo-hero-card(mark + id +km-wo-pillshowing gate label) b..km-panel“Visual sub-checks” / “Electrical sub-checks” + meta “N of M checked” —.km-sop-listrows: todo rows →openSubcheck(i)(capture sheet); pass/fail rows →openSubcheckEvidence(i)(read-only sheet w/ optional Edit) c. bottom CTAwelcome-cta-wrap: “Complete {gate}” (btn-primary--lg, disabled until all sub-checks answered; label appends “· N defect(s)” when defects exist) +.km-cta-hintInteraction:completeGate()sets gate pass/fail, toasts, andgoTo('s-qa-detail'). Defect logging: capture sheet “Log defect” → defect-reason sheet →confirmDefect()keeps you ON the gate screen to continue inspecting. Photos are mandatory: pass/defect buttons stay disabled at 0 photos.
4.8 Profile (#s-profile, tab-screen) — “settings” page type
Section titled “4.8 Profile (#s-profile, tab-screen) — “settings” page type”.sb→ 2..rsm-page-head“Profile” → 3..rsm-body: a..rsm-profile-usercard (56px avatar, name, role pill, email, chevron) b. four grouped sections, eachp.rsm-profile-section-title(UPPERCASE) +.rsm-profile-sectioncard of.rsm-profile-rows: Workspace (QA Mode / Active BOMs / Production Manager), Account (Account details / Password / 2FA), Preferences (Push notifications toggle / Dark appearance toggle / Language), Support (Help / About / Sign out as--dangerrow →goTo('s-welcome'))nav.rsm-nav(Profile active) → 5..home-ind.
4.9 Cross-cutting interaction rules
Section titled “4.9 Cross-cutting interaction rules”- Row/card click → navigates to a dedicated detail SCREEN (
openQaDetail/openGate), never a modal. Detail screens remember the originating screen (qaReturnScreen) for back. - Confirmations → always bottom sheets with stacked full-width CTAs (affirmative on top, Cancel below); destructive confirm =
btn-danger. - Evidence/inspection of a completed item → read-only bottom sheet (
openEvidence) with optional “Edit submission” that reopens the capture sheet prefilled. - Feedback → every state mutation fires
showToast(...)(success = green, defect/route-back = error red, informational gate completion = info blue). - Default selections: splash is the entry screen; queue defaults to Pending tab; detail default record
currentQaKey = 'q5-0188'. - Search: none in this app.
- Data discipline: one
QA_DATAmap, detail + gate screens are single templates hydrated per key (same discipline as PM’sWO_DATAand Assembler’sASM_DATA).
5. JS contracts
Section titled “5. JS contracts”Navigation & shell:
SCREENS: string[]— ordered screen id registry.goTo(screenId)— swap.active, play.screen-enter(removed after 320ms), reset.rsm-bodyscrollTop.initSplash()— IIFE; honors?screen=<id>deep link, else auto-advance splash → onboarding at 2500ms.
Toast:
showToast(message, badge, variant)— variant'success'|'info'|'error', default'success', auto-hide 3500ms.hideToast().
Auth:
onWelcomeInput()— enable login on valid email + pw ≥ 4.togglePw(inputId)— password visibility.attemptLogin()—goTo('s-dashboard').toggleSwitch(btn)— flipsaria-pressedon.rsm-toggle.
Tabs:
setQaTab(btn)— readsbtn.dataset.qaTab, setsaria-pressedacross#s-qa-queue .km-tab, toggles[data-qa-pane]hidden, centers the tab, scrolls body to top.
Detail / gates (state + render):
QA_DATA— record map; fields:id, wo, type('standard'|'kaag'), bom, assembler, submittedAt, components, status('pending'|'in-progress'|'pass'|'defect'), statusLabel, g2, g3 ('pending'|'pass'|'fail'), optionalkitId, closedAt, flaggedAt, g2checks[], g3checks[], defects[{gate, subcheck, reason}].currentQaKey,qaReturnScreen,currentGate('g2'|'g3'),currentSubcheckIdx,subcheckPhotoCount,editingSubcheckIdx.openQaDetail(key, returnScreen)→renderQaDetail()+goTo('s-qa-detail').renderQaDetail()— hydrates#qa-detail-body(hero + evidence + gates + defects + tail CTA).GATE_CHECKS(g2: 4 checks,g3: 3 checks),GATE_LABEL({ g2: 'G2 Visual', g3: 'G3 Electrical pre-check' })._ensureGateResults(q, gate),openGate(gate),renderGate(),completeGate()._gateDot(label, state),_checkSvg,_xSvg— shared SVG string constants.
Sheets (each has open/close pair toggling .is-open):
openSubcheck(idx)/editSubcheckSubmission(idx)/closeSubcheckSheet();subcheckCapturePhoto(),deleteSubcheckPhoto(btn),refreshSubcheckCta(),markSubcheckPass(),markSubcheckDefect(),_subcheckThumbHTML().openDefectSheet()/closeDefectSheet();refreshDefectCta(),confirmDefect().openDefectSummarySheet()/closeDefectSummarySheet();confirmDefectSummary().openQaPassSheet()/closeQaPassSheet();confirmQaPass()(generateskitIdprefixkit-K-88301-WS-std /kit-K-88412-KG-kaag).openEvidence(opts)/closeEvidence()— opts:{ title, sub, result, resultState, photos, note, noteDefect, onEdit };openSubcheckEvidence(idx).
Wiring style: all handlers are inline onclick="…" / oninput="…" attributes; sheets and toast are shared singletons re-hydrated per call; disabled buttons always toggle BOTH the disabled attribute and .is-disabled class.
6. Naming
Section titled “6. Naming”Class prefixes (namespace = origin flow):
rsm-— shared mobile shell (hero, page-head, body, nav, stats, section heads, queue cards, profile, sheets, toggle). Largest namespace.km-— Kit-production shared patterns from the PM flow (km-tabs,km-panel,km-wo-hero-*,km-wo-pill,km-serial-*,km-sop-*,km-gate-dot*,km-cta-hint).rt-— retrofit-flow imports (rt-active-*,rt-progress-*,rt-capture-*,rt-note-input,rt-placeholder).ka-— Kit-assembler imports (ka-check-row*,ka-defect-note*,ka-instr*,ka-label-*,ka-evi-*,ka-camera-*,ka-viewfinder-*,ka-shutter).- Unprefixed auth/global atoms:
btn-primary,btn-secondary,btn-danger,input,field-group,field-label,field-aside,text-link,sheet,sheet-pill,sheet-screen,splash-logo,ob-*,welcome-*,sb,sb-*,home-ind,alert-toast*,phone,screen,tab-screen. - State classes/attrs:
.is-active(nav),.is-open(sheets),.is-visible(toast),.is-disabled(+disabled),aria-pressed(tabs/toggles),data-state(pills/dots),[hidden](panes), modifier suffix--lg,--kaag,--std,--running,--dark,--light,--alert,--danger,--locked,--tappable,--active,--todo,--fail,--defect,--pending,--in-progress,--pass.
Screen ids: s-<kebab> (s-splash, s-qa-queue, s-g2-visual …). Sheet ids: sheet-<kebab>. Element ids for hydration: <context>-<part> (qa-detail-body, g2-body, subcheck-title, defect-confirm-btn, qapass-kitid, defectsum-list, evidence-thumbs).
Personas & people: Adaeze Nwosu (AN, “Production QA”, adaeze.nwosu@rechaj.com) — the app user; Muiz Olawale and Yusuf Adeyemi — assemblers; Erioluwa Morenikeji — Production Manager.
ID formats:
- Work orders:
WO-K-2407-0188(WO-K-<YYMM>-<seq4>), kits referenced as “Kit 5 of WO-K-2407-0188”. - Serialized kit ids:
kit-K-88301-WS-0005(standard) /kit-K-88412-KG-####(KaaG) —kit-K-<batch5>-<site2>-<seq4>. - QA record keys:
q<kitNo>-<woSuffix>(q5-0188). - BOM refs:
BOM v4,BOM v2 KaaG. Gates:G1–G4lettering,G2 Visual,G3 Electrical pre-check. - Copy uses the DS middot
·as separator everywhere (“Standard kit · BOM v4 · from Muiz”); the only em dashes are in narrative strings ('Failed — harness routing', toasts' — passed','QA passed — ${q.kitId}') — note these are in data/toast copy, which strictly conflicts with the no-em-dash rule for UI copy; prefer·when replicating.
7. Weight & spacing discipline
Section titled “7. Weight & spacing discipline”700 weights — complete inventory (grep-verified; no 800s in the file)
Section titled “700 weights — complete inventory (grep-verified; no 800s in the file)”| Selector | What | Legit? |
|---|---|---|
.sb-time (15px) |
iOS status-bar clock | Yes — approved exception |
.rsm-stat-num (20px) |
KPI digits | Yes — display numbers |
.ob-brand-name (22px) |
Onboarding brand wordmark | Yes — splash/onboarding display |
.ob-title (24px) |
Onboarding display title | Yes |
.welcome-title (28px) |
Welcome display title | Yes |
.km-wo-hero-progress-val (15px) |
Progress numeric value | Yes — numeric display value (borderline: only 15px) |
.km-gate-dot (9px) |
G1/G2/G3 letters inside 20px dots | Violation-adjacent — 700 on non-display 9px text; kept for legibility at tiny size. Flag if replicating elsewhere. |
Everything else — names, WO ids, titles, buttons, pills, avatars, section titles — is 600 max. Body/meta = 400–500. This matches the “bold sparingly” rule.
Type scale observed
Section titled “Type scale observed”9 / 10 / 10.5 / 11 / 11.5 / 12 / 12.5 / 13 / 13.5 / 14 / 14.5 / 15 / 16 / 17 / 18 / 19 / 20 / 22 / 24 / 28 px. Half-pixel sizes (10.5–14.5) are a deliberate feature of this canon. Negative letter-spacing throughout (−0.01em body, up to −0.5px on display); uppercase micro-labels get positive tracking (0.02–0.08em). Numbers use font-variant-numeric: tabular-nums.
Spacing rhythm
Section titled “Spacing rhythm”- Body column:
padding: 0 16px 24px, gap: 18px between top-level sections. - Card internals: padding 16px (panels/heroes) or 14px (stat/queue cards); internal gap 12–16px.
- Inset rows:
10px 12pxpadding, 6px list gap; queue-card list gap 14px; stats grid gap 10px; sheet body gap 10px; sheet CTA gap 10px,padding: 16px 0 28px. - Micro-gaps: 1/2/4/6px for text stacks; 10/12px for icon-to-text.
- Screen gutters: 16px (app pages), 24px (auth screens & sheets).
- Radii ladder: 8 (buttons/inputs) · 10 (inset rows) · 12 (tab track, marks, instr) · 14 (capture tiles) · 16 (cards) · 18 (hero cards/viewfinder) · 24 (sheets) · 28 (device) · 100px/999px/50% (pills, dots, circles).
Grid violations noticed (within the file itself)
Section titled “Grid violations noticed (within the file itself)”The repo rule says strict 4px grid, no 7/11/15px. This file’s actual rhythm is a 2px grid: it freely uses 2, 6, 10, 14, 18px paddings/gaps (e.g. .rsm-body gap:18px, .rsm-stat padding:14px, .rsm-q-list gap:14px, .km-sop-row padding:10px 12px, pill paddings 2px 9px/3px 9px/4px 11px, .rsm-q-day-head padding:12px 0 6px). Odd one-offs: 9px & 11px pill side-padding, 5px gaps (.sb-icons, .km-tab), 1px text gap (.rsm-q-card-id-text), 22px/28px welcome-head padding, top:-5px on camera thumb delete. There are NO 7px or 15px values. When replicating, match THIS file’s values verbatim (the reference wins over the generic 4px-grid phrasing).
Other self-consistency notes
Section titled “Other self-consistency notes”--grey7,--grey1,--amber9are declared but barely used (--grey1only on.rt-note-inputbg +.rsm-profile-user:hover;--amber9never referenced — amber pills hard-code#B45309)..rsm-hero-bell,.km-serial-row,.km-sop-row--active,.ka-instr,.rt-placeholder, and the entire.ka-camera-*screen are CSS-only in this file (no markup instance) — they are carried for cross-flow consistency and remain canonical.- Inline
style="…"is used sparingly for one-off spacing/colors inside JS templates (style="padding-top: 4px;",style="color:var(--g-dark);",style="color:#B91C1C;", defect item titlesstyle="font-size:11.5px; font-weight:600; color:#B91C1C;") — acceptable in this canon for template-local tweaks.