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 "Free includes passwords, cards, secure notes, two-factor codes, autofill, strong passwords, and breach checks.",32 "Pro adds encrypted sync, Peach Codex paper restore, passkeys, advanced vault health, canary entries, and duress tools.",33 ],34 warning: null,35 },36];3738const CARDS: OnboardingCard[] = BASE_CARDS;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 async 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 63 64 65 66 window.setTimeout(() => {67 void props.onComplete();68 }, 200);69 }70 }7172 function goBack() {73 if (exiting() || completing()) return;74 if (card() === 0) return;75 setExiting(true);76 window.setTimeout(() => {77 setCard((c) => c - 1);78 setExiting(false);79 }, CARD_EXIT_MS + CARD_ENTER_GAP_MS);80 }8182 return (83 <section84 class="peach-screen peach-theme peach-onboarding"85 classList={{ "is-completing": completing() }}86 >87 <RuneBackground opacity={0.07} />8889 <header class="peach-screen-header peach-onboarding-header">90 <PeachWordmark class="peach-wordmark-wrap" />91 </header>9293 <div94 class="peach-onboarding-card"95 classList={{ "is-exiting": exiting() }}96 onClick={advance}97 role="button"98 tabIndex={0}99 onKeyDown={(e) => {100 if (e.key === "Enter" || e.key === " ") {101 e.preventDefault();102 advance();103 }104 }}105 >106 <h2 class="peach-onboarding-card-headline">107 {CARDS[card()]!.headline}108 </h2>109 <hr class="peach-onboarding-divider" />110 <For each={CARDS[card()]!.body}>111 {(para) => <p class="peach-onboarding-body">{para}</p>}112 </For>113 <Show when={CARDS[card()]!.warning}>114 <p class="peach-onboarding-warning">115 <span class="peach-onboarding-warning-icon" aria-hidden="true">⚠</span>116 <span>{CARDS[card()]!.warning}</span>117 </p>118 </Show>119 </div>120121 <div class="peach-onboarding-nav">122 <Show when={card() > 0} fallback={<div class="peach-onboarding-back-spacer" />}>123 <button124 class="peach-onboarding-back"125 onClick={(e) => {126 e.stopPropagation();127 goBack();128 }}129 disabled={completing()}130 >131 ← back132 </button>133 </Show>134 <div class="peach-onboarding-dots" aria-hidden="true">135 <For each={CARDS}>136 {(_, i) => (137 <div138 class="peach-onboarding-dot"139 classList={{ "is-active": i() === card() }}140 />141 )}142 </For>143 </div>144 <button145 class="peach-onboarding-next"146 onClick={(e) => {147 e.stopPropagation();148 advance();149 }}150 disabled={completing()}151 >152 {card() === CARDS.length - 1 ? "Get started →" : "next →"}153 </button>154 </div>155 </section>156 );157}