From d60d16f252954391ec1e26fbc065d30a92a91d14 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 18:25:35 +0000 Subject: [PATCH 1/9] =?UTF-8?q?design:=20=F0=9F=8C=B8=20one=20card,=20one?= =?UTF-8?q?=20dock,=20one=20pink=20=E2=80=94=20restraint=20pass=20on=20the?= =?UTF-8?q?=20home=20page=20and=20info=20dialogs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modals - New islands/modal/CardModal.tsx: the single info-dialog skin on the useModalShell hook — cream card, max-w 460px, centered mascot badge, circular ✕ top-right, pill buttons. Gradient header bars are gone. - AboutModal: the seven-bullet tour is now a manifesto ("QR codes are boring. QRBuddy fixed that.") with one primary pill and a Supporter / Tip Jar row. First-visit intro keeps its three punches inside the same card. - PricingModal: the Free-vs-Supporter comparison grid is replaced by a single-tier unlock card — badge, four perks, magenta CTA with the price on it, one-line no-auto-renew caption, FAQ + pass restore tucked under one collapsed toggle. Checkout/claim logic untouched. - KofiModal: same card; the embed bleeds to the card edge; ESC hint gone. Page chrome - islands/FooterDock.tsx: one bottom bar, no divider line — © left, pill row right (About · Solutions ▴ popover · Tip Jar · Supporter). Pinned to the viewport bottom on both home routes; main column centers above it on desktop. - Top rail: StyleSelector trigger now shares the language toggle's lightweight pill recipe. - Dropped the uppercase ZERO SUBSCRIPTIONS pledge under the title; that line lives in the About manifesto now. Tokens - qr-pop / qr-popDeep added to the Tailwind theme as the one pink accent; all footer/link pills unified on rounded-full border-2 shadow-chunky. - CLAUDE.md layout rules updated (CardModal, dock, top rail, voice: QRBuddy is the entity, never "we"). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01FPzBAXN9jpnvWncMrM6vet --- CLAUDE.md | 22 ++- GLOSSARY.md | 7 +- README.md | 2 +- fresh.gen.ts | 4 + islands/AboutModal.tsx | 328 +++++++++++---------------------- islands/FooterDock.tsx | 87 +++++++++ islands/KofiModal.tsx | 98 ++++------ islands/PricingModal.tsx | 349 +++++++++++++++--------------------- islands/StyleSelector.tsx | 19 +- islands/modal/CardModal.tsx | 137 ++++++++++++++ islands/modal/README.md | 1 + routes/es/index.tsx | 62 ++----- routes/index.tsx | 64 ++----- tailwind.config.ts | 4 + 14 files changed, 568 insertions(+), 616 deletions(-) create mode 100644 islands/FooterDock.tsx create mode 100644 islands/modal/CardModal.tsx diff --git a/CLAUDE.md b/CLAUDE.md index be0be39..f32029a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,7 @@ HTML. - Orchestrates component layout - Handles SEO meta tags -**Islands (Interactive Components - 40 registered)** +**Islands (Interactive Components - 41 registered)** See GLOSSARY.md for complete list organized by category. Key islands: @@ -247,8 +247,20 @@ adding surfaces: border-black sm:rounded-3xl`, `sm:animate-pop-in`); `max-h-[92dvh]`; header/body `p-4 sm:p-6`; bottom padding respects the home indicator: `pb-[max(1rem,env(safe-area-inset-bottom))] sm:pb-6`. -- **Info dialogs** (About/Kofi/Pricing): centered at all sizes, gradient - header + `border-4` card, same backdrop/z as sheets. +- **Info dialogs** (About/Kofi/Pricing): always `islands/modal/CardModal.tsx` — + centered cream card at all sizes, `max-w-[460px]`, `border-4` + `rounded-3xl`, + mascot badge hanging centered at the top, circular `✕` top-right, same + backdrop/z as sheets. No gradient header bars. Titles are + `font-black text-2xl tracking-tight`; the one accent is `qr-pop` pink; primary + action is one full-width `rounded-full` pill (`CardPrimaryButton`), + secondaries are white bordered pills. Copy is a manifesto, not a spec sheet. +- **Footer dock**: `islands/FooterDock.tsx` — no divider line, metadata left, + utility pills right, Solutions pops upward from the dock. Home routes pin it + to the viewport bottom edge (`min-h-[100dvh]` shell, `mt-auto` footer). +- **Top rail**: language toggle and style picker share one lightweight pill + recipe + (`min-h-[36px] rounded-full border-2 text-xs font-black shadow-chunky`). +- **Voice**: QRBuddy is the entity. Never "we"/"us" in UI copy. - **Toasts**: bottom snackbar, `bottom-[max(1.5rem,env(safe-area-inset-bottom))]`, full-width flex container (never `left-1/2` — it caps layout width at 50vw and wraps). @@ -300,7 +312,7 @@ adding surfaces: ### Performance Optimizations -- **Islands Architecture**: Only interactive components hydrate (40 islands +- **Islands Architecture**: Only interactive components hydrate (41 islands total) - **Signal-Based State**: Efficient reactive updates without re-renders - **Lazy QR Updates**: QR regenerates only on url/style change via useEffect @@ -349,7 +361,7 @@ adding surfaces: redirect logic - **Visual Indicators**: Purple/pink "editable" badge on dynamic QRs alongside destructible badge -- **40 Interactive Islands**: Complete island architecture with modals, QR +- **41 Interactive Islands**: Complete island architecture with modals, QR types, and feature discovery ### Previous Features diff --git a/GLOSSARY.md b/GLOSSARY.md index 13de352..9b5db60 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -1,6 +1,6 @@ # Glossary - QRBuddy -## Islands (Interactive Components - 40 registered) +## Islands (Interactive Components - 41 registered) **Core QR Components:** @@ -22,9 +22,10 @@ **Modals & Overlays:** -- `AboutModal` - Feature overview + release notes +- `AboutModal` - Manifesto card (first-visit intro poster + about) - `KofiModal` - Ko-fi support modal -- `PricingModal` - Pro tier upsell modal +- `PricingModal` - Single-tier Supporter unlock card +- `FooterDock` - Bottom dock: metadata + About/Solutions/Tip Jar/Supporter pills **Dynamic + Locker Features:** diff --git a/README.md b/README.md index 88eec60..fec25ca 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ qrbuddy/ │ ├── f/[code].tsx # Destructible file download gate │ ├── bucket/[code].tsx # File locker viewer │ └── api/download-file.ts # Server-side file proxy -├── islands/ # Interactive Preact islands (40 registered) +├── islands/ # Interactive Preact islands (41 registered) │ ├── QRCanvas.tsx # Core QR rendering + download/copy logic │ ├── SmartInput.tsx # Smart input (URLs/files/text + dynamic settings) │ ├── StyleSelector.tsx # Gradient selector + custom creator entry diff --git a/fresh.gen.ts b/fresh.gen.ts index eb6dbe8..f23348f 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -25,6 +25,7 @@ import * as $ErrorBoundary from "./islands/ErrorBoundary.tsx"; import * as $FileSlideshow from "./islands/FileSlideshow.tsx"; import * as $GradientCreator from "./islands/GradientCreator.tsx"; import * as $HistoryDrawer from "./islands/HistoryDrawer.tsx"; +import * as $FooterDock from "./islands/FooterDock.tsx"; import * as $KofiModal from "./islands/KofiModal.tsx"; import * as $LogoUploader from "./islands/LogoUploader.tsx"; import * as $NoteCard from "./islands/NoteCard.tsx"; @@ -49,6 +50,7 @@ import * as $extras_LockerSettings from "./islands/extras/LockerSettings.tsx"; import * as $extras_MultiLinkSettings from "./islands/extras/MultiLinkSettings.tsx"; import * as $extras_SplashSettings from "./islands/extras/SplashSettings.tsx"; import * as $extras_TimeBombSettings from "./islands/extras/TimeBombSettings.tsx"; +import * as $modal_CardModal from "./islands/modal/CardModal.tsx"; import * as $modal_ModalShell from "./islands/modal/ModalShell.tsx"; import * as $modal_useModalShell from "./islands/modal/useModalShell.ts"; import * as $smart_input_FileUploadOptions from "./islands/smart-input/FileUploadOptions.tsx"; @@ -90,6 +92,7 @@ const manifest = { "./islands/FileSlideshow.tsx": $FileSlideshow, "./islands/GradientCreator.tsx": $GradientCreator, "./islands/HistoryDrawer.tsx": $HistoryDrawer, + "./islands/FooterDock.tsx": $FooterDock, "./islands/KofiModal.tsx": $KofiModal, "./islands/LogoUploader.tsx": $LogoUploader, "./islands/NoteCard.tsx": $NoteCard, @@ -114,6 +117,7 @@ const manifest = { "./islands/extras/MultiLinkSettings.tsx": $extras_MultiLinkSettings, "./islands/extras/SplashSettings.tsx": $extras_SplashSettings, "./islands/extras/TimeBombSettings.tsx": $extras_TimeBombSettings, + "./islands/modal/CardModal.tsx": $modal_CardModal, "./islands/modal/ModalShell.tsx": $modal_ModalShell, "./islands/modal/useModalShell.ts": $modal_useModalShell, "./islands/smart-input/FileUploadOptions.tsx": diff --git a/islands/AboutModal.tsx b/islands/AboutModal.tsx index b006fe9..60cecb4 100644 --- a/islands/AboutModal.tsx +++ b/islands/AboutModal.tsx @@ -1,10 +1,16 @@ import { useEffect } from "preact/hooks"; import { signal } from "@preact/signals"; -import { useModalShell } from "./modal/useModalShell.ts"; +import { + CardModal, + CardPrimaryButton, + CardSecondaryButton, +} from "./modal/CardModal.tsx"; +import { openPricingModal } from "./PricingModal.tsx"; +import { openKofiModal } from "./KofiModal.tsx"; // Global signal for modal state export const aboutModalOpen = signal(false); -// "intro" = short first-visit poster; "about" = the full tour. +// "intro" = first-visit poster with the three punches; "about" = the manifesto. export const aboutModalMode = signal<"intro" | "about">("about"); export function openAboutModal(mode: "intro" | "about" = "about") { @@ -18,6 +24,13 @@ export function closeAboutModal() { const INTRO_SEEN_KEY = "qrbuddy_intro_seen"; +// One modal at a time (the shell's scroll-lock is single-slot), so hand off +// to the next card after this one has let go. +function handOff(open: () => void) { + closeAboutModal(); + setTimeout(open, 60); +} + export function AboutModal() { const isOpen = aboutModalOpen.value; const isIntro = aboutModalMode.value === "intro"; @@ -35,237 +48,112 @@ export function AboutModal() { } }, []); - const shell = useModalShell({ open: isOpen, onClose: closeAboutModal }); - - if (!shell.mounted) return null; - - // ─── First-visit poster: mascot, rhyme, three punches, one CTA ─── - if (isIntro) { - return ( -