diff --git a/apps/marketing/MARKETING_IMPROVEMENTS.md b/apps/marketing/MARKETING_IMPROVEMENTS.md new file mode 100644 index 000000000..fdd722d4c --- /dev/null +++ b/apps/marketing/MARKETING_IMPROVEMENTS.md @@ -0,0 +1,1009 @@ +# Marketing Page Improvement Recommendations + +Comprehensive audit and recommendations to bring the OK Code marketing site to production-level quality comparable to OpenAI, Vercel, and Linear marketing pages. + +--- + +## Table of Contents + +1. [Critical Issues](#1-critical-issues) +2. [Accessibility](#2-accessibility) +3. [Visual Design & Polish](#3-visual-design--polish) +4. [Animation & Interaction](#4-animation--interaction) +5. [Layout & Spacing](#5-layout--spacing) +6. [Responsive Design](#6-responsive-design) +7. [Performance](#7-performance) +8. [SEO & Meta](#8-seo--meta) +9. [Content & Copy](#9-content--copy) +10. [Architecture & Code Quality](#10-architecture--code-quality) +11. [New Sections & Features](#11-new-sections--features) +12. [Reference Benchmarks](#12-reference-benchmarks) + +--- + +## 1. Critical Issues + +These must be fixed before considering the page production-ready. + +### 1.1 Missing Focus Indicators (WCAG 2.1 AA Failure) + +**Problem:** Zero `:focus-visible` styles exist on any interactive element -- buttons, links, tabs, FAQ triggers, nav links, or form elements. This is a WCAG 2.1 Level AA violation and makes the site unusable for keyboard-only users. + +**Affected elements:** + +- `.hero-button`, `.secondary-button` +- `.kn-nav-link`, `.kn-nav-signin` +- `.tab-chip`, `.spotlight-item` +- `.faq-trigger` +- `.kn-pill` (hero badge link) +- All footer links +- Logo cloud items (if made focusable) + +**Fix:** Add a global focus-visible rule in Layout.astro: + +```css +:focus-visible { + outline: 2px solid var(--kn-landing-accent-bright); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +/* Subtle ring for buttons/interactive elements */ +.hero-button:focus-visible, +.secondary-button:focus-visible, +.tab-chip:focus-visible, +.spotlight-item:focus-visible, +.faq-trigger:focus-visible { + outline: 2px solid var(--kn-landing-accent-bright); + outline-offset: 2px; + box-shadow: 0 0 0 4px rgba(255, 78, 65, 0.15); +} +``` + +### 1.2 Duplicate / Conflicting Style Definitions + +**Problem:** Animation and component styles are defined in both `Layout.astro` and `index.astro` with conflicting values. Whichever loads last wins, creating unpredictable behavior. + +**Specific conflicts:** +| Style | Layout.astro | index.astro | Delta | +|-------|-------------|-------------|-------| +| `[data-reveal]` transition | `0.8s` | `0.7s` | 0.1s mismatch | +| `.hero-button` styles | Full definition | Full redefinition | Competing specificity | +| `.hero-button-shine` | Defined | Redefined | Duplicate | +| `.section-title` | Defined | Redefined | Duplicate | +| Card shared styles | Defined in Layout | Overridden in index | Cascading conflicts | + +**Fix:** Establish a single source of truth: + +- Layout.astro owns: CSS variables, base resets, nav, footer, card base, reveal system, utility classes +- index.astro owns: page-specific layout (hero layout, grid arrangements, section spacing) +- Components own: their scoped hover/interaction styles + +### 1.3 Hardcoded FAQ Max-Height + +**Problem:** The first FAQ panel has `style="max-height: 220px"` hardcoded inline. If content exceeds 220px (e.g., longer answers, larger font size, narrow viewport), it will be clipped with no scroll. + +**Fix:** Use `scrollHeight` calculation on mount, or switch to CSS `grid-template-rows: 0fr / 1fr` animation pattern which naturally sizes to content: + +```css +.faq-panel { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.35s ease; +} +.faq-item[data-open="true"] .faq-panel { + grid-template-rows: 1fr; +} +.faq-panel > div { + overflow: hidden; +} +``` + +--- + +## 2. Accessibility + +### 2.1 Mobile Menu Focus Trap + +**Problem:** When the mobile hamburger menu opens, keyboard focus is not trapped inside the overlay. Users can Tab into content behind the menu. + +**Fix:** Implement a focus trap that cycles between the first and last focusable element in the menu. Close on Escape (already implemented) and restore focus to the trigger button on close. + +### 2.2 Icon-Only Elements Missing Labels + +**Problem:** Several SVG icons serve as the sole content of interactive or informational elements but lack `aria-label`: + +- Trust strip icons (decorative only -- acceptable since text follows) +- Logo cloud items -- SVGs have no accessible name +- Mobile hamburger button -- check that `aria-label="Menu"` is present +- Feature grid icons -- decorative, but `kn-ico-wrap` has no role + +**Fix for LogoCloud:** + +```html + +``` + +### 2.3 Color-Only Differentiation + +**Problem:** Feature grid cards use different icon colors (red, cyan, yellow) as the only visual differentiator between cards. This fails WCAG 1.4.1 (Use of Color). + +**Fix:** The eyebrow text already differentiates semantically. Add a subtle visual indicator beyond color -- e.g., different icon backgrounds or border accents that also vary in lightness/shape. + +### 2.4 Stats Bar Semantic Structure + +**Problem:** Stats values/labels use plain `` elements with no semantic meaning. Screen readers won't understand the relationship between "100%" and "Open Source". + +**Fix:** Use a description list: + +```html +
+
+
Open Source
+
100%
+
+
+``` + +### 2.5 Reduced Motion Coverage + +**Problem:** `prefers-reduced-motion` rules exist in Layout.astro and index.astro but don't cover: + +- Background.astro mesh orb animations +- Hero mesh blob floating +- Final CTA orb animations +- Button shimmer effects +- Star twinkling (partially covered) + +**Fix:** Add a global catch-all in Layout.astro and specific overrides in components: + +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} +``` + +--- + +## 3. Visual Design & Polish + +### 3.1 Add a Video or Animated Product Demo + +**What OpenAI/Vercel do:** Both feature prominent hero-area product demos -- either a looping video, an interactive demo, or an animated screenshot walkthrough. The current OK Code hero has text + icon only with no product visual. + +**Recommendation:** Add an autoplay muted video or animated sequence below the hero tagline showing the actual product: + +- A 10-15 second loop showing: opening a thread, seeing a diff, previewing, and reviewing +- Wrapped in the existing `glass-stage` chrome (already styled in index.astro but unused) +- Lazy-loaded below the fold with a poster frame +- Fallback to a static screenshot for `prefers-reduced-motion` + +### 3.2 Typography Refinement + +**Current gaps vs. premium benchmarks:** + +- Tagline size maxes at `6.2rem` -- Vercel uses `5.5rem` but with tighter tracking and bolder weight. Consider testing `font-weight: 700` (currently not set, inherits `400`) +- Section titles at `clamp(2rem, 3vw, 3.15rem)` -- could push to `clamp(2.2rem, 3.5vw, 3.75rem)` for more dramatic hierarchy +- Body text line-height at `1.7` is generous -- OpenAI uses `1.6`. Test reducing slightly for density +- Missing font-weight variation: headings don't declare `font-weight` explicitly, relying on `` or browser defaults + +**Recommendations:** + +```css +.tagline { + font-weight: 700; +} +.section-title { + font-weight: 650; + font-size: clamp(2.2rem, 3.5vw, 3.75rem); +} +.section-body { + line-height: 1.6; +} +``` + +### 3.3 Card Glass Effect Refinement + +**Current:** Cards use `backdrop-filter: blur(18px)` uniformly. Premium sites vary the glass intensity by section importance. + +**Recommendation:** + +- Hero card/CTA: `blur(24px) saturate(180%)` +- Feature/workflow cards: `blur(12px) saturate(140%)` +- FAQ/minor cards: `blur(8px)` +- Add `box-shadow: inset 0 0 0 1px rgba(255,255,255,0.03)` for an inner light edge + +### 3.4 Gradient Border Effects on Key Cards + +**What Linear/Vercel do:** Animated gradient borders on featured cards using `@property` for animatable CSS custom properties. + +**Recommendation:** Add gradient borders to the hero mark, feature cards, and CTA section: + +```css +@property --border-angle { + syntax: ""; + initial-value: 0deg; + inherits: false; +} + +.feature-card { + border: 1px solid transparent; + background-origin: border-box; + background-clip: padding-box, border-box; + background-image: + linear-gradient(var(--kn-landing-card-bg), var(--kn-landing-card-bg)), + conic-gradient( + from var(--border-angle), + transparent 60%, + var(--kn-landing-accent) 80%, + transparent 100% + ); + animation: rotate-border 6s linear infinite; +} + +@keyframes rotate-border { + to { + --border-angle: 360deg; + } +} +``` + +### 3.5 Noise Texture Refinement + +**Current:** Single noise overlay at `0.03` opacity. Barely visible. + +**Recommendation:** Add a second finer-grain noise layer with `mix-blend-mode: overlay` for more natural integration: + +```css +body::after { + /* existing noise */ + mix-blend-mode: soft-light; + opacity: 0.04; +} +body::before { + /* second layer: finer grain */ + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + opacity: 0.02; + background-image: url("data:image/svg+xml,..."); /* higher baseFrequency */ + mix-blend-mode: overlay; + z-index: 1; +} +``` + +### 3.6 Button Polish + +**Current gaps:** + +- Primary button uses accent red gradient but lacks "depth" compared to Vercel's buttons +- No active/pressed state (`:active`) +- Shimmer runs continuously -- should only trigger on hover or on a slow interval + +**Recommendations:** + +```css +.hero-button:active { + transform: translateY(0) scale(0.98); + box-shadow: 0 6px 24px rgba(202, 58, 41, 0.15); +} + +/* Shimmer only on hover, not continuous */ +.hero-button-shimmer { + animation: none; +} +.hero-button:hover .hero-button-shimmer { + animation: shimmer 0.6s ease forwards; +} +``` + +--- + +## 4. Animation & Interaction + +### 4.1 Scroll-Linked Progress Indicator + +**What Vercel does:** A thin progress bar at the very top of the viewport that fills as users scroll. + +**Implementation:** + +```css +.scroll-progress { + position: fixed; + top: 0; + left: 0; + height: 2px; + background: linear-gradient(90deg, var(--kn-landing-accent), var(--kn-landing-accent-bright)); + z-index: 100; + transform-origin: left; + transition: none; +} +``` + +```js +window.addEventListener( + "scroll", + () => { + const pct = window.scrollY / (document.body.scrollHeight - window.innerHeight); + bar.style.transform = `scaleX(${pct})`; + }, + { passive: true }, +); +``` + +### 4.2 Section Entrance Animations Should Be More Varied + +**Current:** Every section uses the same `translateY(16px) + opacity` fade-in-up. This becomes monotonous. + +**Recommendation:** Vary the entrance animation by section type: + +- Hero: `scale(0.96)` + fade (expand in) +- Trust strip: Stagger left-to-right with `translateX(-20px)` per item +- Feature grid: Each card fades from its own direction (left, bottom, right) +- Workflow: Sequential slide-in from left +- Spotlight: Split entrance -- copy slides from left, panels from right +- Theme: Tabs fade in then preview scales up +- FAQ: Gentle fade only (no transform) since it's a reading section +- CTA: `scale(0.98)` + fade (subtle zoom) + +### 4.3 Parallax-Light Effect on Hero + +**What OpenAI does:** Subtle parallax on background elements as the user scrolls the hero section. + +**Recommendation:** Apply `transform: translateY(calc(var(--scroll) * -0.3))` to the hero mesh blobs, driven by scroll position. Use `requestAnimationFrame` for performance: + +```js +let ticking = false; +window.addEventListener( + "scroll", + () => { + if (!ticking) { + requestAnimationFrame(() => { + document.documentElement.style.setProperty("--scroll", window.scrollY); + ticking = false; + }); + ticking = true; + } + }, + { passive: true }, +); +``` + +### 4.4 Cursor-Follow Glow Effect + +**What Linear does:** A subtle glow that follows the mouse cursor on card hover. + +**Recommendation:** Add to feature cards and workflow cards: + +```js +card.addEventListener("mousemove", (e) => { + const rect = card.getBoundingClientRect(); + card.style.setProperty("--mouse-x", `${e.clientX - rect.left}px`); + card.style.setProperty("--mouse-y", `${e.clientY - rect.top}px`); +}); +``` + +```css +.feature-card::after { + content: ""; + position: absolute; + inset: 0; + opacity: 0; + transition: opacity 0.3s; + background: radial-gradient( + 400px circle at var(--mouse-x) var(--mouse-y), + rgba(255, 255, 255, 0.04), + transparent 40% + ); +} +.feature-card:hover::after { + opacity: 1; +} +``` + +### 4.5 Tab Transition Enhancement + +**Current:** Tab panels use a simple `opacity + translateY(6px)` crossfade. Feels basic. + +**Recommendation:** Add a subtle scale and blur transition: + +```css +[data-tab-panel] { + opacity: 0; + transform: translateY(8px) scale(0.98); + filter: blur(2px); + transition: + opacity 0.4s ease, + transform 0.4s ease, + filter 0.4s ease; +} +[data-tab-panel][data-active="true"] { + opacity: 1; + transform: translateY(0) scale(1); + filter: blur(0); +} +``` + +### 4.6 Nav Link Hover Underline + +**What Vercel/Linear do:** Nav links get a subtle animated underline on hover. + +```css +.kn-nav-link::after { + content: ""; + position: absolute; + bottom: -4px; + left: 0; + width: 0; + height: 1px; + background: var(--kn-landing-accent); + transition: width 0.3s ease; +} +.kn-nav-link:hover::after { + width: 100%; +} +``` + +--- + +## 5. Layout & Spacing + +### 5.1 Increase Section Vertical Spacing + +**Current:** Most sections use `4rem` margin-bottom (some now upgraded to `var(--kn-landing-section-gap)`). + +**Recommendation:** Ensure ALL sections use the CSS variable, and increase the clamp: + +```css +--kn-landing-section-gap: clamp(6rem, 10vw, 10rem); +``` + +Premium sites like Vercel use 120-160px between major sections. Current 4rem = 64px is too tight. + +### 5.2 Content Width Constraints + +**Current:** `--marketing-content-max: 1680px` -- this is very wide. On ultra-wide monitors, text lines become too long for comfortable reading. + +**Recommendation:** + +- Max content width: `1280px` (matches Vercel) +- Text content max: `680px` for paragraphs (optimal reading width) +- Full-bleed sections: background extends to edges, content stays constrained + +```css +--marketing-content-max: 1280px; +--marketing-text-max: 680px; + +.section-body, +.subtagline, +.final-cta p { + max-width: var(--marketing-text-max); +} +``` + +### 5.3 Card Padding Increase + +**Current:** Cards use `1.35rem` padding. This feels cramped compared to premium sites. + +**Recommendation:** Increase to `1.75rem` on desktop, `1.35rem` on mobile: + +```css +.feature-card, +.workflow-card, +.spotlight-panel { + padding: clamp(1.35rem, 2vw, 1.75rem); +} +``` + +### 5.4 Hero Vertical Rhythm + +**Current:** Hero padding-top is `clamp(3rem, 8vw, 6rem)` and margin-bottom is `clamp(4rem, 8vw, 7rem)`. + +**Recommendation:** Push further for dramatic impact: + +```css +.hero { + padding-top: clamp(4rem, 12vw, 8rem); + margin-bottom: clamp(5rem, 10vw, 9rem); +} +``` + +--- + +## 6. Responsive Design + +### 6.1 Standardize Breakpoints + +**Current inconsistency:** + +- Layout.astro: `720px`, `480px` +- index.astro: `1100px`, `720px` +- download.astro: `640px` + +**Recommendation:** Adopt a consistent breakpoint system: + +```css +/* Breakpoints (add as CSS custom media when Astro supports it) */ +/* sm: 480px -- small mobile */ +/* md: 768px -- tablet portrait */ +/* lg: 1024px -- tablet landscape / small desktop */ +/* xl: 1280px -- desktop */ +/* 2xl: 1536px -- large desktop */ +``` + +### 6.2 Tablet-Specific Layouts + +**Problem:** The site jumps from desktop (3-4 columns) directly to mobile (1 column) at 1100px with no intermediate state. Tablets get a broken layout. + +**Recommendation:** + +```css +/* Tablet: 2-column layouts */ +@media (max-width: 1024px) and (min-width: 769px) { + .feature-grid { + grid-template-columns: repeat(2, 1fr); + } + .workflow-grid { + grid-template-columns: repeat(2, 1fr); + } + .spotlight, + .theme-section { + grid-template-columns: 1fr; + } +} +``` + +### 6.3 Mobile Typography Scale + +**Problem:** Large headings on mobile could be tighter. The `clamp()` functions work but haven't been tuned for the smallest screens. + +**Recommendation:** + +```css +@media (max-width: 480px) { + .tagline { + font-size: 2.6rem; + letter-spacing: -0.05em; + } + .section-title { + font-size: 1.75rem; + } + .final-cta h2 { + font-size: 1.85rem; + } +} +``` + +### 6.4 Touch Target Sizes + +**Problem:** Some interactive elements may be smaller than the 44x44px minimum recommended touch target. + +**Recommendation:** Audit and ensure: + +- Nav links: `min-height: 44px; padding: 0.75rem 1rem;` +- Tab chips: `min-height: 44px;` +- FAQ triggers: Already 44px+ (good) +- Footer links: Add padding for touch + +--- + +## 7. Performance + +### 7.1 Background SVG Optimization + +**Problem:** Background.astro renders 220 SVG circles + 13 path elements in a fixed-position container that scrolls with the page. This causes paint thrashing on every scroll event. + +**Recommendations:** + +- Add `will-change: transform` to `.kn-stars` and `.kn-threads` +- Use `contain: strict` on the background container +- Consider converting star field to a static PNG/WebP with CSS animation for the twinkle subset +- Reduce star count to 150 (many are invisible at `opacity: 0.12`) + +### 7.2 Image Optimization + +**Problem:** `icon.png` is loaded unoptimized in multiple sizes (88x88 hero, 28x28 nav, 14x14 footer) without srcset, lazy loading, or format optimization. + +**Recommendation:** + +- Install `@astrojs/image` or use Astro's built-in `` component +- Generate WebP/AVIF variants +- Add `loading="lazy"` to all images below the fold +- Use `fetchpriority="high"` on the hero icon +- Create properly sized variants (no serving 88px image at 14px) + +### 7.3 Font Loading Strategy + +**Problem:** DM Sans Variable is imported but no font-display strategy is set. This may cause FOIT (Flash of Invisible Text). + +**Recommendation:** + +```css +@font-face { + font-family: "DM Sans Variable"; + font-display: swap; + /* ... */ +} +``` + +Or preload the font in the ``: + +```html + +``` + +### 7.4 CSS Containment + +**Current:** `content-visibility: auto` applied to some below-fold sections but not consistently. + +**Recommendation:** Apply to all below-fold sections and add proper `contain-intrinsic-size`: + +```css +.trust-strip, +.feature-grid, +.workflow-section, +.spotlight, +.theme-section, +.faq-section, +.final-cta, +.logo-cloud, +.stats-bar { + content-visibility: auto; + contain-intrinsic-block-size: auto 500px; +} +``` + +### 7.5 Reduce Animation Overhead + +**Problem:** Multiple `blur(60-100px)` filters on animated elements (mesh orbs, hero blobs, CTA orbs) are GPU-intensive and may cause frame drops on low-end devices. + +**Recommendation:** + +- Use `@media (prefers-reduced-motion: no-preference)` to only show blobs when motion is OK +- Reduce blur radius: 100px -> 60px on background mesh, 80px -> 50px on hero blobs +- Add `will-change: transform` to animated blob elements +- Consider using CSS `background-image` radial gradients instead of blurred elements + +--- + +## 8. SEO & Meta + +### 8.1 Structured Data (JSON-LD) + +**Problem:** No structured data markup. Search engines can't understand the product type or organization. + +**Recommendation:** Add to Layout.astro ``: + +```html + +``` + +### 8.2 Open Graph Enhancements + +**Current:** OG image defaults to `/icon.png` (a small app icon). Not ideal for social sharing. + +**Recommendation:** + +- Create a dedicated 1200x630px OG image showing the product UI +- Add `og:site_name`, `og:locale` +- Add Twitter-specific card size (`twitter:card: summary_large_image` -- already present, good) + +### 8.3 Sitemap + +**Problem:** No sitemap generated. + +**Recommendation:** Install `@astrojs/sitemap`: + +```js +// astro.config.mjs +import sitemap from "@astrojs/sitemap"; +export default defineConfig({ + integrations: [sitemap()], + site: process.env.PUBLIC_SITE_URL, +}); +``` + +### 8.4 Canonical URLs + +**Current:** Canonical URL only set when `Astro.site` is available (requires `PUBLIC_SITE_URL` env var). In development, no canonical is set. + +**Recommendation:** Always set a canonical, even in dev: + +```js +const canonical = Astro.site + ? new URL(Astro.url.pathname, Astro.site).href + : `https://okcode.dev${Astro.url.pathname}`; +``` + +### 8.5 Heading Hierarchy + +**Problem:** Multiple pages may have inconsistent heading levels since components define their own `

`, `

` tags independently. + +**Recommendation:** Audit heading hierarchy per page: + +- `

`: Only the hero tagline (one per page) +- `

`: Section titles (Features, Workflow, Surfaces, Themes, FAQ, CTA) +- `

`: Within sections (feature card titles, workflow step titles, FAQ questions) + +--- + +## 9. Content & Copy + +### 9.1 Hero Tagline A/B Testing Opportunities + +**Current:** "The beautiful workspace for shipping with AI." + +**Alternative test candidates:** + +- "Ship with AI. Stay in flow." (shorter, more action-oriented) +- "One workspace for the entire AI coding loop." (benefit-focused) +- "Chat. Diff. Preview. Ship." (feature-list cadence, like Vercel's style) + +### 9.2 Social Proof + +**Current:** LogoCloud shows "Works with the tools you already use" with generic tool icons. This is weak social proof compared to what premium sites show. + +**Stronger alternatives:** + +- GitHub star count (dynamic, auto-updating) +- "Used by X developers" (if metrics available) +- Testimonial quotes from real users +- "Featured in" press/blog logos +- Community size (Discord member count) + +### 9.3 Comparison Section + +**What competitors show:** A "Why OK Code vs. X" comparison table is a high-conversion element. + +**Recommendation:** Add a comparison section after the workflow section: + +``` +| Feature | OK Code | Terminal AI | IDE Plugins | +|---------------------|---------|-------------|-------------| +| Persistent threads | Yes | No | Partial | +| Built-in diffs | Yes | No | No | +| Local preview | Yes | No | Partial | +| PR review surface | Yes | No | No | +| Open source | Yes | Varies | No | +``` + +### 9.4 CTA Copy Refinement + +**Current CTAs:** "Download OK Code" / "View on GitHub" / "Explore on GitHub" + +**Recommendations:** + +- Primary: "Get OK Code Free" (emphasizes free, reduces friction) +- Secondary: "Star on GitHub" (more specific action) +- Bottom CTA: "Start shipping calmer" (emotional, benefit-oriented) + +### 9.5 FAQ Expansion + +**Current:** 4 FAQ items. Premium sites typically show 6-10. + +**Suggested additions:** + +- "How does it compare to Cursor / Windsurf / Copilot?" +- "What AI models does it support?" +- "Can I self-host it?" +- "Is my code sent to a server?" +- "How does the desktop app work offline?" +- "What's on the roadmap?" + +--- + +## 10. Architecture & Code Quality + +### 10.1 Style Architecture Consolidation + +**Problem:** Styles are split across Layout.astro (global), index.astro (global via `:global()`), and component scoped ` diff --git a/apps/marketing/src/components/FaqSection.astro b/apps/marketing/src/components/FaqSection.astro index 2699d5ae2..7aad77b0e 100644 --- a/apps/marketing/src/components/FaqSection.astro +++ b/apps/marketing/src/components/FaqSection.astro @@ -24,8 +24,13 @@ const { faqs } = Astro.props; aria-controls={`${id}-panel`} data-faq-trigger > - {item.question} - + + {String(index + 1).padStart(2, '0')} + {item.question} + +
+ + diff --git a/apps/marketing/src/components/FeatureGrid.astro b/apps/marketing/src/components/FeatureGrid.astro index a67cccde7..90b0345a3 100644 --- a/apps/marketing/src/components/FeatureGrid.astro +++ b/apps/marketing/src/components/FeatureGrid.astro @@ -11,7 +11,7 @@ const icons = [ ] as const; --- -
+
{pillars.map((pillar, index) => (
@@ -25,3 +25,37 @@ const icons = [
))}
+ + diff --git a/apps/marketing/src/components/FinalCta.astro b/apps/marketing/src/components/FinalCta.astro index 752dd9fc7..d99bae0c4 100644 --- a/apps/marketing/src/components/FinalCta.astro +++ b/apps/marketing/src/components/FinalCta.astro @@ -5,24 +5,91 @@ ---
-

Ready

-

Stop building through tab chaos.

-

- Bring chat, code review, preview, and release flow into one calm workspace built for serious - AI-assisted work. -

-
- - - Download OK Code - - - Explore on GitHub - + +
+

Ready

+

Stop building through tab chaos.

+

+ Bring chat, code review, preview, and release flow into one calm workspace built for serious + AI-assisted work. +

+
+ + diff --git a/apps/marketing/src/components/Hero.astro b/apps/marketing/src/components/Hero.astro index 3af5d136a..3e6ae0b3f 100644 --- a/apps/marketing/src/components/Hero.astro +++ b/apps/marketing/src/components/Hero.astro @@ -5,6 +5,12 @@ ---
+ +
-

The beautiful workspace for shipping with AI.

+

The beautiful workspace for shipping with AI.

Chat, diffs, preview, PR review, and release actions in one glassy desktop + web @@ -31,6 +37,7 @@

+ Download OK Code @@ -55,4 +62,68 @@
+ + + + diff --git a/apps/marketing/src/components/LogoCloud.astro b/apps/marketing/src/components/LogoCloud.astro new file mode 100644 index 000000000..b74f2caa4 --- /dev/null +++ b/apps/marketing/src/components/LogoCloud.astro @@ -0,0 +1,96 @@ +--- +/** + * Logo cloud — social proof showing compatible tools and integrations. + */ +--- + +
+

Works with the tools you already use

+
+
+ + GitHub +
+
+ + VS Code +
+
+ + Claude +
+
+ + OpenAI +
+
+ + Vercel +
+
+ + Terminal +
+
+
+ + diff --git a/apps/marketing/src/components/StatsBar.astro b/apps/marketing/src/components/StatsBar.astro new file mode 100644 index 000000000..fc4357fef --- /dev/null +++ b/apps/marketing/src/components/StatsBar.astro @@ -0,0 +1,89 @@ +--- +/** + * Stats bar — metrics section for visual credibility. + */ +interface Props { + metrics: ReadonlyArray<{ value: string; label: string }>; +} + +const { metrics } = Astro.props; +--- + +
+ {metrics.map((metric) => ( +
+ {metric.value} + {metric.label} +
+ ))} +
+ + diff --git a/apps/marketing/src/components/TrustStrip.astro b/apps/marketing/src/components/TrustStrip.astro index 55fcb6f7a..ed11e8227 100644 --- a/apps/marketing/src/components/TrustStrip.astro +++ b/apps/marketing/src/components/TrustStrip.astro @@ -2,19 +2,75 @@ /** * Trust strip — three trust signals below the hero. */ +const items = [ + { + icon: ``, + label: "Made for builders", + text: "Designed around real AI coding workflows", + }, + { + icon: ``, + label: "Calm by default", + text: "Less context switching, more legible work", + }, + { + icon: ``, + label: "Close to the metal", + text: "Open source, desktop-aware, release-friendly", + }, +]; --- -
-
- Made for builders - Designed around real AI coding workflows -
-
- Calm by default - Less context switching, more legible work -
-
- Close to the metal - Open source, desktop-aware, release-friendly -
+
+ {items.map((item) => ( +
+ + {item.label} + {item.text} +
+ ))}
+ + diff --git a/apps/marketing/src/components/WorkflowSection.astro b/apps/marketing/src/components/WorkflowSection.astro index ece7d88f5..d4ece7d81 100644 --- a/apps/marketing/src/components/WorkflowSection.astro +++ b/apps/marketing/src/components/WorkflowSection.astro @@ -16,13 +16,59 @@ const { steps } = Astro.props;

-
- {steps.map((item) => ( +
+ {steps.map((item, index) => (
+ {item.step}

{item.title}

{item.body}

+ {index < steps.length - 1 &&
))}
+ + diff --git a/apps/marketing/src/layouts/Layout.astro b/apps/marketing/src/layouts/Layout.astro index debfbb4d1..562ed7d47 100644 --- a/apps/marketing/src/layouts/Layout.astro +++ b/apps/marketing/src/layouts/Layout.astro @@ -52,6 +52,12 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; OK Code + + @@ -126,6 +241,7 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; --kn-landing-border: rgba(192, 200, 212, 0.08); --kn-landing-border-hover: rgba(192, 200, 212, 0.16); --kn-landing-card-bg: rgba(192, 200, 212, 0.02); + --kn-landing-card-bg-hover: rgba(192, 200, 212, 0.04); --kn-landing-glow-1: rgba(192, 200, 212, 0.07); --kn-landing-glow-2: rgba(202, 58, 41, 0.06); --kn-landing-star-color: #c0c8d4; @@ -140,6 +256,9 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; --kn-landing-footer-link-hover: #ff4e41; --kn-landing-ico-bg: rgba(192, 200, 212, 0.04); --kn-landing-status-green: #00d4aa; + --kn-landing-section-gap: clamp(5rem, 8vw, 8rem); + --kn-landing-ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); + --kn-landing-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); } * { @@ -151,6 +270,7 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; html { color-scheme: dark; scroll-behavior: smooth; + scroll-padding-top: 5rem; } body { @@ -168,7 +288,7 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; position: fixed; inset: 0; pointer-events: none; - opacity: 0.05; + opacity: 0.03; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); z-index: 1; } @@ -224,6 +344,41 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; min-width: 0; } + /* ── Keyframe Animations ── */ + + @keyframes kn-star-twinkle { + 0%, 100% { opacity: 0.12; } + 50% { opacity: 0.6; } + } + + @keyframes gradient-shift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } + } + + @keyframes shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } + } + + @keyframes pulse-glow { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 1; } + } + + @keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-12px); } + } + + @keyframes fade-in-up { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } + } + + /* ── Header ── */ + .site-header { position: sticky; top: 0; @@ -264,6 +419,44 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; box-shadow: 0 0 18px rgba(167, 139, 250, 0.12); } + /* ── Mobile hamburger toggle ── */ + + .kn-nav-toggle { + display: none; + flex-direction: column; + justify-content: center; + gap: 5px; + width: 36px; + height: 36px; + padding: 6px; + background: none; + border: none; + cursor: pointer; + z-index: 25; + } + + .kn-nav-toggle-bar { + display: block; + width: 100%; + height: 2px; + border-radius: 2px; + background: var(--kn-landing-text-secondary); + transition: transform 0.3s var(--kn-landing-ease-out-expo), opacity 0.3s ease; + transform-origin: center; + } + + .kn-nav-toggle.is-open .kn-nav-toggle-bar:nth-child(1) { + transform: translateY(7px) rotate(45deg); + } + .kn-nav-toggle.is-open .kn-nav-toggle-bar:nth-child(2) { + opacity: 0; + } + .kn-nav-toggle.is-open .kn-nav-toggle-bar:nth-child(3) { + transform: translateY(-7px) rotate(-45deg); + } + + /* ── Nav Links ── */ + .kn-nav-links { display: flex; align-items: center; @@ -272,6 +465,7 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; } .kn-nav-link { + position: relative; color: var(--kn-landing-nav-link); font-size: 0.84rem; transition: color 0.2s ease; @@ -281,6 +475,22 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; color: var(--kn-landing-text-secondary); } + .kn-nav-link.is-active { + color: var(--kn-landing-accent-bright); + } + + .kn-nav-link.is-active::after { + content: ""; + position: absolute; + bottom: -6px; + left: 50%; + transform: translateX(-50%); + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--kn-landing-accent-bright); + } + .kn-nav-signin, .hero-button, .secondary-button, @@ -322,6 +532,8 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; box-shadow: 0 12px 36px rgba(0, 0, 0, 0.24), 0 0 24px rgba(202, 58, 41, 0.08); } + /* ── Sections ── */ + .hero, .trust-strip, .feature-grid, @@ -353,11 +565,6 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; height: 100%; } - @keyframes kn-star-twinkle { - 0%, 100% { opacity: 0.12; } - 50% { opacity: 0.6; } - } - .kn-star-twinkle { animation: kn-star-twinkle 5s ease-in-out infinite; } @@ -377,12 +584,14 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; .kn-dot { fill: var(--kn-landing-text-secondary); opacity: 0.25; } .kn-glow { fill: var(--kn-landing-accent); opacity: 0.06; } + /* ── Reveal Animations ── */ + [data-reveal] { opacity: 0; transform: translateY(16px); transition: - opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1), - transform 0.8s cubic-bezier(0.16, 1, 0.3, 1); + opacity 0.8s var(--kn-landing-ease-out-expo), + transform 0.8s var(--kn-landing-ease-out-expo); will-change: opacity, transform; } @@ -391,12 +600,26 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; transform: translateY(0); } + [data-reveal-children] > * { + opacity: 0; + transform: translateY(16px); + transition: opacity 0.6s var(--kn-landing-ease-out-expo), transform 0.6s var(--kn-landing-ease-out-expo); + transition-delay: calc(var(--reveal-index, 0) * 80ms); + } + + [data-reveal-children].revealed > * { + opacity: 1; + transform: translateY(0); + } + + /* ── Tab Panels ── */ + [data-tab-panel] { opacity: 0; transform: translateY(8px); transition: - opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1), - transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); + opacity 0.35s var(--kn-landing-ease-out-expo), + transform 0.35s var(--kn-landing-ease-out-expo); position: absolute; inset: 0 auto auto 0; width: 100%; @@ -412,6 +635,8 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; visibility: visible; } + /* ── Card System ── */ + .kn-card, .feature-card, .workflow-card, @@ -425,12 +650,15 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; .panel, .rail, .final-cta { + position: relative; + overflow: hidden; background: var(--kn-landing-card-bg); border: 1px solid var(--kn-landing-border); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 24px 80px rgba(0, 0, 0, 0.34); backdrop-filter: blur(18px); + transition: border-color 0.3s ease, box-shadow 0.4s ease, transform 0.3s var(--kn-landing-ease-out-expo); } .kn-card:hover, @@ -441,9 +669,16 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; .theme-preview-card:hover, .faq-card:hover, .trust-item:hover { + transform: translateY(-2px); border-color: var(--kn-landing-border-hover); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.04), + 0 28px 90px rgba(0, 0, 0, 0.38), + 0 0 24px rgba(202, 58, 41, 0.06); } + /* ── Typography ── */ + .eyebrow, .hero-pills span, .tab-chip, @@ -496,6 +731,59 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; line-height: 1.7; } + /* ── Gradient Text Utility ── */ + + .gradient-text { + background: linear-gradient(135deg, var(--kn-landing-text), var(--kn-landing-accent-bright)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + /* ── Pill Badge ── */ + + .kn-pill { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.85rem 0.4rem 0.45rem; + border-radius: 999px; + border: 1px solid rgba(192, 200, 212, 0.12); + background: rgba(192, 200, 212, 0.04); + backdrop-filter: blur(18px); + font-size: 0.84rem; + color: var(--kn-landing-text-secondary); + transition: border-color 0.3s ease, background 0.3s ease; + } + + .kn-pill:hover { + border-color: rgba(192, 200, 212, 0.2); + background: rgba(192, 200, 212, 0.06); + } + + .kn-pill-badge { + padding: 0.2rem 0.55rem; + border-radius: 999px; + background: linear-gradient(135deg, var(--kn-landing-accent), var(--kn-landing-accent-bright)); + color: #fff; + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + .kn-pill-arrow { + opacity: 0.5; + transition: transform 0.2s ease, opacity 0.2s ease; + } + + .kn-pill:hover .kn-pill-arrow { + transform: translateX(2px); + opacity: 0.8; + } + + /* ── Hero Button ── */ + .hero-button { position: relative; display: inline-flex; @@ -539,14 +827,7 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; transform: rotate(24deg) translateX(1.4rem); } - .kn-footer { - position: relative; - z-index: 1; - border-top: 1px solid var(--kn-landing-border); - margin-top: 2rem; - background: linear-gradient(180deg, rgba(255,255,255,0.01), rgba(255,255,255,0.02)); - backdrop-filter: blur(18px); - } + /* ── Section Divider ── */ .section-divider { position: relative; @@ -555,7 +836,7 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; justify-content: center; gap: 1rem; width: 100%; - margin: 0 auto 4rem; + margin: 0 auto var(--kn-landing-section-gap); color: rgba(192, 200, 212, 0.34); } @@ -575,62 +856,196 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; filter: drop-shadow(0 0 16px rgba(202, 58, 41, 0.12)); } + /* ── Footer ── */ + + .kn-footer { + position: relative; + z-index: 1; + margin-top: 2rem; + background: linear-gradient(180deg, rgba(255,255,255,0.01), rgba(255,255,255,0.02)); + backdrop-filter: blur(18px); + } + + .kn-footer-gradient-line { + height: 1px; + width: 100%; + background: linear-gradient(90deg, transparent, var(--kn-landing-accent), transparent); + } + .kn-footer-inner { + padding: 4rem var(--marketing-pad-x) 2rem; + } + + .kn-footer-grid { + display: grid; + grid-template-columns: 2fr repeat(3, 1fr); + gap: 3rem; + } + + .kn-footer-brand { display: flex; - align-items: center; - justify-content: space-between; + flex-direction: column; gap: 1rem; - padding: 2rem var(--marketing-pad-x); } .kn-footer-mark { display: inline-flex; align-items: center; - gap: 0.55rem; + gap: 0.65rem; + color: var(--kn-landing-text); + font-size: 1rem; + font-weight: 600; + letter-spacing: -0.02em; + } + + .kn-footer-tagline { + font-size: 0.84rem; color: var(--kn-landing-text-muted); - font-size: 0.78rem; - font-weight: 500; + line-height: 1.6; + } + + .kn-footer-col { + display: flex; + flex-direction: column; + gap: 0.85rem; + } + + .kn-footer-heading { + text-transform: uppercase; + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.14em; + color: var(--kn-landing-text-muted); + margin-bottom: 0.25rem; } - .kn-footer-links { + .kn-footer-list { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.6rem; + } + + .kn-footer-list a { + position: relative; + display: inline-block; + font-size: 0.84rem; + color: var(--kn-landing-text-dim); + transition: color 0.2s ease; + } + + .kn-footer-list a::after { + content: ""; + position: absolute; + bottom: -1px; + left: 0; + width: 0; + height: 1px; + background: var(--kn-landing-accent-bright); + transition: width 0.3s var(--kn-landing-ease-out-expo); + } + + .kn-footer-list a:hover { + color: var(--kn-landing-text-secondary); + } + + .kn-footer-list a:hover::after { + width: 100%; + } + + .kn-footer-bottom { display: flex; align-items: center; - gap: 1rem; - flex-wrap: wrap; - font-size: 0.82rem; + justify-content: space-between; + margin-top: 3rem; + padding-top: 1.5rem; + border-top: 1px solid var(--kn-landing-border); + } + + .kn-footer-copyright { + font-size: 0.78rem; color: var(--kn-landing-text-dim); } - .kn-footer-links a { - color: var(--kn-landing-footer-link); + .kn-footer-top-link { + font-size: 0.78rem; + color: var(--kn-landing-text-muted); transition: color 0.2s ease; } - .kn-footer-links a:hover { - color: var(--kn-landing-footer-link-hover); + .kn-footer-top-link:hover { + color: var(--kn-landing-accent-bright); } + /* ── Mobile ── */ + @media (max-width: 720px) { - .kn-nav-inner, - .kn-footer-inner { + .kn-nav-toggle { + display: flex; + } + + .kn-nav-links { + position: fixed; + inset: 0; + z-index: 22; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2rem; + background: rgba(5, 5, 7, 0.96); + backdrop-filter: blur(24px); + opacity: 0; + visibility: hidden; + transition: opacity 0.3s var(--kn-landing-ease-out-expo), visibility 0.3s; + } + + .kn-nav-links.is-open { + opacity: 1; + visibility: visible; + } + + .kn-nav-links a { + font-size: 1.5rem; + opacity: 0; + transform: translateY(16px); + transition: opacity 0.4s var(--kn-landing-ease-out-expo), transform 0.4s var(--kn-landing-ease-out-expo), color 0.2s ease; + } + + .kn-nav-links.is-open a:nth-child(1) { opacity: 1; transform: translateY(0); transition-delay: 0.08s; } + .kn-nav-links.is-open a:nth-child(2) { opacity: 1; transform: translateY(0); transition-delay: 0.14s; } + .kn-nav-links.is-open a:nth-child(3) { opacity: 1; transform: translateY(0); transition-delay: 0.20s; } + .kn-nav-links.is-open a:nth-child(4) { opacity: 1; transform: translateY(0); transition-delay: 0.26s; } + + .kn-nav-inner { padding-inline: var(--marketing-pad-x); } - .kn-nav-inner, - .kn-footer-inner { + .kn-footer-grid { + grid-template-columns: 1fr 1fr; + gap: 2.5rem; + } + + .kn-footer-brand { + grid-column: 1 / -1; + } + + .kn-footer-bottom { flex-direction: column; align-items: flex-start; + gap: 0.75rem; } + } - .kn-nav-links, - .kn-footer-links { - width: 100%; + @media (max-width: 480px) { + .kn-footer-grid { + grid-template-columns: 1fr; } } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } [data-reveal], + [data-reveal-children] > *, [data-tab-panel], .hero-button, .secondary-button, @@ -638,7 +1053,9 @@ const ogImage = Astro.site ? new URL(ogImagePath, Astro.site).href : null; .spotlight-item, .faq-trigger, .hero-button-shine, - .kn-star-twinkle { + .kn-star-twinkle, + .kn-nav-toggle-bar, + .kn-nav-links a { transition: none !important; animation: none !important; transform: none !important; diff --git a/apps/marketing/src/pages/index.astro b/apps/marketing/src/pages/index.astro index 28436e5c2..958744556 100644 --- a/apps/marketing/src/pages/index.astro +++ b/apps/marketing/src/pages/index.astro @@ -1,7 +1,9 @@ --- import Layout from "../layouts/Layout.astro"; import Hero from "../components/Hero.astro"; +import LogoCloud from "../components/LogoCloud.astro"; import TrustStrip from "../components/TrustStrip.astro"; +import StatsBar from "../components/StatsBar.astro"; import FeatureGrid from "../components/FeatureGrid.astro"; import WorkflowSection from "../components/WorkflowSection.astro"; import CoreSurfaces from "../components/CoreSurfaces.astro"; @@ -9,6 +11,13 @@ import ThemeShowcase from "../components/ThemeShowcase.astro"; import FaqSection from "../components/FaqSection.astro"; import FinalCta from "../components/FinalCta.astro"; +const metrics = [ + { value: "100%", label: "Open Source" }, + { value: "5", label: "Core Surfaces" }, + { value: "3", label: "Premium Themes" }, + { value: "<1s", label: "Thread Restore" }, +] as const; + const description = "OK Code is the premium dark workspace for AI coding flows — persistent threads, diffs, preview, PR review, and release actions in one calm desktop + web product."; @@ -170,21 +179,15 @@ const faqs = [ + + -