1import { For, Show, createSignal } from "solid-js";23import { PeachWordmark } from "../components/PeachMark";4import { RuneBackground } from "../components/RuneBackground";56interface OnboardingCard {7 headline: string;8 body: string[];9 warning: string | null;10}1112const 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];3940const CARD_EXIT_MS = 120;41const CARD_ENTER_GAP_MS = 60;4243interface OnboardingProps {44 onComplete: () => void | Promise<void>;45}4647export function Onboarding(props: OnboardingProps) {48 const [card, setCard] = createSignal(0);49 const [exiting, setExiting] = createSignal(false);50 const [completing, setCompleting] = createSignal(false);5152 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 66 }67 window.setTimeout(() => {68 void props.onComplete();69 }, 200);70 }71 }7273 return (74 <section75 class="peach-screen peach-theme peach-onboarding"76 classList={{ "is-completing": completing() }}77 >78 <RuneBackground opacity={0.07} />7980 <header class="peach-screen-header peach-onboarding-header">81 <PeachWordmark class="peach-wordmark-wrap" />82 </header>8384 <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>111112 <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}