a4900d4d1c92
add Onboarding view with three-card welcome flow
- time
- 2026-04-28 04:41 UTC · 6 months ago
- machine
- koh2
- parent
- e056e5981747
↓ peach-browser-a4900d4d.tar.gzatlas at this save
files changed1 file
src/popup/views/Onboarding.tsx+136 −0src/popup/views/Onboarding.tsx+136 −0
@@ -0,0 +1,136 @@1+import { For, Show, createSignal } from "solid-js";2+3+import { PeachWordmark } from "../components/PeachMark";4+import { RuneBackground } from "../components/RuneBackground";5+6+interface OnboardingCard {7+ headline: string;8+ body: string[];9+ warning: string | null;10+}11+12+const CARDS: OnboardingCard[] = [13+ {14+ headline: "Your passwords\nlive here.\nNowhere else.",15+ body: [16+ "No account. No server. No one but you can ever see your vault.",17+ "Everything is encrypted on your device with a password only you know.",18+ ],19+ warning: null,20+ },21+ {22+ headline: "One rule.",23+ body: [24+ "Your master password is yours alone. Peach cannot recover it. There is no reset.",25+ "Choose something you will remember. Write it down somewhere safe.",26+ ],27+ warning: "If you forget it, your vault cannot be opened.",28+ },29+ {30+ headline: "Everything\nyou need.",31+ body: [32+ "Passwords, passkeys, cards, secure notes, two-factor codes.",33+ "Autofills on every site. Generates strong passwords. Detects breaches.",34+ "Free forever. Pro adds sync, mobile, and passkeys.",35+ ],36+ warning: null,37+ },38+];39+40+const CARD_EXIT_MS = 120;41+const CARD_ENTER_GAP_MS = 60;42+43+interface OnboardingProps {44+ onComplete: () => void | Promise<void>;45+}46+47+export function Onboarding(props: OnboardingProps) {48+ const [card, setCard] = createSignal(0);49+ const [exiting, setExiting] = createSignal(false);50+ const [completing, setCompleting] = createSignal(false);51+52+ function advance() {53+ if (exiting() || completing()) return;54+ if (card() < CARDS.length - 1) {55+ setExiting(true);56+ window.setTimeout(() => {57+ setCard((c) => c + 1);58+ setExiting(false);59+ }, CARD_EXIT_MS + CARD_ENTER_GAP_MS);60+ } else {61+ setCompleting(true);62+ try {63+ chrome.storage.local.set({ peach_onboarded: true });64+ } catch {65+ // storage unavailable — still complete the flow66+ }67+ window.setTimeout(() => {68+ void props.onComplete();69+ }, 200);70+ }71+ }72+73+ return (74+ <section75+ class="peach-screen peach-theme peach-onboarding"76+ classList={{ "is-completing": completing() }}77+ >78+ <RuneBackground opacity={0.07} />79+80+ <header class="peach-screen-header peach-onboarding-header">81+ <PeachWordmark class="peach-wordmark-wrap" />82+ </header>83+84+ <div85+ class="peach-onboarding-card"86+ classList={{ "is-exiting": exiting() }}87+ onClick={advance}88+ role="button"89+ tabIndex={0}90+ onKeyDown={(e) => {91+ if (e.key === "Enter" || e.key === " ") {92+ e.preventDefault();93+ advance();94+ }95+ }}96+ >97+ <h2 class="peach-onboarding-card-headline">98+ {CARDS[card()]!.headline}99+ </h2>100+ <hr class="peach-onboarding-divider" />101+ <For each={CARDS[card()]!.body}>102+ {(para) => <p class="peach-onboarding-body">{para}</p>}103+ </For>104+ <Show when={CARDS[card()]!.warning}>105+ <p class="peach-onboarding-warning">106+ <span class="peach-onboarding-warning-icon" aria-hidden="true">⚠</span>107+ <span>{CARDS[card()]!.warning}</span>108+ </p>109+ </Show>110+ </div>111+112+ <div class="peach-onboarding-nav">113+ <div class="peach-onboarding-dots" aria-hidden="true">114+ <For each={CARDS}>115+ {(_, i) => (116+ <div117+ class="peach-onboarding-dot"118+ classList={{ "is-active": i() === card() }}119+ />120+ )}121+ </For>122+ </div>123+ <button124+ class="peach-onboarding-next"125+ onClick={(e) => {126+ e.stopPropagation();127+ advance();128+ }}129+ disabled={completing()}130+ >131+ {card() === CARDS.length - 1 ? "Get started →" : "next →"}132+ </button>133+ </div>134+ </section>135+ );136+}