Skip to content

Commit 036baf3

Browse files
authored
Merge pull request #995 from SonicJs-Org/lane711/rework-why-switch-section
Rework why-switch section + copyable hero command
2 parents 350ef62 + f0c1b30 commit 036baf3

7 files changed

Lines changed: 109 additions & 31 deletions

File tree

tests/e2e/94-marketing-home.spec.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,14 @@ test.describe('Marketing Home Page', () => {
116116
await expect(discordLinks.first()).toBeVisible()
117117
})
118118

119-
test('why-switch cards render with overlaid text on image background', async ({ page }) => {
119+
test('why-switch cards render as icon cards (no background images)', async ({ page }) => {
120120
await page.goto('/')
121121
await expect(
122122
page.getByRole('heading', { name: 'Why Developers Switch to SonicJS' }),
123123
).toBeVisible()
124-
// Pain-card background image present (Next fill image keeps alt text)
125-
await expect(page.getByAltText('Seamless migration visualization')).toBeVisible()
126-
// All four card titles overlaid on the images
124+
// AI-generated pain images removed — assets 404 and no alt text remains
125+
await expect(page.getByAltText('Seamless migration visualization')).toHaveCount(0)
126+
// All four card titles still render
127127
await expect(page.getByRole('heading', { name: 'No More Migration Hell' })).toBeVisible()
128128
await expect(
129129
page.getByRole('heading', { name: 'The Features You Need Are Paywalled' }),
@@ -151,6 +151,26 @@ test.describe('Marketing Home Page', () => {
151151
await expect(page.getByText(/mcpServers/)).toBeVisible()
152152
})
153153

154+
test('hero install command is copyable to clipboard', async ({ page, context }) => {
155+
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
156+
await page.goto('/')
157+
158+
// Command text visible in hero
159+
await expect(page.getByText('npx create-sonicjs@latest my-app').first()).toBeVisible()
160+
161+
// Click the first Copy button (hero)
162+
const copyButton = page.getByRole('button', { name: /copy command/i }).first()
163+
await expect(copyButton).toBeVisible()
164+
await copyButton.click()
165+
166+
// Button flips to "Copied" confirmation
167+
await expect(page.getByRole('button', { name: /copied/i }).first()).toBeVisible()
168+
169+
// Clipboard holds the install command
170+
const clip = await page.evaluate(() => navigator.clipboard.readText())
171+
expect(clip).toBe('npx create-sonicjs@latest my-app')
172+
})
173+
154174
test('four pillars section shows the wedge heading', async ({ page }) => {
155175
await page.goto('/')
156176
await expect(
-2.25 MB
Binary file not shown.
-2.32 MB
Binary file not shown.
-2.22 MB
Binary file not shown.
-2.98 MB
Binary file not shown.

www/src/app/page.tsx

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Image from 'next/image'
22
import Link from 'next/link'
3+
import { CopyCommand } from '@/components/CopyCommand'
34

45
function GridBackdrop({ patternId }: { patternId: string }) {
56
return (
@@ -171,9 +172,7 @@ export default function HomePage() {
171172
<p className="mt-3 text-xs text-gray-500">
172173
No signup required — instant access to real admin
173174
</p>
174-
<div className="mt-8 rounded-lg bg-white/5 px-4 py-3 font-mono text-sm text-gray-300 ring-1 ring-white/10">
175-
$ npx create-sonicjs@latest my-app
176-
</div>
175+
<CopyCommand command="npx create-sonicjs@latest my-app" className="mt-8" />
177176
<div className="mt-6 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-gray-500">
178177
<span>MIT licensed</span>
179178
<span aria-hidden="true">·</span>
@@ -326,8 +325,8 @@ export default function HomePage() {
326325
<div className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 md:grid-cols-2 lg:max-w-none">
327326
{[
328327
{
329-
src: '/images/home/pain-migration.png',
330-
alt: 'Seamless migration visualization',
328+
icon: ICONS.arrowsRightLeft,
329+
accent: 'text-cyan-400',
331330
title: 'No More Migration Hell',
332331
body: (
333332
<>
@@ -340,8 +339,8 @@ export default function HomePage() {
340339
'SonicJS: semantic versioning with automatic, versioned migrations — upgrades stay boring.',
341340
},
342341
{
343-
src: '/images/home/pain-pricing.png',
344-
alt: 'Cost efficiency visualization',
342+
icon: ICONS.lockOpen,
343+
accent: 'text-emerald-400',
345344
title: 'The Features You Need Are Paywalled',
346345
body: (
347346
<>
@@ -356,8 +355,8 @@ export default function HomePage() {
356355
'SonicJS: every feature in the MIT core. No Growth tier. No Enterprise gate. Ever.',
357356
},
358357
{
359-
src: '/images/home/pain-latency.png',
360-
alt: 'Low-latency global routing visualization',
358+
icon: ICONS.bolt,
359+
accent: 'text-amber-400',
361360
title: '0ms Cold Start, Sub-50ms Worldwide',
362361
body: (
363362
<>
@@ -371,8 +370,8 @@ export default function HomePage() {
371370
'SonicJS: V8 isolates in 300+ edge cities — no boot penalty, responses served near the user.',
372371
},
373372
{
374-
src: '/images/home/pain-coldstart.png',
375-
alt: 'Glowing AI circuitry stack visualization',
373+
icon: ICONS.sparkles,
374+
accent: 'text-fuchsia-400',
376375
title: 'AI Included, Not Upsold',
377376
body: (
378377
<>
@@ -387,21 +386,18 @@ export default function HomePage() {
387386
].map((card) => (
388387
<div
389388
key={card.title}
390-
className="group relative flex min-h-[22rem] flex-col justify-end overflow-hidden rounded-2xl p-6 ring-1 ring-white/10 transition-shadow duration-300 hover:shadow-2xl"
389+
className="group relative flex min-h-[18rem] flex-col overflow-hidden rounded-2xl bg-white/[0.02] p-8 ring-1 ring-white/10 transition-all duration-300 hover:bg-white/[0.04] hover:ring-white/20"
391390
>
392-
<Image
393-
src={card.src}
394-
alt={card.alt}
395-
fill
396-
sizes="(min-width: 768px) 50vw, 100vw"
397-
className="object-cover transition-transform duration-500 group-hover:scale-105"
398-
/>
399-
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/60 to-black/25" />
400-
<div className="relative">
401-
<h3 className="mb-2 text-xl font-semibold text-white">{card.title}</h3>
402-
<p className="mb-2 text-sm text-gray-100">{card.body}</p>
403-
<p className="text-xs italic text-gray-200">{card.footer}</p>
391+
<div
392+
className={`mb-6 flex size-11 items-center justify-center rounded-xl bg-white/5 ring-1 ring-white/10 ${card.accent}`}
393+
>
394+
<FeatureIcon d={card.icon} />
404395
</div>
396+
<h3 className="mb-3 text-xl font-semibold text-white">{card.title}</h3>
397+
<p className="mb-4 flex-auto text-sm/6 text-gray-300">{card.body}</p>
398+
<p className="border-t border-white/10 pt-4 text-sm/6 font-medium text-cyan-300">
399+
{card.footer}
400+
</p>
405401
</div>
406402
))}
407403
</div>
@@ -856,9 +852,10 @@ GET /api/products?category=electronics&sort=-price&limit=20`}</pre>
856852
<p className="mx-auto mt-6 max-w-xl text-pretty text-lg/8 text-gray-300">
857853
MIT licensed. No credit card. No paywalls. Your first global deploy is minutes away.
858854
</p>
859-
<div className="mx-auto mt-8 max-w-md rounded-lg bg-white/5 px-6 py-4 text-left font-mono text-sm text-gray-300 ring-1 ring-white/10">
860-
$ npx create-sonicjs@latest my-app
861-
</div>
855+
<CopyCommand
856+
command="npx create-sonicjs@latest my-app"
857+
className="mx-auto mt-8 max-w-md"
858+
/>
862859
<div className="mt-10 flex items-center justify-center gap-x-6">
863860
<Link
864861
href="https://discord.gg/8bMy6bv3sZ"

www/src/components/CopyCommand.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
5+
export function CopyCommand({
6+
command,
7+
className = '',
8+
}: {
9+
command: string
10+
className?: string
11+
}) {
12+
const [copied, setCopied] = useState(false)
13+
14+
async function copy() {
15+
try {
16+
await navigator.clipboard.writeText(command)
17+
setCopied(true)
18+
setTimeout(() => setCopied(false), 2000)
19+
} catch {
20+
// clipboard unavailable — no-op
21+
}
22+
}
23+
24+
return (
25+
<div
26+
className={`flex items-center justify-between gap-4 rounded-lg bg-white/5 px-4 py-3 font-mono text-sm text-gray-300 ring-1 ring-white/10 ${className}`}
27+
>
28+
<code className="truncate">
29+
<span className="select-none text-gray-500">$ </span>
30+
{command}
31+
</code>
32+
<button
33+
type="button"
34+
onClick={copy}
35+
aria-label={copied ? 'Copied' : 'Copy command'}
36+
className="-my-1 flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-gray-400 transition-colors hover:bg-white/5 hover:text-white"
37+
>
38+
{copied ? (
39+
<>
40+
<svg viewBox="0 0 20 20" fill="currentColor" className="size-4 text-emerald-400">
41+
<path
42+
fillRule="evenodd"
43+
d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z"
44+
clipRule="evenodd"
45+
/>
46+
</svg>
47+
<span className="text-emerald-400">Copied</span>
48+
</>
49+
) : (
50+
<>
51+
<svg viewBox="0 0 20 20" fill="currentColor" className="size-4">
52+
<path d="M7 3.5A1.5 1.5 0 0 1 8.5 2h3.879a1.5 1.5 0 0 1 1.06.44l3.122 3.12A1.5 1.5 0 0 1 17 6.622V12.5a1.5 1.5 0 0 1-1.5 1.5h-1v-3.379a3 3 0 0 0-.879-2.121L10.5 5.379A3 3 0 0 0 8.379 4.5H7v-1Z" />
53+
<path d="M4.5 6A1.5 1.5 0 0 0 3 7.5v9A1.5 1.5 0 0 0 4.5 18h7a1.5 1.5 0 0 0 1.5-1.5v-5.879a1.5 1.5 0 0 0-.44-1.06L9.44 6.439A1.5 1.5 0 0 0 8.378 6H4.5Z" />
54+
</svg>
55+
<span>Copy</span>
56+
</>
57+
)}
58+
</button>
59+
</div>
60+
)
61+
}

0 commit comments

Comments
 (0)