1import { For, Show, createSignal } from "solid-js";2import { PeachWordmark } from "../components/PeachMark";3import { RuneBackground } from "../components/RuneBackground";45interface OnboardingCard {6 headline: string;7 body: string[];8 warning: string | null;9}1011const BASE_CARDS: OnboardingCard[] = [12 {13 headline: "Your passwords\nlive here.\nNowhere else.",14 body: [15 "No account. No server. No one but you can ever see your vault.",16 "Everything is encrypted on your device with a password only you know.",17 ],18 warning: null,19 },20 {21 headline: "Two keys.",22 body: [23 "Your master password is yours alone. Peach never sees it.",24 "If you forget it, your 12 recovery words can reset it from settings — save them somewhere safe.",25 ],26 warning: "Lose both and your vault cannot be opened.",27 },28 {29 headline: "Everything\nyou need.",30 body: [31 "Passwords, passkeys, cards, secure notes, two-factor codes.",32 "Autofills on every site. Generates strong passwords. Detects breaches.",33 "Free forever. Pro adds sync, passkeys, and advanced vault health.",34 ],35 warning: null,36 },37];3839const CARDS: OnboardingCard[] = BASE_CARDS;4041const CARD_EXIT_MS = 120;42const CARD_ENTER_GAP_MS = 60;4344interface OnboardingProps {45 onComplete: () => void | Promise<void>;46}4748export function Onboarding(props: OnboardingProps) {49 const [card, setCard] = createSignal(0);50 const [exiting, setExiting] = createSignal(false);51 const [completing, setCompleting] = createSignal(false);5253 async function advance() {54 if (exiting() || completing()) return;55 if (card() < CARDS.length - 1) {56 setExiting(true);57 window.setTimeout(() => {58 setCard((c) => c + 1);59 setExiting(false);60 }, CARD_EXIT_MS + CARD_ENTER_GAP_MS);61 } else {62 setCompleting(true);63 64 65 66 67 window.setTimeout(() => {68 void props.onComplete();69 }, 200);70 }71 }7273 function goBack() {74 if (exiting() || completing()) return;75 if (card() === 0) return;76 setExiting(true);77 window.setTimeout(() => {78 setCard((c) => c - 1);79 setExiting(false);80 }, CARD_EXIT_MS + CARD_ENTER_GAP_MS);81 }8283 return (84 <section85 class="peach-screen peach-theme peach-onboarding"86 classList={{ "is-completing": completing() }}87 >88 <RuneBackground opacity={0.07} />8990 <header class="peach-screen-header peach-onboarding-header">91 <PeachWordmark class="peach-wordmark-wrap" />92 </header>9394 <div95 class="peach-onboarding-card"96 classList={{ "is-exiting": exiting() }}97 onClick={advance}98 role="button"99 tabIndex={0}100 onKeyDown={(e) => {101 if (e.key === "Enter" || e.key === " ") {102 e.preventDefault();103 advance();104 }105 }}106 >107 <h2 class="peach-onboarding-card-headline">108 {CARDS[card()]!.headline}109 </h2>110 <hr class="peach-onboarding-divider" />111 <For each={CARDS[card()]!.body}>112 {(para) => <p class="peach-onboarding-body">{para}</p>}113 </For>114 <Show when={CARDS[card()]!.warning}>115 <p class="peach-onboarding-warning">116 <span class="peach-onboarding-warning-icon" aria-hidden="true">⚠</span>117 <span>{CARDS[card()]!.warning}</span>118 </p>119 </Show>120 </div>121122 <div class="peach-onboarding-nav">123 <Show when={card() > 0} fallback={<div class="peach-onboarding-back-spacer" />}>124 <button125 class="peach-onboarding-back"126 onClick={(e) => {127 e.stopPropagation();128 goBack();129 }}130 disabled={completing()}131 >132 ← back133 </button>134 </Show>135 <div class="peach-onboarding-dots" aria-hidden="true">136 <For each={CARDS}>137 {(_, i) => (138 <div139 class="peach-onboarding-dot"140 classList={{ "is-active": i() === card() }}141 />142 )}143 </For>144 </div>145 <button146 class="peach-onboarding-next"147 onClick={(e) => {148 e.stopPropagation();149 advance();150 }}151 disabled={completing()}152 >153 {card() === CARDS.length - 1 ? "Get started →" : "next →"}154 </button>155 </div>156 </section>157 );158}