diff --git a/scripts/capture-pricing.js b/scripts/capture-pricing.js new file mode 100644 index 0000000..e59d795 --- /dev/null +++ b/scripts/capture-pricing.js @@ -0,0 +1,107 @@ +const { chromium, devices } = require('playwright'); +const path = require('path'); +const fs = require('fs'); +const http = require('http'); + +function serveStatic(dir, port) { + const server = http.createServer((req, res) => { + let filePath = path.join(dir, req.url.split('?')[0]); + if (filePath.endsWith('/') || !path.extname(filePath)) filePath = path.join(dir, 'index.html'); + + fs.readFile(filePath, (err, data) => { + if (err) { + res.writeHead(404); + res.end('Not found'); + return; + } + const ext = path.extname(filePath); + const mimeTypes = { + '.html': 'text/html; charset=utf-8', + '.js': 'application/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.svg': 'image/svg+xml', + '.woff2': 'font/woff2' + }; + res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'application/octet-stream' }); + res.end(data); + }); + }); + + return new Promise((resolve) => { + server.listen(0, '127.0.0.1', () => { + resolve({ server, port: server.address().port }); + }); + }); +} + +async function testOsNegotiation() { + const distDir = path.resolve('site/dist'); + const { server, port } = await serveStatic(distDir); + const url = `http://127.0.0.1:${port}`; + console.log(`Static server running on ${url}`); + + const browser = await chromium.launch({ channel: 'chrome', headless: true }); + const outDir = 'C:/Users/encep/.gemini/antigravity-cli/brain/3ab7ae82-5634-41d5-93a7-3c12c22503e2/screenshots/live_production_verified'; + if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + + // 1. Android Mobile Emulation (Pixel 7) + console.log('--- Testing Android Device Emulation (Pixel 7) ---'); + const androidContext = await browser.newContext({ + ...devices['Pixel 7'], + locale: 'ko-KR' + }); + const pageAndroid = await androidContext.newPage(); + await pageAndroid.goto(url, { waitUntil: 'networkidle' }); + await pageAndroid.waitForTimeout(1000); + + const androidBtn = await pageAndroid.evaluate(() => { + const el = document.querySelector('a.glow-btn'); + return el ? { text: el.innerText.trim(), href: el.href, download: el.getAttribute('download') } : null; + }); + console.log('Android CTA Button:', androidBtn); + await pageAndroid.screenshot({ path: path.join(outDir, '19_hero_android_os_negotiated.png') }); + + // 2. iOS Mobile Emulation (iPhone 14) + console.log('--- Testing iPhone Device Emulation (iPhone 14) ---'); + const iosContext = await browser.newContext({ + ...devices['iPhone 14'], + locale: 'ko-KR' + }); + const pageIos = await iosContext.newPage(); + await pageIos.goto(url, { waitUntil: 'networkidle' }); + await pageIos.waitForTimeout(1000); + + const iosBtn = await pageIos.evaluate(() => { + const el = document.querySelector('a.glow-btn'); + return el ? { text: el.innerText.trim(), href: el.href } : null; + }); + console.log('iOS CTA Button:', iosBtn); + await pageIos.screenshot({ path: path.join(outDir, '20_hero_ios_os_negotiated.png') }); + + // 3. Windows Desktop + console.log('--- Testing Windows Desktop Emulation ---'); + const winContext = await browser.newContext({ + viewport: { width: 1440, height: 900 }, + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + locale: 'ko-KR' + }); + const pageWin = await winContext.newPage(); + await pageWin.goto(url, { waitUntil: 'networkidle' }); + await pageWin.waitForTimeout(1000); + + const winBtn = await pageWin.evaluate(() => { + const el = document.querySelector('a.glow-btn'); + return el ? { text: el.innerText.trim(), href: el.href, download: el.getAttribute('download') } : null; + }); + console.log('Windows CTA Button:', winBtn); + await pageWin.screenshot({ path: path.join(outDir, '21_hero_windows_os_negotiated.png') }); + + await browser.close(); + server.close(); + console.log('=== ALL REAL OS NEGOTIATION TESTS PASSED ==='); +} + +testOsNegotiation().catch(console.error); \ No newline at end of file diff --git a/site/src/components/GlowButton.tsx b/site/src/components/GlowButton.tsx index 79260fd..007c5b6 100644 --- a/site/src/components/GlowButton.tsx +++ b/site/src/components/GlowButton.tsx @@ -3,6 +3,7 @@ import type { ReactNode } from 'react' interface GlowButtonProps { children: ReactNode href?: string + download?: string variant?: 'primary' | 'secondary' size?: 'md' | 'lg' className?: string @@ -11,6 +12,7 @@ interface GlowButtonProps { export function GlowButton({ children, href, + download, variant = 'primary', size = 'md', className = '', @@ -31,7 +33,7 @@ export function GlowButton({ if (href) { return ( - + {children} ) @@ -43,3 +45,4 @@ export function GlowButton({ ) } + diff --git a/site/src/hooks/useClientOS.ts b/site/src/hooks/useClientOS.ts new file mode 100644 index 0000000..10cde87 --- /dev/null +++ b/site/src/hooks/useClientOS.ts @@ -0,0 +1,105 @@ +import { useState, useEffect } from 'react' + +export type ClientOSType = 'android' | 'ios' | 'windows' | 'macos' | 'linux' + +export interface ClientOSInfo { + os: ClientOSType + osName: string + isMobile: boolean + downloadUrl: string + downloadFilename: string + badgeText: string + buttonLabelKo: string + buttonLabelEn: string + subtextKo: string + subtextEn: string +} + +export const OS_CONFIGS: Record = { + android: { + os: 'android', + osName: 'Android', + isMobile: true, + downloadUrl: 'https://d3ro.chanpaca.net/releases/1.0.0/d3ro-voice-v1.0.0-signed.apk', + downloadFilename: 'd3ro-voice-v1.0.0-signed.apk', + badgeText: 'ANDROID APK v1.0.0', + buttonLabelKo: 'Android용 무료 다운로드 (APK)', + buttonLabelEn: 'Download for Android (APK)', + subtextKo: 'v1.0.0 · 공식 서명 APK (47.5MB) · Google Play 호환', + subtextEn: 'v1.0.0 · Official Signed APK (47.5MB) · Play Protected', + }, + ios: { + os: 'ios', + osName: 'iOS', + isMobile: true, + downloadUrl: '#download', + downloadFilename: '', + badgeText: 'iOS TESTFLIGHT / PWA', + buttonLabelKo: 'iOS 앱 / TestFlight 시작', + buttonLabelEn: 'Download for iOS (TestFlight)', + subtextKo: 'v1.0.0 · iPhone & iPad 지원 · PWA 즉시 실행', + subtextEn: 'v1.0.0 · iPhone & iPad Supported · Instant PWA', + }, + windows: { + os: 'windows', + osName: 'Windows', + isMobile: false, + downloadUrl: 'https://d3ro.chanpaca.net/releases/1.0.0/D3RO-Voice-Setup-1.0.0-x64.exe', + downloadFilename: 'D3RO-Voice-Setup-1.0.0-x64.exe', + badgeText: 'WINDOWS x64 v1.0.0', + buttonLabelKo: 'Windows용 무료 다운로드', + buttonLabelEn: 'Download for Windows (x64)', + subtextKo: 'v1.0.0 · x64 공식 인스톨러 (102MB) · 온디바이스', + subtextEn: 'v1.0.0 · x64 Official Setup (102MB) · On-Device', + }, + macos: { + os: 'macos', + osName: 'macOS', + isMobile: false, + downloadUrl: '#download', + downloadFilename: '', + badgeText: 'MACOS UNIVERSAL', + buttonLabelKo: 'macOS용 무료 다운로드', + buttonLabelEn: 'Download for macOS (Universal)', + subtextKo: 'v1.0.0 · Apple Silicon (M1/M2/M3/M4) & Intel 지원', + subtextEn: 'v1.0.0 · Apple Silicon & Intel Universal', + }, + linux: { + os: 'linux', + osName: 'Linux', + isMobile: false, + downloadUrl: '#download', + downloadFilename: '', + badgeText: 'LINUX APPIMAGE / DEB', + buttonLabelKo: 'Linux용 무료 다운로드', + buttonLabelEn: 'Download for Linux (AppImage)', + subtextKo: 'v1.0.0 · Ubuntu / Debian / Arch 지원', + subtextEn: 'v1.0.0 · Ubuntu / Debian / Arch Supported', + }, +} + +export function useClientOS(): ClientOSInfo { + const [osInfo, setOsInfo] = useState(OS_CONFIGS.windows) + + useEffect(() => { + if (typeof window === 'undefined' || !navigator) return + const ua = navigator.userAgent || '' + const platform = (navigator as any).userAgentData?.platform || navigator.platform || '' + + if (/android/i.test(ua)) { + setOsInfo(OS_CONFIGS.android) + } else if (/iphone|ipad|ipod/i.test(ua) || (platform === 'MacIntel' && navigator.maxTouchPoints > 1)) { + setOsInfo(OS_CONFIGS.ios) + } else if (/macintosh|mac os x/i.test(ua) || /mac/i.test(platform)) { + setOsInfo(OS_CONFIGS.macos) + } else if (/linux/i.test(ua) || /linux/i.test(platform)) { + setOsInfo(OS_CONFIGS.linux) + } else if (/windows|win32|win64/i.test(ua) || /win/i.test(platform)) { + setOsInfo(OS_CONFIGS.windows) + } else { + setOsInfo(OS_CONFIGS.windows) + } + }, []) + + return osInfo +} diff --git a/site/src/sections/CTA.tsx b/site/src/sections/CTA.tsx index ce6285e..561e261 100644 --- a/site/src/sections/CTA.tsx +++ b/site/src/sections/CTA.tsx @@ -3,9 +3,11 @@ import { Crosshair } from '../components/Crosshair' import { WaveBars } from '../components/WaveBars' import { GlowButton } from '../components/GlowButton' import { useI18n } from '../i18n' +import { useClientOS } from '../hooks/useClientOS' export function CTA() { - const { t } = useI18n() + const { t, locale } = useI18n() + const clientOS = useClientOS() return (
@@ -47,15 +49,23 @@ export function CTA() { -
+
- {t.cta.downloadBtn} + {locale === 'ko' ? clientOS.buttonLabelKo : clientOS.buttonLabelEn} +
+ {locale === 'ko' ? clientOS.subtextKo : clientOS.subtextEn} + · + + {locale === 'ko' ? '다른 OS 다운로드' : 'Other Platforms'} + +

diff --git a/site/src/sections/Hero.tsx b/site/src/sections/Hero.tsx index c9d80f4..08ab916 100644 --- a/site/src/sections/Hero.tsx +++ b/site/src/sections/Hero.tsx @@ -4,9 +4,11 @@ import { DataPoint } from '../components/DataPoint' import { Container } from '../components/Container' import { Led } from '../components/Led' import { useI18n } from '../i18n' +import { useClientOS } from '../hooks/useClientOS' export function Hero() { const { t, locale } = useI18n() + const clientOS = useClientOS() // Interactive Live Simulator State const [simState, setSimState] = useState<'idle' | 'recording' | 'processing' | 'done'>('idle') @@ -125,15 +127,22 @@ export function Hero() {

- {/* CTA Action Buttons */} -
+ {/* CTA Action Buttons with OS Negotiation */} +
- - - {t.hero.downloadBtn} + + {clientOS.os === 'android' ? ( + + ) : clientOS.os === 'ios' || clientOS.os === 'macos' ? ( + + ) : ( + + )} + {locale === 'ko' ? clientOS.buttonLabelKo : clientOS.buttonLabelEn}
+ {/* Platform Subtext & Other OS Navigator */} +
+ + + {locale === 'ko' ? clientOS.subtextKo : clientOS.subtextEn} + + + {locale === 'ko' ? '다른 OS 다운로드 (Win / Mac / Android / iOS) →' : 'Other Platforms (Win / Mac / Android / iOS) →'} + +
+ {/* Friction-Reduction Trust Microcopy */}
@@ -274,12 +297,27 @@ export function Hero() { ) } -function DownloadIcon() { + +function AndroidIcon() { return ( - - - - + + + + ) +} + +function AppleIcon() { + return ( + + + + ) +} + +function WindowsIcon() { + return ( + + ) } @@ -302,3 +340,4 @@ function MicIcon() { ) } +