* 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]>
204 lines
11 KiB
HTML
204 lines
11 KiB
HTML
<!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 & 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>
|