The landing site at https://stuff.flopbut.pl - a React SPA deployed on Cloudflare Workers.
- Vite + React 19 + React Router v7 - SPA, client-side routing.
- Plain CSS with design tokens -
src/styles/tokens.cssis the single source of truth for color, typography, spacing, radius, shadows, and motion. Components consume only semantic tokens (--color-primary, not--cyan-400). - Cloudflare Workers static assets - served via
wrangler.json; SPA not-found handling re-routes all 404s toindex.html. - Domain -
stuff.flopbut.pl(subdomain offlopbut.pl, same CF account). Moved here fromfs.aignite.pl.
All projects live in src/data/projects.ts. Add a new entry to the projects array -
at the top of its category block, never at the end:
{
slug: "my-project", // URL slug - must be unique
name: "my-project", // display name
category: "ai-dev-tooling", // "ai-dev-tooling" | "ksef" | "hardware" | "forks"
tagline: "One sentence.", // shown on the card (clamped to 2 lines)
description: "Longer…", // shown on the detail page
repoUrl: "https://github.com/Flopsstuff/my-project",
webUrl: "https://optional.example.com", // omit if none
status: "active", // "active" | "experimental" | "fork" - omit if unknown
}The home page and detail pages pick it up automatically.
A new project goes first in its section on https://stuff.flopbut.pl and everything else
shifts down. The home page renders each category straight from this array's order
(getProjectsByCategory only filters, it does not sort), so position in the file is position
on the page: put the new entry directly above the first existing entry of the same category.
The same rule applies to the org profile list in profile/README.md - a new row goes at the
top of its table, not appended to the bottom. Adding a project means touching both files, plus
an optional detail page in src/content/<slug>.md.
| Command | Action |
|---|---|
yarn install --immutable |
Install dependencies (pinned Yarn 4.6.0 via Corepack) |
yarn build |
TypeScript check + Vite production build → dist/ |
yarn preview |
wrangler dev - serve the dist/ build locally at http://localhost:8787 |
yarn deploy |
Deploy to Cloudflare Workers (sources .env for credentials) |
yarn deploy --dry-run |
Validate config and asset bundle without actually deploying |
Copy .env.example to .env and fill in:
CLOUDFLARE_API_TOKEN=...
CLOUDFLARE_ACCOUNT_ID=42548ca95c85a68b4ce20ad79b805334
yarn deploy sources .env automatically. The file is gitignored.
.github/workflows/deploy.yml deploys on every push to main:
Node 22 → Corepack → yarn install --immutable → wrangler deploy (via cloudflare/wrangler-action@v3).
Required GitHub Actions secrets: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID (both already set).
The brand guide and design tokens live in docs/brand/:
docs/brand/tokens.css→ copied tosrc/styles/tokens.css(Iris owns this file).docs/brand/README.md- palette, typography, spacing, logo usage, do/don't.
Primary palette: electric cyan (--color-primary). Red (#ff2d2d) is the logo/accent color only - never a primary CTA or body link color.
src/
components/ Header, Footer, ProjectCard
pages/ Home, ProjectDetail (includes in-app 404 state)
data/ projects.ts - the project registry
styles/ tokens.css - design tokens (import once at root)
index.css global resets + token import
main.tsx router + Layout (Header + Outlet + Footer)
docs/
brand/ Design system (Iris)
decisions/ Architecture Decision Records
public/ logo.svg, favicon.svg