feat(site): implement dynamic client OS negotiation for Hero and CTA download buttons
This commit is contained in:
parent
4865387d7f
commit
55d03baf6b
5 changed files with 280 additions and 16 deletions
107
scripts/capture-pricing.js
Normal file
107
scripts/capture-pricing.js
Normal file
|
|
@ -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);
|
||||||
|
|
@ -3,6 +3,7 @@ import type { ReactNode } from 'react'
|
||||||
interface GlowButtonProps {
|
interface GlowButtonProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
href?: string
|
href?: string
|
||||||
|
download?: string
|
||||||
variant?: 'primary' | 'secondary'
|
variant?: 'primary' | 'secondary'
|
||||||
size?: 'md' | 'lg'
|
size?: 'md' | 'lg'
|
||||||
className?: string
|
className?: string
|
||||||
|
|
@ -11,6 +12,7 @@ interface GlowButtonProps {
|
||||||
export function GlowButton({
|
export function GlowButton({
|
||||||
children,
|
children,
|
||||||
href,
|
href,
|
||||||
|
download,
|
||||||
variant = 'primary',
|
variant = 'primary',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
className = '',
|
className = '',
|
||||||
|
|
@ -31,7 +33,7 @@ export function GlowButton({
|
||||||
|
|
||||||
if (href) {
|
if (href) {
|
||||||
return (
|
return (
|
||||||
<a href={href} className={cls}>
|
<a href={href} download={download} className={cls}>
|
||||||
<span className="relative z-10">{children}</span>
|
<span className="relative z-10">{children}</span>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
|
@ -43,3 +45,4 @@ export function GlowButton({
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
105
site/src/hooks/useClientOS.ts
Normal file
105
site/src/hooks/useClientOS.ts
Normal file
|
|
@ -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<ClientOSType, ClientOSInfo> = {
|
||||||
|
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<ClientOSInfo>(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
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,11 @@ import { Crosshair } from '../components/Crosshair'
|
||||||
import { WaveBars } from '../components/WaveBars'
|
import { WaveBars } from '../components/WaveBars'
|
||||||
import { GlowButton } from '../components/GlowButton'
|
import { GlowButton } from '../components/GlowButton'
|
||||||
import { useI18n } from '../i18n'
|
import { useI18n } from '../i18n'
|
||||||
|
import { useClientOS } from '../hooks/useClientOS'
|
||||||
|
|
||||||
export function CTA() {
|
export function CTA() {
|
||||||
const { t } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
|
const clientOS = useClientOS()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative py-28 md:py-36 overflow-hidden">
|
<section className="relative py-28 md:py-36 overflow-hidden">
|
||||||
|
|
@ -47,15 +49,23 @@ export function CTA() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="flex flex-col items-center gap-3">
|
||||||
<GlowButton
|
<GlowButton
|
||||||
href="#download"
|
href={clientOS.downloadUrl}
|
||||||
|
download={clientOS.downloadFilename || undefined}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="shadow-glow-md hover:shadow-glow-lg"
|
className="shadow-glow-md hover:shadow-glow-lg"
|
||||||
>
|
>
|
||||||
{t.cta.downloadBtn}
|
{locale === 'ko' ? clientOS.buttonLabelKo : clientOS.buttonLabelEn}
|
||||||
</GlowButton>
|
</GlowButton>
|
||||||
|
<div className="flex items-center gap-2 font-mono text-xs text-neutral-400">
|
||||||
|
<span>{locale === 'ko' ? clientOS.subtextKo : clientOS.subtextEn}</span>
|
||||||
|
<span>·</span>
|
||||||
|
<a href="#download" className="underline hover:text-brand-amber transition-colors">
|
||||||
|
{locale === 'ko' ? '다른 OS 다운로드' : 'Other Platforms'}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="font-mono text-nano text-neutral-500 mt-8 uppercase tracking-widest">
|
<p className="font-mono text-nano text-neutral-500 mt-8 uppercase tracking-widest">
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ import { DataPoint } from '../components/DataPoint'
|
||||||
import { Container } from '../components/Container'
|
import { Container } from '../components/Container'
|
||||||
import { Led } from '../components/Led'
|
import { Led } from '../components/Led'
|
||||||
import { useI18n } from '../i18n'
|
import { useI18n } from '../i18n'
|
||||||
|
import { useClientOS } from '../hooks/useClientOS'
|
||||||
|
|
||||||
export function Hero() {
|
export function Hero() {
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
|
const clientOS = useClientOS()
|
||||||
|
|
||||||
// Interactive Live Simulator State
|
// Interactive Live Simulator State
|
||||||
const [simState, setSimState] = useState<'idle' | 'recording' | 'processing' | 'done'>('idle')
|
const [simState, setSimState] = useState<'idle' | 'recording' | 'processing' | 'done'>('idle')
|
||||||
|
|
@ -125,15 +127,22 @@ export function Hero() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Action Buttons */}
|
{/* CTA Action Buttons with OS Negotiation */}
|
||||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-4 mb-3.5">
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-4 mb-3">
|
||||||
<a
|
<a
|
||||||
href="#download"
|
href={clientOS.downloadUrl}
|
||||||
|
download={clientOS.downloadFilename || undefined}
|
||||||
className="glow-btn inline-flex items-center justify-center gap-2.5 px-8 py-4 font-mono text-sm font-semibold uppercase tracking-wider text-white bg-brand-amber rounded-panel shadow-glow-sm hover:shadow-glow-md"
|
className="glow-btn inline-flex items-center justify-center gap-2.5 px-8 py-4 font-mono text-sm font-semibold uppercase tracking-wider text-white bg-brand-amber rounded-panel shadow-glow-sm hover:shadow-glow-md"
|
||||||
>
|
>
|
||||||
<span className="relative z-10 flex items-center gap-2">
|
<span className="relative z-10 flex items-center gap-2.5">
|
||||||
<DownloadIcon />
|
{clientOS.os === 'android' ? (
|
||||||
{t.hero.downloadBtn}
|
<AndroidIcon />
|
||||||
|
) : clientOS.os === 'ios' || clientOS.os === 'macos' ? (
|
||||||
|
<AppleIcon />
|
||||||
|
) : (
|
||||||
|
<WindowsIcon />
|
||||||
|
)}
|
||||||
|
{locale === 'ko' ? clientOS.buttonLabelKo : clientOS.buttonLabelEn}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
|
|
@ -145,6 +154,20 @@ export function Hero() {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Platform Subtext & Other OS Navigator */}
|
||||||
|
<div className="flex flex-wrap items-center gap-y-2 gap-x-4 mb-3 font-mono text-xs">
|
||||||
|
<span className="text-brand-amber font-medium flex items-center gap-1.5">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-brand-amber animate-pulse" />
|
||||||
|
{locale === 'ko' ? clientOS.subtextKo : clientOS.subtextEn}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="#download"
|
||||||
|
className="text-neutral-400 hover:text-neutral-200 underline decoration-neutral-600 underline-offset-4 transition-colors"
|
||||||
|
>
|
||||||
|
{locale === 'ko' ? '다른 OS 다운로드 (Win / Mac / Android / iOS) →' : 'Other Platforms (Win / Mac / Android / iOS) →'}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Friction-Reduction Trust Microcopy */}
|
{/* Friction-Reduction Trust Microcopy */}
|
||||||
<div className="flex items-center gap-2 font-mono text-nano text-neutral-500">
|
<div className="flex items-center gap-2 font-mono text-nano text-neutral-500">
|
||||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
||||||
|
|
@ -274,12 +297,27 @@ export function Hero() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DownloadIcon() {
|
|
||||||
|
function AndroidIcon() {
|
||||||
return (
|
return (
|
||||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
<svg className="w-4 h-4 text-emerald-400" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
<path d="M17.523 15.3414c-.5511 0-.9993-.4486-.9993-.9997s.4482-.9993.9993-.9993c.551 0 .9993.4482.9993.9993.0001.5511-.4483.9997-.9993.9997m-11.046 0c-.5511 0-.9993-.4486-.9993-.9997s.4482-.9993.9993-.9993c.5511 0 .9993.4482.9993.9993 0 .5511-.4482.9997-.9993.9997m11.4045-6.02l1.996-3.4572a.416.416 0 0 0-.1521-.5676.416.416 0 0 0-.5676.1521l-2.0223 3.503C15.5902 8.411 13.856 8.125 12 8.125s-3.5902.286-5.1315.8267L4.8462 5.4487a.4161.4161 0 0 0-.5677-.1521.4157.4157 0 0 0-.1521.5676l1.996 3.4572C2.6889 11.1867.3432 14.6589 0 18.775h24c-.3432-4.1161-2.6889-7.5883-6.1185-9.4536" />
|
||||||
<polyline points="7 10 12 15 17 10" />
|
</svg>
|
||||||
<line x1="12" y1="15" x2="12" y2="3" />
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppleIcon() {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 text-neutral-200" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M15.97 6.37c.61-.75 1.04-1.8 1.01-2.87-.96.04-2.13.64-2.79 1.43-.58.68-.99 1.74-.94 2.82 1.08.08 2.11-.63 2.72-1.38z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function WindowsIcon() {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 text-sky-400" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.551H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-13.051-1.851" />
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -302,3 +340,4 @@ function MicIcon() {
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue