Spec reference: SPEC.md
⚠️ HISTORICAL DESIGN INTENT — partially superseded (last verified 2026-07-15). This document captures the original architecture plan. Several pieces below were never built or have since changed. For the current state, trustCLAUDE.mdandREADME.md, the code, andpackage.json. Notable divergences from what's actually shipped:
- No Three.js / GSAP / 3D scroll experience.
three,@react-three/*, andgsapare not dependencies, andpublic/models/does not exist. The orphanedHardwareProjectViewer.tsx/HashStreamCanvas.tsxplaceholder components have been removed.- No
POST /api/contact(Resend). The contact form posts directly to Formspree from the client./api/hashdashis the only API route.- Hashdash is a proxy to the Prometheus API (
pool.256foundation.org), notdash.256f.org, and additionally resolves Nostr display names via Primal.- Primary deployment is Vercel, not the self-hosted Proxmox LXC described here (Proxmox remains a documented option in the README).
A content-driven nonprofit website built with Next.js 15 (App Router), TypeScript, and Tailwind CSS v4. Deployed to a self-hosted Proxmox LXC container as a standard Node.js process. All content is managed via static TypeScript data files in the repository — no CMS or database required.
The site has one live external data dependency (Hashdash hashrate feed) and one server-side periodic data fetch (Substack RSS). Everything else is static at build time.
Key complexity: Hardware project pages (/projects/ember-one, /projects/libre-board) feature Apple-style scroll-driven 3D model assembly using Three.js + GSAP. Software project pages (/projects/mujina, /projects/hydrapool) feature animated hash stream canvas backgrounds.
┌─────────────────────────────────────────────────────────────────────┐
│ Browser (Client) │
│ │
│ ┌──────────────┐ ┌────────────────┐ ┌────────────────────────┐ │
│ │ Next.js │ │ Hashdash │ │ 3D Scroll / Hash │ │
│ │ Pages (RSC) │ │ Leaderboard │ │ Stream Canvas │ │
│ │ │ │ (polls proxy) │ │ (Three.js / Canvas) │ │
│ └──────┬───────┘ └───────┬────────┘ └───────────┬────────────┘ │
└─────────│──────────────────│───────────────────────│───────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Next.js Server (LXC Container) │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ App Router │ │
│ │ / /mission /projects /projects/[slug] │ │
│ │ /grants /donate /telehash /faq │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ API Routes │ │
│ │ POST /api/contact → email dispatch (Resend) │ │
│ │ GET /api/hashdash → proxy to dash.256f.org API │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Static Data Layer (TypeScript files) │ │
│ │ data/projects.ts data/team.ts data/supporters.ts │ │
│ │ data/grants.ts data/stats.ts data/telehash.ts │ │
│ │ data/faq.ts data/navigation.ts │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ ISR Data Fetches (server-side) │ │
│ │ Substack RSS → revalidate: 3600 (1 hour) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 3D Asset Serving (static) │ │
│ │ public/models/ember-one.glb │ │
│ │ public/models/libre-board.glb │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────┐ ┌─────────────────────────────┐
│ dash.256f │ │ External Services │
│ .org API │ │ ─ Zaprite (donate) │
│ (hashrate │ │ ─ Typeform (grants) │
│ feed) │ │ ─ Substack (newsletter RSS) │
└──────────────┘ │ ─ Resend (email) │
│ ─ Umami (analytics) │
└─────────────────────────────┘
| Route | Strategy | Reason |
|---|---|---|
/ (home) |
SSG + ISR (1hr) | Substack posts fetched server-side; rest is static |
/mission |
SSG | Fully static content from data files |
/projects |
SSG | Static data; grant log from data file |
/projects/[slug] |
SSG (generateStaticParams) |
4 known slugs; 3D viewer is client-only hydration |
/grants |
SSG | Static content |
/donate |
SSG | Static content |
/telehash |
SSG | Static content; countdown timer is client-side only |
/faq |
SSG | Static content from data file |
/api/contact |
Node API Route | POST handler — runtime only |
/api/hashdash |
Node API Route (force-dynamic) |
Proxy GET — runtime only, no caching |
/sitemap.xml |
Generated at build | Next.js app/sitemap.ts |
/robots.txt |
Generated at build | Next.js app/robots.ts |
Hashdash live feed: The HashrateLeaderboard component is a Client Component that polls /api/hashdash every 60 seconds. The API route proxies to dash.256f.org server-side — prevents CORS issues and allows auth header injection via HASHDASH_API_KEY env var.
Substack RSS: Fetched server-side with { next: { revalidate: 3600 } }. Parsed with fast-xml-parser. URL: https://256foundation.substack.com/feed.
3D model pages: Project sub-pages are SSG — the HardwareProjectViewer component (Three.js) hydrates on the client after the static shell is served. GLTF/GLB files are served from /public/models/ as static assets.
@theme {
/* Primary accent — replaces orange entirely */
--color-brand-purple: #7C3AED;
--color-brand-purple-dark: #6D28D9;
--color-brand-purple-light: #5B21B6; /* light mode only — more contrast on white */
/* Terminal / status */
--color-brand-green: #00FF41;
/* Surfaces — dark mode */
--color-brand-dark: #0a0a0a;
--color-brand-surface: #111111;
--color-brand-border: #1f1f1f;
/* Surfaces — light mode */
--color-brand-light-bg: #ffffff;
--color-brand-light-surface: #f5f5f5;
--color-brand-light-border: #e5e5e5;
/* Fonts */
--font-display: var(--font-barlow-condensed), 'Barlow Condensed', sans-serif;
--font-mono: var(--font-space-mono), ui-monospace, monospace;
--font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
}Note: Orange (#F7931A) is removed from all UI usage. It appears only in textual/content contexts referencing Bitcoin (e.g., the ₿ symbol).
| Use | Font | Weight | Class pattern |
|---|---|---|---|
| Hero headlines, page titles | Barlow Condensed | 800 (ExtraBold) | font-display font-extrabold uppercase |
| Section headings | Barlow Condensed | 700 (Bold) | font-display font-bold |
| Nav links | Barlow Condensed | 600 (SemiBold) | font-display font-semibold uppercase |
| Labels, badges, stats labels | Space Mono | 400/700 | font-mono |
| Body prose | Inter | 400 | font-sans |
| Code blocks | Space Mono | 400 | font-mono |
Font loading via next/font/google in app/layout.tsx:
Barlow_Condensed— weights 600, 700, 800Space_Mono— weights 400, 700Inter— variable weight
CSS vars: --font-barlow-condensed, --font-space-mono, --font-inter
- Border radius: 0 — all cards, buttons, panels, inputs use
rounded-none(orrounded-smmaximum) - Card default:
bg-[#111111] border border-[#1f1f1f] - Card hover:
border-[#7C3AED]/50 shadow-[0_0_20px_rgba(124,58,237,0.15)] - Primary button:
bg-white text-[#0a0a0a] font-mono font-bold rounded-none— white fill, dark text - Secondary button:
text-[#7C3AED] font-mono hover:underline— no border, underline on hover - Outlined button:
border border-[#7C3AED]/40 text-[#7C3AED] rounded-none hover:border-[#7C3AED]
The existing PCBBackground.tsx component is kept as gray/neutral traces — not recolored purple. The PCB pattern provides subtle technical depth without competing with purple accents. Opacity: 0.08–0.15 depending on section.
website-256F/
├── app/
│ ├── layout.tsx # Root layout: Header, Footer, Umami, fonts
│ ├── globals.css # @theme tokens, base styles, animations
│ ├── page.tsx # Home (10 sections, ISR for Substack)
│ ├── mission/
│ │ └── page.tsx
│ ├── projects/
│ │ ├── page.tsx # Projects hub
│ │ └── [slug]/
│ │ └── page.tsx # Pillar project sub-pages (generateStaticParams)
│ ├── grants/
│ │ └── page.tsx
│ ├── donate/
│ │ └── page.tsx
│ ├── telehash/
│ │ └── page.tsx
│ ├── faq/
│ │ └── page.tsx
│ ├── api/
│ │ ├── contact/
│ │ │ └── route.ts # POST: contact form → Resend email
│ │ └── hashdash/
│ │ └── route.ts # GET: proxy to Hashdash API (force-dynamic)
│ ├── sitemap.ts
│ └── robots.ts
│
├── components/
│ ├── layout/
│ │ ├── Header.tsx # Fixed nav, purple accent line, pill hover
│ │ ├── Footer.tsx # 4-col footer, social icons, clean/functional
│ │ ├── NavDropdown.tsx # Dropdown with dark bg + purple hover
│ │ └── MobileNav.tsx # Hamburger drawer, accordion dropdowns
│ │
│ ├── ui/ # Primitive / reusable UI components
│ │ ├── Button.tsx # primary | secondary | outlined variants
│ │ ├── Card.tsx # Sharp corners, purple glow on hover
│ │ ├── Badge.tsx # Status: Active | In Progress | Completed
│ │ ├── SectionWrapper.tsx # Consistent section padding + max-width
│ │ ├── SectionHeader.tsx # Barlow Condensed heading + Space Mono label
│ │ ├── ExternalLink.tsx # <a target="_blank" rel="noopener noreferrer">
│ │ ├── PCBBackground.tsx # SVG PCB trace pattern (gray/neutral)
│ │ ├── Logo.tsx # next/image logo, inverted prop for dark bg
│ │ └── ASCIIArt.tsx # ASCII art decorative blocks
│ │
│ ├── home/ # Home page section components
│ │ ├── HeroSection.tsx # PCB bg + purple radial, Logo + tagline, metrics
│ │ ├── DonateCards.tsx # Two equal panels: BTC + Hashrate
│ │ ├── WhySection.tsx # 3/9 col grid, "WHY" watermark, pull quote
│ │ ├── AllocationStats.tsx # Giant numbers, divide-x layout
│ │ ├── BlocksFound.tsx # Compact: green block count + YouTube embed
│ │ ├── StayUpdated.tsx # 3 Substack post cards + newsletter + POD256
│ │ ├── ApplySection.tsx # Purple-accent CTA, category pills
│ │ ├── CommunitySection.tsx # 3/9 col split, icon+text rows
│ │ ├── SupporterShowcase.tsx # "use client" — tier grid + leaderboard
│ │ ├── HashrateLeaderboard.tsx # "use client" — F1-style ranked table, polls 60s
│ │ └── ContactForm.tsx # "use client" — controlled form, POST /api/contact
│ │
│ ├── projects/
│ │ ├── PillarProjectCard.tsx # Detailed card with progress bar, milestones
│ │ ├── EcosystemCard.tsx # External link card (OSMU, Heatpunks)
│ │ ├── GrantLogTable.tsx # Table of all funded grants
│ │ ├── MilestoneTracker.tsx # Horizontal progress steps (not vertical)
│ │ ├── ProjectTeamSection.tsx # Lead engineer + PM cards
│ │ ├── HardwareProjectViewer.tsx # "use client" — Three.js scroll-driven 3D assembly
│ │ └── HashStreamCanvas.tsx # "use client" — SHA-256 hash stream canvas bg
│ │
│ ├── telehash/
│ │ ├── CountdownTimer.tsx # "use client" — DD:HH:MM:SS countdown
│ │ └── TeleHashEventCard.tsx # Past event: results + YouTube embed
│ │
│ └── shared/
│ ├── TeamMemberCard.tsx # "use client" — photo with onError fallback
│ ├── Timeline.tsx # Vertical timeline (purple dots + line)
│ └── SubstackEmbed.tsx # Substack subscription widget wrapper
│
├── data/ # Static content — edit to update the site
│ ├── projects.ts # Pillar project details, milestones, team
│ ├── supporters.ts # Financial supporter tiers
│ ├── grants.ts # Full funded grant log
│ ├── stats.ts # Allocation totals, blocks found
│ ├── telehash.ts # TeleHash event history + next event date
│ ├── team.ts # Founders + board bios, photos, socials
│ ├── faq.ts # FAQ Q&A by category
│ └── navigation.ts # topNav + footer links
│
├── types/
│ └── index.ts # Shared TypeScript types
│
├── lib/
│ ├── substack.ts # fetchSubstackPosts() — RSS parse utility
│ └── metadata.ts # generatePageMetadata() — shared OG/meta helper
│
├── public/
│ ├── logo.jpg # Foundation logo (white bg JPG, 1024×1024)
│ ├── models/ # GLTF/GLB 3D models (hardware projects)
│ │ ├── ember-one.glb # Ember One PCB — exported from STEP/KiCad
│ │ └── libre-board.glb # Libre Board PCB — exported from STEP/KiCad
│ ├── team/ # Team member headshots
│ │ └── placeholder.png
│ └── og/
│ ├── og-default.png # Default OG image 1200×630
│ ├── og-ember-one.png
│ ├── og-mujina.png
│ ├── og-libre-board.png
│ └── og-hydrapool.png
│
├── SPEC.md
├── ARCHITECTURE.md
├── next.config.ts
├── tsconfig.json
├── .env.local # gitignored
└── .env.example
- Fixed top, 2px purple accent line at very top (
z-50) - Scrolled state:
bg-[#0a0a0a]/95 backdrop-blur-md border-b border-[#1f1f1f] - Nav links: Barlow Condensed, uppercase. Hover:
bg-[#7C3AED]/10 roundedpill effect - Active page: white text
- Logo left, nav center/right, Donate CTA (white button) far right
"use client"— scroll listener + mobile state
- Functional, clean 4-column grid (not a design showcase)
- Logo (inverted for dark bg), tagline, 501(c)(3), social icons
- Social icons:
text-gray-500 hover:text-[#7C3AED] border border-[#1f1f1f] hover:border-[#7C3AED]/40 - Copyright + "Open source. Always." strip at bottom
The most complex component in the codebase. Implements Apple-style scroll-driven 3D model assembly.
Architecture:
┌─────────────────────────────────────────────────────┐
│ Scroll Container (tall — e.g. 400vh) │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Sticky Panel (100vh, position: sticky) │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌─────────────────┐ │ │
│ │ │ Three.js Canvas │ │ Text Panel │ │ │
│ │ │ (R3F <Canvas>) │ │ (scrolls over) │ │ │
│ │ │ │ │ │ │ │
│ │ │ GLTF Model │ │ Section text │ │ │
│ │ │ scroll-driven │ │ fades in/out │ │ │
│ │ │ assembly │ │ at breakpoints │ │ │
│ │ └──────────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Scroll binding: GSAP ScrollTrigger maps scroll position (0→1) to model assembly progress. Component positions are interpolated between exploded and assembled states.
Props: modelPath: string (e.g. /models/ember-one.glb), sections: { label, description, scrollStart, scrollEnd }[]
Fallback: If matchMedia('(prefers-reduced-motion)') is true, OR on mobile viewport, shows static rendered model (no scroll binding, auto-rotate only).
Dependencies: three, @react-three/fiber, @react-three/drei, gsap
Animated SHA-256 hash stream background canvas for software project pages.
Behavior:
<canvas>absolutely positioned behind page content,z-index: 0- Columns of streaming hex characters (hash values, nonces, difficulty targets)
- Terminal green (
#00FF41) text atopacity: 0.08–0.12 - On scroll, stream speed subtly increases
- At designated scroll milestones, a brief "VALID BLOCK FOUND" flash highlights one column
- Uses
requestAnimationFrameloop; cleaned up on unmount
Performance: Capped at 30fps; off-screen columns skip rendering via viewport check.
F1-style ranked competition leaderboard.
Visual spec:
#1— gold rank badgebg-yellow-500, bold, elevated#2— silver rank badgebg-gray-400#3— bronze rank badgebg-amber-700#4+— standard rank number- Worker name in Space Mono, hashrate in Barlow Condensed (large)
- Shimmer skeleton loading state
- Graceful error: "Dashboard unavailable" + link to
dash.256f.org
Polling: setInterval(60_000) cleared on unmount. Calls GET /api/hashdash.
Horizontal progress steps (not vertical timeline).
● ─────── ● ─────── ○ ─────── ○
[Done] [Done] [Active] [Upcoming]
- Completed steps: filled purple circle + purple connecting line
- Active step: purple outline circle, pulsing
- Upcoming: gray outline + gray line
- Labels below in Space Mono
text-xs - Server component (no interactivity)
export type ProjectSlug = 'ember-one' | 'mujina' | 'libre-board' | 'hydrapool'
export type ProjectType = 'hardware' | 'software'
export interface PillarProject {
slug: ProjectSlug
type: ProjectType // determines which project page template to use
name: string
tagline: string
description: string
whyCoreGrant: string
whyNecessary: string
technicalDetails: string
status: 'active' | 'completed' | 'paused'
externalUrl: string
githubUrl: string
forumCategory: string
ogImage: string // path under /public/og/
modelPath?: string // GLTF path under /public/models/ — hardware only
milestones: Milestone[]
team: { leadEngineer: TeamMember; projectManager?: TeamMember }
}
export interface Milestone {
label: string
status: 'completed' | 'active' | 'upcoming'
date?: string
}
export interface TeamMember {
name: string
handle?: string
bio: string
headshot: string
links: { x?: string; nostr?: string; github?: string; website?: string }
}
export interface Supporter {
name: string
image: string // logo URL (org) or photo URL (individual)
link: string // website or social profile
tier: 1 | 2 | 3
}
export interface Grant {
name: string
grantee: string
category: 'hardware' | 'software' | 'research' | 'education'
amountBTC: number
status: 'active' | 'completed'
dateFunded: string
}
export interface TeleHashEvent {
number: number
date: string
blockFound: boolean
btcRaised?: number
videoUrl?: string
}
export interface FAQItem {
question: string
answer: string
category: 'foundation' | 'donations' | 'grants' | 'projects' | 'technical'
}
export interface NavItem {
label: string
href: string
external?: boolean
children?: NavItem[]
}Body: { name: string, email: string, message: string }
Validation: All fields required; email format regex check
Success:
1. Send email to process.env.CONTACT_EMAIL (foundation inbox)
2. Send auto-reply to submitter's email address
3. Return 200 { ok: true }
Failure: Return 500 { error: string }
Provider: Resend (RESEND_API_KEY env var)
Dev: Returns 500 gracefully when RESEND_API_KEY is missing
Purpose: Server-side proxy to dash.256f.org — avoids CORS, abstracts auth
Config: export const dynamic = 'force-dynamic'
Behavior:
- Fetch from HASHDASH_API_URL (default: https://dash.256f.org/api/workers)
- If HASHDASH_API_KEY set, add Authorization: Bearer header
- Forward response as-is
- On upstream failure: return 503 { error: 'Upstream unavailable' }
Cache: None — client manages polling on its own schedule
Hardware project pages require GLTF/GLB files. These must be produced from the existing KiCad/STEP source files.
KiCad Native (.kicad_pcb)
│
│ KiCad 3D Viewer → Export STEP
▼
STEP File (.step)
│
│ FreeCAD → File > Export > GLTF/GLB
▼
Raw GLTF/GLB
│
│ npx @gltf-transform/cli optimize model.glb optimized.glb
│ [optional: --compress draco if file > 5MB]
▼
public/models/[slug].glb (target: < 5MB)
For the scroll-driven explosion/assembly effect, the GLTF must have named mesh groups per logical component (e.g., PCB_Board, ASIC_Chip, VRM_Section, Connectors). These names are referenced in HardwareProjectViewer.tsx to animate component positions independently.
If the exported GLTF has a flat mesh structure, it can be split in Blender:
- Import GLB into Blender
- Select faces by logical grouping, separate into named objects (
P→ Separate → Selection) - Export as GLB
While GLTF files are pending, the hardware project pages render:
- A static
<img>hero with a photograph or render of the board - All other sections (milestones, team, links) render normally
HardwareProjectViewer.tsxshows a placeholder:"3D model coming soon"
Shared helper in lib/metadata.ts:
export function generatePageMetadata({
title,
description,
path,
ogImage = '/og/og-default.png',
}: MetadataProps): Metadata {
const base = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://256foundation.org'
const url = `${base}${path}`
return {
title: `${title} | 256 Foundation`,
description,
openGraph: {
title, description, url,
images: [{ url: ogImage, width: 1200, height: 630 }],
siteName: '256 Foundation',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title, description,
images: [ogImage],
site: '@256FOUNDATION',
},
alternates: { canonical: url },
}
}Each page.tsx exports metadata via generatePageMetadata(). Project sub-pages use their custom /og/og-[slug].png.
| Integration | How It Works | Where |
|---|---|---|
| Zaprite | href={process.env.NEXT_PUBLIC_ZAPRITE_URL} button |
/donate |
| Typeform | href={process.env.NEXT_PUBLIC_TYPEFORM_URL} button, new tab |
/grants |
| Substack RSS | fetch(rssUrl, { next: { revalidate: 3600 } }) + fast-xml-parser |
Home page (server) |
| Substack embed | Script-based widget in SubstackEmbed.tsx (client, lazy) |
Home + TeleHash |
| Hashdash API | Client polls /api/hashdash every 60s |
Home HashrateLeaderboard |
| YouTube embed | <iframe loading="lazy" aspect-video> |
TeleHash page, BlocksFound |
| Umami | <Script strategy="afterInteractive">, production only |
Root layout |
| Three.js (R3F) | Client-only, dynamic import with ssr: false |
Hardware project pages |
Important: Three.js / @react-three/fiber must be loaded with dynamic(() => import(...), { ssr: false }) to prevent SSR errors, since it accesses window/WebGL context.
Decision: All content in TypeScript data files, no CMS. Rationale: Team is developer-comfortable, content changes infrequently, zero operational overhead, type safety on all content, no external service dependency. Trade-off: Non-developers cannot update content without a code change + deploy.
Decision: GET /api/hashdash proxies to dash.256f.org rather than direct browser fetch.
Rationale: Prevents CORS issues; allows auth header injection via env var; single place to update when API docs arrive; can add server-side caching later.
Decision: app/projects/[slug]/page.tsx with generateStaticParams() for the 4 known slugs.
Rationale: DRY template. Statically generated. Adding a 5th project requires only a data entry + deploy.
Decision: Dark/light mode via @custom-variant dark (@media (prefers-color-scheme: dark)) — no manual toggle.
Rationale: Spec explicitly requires no toggle. Avoids hydration mismatch. If toggle is desired in future, add ThemeProvider + class strategy.
Decision: Static PNG files in /public/og/ rather than next/og dynamic generation.
Rationale: Simpler, no runtime cost, no edge function needed. Default image covers all non-project pages; custom images for the 4 project sub-pages.
Decision: R3F over vanilla Three.js or <model-viewer> web component.
Rationale: R3F provides React-idiomatic component model for scene management; @react-three/drei provides useGLTF, OrbitControls, lighting helpers out of the box; GSAP ScrollTrigger integrates cleanly with R3F via useFrame/useEffect.
Trade-off: Adds ~300KB gzipped to the hardware project page bundle. Mitigated by dynamic import (ssr: false) so it only loads on those 2 pages.
Decision: <canvas> with requestAnimationFrame for the hash stream background.
Rationale: Thousands of animating text characters would be too expensive as DOM nodes. Canvas handles this trivially at 30fps. More authentic terminal aesthetic than SVG or CSS approaches.
Trade-off: Canvas is harder to make accessible. Mitigation: aria-hidden="true" on canvas, role="presentation".
Decision: Substack RSS fetched server-side with 1-hour ISR. Rationale: No loading spinner for visitors. Avoids client-side CORS with RSS. Content doesn't need real-time freshness.
Decision: Brand color is #7C3AED (purple), matching the live 256foundation.org. Orange (#F7931A) removed from UI.
Rationale: Design overhaul to match existing brand identity and eliminate generic "AI-generated" aesthetic. Orange remains only in Bitcoin-specific content contexts (₿ symbol, price references).
Decision: Barlow Condensed Bold/ExtraBold for all headlines and display text.
Rationale: Creates the bold, industrial, motorsport aesthetic aligned with the logo design language. Pairs well with Space Mono for technical labels.
- Update
app/globals.csswith new@theme {}tokens (purple, Barlow Condensed, sharp edges) - Update
app/layout.tsxto load Barlow Condensed + Space Mono + Inter vianext/font - Update
tailwind.config.tsto remove orange, add purple tokens - Update
components/ui/Button.tsx— new primary (white), secondary (purple text), outlined variants - Update
components/ui/Card.tsx— sharp edges, purple hover glow - Update
components/ui/SectionHeader.tsx— Barlow Condensed, Space Mono label
Header.tsx— purple accent line, pill hover nav, Barlow Condensed nav links, white Donate buttonFooter.tsx— retheme to purple social icons, clean layoutMobileNav.tsx— Barlow Condensed accordion links
- Retheme all 10 section components with new design tokens
HashrateLeaderboard.tsx— F1-style ranking with gold/silver/bronze badgesAllocationStats.tsx— giant Barlow Condensed numbers, purple accentHeroSection.tsx— PCB + subtle purple radial bg, Logo + big taglineDonateCards.tsx— two equal sharp-edge panelsWhySection.tsx— 3/9 col grid, pull quote with purple left barMilestoneTracker.tsx— refactor to horizontal stepsBlocksFound.tsx— compact: green number + YouTube embed side-by-sideSupporterShowcase.tsx— logo grid with tier structure
/mission— bold Barlow Condensed text hero, purple vertical timeline, team cards/grants— explain + grant log + CTA structure/donate— BTC first, hashrate second layout/telehash— three equal sections, purple countdown numbers/faq— grouped accordion with<details>/<summary>, purple open state
/projectshub — larger detailed pillar cards, ecosystem cards, grant log tablePillarProjectCard.tsx— progress bar, full description, milestone summary, purple glow hover
Hardware (Ember One, Libre Board):
- Install dependencies:
npm install three @react-three/fiber @react-three/drei gsap - Build
HardwareProjectViewer.tsx— R3F canvas, GSAP ScrollTrigger scroll binding - Implement explode/assemble logic with named mesh group interpolation
- Wire into
/projects/[slug]/page.tsxviadynamic(() => import(...), { ssr: false }) - Add mobile fallback (static render, no scroll binding)
- Integrate GLTF files once client delivers them (placeholder until then)
Software (Mujina, Hydrapool):
- Build
HashStreamCanvas.tsx— canvas-based SHA-256 hash rain - Implement scroll-speed binding and milestone flash events
- Wire into software project page sections
POST /api/contact— Resend integrationGET /api/hashdash— proxy withforce-dynamic
lib/metadata.tshelpergenerateMetadata()on every pageapp/sitemap.ts,app/robots.ts- OG image assets (placeholders → final branded assets)
- Responsive testing (mobile / tablet / desktop)
- Dark + light mode verification across all pages
- Accessibility pass: ARIA labels on 3D canvas (
aria-hidden), focus states, color contrast check for purple on dark - Performance audit: dynamic imports for Three.js; canvas off-screen optimization; Lighthouse target 90+
- Content replacement: stubs → real data as client delivers
# Contact form
CONTACT_EMAIL=
EMAIL_FROM=
RESEND_API_KEY=
# Hashdash proxy
HASHDASH_API_URL=
HASHDASH_API_KEY=
# Analytics
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
NEXT_PUBLIC_UMAMI_URL=
# External link URLs
NEXT_PUBLIC_ZAPRITE_URL=
NEXT_PUBLIC_TYPEFORM_URL=
NEXT_PUBLIC_SITE_URL=https://256foundation.org| Risk | Mitigation |
|---|---|
| GLTF files not yet available | Hardware pages show static image placeholder; 3D viewer is non-blocking feature |
| GLTF file > 5MB after export | Apply Draco compression via @gltf-transform/cli --compress draco; target < 5MB |
| GLTF has flat mesh (no named groups) | Blender mesh-split workflow documented in Asset Pipeline section above |
| Three.js SSR errors | dynamic(() => import(...), { ssr: false }) on all R3F components |
| Three.js bundle size (~300KB) | Dynamic import ensures it only loads on the 2 hardware project pages |
| Canvas performance on low-end devices | Hash stream capped at 30fps; off-screen columns skip; prefers-reduced-motion disables it |
| Hashdash API shape unknown | Proxy route isolates frontend from API changes; HashrateLeaderboard has graceful error state |
| Substack RSS structure changes | lib/substack.ts isolates parsing; single function to update |
| Content stubs at launch | All data files have realistic placeholder stubs; site builds and deploys |
| Purple contrast on dark bg | #7C3AED on #0a0a0a passes WCAG AA for large text; check small text with contrast tool |
- Spec: SPEC.md
- Next.js App Router: https://nextjs.org/docs/app
- React Three Fiber: https://docs.pmnd.rs/react-three-fiber
- GSAP ScrollTrigger: https://gsap.com/docs/v3/Plugins/ScrollTrigger/
- gltf-transform CLI: https://gltf-transform.dev/cli
- Tailwind CSS v4: https://tailwindcss.com/docs
- Design references: bitaxe.org, heatpunks.org, homeassistant.io, apple.com/mac-pro