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>
43 KiB
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.mdDate: 2026-03-15
Table of Contents
- Product Definition
- Tech Stack Decision
- Data Architecture
- Component Architecture
- Constraint System
- Visual Design System
- 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 (4–6 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
-
No UI framework islands — Every component is a
.astrofile 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). -
Self-hosted fonts — Font files are committed to
Apps/LandingSite/public/fonts/. Subset to Latin + CJK ranges. Usefont-display: swapfor all faces. -
Build-time release manifest — A
scripts/fetch-release.tsscript runs at build time to query the GitHub Releases API and emitsrc/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
/**
* 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):
- Build-time generated
release-manifest.jsonviascripts/fetch-release.ts - Static fallback
src/data/release-fallback.json(committed, manually maintained) - 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
/**
* 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.
/* ══════════════════════════════════════════════════════
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 (8–24px 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:
/* Block */
.hero { ... }
/* Element */
.hero__headline { ... }
.hero__cta { ... }
/* Modifier */
.hero__cta--primary { ... }
.hero__cta--secondary { ... }
Card pattern (maps from AtlasCardModifier):
.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):
.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):
.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 |
| 640–860px (tablet) | Two-column feature grid; hero screenshot beside text; nav items visible |
| 860–1080px (small desktop) | Three-column feature grid; full nav; gallery grid |
| > 1080px (desktop) | Max content width 1080px centered; generous margins |
6.7 Font Loading Strategy
@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>:
<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
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:
- Push to
mainthat modifiesApps/LandingSite/** - GitHub Release publication (triggers manifest regeneration)
- 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 buildsucceeds with zero warningsrelease-manifest.jsonis 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
hreflangtags present and correct on both locale pages- Open Graph meta tags render correct title, description, and image per locale
- No
Molebrand references in rendered HTML - Client JS budget < 20KB confirmed via build output
robots.txtandsitemap.xmlpresent 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