Files
CleanMM/Apps/LandingSite/DESIGN.md
zhukang 03fe98c163 feat(landing): add Atlas landing page with Astro static site
Implement the full landing page for atlas.atomstorm.ai per the PRD at
Docs/Execution/Landing-Page-PRD-2026-03-14.md. Includes design spec,
bilingual Astro site, CI pipeline, and all assets.

Key deliverables:
- DESIGN.md: 7-section Atom Web Design specification
- Astro 5.x static site with 15 components and 11 page sections
- Bilingual i18n (zh/en) with path-based routing
- Build-time GitHub release manifest integration
- Release channel state machine (Stable/Prerelease/Coming Soon)
- CSS design tokens mapped from AtlasBrand.swift
- Self-hosted fonts (Space Grotesk, Instrument Sans, IBM Plex Mono)
- OG social sharing images for both locales
- GitHub Actions workflow for GitHub Pages deployment
- Zero client JS, 227KB page weight, 17/17 quality checks passed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 10:18:27 +08:00

989 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Atlas Landing Page — Atom Web Design Specification
> **Domain**: `atlas.atomstorm.ai` | **Deploy**: GitHub Pages | **Source**: `Apps/LandingSite/`
> **PRD**: `Docs/Execution/Landing-Page-PRD-2026-03-14.md`
> **Date**: 2026-03-15
---
## Table of Contents
1. [Product Definition](#1-product-definition)
2. [Tech Stack Decision](#2-tech-stack-decision)
3. [Data Architecture](#3-data-architecture)
4. [Component Architecture](#4-component-architecture)
5. [Constraint System](#5-constraint-system)
6. [Visual Design System](#6-visual-design-system)
7. [Quality Gates](#7-quality-gates)
---
## 1. Product Definition
### 1.1 User Stories
| ID | Story | Acceptance Criteria |
|----|-------|-------------------|
| US-01 | As a first-time visitor, I want to understand what Atlas does in one screen so I can decide if it solves my problem. | Hero section communicates product promise in < 10 seconds; headline + subheadline + screenshot visible above the fold. |
| US-02 | As a cautious Mac user, I want to see safety and trust signals so I can decide whether Atlas is safe enough to install. | Trust strip, open-source badge, recovery-first messaging, permissions explanation, and Gatekeeper guidance are all visible without deep scrolling. |
| US-03 | As a developer, I want to see that Atlas understands developer disk pressure (Xcode, simulators, caches) so I know it is relevant to my workflow. | Developer cleanup section lists concrete developer artifacts (derived data, simulators, package caches) with Atlas-specific handling. |
| US-04 | As a potential downloader, I want to get the latest build without navigating GitHub so I can install Atlas quickly. | Primary CTA links to the correct release asset; version number, channel badge, and release date are visible next to the CTA. |
| US-05 | As a visitor encountering a prerelease, I want honest disclosure about signing status and Gatekeeper friction so I can install without confusion. | Prerelease badge, warning label, and "Open Anyway" recovery path are shown when the latest release is not Developer ID signed. |
### 1.2 MVP Scope
| Included | Deferred |
|----------|----------|
| Single bilingual single-page site (`/zh/`, `/en/`) | Blog / CMS |
| Responsive hero with release-state CTA | Changelog microsite |
| 11 page sections (Hero → Footer) | Testimonials from named users |
| Screenshot gallery (46 images) | Interactive benchmark calculator |
| Dynamic release state block (build-time) | Gated PDF lead magnet |
| Trust/safety section with Gatekeeper guidance | Multi-page docs hub |
| FAQ with expand/collapse | Account system |
| GitHub Pages deployment with custom domain | Pricing or checkout flow |
| Privacy-respecting analytics (Plausible) | In-browser disk scan demo |
| Optional beta email capture (3rd-party form) | |
### 1.3 Release Channel State Machine
The page must treat release channel status as product truth. Three states drive CTA behavior:
```
┌─────────────────────┐
│ No Public Release │ ← no GitHub Release with assets
│ CTA: "View on │
│ GitHub" / "Join │
│ Beta Updates" │
└──────────┬──────────┘
│ first release published
┌─────────────────────┐
│ Prerelease Only │ ← prerelease=true on latest release
│ CTA: "Download │
│ Prerelease" │
│ + warning label │
│ + Gatekeeper note │
└──────────┬──────────┘
│ Developer ID signing configured
┌─────────────────────┐
│ Stable Release │ ← prerelease=false on latest release
│ CTA: "Download │
│ for macOS" │
│ + version badge │
└─────────────────────┘
```
**State × CTA Behavior Matrix**:
| State | Primary CTA Label | Badge | Warning | Secondary CTA |
|-------|-------------------|-------|---------|---------------|
| Stable | Download for macOS | `Stable` (teal) | None | View on GitHub |
| Prerelease | Download Prerelease | `Prerelease` (amber) | Gatekeeper install note | View on GitHub |
| No Release | View on GitHub | `Coming Soon` (slate) | None | Join Beta Updates |
---
## 2. Tech Stack Decision
### 2.1 Stack Table
| Layer | Choice | Why |
|-------|--------|-----|
| **Framework** | Astro 5.x (static adapter) | Outputs pure HTML/CSS with zero JS by default; matches PRD's "static-first" requirement |
| **Styling** | Vanilla CSS + custom properties | 1:1 token mapping from `AtlasBrand.swift`; avoids Tailwind's generic aesthetic per PRD's "not generic SaaS" direction |
| **i18n** | `@astrojs/i18n` path-based routing | Native Astro feature; produces `/en/` and `/zh/` paths with `hreflang` tags |
| **Fonts** | Self-hosted (Space Grotesk, Instrument Sans, IBM Plex Mono) | Eliminates Google Fonts dependency; keeps < 20KB JS budget; GDPR-safe |
| **Analytics** | Plausible (self-hosted or cloud) | Privacy-respecting, cookie-free, GDPR-compliant; custom events for CTA/FAQ tracking |
| **Search Console** | Google Search Console | Indexing monitoring and query analysis; no client-side impact |
| **Hosting** | GitHub Pages | Free, reliable, native GitHub Actions integration; custom domain with HTTPS |
| **CI/CD** | GitHub Actions (`.github/workflows/landing-page.yml`) | Triggers on source changes + release events; deploys via `actions/deploy-pages` |
| **Release Data** | Build-time GitHub API fetch → static manifest | No client-side API dependency for first paint; fallback to embedded JSON |
| **UI Framework** | None (no React/Vue/Svelte) | Zero framework overhead; `.astro` components render to static HTML |
| **Package Manager** | pnpm | Fast, deterministic, disk-efficient |
### 2.2 Key Technical Decisions
1. **No UI framework islands** — Every component is a `.astro` file that renders to static HTML. The only client JS is: language toggle persistence (`localStorage`, < 200 bytes), FAQ accordion (`<details>` elements with optional progressive enhancement), and Plausible analytics snippet (< 1KB).
2. **Self-hosted fonts** — Font files are committed to `Apps/LandingSite/public/fonts/`. Subset to Latin + CJK ranges. Use `font-display: swap` for all faces.
3. **Build-time release manifest** — A `scripts/fetch-release.ts` script runs at build time to query the GitHub Releases API and emit `src/data/release-manifest.json`. A static fallback (`src/data/release-fallback.json`) is used if the API call fails.
---
## 3. Data Architecture
### 3.1 `ReleaseManifest` Schema
```typescript
/**
* Generated at build time by scripts/fetch-release.ts
* Consumed by Hero, CTA, and Footer components.
* File: src/data/release-manifest.json
*/
interface ReleaseManifest {
/** Release channel: determines CTA behavior and badge */
channel: "stable" | "prerelease" | "none";
/** Semantic version string, e.g. "1.0.2" */
version: string | null;
/** ISO 8601 date string of the release publication */
publishedAt: string | null;
/** GitHub Release page URL */
releaseUrl: string | null;
/** Direct download asset links */
assets: {
dmg: string | null;
zip: string | null;
pkg: string | null;
sha256: string | null;
};
/** Whether Gatekeeper friction is expected */
gatekeeperWarning: boolean;
/** Human-readable install note for prerelease builds */
installNote: string | null;
/** Git tag name, e.g. "V1.0.2" */
tagName: string | null;
/** Timestamp of manifest generation (ISO 8601) */
generatedAt: string;
}
```
**Priority chain** (per PRD):
1. Build-time generated `release-manifest.json` via `scripts/fetch-release.ts`
2. Static fallback `src/data/release-fallback.json` (committed, manually maintained)
3. No client-side GitHub API fetch for first paint
**Manifest generation logic** (`scripts/fetch-release.ts`):
```
1. Fetch latest release from GitHub API (repos/{owner}/{repo}/releases/latest)
2. If no release exists → channel: "none"
3. If release.prerelease === true → channel: "prerelease", gatekeeperWarning: true
4. If release.prerelease === false → channel: "stable", gatekeeperWarning: false
5. Extract .dmg, .zip, .pkg, .sha256 from release.assets[]
6. Write to src/data/release-manifest.json
```
### 3.2 `LandingCopy` i18n Schema
```typescript
/**
* Translation file structure.
* Files: src/i18n/en.json, src/i18n/zh.json
* Keys are grouped by page section for maintainability.
*/
interface LandingCopy {
meta: {
title: string; // <title> and og:title
description: string; // <meta name="description"> and og:description
ogImage: string; // og:image path
};
nav: {
whyAtlas: string;
howItWorks: string;
developers: string;
safety: string;
faq: string;
download: string; // CTA label (dynamic, overridden by channel)
};
hero: {
headline: string;
subheadline: string;
ctaPrimary: string; // Overridden by channel state
ctaSecondary: string; // "View on GitHub"
badgeStable: string;
badgePrerelease: string;
badgeComingSoon: string;
prereleaseWarning: string;
gatekeeperNote: string;
versionLabel: string; // "Version {version} · {date}"
};
trustStrip: {
openSource: string;
recoveryFirst: string;
developerAware: string;
macNative: string;
directDownload: string;
};
problem: {
sectionTitle: string;
scenarios: Array<{
before: string; // Pain point
after: string; // Atlas outcome
}>;
};
features: {
sectionTitle: string;
cards: Array<{
title: string;
value: string; // User-facing value proposition
example: string; // Concrete example
trustCue: string; // Trust signal
}>;
};
howItWorks: {
sectionTitle: string;
steps: Array<{
label: string;
description: string;
}>;
};
developer: {
sectionTitle: string;
subtitle: string;
items: Array<{
title: string;
description: string;
}>;
};
safety: {
sectionTitle: string;
subtitle: string;
points: Array<{
title: string;
description: string;
}>;
gatekeeperGuide: {
title: string;
steps: string[];
};
};
screenshots: {
sectionTitle: string;
items: Array<{
src: string;
alt: string;
caption: string;
}>;
};
openSource: {
sectionTitle: string;
repoLabel: string;
licenseLabel: string;
attributionLabel: string;
changelogLabel: string;
};
faq: {
sectionTitle: string;
items: Array<{
question: string;
answer: string;
}>;
};
footer: {
download: string;
github: string;
documentation: string;
privacy: string;
security: string;
copyright: string;
};
}
```
### 3.3 State Management Map
All state is resolved at build time. The only client-side state is:
| State | Scope | Storage | Purpose |
|-------|-------|---------|---------|
| Language preference | Session | `localStorage` key `atlas-lang` | Remember manual language switch |
| FAQ expanded items | Transient | DOM (`<details open>`) | No persistence needed |
| Release data | Build-time | `release-manifest.json` | Embedded in HTML at build |
| Analytics events | Fire-and-forget | Plausible JS SDK | No local state |
---
## 4. Component Architecture
### 4.1 File Structure
```
Apps/LandingSite/
├── astro.config.mjs # Astro config: static adapter, i18n, site URL
├── package.json # Dependencies: astro, @astrojs/sitemap
├── pnpm-lock.yaml
├── tsconfig.json
├── public/
│ ├── fonts/ # Self-hosted font files (woff2)
│ │ ├── SpaceGrotesk-Bold.woff2
│ │ ├── SpaceGrotesk-Medium.woff2
│ │ ├── InstrumentSans-Regular.woff2
│ │ ├── InstrumentSans-Medium.woff2
│ │ ├── IBMPlexMono-Regular.woff2
│ │ └── IBMPlexMono-Medium.woff2
│ ├── images/
│ │ ├── atlas-icon.png # App icon (from Docs/Media/README/)
│ │ ├── og-image-en.png # Open Graph image (English)
│ │ ├── og-image-zh.png # Open Graph image (Chinese)
│ │ └── screenshots/ # Product screenshots
│ │ ├── atlas-overview.png
│ │ ├── atlas-smart-clean.png
│ │ ├── atlas-apps.png
│ │ ├── atlas-history.png
│ │ ├── atlas-settings.png
│ │ └── atlas-prerelease-warning.png
│ ├── favicon.ico
│ └── robots.txt
├── src/
│ ├── data/
│ │ ├── release-manifest.json # Build-time generated
│ │ └── release-fallback.json # Static fallback (committed)
│ ├── i18n/
│ │ ├── en.json # English translations
│ │ ├── zh.json # Chinese translations
│ │ └── utils.ts # t() helper, locale detection
│ ├── styles/
│ │ ├── tokens.css # Design tokens as CSS custom properties
│ │ ├── reset.css # Minimal CSS reset
│ │ ├── global.css # Global styles (fonts, base elements)
│ │ └── utilities.css # Utility classes (sr-only, container, etc.)
│ ├── layouts/
│ │ └── BaseLayout.astro # HTML shell: <head>, meta, fonts, analytics
│ ├── components/
│ │ ├── NavBar.astro # [interactive] Sticky top nav + language toggle + CTA
│ │ ├── Hero.astro # [static] Headline, subheadline, CTA, badge, screenshot
│ │ ├── TrustStrip.astro # [static] Five trust signal pills
│ │ ├── ProblemOutcome.astro # [static] Three pain → solution cards
│ │ ├── FeatureGrid.astro # [static] Six feature story cards
│ │ ├── HowItWorks.astro # [static] Four-step workflow visualization
│ │ ├── DeveloperSection.astro # [static] Developer cleanup showcase
│ │ ├── SafetySection.astro # [static] Permissions, trust, Gatekeeper guide
│ │ ├── ScreenshotGallery.astro# [interactive] Desktop gallery / mobile carousel
│ │ ├── OpenSourceSection.astro# [static] Repo link, license, attribution
│ │ ├── FaqSection.astro # [interactive] Expandable Q&A using <details>
│ │ ├── FooterSection.astro # [static] Links, privacy, security contact
│ │ ├── CtaButton.astro # [static] Reusable CTA (primary/secondary variants)
│ │ ├── ChannelBadge.astro # [static] Release channel badge (stable/prerelease/coming)
│ │ └── FeatureCard.astro # [static] Reusable card for feature grid
│ └── pages/
│ ├── index.astro # Root redirect → /zh/
│ ├── zh/
│ │ └── index.astro # Chinese landing page
│ └── en/
│ └── index.astro # English landing page
└── scripts/
└── fetch-release.ts # Build-time script: GitHub API → release-manifest.json
```
### 4.2 Component Interaction Map
```
BaseLayout.astro
└── [lang]/index.astro
├── NavBar.astro .................. [interactive: language toggle, mobile menu]
│ ├── CtaButton.astro props: { label, href, variant: "primary" }
│ └── ChannelBadge.astro props: { channel }
├── Hero.astro .................... [static]
│ ├── CtaButton.astro props: { label, href, variant: "primary" }
│ ├── CtaButton.astro props: { label, href, variant: "secondary" }
│ └── ChannelBadge.astro props: { channel, version, date }
├── TrustStrip.astro .............. [static]
├── ProblemOutcome.astro .......... [static]
├── FeatureGrid.astro ............. [static]
│ └── FeatureCard.astro (×6) props: { title, value, example, trustCue, icon }
├── HowItWorks.astro .............. [static]
├── DeveloperSection.astro ........ [static]
├── SafetySection.astro ........... [static]
├── ScreenshotGallery.astro ....... [interactive: carousel on mobile]
├── OpenSourceSection.astro ....... [static]
├── FaqSection.astro .............. [interactive: <details> expand/collapse]
└── FooterSection.astro ........... [static]
└── CtaButton.astro props: { label, href, variant: "primary" }
```
**Boundary annotations**:
- `[static]` — Pure HTML at build time, zero client JS
- `[interactive]` — Minimal client JS via inline `<script>` in the component (no framework island)
### 4.3 Component Props Summary
| Component | Props | Data Source |
|-----------|-------|-------------|
| `CtaButton` | `label: string, href: string, variant: "primary" \| "secondary" \| "ghost"` | i18n + manifest |
| `ChannelBadge` | `channel: "stable" \| "prerelease" \| "none", version?: string, date?: string` | manifest |
| `FeatureCard` | `title: string, value: string, example: string, trustCue: string, icon: string` | i18n |
| `NavBar` | `locale: "en" \| "zh", manifest: ReleaseManifest` | i18n + manifest |
| `Hero` | `locale: "en" \| "zh", manifest: ReleaseManifest` | i18n + manifest |
| `FaqSection` | `items: Array<{ question: string, answer: string }>` | i18n |
| `ScreenshotGallery` | `items: Array<{ src: string, alt: string, caption: string }>` | i18n |
---
## 5. Constraint System
### 5.1 NEVER Rules
| # | Category | Rule |
|---|----------|------|
| N-01 | Brand | NEVER use the `Mole` brand name in any user-facing text, metadata, or alt text. |
| N-02 | Brand | NEVER claim malware protection, antivirus behavior, or security scanning capability. |
| N-03 | Brand | NEVER overstate physical recovery coverage — always qualify with "when supported". |
| N-04 | Brand | NEVER imply all releases are Apple-signed if the current release is a prerelease. |
| N-05 | Security | NEVER include hardcoded GitHub tokens, API keys, or secrets in client-facing code. |
| N-06 | Security | NEVER use client-side GitHub API fetches for critical first-paint release information. |
| N-07 | Security | NEVER rely on a manually committed `CNAME` file when using a custom GitHub Actions Pages workflow. |
| N-08 | Performance | NEVER ship a client JS bundle exceeding 20KB (excluding analytics). |
| N-09 | Performance | NEVER load fonts from external CDNs (Google Fonts, etc.). |
| N-10 | Performance | NEVER use framework islands (React, Vue, Svelte) for any component. |
| N-11 | Aesthetics | NEVER use generic SaaS gradients, purple-heavy palettes, or interchangeable startup layouts. |
| N-12 | Aesthetics | NEVER use endless floating particles, decorative animation loops, or bouncy spring motion. |
| N-13 | Copy | NEVER use hype words: "ultimate", "magic", "AI cleaner", "revolutionary", "blazing fast". |
| N-14 | Copy | NEVER use fear-based maintenance language: "Your Mac is at risk", "Critical error", "You must allow this". |
### 5.2 ALWAYS Rules
| # | Category | Rule |
|---|----------|------|
| A-01 | Disclosure | ALWAYS show exact version number and release date next to the download CTA. |
| A-02 | Disclosure | ALWAYS show a channel badge (`Stable`, `Prerelease`, or `Coming Soon`) next to the CTA. |
| A-03 | Disclosure | ALWAYS show a Gatekeeper install note when the release is a prerelease. |
| A-04 | Accessibility | ALWAYS maintain WCAG 2.1 AA contrast ratios (4.5:1 for normal text, 3:1 for large text). |
| A-05 | Accessibility | ALWAYS provide alt text for every image; screenshot alt text must describe the UI state shown. |
| A-06 | Accessibility | ALWAYS ensure all interactive elements are keyboard-navigable with visible focus indicators. |
| A-07 | i18n | ALWAYS include `hreflang` tags on both `/en/` and `/zh/` pages. |
| A-08 | i18n | ALWAYS serve localized `<title>`, `<meta description>`, and Open Graph metadata per locale. |
| A-09 | Testing | ALWAYS validate HTML output with the W3C validator before each deploy. |
| A-10 | Testing | ALWAYS run Lighthouse CI on both locales before merging to main. |
| A-11 | SEO | ALWAYS use crawlable `<h1>``<h6>` headings; never render hero text only inside images. |
| A-12 | Copy | ALWAYS qualify recovery claims with "when supported" or "while the retention window is open". |
| A-13 | Copy | ALWAYS use concrete verbs for CTAs: `Scan`, `Review`, `Restore`, `Download`. |
### 5.3 Naming Conventions
| Entity | Convention | Example |
|--------|-----------|---------|
| CSS custom property | `--atlas-{category}-{name}` | `--atlas-color-brand` |
| Component file | PascalCase `.astro` | `FeatureCard.astro` |
| CSS class | BEM-lite: `block__element--modifier` | `hero__cta--primary` |
| i18n key | dot-separated section path | `hero.headline` |
| Image file | kebab-case, descriptive | `atlas-overview.png` |
| Script file | kebab-case `.ts` | `fetch-release.ts` |
| Data file | kebab-case `.json` | `release-manifest.json` |
---
## 6. Visual Design System
### 6.1 Design Thinking — Four Questions
**Q1: Who is viewing this page?**
Mac users with disk pressure — both mainstream and developers — evaluating Atlas as an alternative to opaque commercial cleanup apps. They are cautious, technically aware, and skeptical of "magic cleaner" marketing.
**Q2: What should they feel?**
"This tool understands my Mac and will be honest with me." Calm authority, not hype. Precision, not spectacle. The page should feel like a modern macOS-native operations console translated into a polished marketing surface.
**Q3: What is the single most important action?**
Download the latest release (or, if prerelease, understand the install friction and proceed anyway).
**Q4: What could go wrong?**
- User mistakes a prerelease for a stable release → mitigated by mandatory channel badge and warning
- User bounces because the page looks like generic SaaS → mitigated by dark "precision utility" theme with native Mac feel
- User can't find the download → mitigated by persistent CTA in nav + hero + footer
### 6.2 CSS Custom Properties — Design Tokens
All values are derived from `AtlasBrand.swift` and the Xcode color assets. The landing page uses a **dark-only** theme per PRD direction.
```css
/* ══════════════════════════════════════════════════════
Atlas Landing Page — Design Tokens
Source of truth: AtlasBrand.swift + AtlasColors.xcassets
Theme: "Precision Utility" (dark-only)
══════════════════════════════════════════════════════ */
:root {
/* ── Colors: Background ──────────────────────────── */
--atlas-color-bg-base: #0D0F11; /* Graphite / near-black */
--atlas-color-bg-surface: #1A1D21; /* Warm slate cards */
--atlas-color-bg-surface-hover: #22262B; /* Card hover state */
--atlas-color-bg-raised: rgba(255, 255, 255, 0.06); /* Glassmorphic tint (matches AtlasColor.cardRaised dark) */
--atlas-color-bg-code: #151820; /* Code block background */
/* ── Colors: Brand ───────────────────────────────── */
/*
* AtlasBrand.colorset:
* Light: sRGB(0.0588, 0.4627, 0.4314) = #0F766E
* Dark: sRGB(0.0784, 0.5647, 0.5216) = #149085
*
* Landing page uses the dark variant as primary.
*/
--atlas-color-brand: #149085; /* AtlasBrand dark — primary teal */
--atlas-color-brand-light: #0F766E; /* AtlasBrand light — used for hover contrast */
--atlas-color-brand-glow: rgba(20, 144, 133, 0.25); /* CTA shadow glow */
/* ── Colors: Accent ──────────────────────────────── */
/*
* AtlasAccent.colorset:
* Light: sRGB(0.2039, 0.8275, 0.6000) = #34D399
* Dark: sRGB(0.3216, 0.8863, 0.7098) = #52E2B5
*/
--atlas-color-accent: #34D399; /* AtlasAccent light — mint highlight */
--atlas-color-accent-bright: #52E2B5; /* AtlasAccent dark — brighter mint */
/* ── Colors: Semantic ────────────────────────────── */
--atlas-color-success: #22C55E; /* systemGreen equivalent */
--atlas-color-warning: #F59E0B; /* Amber — prerelease/caution states */
--atlas-color-danger: #EF4444; /* systemRed equivalent */
--atlas-color-info: #3B82F6; /* systemBlue equivalent */
/* ── Colors: Text ────────────────────────────────── */
--atlas-color-text-primary: #F1F5F9; /* High contrast on dark bg */
--atlas-color-text-secondary: #94A3B8; /* Muted body text */
--atlas-color-text-tertiary: rgba(148, 163, 184, 0.6); /* Footnotes, timestamps (matches AtlasColor.textTertiary) */
/* ── Colors: Border ──────────────────────────────── */
--atlas-color-border: rgba(241, 245, 249, 0.08); /* Subtle (matches AtlasColor.border) */
--atlas-color-border-emphasis: rgba(241, 245, 249, 0.14); /* Focus/prominent (matches AtlasColor.borderEmphasis) */
/* ── Typography ──────────────────────────────────── */
--atlas-font-display: 'Space Grotesk', system-ui, sans-serif;
--atlas-font-body: 'Instrument Sans', system-ui, sans-serif;
--atlas-font-mono: 'IBM Plex Mono', ui-monospace, monospace;
/* Display sizes */
--atlas-text-hero: clamp(2.5rem, 5vw, 4rem); /* Hero headline */
--atlas-text-hero-weight: 700;
--atlas-text-section: clamp(1.75rem, 3.5vw, 2.5rem); /* Section title */
--atlas-text-section-weight: 700;
--atlas-text-card-title: 1.25rem; /* 20px — card heading */
--atlas-text-card-title-weight: 600;
/* Body sizes (mapped from AtlasTypography) */
--atlas-text-body: 1rem; /* 16px — standard body */
--atlas-text-body-weight: 400;
--atlas-text-body-small: 0.875rem; /* 14px — secondary body */
--atlas-text-label: 0.875rem; /* 14px — semibold label */
--atlas-text-label-weight: 600;
--atlas-text-caption: 0.75rem; /* 12px — chips, footnotes */
--atlas-text-caption-weight: 600;
--atlas-text-caption-small: 0.6875rem; /* 11px — legal, timestamps */
/* Line heights */
--atlas-leading-tight: 1.2; /* Display text */
--atlas-leading-normal: 1.6; /* Body text */
--atlas-leading-relaxed: 1.8; /* Long-form reading */
/* Letter spacing */
--atlas-tracking-tight: -0.02em; /* Display */
--atlas-tracking-normal: 0; /* Body */
--atlas-tracking-wide: 0.05em; /* Overlines, badges */
/* ── Spacing (4pt grid from AtlasSpacing) ────────── */
--atlas-space-xxs: 4px; /* AtlasSpacing.xxs */
--atlas-space-xs: 6px; /* AtlasSpacing.xs */
--atlas-space-sm: 8px; /* AtlasSpacing.sm */
--atlas-space-md: 12px; /* AtlasSpacing.md */
--atlas-space-lg: 16px; /* AtlasSpacing.lg */
--atlas-space-xl: 20px; /* AtlasSpacing.xl */
--atlas-space-xxl: 24px; /* AtlasSpacing.xxl */
--atlas-space-screen-h: 28px; /* AtlasSpacing.screenH */
--atlas-space-section: 32px; /* AtlasSpacing.section */
/* Web-specific extended spacing */
--atlas-space-section-gap: 80px; /* Between page sections */
--atlas-space-section-gap-lg: 120px; /* Hero → Trust strip gap */
/* ── Radius (continuous corners from AtlasRadius) ── */
--atlas-radius-sm: 8px; /* AtlasRadius.sm — chips, tags */
--atlas-radius-md: 12px; /* AtlasRadius.md — inline cards */
--atlas-radius-lg: 16px; /* AtlasRadius.lg — detail rows */
--atlas-radius-xl: 20px; /* AtlasRadius.xl — standard cards */
--atlas-radius-xxl: 24px; /* AtlasRadius.xxl — hero cards */
--atlas-radius-full: 9999px; /* Pills, badges, CTA buttons */
/* ── Elevation (shadow system from AtlasElevation) ── */
/* Flat — no shadow */
--atlas-shadow-flat: none;
/* Raised — default card level */
--atlas-shadow-raised: 0 10px 18px rgba(0, 0, 0, 0.05);
--atlas-shadow-raised-border: rgba(241, 245, 249, 0.08);
/* Prominent — hero cards, primary action areas */
--atlas-shadow-prominent: 0 16px 28px rgba(0, 0, 0, 0.09);
--atlas-shadow-prominent-border: rgba(241, 245, 249, 0.12);
/* CTA glow */
--atlas-shadow-cta: 0 6px 12px rgba(20, 144, 133, 0.25);
--atlas-shadow-cta-hover: 0 8px 20px rgba(20, 144, 133, 0.35);
/* ── Motion (from AtlasMotion) ───────────────────── */
--atlas-motion-fast: 150ms cubic-bezier(0.2, 0, 0, 1); /* Hover, press */
--atlas-motion-standard: 220ms cubic-bezier(0.2, 0, 0, 1); /* Toggle, selection */
--atlas-motion-slow: 350ms cubic-bezier(0.2, 0, 0, 1); /* Page transitions */
--atlas-motion-spring: 450ms cubic-bezier(0.34, 1.56, 0.64, 1); /* Playful feedback */
/* Staggered section reveal */
--atlas-stagger-delay: 80ms; /* Delay between items in stagger */
/* ── Layout (from AtlasLayout) ───────────────────── */
--atlas-width-reading: 920px; /* AtlasLayout.maxReadingWidth */
--atlas-width-workspace: 1200px; /* AtlasLayout.maxWorkspaceWidth */
--atlas-width-content: 1080px; /* AtlasLayout.maxWorkflowWidth — main content ceiling */
/* Responsive breakpoints */
--atlas-bp-sm: 640px; /* Mobile → Tablet */
--atlas-bp-md: 860px; /* Matches AtlasLayout.browserSplitThreshold */
--atlas-bp-lg: 1080px; /* Tablet → Desktop */
--atlas-bp-xl: 1280px; /* Wide desktop */
}
```
### 6.3 Five-Dimension Design Decisions
| Dimension | Decision | Rationale |
|-----------|----------|-----------|
| **Color** | Dark-only graphite base (#0D0F11) with teal brand (#149085) and mint accent (#34D399) | PRD specifies "graphite/near-black" background; teal carries trust; mint provides discovery cues without clashing |
| **Typography** | Space Grotesk (display) + Instrument Sans (body) + IBM Plex Mono (utility) | Geometric display for tech authority; humanist sans for readability; monospace for version/code credibility |
| **Spacing** | 80px section gap, 4pt internal grid, max 1080px content width | Generous whitespace per PRD "not crowded"; 4pt grid matches native app; reading width prevents long lines |
| **Shape** | Continuous corners (824px radius scale), capsule CTAs, no sharp edges | Maps directly from `AtlasRadius`; "rounded but not bubbly" per PRD; capsule CTAs match `AtlasPrimaryButtonStyle` |
| **Motion** | Staggered section reveal on scroll, 150ms hover transitions, no decorative loops | PRD: "snappy but never bouncy"; intersection observer triggers for progressive reveal; respects `prefers-reduced-motion` |
### 6.4 Section Band Pattern
The page alternates between "dark" and "surface" bands to create visual rhythm:
```
Section Background Band
─────────────────────────────────────────────────────
NavBar transparent → bg-base —
Hero bg-base Dark
Trust Strip bg-surface Surface
Problem → Outcome bg-base Dark
Feature Grid bg-surface Surface
How It Works bg-base Dark
Developer Section bg-surface Surface
Safety Section bg-base Dark
Screenshot Gallery bg-surface Surface
Open Source bg-base Dark
FAQ bg-surface Surface
Footer bg-base + border-top Dark
```
### 6.5 Component Styling Convention
**BEM-lite + CSS custom properties**:
```css
/* Block */
.hero { ... }
/* Element */
.hero__headline { ... }
.hero__cta { ... }
/* Modifier */
.hero__cta--primary { ... }
.hero__cta--secondary { ... }
```
**Card pattern** (maps from `AtlasCardModifier`):
```css
.card {
padding: var(--atlas-space-xl); /* 20px — AtlasSpacing.xl */
background: var(--atlas-color-bg-surface);
border: 1px solid var(--atlas-color-border); /* 0.08 opacity */
border-radius: var(--atlas-radius-xl); /* 20px — AtlasRadius.xl */
box-shadow: var(--atlas-shadow-raised); /* 0 10px 18px */
transition: transform var(--atlas-motion-fast),
box-shadow var(--atlas-motion-fast);
}
.card:hover {
transform: scale(1.008); /* Matches AtlasHoverModifier */
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.08);
}
.card--prominent {
border-radius: var(--atlas-radius-xxl); /* 24px — AtlasRadius.xxl */
border-width: 1.5px;
border-color: var(--atlas-shadow-prominent-border);
box-shadow: var(--atlas-shadow-prominent);
background:
linear-gradient(135deg, rgba(255,255,255,0.08) 0%, transparent 50%),
var(--atlas-color-bg-surface); /* Top-left inner glow */
}
```
**CTA button pattern** (maps from `AtlasPrimaryButtonStyle`):
```css
.cta--primary {
font-family: var(--atlas-font-body);
font-size: var(--atlas-text-label);
font-weight: var(--atlas-text-label-weight);
color: #FFFFFF;
padding: var(--atlas-space-md) var(--atlas-space-xxl); /* 12px 24px */
background: var(--atlas-color-brand);
border-radius: var(--atlas-radius-full); /* Capsule */
box-shadow: var(--atlas-shadow-cta);
transition: transform var(--atlas-motion-fast),
box-shadow var(--atlas-motion-fast);
cursor: pointer;
border: none;
}
.cta--primary:hover {
box-shadow: var(--atlas-shadow-cta-hover);
transform: translateY(-1px);
}
.cta--primary:active {
transform: scale(0.97);
box-shadow: 0 2px 4px rgba(20, 144, 133, 0.15);
}
.cta--primary:disabled {
background: rgba(20, 144, 133, 0.4);
cursor: not-allowed;
box-shadow: none;
}
```
**Badge pattern** (channel badge):
```css
.badge {
font-family: var(--atlas-font-mono);
font-size: var(--atlas-text-caption);
font-weight: var(--atlas-text-caption-weight);
letter-spacing: var(--atlas-tracking-wide);
text-transform: uppercase;
padding: var(--atlas-space-xxs) var(--atlas-space-sm);
border-radius: var(--atlas-radius-sm);
}
.badge--stable {
background: rgba(20, 144, 133, 0.15);
color: var(--atlas-color-accent);
border: 1px solid rgba(20, 144, 133, 0.3);
}
.badge--prerelease {
background: rgba(245, 158, 11, 0.15);
color: var(--atlas-color-warning);
border: 1px solid rgba(245, 158, 11, 0.3);
}
.badge--coming {
background: rgba(148, 163, 184, 0.1);
color: var(--atlas-color-text-secondary);
border: 1px solid rgba(148, 163, 184, 0.2);
}
```
### 6.6 Responsive Strategy
| Breakpoint | Layout Behavior |
|------------|----------------|
| < 640px (mobile) | Single column; hero screenshot below CTA; feature cards stack; screenshot carousel; hamburger nav |
| 640860px (tablet) | Two-column feature grid; hero screenshot beside text; nav items visible |
| 8601080px (small desktop) | Three-column feature grid; full nav; gallery grid |
| > 1080px (desktop) | Max content width 1080px centered; generous margins |
### 6.7 Font Loading Strategy
```css
@font-face {
font-family: 'Space Grotesk';
src: url('/fonts/SpaceGrotesk-Bold.woff2') format('woff2');
font-weight: 700;
font-display: swap;
unicode-range: U+0000-024F, U+4E00-9FFF; /* Latin + CJK */
}
@font-face {
font-family: 'Instrument Sans';
src: url('/fonts/InstrumentSans-Regular.woff2') format('woff2');
font-weight: 400;
font-display: swap;
unicode-range: U+0000-024F, U+4E00-9FFF;
}
/* ... additional faces for Medium weights and IBM Plex Mono */
```
Preload critical fonts in `<head>`:
```html
<link rel="preload" href="/fonts/SpaceGrotesk-Bold.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/InstrumentSans-Regular.woff2" as="font" type="font/woff2" crossorigin>
```
---
## 7. Quality Gates
### 7.1 Core Web Vitals
| Metric | Target | Tool |
|--------|--------|------|
| Largest Contentful Paint (LCP) | < 2.0s | Lighthouse CI |
| Interaction to Next Paint (INP) | < 100ms | Lighthouse CI |
| Cumulative Layout Shift (CLS) | < 0.05 | Lighthouse CI |
| Lighthouse Performance Score | >= 95 | Lighthouse CI |
| Lighthouse Accessibility Score | >= 95 | Lighthouse CI |
| Lighthouse Best Practices Score | >= 95 | Lighthouse CI |
| Lighthouse SEO Score | >= 95 | Lighthouse CI |
| Total Client JS | < 20KB (gzip) | Build output check |
| Total Page Weight | < 500KB (excl. screenshots) | Build output check |
### 7.2 Accessibility (WCAG 2.1 AA)
| Check | Requirement |
|-------|-------------|
| Color contrast | 4.5:1 minimum for normal text; 3:1 for large text (>= 18px bold / >= 24px) |
| Keyboard navigation | All interactive elements focusable; visible focus ring; logical tab order |
| Screen reader | Semantic HTML (`<nav>`, `<main>`, `<section>`, `<article>`); ARIA labels where needed |
| Alt text | Every `<img>` has descriptive alt text; decorative images use `alt=""` |
| Reduced motion | `@media (prefers-reduced-motion: reduce)` disables all animations |
| Language | `<html lang="zh-Hans">` / `<html lang="en">` set per locale |
### 7.3 CI Pipeline
**File**: `.github/workflows/landing-page.yml`
```yaml
name: Landing Page
on:
push:
paths:
- 'Apps/LandingSite/**'
branches: [main]
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: Apps/LandingSite/pnpm-lock.yaml
- name: Install dependencies
working-directory: Apps/LandingSite
run: pnpm install --frozen-lockfile
- name: Fetch release manifest
working-directory: Apps/LandingSite
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm run fetch-release
- name: Build static site
working-directory: Apps/LandingSite
run: pnpm run build
- name: Validate HTML
working-directory: Apps/LandingSite
run: pnpm run validate
- name: Run Lighthouse CI
working-directory: Apps/LandingSite
run: pnpm run lighthouse
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: Apps/LandingSite/dist
deploy:
name: Deploy
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
```
**Trigger conditions**:
1. Push to `main` that modifies `Apps/LandingSite/**`
2. GitHub Release publication (triggers manifest regeneration)
3. Manual dispatch for ad-hoc deploys
### 7.4 Acceptance Criteria → PRD Traceability
| FR | Requirement | How Addressed |
|----|-------------|---------------|
| FR-01 | Release metadata (version, channel, date, asset links) | `ReleaseManifest` schema (§3.1); Hero + Footer render from manifest; build-time fetch |
| FR-02 | Channel-aware UI (prerelease badge, warning, Gatekeeper help) | `ChannelBadge` component; release state machine (§1.3); SafetySection Gatekeeper guide |
| FR-03 | Bilingual support (EN + ZH, stable URLs) | Path-based i18n (`/en/`, `/zh/`); `LandingCopy` schema (§3.2); `hreflang` tags (A-07) |
| FR-04 | Download path clarity (where, which file, prerelease, Gatekeeper) | Hero CTA links to correct asset; ChannelBadge shows state; SafetySection explains Gatekeeper |
| FR-05 | Trust links (GitHub, releases, changelog, security, license) | OpenSourceSection + FooterSection; links derived from constants |
| FR-06 | Optional beta email capture | Deferred to 3rd-party form endpoint slot in FooterSection; no custom backend |
| FR-07 | Responsive behavior (desktop, tablet, mobile) | Responsive strategy (§6.6); breakpoints at 640/860/1080px; no CTA hidden in accordion |
### 7.5 User Story → Verification
| Story | Test |
|-------|------|
| US-01: Understand Atlas in one screen | Lighthouse "First Meaningful Paint" check; manual review that headline + subheadline + screenshot are above the fold on 1280×720 viewport |
| US-02: Safety and trust signals | Automated check: TrustStrip rendered; SafetySection contains "recovery", "permissions", "Gatekeeper" keywords; OpenSourceSection links to GitHub |
| US-03: Developer cleanup awareness | DeveloperSection renders with >= 4 concrete developer artifact types (Xcode derived data, simulators, package caches, build artifacts) |
| US-04: Download without GitHub navigation | Hero CTA `href` matches `release-manifest.json` asset URL; version and date rendered next to CTA |
| US-05: Honest prerelease disclosure | When `channel === "prerelease"`: ChannelBadge shows amber "Prerelease" badge; warning text visible; Gatekeeper "Open Anyway" steps visible |
### 7.6 Pre-Deploy Checklist
- [ ] `pnpm run build` succeeds with zero warnings
- [ ] `release-manifest.json` is valid and matches latest GitHub Release
- [ ] Both `/en/` and `/zh/` render without missing translation keys
- [ ] Lighthouse scores >= 95 on all four categories for both locales
- [ ] All images have alt text; screenshots have descriptive captions
- [ ] `hreflang` tags present and correct on both locale pages
- [ ] Open Graph meta tags render correct title, description, and image per locale
- [ ] No `Mole` brand references in rendered HTML
- [ ] Client JS budget < 20KB confirmed via build output
- [ ] `robots.txt` and `sitemap.xml` present and valid
- [ ] Custom domain DNS verified and HTTPS enforced
- [ ] Plausible analytics tracking confirmed on both locales
- [ ] Mobile viewport (375px) preserves CTA visibility and screenshot clarity