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">1. Tokens
Section titled “1. Tokens”Verbatim :root block:
:root { --brand: #01E46F; --brand-dark: #013F1F; --g-primary: #01E46F; --g-dark: #05853E; --g-2: #DCFBE2; --g-3: #B5F1C2; --grey1: #FAFAFA; --grey2: #F9F9F9; --grey3: #E4E4E4; --grey4: #E8E8E8; --grey5: #E0E0E0; --grey6: #D9D9D9; --grey7: #B0B0B0; --grey9: #A0A0A0; --grey10: #888888; --grey11: #555555; --grey12: #202020; --red9: #E32627; --amber9: #B45309; --white: #FFFFFF; --text-main: #202020; --sh-card: 2.08px 2.78px 16px 0px rgba(193,197,210,0.03); --sh-sheet: 0 -8px 28px rgba(0, 0, 0, 0.18);}Hard-coded non-token colors that recur (treat as canon constants):
- Blue status:
#DBEAFEbg /#1E40AFtext (in-progress / KaaG / gate-active). - Amber status:
#FEF3C7bg /#B45309text (pending). - Red status:
#FEE2E2bg /#B91C1Ctext (defect/error/danger); red tintrgba(227,38,39,0.06); red outlinergba(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 backdroprgba(14,15,18,0.45). - Tab-screen page background:
#F5F5F7. - Primary button hover:
#00C45B. - Photo-thumb gradient:
linear-gradient(135deg, #1f2230 0%, #2c3140 100%).
2. Shell
Section titled “2. Shell”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).
3. Atoms
Section titled “3. Atoms”3.1 Bottom nav (.rsm-nav / .rsm-nav-tab)
Section titled “3.1 Bottom nav (.rsm-nav / .rsm-nav-tab)”.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.
3.4 Section heads & day heads
Section titled “3.4 Section heads & day 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); }.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.
3.7 Buttons
Section titled “3.7 Buttons”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--grey6border (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.
3.8 Badges & status pills
Section titled “3.8 Badges & status pills”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.
3.9b Progress + gate dots
Section titled “3.9b Progress + gate dots”.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; }3.12 Toast (.alert-toast)
Section titled “3.12 Toast (.alert-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; }<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.
3.13 Inputs & forms
Section titled “3.13 Inputs & forms”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.
3.14 Dropdown menus
Section titled “3.14 Dropdown menus”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.
3.16 Icon-container convention
Section titled “3.16 Icon-container convention”- Default: CIRCULAR (
border-radius:50%), tonal tint — tinted background + colored glyph, never solid-filled:.rsm-stat-ic(36px,--g-2bg /--g-primaryglyph),.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; }4. Page anatomy
Section titled “4. Page anatomy”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-xxxURL param jumps straight to a screen (skips splash).
5. JS contracts
Section titled “5. JS contracts”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-checkconst 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 sessionslet sopPhotoCount = 0;let sopEditingIdx = null; // non-null = capture screen is in edit modelet _toastTimer = null;Navigation & tabs:
goTo(screenId)— hides all SCREENS, shows target, adds.screen-enter(removed after 320ms), resets its.rsm-bodyscrollTop.setTab(btn)— generic[data-tab-btn]/[data-tab]pane switcher (defined; unused in markup).setAsmTab(btn)— Tasks filter: aria-pressed sync, panehiddentoggling viadata-asm-tab/data-asm-pane, auto-centers pressed tab in scroll track, scrolls body to top.
Assembly record:
openAsm(key, returnScreen)— setscurrentAsmKey+asmReturnScreen,renderAsm(),goTo('s-active-assembly').renderAsm()— hydrates#asm-body(hero + components panel + SOP panel + CTA) fromASM_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 bumpssopDone) /_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)(flipsaria-pressed).initSplash()IIFE — honors?screen=param; else splash → onboarding after 2500ms.
6. Naming
Section titled “6. Naming”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-pressedfor tabs/toggles,data-statefor pill/gate variants,data-asm-tab/data-asm-panepairs,hiddenattribute +[hidden]{display:none!important}for panes,.is-active(nav),.is-open(sheets),.is-visible(toast),.is-disabled(buttons),--todo/--active/--tappable/--lockedrow-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-nativeRBN-88412-WH-014; lotSAX-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).
7. Weight & spacing discipline
Section titled “7. Weight & spacing discipline”Where 700 appears (complete list):
.sb-time15px — iOS clock (sanctioned exception)..rsm-stat-num20px — KPI digits (sanctioned)..km-wo-hero-progress-val15px — progress fraction “8 / 12” (display number, sanctioned)..ob-brand-name22px,.ob-title24px,.welcome-title28px — splash/onboarding/welcome display titles (sanctioned)..km-gate-dot9px 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-bodypadding0 16px 24px); sheets and auth use 24px gutters. - Vertical rhythm between body sections: 18px (
.rsm-bodygap); 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-serialpadding9px 11px;.ka-evi-resultpadding4px 11px; pill paddings2px 9px/3px 9px;.sb-iconsgap 5px;.km-tabgap 5px;.home-indheight 5px. - 2px-grid (not 4px) values are pervasive and canonical: 6, 10, 14, 18px gaps/paddings.
--sh-carduses fractional offsets (2.08px 2.78px 16px) at 3% alpha — cards read as effectively flat; separation comes from white-on-grey (#F5F5F7page) 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.