From 2d7af224dd0a9797b351bca395e54c565a3d83f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 9 Oct 2026 23:37:23 -0400 Subject: [PATCH] Website: 16-bit pixel-art rebuild with a live Three.js office, downloads from Gitea v1.5.1 - Title screen over a real-time voxel office rendered at low resolution and quantized to a rotating 32-colour palette with Bayer dithering; day part and season re-light the scene and every surface. - 16-bit UI kit: name-plate dialogs, menu cursor, inventory, XP bar, badges, NPC speech, GAME OVER with CONTINUE, hand-placed 12x12 icons, Fusion Pixel font (Latin subset preloaded). - Sections as a game: quest log (onboarding), character select (AI CEO / AI Co-Founder), inventory (features), party roster (providers), live download XP, save point (all v1.5.1 builds), FAQ as NPC dialog, credits. - Downloads and source point to the Gitea release; the download function only redirects to git.zacharyzhang.com. - Tailwind and the old components removed; unit tests for platform/downloads, rewritten e2e suite. Co-Authored-By: Claude Opus 5.5 --- landing/README.md | 50 +- landing/app/globals.css | 376 ++++---- landing/app/layout.tsx | 248 +----- landing/app/not-found.tsx | 19 + landing/app/page.module.css | 34 + landing/app/page.tsx | 53 +- landing/components/brand/icon-mark.tsx | 15 - landing/components/layout/footer.tsx | 96 --- landing/components/layout/navbar.tsx | 116 --- landing/components/pixel/badge.tsx | 9 + landing/components/pixel/dialog.tsx | 26 + landing/components/pixel/game-over.tsx | 31 + landing/components/pixel/hud.tsx | 38 + landing/components/pixel/icon.tsx | 33 + landing/components/pixel/index.ts | 12 + landing/components/pixel/inventory.tsx | 69 ++ landing/components/pixel/menu-list.tsx | 66 ++ landing/components/pixel/npc-say.tsx | 75 ++ landing/components/pixel/palette-boot.ts | 20 + landing/components/pixel/palette-clock.tsx | 23 + landing/components/pixel/palette-context.tsx | 50 ++ landing/components/pixel/pixel-button.tsx | 23 + landing/components/pixel/pixel.module.css | 427 +++++++++ landing/components/pixel/xp-bar.tsx | 51 ++ landing/components/sections/bento.tsx | 101 --- .../sections/character-select.module.css | 127 +++ .../components/sections/character-select.tsx | 71 ++ .../components/sections/credits.module.css | 104 +++ landing/components/sections/credits.tsx | 46 + .../components/sections/download-section.tsx | 81 -- .../sections/faq-section.module.css | 12 + landing/components/sections/faq-section.tsx | 40 + landing/components/sections/faq.tsx | 96 --- landing/components/sections/hero.tsx | 66 -- landing/components/sections/how-it-works.tsx | 42 - .../sections/inventory-section.module.css | 12 + .../components/sections/inventory-section.tsx | 34 + landing/components/sections/logotype.tsx | 49 ++ .../sections/party-roster.module.css | 64 ++ landing/components/sections/party-roster.tsx | 51 ++ landing/components/sections/platform-cta.tsx | 40 + .../components/sections/quest-log.module.css | 144 ++++ landing/components/sections/quest-log.tsx | 77 ++ landing/components/sections/reveal.tsx | 37 + landing/components/sections/runtimes.tsx | 33 - .../components/sections/save-point.module.css | 149 ++++ landing/components/sections/save-point.tsx | 121 +++ .../components/sections/section-header.tsx | 17 + .../components/sections/section.module.css | 147 ++++ landing/components/sections/showcase.tsx | 25 - landing/components/sections/site-menu.tsx | 54 ++ .../sections/title-screen.module.css | 276 ++++++ landing/components/sections/title-screen.tsx | 43 + .../components/sections/top-bar.module.css | 86 ++ landing/components/sections/top-bar.tsx | 26 + .../components/sections/xp-section.module.css | 60 ++ landing/components/sections/xp-section.tsx | 81 ++ landing/components/theme/theme-provider.tsx | 76 -- landing/components/theme/theme-toggle.tsx | 20 - landing/components/ui/art-stage.tsx | 68 -- landing/components/ui/aurora.tsx | 20 - landing/components/ui/badge.tsx | 24 - landing/components/ui/button.tsx | 48 -- landing/components/ui/container.tsx | 6 - landing/components/ui/eyebrow.tsx | 10 - landing/components/ui/reveal.tsx | 59 -- landing/components/ui/section-heading.tsx | 32 - landing/components/world/PixelWorld.tsx | 133 +++ landing/components/world/agents.ts | 168 ++++ landing/components/world/engine.ts | 157 ++++ landing/components/world/index.ts | 1 + landing/components/world/layout.ts | 51 ++ landing/components/world/lighting.ts | 52 ++ landing/components/world/office.ts | 284 ++++++ landing/components/world/quantize.ts | 64 ++ landing/components/world/sky.ts | 102 +++ landing/components/world/voxel.ts | 26 + landing/components/world/world.module.css | 48 ++ landing/functions/api/download-stats.ts | 8 +- landing/functions/api/download.ts | 17 +- landing/lib/content.ts | 155 ++++ landing/lib/downloads.ts | 7 +- landing/lib/landing-content.ts | 56 -- landing/lib/palette.ts | 86 ++ landing/lib/pixel-icons.ts | 61 ++ landing/lib/platform-support.ts | 112 --- landing/lib/platform.ts | 56 ++ landing/lib/product-narrative.ts | 80 -- landing/lib/release-links.generated.ts | 59 -- landing/lib/release-links.ts | 78 ++ landing/package-lock.json | 755 ++-------------- landing/package.json | 9 +- landing/playwright.config.ts | 20 +- landing/postcss.config.mjs | 5 - landing/preview-dark.html | 810 ----------------- landing/preview.html | 814 ------------------ landing/public/_headers | 12 +- .../public/fonts/FusionPixel-12px-latin.woff2 | Bin 0 -> 13484 bytes .../fonts/FusionPixel-12px-zh_hans.woff2 | Bin 0 -> 666264 bytes .../fonts/fusion-pixel-licenses/OFL.txt | 96 +++ .../fusion-pixel-licenses/ark-pixel/OFL.txt | 96 +++ .../fusion-pixel-licenses/cubic-11/OFL.txt | 108 +++ .../fusion-pixel-licenses/galmuri/LICENSE.txt | 93 ++ landing/tests/e2e/landing.spec.ts | 393 +++++++-- landing/tests/e2e/serve.mjs | 4 +- landing/tests/unit/downloads.test.ts | 47 + landing/tests/unit/platform.test.ts | 44 + landing/tests/unit/resolve-ts.mjs | 13 + landing/undefined/dawn.png | Bin 0 -> 31563 bytes landing/undefined/day.png | Bin 0 -> 30618 bytes landing/undefined/dusk.png | Bin 0 -> 30656 bytes landing/undefined/night.png | Bin 0 -> 29740 bytes 112 files changed, 5692 insertions(+), 4223 deletions(-) create mode 100644 landing/app/not-found.tsx create mode 100644 landing/app/page.module.css delete mode 100644 landing/components/brand/icon-mark.tsx delete mode 100644 landing/components/layout/footer.tsx delete mode 100644 landing/components/layout/navbar.tsx create mode 100644 landing/components/pixel/badge.tsx create mode 100644 landing/components/pixel/dialog.tsx create mode 100644 landing/components/pixel/game-over.tsx create mode 100644 landing/components/pixel/hud.tsx create mode 100644 landing/components/pixel/icon.tsx create mode 100644 landing/components/pixel/index.ts create mode 100644 landing/components/pixel/inventory.tsx create mode 100644 landing/components/pixel/menu-list.tsx create mode 100644 landing/components/pixel/npc-say.tsx create mode 100644 landing/components/pixel/palette-boot.ts create mode 100644 landing/components/pixel/palette-clock.tsx create mode 100644 landing/components/pixel/palette-context.tsx create mode 100644 landing/components/pixel/pixel-button.tsx create mode 100644 landing/components/pixel/pixel.module.css create mode 100644 landing/components/pixel/xp-bar.tsx delete mode 100644 landing/components/sections/bento.tsx create mode 100644 landing/components/sections/character-select.module.css create mode 100644 landing/components/sections/character-select.tsx create mode 100644 landing/components/sections/credits.module.css create mode 100644 landing/components/sections/credits.tsx delete mode 100644 landing/components/sections/download-section.tsx create mode 100644 landing/components/sections/faq-section.module.css create mode 100644 landing/components/sections/faq-section.tsx delete mode 100644 landing/components/sections/faq.tsx delete mode 100644 landing/components/sections/hero.tsx delete mode 100644 landing/components/sections/how-it-works.tsx create mode 100644 landing/components/sections/inventory-section.module.css create mode 100644 landing/components/sections/inventory-section.tsx create mode 100644 landing/components/sections/logotype.tsx create mode 100644 landing/components/sections/party-roster.module.css create mode 100644 landing/components/sections/party-roster.tsx create mode 100644 landing/components/sections/platform-cta.tsx create mode 100644 landing/components/sections/quest-log.module.css create mode 100644 landing/components/sections/quest-log.tsx create mode 100644 landing/components/sections/reveal.tsx delete mode 100644 landing/components/sections/runtimes.tsx create mode 100644 landing/components/sections/save-point.module.css create mode 100644 landing/components/sections/save-point.tsx create mode 100644 landing/components/sections/section-header.tsx create mode 100644 landing/components/sections/section.module.css delete mode 100644 landing/components/sections/showcase.tsx create mode 100644 landing/components/sections/site-menu.tsx create mode 100644 landing/components/sections/title-screen.module.css create mode 100644 landing/components/sections/title-screen.tsx create mode 100644 landing/components/sections/top-bar.module.css create mode 100644 landing/components/sections/top-bar.tsx create mode 100644 landing/components/sections/xp-section.module.css create mode 100644 landing/components/sections/xp-section.tsx delete mode 100644 landing/components/theme/theme-provider.tsx delete mode 100644 landing/components/theme/theme-toggle.tsx delete mode 100644 landing/components/ui/art-stage.tsx delete mode 100644 landing/components/ui/aurora.tsx delete mode 100644 landing/components/ui/badge.tsx delete mode 100644 landing/components/ui/button.tsx delete mode 100644 landing/components/ui/container.tsx delete mode 100644 landing/components/ui/eyebrow.tsx delete mode 100644 landing/components/ui/reveal.tsx delete mode 100644 landing/components/ui/section-heading.tsx create mode 100644 landing/components/world/PixelWorld.tsx create mode 100644 landing/components/world/agents.ts create mode 100644 landing/components/world/engine.ts create mode 100644 landing/components/world/index.ts create mode 100644 landing/components/world/layout.ts create mode 100644 landing/components/world/lighting.ts create mode 100644 landing/components/world/office.ts create mode 100644 landing/components/world/quantize.ts create mode 100644 landing/components/world/sky.ts create mode 100644 landing/components/world/voxel.ts create mode 100644 landing/components/world/world.module.css create mode 100644 landing/lib/content.ts delete mode 100644 landing/lib/landing-content.ts create mode 100644 landing/lib/palette.ts create mode 100644 landing/lib/pixel-icons.ts delete mode 100644 landing/lib/platform-support.ts create mode 100644 landing/lib/platform.ts delete mode 100644 landing/lib/product-narrative.ts delete mode 100644 landing/lib/release-links.generated.ts create mode 100644 landing/lib/release-links.ts delete mode 100644 landing/postcss.config.mjs delete mode 100644 landing/preview-dark.html delete mode 100644 landing/preview.html create mode 100644 landing/public/fonts/FusionPixel-12px-latin.woff2 create mode 100644 landing/public/fonts/FusionPixel-12px-zh_hans.woff2 create mode 100644 landing/public/fonts/fusion-pixel-licenses/OFL.txt create mode 100644 landing/public/fonts/fusion-pixel-licenses/ark-pixel/OFL.txt create mode 100644 landing/public/fonts/fusion-pixel-licenses/cubic-11/OFL.txt create mode 100644 landing/public/fonts/fusion-pixel-licenses/galmuri/LICENSE.txt create mode 100644 landing/tests/unit/downloads.test.ts create mode 100644 landing/tests/unit/platform.test.ts create mode 100644 landing/tests/unit/resolve-ts.mjs create mode 100644 landing/undefined/dawn.png create mode 100644 landing/undefined/day.png create mode 100644 landing/undefined/dusk.png create mode 100644 landing/undefined/night.png diff --git a/landing/README.md b/landing/README.md index 7cc95a46..71db0f2c 100644 --- a/landing/README.md +++ b/landing/README.md @@ -1,14 +1,48 @@ # AgentCompany Landing -Next.js landing page for the AgentCompany desktop release lane. +The website for [AgentCompany](https://agent-company.dev): a 16-bit pixel-art landing page built with Next.js (static export), React and three.js, hosted on Cloudflare Pages. -## Copy Sources +## Structure -| Surface | Source | +| Path | What lives there | |---|---| -| Download cards | `landing/lib/release-links.generated.ts` | -| Platform FAQ | `landing/lib/platform-support.ts` | -| Hero, How It Works, software JSON-LD features | `landing/lib/product-narrative.ts` | -| Runtime download URLs | generated from `scripts/generate-landing-release-links.mjs` | +| `app/` | Page shell (`layout.tsx`, `page.tsx`, `not-found.tsx`) and `globals.css` | +| `components/sections/` | Page sections, with copy in `lib/content.ts` | +| `components/pixel/` | The pixel UI kit: dialogs, NPC speech, menus, buttons, XP bars, badges, inventory, icons, GAME OVER, HUD, and the `PaletteClock` provider | +| `components/world/` | `PixelWorld`, the three.js office diorama rendered at low resolution and quantized to the palette with ordered dithering | +| `lib/palette.ts` | The 32-colour palette per day part and season; every colour on the site comes from its CSS variables | +| `lib/pixel-icons.ts` | Hand-placed 12×12 icons | +| `lib/release-links.ts` | Release version, Gitea source and release URLs, and every download asset | +| `lib/downloads.ts` | Flat asset list and tracked `/api/download` links | +| `lib/platform.ts` | Visitor OS/architecture detection and the recommended download | +| `functions/api/` | Cloudflare Pages Functions: `download` (counts a unique download in D1, then redirects to Gitea) and `download-stats` | +| `migrations/` | D1 schema for the download counter | +| `public/_headers` | Security and cache headers | -The public story mirrors the first-run desktop flow: Welcome, Requirements, Providers, Company, Team Models, Mode, Mission, Launch. +Append `?time=dawn|day|dusk|night&season=spring|summer|autumn|winter` to preview a palette. + +## Downloads and source + +Installers and source code live on Gitea, not GitHub: . Contributions go through AGit pull requests there. + +## Updating the release + +Edit `lib/release-links.ts`: bump `releaseVersion` and update each asset's file name and byte size from the release API: + +```sh +curl -s https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/AgentCompany/releases/tags/v +``` + +Asset ids stay stable across releases so existing `/api/download?asset=` links keep working. The download function only redirects to `git.zacharyzhang.com`. + +## Develop, test, build, deploy + +```sh +npm install +npm run dev +node --import ./tests/unit/resolve-ts.mjs --test 'tests/unit/*.test.ts' +npm run build +npx wrangler pages deploy out --project-name agentcompany +``` + +`npm run build` writes the static site to `out/`. Pages Functions and the `DOWNLOAD_COUNTER_DB` D1 binding are configured in `wrangler.toml`; `npm run preview` serves `out/` with the functions locally. diff --git a/landing/app/globals.css b/landing/app/globals.css index e567027a..2cdd199d 100644 --- a/landing/app/globals.css +++ b/landing/app/globals.css @@ -1,246 +1,184 @@ -@import "tailwindcss"; - -/* - * AgentCompany landing — design system. - * Light theme is a faithful rendering of the adaline pebble/meadow palette - * (warm cream + dark green). Dark theme uses the matching Material-3 dark - * surfaces + mint primary. Components consume the CSS variables below, so the - * whole palette is defined once here. - */ - -@theme { - --font-sans: var(--font-instrument-sans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; - --font-serif: var(--font-newsreader), "Source Serif Pro", Georgia, "Times New Roman", serif; - --font-mono: var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; - --font-display: var(--font-instrument-serif), "Cormorant Garamond", Georgia, serif; +/* Fusion Pixel 12px (OFL, see /fonts/fusion-pixel-licenses): a small Latin subset, and the full CJK file only when CJK text appears. */ +@font-face { + font-family: "Fusion Pixel"; + font-display: swap; + src: url("/fonts/FusionPixel-12px-zh_hans.woff2") format("woff2"); + unicode-range: U+3000-303F, U+4E00-4E2C, U+4E2E-4F52, U+4F54-6586, U+6588-7B7F, U+7B81-9FFF, U+FF00-FFEF; +} +@font-face { + font-family: "Fusion Pixel"; + font-display: swap; + src: url("/fonts/FusionPixel-12px-latin.woff2") format("woff2"); + unicode-range: U+0020-007E, U+00A0-00FF, U+2010-2027, U+2030-203A, U+2122, U+2190-21FF, U+2500-26FF, U+4E2D, U+4F53, U+6587, U+7B80; } -/* ── Light theme (pebble / meadow) ── */ +/* Palette inks are registered colours so a palette swap fades in four hard steps, like a 16-bit palette cycle. */ +@property --ink-void { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-shadow { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-ink { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-ink-soft { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-stone { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-stone-light { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-paper { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-white { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-sky-0 { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-sky-1 { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-sky-2 { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-sky-3 { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-sun { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-glow { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-cloud { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-star { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-wood { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-wood-dark { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-brick { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-rug { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-leaf-0 { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-leaf-1 { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-leaf-2 { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-bloom { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-accent { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-accent-dark { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-gold { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-mint { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-sea { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-berry { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-skin { syntax: ""; inherits: true; initial-value: #000; } +@property --ink-screen { syntax: ""; inherits: true; initial-value: #000; } + +/* The day palette (lib/palette.ts) for first paint without script; PaletteClock overwrites these on . */ :root { - --bg: #fbfdf6; - --bg-subtle: #f8f9f5; - --panel: #fdfefb; - --panel-soft: #f1f3ef; - --panel-illustration: #eff2e8; - --text: #0a1d08; - --text-secondary: #2e3b28; - --muted: #6b7860; - --muted-strong: #505c47; - --accent: #203b14; - --accent-hover: #13290e; - --accent-soft: #ecf2df; - --text-on-accent: #fbfdf6; - --cta: #203b14; - --cta-hover: #13290e; - --cta-soft: rgba(32, 59, 20, 0.06); - --text-on-cta: #fbfdf6; - --text-on-cta-muted: rgba(251, 253, 246, 0.75); - --text-on-cta-faint: rgba(251, 253, 246, 0.6); - --border: #e1e6df; - --border-strong: #c4cbc2; - --border-cool: #c4cbc2; - --band: #2a332a; - --band-text: #fbfdf6; - --band-border: rgba(253, 254, 251, 0.24); - --footer-bg: #050e11; - --footer-text: #fbfdf6; - --glow-warm: rgba(32, 59, 20, 0.05); - --glow-accent: rgba(99, 143, 61, 0.06); - --section-alt: rgba(32, 59, 20, 0.02); - --aurora-mint: rgba(94, 236, 151, 0.22); - --aurora-teal: rgba(45, 212, 191, 0.16); - --aurora-lime: rgba(137, 175, 84, 0.16); + --p0: #0b0a12; --p1: #1d1b2b; --p2: #2f2c40; --p3: #4a4660; + --p4: #6d6884; --p5: #9a95ad; --p6: #e9e2cf; --p7: #fbf7ec; + --p8: #3f7fd1; --p9: #68a2e6; --p10: #9cc6f2; --p11: #d4e8fa; + --p12: #ffe08a; --p13: #fff2c2; --p14: #f4f7fb; --p15: #ffffff; + --p16: #a8693c; --p17: #6e3f23; --p18: #b4533f; --p19: #5d6ea8; + --p20: #235c2e; --p21: #3f8f3f; --p22: #78c050; --p23: #ffd35c; + --p24: #e0663d; --p25: #9c3b22; --p26: #f2b632; --p27: #5fd3a8; + --p28: #2a8fa8; --p29: #c4466f; --p30: #f1c79a; --p31: #7ef0c2; + --ink-void: var(--p0); --ink-shadow: var(--p1); --ink-ink: var(--p2); --ink-ink-soft: var(--p3); + --ink-stone: var(--p4); --ink-stone-light: var(--p5); --ink-paper: var(--p6); --ink-white: var(--p7); + --ink-sky-0: var(--p8); --ink-sky-1: var(--p9); --ink-sky-2: var(--p10); --ink-sky-3: var(--p11); + --ink-sun: var(--p12); --ink-glow: var(--p13); --ink-cloud: var(--p14); --ink-star: var(--p15); + --ink-wood: var(--p16); --ink-wood-dark: var(--p17); --ink-brick: var(--p18); --ink-rug: var(--p19); + --ink-leaf-0: var(--p20); --ink-leaf-1: var(--p21); --ink-leaf-2: var(--p22); --ink-bloom: var(--p23); + --ink-accent: var(--p24); --ink-accent-dark: var(--p25); --ink-gold: var(--p26); --ink-mint: var(--p27); + --ink-sea: var(--p28); --ink-berry: var(--p29); --ink-skin: var(--p30); --ink-screen: var(--p31); + + /* UI roles. Dawn, day and dusk are paper-and-ink; night (below) inverts to a lit-window look. */ + --ui-ground: var(--ink-paper); + --ui-dither: var(--ink-white); + --ui-panel: var(--ink-white); + --ui-text: var(--ink-void); + --ui-muted: var(--ink-ink-soft); + --ui-line: var(--ink-void); + --ui-on-line: var(--ink-white); + --ui-hi: var(--ink-star); + --ui-lo: var(--ink-stone-light); + --ui-drop: var(--ink-stone-light); + --ui-focus: var(--ink-void); + --ui-danger: var(--ink-accent-dark); + --ui-select: var(--ink-accent-dark); + --ui-xp: var(--ink-mint); + --ui-xp-off: var(--ink-ink-soft); + + /* --px snaps one font pixel to whole device pixels (set before paint by PaletteClock); --u is one 2× art pixel. */ + --px: 1; + --u: calc(2px * var(--px)); + --fs-1: calc(12px * var(--px)); + --fs-2: calc(24px * var(--px)); + --fs-3: calc(36px * var(--px)); + --fs-4: calc(48px * var(--px)); + + color-scheme: light; + transition-property: + --ink-void, --ink-shadow, --ink-ink, --ink-ink-soft, --ink-stone, --ink-stone-light, --ink-paper, --ink-white, + --ink-sky-0, --ink-sky-1, --ink-sky-2, --ink-sky-3, --ink-sun, --ink-glow, --ink-cloud, --ink-star, + --ink-wood, --ink-wood-dark, --ink-brick, --ink-rug, --ink-leaf-0, --ink-leaf-1, --ink-leaf-2, --ink-bloom, + --ink-accent, --ink-accent-dark, --ink-gold, --ink-mint, --ink-sea, --ink-berry, --ink-skin, --ink-screen; + transition-duration: 600ms; + transition-timing-function: steps(4, jump-end); } -/* ── Dark theme (Material-3 dark surfaces + mint primary) ── */ -.dark { - --bg: #131515; - --bg-subtle: #171b1c; - --panel: #1d2226; - --panel-soft: #20262a; - --panel-illustration: #1d2226; - --text: #d8e3dd; - --text-secondary: #b2c3bf; - --muted: #93a49e; - --muted-strong: #b2c3bf; - --accent: #5eec97; - --accent-hover: #4af58f; - --accent-soft: #1c4534; - --text-on-accent: #124627; - --cta: #5eec97; - --cta-hover: #4af58f; - --cta-soft: rgba(94, 236, 151, 0.1); - --text-on-cta: #124627; - --text-on-cta-muted: rgba(18, 70, 39, 0.75); - --text-on-cta-faint: rgba(18, 70, 39, 0.55); - --border: #2c343b; - --border-strong: #3b454f; - --border-cool: #3b454f; - --band: #0b1510; - --band-text: #d8e3dd; - --band-border: rgba(216, 227, 221, 0.2); - --footer-bg: #05090b; - --footer-text: #d8e3dd; - --glow-warm: rgba(94, 236, 151, 0.05); - --glow-accent: rgba(94, 236, 151, 0.04); - --section-alt: rgba(94, 236, 151, 0.03); - --aurora-mint: rgba(94, 236, 151, 0.3); - --aurora-teal: rgba(45, 212, 191, 0.22); - --aurora-lime: rgba(137, 175, 84, 0.18); +:root[data-daypart="night"] { + --ui-ground: var(--ink-shadow); + --ui-dither: var(--ink-void); + --ui-panel: var(--ink-ink); + --ui-text: var(--ink-star); + --ui-muted: var(--ink-glow); + --ui-line: var(--ink-glow); + --ui-on-line: var(--ink-void); + --ui-hi: var(--ink-stone); + --ui-lo: var(--ink-void); + --ui-drop: var(--ink-void); + --ui-focus: var(--ink-gold); + --ui-danger: var(--ink-accent); + --ui-select: var(--ink-accent); + --ui-xp-off: var(--ink-stone); + color-scheme: dark; } -/* ── Base ── */ -html { - scroll-behavior: smooth; +*, +*::before, +*::after { + box-sizing: border-box; } body { - background-color: var(--bg); - color: var(--text); - font-family: var(--font-sans); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; - line-height: 1.55; + margin: 0; + min-height: 100vh; + color: var(--ui-text); + font-family: "Fusion Pixel", ui-monospace, monospace; + font-size: var(--fs-1); + line-height: 1.5; + font-synthesis: none; + -webkit-font-smoothing: none; + text-rendering: optimizeSpeed; + image-rendering: pixelated; + overflow-x: clip; + /* Ground: a sparse two-colour dither, one art pixel lit in every 4×4 block on alternating diagonals. */ + background: + conic-gradient(from 270deg at var(--u) var(--u), var(--ui-dither) 25%, transparent 0) 0 0 / calc(4 * var(--u)) calc(4 * var(--u)), + conic-gradient(from 270deg at var(--u) var(--u), var(--ui-dither) 25%, transparent 0) calc(2 * var(--u)) calc(2 * var(--u)) / calc(4 * var(--u)) calc(4 * var(--u)), + var(--ui-ground); } -.font-display { - font-family: var(--font-display); - font-variant-ligatures: common-ligatures discretionary-ligatures; +h1, h2, h3, h4, h5, h6, b, strong, th { + font-weight: normal; +} + +a { + color: inherit; +} + +button, input, select, textarea { + font: inherit; + color: inherit; +} + +img, svg, canvas, video { + image-rendering: pixelated; } ::selection { - background-color: var(--accent); - color: var(--text-on-accent); + background: var(--ink-gold); + color: var(--ink-void); } :focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; + outline: var(--u) solid var(--ui-focus); + outline-offset: var(--u); } -:focus:not(:focus-visible) { - outline: none; -} - -/* ── Eyebrow / small-caps mono label ── */ -.eyebrow { - font-family: var(--font-mono); - font-variant-caps: all-small-caps; - letter-spacing: 0.06em; -} - -/* ── Scrollbar ── */ -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: var(--border-strong); - border-radius: 3px; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--muted); -} - -* { - scrollbar-width: thin; - scrollbar-color: var(--border-strong) transparent; -} - -/* ── Entrance + reveal motion (faithful to adaline: ease-out-quint) ── */ -@keyframes nav-enter { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes rise-enter { - from { - opacity: 0; - transform: translateY(14px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.enter-nav { - animation: nav-enter 0.52s cubic-bezier(0.22, 1, 0.36, 1) 0.1s both; -} - -.enter-rise { - animation: rise-enter 0.55s cubic-bezier(0.22, 1, 0.36, 1) both; -} - -/* - * Scroll-reveal primitive. Progressive enhancement: content is visible by - * default and only starts hidden once the `js` class is present, so the page - * stays fully readable if scripts never run. - */ -.reveal { - transition: - opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1), - transform 0.6s cubic-bezier(0.22, 1, 0.36, 1); - transition-delay: var(--reveal-delay, 0ms); -} - -.js .reveal { - opacity: 0; - transform: translateY(16px); -} - -.js .reveal[data-in-view="true"] { - opacity: 1; - transform: translateY(0); -} - -/* ── Aurora: slow-drifting gradient light for dark showcase surfaces ── */ -@keyframes aurora-drift { - 0% { - transform: translate3d(0, 0, 0) scale(1); - } - 50% { - transform: translate3d(-4%, 3%, 0) scale(1.12); - } - 100% { - transform: translate3d(0, 0, 0) scale(1); - } -} - -.aurora-layer { - animation: aurora-drift 20s ease-in-out infinite; - will-change: transform; -} - -/* ── Reduced motion ── */ @media (prefers-reduced-motion: reduce) { - html { - scroll-behavior: auto; + :root { + transition: none; } - *, *::before, *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } - - .js .reveal { - opacity: 1; - transform: none; + animation: none !important; + transition: none !important; + scroll-behavior: auto !important; } } diff --git a/landing/app/layout.tsx b/landing/app/layout.tsx index 33882a71..0b2e6b10 100644 --- a/landing/app/layout.tsx +++ b/landing/app/layout.tsx @@ -1,97 +1,29 @@ -import type { Metadata } from "next"; -import { Instrument_Sans, Instrument_Serif, Newsreader, JetBrains_Mono } from "next/font/google"; +import type { Metadata, Viewport } from "next"; +import type { ReactNode } from "react"; import "./globals.css"; -import { ThemeProvider } from "../components/theme/theme-provider"; +import { PaletteClock } from "../components/pixel"; +import { description, faqs } from "../lib/content"; +import { RELEASE_PAGE_URL, releaseVersion } from "../lib/release-links"; +import { WEBSITE_URL } from "../lib/site-constants"; -const instrumentSans = Instrument_Sans({ - subsets: ["latin"], - variable: "--font-instrument-sans", - display: "swap", - weight: ["400", "500", "600", "700"], -}); - -const instrumentSerif = Instrument_Serif({ - subsets: ["latin"], - variable: "--font-instrument-serif", - display: "swap", - weight: "400", -}); - -const newsreader = Newsreader({ - subsets: ["latin"], - variable: "--font-newsreader", - display: "swap", - weight: ["300", "400", "500"], -}); - -const jetbrainsMono = JetBrains_Mono({ - subsets: ["latin"], - variable: "--font-jetbrains-mono", - display: "swap", - weight: ["400", "500"], -}); - -const fontVariables = `${instrumentSans.variable} ${instrumentSerif.variable} ${newsreader.variable} ${jetbrainsMono.variable}`; -import { - landingHowItWorksNarrative, - landingStructuredDataNarrative, -} from "../lib/product-narrative"; -import { platformSupportSummary } from "../lib/platform-support"; - -const description = - "Local-first desktop control plane for autonomous AI companies. Set a goal, let an AI CEO assemble specialists, delegate work, and deliver code, research, content, and operations from your desktop."; +const title = "AgentCompany · Run an autonomous AI company on your desktop"; export const metadata: Metadata = { - metadataBase: new URL("https://agent-company.dev"), - title: "AgentCompany - Launch an Autonomous AI Company from Your Desktop", + metadataBase: new URL(WEBSITE_URL), + title, description, - keywords: [ - "AI company", - "autonomous AI agents", - "AI automation", - "multi-agent system", - "AI CEO", - "AI agent orchestration", - "autonomous agents desktop app", - "AI task delegation", - "AI team management", - "AI code generation", - "AI content generation", - "AI research automation", - "self-organizing AI", - "AI workforce", - "Claude Code", - "Codex CLI", - "Gemini CLI", - "multi-agent framework", - "AI company builder", - ], + alternates: { canonical: "/" }, openGraph: { - title: "AgentCompany - Launch an Autonomous AI Company from Your Desktop", + title, description, - url: "https://agent-company.dev/", + url: "/", siteName: "AgentCompany", locale: "en_US", - images: [ - { - url: "/images/og.jpg", - width: 1200, - height: 630, - alt: "AgentCompany desktop dashboard — quarter goal, budget, agent workload, and recent runs", - }, - ], type: "website", + images: [{ url: "/images/og.jpg", width: 1200, height: 630, alt: "The AgentCompany desktop app" }], }, - twitter: { - card: "summary_large_image", - title: "AgentCompany - Launch an Autonomous AI Company from Your Desktop", - description, - images: ["/images/og.jpg"], - }, - robots: { - index: true, - follow: true, - }, + twitter: { card: "summary_large_image", title, description, images: ["/images/og.jpg"] }, + robots: { index: true, follow: true }, icons: { icon: [ { url: "/favicon.ico?v=2", sizes: "any", type: "image/x-icon" }, @@ -101,130 +33,42 @@ export const metadata: Metadata = { }, }; -const themeScript = ` -(function(){document.documentElement.classList.add("js");try{var t=localStorage.getItem("agentcompany-theme");if(t==="dark"||(!t&&window.matchMedia("(prefers-color-scheme:dark)").matches)){document.documentElement.classList.add("dark")}}catch(e){}})(); -`; +export const viewport: Viewport = { width: "device-width", initialScale: 1 }; -const softwareJsonLd = { - "@context": "https://schema.org", - "@type": "SoftwareApplication", - name: "AgentCompany", - applicationCategory: "BusinessApplication", - operatingSystem: "macOS, Windows", - url: "https://agent-company.dev", - downloadUrl: "https://agent-company.dev/#download", - screenshot: "https://agent-company.dev/images/dashboard.webp", - softwareVersion: landingStructuredDataNarrative.softwareVersion, - description, - featureList: landingStructuredDataNarrative.featureList, - author: { "@type": "Organization", name: "AgentCompany", url: "https://agent-company.dev" }, -}; +const jsonLd = [ + { + "@context": "https://schema.org", + "@type": "SoftwareApplication", + name: "AgentCompany", + applicationCategory: "BusinessApplication", + operatingSystem: "macOS, Windows, Linux", + softwareVersion: releaseVersion, + url: WEBSITE_URL, + downloadUrl: RELEASE_PAGE_URL, + description, + offers: { "@type": "Offer", price: "0", priceCurrency: "USD" }, + inLanguage: ["en", "zh-Hans"], + }, + { + "@context": "https://schema.org", + "@type": "FAQPage", + mainEntity: faqs.map((faq) => ({ + "@type": "Question", + name: faq.question, + acceptedAnswer: { "@type": "Answer", text: faq.answer.join(" ") }, + })), + }, +]; -const organizationJsonLd = { - "@context": "https://schema.org", - "@type": "Organization", - name: "AgentCompany", - url: "https://agent-company.dev", - logo: "https://agent-company.dev/icon.png", -}; - -const howToJsonLd = { - "@context": "https://schema.org", - "@type": "HowTo", - name: "How to Launch an Autonomous AI Company with AgentCompany", - description: "Complete the AgentCompany desktop first-run flow and queue the first mission.", - step: landingHowItWorksNarrative.steps.map((step) => ({ - "@type": "HowToStep", - position: step.number, - name: step.title, - text: step.description, - })), -}; - -const faqJsonLd = { - "@context": "https://schema.org", - "@type": "FAQPage", - mainEntity: [ - { - "@type": "Question", - name: "What is AgentCompany?", - acceptedAnswer: { - "@type": "Answer", - text: "AgentCompany is a local-first desktop application for launching and operating autonomous AI companies. You define the mission, an AI CEO organizes the team, delegates work, and coordinates delivery across engineering, design, research, and operations.", - }, - }, - { - "@type": "Question", - name: "How do I download AgentCompany?", - acceptedAnswer: { - "@type": "Answer", - text: platformSupportSummary.downloadAnswer, - }, - }, - { - "@type": "Question", - name: "What platforms does AgentCompany support?", - acceptedAnswer: { - "@type": "Answer", - text: platformSupportSummary.platformAnswer, - }, - }, - { - "@type": "Question", - name: "What AI models can AgentCompany use?", - acceptedAnswer: { - "@type": "Answer", - text: "AgentCompany supports multiple AI runtimes as first-class connectors: Claude Code, Codex CLI, and Gemini CLI. You can connect your preferred provider during setup.", - }, - }, - { - "@type": "Question", - name: "How does the AI CEO work?", - acceptedAnswer: { - "@type": "Answer", - text: "When you create a company with a goal, the AI CEO agent analyzes the objective, creates the necessary departments, hires specialized agents for each role, builds an org chart, and delegates tasks. The CEO monitors progress, handles blockers, and coordinates cross-department work.", - }, - }, - { - "@type": "Question", - name: "Is my data private? Does it leave my machine?", - acceptedAnswer: { - "@type": "Answer", - text: "AgentCompany is local-first. All data is stored in a local SQLite database on your machine. Secrets are encrypted via an OS-level keychain. The only external communication is between your machine and your AI provider's API.", - }, - }, - { - "@type": "Question", - name: "What can an AI company actually deliver?", - acceptedAnswer: { - "@type": "Answer", - text: "AI companies built with AgentCompany can produce code, content, research, business documents, and more. The deliverables depend on the goal you set and the AI runtime you connect.", - }, - }, - { - "@type": "Question", - name: "How are updates delivered?", - acceptedAnswer: { - "@type": "Answer", - text: "Desktop updates are published to a Cloudflare-hosted release lane. macOS builds include the signed update metadata required for in-app update checks, while the Windows build is delivered from the same release host for manual installation.", - }, - }, - ], -}; - -export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { +export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) { return ( - + - -