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

43 KiB
Raw Blame History

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
  2. Tech Stack Decision
  3. Data Architecture
  4. Component Architecture
  5. Constraint System
  6. Visual Design System
  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

/**
 * 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

/**
 * 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 (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:

/* 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
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

@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:

  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