Skip to content

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.


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.


  • Device: 390 × 844, radius 28px, white, overflow:hidden, centered on a #0E0F12 page 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-body scrolls (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)
* { 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-body is the single scroll container; children get gap: 18px rhythm and flex-shrink:0.
  • App pages (dashboard, queue, detail, gates, profile) use .screen.tab-screen → grey #F5F5F7 page 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-body scrollTop to 0 and plays .screen-enter slide-in.
  • Interior pages give .rsm-body inline style="padding-top: 4px;"; the dashboard uses style="padding-top: 16px;" (below the overlapping stat cards).
  • Status bar has two skins: dark screens use .sb.sb--dark with white #FFFFFF SVG fills; light screens use plain .sb with #202020 SVG fills. Time is always 9:41.

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

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.

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

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-primary text on dark.
  • .rsm-profile-role-pill — light-green --g-2 bg + --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.

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

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 &amp; 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--lg OR btn-danger--lg for destructive), btn-secondary--lg Cancel 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-open on the overlay; sheet animates translateY(100%) → 0 at 0.28s cubic-bezier(0.32,0.72,0,1); backdrop rgba(14,15,18,0.45) fades 0.22s.
  • role="dialog" aria-modal="true" aria-labelledby on every sheet.
  • Sheets are shared singletons at .phone level 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; }
/* 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.

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

None present. No dropdown/menu/popover component exists in this file. Choices are made via bottom sheets or tabs.

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.

  • Circular is the default for all state/identity icons: .rsm-stat-ic 36px, .rsm-q-card-icon 36px, .km-sop-check 26px, .ka-check-row-ic 26px, .km-gate-dot 20px, .rsm-hero-avatar 44px, .rsm-profile-avatar 56px, .rsm-profile-chev 28px, .rt-placeholder-ic 56px, .ka-defect-note-ic 28px, all icon buttons.
  • Tonal tinting pattern everywhere: light tint background + saturated glyph of the same hue (e.g. --g-2 bg + --g-primary glyph; #FEE2E2 bg + #B91C1C glyph; #DBEAFE bg + #1E40AF glyph). Solid fills are reserved for the brand avatar (--brand bg + dark text) and the primary button.
  • Rounded squares (r10–r14) are reserved for: kit-type marks (.rt-active-icon 44px r12, .km-wo-hero-mark 46px r12 — lettered “STD”/“KaaG” tiles), photo/media thumbnails (.rt-capture-thumb/.rt-capture-add 88px r14, .ka-evi-thumb 76px r12, .ka-camera-thumb 48px r8, .ka-label-qr 48px 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;">).


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.

  1. .sb.sb--dark (absolute overlay) → 2. centered .splash-logo → 3. .home-ind.home-ind--light. Interaction: whole screen onclick="goTo('s-onboarding')"; auto-advances after 2500ms (initSplash). ?screen= query param can deep-link to any screen and skips splash.
  1. .sb.sb--dark → 2. .ob-brand → 3. .ob-photo (flex:1) → 4. .ob-text (title + tag) → 5. .ob-cta-wrap with btn-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)”
  1. .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-wrap Login button. Interaction: Login disabled until /\S+@\S+\.\S+/ email + pw ≥ 4 chars (onWelcomeInput); Face ID and Login both goTo('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:

  1. header.rsm-hero (contains .sb.sb--dark, .rsm-hero-bg art, avatar+name+role+meta)
  2. .rsm-stats (2 KPI cards, overlapping hero −52px)
  3. .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-list of .rsm-q-row pending cards
  4. nav.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”
  1. .sb (light) → 2. .rsm-page-head (centered title, no back) → 3. .rsm-body (padding-top:4px): a. .km-tabs segmented filter (Pending / In progress / Completed with counts) b. three div[data-qa-pane] panes, each: .rsm-q-day-head day bucket → .rsm-q-list of .rsm-q-row
  2. nav.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”
  1. .sb → 2. .rsm-page-head with .rsm-page-back (→ goTo(qaReturnScreen)) + title “QA detail” → 3. .rsm-body#qa-detail-body fully hydrated by renderQaDetail() in this order: a. .km-wo-hero-card — kit mark, id, type·BOM sub, .km-wo-pill status, 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-list with G2 and G3 rows (pass → tappable w/ green check; fail → tappable w/ red X; todo → numbered; G3 --locked at 0.55 opacity until G2 completes) d. .km-panel “Defects logged / Defects routed back” (only if defects exist) — .ka-defect-note items + .km-cta-hint e. 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-panel success summary (3 green gate dots, “QA passed — {kit-id}”) — no button
    • status defect → no tail (defects panel shows routed-back state)
  2. .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”
  1. .sb → 2. .rsm-page-head + back (→ 's-qa-detail') + title (“G2 Visual” / “G3 Electrical”) → 3. .rsm-body#g2-body|#g3-body hydrated by renderGate(): a. compact .km-wo-hero-card (mark + id + km-wo-pill showing gate label) b. .km-panel “Visual sub-checks” / “Electrical sub-checks” + meta “N of M checked” — .km-sop-list rows: todo rows → openSubcheck(i) (capture sheet); pass/fail rows → openSubcheckEvidence(i) (read-only sheet w/ optional Edit) c. bottom CTA welcome-cta-wrap: “Complete {gate}” (btn-primary--lg, disabled until all sub-checks answered; label appends “· N defect(s)” when defects exist) + .km-cta-hint Interaction: completeGate() sets gate pass/fail, toasts, and goTo('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”
  1. .sb → 2. .rsm-page-head “Profile” → 3. .rsm-body: a. .rsm-profile-user card (56px avatar, name, role pill, email, chevron) b. four grouped sections, each p.rsm-profile-section-title (UPPERCASE) + .rsm-profile-section card 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 --danger row → goTo('s-welcome'))
  2. nav.rsm-nav (Profile active) → 5. .home-ind.
  • 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_DATA map, detail + gate screens are single templates hydrated per key (same discipline as PM’s WO_DATA and Assembler’s ASM_DATA).

Navigation & shell:

  • SCREENS: string[] — ordered screen id registry.
  • goTo(screenId) — swap .active, play .screen-enter (removed after 320ms), reset .rsm-body scrollTop.
  • 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) — flips aria-pressed on .rsm-toggle.

Tabs:

  • setQaTab(btn) — reads btn.dataset.qaTab, sets aria-pressed across #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'), optional kitId, 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() (generates kitId prefix kit-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.


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–G4 lettering, 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.

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.

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.

  • 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 12px padding, 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).

  • --grey7, --grey1, --amber9 are declared but barely used (--grey1 only on .rt-note-input bg + .rsm-profile-user:hover; --amber9 never 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 titles style="font-size:11.5px; font-weight:600; color:#B91C1C;") — acceptable in this canon for template-local tweaks.