Skip to content

canon extract assembler

Canon extraction — Kit Assembler flow (SOURCE OF TRUTH)

Section titled “Canon extraction — Kit Assembler flow (SOURCE OF TRUTH)”

Source: /Users/Ridwan/Pictures/Claude/Rechaj 2.0/rechaj-design-system/products/rechaj-kit-production-app/flows/assembler/index.html (1774 lines; CSS lines 11–496 read in full, markup 498–1256, JS 1258–1772).

Font load (head, verbatim):

<link rel="preconnect" href="https://api.fontshare.com" crossorigin>
<link href="https://api.fontshare.com/v2/css?f[]=general-sans@400,500,600,700&display=swap" rel="stylesheet">

Viewport / theme-color:

<meta name="viewport" content="width=390,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#01E46F">

Verbatim :root block:

:root {
--brand: #01E46F; --brand-dark: #013F1F; --g-primary: #01E46F; --g-dark: #05853E;
--g-2: #DCFBE2; --g-3: #B5F1C2;
--grey1: #FAFAFA; --grey2: #F9F9F9; --grey3: #E4E4E4; --grey4: #E8E8E8;
--grey5: #E0E0E0; --grey6: #D9D9D9; --grey7: #B0B0B0; --grey9: #A0A0A0; --grey10: #888888; --grey11: #555555; --grey12: #202020;
--red9: #E32627; --amber9: #B45309;
--white: #FFFFFF; --text-main: #202020;
--sh-card: 2.08px 2.78px 16px 0px rgba(193,197,210,0.03);
--sh-sheet: 0 -8px 28px rgba(0, 0, 0, 0.18);
}

Hard-coded non-token colors that recur (treat as canon constants):

  • Blue status: #DBEAFE bg / #1E40AF text (in-progress / KaaG / gate-active).
  • Amber status: #FEF3C7 bg / #B45309 text (pending).
  • Red status: #FEE2E2 bg / #B91C1C text (defect/error/danger); red tint rgba(227,38,39,0.06); red outline rgba(227,38,39,0.4).
  • Toast: success #dcfbe2/#05853e, info #e6f4fe/#0d74ce, error #FEE2E2/#B91C1C.
  • Backdrop dark: #0E0F12 (page backdrop AND camera screen bg); sheet backdrop rgba(14,15,18,0.45).
  • Tab-screen page background: #F5F5F7.
  • Primary button hover: #00C45B.
  • Photo-thumb gradient: linear-gradient(135deg, #1f2230 0%, #2c3140 100%).

390×844 iPhone frame, radius 28, centered on a #0E0F12 dark backdrop. Screens are absolutely-stacked .screen divs inside .phone; only .active displays. Screens that live under the bottom nav use .tab-screen (grey #F5F5F7 bg).

┌ body (#0E0F12, flex-centered) ─────────────────────┐
│ ┌ .phone 390×844, r28, overflow hidden ──────────┐ │
│ │ ┌ .screen.active (absolute inset:0, flex col) ┐│ │
│ │ │ .sb — 44px status bar (fixed, top) ││ │
│ │ │ header .rsm-page-head / .rsm-hero (fixed) ││ │
│ │ │ [.rsm-stats — overlaps hero by -52px] ││ │
│ │ │ .rsm-body — flex:1 overflow-y:auto SCROLLS ││ │
│ │ │ nav .rsm-nav — bottom nav (fixed) ││ │
│ │ │ .home-ind — home indicator (absolute) ││ │
│ │ └─────────────────────────────────────────────┘│ │
│ │ .rsm-sheet-overlay ×2 (absolute inset:0, z50) │ │
│ │ .alert-toast (absolute top:56px, z80) │ │
│ └────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘

Scroll model: ONLY .rsm-body scrolls (scrollbar hidden). Status bar, page head/hero, bottom nav, home indicator, sheets and toast are all outside the scroll container. goTo() resets .rsm-body scrollTop to 0 on every navigation.

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

Scroll body (verbatim):

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

Status bar (verbatim):

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

Status-bar markup (light version; dark screens use .sb--dark and #FFFFFF fills instead of #202020):

<div class="sb">
<span class="sb-time">9:41</span>
<div class="sb-icons">
<svg width="17" height="11" viewBox="0 0 17 11" fill="none"><rect x="0" y="5" width="3" height="6" rx="1" fill="#202020"></rect><rect x="4.5" y="3" width="3" height="8" rx="1" fill="#202020"></rect><rect x="9" y="1" width="3" height="10" rx="1" fill="#202020"></rect><rect x="13.5" y="0" width="3" height="11" rx="1" fill="#202020" opacity="0.35"></rect></svg>
<svg width="16" height="12" viewBox="0 0 16 12" fill="none"><path d="M8 9.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3z" fill="#202020"></path><path d="M4.6 6.9a4.8 4.8 0 0 1 6.8 0" stroke="#202020" stroke-width="1.5" fill="none" stroke-linecap="round"></path><path d="M1.5 3.8a8.5 8.5 0 0 1 13 0" stroke="#202020" stroke-width="1.5" fill="none" stroke-linecap="round"></path></svg>
<svg width="25" height="12" viewBox="0 0 25 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="3.5" stroke="#202020" stroke-opacity="0.35"></rect><rect x="2" y="2" width="17" height="8" rx="2" fill="#202020"></rect><path d="M23 4v4a2 2 0 0 0 0-4z" fill="#202020" opacity="0.4"></path></svg>
</div>
</div>

On dark screens the .sb is position:absolute;top:0;left:0;right:0; (splash, camera) or simply first-child of a dark header (hero, onboarding). Every screen ends with <div class="home-ind"></div> (or home-ind--light on dark screens).


.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-tasks')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg><span>Tasks</span></button>
<button class="rsm-nav-tab" type="button" onclick="goTo('s-profile')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg><span>Profile</span></button>
</nav>

Usage: exactly 3 tabs (Home / Tasks / Profile), duplicated verbatim on every tab screen with is-active moved. No badges on nav items in this flow; active state is --g-dark color + weight bump 500→600. Icons are 22px feather-style stroke SVGs (stroke-width 2, currentColor). The nav sits above .home-ind.

3.2 Dashboard hero header (.rsm-hero) — the “topbar” of the home screen

Section titled “3.2 Dashboard hero header (.rsm-hero) — the “topbar” of the home screen”
.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">MO</span>
<div class="rsm-hero-text">
<p class="rsm-hero-name">Muiz</p>
<span class="rsm-hero-role">Kit Assembler</span>
<p class="rsm-hero-meta">Station 3 · Wed, 14 May 2026</p>
</div>
</div>
</div>
</header>

Usage: dark brand-green hero with faint 18%-opacity brand-mark watermark on the right, avatar = brand-green circle with initials in dark text, first-name greeting + role pill + station/date meta. Padding-bottom 64px reserves room for the -52px overlapping stat cards. .rsm-hero-bell (white 40px circular icon button + red dot) is defined but NOT used in this file’s markup — it is available canon.

3.3 Page head (.rsm-page-head) — interior pages

Section titled “3.3 Page head (.rsm-page-head) — interior pages”
.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(asmReturnScreen)" 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">Assembly record</h1>
</div>
</header>

Usage: 56px row, centered 20/600 title; back button is a 40px WHITE circle with a 1px --grey6 border (the one bordered element that is allowed — it is a button, not a card), absolutely positioned left. Root tab pages (Tasks, Profile) use the same head with title only, no back button. There is no sub/actions row and no search anywhere in this flow.

.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); }
.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-section-head">
<h2 class="rsm-section-title">Active assembly</h2>
<span class="rsm-section-meta">8 of 12 scanned</span>
</div>
<div class="rsm-q-day-head"><span>Assigned today · Wed 14 May</span><span class="rsm-q-day-count">2 kits</span></div>

Usage: .rsm-section-head labels dashboard sections (title left, count meta right). .rsm-q-day-head is the uppercase group label inside list panes (date/context left, count right).

3.5 Tabs & segmented controls — TWO variants

Section titled “3.5 Tabs & segmented controls — TWO variants”

Variant A — .km-tabs scrollable filter tabs with count chips (the canonical list-filter control; used on Tasks):

.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-asm-pane][hidden] { display: none; }
<div class="km-tabs" role="tablist">
<button class="km-tab" type="button" aria-pressed="true" data-asm-tab="pending" onclick="setAsmTab(this)">Pending <span class="km-tab-count">2</span></button>
<button class="km-tab" type="button" aria-pressed="false" data-asm-tab="in-progress" onclick="setAsmTab(this)">Active <span class="km-tab-count">1</span></button>
<button class="km-tab" type="button" aria-pressed="false" data-asm-tab="submitted" onclick="setAsmTab(this)">In QA <span class="km-tab-count">2</span></button>
<button class="km-tab" type="button" aria-pressed="false" data-asm-tab="defect" onclick="setAsmTab(this)">Defects <span class="km-tab-count">1</span></button>
<button class="km-tab" type="button" aria-pressed="false" data-asm-tab="closed" onclick="setAsmTab(this)">Closed <span class="km-tab-count">3</span></button>
</div>

Panes are sibling <div data-asm-pane="pending">…</div> blocks, hidden via the hidden attribute. Active tab = white pill on grey track, green text, subtle shadow; count chip flips grey→green tint. setAsmTab also auto-centers the pressed tab in the scrollable track.

Variant B — .rsm-segmented equal-width pill segmented (defined for 2–3-way switches; not instantiated in this file’s markup but part of the shared kit):

.rsm-segmented { display: inline-flex; background: var(--grey3); border-radius: 9px; padding: 3px; width: 100%; }
.rsm-segmented button { flex: 1; appearance: none; background: transparent; border: 0; cursor: pointer; font-family: inherit; font-size: 12px; font-weight: 500; color: var(--grey11); letter-spacing: -0.01em; height: 32px; padding: 0 6px; border-radius: 7px; transition: background 0.15s ease, color 0.15s ease; white-space: nowrap; }
.rsm-segmented button[aria-pressed="true"] { background: var(--white); color: var(--g-dark); font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,0.06); }

No underline/line tabs exist in this flow. Both variants use aria-pressed, white active thumb, --g-dark active text.

3.6 List system — queue cards (.rsm-q-list / .rsm-q-row)

Section titled “3.6 List system — queue cards (.rsm-q-list / .rsm-q-row)”

The mobile equivalent of a table: a stack of white tappable cards, each with a bordered head (icon + id + type / time + status pill) and label:value rows.

.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, Submitted/Completed = 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--submitted { background: var(--g-3); color: var(--g-dark); }
.rsm-q-card-status--closed { background: var(--grey3); color: var(--grey11); }
.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; }
<div class="rsm-q-list">
<button class="rsm-q-row" type="button" onclick="openAsm('k8-0188','s-tasks')">
<div class="rsm-q-card-head">
<div class="rsm-q-card-id-row">
<span class="rsm-q-card-icon rsm-q-card-icon--std">STD</span>
<div class="rsm-q-card-id-text"><p class="rsm-q-card-id">Kit 8 of WO-K-2407-0188</p><p class="rsm-q-card-type">Standard kit · BOM v4</p></div>
</div>
<div class="rsm-q-card-meta"><span class="rsm-q-card-time">Assigned 8:14 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">Components</span><span class="rsm-q-card-row-value">12 to scan</span></div></div>
</button>
</div>

Usage: whole card is a <button>; press feedback is scale(0.995) — no hover-highlight row style. Hairline --grey3 INTERNAL dividers only (head underline + between label:value rows) — these are borders inside the card, never around it. No pagination/footers; lists are grouped under day heads. Values are tabular-nums, label left grey / value right dark.

Primary (brand-green pill, 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 (transparent, 1.5px grey border):

.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; }
<button class="btn-primary btn-primary--lg is-disabled" id="sop-save-cta" disabled type="button" onclick="markSopComplete()">Mark step complete</button>
<button class="btn-secondary btn-secondary--lg" type="button" onclick="closeSubmitSheet()">Cancel</button>

Specs: full-width, 48px (default) / 52px (--lg, used for all screen-level CTAs), radius 8px, 16px/600 label. Disabled = both the disabled attribute AND .is-disabled class (tint --g-3, 45% dark text). No destructive button variant exists — danger is expressed via the red profile row and red pills, not buttons.

Icon buttons (all circular):

  • .rsm-page-back — 40px white circle, 1px --grey6 border (see 3.3).
  • .rsm-hero-bell — 40px white circle, shadow, red 8px dot (see 3.2, unused in markup).
  • .ka-camera-back — 36px, rgba(255,255,255,0.12) on dark, white glyph (camera close).
  • .ka-shutter — 72px white circle, border: 4px solid rgba(255,255,255,0.22), box-shadow: 0 0 0 2px rgba(255,255,255,0.06), :active { transform: scale(0.92); }.
  • .rt-capture-thumb-del — 22px, rgba(0,0,0,0.6) circle over a photo thumb.
  • .input-icon — 20px borderless glyph button inside inputs.
  • .alert-toast-close — borderless glyph, opacity 0.55→1 on hover.

CTA hint text under buttons:

.km-cta-hint { font-size: 11.5px; color: var(--grey10); text-align: center; margin-top: 6px; line-height: 16px; }

CTAs are wrapped in .welcome-cta-wrap (margin-top:auto; padding-top:32px; display:flex; flex-direction:column; gap:16px; align-items:center;), usually overridden inline to style="padding-top: 4px;" on content pages.

All pills: border-radius:100px, weight 600, 10–11px type, tinted bg + strong-tint text. The full color mapping (canon):

State Background Text
Pending #FEF3C7 #B45309
In progress / active #DBEAFE #1E40AF
Submitted / pass / done var(--g-3) #B5F1C2 var(--g-dark) #05853E
Closed / neutral var(--grey3) var(--grey11)
Defect / fail / error #FEE2E2 #B91C1C

Pill families (verbatim):

.rsm-q-card-status { font-size: 10px; font-weight: 600; padding: 2px 9px; border-radius: 100px; flex-shrink: 0; }
/* + --pending/--in-progress/--submitted/--closed/--defect modifiers (see 3.6) */
.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="submitted"] { 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 { 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); }
.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-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; }
.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; }
.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; }
.km-serial-row-pill--scan { background: var(--white); color: var(--g-dark); box-shadow: inset 0 0 0 1.5px var(--g-3); }
<span class="km-wo-pill" data-state="defect">Defect</span>
<span class="rsm-q-card-status rsm-q-card-status--pending">Pending</span>
<span class="km-serial-row-pill"><svg …><polyline points="20 6 9 17 4 12"/></svg>Scanned</span>

Status-dot pattern: no bare dots on pills; the dot pattern here is .rsm-hero-bell-dot (notification red dot) and the .km-gate-dot G-gate tracker (see 3.9b). NOTE: this flow’s pills (100px radius, 600 weight, 10–11px) predate the newer .ds-badge spec (12px/500/6px radius) in DESIGN.md — inside kit-production mobile, THESE pills are the precedent.

3.9 KPI / stat cards (.rsm-stats / .rsm-stat)

Section titled “3.9 KPI / stat cards (.rsm-stats / .rsm-stat)”
/* 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-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">Closed today</p>
<p class="rsm-stat-num"><span>3</span><span class="rsm-stat-num-of">kits</span></p>
</div>
</div>
…
</div>

Usage: exactly 2 cards on the dashboard, overlapping the hero by 52px. Icon = 36px circle, --g-2 tint bg + --g-primary glyph. Value 20px/700 (the sanctioned KPI 700) with a 13px/500 grey unit suffix. No deltas in this flow.

.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; }
.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; }
/* 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; }
<span class="km-gate-dots">
<span class="km-gate-dot" data-state="pass"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></span>
<span class="km-gate-dot" data-state="active">G2</span>
<span class="km-gate-dot" data-state="active">G3</span>
</span>

Usage: dashboard progress bar is 6px (.rt-progress-*); detail-hero progress bar is 8px on --grey2 (.km-wo-hero-progress-*). Gate dots: 20px circles, checkmark svg when passed, “G2”/“G3” text otherwise.

3.10 Cards / panels — NO CARD HAS A BORDER

Section titled “3.10 Cards / panels — NO CARD HAS A BORDER”

Universal card recipe: background: var(--white); border-radius: 14–18px; box-shadow: var(--sh-card); padding: 14–16px; and explicitly border: 0 on button-cards. The ONLY borders anywhere are: internal hairline dividers (--grey3/--grey4), input borders, .btn-secondary, the circular back-button, dashed capture-add tile, and inset-ring pills (box-shadow: inset). Never around a card.

Generic panel (.km-panel) with header pattern:

.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">Components</h2>
<span class="km-panel-meta">8 of 12 scanned</span>
</div>
<div class="km-serial-list">…</div>
</div>

Detail hero card (.km-wo-hero-card):

.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-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; }
<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 6 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="defect">Defect</span>
</div>
</div>

Active-work card (.rt-active-card, dashboard) and inbox snippet (.rsm-inbox-snippet):

.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-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-step-meta { display: flex; align-items: center; justify-content: space-between; font-size: 12px; color: var(--grey11); font-weight: 500; }
.rsm-inbox-snippet { display: flex; align-items: center; gap: 12px; padding: 12px 14px; background: var(--white); border-radius: 14px; box-shadow: var(--sh-card); border: 0; cursor: pointer; font-family: inherit; width: 100%; text-align: left; -webkit-appearance: none; appearance: none; }
.rsm-inbox-snippet-ic { width: 36px; height: 36px; border-radius: 50%; background: #FEE2E2; color: #B91C1C; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.rsm-inbox-snippet-ic svg { width: 18px; height: 18px; }
.rsm-inbox-snippet-text { flex: 1; min-width: 0; }
.rsm-inbox-snippet-title { font-size: 13.5px; font-weight: 600; color: var(--grey12); letter-spacing: -0.05em; }
.rsm-inbox-snippet-sub { font-size: 12px; color: var(--grey11); line-height: 16px; margin-top: 2px; }
.rsm-inbox-snippet-chev { width: 18px; height: 18px; color: var(--grey10); flex-shrink: 0; }
<button class="rsm-inbox-snippet" type="button" onclick="goTo('s-defect-fix')" style="margin-top: 0;">
<span class="rsm-inbox-snippet-ic">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
</span>
<div class="rsm-inbox-snippet-text">
<p class="rsm-inbox-snippet-title">Defect to fix · Kit 6 of WO-K-2407-0188</p>
<p class="rsm-inbox-snippet-sub">Production QA flagged G2 — harness routing</p>
</div>
<svg class="rsm-inbox-snippet-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>

Profile cards:

.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-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 viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg></span>
<span class="rsm-profile-row-label">My station
<span class="rsm-profile-row-sub">Station 3 · Tricycle Hardware · Wed shift</span>
</span>
<span class="rsm-profile-row-tail"><svg 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></span>
</button>
</div>
</div>

Usage: profile groups = uppercase section title outside the card, white card holding rows with left:60px inset dividers. .rsm-profile-row-ic is the sanctioned 10px rounded-square exception. Row tails show a value (“On”, “English”) + chevron. Danger row (Sign out) = red tint icon + red label.

3.11 Overlays — bottom sheet is the ONLY modal surface

Section titled “3.11 Overlays — bottom sheet is the ONLY modal surface”

Confirmations and read-only detail overlays both use the .rsm-sheet bottom sheet (canonical for mobile). There is NO centered dialog, drawer, or dropdown in this flow.

/* 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: 80%; display: flex; flex-direction: column; }
.rsm-sheet-overlay.is-open .rsm-sheet { transform: translateY(0); }
.rsm-sheet-handle-row { display: flex; align-items: center; justify-content: center; padding: 10px 0 6px; flex-shrink: 0; }
.rsm-sheet-handle { width: 40px; height: 4px; border-radius: 100px; background: var(--grey5); }
.rsm-sheet-head { padding: 8px 0 16px; flex-shrink: 0; }
.rsm-sheet-title { font-size: 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; }
<div class="rsm-sheet-overlay" id="sheet-submit-qa">
<div class="rsm-sheet-backdrop" onclick="closeSubmitSheet()"></div>
<div class="rsm-sheet" role="dialog" aria-modal="true" aria-labelledby="submit-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="submit-sheet-title">Submit to Production QA</h2>
<p class="rsm-sheet-sub" id="submit-sheet-sub">Kit 7 of WO-K-2407-0188 · Standard kit</p>
</div>
<div class="rsm-sheet-body">
<div class="ka-check-row">
<span class="ka-check-row-ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></span>
<span class="ka-check-row-text" id="submit-sheet-comp">12 component serials scanned</span>
</div>
<p class="rsm-sheet-note">G1 BOM compliance passed. Production QA will run G2 Visual + G3 Electrical pre-check. The kit returns here if a defect is logged.</p>
</div>
<div class="rsm-sheet-cta">
<button class="btn-primary btn-primary--lg" type="button" onclick="confirmSubmitToQa()">Send for QA</button>
<button class="btn-secondary btn-secondary--lg" type="button" onclick="closeSubmitSheet()">Cancel</button>
</div>
</div>
</div>

Footer button order: primary action FIRST (top), secondary Cancel/Close BELOW, both full-width stacked with 10px gap. Backdrop click closes. Two sheet instances live at .phone level: #sheet-submit-qa (confirmation) and #sheet-evidence (read-only evidence viewer, hydrated by openEvidence()).

There is also the auth sheet-screen (login on dark backdrop; a static layout, not an overlay):

.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; }
.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; }
<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) where variant ∈ 'success' | 'info' | 'error' (default 'success'); auto-hides after 3500ms; hideToast() to dismiss. Single #toast element per page, top-center at top:56px, pill with a white translucent badge + text + close glyph. This IS the canonical DS toast.

Text input (auth):

.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; }
.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; }
<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 muiz.olawale@rechaj.com" value="muiz.olawale@rechaj.com" oninput="onWelcomeInput()">
</div>
</div>

Focus ring = brand border + 4px rgba(1,228,111,0.12) glow via :focus-within on the wrapper. Trailing icon buttons (eye toggle) sit inside .input as .input-icon.

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); }
<div>
<label style="display:block; font-size:12px; font-weight:500; color:var(--grey10); margin-bottom:6px;" for="sop-note">Notes (optional)</label>
<textarea class="rt-note-input" id="sop-note" rows="3" placeholder="Anything noteworthy about this step..."></textarea>
</div>

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>

No <select> and no search input exist in this flow.

None present in this file. All choice UI is tabs, sheets, or navigation.

3.15 Empty state / placeholder (.rt-placeholder)

Section titled “3.15 Empty state / placeholder (.rt-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; }

Defined but not instantiated in this file’s markup (carried from the retrofit kit). Canonical empty state: centered 56px grey circle icon + 18/600 title + 13px capped-width body.

  • Default: CIRCULAR (border-radius:50%), tonal tint — tinted background + colored glyph, never solid-filled: .rsm-stat-ic (36px, --g-2 bg / --g-primary glyph), .rsm-q-card-icon (36px), .rsm-inbox-snippet-ic (36px, red tint), .km-sop-check / .ka-check-row-ic (26px, --g-3/--g-dark), .ka-scan-card-ic (32px), .ka-defect-note-ic (28px, white bg + red inset ring), .rt-placeholder-ic (56px), .rsm-profile-chev (28px), avatars (44/56px, solid brand — avatars are the solid exception).
  • Rounded-square exceptions (12px radius): kit-type marks .rt-active-icon (44px) and .km-wo-hero-mark (46px) — these hold TEXT (“STD”/“KaaG”), not glyphs. 10px radius: .rsm-profile-row-ic (32px, documented DS exception). 14px radius: photo/media thumbs .rt-capture-add / .rt-capture-thumb (88px) and 12px .ka-evi-thumb (76px) — media thumbnails are allowed rounded squares.
  • Error icons use a white circle with a red INSET ring (box-shadow: inset 0 0 0 1.5px rgba(227,38,39,0.4)) rather than a flooded red circle.

3.17 Flow-specific widgets (all part of the canon kit)

Section titled “3.17 Flow-specific widgets (all part of the canon kit)”

Serial/checklist row (.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; border: 0; text-align: left; font-family: inherit; width: 100%; -webkit-appearance: none; appearance: none; }
.km-serial-row--todo { cursor: pointer; transition: background 0.15s ease, transform 0.1s ease; }
.km-serial-row--todo:hover { background: var(--grey3); }
.km-serial-row--todo:active { transform: scale(0.997); }
.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--locked { opacity: 0.5; }

SOP step row (.km-sop-row) with done/active/locked states:

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

Row states: done = green check circle + chevron (tappable → evidence sheet); active/next = numbered grey circle + 1.5px green outline ring; locked = 0.55 opacity, non-interactive. “Completed state is subtle”: done rows stay grey --grey2, only the 26px check circle is green.

Photo capture strip (.rt-capture-*):

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

Instruction block, check row, defect note:

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

Evidence sheet content:

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

Fullscreen camera scanner (#s-component-scan, .ka-camera-*, .ka-viewfinder*, .ka-scan-card):

#s-component-scan { 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: 180px; 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-controls { display: flex; align-items: center; justify-content: space-between; padding: 16px 24px 32px; }
.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); }
.ka-camera-side { width: 84px; flex-shrink: 0; display: flex; justify-content: center; }
.ka-camera-side-btn { background: rgba(255,255,255,0.12); color: rgba(255,255,255,0.78); border: 0; border-radius: 10px; padding: 6px 12px; font-family: inherit; font-size: 11px; font-weight: 500; cursor: pointer; -webkit-appearance: none; appearance: none; line-height: 1.3; text-align: center; }
/* Scan result card — sits below the viewfinder after a scan */
.ka-scan-result { margin: 10px 16px 0; }
.ka-scan-result:empty { display: none; }
.ka-scan-card { background: var(--white); border-radius: 14px; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.ka-scan-card-head { display: flex; align-items: flex-start; gap: 10px; }
.ka-scan-card-ic { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.ka-scan-card--ok .ka-scan-card-ic { background: var(--g-3); color: var(--g-dark); }
.ka-scan-card--block .ka-scan-card-ic { background: var(--white); color: var(--red9); box-shadow: inset 0 0 0 1.5px rgba(227,38,39,0.4); }
.ka-scan-card-ic svg { width: 16px; height: 16px; }
.ka-scan-card-title { font-size: 13.5px; font-weight: 600; }
.ka-scan-card--ok .ka-scan-card-title { color: var(--g-dark); }
.ka-scan-card--block .ka-scan-card-title { color: var(--red9); }
.ka-scan-card-sub { font-size: 11.5px; line-height: 16px; margin-top: 2px; }
.ka-scan-card--ok .ka-scan-card-sub { color: var(--grey11); }
.ka-scan-card--block .ka-scan-card-sub { color: #B91C1C; }
.ka-scan-card-serial { font-size: 12px; font-weight: 600; color: var(--grey12); font-variant-numeric: tabular-nums; background: var(--grey2); border-radius: 8px; padding: 9px 11px; }

Auth/onboarding display pieces:

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

Brand mark (two-tone; splash uses dark-on-green, onboarding uses white+green on dark) — verbatim onboarding version:

<svg class="ob-brand-mark" viewBox="0 0 57 57" fill="none" aria-hidden="true">
<path d="M39.6964 8.4H33.2341c-1.1019 0-1.9953.8934-1.9953 1.9954V26.08H40.8856L19.1794 52.4274H35.6029c1.2188 0 2.3707-.557 3.1278-1.5122L52.8618 33.0847c.475-.5994.5657-1.4176.2333-2.1064L43.2908 10.6568C42.6251 9.277 41.2284 8.4 39.6964 8.4Z" fill="#FFFFFF"></path>
<path d="M17.597 48.0272h6.4623c1.1019 0 1.9953-.8933 1.9953-1.9954V30.3465H16.4077L38.1139 4 21.6905 4c-1.2188 0-2.3707.5569-3.1278 1.512L4.43159 23.3426c-.47504.5993-.56568 1.4176-.23334 2.1065l9.80405 20.3215c.6657 1.3796 2.0624 2.2566 3.59415 2.2566Z" fill="#01E46F"></path>
</svg>

Utility rules:

[data-tab][hidden] { display: none; }
[hidden] { display: none !important; }

10 screens: s-splash, s-onboarding, s-welcome, s-dashboard, s-tasks, s-active-assembly, s-component-scan, s-sop-capture, s-defect-fix, s-profile.

Splash (#s-splash) — brand-dark screen, absolute dark status bar, centered 80px green circle w/ dark brand mark, light home indicator. Whole screen tap → onboarding; auto-advances after 2500ms.

Onboarding (#s-onboarding) — dark sb → .ob-brand (mark + wordmark) → .ob-photo (flex:1 persona photo ./onboarding-photo.png) → .ob-text (24/700 title with green .accent word + tagline) → .ob-cta-wrap w/ “Get started” btn-primary--lg → light home-ind.

Welcome/Login (#s-welcome, .sheet-screen) — dark backdrop, white .sheet rising from 60px: pill → centered 28/700 “Welcome” head → Email field → Password field (eye toggle, “Forgot password?” right-aligned text-link) → Face ID glyph button (goes straight to dashboard) → bottom-pinned Login CTA, disabled until email regex + pw≥4 chars (onWelcomeInput). Login → dashboard.

Dashboard (#s-dashboard, tab-screen) — order: hero (dark, sb + watermark + avatar/name/role/meta) → 2 overlapping .rsm-stats KPI cards (-52px) → .rsm-body (padding-top:16px): ① red defect callout .rsm-inbox-snippet (only when a defect exists) → ② “Active assembly” section head + .rt-active-card (progress bar + step meta) → ③ “Assigned by Production Manager” section head + .rsm-q-list of pending queue cards → bottom nav (Home active) → home-ind. Card click = openAsm(key,'s-dashboard') (opens detail with return-route memory); defect callout → s-defect-fix.

Tasks (#s-tasks, tab-screen) — light sb → page head (title only, centered, no back) → .rsm-body (padding-top:4px): .km-tabs filter row (Pending/Active/In QA/Defects/Closed with counts) → 5 data-asm-pane panes each = .rsm-q-day-head + .rsm-q-list of queue cards. Default selected tab = Pending. Card click → openAsm(key,'s-tasks'); defect card → goTo('s-defect-fix'). Bottom nav (Tasks active).

Assembly record (#s-active-assembly, tab-screen) — the detail page. sb → page head with back (goTo(asmReturnScreen) — back returns to WHERE the record was opened from) + centered title “Assembly record” → #asm-body .rsm-body fully hydrated by renderAsm() in this order: ① .km-wo-hero-card (mark, id, status pill, progress bar, 3-col meta grid) → ② Components .km-panel (serial rows: scanned=green pill / next=Scan pill button / rest=locked) → ③ SOP steps .km-panel (done/active/locked rows + unlock hint) → ④ CTA zone: editable→ Submit-to-QA primary CTA (+hint, disabled until 12/12 + 4/4); submitted→ gate-dots status panel; completed→ all-pass gate-dots + kit-id panel. Detail is a FULL SCREEN (not modal); row states enforce a strict linear order (only the next component is scannable, SOP locked until all scanned).

Component scan (#s-component-scan, dark fullscreen) — absolute dark sb → .ka-camera-shell: head (36px ✕-circle back → assembly record; title “Scan: {component}”; meta “Camera-only · validates In-Stock + unique per kit”) → .ka-viewfinder (tap = scan; grid, 4 corners, center target, uppercase label) → #scan-result slot (.ka-scan-card--ok w/ serial + “Add to kit” btn-primary, or --block w/ “Re-scan” btn-secondary) → controls row (72px shutter center, “Rejected example” side chip). Confirm → addToKit() increments, toasts, returns to record.

SOP step capture (#s-sop-capture, tab-screen) — sb → page head w/ back (exitSopCapture()) + dynamic title “SOP · {step}” / “Edit · {step}” → body: .ka-instr instruction block → .rt-capture-section (heading “Step evidence”, add-photo tile + thumbs w/ delete, hint “Camera-only.”) → Notes textarea → CTA “Mark step complete”/“Save changes”, disabled until ≥1 photo (refreshSopCta). Save → toast + back to record.

Defect to fix (#s-defect-fix, tab-screen) — sb → page head w/ back (→ dashboard) → body: kit .km-wo-hero-card (Defect pill) → .km-panel “Defect logged by Production QA” (label:value rows: Inspector/Gate failed/Area; .ka-defect-note red note; QA photo thumb + hint) → CTA “Reopen assembly to fix” + hint. Reopen mutates state, opens record, toasts info.

Profile (#s-profile, tab-screen) — sb → page head (title only) → body: .rsm-profile-user card (avatar 56, name, role pill, email, chevron) → 4 grouped sections (Workspace / Account / Preferences / Support) each = uppercase .rsm-profile-section-title + white .rsm-profile-section card of rows (icon square, label+sub, tail value+chevron or .rsm-toggle) → Sign out = --danger row → bottom nav (Profile active).

Interaction rules (canon):

  • Row/card click opens a FULL-SCREEN detail via openAsm(key, returnScreen); the back arrow honors the origin screen (asmReturnScreen).
  • Detail screens are single templates hydrated per-entity from a data map (ASM_DATA) — status drives which affordances render (a Submitted kit never shows editing affordances).
  • Confirmations = bottom sheet (.rsm-sheet-overlay.is-open), primary on top, cancel below; backdrop click dismisses.
  • Read-only evidence = the same bottom sheet, with optional “Edit submission” primary that appears only while the record is still editable.
  • Every action gives a toast (showToast); success toasts on scans/step completion/submission; info toasts on reopen.
  • No search, no dropdowns, no pagination anywhere.
  • Unbuilt destinations toast “Coming soon” (showToast('Edit profile', 'Coming soon', 'info')).
  • Deep-linking: ?screen=s-xxx URL param jumps straight to a screen (skips splash).

State (globals):

const SCREENS = ['s-splash','s-onboarding','s-welcome','s-dashboard','s-tasks','s-active-assembly','s-component-scan','s-sop-capture','s-defect-fix','s-profile'];
const ASM_COMPONENTS = { standard: [ [name, spec, serial] ×12 ], kaag: /* standard + Inverter spliced after Buck converter, 13 items */ };
const ASM_SOP = [ [name, shortResult, instruction] ×4 ]; // Alignment, Torque, Harness routing, Electrical pre-check
const ASM_DATA = { 'k7-0188': { id, wo, type:'standard'|'kaag', bom, status:'pending'|'in-progress'|'submitted'|'defect'|'completed', statusLabel, scanned, sopDone, started, closed?, kitId? }, … };
let currentAsmKey = 'k7-0188';
let asmReturnScreen = 's-dashboard';
const sopNotes = {}; // per-step notes persisted across edit sessions
let sopPhotoCount = 0;
let sopEditingIdx = null; // non-null = capture screen is in edit mode
let _toastTimer = null;

Navigation & tabs:

  • goTo(screenId) — hides all SCREENS, shows target, adds .screen-enter (removed after 320ms), resets its .rsm-body scrollTop.
  • setTab(btn) — generic [data-tab-btn]/[data-tab] pane switcher (defined; unused in markup).
  • setAsmTab(btn) — Tasks filter: aria-pressed sync, pane hidden toggling via data-asm-tab/data-asm-pane, auto-centers pressed tab in scroll track, scrolls body to top.

Assembly record:

  • openAsm(key, returnScreen) — sets currentAsmKey + asmReturnScreen, renderAsm(), goTo('s-active-assembly').
  • renderAsm() — hydrates #asm-body (hero + components panel + SOP panel + CTA) from ASM_DATA[currentAsmKey].
  • asmTotal(a) — 13 for kaag else 12.

Scanner:

  • openScan() / resetScan() / doScan() / showRejectedScan() / addToKit().

SOP capture:

  • openSop() (fresh capture for next step) / editSopSubmission(idx) (prefilled edit mode) / exitSopCapture() (clears edit state, back) / sopCapturePhoto() / deleteSopPhoto(btn) / refreshSopCta() / markSopComplete() (edit mode saves note only; normal mode bumps sopDone) / _sopThumbHTML().

Sheets:

  • submitToQa() → fills + opens #sheet-submit-qa; closeSubmitSheet(); confirmSubmitToQa() — mutates status→submitted, closes, toasts, setTimeout(() => goTo('s-tasks'), 350).
  • openEvidence(opts) — { title, sub, result, resultState, photos, note, noteDefect, onEdit }; closeEvidence(); openSopEvidence(idx) wraps it for SOP steps.

Misc:

  • reopenDefect() — resets k6-0188 to in-progress w/ sopDone=2, opens record, info toast.
  • showToast(message, badge, variant) / hideToast() — variant ∈ success|info|error; 3500ms auto-hide.
  • onWelcomeInput(), togglePw(inputId), attemptLogin(), toggleSwitch(btn) (flips aria-pressed).
  • initSplash() IIFE — honors ?screen= param; else splash → onboarding after 2500ms.

Class prefixes:

  • rsm- — shared mobile shell kit (hero, body, nav, page head, queue cards, segmented, sheet, profile, stats, toggle). Inherited from the Retrofit Station Manager flow.
  • km- — Kit-production shared patterns consistent with the PM flow (tabs, panels, WO hero card, pills, serial rows, SOP rows, gate dots, cta-hint).
  • ka- — Kit-Assembler-specific widgets (camera, scan cards, evidence, instruction, check row, defect note).
  • rt- — retrofit-inherited widgets (active card, progress bar, capture row, note input, placeholder).
  • ob- / welcome- / sheet / splash- / field- / input — auth screens (replicated 1:1 from retrofit personas).
  • sb — status bar; btn- — buttons; alert-toast — toast; home-ind — home indicator.
  • Screen IDs: s-<kebab>. Sheet IDs: sheet-<kebab>. Element IDs: <screen>-<part> (e.g. sop-save-cta, submit-sheet-sub, welcome-login-btn).
  • State attrs: aria-pressed for tabs/toggles, data-state for pill/gate variants, data-asm-tab/data-asm-pane pairs, hidden attribute + [hidden]{display:none!important} for panes, .is-active (nav), .is-open (sheets), .is-visible (toast), .is-disabled (buttons), --todo/--active/--tappable/--locked row-state modifiers.

Personas: Muiz Olawale — Kit Assembler (initials “MO”, muiz.olawale@rechaj.com, Station 3, Tricycle Hardware, Wed shift). Production Manager: Erioluwa Morenikeji. Production QA inspector: Adaeze Nwosu.

ID formats:

  • Work orders: WO-K-2407-0188 (WO-K-YYMM-seq); kits referenced as “Kit 7 of WO-K-2407-0188”.
  • Kit ids (generated on closure): kit-K-88301-WS-0012.
  • Component serials: manufacturer MTR-A4-22914-7K9, MCU-V3-88102-031; Rechaj-native RBN-88412-WH-014; lot SAX-LOT-22107, FST-LOT-11902.
  • Data keys: k7-0188 (kit number + WO suffix). BOMs: “BOM v4” / “BOM v2 KaaG”. Gates: G1 (BOM compliance), G2 (Visual), G3 (Electrical pre-check). Middot · is the universal meta separator (never em dash in copy; note two body-copy em dashes exist in QA note strings, see §7).

Where 700 appears (complete list):

  • .sb-time 15px — iOS clock (sanctioned exception).
  • .rsm-stat-num 20px — KPI digits (sanctioned).
  • .km-wo-hero-progress-val 15px — progress fraction “8 / 12” (display number, sanctioned).
  • .ob-brand-name 22px, .ob-title 24px, .welcome-title 28px — splash/onboarding/welcome display titles (sanctioned).
  • .km-gate-dot 9px font-weight:700 — “G2”/“G3” letters inside 20px gate dots. Borderline: these are letters, not KPI digits — flag if reused; inside this file it is established precedent for gate dots only.
  • Everything else — names, WO ids, titles, buttons, pills, panel titles, avatars — is 600. Body/meta 400–500. This matches the “bold sparingly” rule.

Spacing rhythm:

  • Screen gutter: 16px (.rsm-body padding 0 16px 24px); sheets and auth use 24px gutters.
  • Vertical rhythm between body sections: 18px (.rsm-body gap); queue-card list gap 14px; in-panel gap 12px; sub-list row gap 6px; icon-to-text gap 10–12px.
  • Card padding: 16px (panels/heroes), 14px (queue cards, scan cards, instr), 12px (check/defect/serial rows at 10px×12px).
  • Radii scale: 100px/999px pills · 28px phone · 24px sheets · 18px hero cards + viewfinder · 16px panels/queue/stat cards · 14px snippet/scan/capture thumbs · 12px kit marks/evi-thumbs/tabs-track/instr/textarea · 10px sub-rows/profile-ic · 8px buttons/inputs/tabs/serial-chip · 9px/7px segmented track/thumb.

Off-grid values observed IN this file (violations of the strict 4px grid to be aware of — do not propagate beyond matching this file):

  • Half-pixel font sizes throughout: 10.5 / 11.5 / 12.5 / 13.5 / 14.5px (deliberate micro-type scale, canon here).
  • Odd paddings: .ka-scan-card-serial padding 9px 11px; .ka-evi-result padding 4px 11px; pill paddings 2px 9px / 3px 9px; .sb-icons gap 5px; .km-tab gap 5px; .home-ind height 5px.
  • 2px-grid (not 4px) values are pervasive and canonical: 6, 10, 14, 18px gaps/paddings.
  • --sh-card uses fractional offsets (2.08px 2.78px 16px) at 3% alpha — cards read as effectively flat; separation comes from white-on-grey (#F5F5F7 page) contrast, consistent with the “no strokes, tinted separation” rule.
  • Two em dashes appear in UI copy strings (“Blocked — serial not In-Stock”, “Harness not secured … — re-route”, “Assembly reopened — redo harness routing”, “All components scanned — G1 passed”, “Kit closed — kit-K-…”, “Standard v4 · KaaG v2 — build reference”, “Production QA flagged G2 — harness routing”): these predate the no-em-dash rule; new work should use · or commas.
  • Inline style overrides are accepted canon for one-off spacing: style="padding-top: 16px;" / "padding-top: 4px;" on .rsm-body, style="margin-top: 0;", and inline-styled paragraphs inside the JS-rendered submitted/closed panels.