Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/web/app/_components/hero-background.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 13 additions & 5 deletions apps/web/app/_components/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,30 @@ import {
import { Badge } from "@workspace/ui/components/badge"
import { Button } from "@workspace/ui/components/button"

// Server Component. Backdrop is a pure CSS stripe texture — no image assets.
import heroBackground from "./hero-background.webp"

// Server Component. Backdrop is a static-imported image: Next fingerprints it
// into `.next/static`, which both deploy paths already ship — unlike `public/`,
// which only the pm2 deploy copies and the Dockerfile skips entirely.
export function Hero() {
return (
<section
aria-label="Introduction"
className="relative overflow-hidden border-b"
>
{/* Angled stripe texture — the hero backdrop (no photo). */}
{/* Backdrop image. Authored dark: shown as-is in dark mode, inverted in
light mode so it reads as a pale technical texture, not a black slab. */}
<div
aria-hidden="true"
className="absolute inset-0 bg-card [background-image:repeating-linear-gradient(135deg,var(--muted)_0_2px,transparent_2px_22px)]"
style={{ backgroundImage: `url(${heroBackground.src})` }}
className="absolute inset-0 bg-card bg-cover bg-[position:72%_center] invert md:bg-center dark:invert-0"
/>
{/* Readability overlay — fades to the theme background where the copy sits. */}
{/* Readability overlay — fades to the theme background where the copy sits.
Narrow screens get a near-flat scrim (copy spans the full width there);
from md the fade turns directional and leaves the artwork on the right. */}
<div
aria-hidden="true"
className="absolute inset-0 bg-[linear-gradient(90deg,var(--background)_0%,var(--background)_34%,transparent_82%),linear-gradient(0deg,var(--background)_2%,transparent_34%)]"
className="absolute inset-0 bg-[linear-gradient(0deg,var(--background)_14%,color-mix(in_oklab,var(--background)_78%,transparent)_62%,color-mix(in_oklab,var(--background)_38%,transparent)_100%)] md:bg-[linear-gradient(90deg,var(--background)_0%,var(--background)_34%,transparent_82%),linear-gradient(0deg,var(--background)_2%,transparent_34%)]"
/>

<div className="relative mx-auto max-w-6xl px-6 pt-24 pb-26">
Expand Down
7 changes: 4 additions & 3 deletions apps/web/app/getting-started/_components/guide-hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { Badge } from "@workspace/ui/components/badge"
const DEVOPS_DOC = "https://github.com/dev-geddy/backflip/blob/master/devops.md"

/**
* Hero band shared by the setup wizards. Same stripe-texture backdrop as the
* homepage hero, shorter. Copy comes from the page — one hero, one flavour of
* chrome, no per-guide fork.
* Hero band shared by the setup wizards: CSS stripe texture, shorter than the
* homepage hero (which carries the backdrop image — guides stay quiet, the
* artwork is a landing-page device). Copy comes from the page — one hero, one
* flavour of chrome, no per-guide fork.
*/
export function GuideHero({
title,
Expand Down
13 changes: 11 additions & 2 deletions docs/notes/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
- `packages/ui/components.json` — shadcn config. Satisfies `L2-UI-06`, `L2-UI-07`.
- `apps/web/app/layout.tsx` — mounts ThemeProvider + TooltipProvider + Toaster. Satisfies `L2-UI-04`, `L2-UI-09`.
- `apps/web/app/page.tsx` — public marketing homepage (RSC/SSR). Composes `_components/`: `SiteHeader`, `Hero`, `CapabilityList`, `FeatureList`, `HowItWorks`, `WordmarkBand`, `SiteFooter`. Sets page `metadata`. Satisfies `L2-UI-11` (proposed).
- `apps/web/app/_components/*` — homepage sections (app-scoped, `L1-ARCH-07/08`): `site-header.tsx` (`"use client"` — sticky nav + wordmark + `ThemeToggle`, links use `Button render={<a/>}`), `theme-toggle.tsx` (dark/light switch via `useTheme`, mount-guarded; `variant` outline|ghost + `className` props — shared by public and admin headers), `brand-icon.tsx` (shared arc glyph SVG — used by the public wordmark and the admin sidebar logo tile: bordered `bg-card` tile + `text-primary` glyph on both), `hero.tsx` (headline + CTAs over CSS stripe texture, no image; primary CTA → `/getting-started`, outline CTA "Fork it, make it yours" → GitHub's `/fork` route in a new tab — the "Open Admin" CTA was dropped, a first-time visitor has no account to open), `capability-list.tsx` (5 `CAPABILITIES` — the non-technical mirror of the feature list, see below), `feature-list.tsx` (5 `FEATURES`), `numbered-list.tsx` (the row layout both of them render), `how-it-works.tsx` (3-step band on `bg-muted`; "Clone" card CTA → GitHub repo (new tab), "Configure" card CTA → `/getting-started`, "Ship" card CTA → `/getting-started/start-building` (outline `Button render={<a/>}`)), `wordmark-band.tsx` (oversized `text-muted-foreground/20` accent), `site-footer.tsx` (copyright line also carries `AppVersion`), `app-version.tsx`. Theme tokens only, no hex. Icons remixicon only.
- `apps/web/app/_components/*` — homepage sections (app-scoped, `L1-ARCH-07/08`): `site-header.tsx` (`"use client"` — sticky nav + wordmark + `ThemeToggle`, links use `Button render={<a/>}`), `theme-toggle.tsx` (dark/light switch via `useTheme`, mount-guarded; `variant` outline|ghost + `className` props — shared by public and admin headers), `brand-icon.tsx` (shared arc glyph SVG — used by the public wordmark and the admin sidebar logo tile: bordered `bg-card` tile + `text-primary` glyph on both), `hero.tsx` (headline + CTAs over `hero-background.webp` — dark-authored artwork, `invert` in light mode, static-imported so Next fingerprints it into `.next/static`; readability scrim is near-flat below `md` and directional from `md` up, leaving the artwork right of the copy; primary CTA → `/getting-started`, outline CTA "Fork it, make it yours" → GitHub's `/fork` route in a new tab — the "Open Admin" CTA was dropped, a first-time visitor has no account to open), `capability-list.tsx` (5 `CAPABILITIES` — the non-technical mirror of the feature list, see below), `feature-list.tsx` (5 `FEATURES`), `numbered-list.tsx` (the row layout both of them render), `how-it-works.tsx` (3-step band on `bg-muted`; "Clone" card CTA → GitHub repo (new tab), "Configure" card CTA → `/getting-started`, "Ship" card CTA → `/getting-started/start-building` (outline `Button render={<a/>}`)), `wordmark-band.tsx` (oversized `text-muted-foreground/20` accent), `site-footer.tsx` (copyright line also carries `AppVersion`), `app-version.tsx`. Theme tokens only, no hex. Icons remixicon only.
- `apps/web/app/getting-started/page.tsx` — three-phase index (RSC, static): hero + `PHASES` sections (01 Discover / 02 Set up / 03 Start building), each a lead + guide cards; linked from the homepage "Configure" card. Satisfies `L2-UI-18`.
- `apps/web/app/getting-started/intro/page.tsx` — discovery page (RSC, static): what Backflip is, `IN_THE_BOX` + `AUDIENCE` card grids, CTA → setup wizard. Satisfies `L2-UI-18`.
- `apps/web/app/getting-started/start-building/page.tsx` — prompts-only page (RSC, static): `PROMPTS` (4 copy-ready coding-agent prompts — contact form, admin view, Resend notification, spam/polish) rendered via page-scoped `_components/prompt-block.tsx` (client — copy-to-clipboard prose card, wraps, no shell gutter). Linked from homepage "Ship" card. Satisfies `L2-UI-18`.
- `apps/web/app/getting-started/setup-on-digitalocean-droplet-docker-flavour/page.tsx` — placeholder (RSC, "Coming soon" badge): docker-flavour guide lands after the docker setup/deploy scripts are verified end-to-end; links to the pm2 guide + index. Satisfies `L2-UI-18`.
- `apps/web/app/getting-started/setup-on-digitalocean-droplet/page.tsx` — public guided deploy walkthrough (RSC shell, `metadata.title` "Setup on a DigitalOcean droplet"); composes `SiteHeader` → `GuideHero` → `SetupGuide` → `SiteFooter`. Prerenders static. See "Getting-started guide route"; satisfies `L2-UI-18`.
- `apps/web/app/getting-started/setup-on-digitalocean-droplet/_components/*` — page-scoped (`L1-ARCH-07/08`): `guide-hero.tsx` (RSC, homepage stripe-texture backdrop, smaller clamp), `setup-vars.ts` (plain module — `SetupVars`, `resolve()`, one builder per command block; `@spec L2-DEVOPS-01, L2-DEVOPS-02, L2-DEVOPS-06`), `variables-form.tsx` (client — the single shared form + its guidance layout), `field-guidance.tsx` (client — step-1 focus-driven help panel), `command-block.tsx` (client — copy-to-clipboard `<pre>` + `<placeholder>` token highlighting, `compact` variant), `setup-guide.tsx` (client island — owns `SetupVars` + step index, sessionStorage persistence, Back/Next/Reset), `setup-steps.tsx` (client — `STEPS` metadata + `StepBody`, all guide copy; step 1 "Get a droplet" — image/size/ssh-key/DNS checklist, ssh-keygen block (keygen lives here, not in the variables sidebar) + DigitalOcean referral-link CTA (m.do.co, labeled as referral); step 2 "Clone" — local software checklist (git, Node 24 + corepack, OpenSSH/openssl) + clone/install block + Claude-Code-primary note (other agents untested); final Summary step renders + downloads `backflip-setup-summary.txt` via Blob — values incl. owner password with a keep-private warning, plus day-two command reference from `summaryDocument()` in setup-vars), `wizard-stepper.tsx` (client — numbered clickable progress dots).
- `apps/web/app/getting-started/setup-on-digitalocean-droplet/_components/*` — page-scoped (`L1-ARCH-07/08`): `guide-hero.tsx` (RSC, CSS stripe-texture backdrop — the homepage hero moved to an image, guides kept the stripes; smaller clamp), `setup-vars.ts` (plain module — `SetupVars`, `resolve()`, one builder per command block; `@spec L2-DEVOPS-01, L2-DEVOPS-02, L2-DEVOPS-06`), `variables-form.tsx` (client — the single shared form + its guidance layout), `field-guidance.tsx` (client — step-1 focus-driven help panel), `command-block.tsx` (client — copy-to-clipboard `<pre>` + `<placeholder>` token highlighting, `compact` variant), `setup-guide.tsx` (client island — owns `SetupVars` + step index, sessionStorage persistence, Back/Next/Reset), `setup-steps.tsx` (client — `STEPS` metadata + `StepBody`, all guide copy; step 1 "Get a droplet" — image/size/ssh-key/DNS checklist, ssh-keygen block (keygen lives here, not in the variables sidebar) + DigitalOcean referral-link CTA (m.do.co, labeled as referral); step 2 "Clone" — local software checklist (git, Node 24 + corepack, OpenSSH/openssl) + clone/install block + Claude-Code-primary note (other agents untested); final Summary step renders + downloads `backflip-setup-summary.txt` via Blob — values incl. owner password with a keep-private warning, plus day-two command reference from `summaryDocument()` in setup-vars), `wizard-stepper.tsx` (client — numbered clickable progress dots).
- `apps/web/app/backflip/(protected)/users/page.tsx` — admin Members surface (RSC). Selects `users` display fields + `emailVerified`/`createdAt` (no hash), newest first; derives per-member `loginMethods`, `usesGoogle`, `status` (Active/Pending) + workspace counts; renders `MembersView`. Sidebar `Users` links here. (Members master-detail — see "Members (design 1A)".)
- `apps/web/app/backflip/(protected)/ui-samples/page.tsx` — component demo, admin-only (moved from public `/ui-samples`; auth-gated by the edge proxy, second Platform nav item below Overview, capability `dashboard`). Public header/footer/hero links now point to `/getting-started`. Dashboard/masonry layout reproducing the `base-mira` create-preview; exercises ~50 components (item, field, input-group, native-select, toggle-group, chart/recharts, empty, spinner, progress, calendar, radio, table, tabs, accordion, …). `d` = dark toggle. Satisfies `L2-UI-05`, `L2-UI-12`. Note: uncontrolled `defaultValue` passed to base-ui ToggleGroup/Slider must be stable module-scope refs (base-ui warns on identity change per render).
- `apps/web/next.config.ts` — `transpilePackages: ["@workspace/ui"]`. Satisfies `L2-UI-10`.
Expand Down Expand Up @@ -133,6 +133,15 @@ Both render the real `devops/` commands with the operator's variables substitute
- Step 2 (Clone) carries the pm2 guide's Claude-Code note too (it was missing here — only the docker-specific "no Docker needed locally" note was present), extended with the line that ties it to the step-9 handoff: the shipped instructions are what let the agent run the rest of the guide, docker flavour included.
- Step 8 owner seed drops the `. $HOME/.nvm/nvm.sh` sourcing — Node is system-wide from apt here, so `corepack` is on the app user's PATH.

## Homepage hero backdrop image
`apps/web/app/_components/hero.tsx` + `hero-background.webp` (colocated, `L1-ARCH-08`).
- **Why a static import, not `public/`**: `public/` is copied by `deploy-for-pm2-build-locally.sh` only — the Dockerfile ships `.next/standalone` + `.next/static` and nothing else, so a `public/` asset would 404 on the docker flavour. A static-imported asset lands in `.next/static` (content-hashed, immutable-cached) and both deploy paths already carry it. No `public/` dir exists in the repo; keep it that way unless both paths are taught about it.
- **No `next/image`**: the optimizer pulls `sharp` (native module) onto the serving path, and the build-locally deploy rsyncs the build machine's traced `node_modules` — a macOS build would ship darwin `sharp` to Ubuntu. Plain CSS `background-image` off `heroBackground.src`.
- Source art is dark. Light mode uses `invert` (pale technical texture on white) rather than a second asset or an opacity knock-back, which turned the artwork to grey mud.
- Encoding: 1536×1024 source PNG (1.4 MB) → WebP q88, **36 KB**. Flat dark gradients compress hard; q72 was 17 KB but banded. The source PNG is deliberately not committed.
- Scrim is two-tier: below `md` a near-flat vertical fade (copy spans the full width there) with the crop pushed to `72%` so the diagonals stay visible; from `md` up the original directional fade (`--background` → transparent at 82%) keeps the left column clean and the artwork right of the copy.
- Guide heroes (`getting-started/_components/guide-hero.tsx`) kept the CSS stripe texture — the image is a landing-page device only.

## Homepage capability section (non-developer audience)
`apps/web/app/_components/capability-list.tsx` — sits between `Hero` and `FeatureList`.
- **Why**: the "Included" grid answers *what is this built from* (auth, Postgres, shadcn, AI wiring) — useful to developers, opaque to the non-devs the project is pitched at. This section answers *what can I do with it* using the same five slots, so the pair reads as two views of one product rather than two lists.
Expand Down