Skip to content

Website v1.5: AI-native copy revamp, new pages, and UX improvements#3086

Merged
drfarrell merged 12 commits into
mainfrom
website-v1.5
Feb 27, 2026
Merged

Website v1.5: AI-native copy revamp, new pages, and UX improvements#3086
drfarrell merged 12 commits into
mainfrom
website-v1.5

Conversation

@drfarrell
Copy link
Copy Markdown
Collaborator

@drfarrell drfarrell commented Feb 27, 2026

Summary

Major website refresh focused on AI-native discoverability, updated copy based on GTM research, and new workflow pages. This PR implements the copy revisions from COPY_REVISIONS_V1_5.md and makes all public pages optimized for AI agent discovery.

What's Changed

New Pages Created

  • /workflows - Hub page for AI tool integrations (Claude Code, Vibe Coding, Codex)
  • /workflows/claude-code - Dedicated page for Claude Code + Onlook workflow with FAQ, comparison table, and use cases
  • /workflows/vibe-coding - Team collaboration page addressing the "vibe coding has a collaboration problem" messaging
  • /site-map - Visual HTML sitemap with sticky sidebar navigation (FAQ-style layout)
  • /sitemap.ts - Dynamic XML sitemap generator for SEO
  • /features/ai-for-frontend - AI for frontend development feature page
  • /features/ai - AI features overview page
  • /features/builder - Visual builder features page
  • /features/prototype - Prototyping features page

AI-Native Optimizations

Every public page now includes:

  • JSON-LD structured data (FAQPage, SoftwareApplication, Organization schemas)
  • sr-only summary sections for AI crawlers
  • Comprehensive meta tags (OpenGraph, Twitter cards, canonical URLs)
  • FAQ sections with common questions AI agents ask

Pages updated with AI-native metadata:

  • /about - Organization schema with founders, team size
  • /faq - FAQPage schema
  • /features and all sub-pages
  • /workflows and all sub-pages
  • /site-map

Copy Revisions (from GTM research)

Homepage "...and so much more" section:

  • "Import from GitHub" → "Works With Your Codebase"
  • "Collaborate with your team" → "Built for Teams"
  • "Publish your work" → "Direct GitHub Integration"
  • "Draw-in Layers" → "Ship PRs, Not Prototypes" (NEW)
  • "Use Images and media assets" → "Reference Anything in Chat"

Features page refresh:

  • Hero: "Design with Your Real Components"
  • Updated benefits section copy (AI constrained to design system, canvas manipulation, design system guardrails)
  • New feature grid (Your Real Components, Built for Teams, Ship PRs Not Prototypes, Layer Management, Works With Your Codebase, Version History)
  • Updated FAQs

Feature block spacing fix:

  • Removed inconsistent mb-6 margins
  • Standardized to gap-6 across all feature blocks

404 Page Redesign

  • Added WebsiteLayout (header/footer for navigation)
  • Giant Onlook seal/crest as background element (90vw, positioned at -35vh)
  • Clean typography: "404" with light font weight
  • Friendly copy: "Seems like you ventured somewhere unknown on your journey. Let us help you find your way."
  • Outlined "Back to home" button
  • Smooth Framer Motion animations (content first, then seal fades in)

Other Updates

  • Routes & navigation - Added new routes to constants, updated footer links
  • Workflows page cards - Reduced title size, styled "coming soon" cards
  • Codex logo - Added white SVG logo for Codex card
  • FAQ section - Removed Storybook mention per brand guidelines
  • About page - Updated team count from 2 to 3

Files Changed

  • 50 files modified/created
  • ~3,800 lines added
  • ~330 lines removed

Test Plan

  • Verify all new pages render correctly (/workflows, /workflows/claude-code, /workflows/vibe-coding, /site-map)
  • Check 404 page displays correctly with animations
  • Validate JSON-LD structured data with Google Rich Results Test
  • Test sitemap.xml generation at /sitemap.xml
  • Verify feature block spacing is consistent on homepage
  • Check all internal links work correctly
  • Test responsive layouts on mobile

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • AI for Frontend landing plus Claude Code and Vibe Coding workflow pages; new feature-specific layouts and pages; enhanced animated 404.
  • UI / Content

    • Multiple hero, CTA, feature, and grid copy updates; new hero components and richer landing visuals; interactive collapsible FAQ rewritten.
  • Documentation / SEO

    • About/FAQ and feature pages now include structured data and hidden AI-friendly summaries.
  • Navigation

    • New site routes and updated product navigation links.
  • Bug Fixes

    • Minor preload/style injection syntax adjustment.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web-onlook Ready Ready Preview, Comment Feb 27, 2026 2:34am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs-onlook Skipped Skipped Feb 27, 2026 2:34am

Request Review

@supabase
Copy link
Copy Markdown

supabase Bot commented Feb 27, 2026

This pull request has been ignored for the connected project wowaemfasoptxrdjhilu because there are no changes detected in apps/backend/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f412b6f and 49ccd7b.

📒 Files selected for processing (2)
  • apps/web/client/src/app/workflows/claude-code/page.tsx
  • apps/web/client/src/app/workflows/vibe-coding/page.tsx

📝 Walkthrough

Walkthrough

Adds many Next.js pages and layouts (features, workflows, site‑map, about, faq), new hero components, widespread landing copy and spacing updates, a FAQDropdown refactor (FAQItem with measured heights and animations), new routes/constants and sitemap updates, and a minor preload‑script syntax tweak.

Changes

Cohort / File(s) Summary
Preload Script
apps/web/client/public/onlook-preload-script.js
Minor syntax adjustment around inlined CSS injection and the placement/termination of document.head.appendChild(r).
New Hero Components
apps/web/client/src/app/_components/hero/ai-frontend-hero.tsx, apps/web/client/src/app/_components/hero/claude-code-hero.tsx
Adds two client-side hero components with motion animations, UnicornBackground, GitHub stats hook, and external CTAs.
Hero & Landing Copy + Spacing
apps/web/client/src/app/_components/hero/..., apps/web/client/src/app/_components/landing-page/*, apps/web/client/src/app/_components/landing-page/feature-blocks/*
Widespread copy refresh across hero and landing components; many spacing tweaks (gap-4 → gap-6) and small className adjustments.
FAQ Refactor & Content
apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx, apps/web/client/src/app/_components/landing-page/faq-section.tsx, apps/web/client/src/app/faq/page.tsx
Introduces FAQItem that measures scrollHeight for animated expand/collapse; replaces default FAQ content; FAQ page adds JSON‑LD generation, smooth scrolling, and scroll‑spy navigation.
New Pages & Layouts (metadata + JSON‑LD)
apps/web/client/src/app/features/..., apps/web/client/src/app/about/..., apps/web/client/src/app/faq/..., apps/web/client/src/app/site-map/*
Adds many Next.js layouts/pages exporting metadata and inlined JSON‑LD; injects sr-only AI‑friendly summaries for accessibility.
Workflows: Layouts & Pages
apps/web/client/src/app/workflows/..., apps/web/client/src/app/workflows/claude-code/..., apps/web/client/src/app/workflows/vibe-coding/...
Adds workflows parent layout and two workflow landing pages with hero, grids, editor mockups, FAQ, motion animations, and modals.
AI‑for‑Frontend Feature
apps/web/client/src/app/features/ai-for-frontend/layout.tsx, apps/web/client/src/app/features/ai-for-frontend/page.tsx
New feature landing with hero, problem/solution, compatibility grid, FAQ, CTA, metadata, and JSON‑LD; new default-exported page component.
Sitemap & Replacement
apps/web/client/src/app/sitemap/page.tsx (deleted), apps/web/client/src/app/site-map/page.tsx, apps/web/client/src/app/sitemap.ts
Removes legacy sitemap page, adds new data-driven site‑map page and updated Next.js sitemap entries.
Footer, Navigation & Routes
apps/web/client/src/app/_components/landing-page/page-footer.tsx, apps/web/client/src/utils/constants/index.ts, apps/web/client/src/utils/constants/navigation.ts
Adds new Routes constants and product nav links (AI for Frontend, Claude Code, Vibe Coding); footer adds Workflows links and updates rel on external anchors.
404 & Misc UI
apps/web/client/src/app/not-found.tsx, assorted feature-block files
Redesigned 404 with motion and WebsiteLayout; assorted copy and small layout/spacing tweaks across many feature blocks and landing components.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WorkflowsPage
    participant ScrollSpy
    participant SectionNav

    User->>WorkflowsPage: GET /workflows (render)
    WorkflowsPage->>WorkflowsPage: mount hero, workflow cards, SectionNav
    WorkflowsPage->>ScrollSpy: init viewport observer (useEffect)
    User->>WorkflowsPage: scroll
    ScrollSpy->>SectionNav: update currentSection state
    SectionNav->>User: highlight active section
    User->>SectionNav: click section link
    SectionNav->>WorkflowsPage: invoke scrollToSection(anchor)
    WorkflowsPage->>User: smooth scroll to anchor
    ScrollSpy->>SectionNav: update active section after scroll
Loading
sequenceDiagram
    participant User
    participant FAQDropdown
    participant FAQItem
    participant RefMeasure

    User->>FAQDropdown: view FAQ list
    FAQDropdown->>FAQItem: render items (closed)
    User->>FAQItem: click toggle
    FAQItem->>RefMeasure: read content.scrollHeight
    RefMeasure->>FAQItem: return height
    FAQItem->>FAQItem: set maxHeight & isOpen -> animate expand
    User->>FAQItem: click toggle again
    FAQItem->>FAQItem: set maxHeight=0 -> animate collapse
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • saddlepaddle

Poem

🐰 I hopped across the canvas wide,
New heroes, workflows side by side,
FAQ doors swing open with a cheer,
Routes and pages blooming far and near,
Ship PRs — the rabbit dances, full of pride!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: a major website refresh with copy revisions, new pages, and UX improvements. It accurately reflects the primary objectives.
Description check ✅ Passed The description is comprehensive and well-structured, covering all major changes including new pages, AI-native optimizations, copy revisions, and UI updates. It follows the repository template with detailed context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch website-v1.5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/web/client/src/utils/constants/navigation.ts (1)

16-40: ⚠️ Potential issue | 🟠 Major

Use localized message keys for the new navigation labels/descriptions.

These newly added title/description values are user-facing strings hardcoded in constants.

As per coding guidelines: apps/web/client/src/**/*.{ts,tsx}: Avoid hardcoded user-facing text; use next-intl messages/hooks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/utils/constants/navigation.ts` around lines 16 - 40,
Replace the hardcoded user-facing strings in the navigation entries with
next-intl message keys and use the translation hook/loader when rendering;
specifically swap the literal title/description values for the entries
referencing Routes.FEATURES_AI_FRONTEND, Routes.FEATURES_BUILDER,
Routes.FEATURES_PROTOTYPE, Routes.WORKFLOWS_CLAUDE_CODE, and
Routes.WORKFLOWS_VIBE_CODING to message IDs (e.g. navigation.aiFrontend.title /
.description etc.), add those keys to the locale message files, and ensure the
component that consumes this constants file calls the next-intl hook (or passes
translated strings) instead of using the hardcoded text.
apps/web/client/src/app/faq/page.tsx (2)

147-167: ⚠️ Potential issue | 🟡 Minor

Use a stable scroll handler effect (no currentSection dependency).

The current dependency array causes the window listener to be reattached as sections change.

💡 Suggested fix
-    useEffect(() => {
+    useEffect(() => {
         const handleScroll = () => {
             const offset = 120;
             let activeIdx = 0;
             for (let i = 0; i < sectionRefs.current.length; i++) {
                 const ref = sectionRefs.current[i];
                 if (ref) {
                     const top = ref.getBoundingClientRect().top;
                     if (top <= offset) {
                         activeIdx = i;
                     }
                 }
             }
-            if (faqSections[activeIdx]?.anchor && faqSections[activeIdx]?.anchor !== currentSection) {
-                setCurrentSection(faqSections[activeIdx]?.anchor || '');
-            }
+            const next = faqSections[activeIdx]?.anchor || '';
+            setCurrentSection(prev => (prev === next ? prev : next));
         };
         window.addEventListener('scroll', handleScroll, { passive: true });
         handleScroll();
         return () => window.removeEventListener('scroll', handleScroll);
-    }, [currentSection]);
+    }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/faq/page.tsx` around lines 147 - 167, The effect
currently re-attaches the scroll listener because it depends on currentSection;
change the useEffect dependency array to [] (stable on mount) and modify the
handler so it no longer reads currentSection directly: compute newAnchor from
sectionRefs.current and faqSections, then call setCurrentSection(prev =>
(newAnchor && newAnchor !== prev) ? newAnchor : prev). Keep the
window.addEventListener/removeEventListener logic and the initial handleScroll()
call; reference the existing handleScroll function, sectionRefs, faqSections,
setCurrentSection and currentSection to locate and update the code.

11-119: ⚠️ Potential issue | 🟠 Major

Localize the new FAQ/summary/CTA copy through message catalogs.

The newly added user-facing strings are hardcoded throughout this page.

As per coding guidelines apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 186-203, 209-222, 247-248

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/faq/page.tsx` around lines 11 - 119, The FAQ page
currently hardcodes all user-facing strings (section "title", "anchor", and each
"faqs" array's "question" and "answer"); extract these into next-intl message
catalogs and replace inline literals with translations via the next-intl hook
(e.g., useTranslations('faq') or a suitable namespace) so titles become
t('aboutOnlook.title'), questions t('aboutOnlook.faqs.0.question') and answers
t('aboutOnlook.faqs.0.answer'); add corresponding keys in the locale JSON files
for each section (about-onlook, features, compatibility, workflow, company) and
update any CTA/summary strings referenced (the same pattern) so all user-facing
text is loaded from the message catalog instead of hardcoded in the page.tsx
data structures.
🟠 Major comments (28)
apps/web/client/src/app/_components/landing-page/features-intro-section.tsx-8-15 (1)

8-15: ⚠️ Potential issue | 🟠 Major

Externalize landing copy to next-intl keys.

These new strings are hardcoded user-facing text. Please move them to message keys and read via next-intl in this component.

Suggested refactor
+import { getTranslations } from 'next-intl/server';
 import React from 'react';
 
-export function FeaturesIntroSection() {
+export async function FeaturesIntroSection() {
+    const t = await getTranslations('landing.featuresIntro');
     return (
         <div className="w-full max-w-6xl mx-auto py-32 px-8 text-center">
             <div className="max-w-3xl mx-auto">
                 <h2 className="text-foreground-secondary text-sm font-medium uppercase tracking-wider mb-6">
-                    Native Design Tool Features
+                    {t('eyebrow')}
                 </h2>
                 <p className="text-foreground-primary text-2xl md:text-5xl leading-[1.1] font-light mb-8 text-balance">
-                    Familiar to Designers. Trusted by Engineers.
+                    {t('title')}
                 </p>
                 <p className="text-foreground-secondary text-lg max-w-xl mx-auto text-balance">
-                    A canvas that feels intuitive, with real code underneath. Engineers can merge what you create directly — no handoff, no rebuilding.
+                    {t('description')}
                 </p>
             </div>
         </div>
     );
 }

As per coding guidelines: apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/_components/landing-page/features-intro-section.tsx`
around lines 8 - 15, Replace the hardcoded headings and paragraphs in the
FeaturesIntroSection component with next-intl message lookups: import and call
useTranslations (or the existing i18n hook) at the top of the component, replace
the literal strings ("Native Design Tool Features", "Familiar to Designers.
Trusted by Engineers.", and the longer paragraph about the canvas) with
t('landing.features.title'), t('landing.features.tagline'), and
t('landing.features.description') (or similarly named keys), and add those keys
to the locale message files used by the app; ensure you keep the existing
className props and markup (h2 and p elements) and only swap the inner text for
the t(...) calls so formatting/layout is unchanged.
apps/web/client/src/app/_components/landing-page/cta-section.tsx-15-15 (1)

15-15: ⚠️ Potential issue | 🟠 Major

Move default CTA copy out of code and into i18n messages.

Line 15 hardcodes user-facing fallback text in this app-route component, which violates the repo i18n rule and makes localization brittle.

💡 Suggested patch
 import { Button } from '@onlook/ui/button';
+import { useTranslations } from 'next-intl';
 import { useRouter } from 'next/navigation';
 import Link from 'next/link';
@@
-export function CTASection({ href, onClick, ctaText = "Ready to stop rebuilding?\nYour design system, on a canvas.", buttonText = "Book a Demo", showSubtext = true }: CTASectionProps = {}) {
+export function CTASection({ href, onClick, ctaText, buttonText, showSubtext = true }: CTASectionProps = {}) {
+    const t = useTranslations('landingPage.ctaSection');
+    const resolvedCtaText = ctaText ?? t('ctaText');
+    const resolvedButtonText = buttonText ?? t('buttonText');
     const router = useRouter();
@@
-                    {ctaText.split('\n').map((line, index) => (
+                    {resolvedCtaText.split('\n').map((line, index) => (
                         <span key={index}>
                             {line}
-                            {index < ctaText.split('\n').length - 1 && <br />}
+                            {index < resolvedCtaText.split('\n').length - 1 && <br />}
                         </span>
                     ))}
@@
-                        {buttonText}
+                        {resolvedButtonText}
                     </Button>

As per coding guidelines: apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/_components/landing-page/cta-section.tsx` at line 15,
The CTASection function currently hardcodes user-facing defaults (ctaText and
buttonText); move those strings into i18n message keys (e.g. "cta.ctaText" and
"cta.buttonText") in the appropriate messages file and replace the hardcoded
defaults by calling the next-intl translation hook (e.g. useTranslations or
equivalent) inside CTASection to supply the fallback values; update
CTASectionProps to accept optional strings and use the translator to compute
final values for ctaText and buttonText (and any subtext) so all user-facing
copy comes from locale messages rather than inline literals.
apps/web/client/src/app/_components/landing-page/builder-features-intro-section.tsx-8-15 (1)

8-15: ⚠️ Potential issue | 🟠 Major

Move landing copy to next-intl message keys.

Lines 8, 11, and 14 contain hardcoded user-facing text, which violates the guideline for app/**/*.tsx components. Extract these strings to translation messages and use useTranslations hook for consistent i18n handling.

Add the required message keys to apps/web/client/messages/en.json (and other language files) under a landing.builderFeaturesIntroSection namespace, then refactor the component to use useTranslations:

Suggested refactor
 import React from 'react';
+import { useTranslations } from 'next-intl';

 export function BuilderFeaturesIntroSection() {
+    const t = useTranslations('landing.builderFeaturesIntroSection');
     return (
         <div className="w-full max-w-6xl mx-auto py-32 px-8 text-center">
             <div className="max-w-3xl mx-auto">
                 <h2 className="text-foreground-secondary text-sm font-medium uppercase tracking-wider mb-6">
-                    Works With Your Codebase
+                    {t('eyebrow')}
                 </h2>
                 <p className="text-foreground-primary text-2xl md:text-5xl leading-[1.1] font-light mb-8 text-balance">
-                    Connect Your Existing Project. Start Designing in Minutes.
+                    {t('title')}
                 </p>
                 <p className="text-foreground-secondary text-lg max-w-xl mx-auto text-balance">
-                    No rebuilding. No migration. Connect your React, Next.js, or Vue project and design with your real components — the ones your engineers already built.
+                    {t('description')}
                 </p>
             </div>
         </div>
     );
 }

Per coding guideline: apps/web/client/src/app/**/*.tsx must avoid hardcoded user-facing text and use next-intl messages/hooks instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/client/src/app/_components/landing-page/builder-features-intro-section.tsx`
around lines 8 - 15, Extract the three hardcoded strings in the
BuilderFeaturesIntroSection component into next-intl message keys under the
landing.builderFeaturesIntroSection namespace (e.g., title, subtitle,
description) in your English messages JSON and other locales, then import and
call useTranslations("landing.builderFeaturesIntroSection") inside the component
and replace the literal strings with t('title'), t('subtitle'), and
t('description'); ensure keys match the JSON entries and the component continues
to render the same markup.
apps/web/client/src/app/features/page.tsx-19-39 (1)

19-39: ⚠️ Potential issue | 🟠 Major

Localize newly added user-facing copy instead of hardcoding it

The new FAQ entries, AI summary copy, bullet list, and CTA text are hardcoded strings. Please move these into next-intl messages and read them via translation hooks.

Suggested pattern
+import { useTranslations } from 'next-intl';

 export default function FeaturesPage() {
+    const t = useTranslations('featuresPage');
+    const featuresFaqs = [
+        { question: t('faqs.whatIs.question'), answer: t('faqs.whatIs.answer') },
+        // ...
+    ];

     return (
         <WebsiteLayout showFooter={true}>
-            <h1>Onlook Features: Design with Your Real Components</h1>
+            <h1>{t('summary.title')}</h1>
             {/* ... */}
-            <CTASection ctaText={`Ready to stop rebuilding?`} />
+            <CTASection ctaText={t('cta.text')} />
         </WebsiteLayout>
     );
 }

As per coding guidelines, apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 49-66, 78-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/page.tsx` around lines 19 - 39, The FAQ
entries and other user-facing strings in the page component are hardcoded;
extract each visible string (FAQ objects' "question" and "answer" values, the AI
summary text, bullet list items, and CTA text) into next-intl message keys, then
replace the literals with translation lookups using the next-intl hook (e.g.,
useTranslations) inside the page component so the FAQ array builds its values
from t('...') calls rather than raw strings; ensure message keys are descriptive
(e.g., faq.questionX, faq.answerX, ai.summary, cta.text) and update any
references that consume the FAQ array or CTA to use the translated values.
apps/web/client/src/app/features/page.tsx-47-67 (1)

47-67: ⚠️ Potential issue | 🟠 Major

Avoid using sr-only content as crawler-targeted copy

This section is labeled for AI/crawlers but exposed to screen readers, which can create noisy/duplicative narration for assistive-tech users. Prefer visible summary content or rely on structured data/meta for crawler signals instead of hidden long-form text.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/page.tsx` around lines 47 - 67, The visually
hidden AI-friendly summary section (the <section> with className "sr-only" and
aria-label "Features Summary") shouldn't contain long crawler-targeted copy
because it will be read by screen readers and produce duplicate / noisy
narration; instead remove the "sr-only" hiding, move this summary into visible
content or into structured metadata (e.g., page meta tags or JSON-LD) and/or
condense it for assistive users, and update the component rendering in page.tsx
to either render a visible summary block or inject the same text into head/meta
while keeping the accessible section concise.
apps/web/client/src/app/_components/landing-page/feature-blocks/direct-editing.tsx-13-15 (1)

13-15: ⚠️ Potential issue | 🟠 Major

Localize the new “Canvas Manipulation” copy.

These newly added user-facing strings should come from next-intl messages rather than inline literals. As per coding guidelines apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/client/src/app/_components/landing-page/feature-blocks/direct-editing.tsx`
around lines 13 - 15, Replace the hardcoded user-facing text in
direct-editing.tsx with next-intl translations: import useTranslations from
'next-intl', call const t = useTranslations('FeatureBlocks') (or the appropriate
namespace), replace the span content "Canvas Manipulation" with
t('canvasManipulation.title') and the paragraph "Drag, resize, and arrange
elements..." with t('canvasManipulation.description'); add the corresponding
keys to the locale messages JSON so strings are loaded at runtime.
apps/web/client/src/app/_components/landing-page/feature-blocks/ai-chat-preview-block.tsx-12-14 (1)

12-14: ⚠️ Potential issue | 🟠 Major

Localize the new AI chat block copy.

Please move these added strings into next-intl message keys/hooks instead of inline JSX text. As per coding guidelines apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/client/src/app/_components/landing-page/feature-blocks/ai-chat-preview-block.tsx`
around lines 12 - 14, The two hardcoded strings in the Ai chat preview component
should be pulled into next-intl message keys and referenced via the
useTranslations hook instead of inline JSX; in the component
(ai-chat-preview-block.tsx) import and call useTranslations with the appropriate
namespace (e.g., const t = useTranslations('Landing')), replace the span content
"AI That Understands Context" with t('aiChat.title') and the paragraph content
with t('aiChat.description'), and add these keys (aiChat.title and
aiChat.description) to your locale JSON message files for each locale.
apps/web/client/src/app/_components/landing-page/feature-blocks/components.tsx-261-264 (1)

261-264: ⚠️ Potential issue | 🟠 Major

Use localized message keys for this updated components copy.

The new title/description are hardcoded user-facing text and should be moved to next-intl. As per coding guidelines apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/client/src/app/_components/landing-page/feature-blocks/components.tsx`
around lines 261 - 264, Replace the two hardcoded user-facing strings in the JSX
(the span containing "Your Real Components" and the p containing "Design with
the buttons, cards, and layouts your engineers already built. Your actual design
system.") with localized message lookups using next-intl: import and call
useTranslations (e.g., const t = useTranslations('featureBlocks')) at the top of
the component, replace the span text with t('yourRealComponents.title') and the
p text with t('yourRealComponents.description'), and add corresponding keys
("featureBlocks.yourRealComponents.title" and
"featureBlocks.yourRealComponents.description") to the relevant next-intl
messages JSON for supported locales.
apps/web/client/src/app/_components/hero/index.tsx-76-78 (1)

76-78: ⚠️ Potential issue | 🟠 Major

Update hero tagline to use next-intl keys.

These new hero strings are hardcoded and should be sourced from localization messages. As per coding guidelines apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/_components/hero/index.tsx` around lines 76 - 78, The
hero component currently contains hardcoded strings ("Design with your real
components.", "Ship PRs, not prototypes."); replace these with next-intl message
lookups by importing and using the useTranslations hook (e.g., const t =
useTranslations('hero')) inside the Hero component and swap the literal JSX text
for t('tagline.line1') and t('tagline.line2') (or your chosen message keys) so
the strings come from localization messages; ensure the message keys are added
to the relevant locale JSON and that the hook is invoked in the same component
(index.tsx) where the JSX currently renders the two lines.
apps/web/client/src/app/features/prototype/layout.tsx-4-139 (1)

4-139: ⚠️ Potential issue | 🟠 Major

Move new page copy/metadata strings into next-intl messages.

This introduces hardcoded user-facing text (metadata, keyword copy, and FAQ answers) instead of message-based localization. As per coding guidelines apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/prototype/layout.tsx` around lines 4 - 139,
The file embeds hardcoded user-facing strings (metadata
title/description/keywords, openGraph/twitter fields, and the jsonLd and
faqJsonLd text blobs) — move these into next-intl message files and replace the
inline literals with lookups; extract each string/keyword array and each FAQ
question/answer into the i18n messages (e.g., keys like prototype.title,
prototype.description, prototype.keywords, prototype.faq.q1, prototype.faq.a1),
then in the layout replace the hardcoded values in the metadata object,
openGraph, twitter, jsonLd, and faqJsonLd constants with calls to the next-intl
API you already use in the app (getMessages or useTranslations / getTranslations
in server components) to load localized text and arrays so metadata and JSON‑LD
are generated from messages instead of literals.
apps/web/client/src/app/_components/landing-page/what-can-onlook-do-section.tsx-311-334 (1)

311-334: ⚠️ Potential issue | 🟠 Major

Externalized copy is required for these updated feature cards.

The new card headings/descriptions are hardcoded literals; please move them to next-intl messages and reference via hooks. As per coding guidelines apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/client/src/app/_components/landing-page/what-can-onlook-do-section.tsx`
around lines 311 - 334, In the what-can-onlook-do-section component, replace
hardcoded card headings and descriptions with next-intl message lookups: import
and use the next-intl hook (e.g., useTranslations) in the component, create
message keys for each literal (e.g.,
"worksWithYourCodebase.title"/".description",
"builtForTeams.title"/".description",
"directGithubIntegration.title"/".description",
"shipPRsNotPrototypes.title"/".description",
"powerUserShortcuts.title"/".description",
"referenceAnythingInChat.title"/".description"), move the literal strings into
the locale JSON files, and call t('key') where the current JSX uses the
hardcoded text (the divs containing those headings and descriptions); leave
dynamic bits like getKeyboardShortcut() and isShortcutAnimating unchanged but
wrap surrounding copy with translated strings where needed.
apps/web/client/src/app/features/ai/page.tsx-47-66 (1)

47-66: ⚠️ Potential issue | 🟠 Major

The new AI summary section should use localized messages.

This block introduces substantial user-facing copy directly in JSX; please move it into next-intl resources/hooks. As per coding guidelines apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/ai/page.tsx` around lines 47 - 66, The AI
summary section currently contains hardcoded user-facing text inside the JSX
(the <section aria-label="AI Features Summary"> block and its children: the
title, paragraph, h2, and list items); extract each visible string into
next-intl message keys (e.g. ai.summary.title, ai.summary.description,
ai.summary.heading, ai.summary.feature.*), add them to the locale resource
files, and replace the literals in the page component with translations via
useTranslations (or the project's next-intl hook) so the section renders
t('ai.summary.title'), t('ai.summary.description'), t('ai.summary.heading'), and
t('ai.summary.feature.xxx') instead of hardcoded strings while preserving the
existing structure and aria-label.
apps/web/client/src/app/features/ai-for-frontend/layout.tsx-4-185 (1)

4-185: ⚠️ Potential issue | 🟠 Major

Localize the newly added public copy via next-intl instead of inline literals.

The added metadata and FAQ JSON-LD content is hardcoded in this TSX file. As per coding guidelines apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/ai-for-frontend/layout.tsx` around lines 4 -
185, The metadata, jsonLd, and faqJsonLd objects (symbols: metadata, jsonLd,
faqJsonLd) contain hardcoded user-facing text — replace those inline literals
with next-intl message lookups (e.g., useTranslations or the next-intl message
loader) and move all strings into locale message files; update the layout
component to call the i18n hook (useTranslations) and substitute message keys
for titles, descriptions, keywords, openGraph/twitter fields, FAQ
question/answer texts, featureList and aggregateRating fields so the file reads
values from the locale messages instead of hardcoded strings.
apps/web/client/src/app/_components/landing-page/ai-features-intro-section.tsx-8-14 (1)

8-14: ⚠️ Potential issue | 🟠 Major

Externalize this intro copy to translation messages.

These updated strings are user-facing and currently hardcoded in JSX.

As per coding guidelines: apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/client/src/app/_components/landing-page/ai-features-intro-section.tsx`
around lines 8 - 14, The three hardcoded user-facing strings in
ai-features-intro-section.tsx should be moved into next-intl translation
messages and referenced via the next-intl hook (e.g., useTranslations) in the
AiFeaturesIntroSection component; create message keys like "aiFeatures.title",
"aiFeatures.lead", and "aiFeatures.desc" in the locale JSON, load them with
useTranslations("aiFeatures") and replace the JSX literals with t("title"),
t("lead"), and t("desc") respectively so the component no longer contains
hardcoded copy.
apps/web/client/src/app/site-map/layout.tsx-4-30 (1)

4-30: ⚠️ Potential issue | 🟠 Major

Localize sitemap metadata copy instead of hardcoding strings.

title, description, and OpenGraph text are user-facing metadata and should follow the same translation flow as page content.

As per coding guidelines: apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/site-map/layout.tsx` around lines 4 - 30, The sitemap
layout currently hardcodes user-facing metadata (the title, description,
openGraph fields and the jsonLd const); replace these literal strings by pulling
localized messages via next-intl (e.g., call useTranslations or the appropriate
message loader in this layout component) and use the translation keys when
constructing the exported metadata object (title, description, openGraph.title,
openGraph.description) and the jsonLd.name/jsonLd.description values; ensure
corresponding message keys are added to the locale JSON files and preserve
URLs/structure while only swapping text values for translated strings.
apps/web/client/src/app/_components/landing-page/faq-section.tsx-20-53 (1)

20-53: ⚠️ Potential issue | 🟠 Major

Localize the updated FAQ entries instead of embedding raw copy.

The newly added FAQ questions/answers are hardcoded, which breaks the i18n standard for app components.

As per coding guidelines: apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/_components/landing-page/faq-section.tsx` around
lines 20 - 53, defaultFaqs contains hardcoded user-facing strings; replace them
with localized messages using next-intl. Import and use the useTranslations (or
messages) hook in the component that consumes defaultFaqs, convert defaultFaqs
into a factory/function (e.g., getDefaultFaqs or buildDefaultFaqs) that calls
t('faq.<key>.question') and t('faq.<key>.answer') for each entry (use
descriptive keys like faq.whatIsOnlook.question and faq.whatIsOnlook.answer),
and update the consumer to call that factory so all question/answer text comes
from next-intl message keys rather than raw literals.
apps/web/client/src/app/_components/landing-page/feature-blocks/brand-compliance.tsx-138-141 (1)

138-141: ⚠️ Potential issue | 🟠 Major

Localize the updated block title and description.

The new text is user-facing copy introduced as hardcoded literals.

Based on learnings: Applies to apps/web/client/src/app/**/*.tsx : Avoid hardcoded user-facing text; use next-intl messages/hooks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/client/src/app/_components/landing-page/feature-blocks/brand-compliance.tsx`
around lines 138 - 141, Replace the hardcoded user-facing strings in the
BrandCompliance component (the span with "Design System Guardrails" and the
paragraph "AI is constrained to your colors, fonts, and tokens. No drift. No
off-brand outputs.") with next-intl lookups: import and call useTranslations
with an appropriate namespace, reference message keys (e.g.
"designSystemGuardrails.title" and "designSystemGuardrails.description"), and
replace the literal text with t('...') so the UI uses localized messages; add
those keys to the corresponding messages JSON for the component's locale files.
apps/web/client/src/app/_components/hero/claude-code-hero.tsx-26-80 (1)

26-80: ⚠️ Potential issue | 🟠 Major

Move hero labels/copy to next-intl instead of hardcoding.

The newly added workflow badge, headline, subtitle, CTA label, and footer labels are all user-facing literals.

As per coding guidelines: apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/_components/hero/claude-code-hero.tsx` around lines
26 - 80, Replace hardcoded user-facing strings in the Claude Code hero component
(the motion.h1 "Workflows", motion.p "Claude Code for Designers", motion.h2
subtitle, CTA label "Book a Demo" inside Button/anchor, and footer spans "GitHub
stars", "YC W25", "Open Source") with next-intl message lookups; import and use
useTranslations (or intl.formatMessage) to read keys (e.g. hero.title,
hero.tagline, hero.subtitle, hero.cta, hero.footer.stars, hero.footer.yc,
hero.footer.openSource), add those keys to the locale message JSON, and pass
formatted strings into the JSX where the literals currently appear so no
user-facing text remains hardcoded.
apps/web/client/src/app/features/builder/page.tsx-47-68 (1)

47-68: ⚠️ Potential issue | 🟠 Major

Move the new summary copy into next-intl messages.

This block introduces screen-reader-exposed user text directly in JSX, which bypasses localization and creates copy drift risk across locales.

As per coding guidelines: apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/builder/page.tsx` around lines 47 - 68, The
screen-reader-only summary JSX (the <section> with className "sr-only" and
aria-label "Visual Builder Summary" containing the h1, p, h2 and list items)
contains hardcoded user-facing copy; extract all visible strings into next-intl
messages and replace them with translation lookups (e.g., useTranslations or
t('...') calls) in the page component (page.tsx). Create corresponding keys in
your locale message files (e.g., builder.* keys in the locale JSON for each
language), import and call useTranslations('builder') at the top of the page
component, and replace each hardcoded string (h1 text, paragraph, h2 and each
<li>) with t('key') lookups so the SR-only block reads localized messages
instead of inline text. Ensure aria-label is also localized via messages.
apps/web/client/src/app/site-map/layout.tsx-77-80 (1)

77-80: ⚠️ Potential issue | 🟠 Major

Add biome-ignore comment and escape < in JSON-LD script to satisfy default Biome security rule.

The noDangerouslySetInnerHtml rule is part of Biome's recommended ruleset and is enabled by default in this project. Line 79 violates it without an exception. This pattern occurs across 15+ files in the codebase (app/layout.tsx, app/about/layout.tsx, app/features/, app/workflows/, app/faq/page.tsx, and others).

Suggested patch
+            {/* biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires raw script content; payload is static and `<` is escaped. */}
             <script
                 type="application/ld+json"
-                dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+                dangerouslySetInnerHTML={{
+                    __html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
+                }}
             />

This fix should be applied systematically across all affected layout files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/site-map/layout.tsx` around lines 77 - 80, The script
tag using dangerouslySetInnerHTML in layout.tsx violates Biome's
noDangerouslySetInnerHtml rule; add a file-local Biome ignore comment (//
biome-ignore noDangerouslySetInnerHtml) immediately above the JSX element and
ensure the JSON-LD string escapes any "<" characters by replacing them with
their safe escape sequence (e.g., use JSON.stringify(jsonLd).replace(/</g,
'\\u003c')) before passing it to dangerouslySetInnerHTML so the script content
is safe; update the usage in the component where jsonLd and the <script ...
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> JSX appear.
apps/web/client/src/app/workflows/page.tsx-30-52 (1)

30-52: ⚠️ Potential issue | 🟠 Major

Externalize newly added workflows copy to translations.

The new page content is hardcoded throughout (cards, summary, hero copy, CTA text).

As per coding guidelines apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 59-113, 167-169

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/workflows/page.tsx` around lines 30 - 52, The
workflows array currently hardcodes user-facing strings (the title and
description fields for items like the workflows constant and any hero/CTA text
used elsewhere between lines 59-113 and 167-169); replace these literals with
translation keys and load them via next-intl (use useTranslations or the
messages object) so each item uses t('workflows.claudeCode.title'),
t('workflows.claudeCode.description'), etc., and update any usages of
Routes.WORKFLOWS_CLAUDE_CODE, Icons.Sparkles, and the Codex item to reference
the translated strings; add the corresponding keys to the locale message files
and ensure the component imports and calls useTranslations at the top to
populate the workflows array from translations instead of hardcoded text.
apps/web/client/src/app/features/ai/layout.tsx-4-63 (1)

4-63: ⚠️ Potential issue | 🟠 Major

Externalize metadata/FAQ copy to next-intl rather than hardcoding.

Title/description/keyword and FAQ strings are directly embedded in this layout.

As per coding guidelines apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 75-132

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/ai/layout.tsx` around lines 4 - 63, The
metadata and copy in the AI layout (variables title, description, keywords
array, openGraph.title/openGraph.description, twitter.title/twitter.description,
alternates.canonical, and any FAQ strings in the same file) are hardcoded;
replace them with localized message lookups via next-intl (e.g.,
loadMessages/useTranslations or the app-level next-intl hook used in the repo)
and reference message keys instead of raw strings, then pass those localized
strings into the metadata objects (openGraph, twitter, alternates) and any FAQ
rendering; ensure all new message keys are added to the locale JSON files and
remove the hardcoded literals so the file uses intl lookups throughout.
apps/web/client/src/app/workflows/vibe-coding/page.tsx-17-42 (1)

17-42: ⚠️ Potential issue | 🟠 Major

Localize new vibe-coding copy via next-intl instead of hardcoded strings.

The page introduces extensive user-facing content directly in code.

As per coding guidelines apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 74-409

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx` around lines 17 - 42,
The vibeCodingFaqs array contains hardcoded user-facing strings; extract all
question/answer texts into next-intl message keys (e.g.,
vibeCoding.faq.0.question / vibeCoding.faq.0.answer or named keys) in your
locale JSON files, import/use the next-intl hook (useTranslations) in the
component that declares vibeCodingFaqs, and replace each literal with t('…')
lookups (or construct the array using t for each faq entry); ensure messages are
added for all locales and update any typing/loader usage so the page uses
localized strings instead of the hardcoded vibeCodingFaqs values.
apps/web/client/src/app/_components/landing-page/benefits-section.tsx-16-19 (1)

16-19: ⚠️ Potential issue | 🟠 Major

Move revised benefits copy into next-intl messages.

These newly added strings are hardcoded in JSX, which blocks localization and drifts from the app i18n pattern.

As per coding guidelines apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 30-33, 44-47

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/_components/landing-page/benefits-section.tsx` around
lines 16 - 19, The hardcoded user-facing strings in the BenefitsSection JSX (the
h2 "AI That Understands Context", the p "AI Constrained to Your Design System",
and the longer descriptive paragraph) must be moved into next-intl message files
and referenced via the next-intl hook; add keys like benefits.title,
benefits.subtitle, benefits.description to your locale messages, import and call
useTranslations (e.g., const t = useTranslations('BenefitsSection') or
appropriate namespace) in the BenefitsSection component, and replace the literal
strings with t('benefits.title'), t('benefits.subtitle'), and
t('benefits.description'); apply the same extraction for the other hardcoded
strings noted around lines 30-33 and 44-47 so all user-facing text follows the
app i18n pattern.
apps/web/client/src/app/site-map/page.tsx-23-175 (1)

23-175: ⚠️ Potential issue | 🟠 Major

Localize sitemap titles/descriptions instead of hardcoding them.

The newly added section titles, descriptions, and page copy are embedded directly in code rather than sourced from translation messages.

As per coding guidelines apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 243-276, 282-295

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/site-map/page.tsx` around lines 23 - 175, The sitemap
entries currently hardcode all user-facing strings (section titles, link titles
and descriptions) — replace them with next-intl translations: import
useTranslations (or the project's i18n hook) at the top of page.tsx, call const
t = useTranslations('sitemap'), and replace every literal title/description/href
label in the sitemap objects (e.g., the objects with anchor "main", "features",
"workflows", "resources", "social", "legal" and their link entries like "Home",
"Pricing", "All Features", "AI", etc.) with t(...) keys (for example
t('main.title'), t('main.links.home.title'), t('main.links.home.description') or
a similar nested key convention you choose), keeping non-text fields (href,
external) intact; add corresponding keys to the locale message files. Ensure all
places flagged in the diff are converted.
apps/web/client/src/app/features/ai-for-frontend/page.tsx-15-52 (1)

15-52: ⚠️ Potential issue | 🟠 Major

Move new feature-page content into next-intl messages.

The added FAQs, summaries, section copy, and CTA text are hardcoded.

As per coding guidelines apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 79-104, 113-291

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/ai-for-frontend/page.tsx` around lines 15 -
52, The aiFrontendFaqs array and other hardcoded user-facing strings in page.tsx
must be moved into next-intl message files and referenced via the next-intl
hooks; create keys for each FAQ question/answer (e.g.
aiFrontend.faqs.0.question, aiFrontend.faqs.0.answer) and for summaries, section
copy, and CTA text, add them to the locale JSON used by next-intl, then update
page.tsx to import/use useTranslations (or the appropriate next-intl helper) and
replace direct string literals in the aiFrontendFaqs definition and other
hardcoded strings (search for aiFrontendFaqs plus the other referenced blocks
~lines 79-104 and 113-291) to pull values from t('aiFrontend.faqs.N.question') /
t('aiFrontend.faqs.N.answer') and t('aiFrontend.sectionKey') instead of inline
text.
apps/web/client/src/app/workflows/claude-code/page.tsx-15-40 (1)

15-40: ⚠️ Potential issue | 🟠 Major

Move newly added workflow copy/FAQ text to next-intl keys.

This page introduces extensive hardcoded user-facing content.

As per coding guidelines apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.

Also applies to: 63-90, 104-111, 117-134, 150-248

apps/web/client/src/app/faq/page.tsx-179-182 (1)

179-182: ⚠️ Potential issue | 🟠 Major

Replace JSON-LD injection with non-dangerous script content.

Using dangerouslySetInnerHTML here is avoidable and currently conflicts with security linting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/faq/page.tsx` around lines 179 - 182, The current
script tag uses dangerouslySetInnerHTML to inject JSON-LD; instead call
generateFAQJsonLd(), stringify its result into a local variable and render it as
the script's children so React outputs plain text (e.g. const faqJson =
JSON.stringify(generateFAQJsonLd()); then <script
type="application/ld+json">{faqJson}</script>). Update the script in page.tsx
that references generateFAQJsonLd to use this child-string approach to avoid
dangerouslySetInnerHTML and satisfy the security linter.
🟡 Minor comments (7)
apps/web/client/src/app/not-found.tsx-32-35 (1)

32-35: ⚠️ Potential issue | 🟡 Minor

Hardcoded user-facing text violates i18n guidelines.

The page contains multiple hardcoded strings that should use next-intl messages/hooks for internationalization:

  • Line 32: "404"
  • Lines 33-35: "Seems like you ventured somewhere unknown on your journey. Let us help you find your way."
  • Line 49: "Back to home"

As per coding guidelines: "Avoid hardcoded user-facing text; use next-intl messages/hooks instead."

🌐 Proposed fix using next-intl
 'use client';
 
 import Link from 'next/link';
 import { motion } from 'framer-motion';
+import { useTranslations } from 'next-intl';
 import { WebsiteLayout } from './_components/website-layout';
 import { Illustrations } from './_components/landing-page/illustrations';
 
 export default function NotFound() {
+    const t = useTranslations('NotFound');
     return (
         <WebsiteLayout>
             <main className="relative min-h-screen w-full overflow-hidden">
                 ...
                         <motion.div
                             className="space-y-3"
                             ...
                         >
-                            <h1 className="text-6xl font-light tracking-tight text-foreground-primary">404</h1>
-                            <p className="text-xl text-foreground-tertiary">
-                                Seems like you ventured somewhere unknown on your journey. Let us help you find your way.
-                            </p>
+                            <h1 className="text-6xl font-light tracking-tight text-foreground-primary">{t('title')}</h1>
+                            <p className="text-xl text-foreground-tertiary">
+                                {t('description')}
+                            </p>
                         </motion.div>
                         ...
                             <Link
                                 href="/"
                                 className="..."
                             >
-                                Back to home
+                                {t('backToHome')}
                             </Link>

Add corresponding messages to your locale files:

{
  "NotFound": {
    "title": "404",
    "description": "Seems like you ventured somewhere unknown on your journey. Let us help you find your way.",
    "backToHome": "Back to home"
  }
}

Also applies to: 49-49

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/not-found.tsx` around lines 32 - 35, Replace
hardcoded user-facing strings in the NotFound component with next-intl message
lookups: import and call useTranslations('NotFound') (or the app-wide
translator) inside the component, then replace the h1 content "404" with
t('title'), the paragraph text with t('description'), and the "Back to home"
link/button text with t('backToHome'); ensure corresponding keys are added to
your locale JSON (NotFound.title, NotFound.description, NotFound.backToHome).
apps/web/client/src/app/features/prototype/layout.tsx-144-151 (1)

144-151: ⚠️ Potential issue | 🟡 Minor

The dangerouslySetInnerHTML pattern for JSON-LD is flagged by Biome but is the recommended Next.js approach.

This file uses Biome's recommended linter rules, which include noDangerouslySetInnerHtml. However, for JSON-LD in the app router, Next.js documentation recommends this exact pattern. You have two options:

  1. Add a narrowly scoped lint exception with a comment explaining why this pattern is necessary for JSON-LD serialization:
// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD structured data requires dangerouslySetInnerHTML per Next.js guidelines
<script
    type="application/ld+json"
    dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }}
/>
  1. Escape angle brackets in the JSON output to reduce XSS risk (shown above as .replace(/</g, "\\u003c")).

The next/script component with children would not serialize JSON-LD correctly; dangerouslySetInnerHTML is the required approach for this use case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/features/prototype/layout.tsx` around lines 144 -
151, The two script tags that set JSON-LD via dangerouslySetInnerHTML (using
jsonLd and faqJsonLd) trigger Biome's noDangerouslySetInnerHtml rule but are the
correct Next.js pattern; update each script to both escape angle brackets in the
JSON (e.g., JSON.stringify(...).replace(/</g, "\\u003c")) and add a narrow
biome-ignore comment immediately above each script (e.g., // biome-ignore
lint/security/noDangerouslySetInnerHtml: JSON-LD requires
dangerouslySetInnerHTML per Next.js) so the linter is documented and the output
is safer.
apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx-25-29 (1)

25-29: ⚠️ Potential issue | 🟡 Minor

Set an explicit button type on the FAQ toggle.

Without type="button", this can submit a surrounding form unexpectedly.

Suggested patch
             <button
+                type="button"
                 className="flex items-center justify-between w-full text-left text-foreground-primary text-lg focus:outline-none cursor-pointer py-2"
                 onClick={onToggle}
                 aria-expanded={isOpen}
             >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx` around
lines 25 - 29, The FAQ toggle button in the FAQ dropdown lacks an explicit type
which can cause it to submit a surrounding form; update the button element in
the FAQ component (where onToggle and aria-expanded={isOpen} are used) to
include type="button" so it behaves as a plain toggle rather than a submit
control.
apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx-50-52 (1)

50-52: ⚠️ Potential issue | 🟡 Minor

Render faq.answer in a neutral container instead of <p>.

answer accepts React.ReactNode; wrapping it in <p> can generate invalid nested markup for richer nodes.

Suggested patch
                 <div ref={contentRef}>
-                    <p className="text-foreground-secondary text-regular leading-relaxed">{faq.answer}</p>
+                    <div className="text-foreground-secondary text-regular leading-relaxed">{faq.answer}</div>
                 </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx` around
lines 50 - 52, The FAQ item renders faq.answer (a React.ReactNode) inside a <p>,
which can produce invalid nested markup for richer nodes; in the LandingPage FAQ
component (faq-dropdown.tsx) replace the <p className="text-foreground-secondary
text-regular leading-relaxed">{faq.answer}</p> with a neutral container (e.g.,
<div> or a React.Fragment) that preserves the styling classes (apply the classes
to the neutral container or wrap children appropriately) while keeping the
existing contentRef and outer structure unchanged so rich nodes inside
faq.answer render valid HTML.
apps/web/client/src/app/faq/layout.tsx-20-31 (1)

20-31: ⚠️ Potential issue | 🟡 Minor

Missing OpenGraph and Twitter images.

Other layout files in this PR (features/builder, workflows, about, etc.) include images in their openGraph and twitter metadata configurations. This FAQ layout is missing them, which could affect social sharing previews.

🔧 Proposed fix to add images
     openGraph: {
         title: 'FAQ | Onlook',
         description: 'Everything you need to know about Onlook - the AI-powered visual editor for frontend development.',
         type: 'website',
         url: 'https://onlook.com/faq',
         siteName: 'Onlook',
+        images: [
+            {
+                url: 'https://framerusercontent.com/images/ScnnNT7JpmUya7afqGAets8.png',
+            },
+        ],
     },
     twitter: {
         card: 'summary_large_image',
         title: 'FAQ | Onlook',
         description: 'Everything you need to know about Onlook - the AI-powered visual editor for frontend development.',
+        images: ['https://framerusercontent.com/images/ScnnNT7JpmUya7afqGAets8.png'],
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/faq/layout.tsx` around lines 20 - 31, The FAQ
layout's metadata is missing social preview images: update the openGraph and
twitter objects in layout.tsx to include the same image(s) used by the other
layouts (add an images array to openGraph and the corresponding images property
for twitter) so social shares show a preview; modify the openGraph and twitter
entries in this file to match the image keys/URLs used by features/builder,
workflows, and about.
apps/web/client/src/app/workflows/page.tsx-66-72 (1)

66-72: ⚠️ Potential issue | 🟡 Minor

Keep sr-only integration items consistent with rendered workflow cards.

The hidden summary includes “Cursor,” but no corresponding card exists in workflows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/workflows/page.tsx` around lines 66 - 72, The sr-only
"Available Integrations" list in page.tsx is out of sync with the actual
rendered workflows (it mentions "Cursor" but there's no corresponding workflow
card); either remove "Cursor" from the hidden list or add a matching workflow
entry so the accessible summary matches the UI. Locate the hardcoded ul in
page.tsx (the "Available Integrations" list) and update its items to mirror the
source of truth used to render cards (the workflows collection or the component
that maps over workflows) so the sr-only items and the rendered workflow cards
remain consistent.
apps/web/client/src/app/site-map/page.tsx-211-231 (1)

211-231: ⚠️ Potential issue | 🟡 Minor

Stop re-binding the scroll listener on each section change.

The effect depends on currentSection, so it repeatedly removes/re-adds the listener during scroll updates.

💡 Suggested fix
-    useEffect(() => {
+    useEffect(() => {
         const handleScroll = () => {
             const offset = 120;
             let activeIdx = 0;
             for (let i = 0; i < sectionRefs.current.length; i++) {
                 const ref = sectionRefs.current[i];
                 if (ref) {
                     const top = ref.getBoundingClientRect().top;
                     if (top <= offset) {
                         activeIdx = i;
                     }
                 }
             }
-            if (sitemapSections[activeIdx]?.anchor && sitemapSections[activeIdx]?.anchor !== currentSection) {
-                setCurrentSection(sitemapSections[activeIdx]?.anchor || '');
-            }
+            const next = sitemapSections[activeIdx]?.anchor || '';
+            setCurrentSection(prev => (prev === next ? prev : next));
         };
         window.addEventListener('scroll', handleScroll, { passive: true });
         handleScroll();
         return () => window.removeEventListener('scroll', handleScroll);
-    }, [currentSection]);
+    }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/site-map/page.tsx` around lines 211 - 231, The
useEffect is re-registering the scroll listener whenever currentSection changes;
move the dependency off currentSection so the listener is added once: define
handleScroll (which reads sectionRefs.current, sitemapSections and calls
setCurrentSection) inside the effect but use an empty dependency array (or
include only stable refs/props), or alternatively wrap handleScroll in
useCallback with stable deps and call window.addEventListener in a useEffect
with [], ensuring cleanup removes the same function; update the effect that
currently references useEffect, handleScroll, sectionRefs, sitemapSections,
currentSection, and setCurrentSection accordingly.

Comment thread apps/web/client/src/app/sitemap.ts Outdated
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – docs-onlook February 27, 2026 02:21 Inactive
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – docs-onlook February 27, 2026 02:22 Inactive
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – docs-onlook February 27, 2026 02:22 Inactive
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – docs-onlook February 27, 2026 02:23 Inactive
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – docs-onlook February 27, 2026 02:23 Inactive
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – docs-onlook February 27, 2026 02:24 Inactive
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – docs-onlook February 27, 2026 02:25 Inactive
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added responsive mobile preview of the Onlook interface mockup to
"The Solution" sections on both /workflows/claude-code and
/workflows/vibe-coding pages. Uses absolute positioning to show a
cropped view of the editor from the right side, matching the approach
used on the /features page.

- Container height: 880px to prevent clipping the prototype
- Mockup positioned with right-10 offset for visual balance
- Hidden on desktop (md:hidden), desktop version unchanged

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – docs-onlook February 27, 2026 02:30 Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/client/src/app/workflows/vibe-coding/page.tsx (1)

10-16: Switch relative imports to configured path aliases.

These imports should use @/* (or ~/*) aliases instead of ../../... to match repo standards.

Suggested diff
-import { UnicornBackground } from '../../_components/hero/unicorn-background';
-import { CTASection } from '../../_components/landing-page/cta-section';
-import { FAQSection } from '../../_components/landing-page/faq-section';
-import { OnlookInterfaceMockup } from '../../_components/landing-page/onlook-interface-mockup';
-import { useGitHubStats } from '../../_components/top-bar/github';
-import { WebsiteLayout } from '../../_components/website-layout';
+import { UnicornBackground } from '@/app/_components/hero/unicorn-background';
+import { CTASection } from '@/app/_components/landing-page/cta-section';
+import { FAQSection } from '@/app/_components/landing-page/faq-section';
+import { OnlookInterfaceMockup } from '@/app/_components/landing-page/onlook-interface-mockup';
+import { useGitHubStats } from '@/app/_components/top-bar/github';
+import { WebsiteLayout } from '@/app/_components/website-layout';

As per coding guidelines, "apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx` around lines 10 - 16,
Replace the relative imports in this file with the project path aliases: change
the imports for UnicornBackground, CTASection, FAQSection,
OnlookInterfaceMockup, useGitHubStats and WebsiteLayout to use the configured
alias (e.g. "@/..." or "~/*") that maps to apps/web/client/src, so each import
uses the alias root instead of ../../..; update each import statement
accordingly and ensure your tsconfig/webpack alias matches if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx`:
- Around line 17-419: The page contains hardcoded user-facing strings (e.g.,
vibeCodingFaqs array, text inside VibeCodingHero, headings/paragraphs in
VibeCodingWorkflowPage, CTASection props and FAQSection call) which must be
moved to next-intl messages; replace each literal with a call to the i18n
message hook (useTranslations or t) and reference message keys (e.g.,
"vibeCoding.hero.title", "vibeCoding.faq.0.question", "vibeCoding.cta.text") and
pass translated strings into FAQSection and CTASection instead of raw literals;
extract the vibeCodingFaqs data into a localized factory that reads from
messages and ensure VibeCodingHero uses translations for starCount label and all
<motion> text nodes.

---

Nitpick comments:
In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx`:
- Around line 10-16: Replace the relative imports in this file with the project
path aliases: change the imports for UnicornBackground, CTASection, FAQSection,
OnlookInterfaceMockup, useGitHubStats and WebsiteLayout to use the configured
alias (e.g. "@/..." or "~/*") that maps to apps/web/client/src, so each import
uses the alias root instead of ../../..; update each import statement
accordingly and ensure your tsconfig/webpack alias matches if needed.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8995079 and f412b6f.

📒 Files selected for processing (2)
  • apps/web/client/src/app/workflows/claude-code/page.tsx
  • apps/web/client/src/app/workflows/vibe-coding/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/client/src/app/workflows/claude-code/page.tsx

Comment on lines +17 to +419
const vibeCodingFaqs = [
{
question: 'What is vibe coding?',
answer: "Vibe coding is a workflow where you describe what you want in natural language and AI generates the code. Most vibe coding tools are designed for solo use, not teams.",
},
{
question: 'What is the problem with vibe coding today?',
answer: "Most vibe coding tools are solo workflows. You can't easily share work-in-progress, collaborate in real-time, or hand off to engineers. The output is often throwaway code that doesn't match your design system.",
},
{
question: 'How does Onlook make vibe coding work for teams?',
answer: 'Onlook adds a visual canvas layer. Share your canvas with teammates, leave spatial comments, work together in real-time. AI is constrained to your design system, so outputs are consistent. Changes become PRs engineers can merge.',
},
{
question: 'Can I use my existing vibe coding tools with Onlook?',
answer: 'Yes. Use any AI coding tool to build. Open in Onlook to visually iterate, collaborate with your team, and refine before shipping. Onlook works with your existing codebase.',
},
{
question: 'Does vibe coding in Onlook use my real components?',
answer: "Yes. Unlike tools that generate generic HTML, Onlook connects to your component library. AI uses YOUR buttons, cards, and layouts. No brand drift, no rebuilding.",
},
{
question: 'How do I share vibe-coded work with my team?',
answer: 'Share your canvas link. Teammates can view, comment spatially, and collaborate in real-time. When ready, submit changes as a PR for engineers to review.',
},
];

// Helper function for blur animations
const getBlurAnimationProps = (delay: number = 0) => ({
initial: { opacity: 0, filter: 'blur(4px)' },
whileInView: { opacity: 1, filter: 'blur(0px)' },
viewport: { once: true, margin: '-100px 0px -100px 0px', amount: 0.3 },
transition: {
duration: 0.6,
delay,
ease: [0.25, 0.46, 0.45, 0.94] as const,
},
style: {
willChange: 'opacity, filter',
transform: 'translateZ(0)',
},
});

function VibeCodingHero() {
const { formatted: starCount } = useGitHubStats();

return (
<div className="relative flex h-full w-full flex-col items-center justify-center gap-12 p-8 text-center text-lg">
<UnicornBackground />
<div className="relative z-20 flex max-w-3xl flex-col items-center gap-6 pt-4 pb-2">
<motion.h1
className="text-foreground-secondary mb-4 text-sm font-medium tracking-wider uppercase"
initial={{ opacity: 0, filter: 'blur(4px)' }}
animate={{ opacity: 1, filter: 'blur(0px)' }}
transition={{ duration: 0.6, ease: 'easeOut' }}
style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }}
>
Vibe Coding for Teams
</motion.h1>
<motion.p
className="text-center text-4xl !leading-[1] leading-tight font-light text-balance md:text-6xl"
initial={{ opacity: 0, filter: 'blur(4px)' }}
animate={{ opacity: 1, filter: 'blur(0px)' }}
transition={{ duration: 0.6, delay: 0.1, ease: 'easeOut' }}
style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }}
>
Vibe coding has a collaboration problem
</motion.p>
<motion.p
className="text-foreground-secondary mx-auto max-w-xl text-center text-lg"
initial={{ opacity: 0, filter: 'blur(4px)' }}
animate={{ opacity: 1, filter: 'blur(0px)' }}
transition={{ duration: 0.6, delay: 0.15, ease: 'easeOut' }}
style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }}
>
Most AI coding tools are solo workflows. Onlook adds the missing layer —
a visual canvas where teams collaborate on AI-generated UIs with their real components.
</motion.p>
<motion.div
className="mt-8"
initial={{ opacity: 0, filter: 'blur(4px)' }}
animate={{ opacity: 1, filter: 'blur(0px)' }}
transition={{ duration: 0.6, delay: 0.3, ease: 'easeOut' }}
style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }}
>
<Button
asChild
variant="secondary"
size="lg"
className="hover:bg-foreground-primary hover:text-background-primary cursor-pointer p-6 transition-all duration-300"
>
<a href={ExternalRoutes.BOOK_DEMO} target="_blank" rel="noopener noreferrer">
Book a Demo
</a>
</Button>
</motion.div>
<motion.div
className="text-foreground-secondary mt-8 flex items-center justify-center gap-6 text-sm"
initial={{ opacity: 0, filter: 'blur(4px)' }}
animate={{ opacity: 1, filter: 'blur(0px)' }}
transition={{ duration: 0.6, delay: 0.4, ease: 'easeOut' }}
style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }}
>
<div className="flex items-center gap-2">
<span>{starCount}+ GitHub stars</span>
</div>
<div className="bg-foreground-secondary h-1 w-1 rounded-full"></div>
<div className="flex items-center gap-2">
<span>YC W25</span>
</div>
<div className="bg-foreground-secondary h-1 w-1 rounded-full"></div>
<div className="flex items-center gap-2">
<span>Open Source</span>
</div>
</motion.div>
</div>
</div>
);
}

export default function VibeCodingWorkflowPage() {
return (
<CreateManagerProvider>
<WebsiteLayout showFooter={true}>
{/* AI-Friendly Summary Section */}
<section className="sr-only" aria-label="Vibe Coding Workflow Summary">
<h1>Vibe Coding for Teams: Add Collaboration to Your AI Coding Workflow</h1>
<p>
Vibe coding — describing what you want and letting AI generate the code — is powerful but has
a collaboration problem. Most AI coding tools are solo workflows. You can't
easily share work-in-progress, collaborate in real-time, or ensure outputs match your design
system. Onlook solves this.
</p>
<h2>The Problem with Solo Vibe Coding</h2>
<ul>
<li>Solo workflow — hard to share work-in-progress with teammates</li>
<li>Throwaway code — doesn't use your real components</li>
<li>Brand drift — AI generates generic HTML/CSS, not your design system</li>
<li>No handoff path — "now how do I share this?" becomes a blocker</li>
</ul>
<h2>Onlook Makes Vibe Coding Work for Teams</h2>
<ul>
<li>Visual canvas — see and arrange AI-generated UIs spatially</li>
<li>Your real components — AI constrained to your design system</li>
<li>Real-time collaboration — share canvas, leave spatial comments</li>
<li>PR output — changes become mergeable pull requests</li>
<li>Works with any AI coding tool you already use</li>
</ul>
</section>

{/* Hero Section */}
<div className="flex h-screen w-screen items-center justify-center" id="hero">
<VibeCodingHero />
</div>

{/* The Problem Section */}
<section className="w-full bg-black py-32">
<div className="mx-auto max-w-6xl px-8">
<motion.h2
className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider"
{...getBlurAnimationProps()}
>
The Problem
</motion.h2>
<motion.p
className="mb-16 max-w-3xl text-4xl font-light leading-tight text-balance md:text-5xl"
{...getBlurAnimationProps(0.1)}
>
Solo vibe coding doesn't scale. Teams need to share, iterate, and ship together.
</motion.p>

<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-4">
{[
{
icon: Icons.Person,
title: 'Solo workflow',
description: 'Most AI coding tools are designed for individual use. Sharing means screenshots or screen shares.',
},
{
icon: Icons.Trash,
title: 'Throwaway code',
description: "AI generates generic HTML/CSS. You'll rebuild it anyway to use your real components.",
},
{
icon: Icons.Brand,
title: 'Brand drift',
description: "Every vibe-coded UI looks different. AI doesn't know your design system.",
},
{
icon: Icons.QuestionMarkCircled,
title: '"Now what?"',
description: 'You built something cool. Now how do you share it, get feedback, or hand it off?',
},
].map((item, index) => (
<motion.div
key={item.title}
className="flex flex-col gap-4"
{...getBlurAnimationProps(0.2 + index * 0.1)}
>
<item.icon className="text-foreground-secondary h-5 w-5" />
<h3 className="text-base font-medium text-balance">{item.title}</h3>
<p className="text-foreground-secondary text-base text-balance">{item.description}</p>
</motion.div>
))}
</div>
</div>
</section>

{/* The Solution Section */}
<section className="w-full bg-black pt-32 pb-16">
<div className="mx-auto max-w-6xl px-8">
<motion.h2
className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider"
{...getBlurAnimationProps()}
>
The Solution
</motion.h2>
<motion.p
className="mb-24 max-w-3xl text-4xl font-light leading-tight text-balance md:text-5xl"
{...getBlurAnimationProps(0.1)}
>
Onlook adds the visual layer. Vibe code with your team, on your components, to real PRs.
</motion.p>
</div>

{/* Editor Mockup - Desktop */}
<motion.div
className="hidden md:block w-screen h-[44rem] items-center justify-center mb-24"
{...getBlurAnimationProps(0.2)}
>
<OnlookInterfaceMockup />
</motion.div>

{/* Editor Mockup - Mobile */}
<motion.div
className="md:hidden w-screen relative overflow-hidden h-[500px]"
{...getBlurAnimationProps(0.2)}
>
<div className="absolute top-0 right-10 h-[800px] w-[1000px]">
<OnlookInterfaceMockup />
</div>
</motion.div>

<div className="mx-auto max-w-6xl px-8">
<div className="grid gap-8 md:grid-cols-4">
{[
{
icon: Icons.Layers,
title: 'Visual canvas',
description: 'See and arrange AI-generated UIs spatially. Not just terminal output.',
},
{
icon: Icons.Component,
title: 'Your real components',
description: 'AI uses your buttons, cards, layouts. Not generic HTML.',
},
{
icon: Icons.Person,
title: 'Team collaboration',
description: 'Share canvas, leave spatial comments, work together in real-time.',
},
{
icon: Icons.Branch,
title: 'Ship PRs',
description: 'Changes become pull requests. Engineers review and merge.',
},
].map((item, index) => (
<motion.div
key={item.title}
className="flex flex-col gap-3"
{...getBlurAnimationProps(0.3 + index * 0.1)}
>
<item.icon className="text-foreground-secondary h-5 w-5" />
<h3 className="text-base font-medium text-balance">{item.title}</h3>
<p className="text-foreground-secondary text-sm text-balance">{item.description}</p>
</motion.div>
))}
</div>
</div>
</section>

{/* How It Works Section */}
<section className="w-full bg-black py-32">
<div className="mx-auto max-w-6xl px-8">
<motion.h2
className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider"
{...getBlurAnimationProps()}
>
How It Works
</motion.h2>
<motion.p
className="mb-16 max-w-3xl text-4xl font-light leading-tight text-balance md:text-5xl"
{...getBlurAnimationProps(0.1)}
>
Vibe code anywhere. Collaborate in Onlook. Ship together.
</motion.p>

<div className="grid gap-12 md:grid-cols-3">
{[
{
step: '01',
title: 'Build with any AI tool',
description: 'Use any AI coding tool to generate your initial UI. Onlook works with your existing codebase.',
},
{
step: '02',
title: 'Iterate on the canvas',
description: 'Open in Onlook to visually refine. Drag elements, adjust styles, use AI with your real components. See changes in real code.',
},
{
step: '03',
title: 'Collaborate and ship',
description: 'Share your canvas with teammates. Leave spatial comments. When ready, submit as a PR for engineers to review and merge.',
},
].map((item, index) => (
<motion.div
key={item.step}
className="flex flex-col gap-4"
{...getBlurAnimationProps(0.2 + index * 0.1)}
>
<span className="text-foreground-tertiary text-sm font-medium">{item.step}</span>
<h3 className="text-xl font-medium">{item.title}</h3>
<p className="text-foreground-secondary text-base text-balance">{item.description}</p>
</motion.div>
))}
</div>
</div>
</section>

{/* Comparison Section */}
<section className="w-full bg-black py-32">
<div className="mx-auto max-w-6xl px-8">
<motion.h2
className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider"
{...getBlurAnimationProps()}
>
Vibe Coding: Solo vs. Team
</motion.h2>
<motion.p
className="mb-16 max-w-3xl text-4xl font-light leading-tight text-balance md:text-5xl"
{...getBlurAnimationProps(0.1)}
>
The difference between prototyping alone and shipping with your team.
</motion.p>

<motion.div
className="grid gap-8 md:grid-cols-2"
{...getBlurAnimationProps(0.2)}
>
{/* Solo Column */}
<div className="border-foreground-primary/10 rounded-lg border p-8">
<h3 className="text-foreground-tertiary mb-6 text-sm font-medium uppercase tracking-wider">
Solo Vibe Coding
</h3>
<ul className="space-y-4">
{[
'Generate generic HTML/CSS',
'Share via screenshots',
'Rebuild to use your components',
'Manual handoff process',
'Code needs translation',
].map((item) => (
<li key={item} className="text-foreground-secondary flex items-start gap-3">
<Icons.CrossCircled className="text-foreground-tertiary mt-0.5 h-5 w-5 flex-shrink-0" />
<span>{item}</span>
</li>
))}
</ul>
</div>

{/* Team Column */}
<div className="border-foreground-primary/30 rounded-lg border bg-gradient-to-b from-white/5 to-transparent p-8">
<h3 className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider">
Team Vibe Coding with Onlook
</h3>
<ul className="space-y-4">
{[
'Use your real components',
'Share a live canvas',
'AI constrained to your design system',
'Collaborate with spatial comments',
'Ship PRs engineers can merge',
].map((item) => (
<li key={item} className="text-foreground-primary flex items-start gap-3">
<Icons.CheckCircled className="text-teal-500 mt-0.5 h-5 w-5 flex-shrink-0" />
<span>{item}</span>
</li>
))}
</ul>
</div>
</motion.div>
</div>
</section>

{/* FAQ Section */}
<FAQSection faqs={vibeCodingFaqs} title="Frequently asked questions" />

{/* CTA Section */}
<CTASection
ctaText={`Ready to vibe code\nwith your team?`}
buttonText="Book a Demo"
href={ExternalRoutes.BOOK_DEMO}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Move user-facing copy to next-intl messages instead of hardcoding strings.

This page embeds large amounts of display copy directly in TSX (FAQ content, headings, paragraph text, labels, CTA copy), which breaks the repo i18n rule and makes localization/error-proof copy updates harder.

Suggested direction (partial)
+import { useTranslations } from 'next-intl';

-const vibeCodingFaqs = [
-  { question: 'What is vibe coding?', answer: '...' },
-  ...
-];

export default function VibeCodingWorkflowPage() {
+  const t = useTranslations('workflows.vibeCoding');
+  const vibeCodingFaqs = [
+    { question: t('faq.items.whatIs.question'), answer: t('faq.items.whatIs.answer') },
+    // ...
+  ];

  return (
    ...
-   <h1>Vibe Coding for Teams: Add Collaboration to Your AI Coding Workflow</h1>
+   <h1>{t('summary.title')}</h1>
    ...
-   <FAQSection faqs={vibeCodingFaqs} title="Frequently asked questions" />
+   <FAQSection faqs={vibeCodingFaqs} title={t('faq.title')} />
    ...
-   <CTASection ctaText={`Ready to vibe code\nwith your team?`} buttonText="Book a Demo" ... />
+   <CTASection ctaText={t('cta.text')} buttonText={t('cta.button')} ... />

As per coding guidelines, "apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx` around lines 17 -
419, The page contains hardcoded user-facing strings (e.g., vibeCodingFaqs
array, text inside VibeCodingHero, headings/paragraphs in
VibeCodingWorkflowPage, CTASection props and FAQSection call) which must be
moved to next-intl messages; replace each literal with a call to the i18n
message hook (useTranslations or t) and reference message keys (e.g.,
"vibeCoding.hero.title", "vibeCoding.faq.0.question", "vibeCoding.cta.text") and
pass translated strings into FAQSection and CTASection instead of raw literals;
extract the vibeCodingFaqs data into a localized factory that reads from
messages and ensure VibeCodingHero uses translations for starCount label and all
<motion> text nodes.

@drfarrell drfarrell merged commit a242be5 into main Feb 27, 2026
6 of 7 checks passed
@drfarrell drfarrell deleted the website-v1.5 branch February 27, 2026 02:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant