docs(prd): learner-first mobile app PRD and interactive prototypes (#916)

* docs(prd): add learner-first mobile app PRD and interactive prototypes

Draft the ClassroomIO iOS/Android companion as a learner-first product
(attention home, compliance wallet, offline content, push on top of
prd/notification-system) with clickable HTML prototypes as the UX source
of truth.

Co-authored-by: Rotimi Best <[email protected]>

* style(prd): quiet mobile app prototype colours to match @cio/ui

Replace filled red/orange/green surfaces with the dashboard Badge and Alert recipes: 10% tint chips, card-background alerts, and default rows. Keep destructive fills only for actual destroy actions.

Co-authored-by: Rotimi Best <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Rotimi Best
2026-08-18 12:02:35 +01:00
committed by GitHub
co-authored by Rotimi Best Cursor Agent
parent 368a7fe5ae
commit 967e81d33f
20 changed files with 6327 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+203
View File
@@ -0,0 +1,203 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sign in — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Entry</span>
<h1>Sign in &amp; organization</h1>
<p>
Email/password, Google, and enterprise SSO all run through Better Auth's Expo client — the session cookie lives in
<code>expo-secure-store</code> and OAuth returns through the <code>classroomio://</code> deep link. Multi-org accounts
pick an organization, which sets the <code>cio-org-id</code> header for every later request and re-themes the app.
Self-hosted installs point the same binary at their own API and skip the picker.
</p>
<div class="proto-links">
<a href="home.html">Next: Home →</a>
<a href="profile.html">Org switcher (Profile)</a>
<a href="downloads.html">Offline entry</a>
</div>
</header>
<div class="stage">
<!-- 1. SIGN IN -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="screen centered">
<div style="text-align: center; margin-bottom: 26px">
<div class="avatar brand lg" style="margin: 0 auto 14px; width: 60px; height: 60px; border-radius: 16px; font-size: 24px">C</div>
<div style="font-size: 21px; font-weight: 600; letter-spacing: -0.02em">Welcome back</div>
<div class="text-muted" style="font-size: 13.5px; margin-top: 4px">Sign in to continue your training</div>
</div>
<div class="field">
<label class="label">Work email</label>
<input class="input" type="email" value="[email protected]" readonly />
</div>
<div class="field">
<label class="label">Password</label>
<input class="input" type="password" value="············" readonly />
</div>
<button class="btn btn-primary btn-lg btn-block" style="margin-top: 4px">Sign in</button>
<div class="row" style="margin: 18px 0 16px">
<div class="divider" style="flex: 1; margin: 0"></div>
<span class="text-muted" style="font-size: 11.5px">OR</span>
<div class="divider" style="flex: 1; margin: 0"></div>
</div>
<div class="stack">
<button class="btn btn-outline btn-lg btn-block">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 8v8M8 12h8"/></svg>
Continue with Google
</button>
<button class="btn btn-outline btn-lg btn-block">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
Sign in with SSO
</button>
</div>
<div style="text-align: center; margin-top: 22px">
<a class="text-primary" style="font-size: 13px; font-weight: 500">Forgot password?</a>
</div>
<div style="text-align: center; margin-top: 14px">
<a class="text-muted" style="font-size: 12px">Use a custom server</a>
<div class="hint" style="margin-top: 3px">Self-hosted ClassroomIO</div>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Sign in</b>Three auth paths in one screen. "Use a custom server" is how a self-hosted deployment points the same app at its own API.</figcaption>
</figure>
<!-- 2. ORG PICKER -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:42"></div>
<div class="navbar">
<div>
<div class="title">Choose organization</div>
<div class="sub">You're a member of 3 academies</div>
</div>
</div>
<div class="screen">
<div class="stack">
<a class="lrow current" href="home.html">
<div class="avatar brand" style="background: oklch(0.488 0.243 264.376)">NL</div>
<div class="grow">
<div class="ltitle">Northwind Logistics</div>
<div class="lmeta"><span class="badge badge-primary-soft">Learner</span><span>4 courses in progress</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="tutor-grading.html">
<div class="avatar brand" style="background: oklch(0.424 0.199 265.638)">AH</div>
<div class="grow">
<div class="ltitle">Acme Health Academy</div>
<div class="lmeta"><span class="badge badge-success-soft">Tutor</span><span>12 submissions to grade</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="home.html">
<div class="avatar brand" style="background: oklch(0.546 0.245 262.881)">MP</div>
<div class="grow">
<div class="ltitle">Meridian Partner Network</div>
<div class="lmeta"><span class="badge badge-primary-soft">Learner</span><span>Certification expires in 21 days</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<div class="banner banner-muted" style="margin-top: 18px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
<div>
<div class="bd" style="color: var(--muted-foreground)">
Each organization keeps its own courses, branding, and records. You can switch any time from your profile.
</div>
</div>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Organization picker</b>Only for multi-org accounts. The choice sets <code>cio-org-id</code> and re-themes the app; single-org and self-hosted users never see it.</figcaption>
</figure>
<!-- 3. BIOMETRIC + OFFLINE LAUNCH -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="7:02" data-offline></div>
<div class="offline-strip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 2 22 22M8.5 16.5a5 5 0 0 1 7 0M5 13a10 10 0 0 1 4-2.4M12 20h.01"/></svg>
You're offline — showing downloaded content
</div>
<div class="screen centered">
<div style="text-align: center">
<div
style="width: 88px; height: 88px; border-radius: 999px; margin: 0 auto 20px; display: grid; place-items: center; background: color-mix(in oklab, var(--primary) 12%, transparent); color: var(--primary)"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="40" height="40" stroke-width="1.6">
<path d="M12 11c0 3-1 5.5-2 7"/><path d="M5 11a7 7 0 0 1 12-5"/><path d="M5 15v-4"/>
<path d="M8.5 20a12 12 0 0 0 1.5-6 2 2 0 1 1 4 0 12 12 0 0 0 .5 3.5"/><path d="M19 11a7 7 0 0 0-.5-2.6"/>
<path d="M17.5 19a20 20 0 0 0 .5-5"/>
</svg>
</div>
<div style="font-size: 20px; font-weight: 600; letter-spacing: -0.02em">Unlock ClassroomIO</div>
<div class="text-muted" style="font-size: 13.5px; margin-top: 6px; line-height: 1.5; padding: 0 12px">
Use Face ID to open your saved session. Your training records stay on this device until you reconnect.
</div>
<button class="btn btn-primary btn-lg btn-block" style="margin-top: 26px">Unlock with Face ID</button>
<button class="btn btn-ghost btn-block" style="margin-top: 8px">Use password instead</button>
</div>
<a class="card card-pad row" href="downloads.html" style="margin-top: 30px; text-align: left">
<div class="media is-current">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>
</div>
<div class="grow">
<div class="ltitle">3 courses available offline</div>
<div class="lsub">Forklift Safety, Fire Safety, GDPR Essentials</div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Biometric re-entry, offline launch</b>Face ID unlocks an existing stored session — it never replaces sign-in. Launching with no network goes straight to downloaded content instead of an error.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+199
View File
@@ -0,0 +1,199 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Certificates — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Proof</span>
<h1>Certificate wallet</h1>
<p>
Proof of training in the learner's pocket — the thing a supervisor, an auditor, or a client site actually asks for.
Reads the existing <code>packages/certificates</code> records, stays viewable offline once downloaded, and shares as
a PDF with a verification link and QR.
</p>
<div class="proto-links">
<a href="compliance.html">← Compliance</a>
<a href="course.html">Earn another →</a>
<a href="profile.html">Profile →</a>
</div>
</header>
<div class="stage">
<!-- 1. WALLET -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<a class="icon-btn plain" href="compliance.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title">Certificates</div><div class="sub">6 issued · 1 expiring soon</div></div>
</div>
<div class="screen">
<div class="chips" data-select style="margin: 10px 0 14px">
<button class="chip on" data-option>All</button>
<button class="chip" data-option>Valid</button>
<button class="chip" data-option>Expiring</button>
<button class="chip" data-option>Expired</button>
</div>
<div class="stack">
<div class="card" style="overflow: hidden">
<div class="card-pad">
<div class="row row-top">
<div class="media lg is-warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></div>
<div class="grow">
<div class="ltitle" style="font-size: 15px">Hazardous Materials Handling</div>
<div class="lsub">Issued 8 Sep 2025 · Northwind Logistics</div>
<div class="lmeta"><span class="badge badge-warning-soft">Expires in 21 days</span></div>
</div>
</div>
</div>
<div class="card-pad row" style="gap: 8px; padding-top: 0">
<button class="btn btn-outline btn-sm" style="flex: 1">View</button>
<button class="btn btn-outline btn-sm" style="flex: 1">Share</button>
<button class="btn btn-primary btn-sm" style="flex: 1.2">Renew</button>
</div>
</div>
<a class="lrow" href="certificates.html">
<div class="media lg is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></div>
<div class="grow">
<div class="ltitle">Data Protection &amp; GDPR Essentials</div>
<div class="lmeta"><span>Issued 16 Aug 2026</span><span class="sep">·</span><span class="tnum">CIO-GDPR-4F91</span></div>
<div class="lmeta"><span class="badge badge-success-soft">Valid until 16 Aug 2027</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="certificates.html">
<div class="media lg is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></div>
<div class="grow">
<div class="ltitle">Manual Handling &amp; Ergonomics</div>
<div class="lmeta"><span>Issued 2 Jun 2026</span><span class="sep">·</span><span class="tnum">CIO-MHE-8C22</span></div>
<div class="lmeta"><span class="badge badge-success-soft">No expiry</span><span class="badge badge-outline">Saved offline</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="certificates.html">
<div class="media lg is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></div>
<div class="grow">
<div class="ltitle">Site Induction — Meridian Depot</div>
<div class="lmeta"><span>Issued 4 Mar 2026</span><span class="sep">·</span><span class="tnum">CIO-SI-1A70</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<div class="lrow" style="opacity: 0.65">
<div class="media lg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></div>
<div class="grow">
<div class="ltitle">Forklift Safety Recertification 2025</div>
<div class="lmeta"><span class="badge badge-danger-soft">Expired 17 Aug 2026</span></div>
</div>
</div>
</div>
</div>
<nav class="tabbar" data-tabbar="compliance"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Wallet</b>Sorted by what needs attention. Expiring certificates get a Renew action that jumps straight into the refresher course.</figcaption>
</figure>
<!-- 2. DETAIL -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:43"></div>
<div class="navbar">
<a class="icon-btn plain" href="certificates.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Certificate</div></div>
<button class="icon-btn" aria-label="Share"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="m16 6-4-4-4 4M12 2v13"/></svg></button>
</div>
<div class="screen">
<!-- rendered certificate -->
<div
class="card"
style="margin: 12px 0 14px; padding: 22px 18px; text-align: center; border-width: 2px; border-color: color-mix(in oklab, var(--primary) 30%, transparent)"
>
<div class="avatar brand" style="margin: 0 auto 12px; width: 38px; height: 38px">NL</div>
<div class="eyebrow">Northwind Logistics Academy</div>
<div style="font-size: 12.5px; color: var(--muted-foreground); margin: 12px 0 4px">This certifies that</div>
<div style="font-size: 19px; font-weight: 600; letter-spacing: -0.02em">Amara Okafor</div>
<div style="font-size: 12.5px; color: var(--muted-foreground); margin: 10px 0 4px">has successfully completed</div>
<div style="font-size: 15.5px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.35">
Data Protection &amp; GDPR Essentials
</div>
<div class="lsub" style="margin-top: 8px">16 August 2026 · Final score 92%</div>
<div class="divider" style="margin: 16px 0 14px"></div>
<div class="row" style="justify-content: center; gap: 14px">
<div style="width: 62px; height: 62px; border-radius: 8px; border: 1px solid var(--border); display: grid; place-items: center; background: var(--muted)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="30" height="30" stroke-width="1.7">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
<path d="M14 14h3v3h-3zM18 18h3v3h-3z"/>
</svg>
</div>
<div style="text-align: left">
<div class="eyebrow">Verify</div>
<div class="tnum" style="font-size: 12px; font-weight: 600; margin-top: 3px">CIO-GDPR-4F91-2026</div>
<div class="lsub" style="margin-top: 2px">verify.classroomio.com</div>
</div>
</div>
</div>
<div class="row" style="gap: 8px; margin-bottom: 14px">
<button class="btn btn-primary btn-lg" style="flex: 1">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="m16 6-4-4-4 4M12 2v13"/></svg>
Share PDF
</button>
<button class="btn btn-outline btn-lg" style="flex: 1">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>
Save
</button>
</div>
<div class="card card-pad">
<div class="kv"><span class="k">Issued to</span><span class="v">Amara Okafor</span></div>
<div class="kv"><span class="k">Issued by</span><span class="v">Northwind Logistics</span></div>
<div class="kv"><span class="k">Issue date</span><span class="v">16 Aug 2026</span></div>
<div class="kv"><span class="k">Valid until</span><span class="v">16 Aug 2027</span></div>
<div class="kv"><span class="k">Certificate ID</span><span class="v tnum" style="font-size: 12.5px">CIO-GDPR-4F91-2026</span></div>
<div class="kv"><span class="k">Available offline</span><span class="v"><span class="badge badge-success-soft">Yes</span></span></div>
</div>
<div class="hint" style="text-align: center; margin-top: 12px">
Anyone with the ID can confirm this certificate is genuine without you sending a file.
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Detail &amp; verification</b>The rendered certificate, share sheet, and a verification ID/QR — so a site supervisor can check it on the spot.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+321
View File
@@ -0,0 +1,321 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Community — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Tab 4</span>
<h1>Community &amp; newsfeed</h1>
<p>
Org and course Q&amp;A plus cohort announcements, backed by the existing <code>/community</code> and
<code>/course/:id/newsfeed</code> routes. There is no direct messaging in ClassroomIO, so threaded Q&amp;A is the
discussion surface — and offline it degrades to read-only with the compose box explicitly disabled.
</p>
<div class="proto-links">
<a href="notifications.html">Notifications →</a>
<a href="tutor-course.html">Tutor moderation →</a>
<a href="home.html">← Home</a>
</div>
</header>
<div class="stage">
<!-- 1. FEED -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="12:20"></div>
<div class="navbar">
<div class="grow"><div class="title">Community</div></div>
<button class="icon-btn" aria-label="Search"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg></button>
<button class="icon-btn" aria-label="New post"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 5v14M5 12h14"/></svg></button>
</div>
<div class="tabs" data-select>
<button class="on" data-option>For you</button>
<button data-option>Q&amp;A</button>
<button data-option>Announcements</button>
<button data-option>My threads</button>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 12px 0 10px; border-color: color-mix(in oklab, var(--primary) 30%, transparent)">
<div class="row row-top">
<div class="avatar">DM</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13.5px">Daniel Mensah <span class="badge badge-primary-soft" style="margin-left: 4px">Tutor</span></div>
<span class="text-muted" style="font-size: 11px">2h</span>
</div>
<div class="lmeta" style="margin-top: 1px"><span>Announcement · Q3 Frontline Onboarding</span></div>
<div style="font-size: 13.5px; line-height: 1.55; margin-top: 7px">
The practical forklift assessment moves to <strong>Bay 4</strong> this Thursday. Bring your completion code
from Lesson 12 — we can't sign anyone off without it.
</div>
<div class="row" style="margin-top: 10px; gap: 14px">
<span class="lmeta" style="margin: 0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7 10v12M15 5.9 14 10h5.8a2 2 0 0 1 2 2.3l-1.4 8a2 2 0 0 1-2 1.7H7V10l4-8a2.7 2.7 0 0 1 4 2z"/></svg> 12</span>
<span class="lmeta" style="margin: 0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg> 4 replies</span>
<span class="badge badge-primary-soft" style="margin-left: auto">Mentions you</span>
</div>
</div>
</div>
</div>
<div class="sec-head" style="margin-top: 14px"><h2>Questions you follow</h2></div>
<div class="stack">
<div class="card card-pad">
<div class="row row-top">
<div class="avatar">TA</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13.5px">Tomás Alvarez</div>
<span class="text-muted" style="font-size: 11px">5h</span>
</div>
<div class="lmeta" style="margin-top: 1px"><span>Forklift Safety Recertification · Lesson 4</span></div>
<div style="font-size: 14px; font-weight: 500; margin-top: 6px; line-height: 1.4">
Does a hairline crack at the fork heel always mean tag-out?
</div>
<div class="lrow" style="margin-top: 10px; background: var(--muted); border: none">
<div class="avatar sm">DM</div>
<div class="grow">
<div style="font-size: 12.5px; line-height: 1.45">
Yes — any crack at the heel is a tag-out. Photograph it, log it, and tell your supervisor before the
next shift.
</div>
<div class="lmeta"><span class="badge badge-success-soft">Answered by tutor</span></div>
</div>
</div>
<div class="row" style="margin-top: 10px; gap: 14px">
<span class="lmeta" style="margin: 0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7 10v12M15 5.9 14 10h5.8a2 2 0 0 1 2 2.3l-1.4 8a2 2 0 0 1-2 1.7H7V10l4-8a2.7 2.7 0 0 1 4 2z"/></svg> 8</span>
<span class="lmeta" style="margin: 0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg> 3</span>
<span class="lmeta" style="margin: 0 0 0 auto"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg> Following</span>
</div>
</div>
</div>
</div>
<div class="card card-pad">
<div class="row row-top">
<div class="avatar">PK</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13.5px">Priya Kaur</div>
<span class="text-muted" style="font-size: 11px">1d</span>
</div>
<div class="lmeta" style="margin-top: 1px"><span>GDPR Essentials · General</span></div>
<div style="font-size: 14px; font-weight: 500; margin-top: 6px; line-height: 1.4">
If a customer asks for their data verbally at the counter, does the 30-day clock start?
</div>
<div class="row" style="margin-top: 10px; gap: 14px">
<span class="lmeta" style="margin: 0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7 10v12M15 5.9 14 10h5.8a2 2 0 0 1 2 2.3l-1.4 8a2 2 0 0 1-2 1.7H7V10l4-8a2.7 2.7 0 0 1 4 2z"/></svg> 5</span>
<span class="lmeta" style="margin: 0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg> 2</span>
<span class="badge badge-warning-soft" style="margin-left: auto">Unanswered</span>
</div>
</div>
</div>
</div>
</div>
<div class="sec-head"><h2>Ask your own</h2></div>
<div class="card card-pad">
<div class="row">
<div class="avatar">AO</div>
<div class="grow"><div class="text-muted" style="font-size: 13.5px">Ask a question about a lesson…</div></div>
<button class="icon-btn" aria-label="Attach"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m21.4 11.1-9.2 9.2a5 5 0 0 1-7-7l9.2-9.2a3.3 3.3 0 1 1 4.7 4.7l-9.2 9.2a1.7 1.7 0 0 1-2.4-2.4l8.5-8.5"/></svg></button>
</div>
</div>
</div>
<nav class="tabbar" data-tabbar="community" data-badges="community:3"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>For you</b>Mentions, followed threads, and announcements in one stream. Tutor answers are marked so learners can trust them at a glance.</figcaption>
</figure>
<!-- 2. THREAD -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="12:24"></div>
<div class="navbar bordered">
<a class="icon-btn plain" href="community.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Question</div><div class="sub">Forklift Safety · Lesson 4</div></div>
<button class="icon-btn" aria-label="More"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg></button>
</div>
<div class="screen">
<div style="padding: 14px 0 10px">
<div class="row row-top">
<div class="avatar">TA</div>
<div class="grow">
<div class="ltitle" style="font-size: 13.5px">Tomás Alvarez</div>
<div class="lmeta" style="margin-top: 0"><span>5h ago</span></div>
</div>
<button class="btn btn-outline btn-sm">Following</button>
</div>
<div style="font-size: 16.5px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.35; margin: 12px 0 8px">
Does a hairline crack at the fork heel always mean tag-out?
</div>
<div class="prose" style="font-size: 14px">
<p>
The lesson says cracks are a fault but doesn't distinguish severity. On our older trucks there are surface
marks that have been there for years. Do I tag out every time?
</p>
</div>
<div class="row" style="gap: 8px">
<button class="btn btn-outline btn-sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7 10v12M15 5.9 14 10h5.8a2 2 0 0 1 2 2.3l-1.4 8a2 2 0 0 1-2 1.7H7V10l4-8a2.7 2.7 0 0 1 4 2z"/></svg> 8</button>
<button class="btn btn-ghost btn-sm">Reply</button>
<button class="btn btn-ghost btn-sm" style="margin-left: auto">Share</button>
</div>
</div>
<div class="divider"></div>
<div class="card card-pad" style="margin-bottom: 12px">
<div class="row row-top">
<div class="avatar">DM</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13.5px">Daniel Mensah <span class="badge badge-success-soft" style="margin-left: 4px">Tutor answer</span></div>
<span class="text-muted" style="font-size: 11px">4h</span>
</div>
<div style="font-size: 13.5px; line-height: 1.55; margin-top: 7px">
Yes — any crack at the heel is a tag-out, no severity judgement. Surface scoring from wear is different:
it's smooth and doesn't catch a fingernail. If you can feel an edge, tag it, photograph it, and log it.
</div>
<div class="row" style="margin-top: 10px; gap: 14px">
<span class="lmeta" style="margin: 0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7 10v12M15 5.9 14 10h5.8a2 2 0 0 1 2 2.3l-1.4 8a2 2 0 0 1-2 1.7H7V10l4-8a2.7 2.7 0 0 1 4 2z"/></svg> 14</span>
<span class="lmeta" style="margin: 0">Reply</span>
</div>
</div>
</div>
</div>
<div class="stack">
<div class="lrow" style="align-items: flex-start">
<div class="avatar sm">PK</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13px">Priya Kaur</div>
<span class="text-muted" style="font-size: 11px">3h</span>
</div>
<div style="font-size: 13px; line-height: 1.5; margin-top: 4px">
The fingernail test is a good rule of thumb — that's what our depot uses too.
</div>
</div>
</div>
<div class="lrow" style="align-items: flex-start">
<div class="avatar sm">AO</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13px">Amara Okafor</div>
<span class="text-muted" style="font-size: 11px">2h</span>
</div>
<div style="font-size: 13px; line-height: 1.5; margin-top: 4px">
Thanks — I tagged out FLT-07 this morning on exactly this.
</div>
</div>
</div>
</div>
<div class="row" style="gap: 8px; margin-top: 16px">
<input class="input" placeholder="Write a reply…" />
<button class="icon-btn" style="width: 40px; height: 40px" aria-label="Send"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m22 2-7 20-4-9-9-4z"/></svg></button>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Thread</b>The tutor answer is visually promoted above learner replies — the same author/team distinction the API already enforces.</figcaption>
</figure>
<!-- 3. OFFLINE READ-ONLY -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="19:05" data-offline></div>
<div class="offline-strip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 2 22 22M8.5 16.5a5 5 0 0 1 7 0M5 13a10 10 0 0 1 4-2.4M12 20h.01"/></svg>
Offline — showing cached posts
</div>
<div class="navbar">
<div class="grow"><div class="title">Community</div></div>
<button class="icon-btn" disabled style="opacity: 0.45" aria-label="New post"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 5v14M5 12h14"/></svg></button>
</div>
<div class="screen">
<div class="banner banner-muted" style="margin: 12px 0 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 2 22 22M8.5 16.5a5 5 0 0 1 7 0M5 13a10 10 0 0 1 4-2.4M12 20h.01"/></svg>
<div class="grow">
<div class="bt">Posting is off while you're offline</div>
<div class="bd">You can read the last 50 posts. Replies need a connection so they don't post out of order.</div>
</div>
</div>
<div class="stack" style="opacity: 0.85">
<div class="card card-pad">
<div class="row row-top">
<div class="avatar">DM</div>
<div class="grow">
<div class="ltitle" style="font-size: 13.5px">Daniel Mensah</div>
<div class="lmeta" style="margin-top: 1px"><span>Cached 2h ago</span></div>
<div style="font-size: 13.5px; line-height: 1.55; margin-top: 7px">
The practical forklift assessment moves to Bay 4 this Thursday.
</div>
</div>
</div>
</div>
<div class="card card-pad">
<div class="row row-top">
<div class="avatar">TA</div>
<div class="grow">
<div class="ltitle" style="font-size: 13.5px">Tomás Alvarez</div>
<div class="lmeta" style="margin-top: 1px"><span>Cached 5h ago</span></div>
<div style="font-size: 13.5px; line-height: 1.55; margin-top: 7px">
Does a hairline crack at the fork heel always mean tag-out?
</div>
</div>
</div>
</div>
</div>
<div class="lrow locked" style="margin-top: 14px">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
<div class="grow">
<div class="ltitle">Reply box disabled</div>
<div class="lsub">Reconnect to post — nothing you type would be saved</div>
</div>
</div>
</div>
<nav class="tabbar" data-tabbar="community"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Offline read-only</b>The compose affordance is disabled and explained rather than accepting text that would be lost — no fake optimistic posting.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+206
View File
@@ -0,0 +1,206 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Compliance — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Tab 3</span>
<h1>Compliance — the differentiator</h1>
<p>
ClassroomIO's core value is knowing where you stand, so it gets a tab rather than a settings sub-page. Deadlines,
grace periods, renewals, and waivers all already exist server-side (<code>/course/:courseId/compliance</code>);
mobile needs one learner-scoped aggregate, <code>GET /account/compliance</code>, to render them together.
</p>
<div class="proto-links">
<a href="certificates.html">Certificate wallet →</a>
<a href="course.html">Open a required course →</a>
<a href="home.html">← Home</a>
</div>
</header>
<div class="stage">
<!-- 1. AT RISK -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<div class="grow"><div class="title">Compliance</div><div class="sub">Northwind Logistics · updated just now</div></div>
<a class="icon-btn" href="certificates.html" aria-label="Certificates"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></a>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 8px 0 14px">
<div class="row">
<div class="media lg is-danger">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.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 0"/></svg>
</div>
<div class="grow">
<div class="ltitle">Action required</div>
<div class="lmeta">
<span class="badge badge-danger-soft">1 overdue</span>
<span class="badge badge-warning-soft">1 expiring</span>
<span class="badge badge-outline">4 compliant</span>
</div>
</div>
</div>
</div>
<div class="sec-head" style="margin-top: 0"><h2>Overdue</h2></div>
<a class="card card-pad" href="course.html" style="display: block">
<div class="row row-top">
<div class="media is-danger"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.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 0"/></svg></div>
<div class="grow">
<div class="ltitle">Forklift Safety Recertification 2026</div>
<div class="lmeta"><span class="badge badge-danger-soft">Overdue 3 days</span><span class="badge badge-warning-soft">Grace ends 24 Aug</span></div>
</div>
</div>
<div class="divider" style="margin: 12px 0"></div>
<div class="kv"><span class="k">Deadline</span><span class="v">17 Aug 2026</span></div>
<div class="kv"><span class="k">Progress</span><span class="v tnum">4 of 12 lessons</span></div>
<div class="kv"><span class="k">If missed</span><span class="v">Authorization suspended</span></div>
<button class="btn btn-primary btn-block" style="margin-top: 12px">Resume now · 28 min left</button>
</a>
<div class="sec-head"><h2>Expiring soon</h2></div>
<div class="card card-pad">
<div class="row row-top">
<div class="media is-warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg></div>
<div class="grow">
<div class="ltitle">Hazardous Materials Handling</div>
<div class="lmeta"><span class="badge badge-warning-soft">Expires in 21 days</span></div>
</div>
</div>
<div class="divider" style="margin: 12px 0"></div>
<div class="row" style="margin-bottom: 6px">
<div class="progress"><span style="width: 82%"></span></div>
<span class="text-muted tnum" style="font-size: 11.5px">21d</span>
</div>
<div class="kv"><span class="k">Certified</span><span class="v">8 Sep 2025</span></div>
<div class="kv"><span class="k">Renewal opens</span><span class="v">8 Sep 2026</span></div>
<a class="btn btn-outline btn-block" href="certificates.html" style="margin-top: 12px">View current certificate</a>
</div>
<div class="sec-head"><h2>Compliant</h2><span class="count">4</span></div>
<div class="stack stack-sm">
<a class="lrow flat" href="certificates.html">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Data Protection &amp; GDPR</div><div class="lmeta"><span>Completed 16 Aug 2026</span><span class="sep">·</span><span>Renews annually</span></div></div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow flat" href="certificates.html">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Manual Handling</div><div class="lmeta"><span>Completed 2 Jun 2026</span></div></div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow flat" href="certificates.html">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Site Induction</div><div class="lmeta"><span>Completed 4 Mar 2026</span></div></div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<div class="lrow flat">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M9 15l2 2 4-4"/></svg></div>
<div class="grow">
<div class="ltitle">Working at Height</div>
<div class="lmeta"><span class="badge badge-muted">Waived</span><span>Not required for your role</span></div>
</div>
</div>
</div>
</div>
<nav class="tabbar" data-tabbar="compliance" data-badges="compliance:dot"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>At risk</b>Overdue first, with the consequence spelled out ("Authorization suspended") and grace-period dates visible. Waivers are shown, not hidden.</figcaption>
</figure>
<!-- 2. ALL CLEAR -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<div class="grow"><div class="title">Compliance</div><div class="sub">Northwind Logistics · updated just now</div></div>
<a class="icon-btn" href="certificates.html" aria-label="Certificates"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></a>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 8px 0 14px">
<div class="row">
<div class="media lg is-done">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></svg>
</div>
<div class="grow">
<div class="ltitle">Fully compliant</div>
<div class="lmeta"><span class="badge badge-success-soft">All 6 requirements up to date</span></div>
</div>
</div>
</div>
<div class="sec-head" style="margin-top: 0"><h2>Renewal timeline</h2></div>
<div class="card card-pad">
<div class="lrow flat">
<div class="media is-warning">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
</div>
<div class="grow"><div class="ltitle">Hazardous Materials Handling</div><div class="lmeta"><span>Renews 15 Feb 2027</span></div></div>
<span class="badge badge-warning-soft tnum">183d</span>
</div>
<div class="lrow flat">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg></div>
<div class="grow"><div class="ltitle">Forklift Safety Recertification</div><div class="lmeta"><span>Renews 17 Aug 2027</span></div></div>
<span class="badge badge-outline tnum">366d</span>
</div>
<div class="lrow flat">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg></div>
<div class="grow"><div class="ltitle">Data Protection &amp; GDPR</div><div class="lmeta"><span>Renews 16 Aug 2027</span></div></div>
<span class="badge badge-outline tnum">365d</span>
</div>
</div>
<div class="sec-head"><h2>Your record</h2></div>
<div class="card card-pad">
<div class="kv"><span class="k">Requirements met</span><span class="v tnum">6 of 6</span></div>
<div class="kv"><span class="k">On-time completion rate</span><span class="v tnum">83%</span></div>
<div class="kv"><span class="k">Certificates held</span><span class="v tnum">6</span></div>
<div class="kv"><span class="k">Last audit export</span><span class="v">1 Aug 2026</span></div>
</div>
<a class="btn btn-outline btn-block" href="certificates.html" style="margin-top: 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg>
Open certificate wallet
</a>
<div class="hint" style="text-align: center">
Your organization can verify any certificate from its ID without you sending a file.
</div>
</div>
<nav class="tabbar" data-tabbar="compliance"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>All clear</b>The same screen when nothing is wrong: status, what renews next, and the record an auditor would ask for.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+284
View File
@@ -0,0 +1,284 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Course detail — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Course</span>
<h1>Course detail &amp; outline</h1>
<p>
Sections and lessons with an explicit state on every row: complete, current, locked (with the reason spelled out),
or downloaded. Compliance courses carry a deadline banner. Download is a whole-course action from the header,
sized before it starts and Wi-Fi-only by default.
</p>
<div class="proto-links">
<a href="lesson.html">Open lesson →</a>
<a href="exercise.html">Knowledge check →</a>
<a href="certificates.html">Certificate →</a>
<a href="learn.html">← Learn</a>
</div>
</header>
<div class="stage">
<!-- 1. OUTLINE -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:44"></div>
<div class="navbar">
<a class="icon-btn plain" href="learn.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Forklift Safety Recertification</div></div>
<button class="icon-btn" data-sheet-open="dl" aria-label="Download course"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></button>
<button class="icon-btn" aria-label="More"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg></button>
</div>
<div class="tabs" data-select>
<button class="on" data-option>Content</button>
<button data-option>Compliance</button>
<button data-option>Newsfeed</button>
<button data-option>Community</button>
<button data-option>Certificate</button>
</div>
<div class="screen">
<div class="banner banner-danger" style="margin: 12px 0 12px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.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 0"/></svg>
<div class="grow">
<div class="bt">Overdue by 3 days</div>
<div class="bd">Grace period ends 24 Aug. After that your forklift authorization is suspended.</div>
</div>
</div>
<div class="card card-pad" style="margin-bottom: 6px">
<div class="row">
<div class="ring lg">
<svg viewBox="0 0 36 36"><circle class="track" cx="18" cy="18" r="15.5" fill="none" stroke-width="3"/><circle class="arc primary" cx="18" cy="18" r="15.5" fill="none" stroke-width="3" stroke-linecap="round" stroke-dasharray="97.4" stroke-dashoffset="65.3"/></svg>
<span class="lbl tnum">33%</span>
</div>
<div class="grow">
<div class="lmeta" style="margin: 0"><span>4 of 12 lessons</span></div>
<div class="lmeta"><span>~28 min remaining</span></div>
<div class="lmeta"><span class="badge badge-success-soft">Downloaded · 84 MB</span></div>
</div>
</div>
<a class="btn btn-primary btn-block" href="lesson.html" style="margin-top: 12px">
<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M8 5v14l11-7z"/></svg>
Continue lesson 4
</a>
</div>
<div class="sec-head"><h2>Section 1 · Before you drive</h2><span class="count">3/3</span></div>
<div class="stack stack-sm">
<a class="lrow flat done" href="lesson.html">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Why recertification matters</div><div class="lmeta"><span>Video · 4 min</span></div></div>
</a>
<a class="lrow flat done" href="lesson.html">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Site rules and exclusion zones</div><div class="lmeta"><span>Reading · 5 min</span></div></div>
</a>
<a class="lrow flat done" href="exercise.html">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Section 1 check</div><div class="lmeta"><span class="badge badge-success-soft">9/10</span></div></div>
</a>
</div>
<div class="sec-head"><h2>Section 2 · Inspection</h2><span class="count">1/4</span></div>
<div class="stack stack-sm">
<a class="lrow current" href="lesson.html" style="padding: 12px 14px">
<div class="media is-current"><svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M8 5v14l11-7z"/></svg></div>
<div class="grow">
<div class="ltitle">Pre-operation inspection checklist</div>
<div class="lmeta"><span>Video · 9 min</span><span class="sep">·</span><span class="text-primary" style="font-weight: 600">6 min left</span></div>
</div>
<span class="dl-dot"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
</a>
<a class="lrow flat" href="lesson.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M8 5v14l11-7z"/></svg></div>
<div class="grow"><div class="ltitle">Load stability and centre of gravity</div><div class="lmeta"><span>Video · 7 min</span></div></div>
<span class="dl-dot"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
</a>
<div class="lrow flat locked">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
<div class="grow">
<div class="ltitle">Hydraulics walkthrough</div>
<div class="lmeta"><span>Complete "Load stability" to unlock</span></div>
</div>
</div>
<div class="lrow flat locked">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
<div class="grow">
<div class="ltitle">Section 2 check</div>
<div class="lmeta"><span>Complete all lessons in this section</span></div>
</div>
</div>
</div>
<div class="sec-head"><h2>Section 3 · Assessment</h2><span class="count">0/5</span></div>
<div class="lrow flat locked">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
<div class="grow">
<div class="ltitle">5 items locked</div>
<div class="lmeta"><span>Finish Section 2 to unlock the assessment</span></div>
</div>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Outline</b>Every row states its own condition. Locked items name the prerequisite instead of just greying out — and the API enforces the same rule.</figcaption>
</figure>
<!-- 2. DOWNLOAD SHEET -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:45"></div>
<div class="navbar">
<a class="icon-btn plain" href="learn.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Warehouse Fire Safety</div></div>
<button class="icon-btn" aria-label="Download course"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></button>
</div>
<div class="tabs">
<button class="on">Content</button>
<button>Compliance</button>
<button>Newsfeed</button>
<button>Community</button>
</div>
<div class="screen">
<div class="banner banner-warning" style="margin: 12px 0">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
<div class="grow"><div class="bt">Due in 6 days</div><div class="bd">Required for all warehouse staff</div></div>
</div>
<div class="stack stack-sm" style="opacity: 0.5">
<div class="lrow flat done"><div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div><div class="grow"><div class="ltitle">Fire triangle basics</div></div></div>
<div class="lrow flat done"><div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div><div class="grow"><div class="ltitle">Extinguisher classes</div></div></div>
<div class="lrow flat"><div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M8 5v14l11-7z"/></svg></div><div class="grow"><div class="ltitle">Evacuation routes</div></div></div>
</div>
</div>
<div class="scrim"></div>
<div class="sheet" style="display: flex">
<div class="grabber"></div>
<div class="sheet-head">
<div class="st">Download this course</div>
<button class="icon-btn plain" style="margin-left: auto" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div>
<div class="sheet-body">
<div class="card card-pad" style="margin-bottom: 12px">
<div class="kv"><span class="k">8 lessons · video, PDFs, transcripts</span><span class="v tnum">112 MB</span></div>
<div class="kv"><span class="k">Free space on this device</span><span class="v tnum">3.4 GB</span></div>
<div class="kv"><span class="k">Connection</span><span class="v">Wi-Fi</span></div>
</div>
<div class="lrow" style="margin-bottom: 8px">
<div class="grow">
<div class="ltitle">Download over Wi-Fi only</div>
<div class="lsub">Avoids using your mobile data</div>
</div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="banner banner-muted" style="margin-bottom: 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
<div class="bd" style="color: var(--muted-foreground)">
Lessons, video, and transcripts work offline. The knowledge check still needs a connection to submit.
</div>
</div>
<button class="btn btn-primary btn-lg btn-block">Download 112 MB</button>
<button class="btn btn-ghost btn-block" style="margin-top: 6px">Not now</button>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Download sheet</b>Size, free space, and connection shown before committing, with the offline limits stated up front rather than discovered on a plane.</figcaption>
</figure>
<!-- 3. COMPLETED -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="10:02"></div>
<div class="navbar">
<a class="icon-btn plain" href="learn.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Data Protection &amp; GDPR Essentials</div></div>
<button class="icon-btn" aria-label="Share"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="m16 6-4-4-4 4M12 2v13"/></svg></button>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 14px 0 12px">
<div class="row row-top">
<div class="media lg is-done">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg>
</div>
<div class="grow">
<div class="ltitle">Course complete</div>
<div class="lmeta"><span class="badge badge-success-soft">Finished 16 Aug 2026</span><span>Final score 92%</span></div>
</div>
</div>
<a class="btn btn-primary btn-block" href="certificates.html" style="margin-top: 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg>
View your certificate
</a>
</div>
<div class="card card-pad" style="margin-bottom: 12px">
<div class="kv"><span class="k">Lessons completed</span><span class="v tnum">10 / 10</span></div>
<div class="kv"><span class="k">Exercises passed</span><span class="v tnum">3 / 3</span></div>
<div class="kv"><span class="k">Time spent</span><span class="v">1h 12m</span></div>
<div class="kv"><span class="k">Certificate ID</span><span class="v tnum" style="font-size: 12.5px">CIO-GDPR-4F91-2026</span></div>
</div>
<div class="sec-head"><h2>Keep the content</h2></div>
<div class="lrow">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></div>
<div class="grow">
<div class="ltitle">Downloaded · 96 MB</div>
<div class="lsub">Review offline any time, or free the space</div>
</div>
<button class="btn btn-outline btn-sm">Remove</button>
</div>
<div class="sec-head"><h2>Recommended next</h2></div>
<a class="lrow" href="course.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg></div>
<div class="grow">
<div class="ltitle">Handling a Data Subject Request</div>
<div class="lmeta"><span>4 lessons</span><span class="sep">·</span><span>~18 min</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Completed</b>Certificate issuance is the payoff, and the download stays until the learner removes it.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+212
View File
@@ -0,0 +1,212 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Downloads &amp; sync — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Platform</span>
<h1>Downloads, storage &amp; sync queue</h1>
<p>
Content is cached in SQLite plus the file system from one <code>GET /course/:courseId/offline-manifest</code> call.
Writes go the other way: completion and playback position queue locally with an idempotency key and replay through
<code>POST /course/:courseId/sync</code>. Completion is monotonic, so a retry can never corrupt a record — which is
exactly why offline exercises are deferred to v2.
</p>
<div class="proto-links">
<a href="lesson.html">← Offline lesson</a>
<a href="course.html">Download a course →</a>
<a href="profile.html">Profile →</a>
</div>
</header>
<div class="stage">
<!-- 1. DOWNLOADS -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:48"></div>
<div class="navbar">
<a class="icon-btn plain" href="profile.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title">Downloads</div><div class="sub">3 courses · 284 MB used</div></div>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 10px 0 14px">
<div class="row between" style="margin-bottom: 8px">
<span class="eyebrow">Storage on this device</span>
<span class="tnum" style="font-size: 12px; font-weight: 600">284 MB of 3.7 GB free</span>
</div>
<div style="display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--muted)">
<span style="width: 30%; background: var(--primary)"></span>
<span style="width: 22%; background: color-mix(in oklab, var(--primary) 55%, var(--muted))"></span>
<span style="width: 12%; background: color-mix(in oklab, var(--primary) 30%, var(--muted))"></span>
</div>
<div class="row" style="gap: 12px; margin-top: 10px; flex-wrap: wrap">
<span class="lmeta" style="margin: 0"><span style="width: 8px; height: 8px; border-radius: 2px; background: var(--primary); display: inline-block"></span> Forklift 84 MB</span>
<span class="lmeta" style="margin: 0"><span style="width: 8px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--primary) 55%, var(--muted)); display: inline-block"></span> Fire Safety 112 MB</span>
<span class="lmeta" style="margin: 0"><span style="width: 8px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--primary) 30%, var(--muted)); display: inline-block"></span> GDPR 88 MB</span>
</div>
</div>
<div class="sec-head" style="margin-top: 0"><h2>Downloading</h2></div>
<div class="card card-pad" style="margin-bottom: 4px">
<div class="row row-top">
<div class="media lg is-current"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></div>
<div class="grow">
<div class="ltitle">Hazardous Materials Handling</div>
<div class="lmeta"><span>Lesson 5 of 8</span><span class="sep">·</span><span class="tnum">48 MB of 96 MB</span></div>
<div class="row" style="margin-top: 8px">
<div class="progress"><span style="width: 50%"></span></div>
<span class="text-muted tnum" style="font-size: 11px">50%</span>
</div>
</div>
<button class="icon-btn plain" aria-label="Pause"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg></button>
</div>
</div>
<div class="sec-head"><h2>On this device</h2><span class="count">3</span></div>
<div class="stack">
<div class="lrow">
<div class="media lg is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow">
<div class="ltitle">Forklift Safety Recertification</div>
<div class="lmeta"><span>12 lessons</span><span class="sep">·</span><span class="tnum">84 MB</span><span class="sep">·</span><span>Synced 2h ago</span></div>
</div>
<button class="icon-btn" aria-label="Remove download"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></button>
</div>
<div class="lrow">
<div class="media lg is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow">
<div class="ltitle">Warehouse Fire Safety</div>
<div class="lmeta"><span>8 lessons</span><span class="sep">·</span><span class="tnum">112 MB</span><span class="sep">·</span><span>Synced 2h ago</span></div>
</div>
<button class="icon-btn" aria-label="Remove download"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></button>
</div>
<div class="lrow">
<div class="media lg is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow">
<div class="ltitle">Data Protection &amp; GDPR Essentials</div>
<div class="lmeta"><span>Completed</span><span class="sep">·</span><span class="tnum">88 MB</span></div>
</div>
<button class="icon-btn" aria-label="Remove download"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></button>
</div>
</div>
<div class="sec-head"><h2>Settings</h2></div>
<div class="card card-pad">
<div class="lrow flat">
<div class="grow"><div class="ltitle">Download over Wi-Fi only</div><div class="lsub">Protects your mobile data</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Auto-download required training</div><div class="lsub">When new compliance courses are assigned</div></div>
<label class="switch"><input type="checkbox" /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="kv"><span class="k">Video quality offline</span><span class="v">Standard (480p)</span></div>
</div>
<button class="btn btn-outline btn-block" style="margin-top: 12px; color: var(--destructive); border-color: color-mix(in oklab, var(--destructive) 35%, transparent)">
Remove all downloads · 284 MB
</button>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Downloads &amp; storage</b>Per-course breakdown, resumable progress, and Wi-Fi-only by default. Nothing is silently evicted — the learner chose to download it.</figcaption>
</figure>
<!-- 2. SYNC QUEUE -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="19:22"></div>
<div class="navbar">
<a class="icon-btn plain" href="downloads.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Sync queue</div><div class="sub">Back online · syncing</div></div>
<button class="icon-btn" aria-label="Sync now"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 12a9 9 0 1 1-6.2-8.6"/><path d="M21 3v6h-6"/></svg></button>
</div>
<div class="screen">
<div class="banner banner-info" style="margin: 12px 0 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 12a9 9 0 1 1-6.2-8.6"/><path d="M21 3v6h-6"/></svg>
<div class="grow">
<div class="bt">Syncing 4 changes</div>
<div class="bd">Recorded while you were offline. Your dashboard will match in a few seconds.</div>
</div>
</div>
<div class="stack">
<div class="lrow">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow">
<div class="ltitle">Lesson 4 completed</div>
<div class="lmeta"><span>Forklift Safety</span><span class="sep">·</span><span>Queued 18:31</span></div>
</div>
<span class="badge badge-success-soft">Synced</span>
</div>
<div class="lrow">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow">
<div class="ltitle">Lesson 5 completed</div>
<div class="lmeta"><span>Forklift Safety</span><span class="sep">·</span><span>Queued 18:40</span></div>
</div>
<span class="badge badge-success-soft">Synced</span>
</div>
<div class="lrow current">
<div class="media is-current"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 12a9 9 0 1 1-6.2-8.6"/><path d="M21 3v6h-6"/></svg></div>
<div class="grow">
<div class="ltitle">Playback position 7:02</div>
<div class="lmeta"><span>Lesson 5</span><span class="sep">·</span><span>Queued 18:47</span></div>
</div>
<span class="badge badge-primary-soft">Syncing</span>
</div>
<div class="lrow">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.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 0"/></svg></div>
<div class="grow">
<div class="ltitle">Lesson 6 completed</div>
<div class="lmeta"><span>Retrying in 8s · attempt 2 of 5</span></div>
</div>
<button class="btn btn-outline btn-sm">Retry</button>
</div>
</div>
<div class="sec-head"><h2>What syncs offline</h2></div>
<div class="card card-pad">
<div class="kv"><span class="k">Lesson completion</span><span class="v"><span class="badge badge-success-soft">Queued offline</span></span></div>
<div class="kv"><span class="k">Playback position</span><span class="v"><span class="badge badge-success-soft">Queued offline</span></span></div>
<div class="kv"><span class="k">Exercise answers</span><span class="v"><span class="badge badge-muted">Online only</span></span></div>
<div class="kv"><span class="k">Community posts</span><span class="v"><span class="badge badge-muted">Online only</span></span></div>
</div>
<div class="hint" style="margin-top: 10px">
Completion only ever moves forward, so replaying a queued change twice is harmless. Grading and submissions
stay online to keep assessment records unambiguous.
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Sync queue</b>Every queued intent is visible with its state and retry count — the learner can see their work is safe rather than hoping.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+254
View File
@@ -0,0 +1,254 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Exercise — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Assessment</span>
<h1>Exercises</h1>
<p>
Touch renderers for all 16 question types in <code>packages/question-types</code> — including the ones the web
takes for granted: <code>MATCHING</code> and <code>ORDERING</code> as drag, <code>HOTSPOT</code> as tap,
<code>FILE_UPLOAD</code> through the camera, and <code>VIDEO_RECORDING</code> in-app. Answers autosave locally so a
phone call never costs an attempt; media goes up through the existing presigned-upload route.
</p>
<div class="proto-links">
<a href="lesson.html">← Lesson</a>
<a href="tutor-grading.html">How it's graded →</a>
<a href="certificates.html">Certificate →</a>
</div>
</header>
<div class="stage">
<!-- 1. IN PROGRESS -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:52"></div>
<div class="navbar bordered">
<button class="icon-btn plain" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
<div class="grow">
<div class="title-sm">Section 2 check</div>
<div class="sub">Question 3 of 8 · no time limit</div>
</div>
<span class="badge badge-muted">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg>
Saved
</span>
</div>
<div class="screen">
<div class="row" style="margin: 12px 0 18px">
<div class="segs">
<span class="seg done"></span><span class="seg done"></span><span class="seg now"></span><span class="seg"></span>
<span class="seg"></span><span class="seg"></span><span class="seg"></span><span class="seg"></span>
</div>
</div>
<div class="eyebrow">Single choice · 2 points</div>
<div style="font-size: 17px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.4; margin: 8px 0 16px">
You find a wet, shiny film on a hydraulic ram that reappears after wiping. What do you do?
</div>
<div class="stack" data-select style="margin-bottom: 18px">
<label class="opt" data-option>
<span class="mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span>Top up the hydraulic fluid and continue the shift</span>
</label>
<label class="opt on" data-option>
<span class="mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span>Tag the truck out of service and report the fault</span>
</label>
<label class="opt" data-option>
<span class="mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span>Note it on the log and check again at the end of the shift</span>
</label>
<label class="opt" data-option>
<span class="mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span>Wipe it down and only escalate if the load feels unstable</span>
</label>
</div>
<div class="banner banner-muted">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
<div class="bd" style="color: var(--muted-foreground)">
Your answers save on this device as you go. If you get interrupted, you'll pick up exactly here.
</div>
</div>
<div class="row" style="gap: 8px; margin-top: 18px">
<button class="btn btn-outline btn-lg" style="flex: 1">Back</button>
<button class="btn btn-primary btn-lg" style="flex: 2">Next question</button>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>In progress</b>One question per screen with a segment bar and a persistent "Saved" chip — the autosave promise made visible.</figcaption>
</figure>
<!-- 2. RICH TYPES -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:58"></div>
<div class="navbar bordered">
<button class="icon-btn plain" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
<div class="grow">
<div class="title-sm">Section 2 check</div>
<div class="sub">Question 7 of 8</div>
</div>
<span class="badge badge-muted"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg> Saved</span>
</div>
<div class="screen">
<div class="row" style="margin: 12px 0 18px">
<div class="segs">
<span class="seg done"></span><span class="seg done"></span><span class="seg done"></span><span class="seg done"></span>
<span class="seg done"></span><span class="seg done"></span><span class="seg now"></span><span class="seg"></span>
</div>
</div>
<div class="eyebrow">Photo evidence · 5 points</div>
<div style="font-size: 17px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.4; margin: 8px 0 6px">
Photograph the data plate on the truck you're authorized to drive.
</div>
<div class="lsub" style="margin-bottom: 14px">Your tutor checks that the rated capacity matches your authorization.</div>
<div class="card card-pad" style="margin-bottom: 10px">
<div class="row">
<div class="thumb" style="background: linear-gradient(135deg, oklch(0.556 0 0), oklch(0.3 0 0)); width: 64px; height: 48px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="6" width="18" height="14" rx="2"/><circle cx="12" cy="13" r="3.5"/><path d="M8 6l1.5-2h5L16 6"/></svg>
</div>
<div class="grow">
<div class="ltitle">IMG_4192.jpg</div>
<div class="lmeta"><span>2.4 MB</span><span class="sep">·</span><span>Uploading…</span></div>
<div class="row" style="margin-top: 6px"><div class="progress"><span style="width: 62%"></span></div><span class="text-muted tnum" style="font-size: 11px">62%</span></div>
</div>
<button class="icon-btn plain" aria-label="Cancel upload"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div>
</div>
<div class="row" style="gap: 8px; margin-bottom: 18px">
<button class="btn btn-outline" style="flex: 1">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="6" width="18" height="14" rx="2"/><circle cx="12" cy="13" r="3.5"/><path d="M8 6l1.5-2h5L16 6"/></svg>
Camera
</button>
<button class="btn btn-outline" style="flex: 1">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>
Files
</button>
</div>
<div class="divider"></div>
<div class="eyebrow">Also in this exercise</div>
<div class="stack stack-sm" style="margin-top: 8px">
<div class="lrow flat">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Match the hazard to its control</div><div class="lmeta"><span>Drag to match · answered</span></div></div>
</div>
<div class="lrow flat">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Tap the blind spot on the diagram</div><div class="lmeta"><span>Hotspot · answered</span></div></div>
</div>
<div class="lrow flat">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m22 8-6 4 6 4V8z"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg></div>
<div class="grow"><div class="ltitle">Record your 60-second handover</div><div class="lmeta"><span>Video · not started</span></div></div>
</div>
</div>
<button class="btn btn-primary btn-lg btn-block" style="margin-top: 18px">Next question</button>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Media &amp; rich question types</b>Camera capture with visible upload progress and retry, plus drag, hotspot, and in-app video recording — the types a phone does better than a laptop.</figcaption>
</figure>
<!-- 3. GRADED -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="14:07"></div>
<div class="navbar bordered">
<a class="icon-btn plain" href="course.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Section 2 check · Result</div></div>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 14px 0 12px; text-align: center">
<div class="ring lg" style="margin: 0 auto 12px; width: 92px; height: 92px">
<svg viewBox="0 0 36 36"><circle class="track" cx="18" cy="18" r="15.5" fill="none" stroke-width="3"/><circle class="arc" cx="18" cy="18" r="15.5" fill="none" stroke-width="3" stroke-linecap="round" stroke-dasharray="97.4" stroke-dashoffset="12.7"/></svg>
<span class="lbl tnum" style="font-size: 20px">87%</span>
</div>
<div style="font-size: 17px; font-weight: 600; letter-spacing: -0.02em">Passed</div>
<div class="lsub" style="margin-top: 3px">26 of 30 points · pass mark 70%</div>
<div class="row" style="justify-content: center; gap: 6px; margin-top: 10px">
<span class="badge badge-success-soft">Graded by Daniel Mensah</span>
<span class="badge badge-outline">2h ago</span>
</div>
</div>
<div class="card card-pad" style="margin-bottom: 12px">
<div class="eyebrow" style="margin-bottom: 8px">Tutor feedback</div>
<div style="font-size: 13.5px; line-height: 1.55">
Solid inspection sequence and the tag-out call was right. Your handover recording skipped the battery state —
mention it next time, it's the one item the incoming driver can't check quickly.
</div>
</div>
<div class="sec-head"><h2>Question breakdown</h2></div>
<div class="stack stack-sm">
<div class="lrow flat">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Q3 · Hydraulic fault response</div><div class="lmeta"><span>2 / 2</span></div></div>
</div>
<div class="lrow flat">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Q5 · Match hazard to control</div><div class="lmeta"><span>4 / 4</span></div></div>
</div>
<div class="lrow flat">
<div class="media is-warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.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 0"/></svg></div>
<div class="grow">
<div class="ltitle">Q8 · Handover recording</div>
<div class="lmeta"><span>4 / 8</span><span class="sep">·</span><span>Missing battery state</span></div>
</div>
</div>
</div>
<div class="divider"></div>
<a class="btn btn-primary btn-lg btn-block" href="course.html">Continue to Section 3</a>
<button class="btn btn-outline btn-block" style="margin-top: 8px">Retake for a higher score</button>
<div class="hint" style="text-align: center">1 retake remaining · your highest score counts</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Graded</b>Score, tutor comment, and a per-question breakdown that names what was missing — feedback the learner can act on without opening a laptop.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+353
View File
@@ -0,0 +1,353 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Home — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Tab 1</span>
<h1>Home — the attention surface</h1>
<p>
Ordered by urgency, not recency: compliance alerts, then continue-learning, due-soon, newly assigned, announcements,
and feedback. The whole screen is one <code>GET /account/home-digest</code> call so a cold launch on a weak
connection costs a single round trip — and the cached copy still renders offline with an explicit staleness chip.
</p>
<div class="proto-links">
<a href="compliance.html">Compliance →</a>
<a href="lesson.html">Continue lesson →</a>
<a href="exercise.html">Due exercise →</a>
<a href="notifications.html">Notifications →</a>
<a href="learn.html">Learn tab →</a>
</div>
</header>
<div class="stage">
<!-- 1. DEFAULT — WORK TO DO -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<div class="avatar brand">NL</div>
<div class="grow">
<div class="sub">Northwind Logistics</div>
<div class="title-sm">Good morning, Amara</div>
</div>
<a class="icon-btn plain" href="notifications.html" aria-label="Notifications">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/></svg>
<span class="ping"></span>
</a>
</div>
<div class="screen">
<a class="banner banner-danger" href="compliance.html">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.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 0"/></svg>
<div class="grow">
<div class="bt">1 training overdue</div>
<div class="bd">Forklift Safety Recertification was due 3 days ago · 4 days of grace left</div>
</div>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width: 15px; height: 15px; margin-top: 3px"><path d="m9 18 6-6-6-6"/></svg>
</a>
<!-- continue -->
<div class="sec-head"><h2>Continue learning</h2></div>
<a class="card" href="lesson.html" style="display: block; overflow: hidden">
<div style="display: flex; gap: 12px; padding: 12px">
<div class="thumb" style="background: linear-gradient(135deg, oklch(0.488 0.243 264.376), oklch(0.546 0.245 262.881))">
<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M8 5v14l11-7z"/></svg>
</div>
<div class="grow">
<div class="lsub" style="margin: 0">Forklift Safety Recertification 2026</div>
<div class="ltitle" style="margin-top: 2px">Pre-operation inspection checklist</div>
<div class="lmeta"><span>Lesson 4 of 12</span><span class="sep">·</span><span>6 min left</span></div>
</div>
</div>
<div style="padding: 0 12px 12px">
<div class="row">
<div class="progress"><span style="width: 33%"></span></div>
<span class="text-muted tnum" style="font-size: 11.5px">33%</span>
</div>
</div>
</a>
<!-- due soon -->
<div class="sec-head"><h2>Due soon</h2><span class="count">4</span><a class="more" href="learn.html">See all</a></div>
<div class="stack">
<a class="lrow" href="exercise.html">
<div class="media is-danger">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.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 0"/></svg>
</div>
<div class="grow">
<div class="ltitle">Forklift Safety Recertification</div>
<div class="lmeta"><span class="badge badge-danger-soft">Overdue 3 days</span><span class="badge badge-outline">Required</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="certificates.html">
<div class="media is-warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg></div>
<div class="grow">
<div class="ltitle">Hazardous Materials Handling</div>
<div class="lmeta"><span class="badge badge-warning-soft">Expires in 21 days</span><span>Renewal opens 8 Sep</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="exercise.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></div>
<div class="grow">
<div class="ltitle">Module 3 knowledge check</div>
<div class="lmeta"><span>Data Protection &amp; GDPR Essentials</span><span class="sep">·</span><span>Due in 2 days</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="course.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg></div>
<div class="grow">
<div class="ltitle">Warehouse Fire Safety</div>
<div class="lmeta"><span>Due in 6 days</span><span class="sep">·</span><span>2 of 8 lessons done</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<!-- newly assigned -->
<div class="sec-head"><h2>Newly assigned</h2></div>
<div class="card card-pad">
<div class="row row-top">
<div class="media lg is-current"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg></div>
<div class="grow">
<div class="ltitle">Hazardous Materials Handling</div>
<div class="lsub">Assigned by Daniel Mensah · 8 lessons · ~45 min</div>
<div class="lmeta"><span class="badge badge-outline">Required</span><span class="badge badge-outline">Deadline 30 Sep</span></div>
</div>
</div>
<button class="btn btn-primary btn-block btn-sm" style="margin-top: 12px; height: 36px">Start course</button>
</div>
<!-- announcements -->
<div class="sec-head"><h2>Cohort announcements</h2><a class="more" href="community.html">See all</a></div>
<a class="card card-pad" href="community.html" style="display: block">
<div class="row row-top">
<div class="avatar">DM</div>
<div class="grow">
<div class="ltitle" style="font-size: 13.5px">Daniel Mensah</div>
<div class="lsub" style="margin-top: 3px">
Reminder: the practical forklift assessment moves to Bay 4 this Thursday. Bring your completion code from
Lesson 12.
</div>
<div class="lmeta"><span>Q3 Frontline Onboarding</span><span class="sep">·</span><span>2h ago</span></div>
</div>
</div>
</a>
<!-- feedback -->
<div class="sec-head"><h2>Recent feedback</h2></div>
<a class="lrow" href="exercise.html">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow">
<div class="ltitle">Incident reporting scenario</div>
<div class="lsub">"Clear escalation path — nice work on step 3."</div>
<div class="lmeta"><span class="badge badge-success-soft">18 / 20 · Passed</span><span>Graded yesterday</span></div>
</div>
</a>
</div>
<nav class="tabbar" data-tabbar="home" data-badges="compliance:dot"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Default — work to do</b>Overdue compliance outranks everything. Every card is a deep-link target for a push notification.</figcaption>
</figure>
<!-- 2. CAUGHT UP -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<div class="avatar brand">NL</div>
<div class="grow">
<div class="sub">Northwind Logistics</div>
<div class="title-sm">Good morning, Amara</div>
</div>
<a class="icon-btn plain" href="notifications.html" aria-label="Notifications">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/></svg>
</a>
</div>
<div class="screen">
<a class="banner banner-muted" href="compliance.html">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></svg>
<div class="grow">
<div class="bt">All 6 requirements compliant</div>
<div class="bd">Next renewal: Hazmat Handling on 15 Feb 2027</div>
</div>
</a>
<div class="empty" style="padding: 34px 22px 26px">
<div class="ei">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg>
</div>
<div class="et">Nothing due — you're all caught up</div>
<div class="ed">No overdue training, no deadlines this week. We'll notify you when something new is assigned.</div>
</div>
<div class="card card-pad" style="margin-bottom: 12px">
<div class="row" style="align-items: stretch">
<div class="stat"><div class="v tnum">9</div><div class="k">Completed</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">6</div><div class="k">Certificates</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">14</div><div class="k">Day streak</div></div>
</div>
</div>
<div class="sec-head"><h2>Keep going</h2><a class="more" href="learn.html">Explore</a></div>
<div class="stack">
<a class="lrow" href="course.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg></div>
<div class="grow">
<div class="ltitle">Customer Service Excellence</div>
<div class="lmeta"><span class="badge badge-outline">Optional</span><span>6 lessons · ~30 min</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="certificates.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></div>
<div class="grow">
<div class="ltitle">Your certificate wallet</div>
<div class="lmeta"><span>6 certificates · 1 expiring soon</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
</div>
<nav class="tabbar" data-tabbar="home"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Caught up</b>The reward state. A muted status line replaces the overdue alert, the urgency stack collapses to an empty state, and the screen shifts to optional learning.</figcaption>
</figure>
<!-- 3. OFFLINE + PUSH -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="18:12" data-offline></div>
<!-- push notification preview -->
<div class="push">
<div class="pi">C</div>
<div class="grow">
<div class="row between">
<div class="pt">Deadline reminder</div>
<div class="pw">now</div>
</div>
<div class="pb">Forklift Safety Recertification is due tomorrow. 8 lessons left — about 20 minutes.</div>
</div>
</div>
<div class="offline-strip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 2 22 22M8.5 16.5a5 5 0 0 1 7 0M5 13a10 10 0 0 1 4-2.4M12 20h.01"/></svg>
Offline — showing your last sync
</div>
<div class="navbar" style="margin-top: 54px">
<div class="avatar brand">NL</div>
<div class="grow">
<div class="sub">Northwind Logistics</div>
<div class="title-sm">Good evening, Amara</div>
</div>
<a class="icon-btn plain" href="notifications.html" aria-label="Notifications">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/></svg>
<span class="ping"></span>
</a>
</div>
<div class="screen">
<div class="row" style="margin-bottom: 10px">
<span class="badge badge-warning-soft">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
Last updated 2h ago
</span>
<a class="badge badge-outline" href="downloads.html">2 changes waiting to sync</a>
</div>
<div class="sec-head" style="margin-top: 6px"><h2>Available offline</h2></div>
<div class="stack">
<a class="card" href="lesson.html" style="display: block; overflow: hidden">
<div style="display: flex; gap: 12px; padding: 12px">
<div class="thumb" style="background: linear-gradient(135deg, oklch(0.488 0.243 264.376), oklch(0.546 0.245 262.881))">
<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M8 5v14l11-7z"/></svg>
</div>
<div class="grow">
<div class="lsub" style="margin: 0">Forklift Safety Recertification 2026</div>
<div class="ltitle" style="margin-top: 2px">Load stability and centre of gravity</div>
<div class="lmeta">
<span class="dl-dot"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span>Downloaded</span><span class="sep">·</span><span>Lesson 5 of 12</span>
</div>
</div>
</div>
<div style="padding: 0 12px 12px">
<div class="row"><div class="progress"><span style="width: 42%"></span></div><span class="text-muted tnum" style="font-size: 11.5px">42%</span></div>
</div>
</a>
<a class="lrow" href="course.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></div>
<div class="grow">
<div class="ltitle">Warehouse Fire Safety</div>
<div class="lmeta"><span>8 lessons downloaded</span><span class="sep">·</span><span>112 MB</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<div class="sec-head"><h2>Needs a connection</h2></div>
<div class="stack">
<div class="lrow locked">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
<div class="grow">
<div class="ltitle">Module 3 knowledge check</div>
<div class="lsub">Exercises submit online only — we'll remind you when you reconnect</div>
</div>
</div>
<div class="lrow locked">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg></div>
<div class="grow">
<div class="ltitle">Community &amp; announcements</div>
<div class="lsub">Reading cached posts · posting disabled offline</div>
</div>
</div>
</div>
</div>
<nav class="tabbar" data-tabbar="home"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Offline + incoming push</b>The cached digest with an explicit staleness chip. Anything that can't work offline is listed and explained rather than silently failing.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+345
View File
@@ -0,0 +1,345 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ClassroomIO Mobile App — Prototype Map</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
<style>
.page {
max-width: 1040px;
margin: 0 auto;
padding: 56px 24px 80px;
}
h1 {
font-size: 32px;
font-weight: 600;
letter-spacing: -0.03em;
margin: 8px 0 10px;
}
.lead {
color: var(--muted-foreground);
font-size: 15px;
max-width: 74ch;
margin: 0 0 12px;
line-height: 1.65;
}
.lead b {
color: var(--foreground);
font-weight: 500;
}
.lead code {
background: var(--muted);
color: var(--foreground);
padding: 1px 6px;
border-radius: 4px;
font-size: 12.5px;
}
.sec {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted-foreground);
margin: 38px 0 14px;
display: flex;
align-items: center;
gap: 10px;
}
.sec::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.shot {
overflow: hidden;
display: flex;
flex-direction: column;
transition: border-color 0.15s, box-shadow 0.15s;
}
.shot:hover {
border-color: var(--ring);
box-shadow: var(--shadow-md);
}
.shot .cover {
height: 72px;
display: grid;
place-items: center;
color: #fff;
}
.shot .cover svg {
width: 26px;
height: 26px;
stroke-width: 1.8;
}
.shot .b {
padding: 13px 15px 15px;
}
.shot .n {
font-size: 11px;
font-weight: 600;
color: var(--muted-foreground);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.shot .t {
font-size: 14.5px;
font-weight: 600;
letter-spacing: -0.01em;
margin: 3px 0 4px;
}
.shot .d {
font-size: 12.5px;
color: var(--muted-foreground);
margin: 0;
line-height: 1.5;
}
.foot {
margin-top: 46px;
padding-top: 20px;
border-top: 1px solid var(--border);
color: var(--muted-foreground);
font-size: 13.5px;
line-height: 1.75;
}
.foot b {
color: var(--foreground);
font-weight: 500;
}
.foot code {
background: var(--muted);
padding: 1px 6px;
border-radius: 4px;
font-size: 12.5px;
}
@media (max-width: 900px) {
.grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="page">
<span class="eyebrow" style="font-size: 12px; letter-spacing: 0.1em">ClassroomIO · Prototype + PRD handoff</span>
<h1>Mobile App</h1>
<p class="lead">
A <b>learner-first</b> iOS and Android app for the people companies actually train — frontline staff, field
technicians, and distributed employees who often have no work laptop and unreliable connectivity. It opens on
<b>what needs your attention</b>, works <b>offline</b>, and finally gives ClassroomIO a <b>push channel</b> for
deadlines and renewals. A small tutor mode handles grading and attendance on the go; authoring and org
administration stay on the web.
</p>
<p class="lead" style="font-size: 13.5px">
Built on the real <code>packages/ui</code> tokens, rendered in a 390 × 844 device frame. Every page has a theme
toggle and cross-links so you can click through each journey end to end. The engineering PRD lives at
<code>prd/mobile-app/README.md</code>.
</p>
<!-- ENTRY -->
<div class="sec">1 · Entry — sign in &amp; organization</div>
<div class="grid">
<a class="card shot" href="auth.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.488 0.243 264.376), oklch(0.623 0.214 259.815))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</div>
<div class="b">
<div class="n">Auth · 3 states</div>
<div class="t">Sign in, org picker, biometric</div>
<p class="d">Password, Google, and enterprise SSO through Better Auth's Expo client; custom server URL for self-hosted; Face ID re-entry and an offline launch path.</p>
</div>
</a>
<a class="card shot" href="home.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.546 0.245 262.881), oklch(0.488 0.243 264.376))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m3 10 9-7 9 7v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg>
</div>
<div class="b">
<div class="n">Tab 1 · 3 states</div>
<div class="t">Home — attention surface</div>
<p class="d">Compliance alerts, continue-learning, due-soon, assignments, announcements, feedback — one <code>/account/home-digest</code> call. Plus caught-up and offline states.</p>
</div>
</a>
<a class="card shot" href="notifications.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.623 0.214 259.815), oklch(0.546 0.245 262.881))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/></svg>
</div>
<div class="b">
<div class="n">Retention · 3 states</div>
<div class="t">Notifications</div>
<p class="d">Permission pre-prompt, the notification center with deep links, and per-category preferences with quiet hours.</p>
</div>
</a>
</div>
<!-- LEARNING -->
<div class="sec">2 · Learner — the consumption path</div>
<div class="grid">
<a class="card shot" href="learn.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.424 0.199 265.638), oklch(0.488 0.243 264.376))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
</div>
<div class="b">
<div class="n">Tab 2 · 3 states</div>
<div class="t">Learn</div>
<p class="d">My learning with progress rings and download markers, the org catalog with enroll-in-place, and cohorts with goal progress.</p>
</div>
</a>
<a class="card shot" href="course.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.685 0.169 237.323), oklch(0.488 0.243 264.376))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><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>
</div>
<div class="b">
<div class="n">Detail · 3 states</div>
<div class="t">Course outline</div>
<p class="d">Complete / current / locked / downloaded on every row, a deadline banner for compliance courses, the download sheet, and the completed state.</p>
</div>
</a>
<a class="card shot" href="lesson.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.424 0.199 265.638), oklch(0.546 0.245 262.881))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m22 8-6 4 6 4V8z"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg>
</div>
<div class="b">
<div class="n">Player · 3 states</div>
<div class="t">Lesson</div>
<p class="d">HLS video with a playback token, transcript and AI-tutor sheets, the fair-use cap state, and offline playback with a queued completion.</p>
</div>
</a>
<a class="card shot" href="exercise.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.488 0.243 264.376), oklch(0.623 0.214 259.815))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
</div>
<div class="b">
<div class="n">Assessment · 3 states</div>
<div class="t">Exercises</div>
<p class="d">Autosaving single-choice, camera capture with upload progress, drag/hotspot/video types, and the graded result with a per-question breakdown.</p>
</div>
</a>
<a class="card shot" href="compliance.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.37 0.044 257.287), oklch(0.488 0.243 264.376))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></svg>
</div>
<div class="b">
<div class="n">Tab 3 · 2 states</div>
<div class="t">Compliance</div>
<p class="d">The differentiator as a tab: overdue with grace dates and consequences, expiring certifications, waivers, and the all-clear renewal timeline.</p>
</div>
</a>
<a class="card shot" href="certificates.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.685 0.169 237.323), oklch(0.424 0.199 265.638))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg>
</div>
<div class="b">
<div class="n">Proof · 2 states</div>
<div class="t">Certificate wallet</div>
<p class="d">Issued certificates sorted by what needs attention, plus the rendered certificate with share, save, and a verification ID/QR.</p>
</div>
</a>
</div>
<!-- PLATFORM -->
<div class="sec">3 · Learner — engagement &amp; platform</div>
<div class="grid">
<a class="card shot" href="community.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.488 0.243 264.376), oklch(0.685 0.169 237.323))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg>
</div>
<div class="b">
<div class="n">Tab 4 · 3 states</div>
<div class="t">Community &amp; newsfeed</div>
<p class="d">Mentions and followed threads, a Q&amp;A thread with the tutor answer promoted, and the offline read-only state with compose disabled.</p>
</div>
</a>
<a class="card shot" href="downloads.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.556 0 0), oklch(0.488 0.243 264.376))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>
</div>
<div class="b">
<div class="n">Offline · 2 states</div>
<div class="t">Downloads &amp; sync queue</div>
<p class="d">Storage breakdown, resumable downloads, Wi-Fi-only defaults, and every queued write with its sync state and retry count.</p>
</div>
</a>
<a class="card shot" href="profile.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.546 0.245 262.881), oklch(0.685 0.169 237.323))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
</div>
<div class="b">
<div class="n">Tab 5 · 2 states</div>
<div class="t">Profile &amp; settings</div>
<p class="d">Learner record, org switcher sheet, app settings and biometric lock — plus frontline mode, QR launch, and the accessibility commitments.</p>
</div>
</a>
</div>
<!-- TUTOR -->
<div class="sec">4 · Tutor companion — role-aware, same binary</div>
<div class="grid">
<a class="card shot" href="tutor-grading.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.424 0.199 265.638), oklch(0.546 0.245 262.881))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M9 15l2 2 4-4"/></svg>
</div>
<div class="b">
<div class="n">Teach · 2 states</div>
<div class="t">Grading queue</div>
<p class="d">Submissions across all courses, oldest first, and a review screen with answer, video, rubric, score, and recorded audio feedback.</p>
</div>
</a>
<a class="card shot" href="tutor-attendance.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.488 0.243 264.376), oklch(0.546 0.245 262.881))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18M9 16l2 2 4-4"/></svg>
</div>
<div class="b">
<div class="n">Teach · 2 states</div>
<div class="t">Attendance</div>
<p class="d">One-tap Present / Late / Absent on the roster, plus a rotating QR check-in code so learners mark themselves.</p>
</div>
</a>
<a class="card shot" href="tutor-course.html">
<div class="cover" style="background: linear-gradient(135deg, oklch(0.685 0.169 237.323), oklch(0.488 0.243 264.376))">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="m7 15 4-4 3 3 5-6"/></svg>
</div>
<div class="b">
<div class="n">Teach · 2 states</div>
<div class="t">Course snapshot &amp; moderation</div>
<p class="d">Completion, overdue learners, and where people get stuck; post an announcement; handle unanswered and reported posts.</p>
</div>
</a>
</div>
<div class="foot">
<p style="margin: 0 0 10px">
<b>Explicitly not in the app.</b> Course, lesson, and exercise authoring; the media manager and AI course builder;
org settings, team management, billing, SSO configuration, custom domains, and API keys; live-class video hosting;
SCORM playback; and <b>any purchase flow at all</b> — no checkout, no plan upgrade, no AI token packs, on any
storefront.
</p>
<p style="margin: 0 0 10px">
<b>Hard dependency.</b> Push notifications ride on <code>prd/notification-system</code>. Mobile contributes a
device-token registry and an Expo Push transport in the existing <code>apps/jobs</code> worker — never a second
notification pipeline.
</p>
<p style="margin: 0">
<b>Offline scope.</b> v1 caches content and queues two monotonic writes (lesson completion, playback position).
Exercises, submissions, and grading stay online so no assessment record can be corrupted by a replay — the same
line Canvas and Blackboard draw.
</p>
</div>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+329
View File
@@ -0,0 +1,329 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Learn — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Tab 2</span>
<h1>Learn — my learning, explore, cohorts</h1>
<p>
Everything the learner is enrolled in, everything the organization has published, and the cohorts they belong to.
Course rows carry a progress ring, a type chip (Compliance / Cohort / Self-paced), and a download marker.
Explore enrolls in place for assigned and free courses — there is deliberately no checkout anywhere in the app.
</p>
<div class="proto-links">
<a href="course.html">Course detail →</a>
<a href="downloads.html">Downloads →</a>
<a href="home.html">← Home</a>
</div>
</header>
<div class="stage">
<!-- 1. MY LEARNING -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<div class="title">Learn</div>
<div class="actions">
<button class="icon-btn" aria-label="Search"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg></button>
<a class="icon-btn" href="downloads.html" aria-label="Downloads"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></a>
</div>
</div>
<div class="tabs" data-select>
<button class="on" data-option>My learning</button>
<button data-option>Explore</button>
<button data-option>Cohorts</button>
</div>
<div class="screen">
<div class="segmented" data-select style="margin: 12px 0 14px">
<button class="on" data-option>In progress</button>
<button data-option>Not started</button>
<button data-option>Completed</button>
</div>
<div class="stack">
<a class="lrow current" href="course.html">
<div class="ring">
<svg viewBox="0 0 36 36"><circle class="track" cx="18" cy="18" r="15.5" fill="none" stroke-width="3.4"/><circle class="arc primary" cx="18" cy="18" r="15.5" fill="none" stroke-width="3.4" stroke-linecap="round" stroke-dasharray="97.4" stroke-dashoffset="65.3"/></svg>
<span class="lbl tnum">33%</span>
</div>
<div class="grow">
<div class="ltitle">Forklift Safety Recertification 2026</div>
<div class="lmeta">
<span class="badge badge-danger-soft">Overdue</span>
<span class="badge badge-outline">Compliance</span>
</div>
<div class="lmeta">
<span class="dl-dot"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span>Downloaded</span><span class="sep">·</span><span>Lesson 4 of 12</span>
</div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="course.html">
<div class="ring">
<svg viewBox="0 0 36 36"><circle class="track" cx="18" cy="18" r="15.5" fill="none" stroke-width="3.4"/><circle class="arc primary" cx="18" cy="18" r="15.5" fill="none" stroke-width="3.4" stroke-linecap="round" stroke-dasharray="97.4" stroke-dashoffset="73.1"/></svg>
<span class="lbl tnum">25%</span>
</div>
<div class="grow">
<div class="ltitle">Warehouse Fire Safety</div>
<div class="lmeta"><span class="badge badge-warning-soft">Due in 6 days</span><span class="badge badge-outline">Compliance</span></div>
<div class="lmeta">
<span class="dl-dot"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span>Downloaded</span><span class="sep">·</span><span>2 of 8 lessons</span>
</div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="course.html">
<div class="ring">
<svg viewBox="0 0 36 36"><circle class="track" cx="18" cy="18" r="15.5" fill="none" stroke-width="3.4"/><circle class="arc primary" cx="18" cy="18" r="15.5" fill="none" stroke-width="3.4" stroke-linecap="round" stroke-dasharray="97.4" stroke-dashoffset="29.2"/></svg>
<span class="lbl tnum">70%</span>
</div>
<div class="grow">
<div class="ltitle">Data Protection &amp; GDPR Essentials</div>
<div class="lmeta"><span class="badge badge-outline">Cohort</span><span>Q3 Frontline Onboarding</span></div>
<div class="lmeta"><span>Knowledge check due in 2 days</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="course.html">
<div class="ring">
<svg viewBox="0 0 36 36"><circle class="track" cx="18" cy="18" r="15.5" fill="none" stroke-width="3.4"/><circle class="arc primary" cx="18" cy="18" r="15.5" fill="none" stroke-width="3.4" stroke-linecap="round" stroke-dasharray="97.4" stroke-dashoffset="88.6"/></svg>
<span class="lbl tnum">9%</span>
</div>
<div class="grow">
<div class="ltitle">Customer Service Excellence</div>
<div class="lmeta"><span class="badge badge-outline">Self-paced</span><span>Optional</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
</div>
<nav class="tabbar" data-tabbar="learn"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>My learning</b>Progress ring, urgency badge, course type, and download state on every row — the four things a learner scans for.</figcaption>
</figure>
<!-- 2. EXPLORE -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<div class="title">Learn</div>
<div class="actions">
<button class="icon-btn" aria-label="Search"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg></button>
<a class="icon-btn" href="downloads.html" aria-label="Downloads"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></a>
</div>
</div>
<div class="tabs" data-select>
<button data-option>My learning</button>
<button class="on" data-option>Explore</button>
<button data-option>Cohorts</button>
</div>
<div class="screen">
<div class="search" style="margin: 12px 0 12px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
Search Northwind Academy
</div>
<div class="chips" data-select style="margin-bottom: 14px">
<button class="chip on" data-option>All</button>
<button class="chip" data-option>Safety</button>
<button class="chip" data-option>Compliance</button>
<button class="chip" data-option>Operations</button>
<button class="chip" data-option>Soft skills</button>
</div>
<div class="stack">
<div class="card" style="overflow: hidden">
<div style="height: 96px; background: linear-gradient(135deg, oklch(0.488 0.243 264.376), oklch(0.424 0.199 265.638)); position: relative">
<span class="badge badge-secondary" style="position: absolute; top: 10px; left: 10px">Safety</span>
</div>
<div class="card-pad">
<div class="ltitle" style="font-size: 15px">Hazardous Materials Handling</div>
<div class="lsub">Segregation, spill response, and documentation for warehouse teams.</div>
<div class="lmeta"><span>8 lessons</span><span class="sep">·</span><span>~45 min</span><span class="sep">·</span><span>Certificate</span></div>
<div class="row" style="margin-top: 12px">
<span class="badge badge-primary-soft">Assigned to you</span>
<button class="btn btn-primary btn-sm" style="margin-left: auto">Start</button>
</div>
</div>
</div>
<a class="lrow" href="course.html">
<div class="media sq" style="background: linear-gradient(135deg, oklch(0.488 0.243 264.376), oklch(0.424 0.199 265.638)); border: none; color: #fff">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
</div>
<div class="grow">
<div class="ltitle">Manual Handling &amp; Ergonomics</div>
<div class="lmeta"><span>6 lessons</span><span class="sep">·</span><span>~25 min</span></div>
</div>
<button class="btn btn-outline btn-sm">Enroll</button>
</a>
<a class="lrow" href="course.html">
<div class="media sq" style="background: linear-gradient(135deg, oklch(0.546 0.245 262.881), oklch(0.488 0.243 264.376)); border: none; color: #fff">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
</div>
<div class="grow">
<div class="ltitle">Working at Height</div>
<div class="lmeta"><span>5 lessons</span><span class="sep">·</span><span>~20 min</span></div>
</div>
<button class="btn btn-outline btn-sm">Enroll</button>
</a>
<a class="lrow" href="course.html">
<div class="media sq" style="background: linear-gradient(135deg, oklch(0.685 0.169 237.323), oklch(0.424 0.199 265.638)); border: none; color: #fff">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
</div>
<div class="grow">
<div class="ltitle">Difficult Conversations</div>
<div class="lmeta"><span>4 lessons</span><span class="sep">·</span><span>~18 min</span></div>
</div>
<button class="btn btn-outline btn-sm">Enroll</button>
</a>
</div>
<div class="banner banner-muted" style="margin-top: 16px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
<div class="bd" style="color: var(--muted-foreground)">
Paid enrollment is handled by your organization on the web — the app never asks for payment.
</div>
</div>
</div>
<nav class="tabbar" data-tabbar="learn"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Explore</b>The org catalog with enroll-in-place. No checkout, no upgrade prompt, no external purchase link — Decision 9 in the PRD.</figcaption>
</figure>
<!-- 3. COHORTS -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<div class="title">Learn</div>
<div class="actions">
<button class="icon-btn" aria-label="Search"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg></button>
<a class="icon-btn" href="downloads.html" aria-label="Downloads"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></a>
</div>
</div>
<div class="tabs" data-select>
<button data-option>My learning</button>
<button data-option>Explore</button>
<button class="on" data-option>Cohorts</button>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 12px 0 10px">
<div class="row between">
<div>
<div class="ltitle" style="font-size: 15px">Q3 Frontline Onboarding</div>
<div class="lsub">18 members · Daniel Mensah</div>
</div>
<span class="badge badge-success-soft">Active</span>
</div>
<div class="divider"></div>
<div class="eyebrow" style="margin-bottom: 8px">Cohort goal</div>
<div class="row">
<div class="progress tall success"><span style="width: 72%"></span></div>
<span class="tnum" style="font-size: 12px; font-weight: 600">13/18</span>
</div>
<div class="lmeta"><span>Everyone completes GDPR Essentials by 30 Aug · 9 days left</span></div>
<div class="divider"></div>
<div class="eyebrow" style="margin-bottom: 8px">Your progress</div>
<div class="stack-sm stack">
<div class="row">
<span class="dl-dot"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span style="font-size: 13px">Welcome &amp; expectations</span>
</div>
<div class="row">
<span class="dl-dot"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
<span style="font-size: 13px">Site induction</span>
</div>
<div class="row">
<span class="media" style="width: 16px; height: 16px; border-radius: 999px"></span>
<span style="font-size: 13px" class="text-muted">GDPR Essentials — knowledge check</span>
</div>
</div>
<a class="btn btn-outline btn-block btn-sm" href="course.html" style="margin-top: 14px; height: 36px">Open cohort course</a>
</div>
<div class="sec-head"><h2>Upcoming sessions</h2></div>
<div class="stack">
<div class="lrow">
<div class="media is-current"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg></div>
<div class="grow">
<div class="ltitle">Practical forklift assessment</div>
<div class="lmeta"><span>Thu 21 Aug · 14:00</span><span class="sep">·</span><span>Bay 4</span></div>
</div>
<span class="badge badge-outline">In person</span>
</div>
<div class="lrow">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m22 8-6 4 6 4V8z"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg></div>
<div class="grow">
<div class="ltitle">GDPR Q&amp;A with Daniel</div>
<div class="lmeta"><span>Fri 22 Aug · 10:00</span><span class="sep">·</span><span>Live class</span></div>
</div>
<button class="btn btn-outline btn-sm">Join</button>
</div>
</div>
<div class="hint" style="margin-top: 12px">
Live classes open in your organization's meeting provider — the app doesn't host video conferencing.
</div>
</div>
<nav class="tabbar" data-tabbar="learn"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Cohorts</b>Goal progress, the learner's own checklist, and upcoming sessions. Live classes hand off to the org's existing provider.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+324
View File
@@ -0,0 +1,324 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lesson player — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Player</span>
<h1>Lesson player</h1>
<p>
HLS video with captions, speed, PiP, background audio, and lock-screen controls, authorized by the new
<code>GET /hls/:assetId/playback-token</code> because a native player can't carry the web's per-asset cookie.
Transcript and the AI Lesson Tutor are bottom sheets over the same screen; mark-complete queues locally when offline.
</p>
<div class="proto-links">
<a href="course.html">← Course outline</a>
<a href="exercise.html">Next: knowledge check →</a>
<a href="downloads.html">Sync queue →</a>
</div>
</header>
<div class="stage">
<!-- 1. ONLINE PLAYER -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:46"></div>
<div class="navbar">
<a class="icon-btn plain" href="course.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow">
<div class="sub">Section 2 · Lesson 4 of 12</div>
<div class="title-sm">Pre-operation inspection</div>
</div>
<button class="icon-btn" aria-label="Lesson language"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20"/></svg></button>
</div>
<div class="screen">
<div class="player">
<span class="pill"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg> Downloaded</span>
<div class="play"><svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M8 5v14l11-7z"/></svg></div>
<div class="ctrl">
<div class="bar"><span style="width: 34%"></span></div>
<div class="meta">
<span>3:04</span>
<span style="opacity: 0.7">9:12</span>
<span class="right">
<span style="font-weight: 600">1.25x</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M7 15h4M14 15h3"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v3"/><rect x="12" y="13" width="10" height="7" rx="2"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
</span>
</div>
</div>
</div>
<div class="row" style="margin: 12px 0 6px">
<div class="segs">
<span class="seg done"></span><span class="seg done"></span><span class="seg done"></span><span class="seg now"></span>
<span class="seg"></span><span class="seg"></span><span class="seg"></span><span class="seg"></span>
<span class="seg"></span><span class="seg"></span><span class="seg"></span><span class="seg"></span>
</div>
<span class="text-muted tnum" style="font-size: 11.5px">4/12</span>
</div>
<h2 style="font-size: 18px; font-weight: 600; letter-spacing: -0.02em; margin: 10px 0 8px">
Pre-operation inspection checklist
</h2>
<div class="row" style="margin-bottom: 12px; gap: 6px; flex-wrap: wrap">
<span class="badge badge-outline">Video · 9 min</span>
<span class="badge badge-outline">English</span>
<span class="badge badge-success-soft">Available offline</span>
</div>
<div class="prose">
<p>
A pre-operation inspection takes about four minutes and catches most of the faults that cause workplace
incidents. Work through it in the same order every time so nothing gets skipped when you're in a hurry.
</p>
<h3>The seven checks</h3>
<ul>
<li><strong>Tyres and wheels</strong> — cuts, embedded debris, correct pressure.</li>
<li><strong>Forks</strong> — cracks at the heel, bent tips, locking pins seated.</li>
<li><strong>Hydraulic hoses</strong> — chafing, weeping seals, pooled fluid underneath.</li>
<li><strong>Mast and chains</strong> — even tension, no kinked links.</li>
<li><strong>Horn, lights, and alarm</strong> — audible and visible from 10 metres.</li>
<li><strong>Data plate</strong> — legible, and the rated capacity matches today's load.</li>
<li><strong>Seatbelt</strong> — retracts cleanly and latches.</li>
</ul>
<p>Log the result before you move the truck. An unlogged inspection counts as no inspection.</p>
</div>
<div class="stack" style="margin: 16px 0 12px">
<button class="lrow" data-sheet-open="transcript" style="width: 100%; text-align: left; cursor: pointer">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M8 13h8M8 17h5"/></svg></div>
<div class="grow"><div class="ltitle">Transcript</div><div class="lsub">Tap a line to jump to that moment</div></div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</button>
<button class="lrow" data-sheet-open="tutor" style="width: 100%; text-align: left; cursor: pointer">
<div class="media is-current"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m12 3 1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/></svg></div>
<div class="grow"><div class="ltitle">Ask the AI tutor</div><div class="lsub">Answers from this lesson only · 8 of 20 questions left today</div></div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</button>
</div>
<div class="divider"></div>
<a class="btn btn-primary btn-lg btn-block" href="lesson.html">
Mark complete &amp; continue
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg>
</a>
<div class="hint" style="text-align: center">Next: Load stability and centre of gravity · 7 min</div>
</div>
<div class="scrim" data-scrim-for="transcript" style="display: none"></div>
<div class="sheet" id="transcript" style="display: none">
<div class="grabber"></div>
<div class="sheet-head">
<div class="st">Transcript</div>
<button class="icon-btn plain" data-sheet-close="transcript" style="margin-left: auto" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div>
<div class="sheet-body">
<div class="lrow flat"><span class="text-muted tnum" style="font-size: 11.5px; width: 34px">0:12</span><div class="grow"><div style="font-size: 13.5px">Before you touch the controls, walk the truck. Start at the front-left tyre.</div></div></div>
<div class="lrow flat" style="background: color-mix(in oklab, var(--primary) 8%, transparent); border-radius: var(--radius-md); padding: 11px 8px">
<span class="text-primary tnum" style="font-size: 11.5px; width: 34px; font-weight: 600">3:04</span>
<div class="grow"><div style="font-size: 13.5px; font-weight: 500">Look for weeping seals — a wet film on the ram is a fault, not a maybe.</div></div>
</div>
<div class="lrow flat"><span class="text-muted tnum" style="font-size: 11.5px; width: 34px">3:41</span><div class="grow"><div style="font-size: 13.5px">Chains should have even tension. If one side sags, tag the truck out.</div></div></div>
<div class="lrow flat"><span class="text-muted tnum" style="font-size: 11.5px; width: 34px">4:18</span><div class="grow"><div style="font-size: 13.5px">The data plate tells you the rated capacity at a given load centre.</div></div></div>
<div class="lrow flat"><span class="text-muted tnum" style="font-size: 11.5px; width: 34px">5:02</span><div class="grow"><div style="font-size: 13.5px">Finally, the seatbelt. It has to retract cleanly and latch first time.</div></div></div>
</div>
</div>
<div class="scrim" data-scrim-for="tutor" style="display: none"></div>
<div class="sheet" id="tutor" style="display: none">
<div class="grabber"></div>
<div class="sheet-head">
<div class="media is-current" style="width: 30px; height: 30px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m12 3 1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/></svg></div>
<div><div class="st">AI lesson tutor</div><div class="lsub" style="margin: 0">Scoped to this lesson</div></div>
<button class="icon-btn plain" data-sheet-close="tutor" style="margin-left: auto" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div>
<div class="sheet-body">
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px">
<div style="max-width: 78%; background: var(--primary); color: var(--primary-foreground); padding: 9px 12px; border-radius: 16px 16px 4px 16px; font-size: 13.5px">
What counts as a "weeping" seal versus normal residue?
</div>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 12px">
<div class="media is-current" style="width: 26px; height: 26px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m12 3 1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/></svg></div>
<div style="max-width: 82%; background: var(--muted); padding: 9px 12px; border-radius: 16px 16px 16px 4px; font-size: 13.5px; line-height: 1.5">
Residue is dry and dull — old dust bonded to grease. Weeping is a wet, shiny film that reappears after you
wipe it. This lesson's rule at 3:04: if it comes back, tag the truck out.
<div class="lmeta" style="margin-top: 7px"><span class="badge badge-outline">From 3:04</span></div>
</div>
</div>
<div class="row" style="gap: 8px">
<input class="input" placeholder="Ask about this lesson…" />
<button class="icon-btn" style="width: 40px; height: 40px" aria-label="Send"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m22 2-7 20-4-9-9-4z"/></svg></button>
</div>
<div class="hint">8 of 20 questions left today · resets at midnight</div>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Online player</b>Video, native prose, and two sheets (transcript, AI tutor) — tap either row in the live prototype to open it.</figcaption>
</figure>
<!-- 2. AI TUTOR CAP -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="21:18"></div>
<div class="navbar">
<a class="icon-btn plain" href="course.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow">
<div class="sub">Section 2 · Lesson 5 of 12</div>
<div class="title-sm">Load stability</div>
</div>
</div>
<div class="screen">
<div class="player">
<div class="play"><svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M8 5v14l11-7z"/></svg></div>
<div class="ctrl">
<div class="bar"><span style="width: 88%"></span></div>
<div class="meta"><span>6:11</span><span style="opacity: 0.7">7:02</span></div>
</div>
</div>
<div class="prose" style="margin-top: 14px">
<p>
The load centre is the horizontal distance from the fork face to the load's centre of gravity. Every truck's
data plate is rated at a specific load centre — usually 500 mm.
</p>
</div>
</div>
<div class="scrim"></div>
<div class="sheet" style="display: flex">
<div class="grabber"></div>
<div class="sheet-head">
<div class="media is-current" style="width: 30px; height: 30px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m12 3 1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/></svg></div>
<div><div class="st">AI lesson tutor</div><div class="lsub" style="margin: 0">Scoped to this lesson</div></div>
<button class="icon-btn plain" style="margin-left: auto" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div>
<div class="sheet-body">
<div class="empty" style="padding: 22px 12px 16px">
<div class="ei">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
</div>
<div class="et">You've used today's 20 questions</div>
<div class="ed">Your allowance resets at midnight. The transcript and lesson notes are still available.</div>
</div>
<button class="btn btn-outline btn-block">Open transcript instead</button>
<button class="btn btn-ghost btn-block" style="margin-top: 6px">Ask in Community</button>
<div class="hint" style="text-align: center; margin-top: 10px">
Fair-use caps are set by your organization.
</div>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>AI tutor, cap reached</b>The fair-use limit is stated plainly with two real alternatives, not a paywall — the app never sells token packs.</figcaption>
</figure>
<!-- 3. OFFLINE / QUEUED -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="18:40" data-offline></div>
<div class="offline-strip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 2 22 22M8.5 16.5a5 5 0 0 1 7 0M5 13a10 10 0 0 1 4-2.4M12 20h.01"/></svg>
Offline — progress will sync automatically
</div>
<div class="navbar">
<a class="icon-btn plain" href="course.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow">
<div class="sub">Section 2 · Lesson 5 of 12</div>
<div class="title-sm">Load stability</div>
</div>
</div>
<div class="screen">
<div class="player">
<span class="pill"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg> Playing from device</span>
<div class="play"><svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg></div>
<div class="ctrl">
<div class="bar"><span style="width: 100%"></span></div>
<div class="meta"><span>7:02</span><span style="opacity: 0.7">7:02</span></div>
</div>
</div>
<div class="banner banner-muted" style="margin: 14px 0 12px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg>
<div class="grow">
<div class="bt">Marked complete</div>
<div class="bd">Saved on this device · will sync when you're back online</div>
</div>
</div>
<div class="sec-head" style="margin-top: 6px"><h2>Waiting to sync</h2><span class="count">2</span></div>
<div class="stack">
<div class="lrow">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 12a9 9 0 1 1-6.2-8.6"/><path d="M21 3v6h-6"/></svg></div>
<div class="grow">
<div class="ltitle">Lesson 5 completed</div>
<div class="lmeta"><span>Queued 18:40</span></div>
</div>
<span class="badge badge-muted">Queued</span>
</div>
<div class="lrow">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 12a9 9 0 1 1-6.2-8.6"/><path d="M21 3v6h-6"/></svg></div>
<div class="grow">
<div class="ltitle">Lesson 4 position 9:12</div>
<div class="lmeta"><span>Queued 18:31</span></div>
</div>
<span class="badge badge-muted">Queued</span>
</div>
</div>
<div class="divider"></div>
<a class="btn btn-primary btn-lg btn-block" href="lesson.html">
Next lesson
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg>
</a>
<div class="lrow locked" style="margin-top: 10px">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
<div class="grow">
<div class="ltitle">Section 2 check</div>
<div class="lsub">Exercises need a connection — we'll nudge you when you're back online</div>
</div>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Offline &amp; queued</b>Completion is a monotonic, idempotent intent held on-device — the reason v1 offline covers content but not assessments.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+263
View File
@@ -0,0 +1,263 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Notifications — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Retention</span>
<h1>Notifications, permission &amp; preferences</h1>
<p>
The single biggest reason to ship an app: ClassroomIO has no push channel today, only email. This rides on
<code>prd/notification-system</code> — mobile adds a device-token registry and an Expo Push transport in the existing
<code>apps/jobs</code> worker, never a second notification pipeline. Every category is independently switchable and
quiet hours defer rather than drop.
</p>
<div class="proto-links">
<a href="home.html">Deep-link target: Home →</a>
<a href="exercise.html">Deep-link target: Exercise →</a>
<a href="profile.html">← Profile</a>
</div>
</header>
<div class="stage">
<!-- 1. PRE-PROMPT -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:43"></div>
<div class="screen centered">
<div style="text-align: center">
<div style="width: 76px; height: 76px; border-radius: 22px; background: color-mix(in oklab, var(--primary) 12%, transparent); color: var(--primary); display: grid; place-items: center; margin: 0 auto 18px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="34" height="34" stroke-width="1.7"><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/></svg>
</div>
<div style="font-size: 20px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.3">
Never miss a deadline
</div>
<div class="text-muted" style="font-size: 14px; margin-top: 8px; line-height: 1.55">
Northwind Logistics assigns required training with real deadlines. Turn on notifications and we'll remind you
before something goes overdue — not after.
</div>
</div>
<div class="card card-pad" style="margin: 22px 0 20px; text-align: left">
<div class="lrow flat">
<div class="media is-warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg></div>
<div class="grow"><div class="ltitle">Deadline &amp; renewal reminders</div></div>
</div>
<div class="lrow flat">
<div class="media is-current"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></div>
<div class="grow"><div class="ltitle">New assignments and grades</div></div>
</div>
<div class="lrow flat">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg></div>
<div class="grow"><div class="ltitle">Replies to your questions</div></div>
</div>
</div>
<button class="btn btn-primary btn-lg btn-block">Turn on notifications</button>
<button class="btn btn-ghost btn-block" style="margin-top: 6px">Not now</button>
<div class="hint" style="text-align: center; margin-top: 12px">
You choose exactly which categories reach you — nothing is marketing.
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Permission pre-prompt</b>Asks in context with the value stated, so a "no" from the cold OS dialog doesn't permanently kill the channel.</figcaption>
</figure>
<!-- 2. CENTER -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:41"></div>
<div class="navbar">
<a class="icon-btn plain" href="home.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title">Notifications</div></div>
<button class="btn btn-ghost btn-sm">Mark all read</button>
</div>
<div class="screen">
<div class="sec-head" style="margin-top: 10px"><h2>Today</h2></div>
<div class="stack">
<a class="lrow" href="compliance.html">
<div class="media is-danger"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.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 0"/></svg></div>
<div class="grow">
<div class="ltitle">Forklift Safety is now overdue</div>
<div class="lsub">Grace period ends 24 Aug · 8 lessons left</div>
<div class="lmeta"><span>08:00</span></div>
</div>
<span style="width: 8px; height: 8px; border-radius: 999px; background: var(--primary)"></span>
</a>
<a class="lrow" href="exercise.html">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></div>
<div class="grow">
<div class="ltitle">Your Section 2 check was graded</div>
<div class="lsub">87% · Passed · Feedback from Daniel Mensah</div>
<div class="lmeta"><span>07:12</span></div>
</div>
<span style="width: 8px; height: 8px; border-radius: 999px; background: var(--primary)"></span>
</a>
<a class="lrow" href="community.html">
<div class="media is-current"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg></div>
<div class="grow">
<div class="ltitle">Daniel replied to your question</div>
<div class="lsub">"Yes — any crack at the heel is a tag-out…"</div>
<div class="lmeta"><span>06:40</span></div>
</div>
<span style="width: 8px; height: 8px; border-radius: 999px; background: var(--primary)"></span>
</a>
</div>
<div class="sec-head"><h2>Earlier</h2></div>
<div class="stack">
<a class="lrow" href="course.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg></div>
<div class="grow">
<div class="ltitle">New course assigned: Hazardous Materials Handling</div>
<div class="lsub">Assigned by Daniel Mensah · due 30 Sep</div>
<div class="lmeta"><span>Yesterday 16:20</span></div>
</div>
</a>
<a class="lrow" href="certificates.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></div>
<div class="grow">
<div class="ltitle">Certificate issued: GDPR Essentials</div>
<div class="lsub">Valid until 16 Aug 2027</div>
<div class="lmeta"><span>Sat 16 Aug</span></div>
</div>
</a>
<a class="lrow" href="compliance.html">
<div class="media is-warning"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg></div>
<div class="grow">
<div class="ltitle">Hazmat certification expires in 30 days</div>
<div class="lsub">Renewal opens 8 Sep</div>
<div class="lmeta"><span>Fri 15 Aug</span></div>
</div>
</a>
<a class="lrow" href="learn.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg></div>
<div class="grow">
<div class="ltitle">Cohort goal: 9 days left</div>
<div class="lsub">Q3 Frontline Onboarding · 13 of 18 done</div>
<div class="lmeta"><span>Thu 14 Aug</span></div>
</div>
</a>
</div>
</div>
<nav class="tabbar" data-tabbar="home"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Center</b>Grouped Today / Earlier with unread dots. Every row deep-links to the exact screen its push notification would open.</figcaption>
</figure>
<!-- 3. PREFERENCES -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:47"></div>
<div class="navbar bordered">
<a class="icon-btn plain" href="profile.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Notification preferences</div></div>
</div>
<div class="screen">
<div class="sec-head" style="margin-top: 12px"><h2>Training &amp; deadlines</h2></div>
<div class="card card-pad">
<div class="lrow flat">
<div class="grow"><div class="ltitle">New assignment</div><div class="lsub">When a course is assigned to you</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Deadline reminder</div><div class="lsub">7 days, 1 day, and morning-of</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Overdue alert</div><div class="lsub">Always on for required training</div></div>
<label class="switch"><input type="checkbox" checked disabled /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Certification renewal</div><div class="lsub">30 and 7 days before expiry</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
</div>
<div class="sec-head"><h2>Progress &amp; feedback</h2></div>
<div class="card card-pad">
<div class="lrow flat">
<div class="grow"><div class="ltitle">Exercise graded</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Certificate issued</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Cohort goal approaching</div></div>
<label class="switch"><input type="checkbox" /><span class="track"></span><span class="knob"></span></label>
</div>
</div>
<div class="sec-head"><h2>Community</h2></div>
<div class="card card-pad">
<div class="lrow flat">
<div class="grow"><div class="ltitle">Mentions &amp; replies</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">New lesson or newsfeed post</div></div>
<label class="switch"><input type="checkbox" /><span class="track"></span><span class="knob"></span></label>
</div>
</div>
<div class="sec-head"><h2>Quiet hours</h2></div>
<div class="card card-pad">
<div class="lrow flat">
<div class="grow"><div class="ltitle">Pause notifications</div><div class="lsub">Deferred, not dropped — they arrive after</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="kv"><span class="k">From</span><span class="v tnum">21:00</span></div>
<div class="kv"><span class="k">Until</span><span class="v tnum">07:00</span></div>
<div class="kv"><span class="k">Time zone</span><span class="v">Europe/London</span></div>
</div>
<div class="banner banner-muted" style="margin-top: 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
<div class="bd" style="color: var(--muted-foreground)">
These settings apply to push and in-app alike, and stay in sync with your email preferences on the web.
</div>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Preferences</b>Per-category switches writing to the same preference store the web uses. Overdue compliance alerts are deliberately not switchable.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+242
View File
@@ -0,0 +1,242 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Profile — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Learner · Tab 5</span>
<h1>Profile &amp; settings</h1>
<p>
The learner's own record plus the app's controls. Switching organization re-themes the app and swaps every list
behind it. Anything that belongs to org administration — billing, team management, SSO, domains — is deliberately a
link to the web, not a screen here.
</p>
<div class="proto-links">
<a href="downloads.html">Downloads →</a>
<a href="notifications.html">Notification preferences →</a>
<a href="certificates.html">Certificates →</a>
</div>
</header>
<div class="stage">
<!-- 1. PROFILE -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="9:50"></div>
<div class="navbar">
<div class="grow"><div class="title">Profile</div></div>
<button class="icon-btn" data-sheet-open="orgs" aria-label="Switch organization"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m18 8 4 4-4 4M2 12h20M6 16l-4-4 4-4"/></svg></button>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 10px 0 12px">
<div class="row">
<div class="avatar lg">AO</div>
<div class="grow">
<div style="font-size: 16px; font-weight: 600; letter-spacing: -0.02em">Amara Okafor</div>
<div class="lsub">[email protected]</div>
<div class="lmeta"><span class="badge badge-primary-soft">Learner</span><span>Warehouse Operations</span></div>
</div>
</div>
<div class="divider"></div>
<div class="row" style="align-items: stretch">
<div class="stat"><div class="v tnum">9</div><div class="k">Completed</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">6</div><div class="k">Certificates</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">83%</div><div class="k">On time</div></div>
</div>
</div>
<button class="lrow" data-sheet-open="orgs" style="width: 100%; text-align: left; cursor: pointer; margin-bottom: 14px">
<div class="avatar brand">NL</div>
<div class="grow">
<div class="ltitle">Northwind Logistics</div>
<div class="lsub">Tap to switch organization · 3 available</div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</button>
<div class="sec-head" style="margin-top: 0"><h2>Your learning</h2></div>
<div class="card card-pad">
<a class="lrow flat" href="certificates.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></div>
<div class="grow"><div class="ltitle">Certificates</div></div>
<span class="badge badge-outline">6</span>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow flat" href="compliance.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></svg></div>
<div class="grow"><div class="ltitle">Compliance record</div></div>
<span class="badge badge-danger-soft">1 overdue</span>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow flat" href="downloads.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 15V3M7 10l5 5 5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg></div>
<div class="grow"><div class="ltitle">Downloads</div></div>
<span class="badge badge-outline tnum">284 MB</span>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<div class="sec-head"><h2>App</h2></div>
<div class="card card-pad">
<a class="lrow flat" href="notifications.html">
<div class="media"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/></svg></div>
<div class="grow"><div class="ltitle">Notifications</div></div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<div class="kv"><span class="k">Language</span><span class="v">English (UK)</span></div>
<div class="kv"><span class="k">Theme</span><span class="v">Match system</span></div>
<div class="kv"><span class="k">Video quality</span><span class="v">Auto</span></div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Unlock with Face ID</div><div class="lsub">Re-lock the app after 5 minutes</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
</div>
<div class="sec-head"><h2>Account</h2></div>
<div class="card card-pad">
<div class="lrow flat">
<div class="grow"><div class="ltitle">Change password</div><div class="lsub">Opens the web dashboard</div></div>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="15" height="15" class="text-muted"><path d="M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Privacy &amp; data</div></div>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="15" height="15" class="text-muted"><path d="M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>
</div>
<a class="lrow flat" href="auth.html">
<div class="grow"><div class="ltitle" style="color: var(--destructive)">Sign out</div></div>
</a>
</div>
<div class="hint" style="text-align: center; margin-top: 14px">
ClassroomIO 1.0.0 (build 118) · Connected to app.classroomio.com
</div>
</div>
<div class="scrim" data-scrim-for="orgs" style="display: none"></div>
<div class="sheet" id="orgs" style="display: none">
<div class="grabber"></div>
<div class="sheet-head">
<div class="st">Switch organization</div>
<button class="icon-btn plain" data-sheet-close="orgs" style="margin-left: auto" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div>
<div class="sheet-body">
<div class="stack">
<div class="lrow current">
<div class="avatar brand">NL</div>
<div class="grow"><div class="ltitle">Northwind Logistics</div><div class="lmeta"><span class="badge badge-primary-soft">Learner</span><span>Current</span></div></div>
<span class="dl-dot"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></span>
</div>
<a class="lrow" href="tutor-grading.html">
<div class="avatar brand" style="background: oklch(0.424 0.199 265.638)">AH</div>
<div class="grow"><div class="ltitle">Acme Health Academy</div><div class="lmeta"><span class="badge badge-success-soft">Tutor</span><span>12 to grade</span></div></div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="home.html">
<div class="avatar brand" style="background: oklch(0.546 0.245 262.881)">MP</div>
<div class="grow"><div class="ltitle">Meridian Partner Network</div><div class="lmeta"><span class="badge badge-primary-soft">Learner</span></div></div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<div class="hint" style="margin-top: 12px">
Switching changes the theme, the courses, and your records. Downloads stay on the device per organization.
</div>
</div>
</div>
<nav class="tabbar" data-tabbar="profile"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Profile</b>Learner record, app settings, and the org switcher (tap the header row in the live prototype). Admin concerns link out to the web on purpose.</figcaption>
</figure>
<!-- 2. FRONTLINE MODE -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="6:05"></div>
<div class="navbar bordered">
<a class="icon-btn plain" href="profile.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Frontline mode</div></div>
</div>
<div class="screen">
<div class="banner banner-info" style="margin: 12px 0 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
<div class="grow">
<div class="bt">Built for shop floors and shared devices</div>
<div class="bd">Bigger targets, lighter media, and QR launch for staff without a work laptop.</div>
</div>
</div>
<div class="card card-pad" style="margin-bottom: 14px">
<div class="lrow flat">
<div class="grow"><div class="ltitle">Large touch targets</div><div class="lsub">Gloves-friendly rows and buttons</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Low-bandwidth media</div><div class="lsub">Audio-first playback on cellular</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Shared device</div><div class="lsub">Sign out automatically after each session</div></div>
<label class="switch"><input type="checkbox" /><span class="track"></span><span class="knob"></span></label>
</div>
</div>
<div class="card card-pad" style="text-align: center">
<div class="eyebrow">Scan to start a course</div>
<div
style="width: 150px; height: 150px; margin: 14px auto; border-radius: var(--radius-lg); border: 1px solid var(--border); background: var(--muted); display: grid; place-items: center; color: var(--muted-foreground)"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="66" height="66" stroke-width="1.5">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
<path d="M14 14h3v3h-3zM18 18h3v3h-3zM14 21h3M21 14v3"/>
</svg>
</div>
<div class="lsub">Point the camera at the poster in the break room, or a QR on the machine itself.</div>
<button class="btn btn-primary btn-lg btn-block" style="margin-top: 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="6" width="18" height="14" rx="2"/><circle cx="12" cy="13" r="3.5"/><path d="M8 6l1.5-2h5L16 6"/></svg>
Open scanner
</button>
</div>
<div class="sec-head"><h2>Accessibility</h2></div>
<div class="card card-pad">
<div class="kv"><span class="k">Text size</span><span class="v">Follows system (Dynamic Type)</span></div>
<div class="kv"><span class="k">Screen reader</span><span class="v">Full labels on every control</span></div>
<div class="kv"><span class="k">Status colours</span><span class="v">Always paired with an icon</span></div>
<div class="kv"><span class="k">Minimum target</span><span class="v tnum">44 pt</span></div>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Frontline mode &amp; accessibility</b>The settings that make the app usable in a warehouse: large targets, low-bandwidth media, shared-device sign-out, and QR launch.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+118
View File
@@ -0,0 +1,118 @@
/*
* proto.js — shared behaviour for the mobile-app prototypes.
*
* Deliberately tiny: a theme toggle, plus opt-in interactions (segmented
* controls, in-screen tabs, chips, bottom sheets, switches) that pages get
* for free by using the right data attribute. No framework, no build step.
*/
(function () {
// ---- device chrome ------------------------------------------------------
// <div class="statusbar" data-status="9:41" data-offline></div>
const SIGNAL =
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 20h.01M7 20v-4M12 20v-8M17 20V8"/></svg>';
const WIFI =
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M5 13a10 10 0 0 1 14 0M8.5 16.5a5 5 0 0 1 7 0M12 20h.01"/></svg>';
const WIFI_OFF =
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 2 22 22M8.5 16.5a5 5 0 0 1 7 0M5 13a10 10 0 0 1 4-2.4M12 20h.01"/></svg>';
const BATTERY =
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="7" width="18" height="10" rx="2"/><path d="M22 11v2"/></svg>';
document.querySelectorAll('[data-status]').forEach((bar) => {
const offline = bar.hasAttribute('data-offline');
bar.innerHTML = `<span>${bar.dataset.status}</span><span class="icons">${offline ? '' : SIGNAL}${
offline ? WIFI_OFF : WIFI
}${BATTERY}</span>`;
});
// <nav class="tabbar" data-tabbar="home" data-role="tutor"></nav>
const TAB_ICONS = {
home: '<path d="m3 10 9-7 9 7v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/>',
learn:
'<path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>',
compliance:
'<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/>',
teach:
'<path d="M21.4 10.9a1 1 0 0 0 0-1.8L12.8 5.2a2 2 0 0 0-1.6 0L2.6 9.1a1 1 0 0 0 0 1.8l8.6 3.9a2 2 0 0 0 1.6 0z"/><path d="M22 10v6"/><path d="M6 12.5V16a6 3 0 0 0 12 0v-3.5"/>',
community: '<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/>',
profile: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>'
};
const LEARNER_TABS = [
{ id: 'home', label: 'Home', href: 'home.html' },
{ id: 'learn', label: 'Learn', href: 'learn.html' },
{ id: 'compliance', label: 'Compliance', href: 'compliance.html' },
{ id: 'community', label: 'Community', href: 'community.html' },
{ id: 'profile', label: 'Profile', href: 'profile.html' }
];
const TUTOR_TABS = [
{ id: 'home', label: 'Home', href: 'home.html' },
{ id: 'learn', label: 'Learn', href: 'learn.html' },
{ id: 'teach', label: 'Teach', href: 'tutor-grading.html' },
{ id: 'community', label: 'Community', href: 'community.html' },
{ id: 'profile', label: 'Profile', href: 'profile.html' }
];
document.querySelectorAll('[data-tabbar]').forEach((bar) => {
const active = bar.dataset.tabbar;
const badges = (bar.dataset.badges || '').split(',').filter(Boolean);
const tabs = bar.dataset.role === 'tutor' ? TUTOR_TABS : LEARNER_TABS;
bar.innerHTML = tabs
.map((tab) => {
const badge = badges.find((entry) => entry.startsWith(`${tab.id}:`));
const value = badge ? badge.split(':')[1] : '';
const marker =
value === 'dot' ? '<span class="dot"></span>' : value ? `<span class="count">${value}</span>` : '';
return `<a class="tab${tab.id === active ? ' on' : ''}" href="${tab.href}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor">${
TAB_ICONS[tab.id]
}</svg>${tab.label}${marker}</a>`;
})
.join('');
});
// ---- theme toggle -------------------------------------------------------
const toggle = document.querySelector('.theme-toggle');
if (toggle) {
toggle.addEventListener('click', () => {
const dark = document.documentElement.classList.toggle('dark');
localStorage.cioTheme = dark ? 'dark' : 'light';
});
}
// ---- exclusive selection groups ----------------------------------------
// Any container with [data-select] makes its direct children mutually
// exclusive on click: .segmented, .tabs, .chips, option lists.
document.querySelectorAll('[data-select]').forEach((group) => {
const activeClass = group.dataset.select || 'on';
group.addEventListener('click', (event) => {
const target = event.target.closest('[data-option]');
if (!target || !group.contains(target)) return;
group.querySelectorAll('[data-option]').forEach((option) => option.classList.remove(activeClass));
target.classList.add(activeClass);
});
});
// ---- bottom sheets ------------------------------------------------------
// [data-sheet-open="id"] shows #id and its scrim; [data-sheet-close] hides.
function setSheet(id, open) {
const sheet = document.getElementById(id);
if (!sheet) return;
const scrim = sheet.parentElement.querySelector(`[data-scrim-for="${id}"]`);
sheet.style.display = open ? 'flex' : 'none';
if (scrim) scrim.style.display = open ? 'block' : 'none';
}
document.querySelectorAll('[data-sheet-open]').forEach((trigger) => {
trigger.addEventListener('click', () => setSheet(trigger.dataset.sheetOpen, true));
});
document.querySelectorAll('[data-sheet-close]').forEach((trigger) => {
trigger.addEventListener('click', () => setSheet(trigger.dataset.sheetClose, false));
});
document.querySelectorAll('[data-scrim-for]').forEach((scrim) => {
scrim.addEventListener('click', () => setSheet(scrim.dataset.scrimFor, false));
});
})();
+192
View File
@@ -0,0 +1,192 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tutor attendance — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Tutor · Release 3</span>
<h1>Attendance</h1>
<p>
The clearest case for a phone over a laptop: marking a roster while standing in the room. Backed by the existing
<code>/course/:courseId/attendance</code> route, with a QR/code check-in so learners mark themselves and the tutor
only handles exceptions.
</p>
<div class="proto-links">
<a href="tutor-grading.html">← Grading queue</a>
<a href="tutor-course.html">Course snapshot →</a>
</div>
</header>
<div class="stage">
<!-- 1. ROSTER -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="14:03"></div>
<div class="navbar bordered">
<a class="icon-btn plain" href="tutor-grading.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow">
<div class="title-sm">Practical assessment</div>
<div class="sub">Thu 21 Aug · 14:00 · Bay 4</div>
</div>
<button class="icon-btn" aria-label="Check-in code"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><path d="M14 14h3v3h-3zM18 18h3v3h-3z"/></svg></button>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 12px 0 12px">
<div class="row" style="align-items: stretch">
<div class="stat"><div class="v tnum">14</div><div class="k">Present</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">2</div><div class="k">Late</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">2</div><div class="k">Absent</div></div>
</div>
<div class="divider"></div>
<div class="row" style="gap: 8px">
<button class="btn btn-outline btn-sm" style="flex: 1">Mark all present</button>
<button class="btn btn-outline btn-sm" style="flex: 1">Reset</button>
</div>
</div>
<div class="search" style="margin-bottom: 12px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
Find a learner
</div>
<div class="stack stack-sm">
<div class="lrow">
<div class="avatar">AO</div>
<div class="grow"><div class="ltitle">Amara Okafor</div><div class="lmeta"><span>Checked in 13:52 via QR</span></div></div>
<div class="segmented" style="width: 128px; padding: 2px">
<button class="on" style="padding: 4px 6px; font-size: 11px">P</button>
<button style="padding: 4px 6px; font-size: 11px">L</button>
<button style="padding: 4px 6px; font-size: 11px">A</button>
</div>
</div>
<div class="lrow">
<div class="avatar">TA</div>
<div class="grow"><div class="ltitle">Tomás Alvarez</div><div class="lmeta"><span>Checked in 13:58 via QR</span></div></div>
<div class="segmented" style="width: 128px; padding: 2px">
<button class="on" style="padding: 4px 6px; font-size: 11px">P</button>
<button style="padding: 4px 6px; font-size: 11px">L</button>
<button style="padding: 4px 6px; font-size: 11px">A</button>
</div>
</div>
<div class="lrow">
<div class="avatar">PK</div>
<div class="grow"><div class="ltitle">Priya Kaur</div><div class="lmeta"><span>Arrived 14:09</span><span class="badge badge-warning-soft">Late</span></div></div>
<div class="segmented" style="width: 128px; padding: 2px">
<button style="padding: 4px 6px; font-size: 11px">P</button>
<button class="on" style="padding: 4px 6px; font-size: 11px">L</button>
<button style="padding: 4px 6px; font-size: 11px">A</button>
</div>
</div>
<div class="lrow">
<div class="avatar">MB</div>
<div class="grow"><div class="ltitle">Marcus Bell</div><div class="lmeta"><span>No check-in</span><span class="badge badge-danger-soft">Absent</span></div></div>
<div class="segmented" style="width: 128px; padding: 2px">
<button style="padding: 4px 6px; font-size: 11px">P</button>
<button style="padding: 4px 6px; font-size: 11px">L</button>
<button class="on" style="padding: 4px 6px; font-size: 11px">A</button>
</div>
</div>
<div class="lrow">
<div class="avatar">EN</div>
<div class="grow"><div class="ltitle">Esther Nwosu</div><div class="lmeta"><span>Checked in 13:47 via QR</span></div></div>
<div class="segmented" style="width: 128px; padding: 2px">
<button class="on" style="padding: 4px 6px; font-size: 11px">P</button>
<button style="padding: 4px 6px; font-size: 11px">L</button>
<button style="padding: 4px 6px; font-size: 11px">A</button>
</div>
</div>
<div class="lrow">
<div class="avatar">LC</div>
<div class="grow"><div class="ltitle">Liam Chen</div><div class="lmeta"><span>Checked in 13:55 via QR</span></div></div>
<div class="segmented" style="width: 128px; padding: 2px">
<button class="on" style="padding: 4px 6px; font-size: 11px">P</button>
<button style="padding: 4px 6px; font-size: 11px">L</button>
<button style="padding: 4px 6px; font-size: 11px">A</button>
</div>
</div>
</div>
<button class="btn btn-primary btn-lg btn-block" style="margin-top: 16px">Save attendance · 18 learners</button>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Roster</b>Present / Late / Absent as one-tap segments, with the QR check-in time shown so the tutor only corrects exceptions.</figcaption>
</figure>
<!-- 2. CHECK-IN CODE -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="13:45"></div>
<div class="navbar bordered">
<a class="icon-btn plain" href="tutor-attendance.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Check-in code</div><div class="sub">Practical assessment · Bay 4</div></div>
</div>
<div class="screen centered">
<div class="card card-pad" style="text-align: center; padding: 24px 18px">
<div class="eyebrow">Learners scan or type this</div>
<div
style="width: 190px; height: 190px; margin: 16px auto 14px; border-radius: var(--radius-lg); border: 1px solid var(--border); background: var(--muted); display: grid; place-items: center; color: var(--foreground)"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="92" height="92" stroke-width="1.4">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
<path d="M14 14h3v3h-3zM18 18h3v3h-3zM14 21h3M21 14v3"/>
</svg>
</div>
<div class="tnum" style="font-size: 30px; font-weight: 600; letter-spacing: 0.14em">4Q7 812</div>
<div class="lsub" style="margin-top: 6px">Expires in 14:52 · rotates automatically</div>
</div>
<div class="card card-pad" style="margin-top: 14px">
<div class="row between" style="margin-bottom: 10px">
<span class="eyebrow">Checked in so far</span>
<span class="tnum" style="font-size: 12px; font-weight: 600">14 of 18</span>
</div>
<div class="progress tall success" style="margin-bottom: 12px"><span style="width: 78%"></span></div>
<div class="row" style="gap: -8px">
<div class="avatar sm">AO</div>
<div class="avatar sm" style="margin-left: -8px">TA</div>
<div class="avatar sm" style="margin-left: -8px">EN</div>
<div class="avatar sm" style="margin-left: -8px">LC</div>
<div class="avatar sm" style="margin-left: -8px">RS</div>
<div class="avatar sm" style="margin-left: -8px; background: var(--muted); color: var(--muted-foreground)">+9</div>
<span class="lmeta" style="margin: 0 0 0 auto">Updating live</span>
</div>
</div>
<button class="btn btn-outline btn-block" style="margin-top: 14px">Show on the room display</button>
<a class="btn btn-ghost btn-block" href="tutor-attendance.html" style="margin-top: 6px">Back to roster</a>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>QR check-in</b>A rotating code the room can scan. The roster fills itself and the tutor's job shrinks to the four people it missed.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+248
View File
@@ -0,0 +1,248 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tutor course snapshot — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Tutor · Release 3</span>
<h1>Course snapshot, announcements &amp; moderation</h1>
<p>
Read-only insight plus the two write actions a tutor genuinely needs away from a desk: post an announcement and
moderate a thread. Authoring, analytics configuration, enrollment management, and org settings are deliberately
absent — they stay on the web.
</p>
<div class="proto-links">
<a href="tutor-grading.html">← Grading queue</a>
<a href="tutor-attendance.html">Attendance →</a>
<a href="community.html">Learner's community view →</a>
</div>
</header>
<div class="stage">
<!-- 1. SNAPSHOT -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="11:32"></div>
<div class="navbar bordered">
<a class="icon-btn plain" href="tutor-grading.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">HIPAA Awareness 2026</div><div class="sub">Acme Health Academy</div></div>
<button class="icon-btn" data-sheet-open="announce" aria-label="Post announcement"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m3 11 18-5v12L3 13z"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/></svg></button>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 12px 0 12px">
<div class="row" style="align-items: stretch">
<div class="stat"><div class="v tnum">64</div><div class="k">Enrolled</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">71%</div><div class="k">Completed</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum" style="color: var(--destructive)">7</div><div class="k">Overdue</div></div>
</div>
<div class="divider"></div>
<div class="row" style="margin-bottom: 6px">
<div class="progress tall success"><span style="width: 71%"></span></div>
<span class="tnum" style="font-size: 12px; font-weight: 600">45/64</span>
</div>
<div class="lmeta"><span>Deadline 30 Sep 2026 · 45 days left</span></div>
</div>
<div class="sec-head" style="margin-top: 0"><h2>Needs a nudge</h2><span class="count">7</span></div>
<div class="stack">
<div class="lrow">
<div class="avatar">MB</div>
<div class="grow"><div class="ltitle">Marcus Bell</div><div class="lmeta"><span>No activity in 21 days</span><span class="sep">·</span><span>1 of 9 lessons</span></div></div>
<button class="btn btn-outline btn-sm">Remind</button>
</div>
<div class="lrow">
<div class="avatar">RS</div>
<div class="grow"><div class="ltitle">Rina Sato</div><div class="lmeta"><span>Stalled at Lesson 4</span><span class="sep">·</span><span>9 days</span></div></div>
<button class="btn btn-outline btn-sm">Remind</button>
</div>
<div class="lrow">
<div class="avatar">JW</div>
<div class="grow"><div class="ltitle">Joseph Wanjiru</div><div class="lmeta"><span>Waiting on your grading</span></div></div>
<a class="btn btn-primary btn-sm" href="tutor-grading.html">Grade</a>
</div>
</div>
<button class="btn btn-outline btn-block" style="margin-top: 12px">Remind all 7 learners</button>
<div class="sec-head"><h2>Where learners get stuck</h2></div>
<div class="card card-pad">
<div class="lrow flat">
<div class="grow"><div class="ltitle">Lesson 4 · Minimum necessary rule</div><div class="lmeta"><span>11 learners stalled here</span></div></div>
<span class="badge badge-warning-soft">17%</span>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Scenario response</div><div class="lmeta"><span>Average score 6.8 / 10</span></div></div>
<span class="badge badge-warning-soft">Low</span>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Lesson 2 · Covered entities</div><div class="lmeta"><span>4 unanswered questions</span></div></div>
<span class="badge badge-outline">Q&amp;A</span>
</div>
</div>
<div class="hint" style="margin-top: 14px">
Full analytics, exports, and course editing live in the web dashboard.
</div>
</div>
<div class="scrim" data-scrim-for="announce" style="display: none"></div>
<div class="sheet" id="announce" style="display: none">
<div class="grabber"></div>
<div class="sheet-head">
<div class="st">Post an announcement</div>
<button class="icon-btn plain" data-sheet-close="announce" style="margin-left: auto" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div>
<div class="sheet-body">
<div class="field">
<label class="label">Audience</label>
<div class="segmented" data-select>
<button class="on" data-option>Everyone</button>
<button data-option>Overdue only</button>
<button data-option>One cohort</button>
</div>
</div>
<div class="field">
<label class="label">Message</label>
<textarea class="textarea">Reminder: the HIPAA scenario response is the last item before certification. If you're stuck on the "minimum necessary" rule, Lesson 4 has a worked example at 2:15.</textarea>
</div>
<div class="lrow" style="margin-bottom: 12px">
<div class="grow"><div class="ltitle">Also send as a push notification</div><div class="lsub">Respects each learner's quiet hours</div></div>
<label class="switch"><input type="checkbox" checked /><span class="track"></span><span class="knob"></span></label>
</div>
<button class="btn btn-primary btn-lg btn-block">Post to 64 learners</button>
</div>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Snapshot &amp; announcement</b>Enough insight to act — who's stalled and where — plus a one-tap nudge. Open the announcement sheet from the header in the live prototype.</figcaption>
</figure>
<!-- 2. MODERATION -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="11:40"></div>
<div class="navbar">
<div class="avatar brand" style="background: oklch(0.424 0.199 265.638)">AH</div>
<div class="grow"><div class="sub">Acme Health Academy</div><div class="title-sm">Teach</div></div>
</div>
<div class="tabs" data-select>
<button data-option>To grade</button>
<button data-option>Attendance</button>
<button data-option>Courses</button>
<button class="on" data-option>Moderation</button>
</div>
<div class="screen">
<div class="sec-head" style="margin-top: 12px"><h2>Unanswered questions</h2><span class="count">4</span></div>
<div class="stack">
<div class="card card-pad">
<div class="row row-top">
<div class="avatar">FA</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13.5px">Fatima Ali</div>
<span class="text-muted" style="font-size: 11px">2d</span>
</div>
<div class="lmeta" style="margin-top: 1px"><span>HIPAA Awareness · Lesson 2</span></div>
<div style="font-size: 13.5px; line-height: 1.5; margin-top: 6px">
Is a contracted cleaning company a business associate if they're never shown records?
</div>
<div class="row" style="gap: 8px; margin-top: 10px">
<button class="btn btn-primary btn-sm">Answer</button>
<button class="btn btn-outline btn-sm">Assign to tutor</button>
</div>
</div>
</div>
</div>
<div class="card card-pad">
<div class="row row-top">
<div class="avatar">LC</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13.5px">Liam Chen</div>
<span class="text-muted" style="font-size: 11px">3d</span>
</div>
<div class="lmeta" style="margin-top: 1px"><span>HIPAA Awareness · General</span></div>
<div style="font-size: 13.5px; line-height: 1.5; margin-top: 6px">
Does the training reset if I change department within the same hospital?
</div>
<div class="row" style="gap: 8px; margin-top: 10px">
<button class="btn btn-primary btn-sm">Answer</button>
<button class="btn btn-outline btn-sm">Assign to tutor</button>
</div>
</div>
</div>
</div>
</div>
<div class="sec-head"><h2>Reported</h2><span class="count">1</span></div>
<div class="card card-pad">
<div class="row row-top">
<div class="avatar">?</div>
<div class="grow">
<div class="row between">
<div class="ltitle" style="font-size: 13.5px">Anonymous learner</div>
<span class="badge badge-danger-soft">Reported ×2</span>
</div>
<div class="lmeta" style="margin-top: 1px"><span>SOC 2 Security Basics · Q&amp;A</span></div>
<div style="font-size: 13.5px; line-height: 1.5; margin-top: 6px; padding: 8px 10px; background: var(--muted); border-radius: var(--radius-md)">
"Here's the answer key from last year's exam, DM me…"
</div>
<div class="row" style="gap: 8px; margin-top: 10px">
<button class="btn btn-destructive btn-sm">Remove post</button>
<button class="btn btn-outline btn-sm">Keep</button>
<button class="btn btn-ghost btn-sm">View thread</button>
</div>
</div>
</div>
</div>
<div class="sec-head"><h2>Recently handled</h2></div>
<div class="stack stack-sm">
<div class="lrow flat">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Marked "audit trail" answer as accepted</div><div class="lmeta"><span>Yesterday</span></div></div>
</div>
<div class="lrow flat">
<div class="media is-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg></div>
<div class="grow"><div class="ltitle">Removed duplicate thread</div><div class="lmeta"><span>Mon 18 Aug</span></div></div>
</div>
</div>
</div>
<nav class="tabbar" data-tabbar="teach" data-role="tutor" data-badges="teach:12"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Moderation</b>Unanswered questions and reported posts in one queue, using the same author/team permission rules the API enforces.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>
+226
View File
@@ -0,0 +1,226 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tutor grading — ClassroomIO Mobile</title>
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="app-theme.css" />
<script>
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
document.documentElement.classList.add('dark');
</script>
</head>
<body>
<header class="proto-head">
<a class="back" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="14" height="14"><path d="m15 18-6-6 6-6"/></svg> All screens</a>
<span class="eyebrow">Tutor · Release 3</span>
<h1>Grading queue</h1>
<p>
The Teach tab appears only when <code>orgRoles[activeOrg]</code> is ADMIN or TUTOR — one binary, a role-aware tab bar.
A single new endpoint, <code>GET /account/grading-queue</code>, gathers submissions awaiting grading across every
course the tutor owns, oldest first, so a backlog can be cleared between meetings.
</p>
<div class="proto-links">
<a href="tutor-attendance.html">Attendance →</a>
<a href="tutor-course.html">Course snapshot →</a>
<a href="exercise.html">Learner's view →</a>
</div>
</header>
<div class="stage">
<!-- 1. QUEUE -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="11:15"></div>
<div class="navbar">
<div class="avatar brand" style="background: oklch(0.424 0.199 265.638)">AH</div>
<div class="grow"><div class="sub">Acme Health Academy</div><div class="title-sm">Teach</div></div>
<button class="icon-btn" aria-label="Filter"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 5h18l-7 8v6l-4 2v-8z"/></svg></button>
</div>
<div class="tabs" data-select>
<button class="on" data-option>To grade</button>
<button data-option>Attendance</button>
<button data-option>Courses</button>
<button data-option>Moderation</button>
</div>
<div class="screen">
<div class="card card-pad" style="margin: 12px 0 14px">
<div class="row" style="align-items: stretch">
<div class="stat"><div class="v tnum" style="color: var(--destructive)">3</div><div class="k">Over 3 days</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">12</div><div class="k">Awaiting</div></div>
<div style="width: 1px; background: var(--border)"></div>
<div class="stat"><div class="v tnum">28</div><div class="k">Graded this week</div></div>
</div>
</div>
<div class="sec-head" style="margin-top: 0"><h2>Waiting longest</h2></div>
<div class="stack">
<a class="lrow" href="tutor-grading.html">
<div class="avatar">JW</div>
<div class="grow">
<div class="ltitle">Joseph Wanjiru</div>
<div class="lsub">HIPAA Awareness · Scenario response</div>
<div class="lmeta"><span class="badge badge-danger-soft">Waiting 4 days</span><span class="badge badge-outline">Written + video</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="tutor-grading.html">
<div class="avatar">RS</div>
<div class="grow">
<div class="ltitle">Rina Sato</div>
<div class="lsub">SOC 2 Security Basics · Incident write-up</div>
<div class="lmeta"><span class="badge badge-warning-soft">Waiting 3 days</span><span class="badge badge-outline">Written</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="tutor-grading.html">
<div class="avatar">MB</div>
<div class="grow">
<div class="ltitle">Marcus Bell</div>
<div class="lsub">HIPAA Awareness · Scenario response</div>
<div class="lmeta"><span class="badge badge-warning-soft">Waiting 3 days</span><span class="badge badge-outline">Photo evidence</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<div class="sec-head"><h2>Recent</h2><span class="count">9</span></div>
<div class="stack">
<a class="lrow" href="tutor-grading.html">
<div class="avatar">EN</div>
<div class="grow">
<div class="ltitle">Esther Nwosu</div>
<div class="lsub">SOC 2 Security Basics · Incident write-up</div>
<div class="lmeta"><span>Submitted 6h ago</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="tutor-grading.html">
<div class="avatar">LC</div>
<div class="grow">
<div class="ltitle">Liam Chen</div>
<div class="lsub">HIPAA Awareness · Scenario response</div>
<div class="lmeta"><span>Submitted 9h ago</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
<a class="lrow" href="tutor-grading.html">
<div class="avatar">FA</div>
<div class="grow">
<div class="ltitle">Fatima Ali</div>
<div class="lsub">SOC 2 Security Basics · Incident write-up</div>
<div class="lmeta"><span>Submitted yesterday</span></div>
</div>
<span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></span>
</a>
</div>
<div class="hint" style="margin-top: 14px">
Authoring, org settings, and analytics configuration stay on the web — this tab is only the work that can't wait.
</div>
</div>
<nav class="tabbar" data-tabbar="teach" data-role="tutor" data-badges="teach:12"></nav>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Queue</b>Oldest first, with waiting time as the primary signal and the submission type visible before you open it.</figcaption>
</figure>
<!-- 2. REVIEW -->
<figure class="device">
<div class="phone">
<div class="phone-inner">
<div class="notch"></div>
<div class="statusbar" data-status="11:18"></div>
<div class="navbar bordered">
<a class="icon-btn plain" href="tutor-grading.html" aria-label="Back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15 18-6-6 6-6"/></svg></a>
<div class="grow"><div class="title-sm">Joseph Wanjiru</div><div class="sub">1 of 12 · HIPAA Awareness</div></div>
<button class="icon-btn" aria-label="Next submission"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m9 18 6-6-6-6"/></svg></button>
</div>
<div class="screen">
<div class="banner banner-danger" style="margin: 12px 0 14px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
<div class="grow"><div class="bt">Waiting 4 days</div><div class="bd">Learner's certification is blocked until this is graded</div></div>
</div>
<div class="eyebrow">Question · 10 points</div>
<div style="font-size: 15px; font-weight: 600; line-height: 1.4; margin: 6px 0 12px">
A colleague asks you to look up a patient record for a friend. Describe your response and the escalation path.
</div>
<div class="card card-pad" style="margin-bottom: 10px">
<div class="eyebrow" style="margin-bottom: 8px">Written answer</div>
<div style="font-size: 13.5px; line-height: 1.6">
I would decline and explain that access has to be tied to a care relationship. I'd remind them that the audit
log records every lookup by user, so an access without a clinical reason is traceable. If they pressed, I'd
report it to the privacy officer the same day using the internal form.
</div>
</div>
<div class="card card-pad" style="margin-bottom: 14px">
<div class="eyebrow" style="margin-bottom: 8px">Video response · 0:48</div>
<div class="player" style="margin: 0; border-radius: var(--radius-md); aspect-ratio: 16/10">
<div class="play" style="width: 44px; height: 44px"><svg viewBox="0 0 24 24" fill="currentColor" stroke="none" width="18" height="18"><path d="M8 5v14l11-7z"/></svg></div>
<div class="ctrl" style="padding: 14px 10px 8px"><div class="bar"><span style="width: 0%"></span></div><div class="meta"><span>0:00</span><span style="opacity: 0.7">0:48</span></div></div>
</div>
</div>
<div class="sec-head" style="margin-top: 0"><h2>Rubric</h2></div>
<div class="card card-pad" style="margin-bottom: 12px">
<div class="lrow flat">
<div class="grow"><div class="ltitle">Declines the request</div></div>
<span class="badge badge-success-soft">3 / 3</span>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Cites the audit trail</div></div>
<span class="badge badge-success-soft">3 / 3</span>
</div>
<div class="lrow flat">
<div class="grow"><div class="ltitle">Names the escalation path</div></div>
<span class="badge badge-warning-soft">2 / 4</span>
</div>
</div>
<div class="field">
<label class="label">Score</label>
<div class="row" style="gap: 8px">
<input class="input tnum" value="8" style="width: 76px; text-align: center; font-size: 17px; font-weight: 600" />
<span class="text-muted" style="font-size: 14px">/ 10 · pass mark 7</span>
</div>
</div>
<div class="field">
<label class="label">Feedback</label>
<textarea class="textarea">Good instinct on the audit trail. Name the privacy officer's 24-hour SLA next time — that's the part auditors check.</textarea>
<div class="row" style="gap: 8px; margin-top: 8px">
<button class="btn btn-outline btn-sm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2M12 19v3"/></svg> Record audio</button>
<span class="hint" style="margin: 0">Faster than typing on a phone</span>
</div>
</div>
<button class="btn btn-primary btn-lg btn-block">Submit grade &amp; next</button>
<button class="btn btn-outline btn-block" style="margin-top: 8px">Request resubmission</button>
</div>
<div class="home-bar"></div>
</div>
</div>
<figcaption><b>Review</b>Answer, video, and rubric on one screen, with recorded audio feedback because typing a paragraph on a phone is the real blocker.</figcaption>
</figure>
</div>
<button class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
</button>
<script src="proto.js"></script>
</body>
</html>