diff --git a/apps/desktop/tests/packaging-config.spec.ts b/apps/desktop/tests/packaging-config.spec.ts index 62c764ce..054b19e3 100644 --- a/apps/desktop/tests/packaging-config.spec.ts +++ b/apps/desktop/tests/packaging-config.spec.ts @@ -86,9 +86,18 @@ describe('desktop packaging configuration', () => { expect(siteSource).toContain('releases/download/v${DESKTOP_VERSION}') expect(siteSource).not.toContain('releases/download/v0.1.0') - const desktopShowcaseMatch = siteSource.match(/
/) - expect(desktopShowcaseMatch).not.toBeNull() - expect(desktopShowcaseMatch![0]).toContain('/brand/windows11.svg') + expect(siteSource).toContain('id="desktop"') + const macDownload = siteSource.match( + /]*:href="desktopDownloads\.mac"[^>]*>[\s\S]*?<\/a>/, + ) + expect(macDownload).not.toBeNull() + expect(macDownload![0]).toContain('/brand/apple.svg') + + const windowsDownload = siteSource.match( + /]*:href="desktopDownloads\.windows"[^>]*>[\s\S]*?<\/a>/, + ) + expect(windowsDownload).not.toBeNull() + expect(windowsDownload![0]).toContain('/brand/windows11.svg') }) it('maps the staged Host node_modules directory as the copy root', () => { diff --git a/apps/site/index.html b/apps/site/index.html index 68dda94e..ff9a68b1 100644 --- a/apps/site/index.html +++ b/apps/site/index.html @@ -11,7 +11,7 @@ @@ -21,7 +21,7 @@ @@ -33,14 +33,14 @@ + + + + diff --git a/apps/site/src/components/InstallCommand.vue b/apps/site/src/components/InstallCommand.vue index a3eca682..2db8ca6b 100644 --- a/apps/site/src/components/InstallCommand.vue +++ b/apps/site/src/components/InstallCommand.vue @@ -7,7 +7,7 @@ const trigger = ref(null); const selectedId = ref(/Win/i.test(navigator.platform || navigator.userAgent) ? 'windows' : 'unix'); const open = ref(false); const copied = ref(false); -const activeChannel = computed(() => INSTALL_CHANNELS.find((channel) => channel.id === selectedId.value)); +const activeChannel = computed(() => INSTALL_CHANNELS.find((channel) => channel.id === selectedId.value) || INSTALL_CHANNELS[0]); let resetTimer; function fallbackCopy(text) { @@ -33,7 +33,7 @@ async function copyCommand() { clearTimeout(resetTimer); resetTimer = setTimeout(() => { copied.value = false; - }, 1600); + }, 1800); } function optionButtons() { @@ -111,20 +111,42 @@ onUnmounted(() => { @click="toggleMenu" @keydown="onTriggerKeydown" > - - Get started - + + + + {{ activeChannel.label }} + + - {{ activeChannel.command }} - - {{ copied ? 'Copied' : '' }} + {{ copied ? 'Copied command to clipboard' : '' }}
+ - + GitHub repository - + npm package @@ -167,142 +191,198 @@ onUnmounted(() => { min-height: 56px; align-items: center; gap: 12px; - padding: 8px; - border: 1px solid var(--hairline); - border-radius: var(--r-pill); + padding: 6px 8px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-pill); background: var(--canvas); + box-shadow: 0 4px 20px -4px rgba(10, 10, 12, 0.07); + transition: border-color 150ms ease, box-shadow 150ms ease; +} + +.install-input:focus-within { + border-color: var(--accent); + box-shadow: 0 4px 24px -2px var(--accent-glow); } .install-trigger { display: inline-flex; - min-height: 44px; + min-height: 42px; flex: 0 0 auto; align-items: center; - gap: 8px; - padding: 10px 20px; - border: 0; - border-radius: var(--r-pill); + gap: 10px; + padding: 8px 16px; + border: 1px solid transparent; + border-radius: var(--radius-pill); background: var(--pill-dark); color: var(--pill-dark-ink); cursor: pointer; - font-size: 15px; + font-size: 14px; font-weight: 600; + transition: all 140ms ease; +} + +.install-trigger:hover { + background: #27272a; } -.install-trigger img { +.channel-indicator { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.channel-current-icon { width: 16px; - height: 20px; - flex: 0 0 auto; + height: 16px; + filter: brightness(0) invert(1); } -.install-trigger svg { - width: 12px; - height: 8px; +.trigger-label { + font-weight: 600; +} + +.chevron-icon { + width: 10px; + height: 6px; + stroke: currentColor; + stroke-width: 2; + fill: none; + stroke-linecap: round; + stroke-linejoin: round; + transition: transform 150ms ease; } -.install-input code { +.chevron-icon.is-open { + transform: rotate(180deg); +} + +.command-text { min-width: 0; flex: 1; overflow: hidden; color: var(--ink); - font-size: 15px; + font-family: 'Geist Mono', 'JetBrains Mono', monospace; + font-size: 14px; text-overflow: ellipsis; white-space: nowrap; + padding-inline: 6px; } .copy-button { position: relative; display: grid; - width: 44px; - height: 44px; - flex: 0 0 44px; + width: 42px; + height: 42px; + flex: 0 0 42px; margin-left: auto; - padding: 12px; - border: 0; + padding: 11px; + border: 1px solid transparent; border-radius: 50%; - background: transparent; + background: var(--surface-1); color: var(--ink-muted); cursor: pointer; - opacity: 0.7; + place-items: center; + transition: all 140ms ease; } .copy-button:hover { color: var(--ink); - opacity: 1; -} - -.copy-button::before { - position: absolute; - inset: 6px; - border-radius: 50%; - content: ''; -} - -.copy-button:hover::before { background: var(--surface-2); + border-color: var(--hairline-strong); } -.copy-button svg { - position: relative; - width: 20px; - height: 20px; +.copy-button.is-copied { + background: #ecfdf5; + color: #059669; + border-color: #a7f3d0; } -svg { +.copy-button svg { + width: 18px; + height: 18px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; - stroke-width: 1.5; + stroke-width: 1.6; +} + +.copy-tooltip { + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%) translateY(4px); + padding: 4px 8px; + border-radius: 6px; + background: #09090b; + color: #ffffff; + font-family: 'Geist Mono', monospace; + font-size: 11px; + font-weight: 500; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity 140ms ease, transform 140ms ease; +} + +.copy-tooltip.is-visible, +.copy-button:hover .copy-tooltip { + opacity: 1; + transform: translateX(-50%) translateY(0); } .install-menu { position: absolute; - z-index: 20; + z-index: 30; bottom: calc(100% + 8px); left: 0; - min-width: 210px; - padding: 6px; - border: 1px solid var(--hairline); - border-radius: var(--radius-sm); + min-width: 240px; + padding: 8px; + border: 1px solid var(--hairline-strong); + border-radius: var(--radius-md); background: var(--canvas); - box-shadow: var(--shadow-card); + box-shadow: var(--shadow-lg); + backdrop-filter: blur(16px); +} + +.menu-label { + padding: 6px 10px; + color: var(--ink-subtle); + font-family: 'Geist Mono', monospace; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; } .install-option, -.install-menu a { +.menu-link { display: flex; width: 100%; - min-height: 44px; + min-height: 40px; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 12px; border: 0; - border-radius: 8px; + border-radius: var(--radius-sm); background: transparent; color: var(--ink); cursor: pointer; font-size: 14px; font-weight: 500; text-align: left; + transition: background-color 100ms ease; } .install-option:hover, .install-option[aria-selected='true'], -.install-menu a:hover { +.menu-link:hover { background: var(--surface-2); } -.install-option svg, -.install-menu a svg { - width: 14px; - height: 14px; - flex: 0 0 14px; - color: var(--accent); -} - -.install-menu a svg { +.install-option[aria-selected='true'] { + font-weight: 600; color: var(--accent); } @@ -310,7 +390,7 @@ svg { display: inline-flex; min-width: 0; align-items: center; - gap: 8px; + gap: 10px; } .channel-label img, @@ -320,20 +400,39 @@ svg { flex: 0 0 16px; } -.channel-label img { - filter: opacity(0.75); -} - .terminal-glyph { + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 1.6; color: var(--ink-muted); } +.check-mark { + width: 16px; + height: 16px; + stroke: var(--accent); + stroke-width: 2; + fill: none; + stroke-linecap: round; + stroke-linejoin: round; +} + .install-divider { height: 1px; - margin: 6px 8px; + margin: 6px 4px; background: var(--hairline); } +.menu-link svg { + width: 14px; + height: 14px; + stroke: var(--ink-subtle); + stroke-width: 1.5; + fill: none; +} + .install-menu-enter-active, .install-menu-leave-active { transition: opacity 120ms ease, transform 120ms ease; @@ -342,27 +441,33 @@ svg { .install-menu-enter-from, .install-menu-leave-to { opacity: 0; - transform: translateY(4px); + transform: translateY(6px); +} + +.icon-fade-enter-active, +.icon-fade-leave-active { + transition: opacity 100ms ease, transform 100ms ease; +} + +.icon-fade-enter-from, +.icon-fade-leave-to { + opacity: 0; + transform: scale(0.8); } @media (max-width: 640px) { .install-input { gap: 8px; + padding: 6px; } .install-trigger { - padding-inline: 16px; + padding: 6px 12px; + font-size: 13px; } - .install-input code { - font-size: 14px; - } -} - -@media (prefers-reduced-motion: reduce) { - .install-menu-enter-active, - .install-menu-leave-active { - transition: none; + .command-text { + font-size: 13px; } } diff --git a/apps/site/src/components/LegacyDownloadsPopup.vue b/apps/site/src/components/LegacyDownloadsPopup.vue deleted file mode 100644 index a1740150..00000000 --- a/apps/site/src/components/LegacyDownloadsPopup.vue +++ /dev/null @@ -1,190 +0,0 @@ - - - - - diff --git a/apps/site/src/components/ParticleField.vue b/apps/site/src/components/ParticleField.vue index e5622b84..0652a5f6 100644 --- a/apps/site/src/components/ParticleField.vue +++ b/apps/site/src/components/ParticleField.vue @@ -6,16 +6,24 @@ const enabled = ref(false); let context; let animationFrame; -let particles = []; let viewportWidth = 0; let viewportHeight = 0; let mounted = false; +let scrollY = 0; +let prefersReducedMotion = false; + +const pointer = { x: -1000, y: -1000, targetX: -1000, targetY: -1000, active: false }; +const GRID_SIZE = 48; -const pointer = { x: Infinity, y: Infinity }; -const pointerRadius = 120; +// Procedural micro-particles with natural drift +let particles = []; +// Procedural grid pulses representing autonomous agent activity +let gridPulses = []; -function createParticles() { - const count = Math.max(40, Math.min(90, Math.round((viewportWidth * viewportHeight) / 16000))); +function initParticles() { + const densityFactor = (viewportWidth * viewportHeight) / 18000; + const count = Math.max(30, Math.min(75, Math.round(densityFactor))); + particles = Array.from({ length: count }, () => { const homeX = Math.random() * viewportWidth; const homeY = Math.random() * viewportHeight; @@ -24,75 +32,217 @@ function createParticles() { homeY, x: homeX, y: homeY, - velocityX: 0, - velocityY: 0, - driftX: (Math.random() - 0.5) * 0.08, - driftY: (Math.random() - 0.5) * 0.08, - radius: 0.8 + Math.random(), - color: Math.random() < 0.06 ? 'rgba(43, 137, 255, 0.35)' : 'rgba(17, 17, 19, 0.22)', + vx: 0, + vy: 0, + driftAngle: Math.random() * Math.PI * 2, + driftSpeed: 0.15 + Math.random() * 0.25, + radius: 0.8 + Math.random() * 0.9, + baseAlpha: 0.12 + Math.random() * 0.2, + isAccent: Math.random() < 0.15, + phase: Math.random() * Math.PI * 2, + }; + }); +} + +function initGridPulses() { + const pulseCount = Math.max(2, Math.min(5, Math.floor(viewportWidth / 360))); + gridPulses = Array.from({ length: pulseCount }, () => { + const isHorizontal = Math.random() > 0.5; + const gridCoord = Math.floor(Math.random() * (isHorizontal ? viewportHeight : viewportWidth) / GRID_SIZE) * GRID_SIZE; + return { + isHorizontal, + coord: gridCoord, + pos: Math.random() * (isHorizontal ? viewportWidth : viewportHeight), + speed: 0.6 + Math.random() * 0.8, + length: 60 + Math.random() * 80, + alpha: 0.18 + Math.random() * 0.14, }; }); } function resize() { + if (!canvas.value) return; viewportWidth = window.innerWidth; viewportHeight = window.innerHeight; - const pixelRatio = Math.min(window.devicePixelRatio || 1, 2); - + scrollY = window.scrollY || window.pageYOffset || 0; + + const dpr = Math.min(window.devicePixelRatio || 1, 2); + canvas.value.width = Math.round(viewportWidth * dpr); + canvas.value.height = Math.round(viewportHeight * dpr); + context.setTransform(1, 0, 0, 1, 0, 0); - canvas.value.width = Math.round(viewportWidth * pixelRatio); - canvas.value.height = Math.round(viewportHeight * pixelRatio); - context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); - createParticles(); + context.scale(dpr, dpr); + + initParticles(); + initGridPulses(); + + if (prefersReducedMotion) { + renderStaticBackground(); + } } -function moveParticles() { - for (const particle of particles) { - particle.homeX += particle.driftX; - particle.homeY += particle.driftY; - if (particle.homeX < 0 || particle.homeX > viewportWidth) particle.driftX *= -1; - if (particle.homeY < 0 || particle.homeY > viewportHeight) particle.driftY *= -1; - - const deltaX = particle.x - pointer.x; - const deltaY = particle.y - pointer.y; - const distance = Math.hypot(deltaX, deltaY); - - if (distance < pointerRadius) { - const force = ((pointerRadius - distance) / pointerRadius) * 0.8; - const safeDistance = Math.max(distance, 0.01); - particle.velocityX += (deltaX / safeDistance) * force; - particle.velocityY += (deltaY / safeDistance) * force; - } else { - particle.velocityX += (particle.homeX - particle.x) * 0.008; - particle.velocityY += (particle.homeY - particle.y) * 0.008; +function updateSimulation() { + // Smooth pointer interpolation + pointer.x += (pointer.targetX - pointer.x) * 0.12; + pointer.y += (pointer.targetY - pointer.y) * 0.12; + + // Update floating particles + for (let i = 0; i < particles.length; i++) { + const p = particles[i]; + p.phase += 0.015; + p.homeX += Math.cos(p.driftAngle + p.phase * 0.2) * (p.driftSpeed * 0.4); + p.homeY += Math.sin(p.driftAngle + p.phase * 0.2) * (p.driftSpeed * 0.4); + + // Screen wrapping + if (p.homeX < -20) p.homeX = viewportWidth + 20; + if (p.homeX > viewportWidth + 20) p.homeX = -20; + if (p.homeY < -20) p.homeY = viewportHeight + 20; + if (p.homeY > viewportHeight + 20) p.homeY = -20; + + // Pointer repulsion physics + if (pointer.active) { + const dx = p.x - pointer.x; + const dy = p.y - pointer.y; + const dist = Math.hypot(dx, dy); + const interactRadius = 140; + + if (dist < interactRadius && dist > 0.01) { + const force = ((interactRadius - dist) / interactRadius) * 1.2; + p.vx += (dx / dist) * force; + p.vy += (dy / dist) * force; + } } - particle.velocityX *= 0.92; - particle.velocityY *= 0.92; - particle.x += particle.velocityX; - particle.y += particle.velocityY; + // Spring return to home position + p.vx += (p.homeX - p.x) * 0.01; + p.vy += (p.homeY - p.y) * 0.01; + p.vx *= 0.9; + p.vy *= 0.9; + p.x += p.vx; + p.y += p.vy; + } + + // Update procedural grid pulses + for (let i = 0; i < gridPulses.length; i++) { + const pulse = gridPulses[i]; + pulse.pos += pulse.speed; + const maxBound = pulse.isHorizontal ? viewportWidth : viewportHeight; + if (pulse.pos - pulse.length > maxBound) { + pulse.pos = -pulse.length; + pulse.isHorizontal = Math.random() > 0.5; + const bound = pulse.isHorizontal ? viewportHeight : viewportWidth; + pulse.coord = Math.floor(Math.random() * bound / GRID_SIZE) * GRID_SIZE; + pulse.speed = 0.5 + Math.random() * 0.8; + pulse.alpha = 0.15 + Math.random() * 0.15; + } } } -function drawParticles() { +function renderFrame() { + if (!context) return; context.clearRect(0, 0, viewportWidth, viewportHeight); - moveParticles(); - for (const particle of particles) { + // 1. Render Interactive Pointer Grid Node Illuminations + if (pointer.active) { + const pX = pointer.x; + const pY = pointer.y; + const glowRadius = 180; + const minGridX = Math.max(0, Math.floor((pX - glowRadius) / GRID_SIZE) * GRID_SIZE); + const maxGridX = Math.min(viewportWidth, Math.ceil((pX + glowRadius) / GRID_SIZE) * GRID_SIZE); + const minGridY = Math.max(0, Math.floor((pY - glowRadius) / GRID_SIZE) * GRID_SIZE); + const maxGridY = Math.min(viewportHeight, Math.ceil((pY + glowRadius) / GRID_SIZE) * GRID_SIZE); + + for (let gx = minGridX; gx <= maxGridX; gx += GRID_SIZE) { + for (let gy = minGridY; gy <= maxGridY; gy += GRID_SIZE) { + const dist = Math.hypot(gx - pX, gy - pY); + if (dist < glowRadius) { + const intensity = 1 - dist / glowRadius; + const alpha = intensity * intensity * 0.35; + context.beginPath(); + context.arc(gx, gy, 1.6, 0, Math.PI * 2); + context.fillStyle = `rgba(37, 99, 235, ${alpha})`; + context.fill(); + } + } + } + } + + // 2. Render Autonomous Grid Pulses (agent pipeline telemetry) + for (let i = 0; i < gridPulses.length; i++) { + const pulse = gridPulses[i]; + context.save(); + context.lineWidth = 1.2; context.beginPath(); - context.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2); - context.fillStyle = particle.color; + + if (pulse.isHorizontal) { + const startX = Math.max(0, pulse.pos - pulse.length); + const endX = Math.min(viewportWidth, pulse.pos); + if (startX < endX) { + const gradient = context.createLinearGradient(startX, pulse.coord, endX, pulse.coord); + gradient.addColorStop(0, 'rgba(37, 99, 235, 0)'); + gradient.addColorStop(0.8, `rgba(56, 189, 248, ${pulse.alpha})`); + gradient.addColorStop(1, `rgba(37, 99, 235, ${pulse.alpha * 1.5})`); + context.strokeStyle = gradient; + context.moveTo(startX, pulse.coord); + context.lineTo(endX, pulse.coord); + context.stroke(); + } + } else { + const startY = Math.max(0, pulse.pos - pulse.length); + const endY = Math.min(viewportHeight, pulse.pos); + if (startY < endY) { + const gradient = context.createLinearGradient(pulse.coord, startY, pulse.coord, endY); + gradient.addColorStop(0, 'rgba(37, 99, 235, 0)'); + gradient.addColorStop(0.8, `rgba(56, 189, 248, ${pulse.alpha})`); + gradient.addColorStop(1, `rgba(37, 99, 235, ${pulse.alpha * 1.5})`); + context.strokeStyle = gradient; + context.moveTo(pulse.coord, startY); + context.lineTo(pulse.coord, endY); + context.stroke(); + } + } + context.restore(); + } + + // 3. Render Subtle Procedural Particles + for (let i = 0; i < particles.length; i++) { + const p = particles[i]; + const pulseAlpha = p.baseAlpha + Math.sin(p.phase) * 0.05; + context.beginPath(); + context.arc(p.x, p.y, p.radius, 0, Math.PI * 2); + + if (p.isAccent) { + context.fillStyle = `rgba(37, 99, 235, ${Math.max(0.04, pulseAlpha * 1.3)})`; + } else { + context.fillStyle = `rgba(15, 23, 42, ${Math.max(0.03, pulseAlpha * 0.85)})`; + } context.fill(); } } +function renderStaticBackground() { + if (!context) return; + context.clearRect(0, 0, viewportWidth, viewportHeight); + // Elegant, quiet static dot matrix for reduced motion + for (let x = GRID_SIZE; x < viewportWidth; x += GRID_SIZE * 2) { + for (let y = GRID_SIZE; y < viewportHeight; y += GRID_SIZE * 2) { + context.beginPath(); + context.arc(x, y, 1, 0, Math.PI * 2); + context.fillStyle = 'rgba(15, 23, 42, 0.06)'; + context.fill(); + } + } +} + function animate() { - drawParticles(); + if (prefersReducedMotion) return; + updateSimulation(); + renderFrame(); animationFrame = window.requestAnimationFrame(animate); } function startAnimation() { - if (animationFrame !== undefined || document.hidden) return; + if (animationFrame !== undefined || document.hidden || prefersReducedMotion) return; animationFrame = window.requestAnimationFrame(animate); } @@ -103,8 +253,19 @@ function stopAnimation() { } function onPointerMove(event) { - pointer.x = event.clientX; - pointer.y = event.clientY; + pointer.targetX = event.clientX; + pointer.targetY = event.clientY; + pointer.active = true; +} + +function onPointerLeave() { + pointer.active = false; + pointer.targetX = -1000; + pointer.targetY = -1000; +} + +function onScroll() { + scrollY = window.scrollY || window.pageYOffset || 0; } function onVisibilityChange() { @@ -114,45 +275,49 @@ function onVisibilityChange() { onMounted(async () => { mounted = true; - if ( - window.matchMedia('(prefers-reduced-motion: reduce)').matches - || !window.matchMedia('(pointer: fine)').matches - ) return; - + prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; enabled.value = true; await nextTick(); if (!mounted) return; - context = canvas.value?.getContext('2d'); + context = canvas.value?.getContext('2d', { alpha: true }); if (!context) return; resize(); window.addEventListener('pointermove', onPointerMove, { passive: true }); - window.addEventListener('resize', resize); + document.addEventListener('mouseleave', onPointerLeave); + window.addEventListener('scroll', onScroll, { passive: true }); + window.addEventListener('resize', resize, { passive: true }); document.addEventListener('visibilitychange', onVisibilityChange); - startAnimation(); + + if (!prefersReducedMotion) { + startAnimation(); + } }); onUnmounted(() => { mounted = false; stopAnimation(); window.removeEventListener('pointermove', onPointerMove); + document.removeEventListener('mouseleave', onPointerLeave); + window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', resize); document.removeEventListener('visibilitychange', onVisibilityChange); }); diff --git a/apps/site/src/style.css b/apps/site/src/style.css index 60c968a4..b1778f28 100644 --- a/apps/site/src/style.css +++ b/apps/site/src/style.css @@ -1,39 +1,59 @@ :root { --canvas: #ffffff; - --surface-1: #f7f7f8; - --surface-2: #efeff1; - --surface-3: #e4e4e7; - --hairline: #e5e7eb; - --hairline-soft: #f0f0f2; - --ink: #111113; - --ink-muted: #55565c; - --ink-subtle: #8b8d94; - --accent: #2b89ff; - --grid-line: rgba(43, 137, 255, 0.055); - --bubble-blue: rgba(43, 137, 255, 0.18); - --bubble-cyan: rgba(92, 206, 255, 0.14); - --pill-dark: #111113; - --pill-dark-ink: #ffffff; - --terminal-bg: #10263c; - --terminal-band: #17324c; - --radius: 18px; - --radius-sm: 12px; + --surface-1: #f8fafc; + --surface-2: #f1f5f9; + --surface-3: #e2e8f0; + --hairline: rgba(0, 0, 0, 0.08); + --hairline-soft: rgba(0, 0, 0, 0.04); + --hairline-strong: rgba(0, 0, 0, 0.14); + --ink: #0a0a0c; + --ink-muted: #52525b; + --ink-subtle: #71717a; + --accent: #2563eb; + --accent-hover: #1d4ed8; + --accent-subtle: rgba(37, 99, 235, 0.08); + --accent-glow: rgba(37, 99, 235, 0.22); + --coral-accent: #ee9983; + --coral-subtle: rgba(238, 153, 131, 0.12); + --green-accent: #10b981; + + --terminal-bg: #0a111e; + --terminal-surface: #0f192b; + --terminal-band: #152238; + --terminal-border: rgba(143, 184, 219, 0.18); + --terminal-text: #f1f5f9; + --terminal-muted: #94a3b8; + + --radius-xs: 6px; + --radius-sm: 10px; + --radius-md: 14px; + --radius-lg: 18px; + --radius-xl: 24px; --radius-pill: 9999px; - --shadow-card: 0 24px 70px -42px rgba(17, 17, 19, 0.35); - --shadow-terminal: 0 26px 70px -42px rgba(16, 38, 60, 0.86), inset 0 1px 0 rgba(255, 255, 255, 0.08); - - /* Existing component aliases mapped to the design system. */ - --hairline-strong: var(--surface-3); - --r-xs: 4px; + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 16px -2px rgba(0, 0, 0, 0.06), 0 2px 6px -1px rgba(0, 0, 0, 0.04); + --shadow-lg: 0 20px 48px -12px rgba(15, 23, 42, 0.1), 0 8px 20px -4px rgba(15, 23, 42, 0.04); + --shadow-terminal: 0 24px 64px -16px rgba(5, 15, 30, 0.6), 0 8px 24px -4px rgba(5, 15, 30, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.12); + + /* Component token aliases */ + --pill-dark: #0a0a0c; + --pill-dark-ink: #ffffff; + --radius: var(--radius-lg); + --r-xs: var(--radius-xs); --r-sm: var(--radius-sm); - --r-md: var(--radius-sm); - --r-lg: var(--radius); - --r-xxl: var(--radius); + --r-md: var(--radius-md); + --r-lg: var(--radius-lg); --r-pill: var(--radius-pill); + --shadow-card: var(--shadow-md); + color-scheme: light; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', helvetica, arial, sans-serif; + font-family: 'Geist', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-feature-settings: 'cv11', 'ss01'; font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } *, @@ -45,94 +65,70 @@ html { scroll-behavior: smooth; + scroll-padding-top: 80px; } body { position: relative; min-width: 320px; - min-height: 100vh; + min-height: 100dvh; overflow-x: clip; background-color: var(--canvas); - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48'%3E%3Cpath d='M48 0H0v48' fill='none' stroke='%23111113' stroke-opacity='0.045' stroke-width='1'/%3E%3C/svg%3E"); + background-image: + radial-gradient(ellipse at 50% 0%, rgba(37, 99, 235, 0.045) 0%, transparent 60%), + linear-gradient(to right, rgba(10, 10, 12, 0.035) 1px, transparent 1px), + linear-gradient(to bottom, rgba(10, 10, 12, 0.035) 1px, transparent 1px); + background-size: 100% 100%, 48px 48px, 48px 48px; color: var(--ink); - font-size: 17px; + font-size: 16px; font-weight: 400; - line-height: 1.55; + line-height: 1.6; } body::before, body::after { position: fixed; z-index: 0; - width: clamp(340px, 34vw, 520px); - border: 1px solid rgba(43, 137, 255, 0.1); + width: clamp(360px, 38vw, 560px); border-radius: 50%; aspect-ratio: 1; content: ''; pointer-events: none; + filter: blur(60px); + opacity: 0.6; will-change: transform; } body::before { - top: 12vh; - left: clamp(-300px, -18vw, -190px); - background: - radial-gradient(circle at 64% 32%, rgba(255, 255, 255, 0.78) 0 4%, transparent 5%), - radial-gradient(circle at 58% 46%, var(--bubble-cyan), var(--bubble-blue) 42%, rgba(43, 137, 255, 0.035) 67%, transparent 72%); - box-shadow: - 112px 300px 0 -190px rgba(92, 206, 255, 0.13), - 188px -38px 0 -214px rgba(43, 137, 255, 0.12), - 32px 30px 100px rgba(43, 137, 255, 0.08); - animation: bubble-drift-left 26s ease-in-out -8s infinite; + top: 6vh; + left: clamp(-240px, -12vw, -120px); + background: radial-gradient(circle, rgba(37, 99, 235, 0.15) 0%, rgba(56, 189, 248, 0.08) 50%, transparent 70%); + animation: atmospheric-drift-1 28s ease-in-out infinite alternate; } body::after { - right: clamp(-310px, -19vw, -200px); - bottom: 8vh; - background: - radial-gradient(circle at 32% 30%, rgba(255, 255, 255, 0.72) 0 3.5%, transparent 4.5%), - radial-gradient(circle at 42% 44%, rgba(92, 206, 255, 0.16), rgba(43, 137, 255, 0.16) 44%, rgba(43, 137, 255, 0.03) 68%, transparent 73%); - box-shadow: - -128px -236px 0 -202px rgba(92, 206, 255, 0.12), - -194px 54px 0 -218px rgba(43, 137, 255, 0.12), - -28px 30px 100px rgba(43, 137, 255, 0.08); - animation: bubble-drift-right 31s ease-in-out -17s infinite; -} - -@keyframes bubble-drift-left { - 0%, - 100% { - transform: translate3d(0, 0, 0) rotate(0deg); - } - - 36% { - transform: translate3d(14px, -18px, 0) rotate(1.5deg); - } - - 72% { - transform: translate3d(-8px, 12px, 0) rotate(-1deg); - } + right: clamp(-240px, -12vw, -120px); + top: 45vh; + background: radial-gradient(circle, rgba(238, 153, 131, 0.12) 0%, rgba(37, 99, 235, 0.08) 50%, transparent 70%); + animation: atmospheric-drift-2 34s ease-in-out infinite alternate; } -@keyframes bubble-drift-right { - 0%, - 100% { - transform: translate3d(0, 0, 0) rotate(0deg); - } - - 42% { - transform: translate3d(-16px, 14px, 0) rotate(-1.4deg); - } +@keyframes atmospheric-drift-1 { + 0% { transform: translate3d(0, 0, 0) scale(1); } + 50% { transform: translate3d(30px, 40px, 0) scale(1.08); } + 100% { transform: translate3d(-20px, 20px, 0) scale(0.95); } +} - 76% { - transform: translate3d(10px, -16px, 0) rotate(0.9deg); - } +@keyframes atmospheric-drift-2 { + 0% { transform: translate3d(0, 0, 0) scale(1); } + 50% { transform: translate3d(-35px, -30px, 0) scale(1.06); } + 100% { transform: translate3d(20px, -15px, 0) scale(0.96); } } #app { position: relative; z-index: 1; - min-height: 100vh; + min-height: 100dvh; } button { @@ -149,24 +145,17 @@ a { text-decoration: none; } -p a { - color: var(--accent); -} - -p a:hover { - color: var(--accent); -} - -code { - font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace; +code, kbd, samp, pre { + font-family: 'Geist Mono', 'JetBrains Mono', ui-monospace, 'SF Mono', monospace; + font-feature-settings: 'liga' 0; } ::selection { - background: var(--surface-3); + background: rgba(37, 99, 235, 0.18); color: var(--ink); } -:where(a, button):focus-visible { +:where(a, button, input, select, textarea):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } @@ -184,92 +173,147 @@ code { } .container { - width: min(100%, 1120px); + width: min(100%, 1160px); margin-inline: auto; padding-inline: 24px; } .section { - margin-top: 96px; + margin-top: 112px; +} + +.section-header { + margin-bottom: 36px; } .eyebrow { - margin-bottom: 14px; - color: var(--ink-subtle); - font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace; - font-size: 0.78rem; - font-weight: 500; + display: inline-flex; + align-items: center; + gap: 7px; + margin-bottom: 12px; + color: var(--accent); + font-family: 'Geist Mono', 'JetBrains Mono', ui-monospace, monospace; + font-size: 0.76rem; + font-weight: 600; line-height: 1.4; - letter-spacing: 0.08em; + letter-spacing: 0.1em; text-transform: uppercase; } -.reveal { - opacity: 0; - transform: translateY(12px); - transition: opacity 0.5s ease, transform 0.5s ease; +.display-lg { + font-size: clamp(40px, 5.2vw, 64px); + font-weight: 750; + line-height: 1.06; + letter-spacing: -0.035em; + color: var(--ink); } -.reveal.is-visible { - opacity: 1; - transform: none; +.display-md { + font-size: clamp(28px, 3.4vw, 38px); + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.028em; + color: var(--ink); } -.display-md { - font-size: clamp(28px, 3.5vw, 40px); - font-weight: 600; - line-height: 1.19; - letter-spacing: -0.025em; +.lead-text { + margin-top: 14px; + color: var(--ink-muted); + font-size: 17px; + font-weight: 400; + line-height: 1.6; + max-width: 62ch; } .button { display: inline-flex; - min-height: 44px; + min-height: 46px; align-items: center; justify-content: center; - padding: 10px 18px; - border: 0; - border-radius: var(--r-pill); + padding: 11px 24px; + border: 1px solid transparent; + border-radius: var(--radius-pill); cursor: pointer; - font-size: 14px; + font-size: 14.5px; font-weight: 600; line-height: 1.4; - transition: background-color 120ms ease, color 120ms ease; + letter-spacing: -0.015em; + transition: all 140ms cubic-bezier(0.16, 1, 0.3, 1); + white-space: nowrap; + user-select: none; +} + +.button:active { + transform: scale(0.98); } .button-primary { - background: var(--pill-dark); - color: var(--pill-dark-ink); + background: #09090b; + color: #ffffff; + border-color: #09090b; + box-shadow: 0 2px 8px -1px rgba(0, 0, 0, 0.25); } .button-primary:hover { - background: var(--ink-muted); + background: #27272a; + border-color: #27272a; + box-shadow: 0 6px 18px -2px rgba(0, 0, 0, 0.35); + transform: translateY(-1px); } .button-secondary { - border: 1px solid var(--hairline); + border-color: var(--hairline-strong); background: var(--canvas); color: var(--ink); + box-shadow: var(--shadow-sm); } .button-secondary:hover { background: var(--surface-1); + border-color: rgba(0, 0, 0, 0.2); + transform: translateY(-1px); +} + +.button-accent { + background: var(--accent); + color: #ffffff; + border-color: var(--accent); + box-shadow: 0 2px 10px -1px var(--accent-glow); +} + +.button-accent:hover { + background: var(--accent-hover); + border-color: var(--accent-hover); + box-shadow: 0 4px 16px -2px var(--accent-glow); + transform: translateY(-1px); +} + +.reveal { + opacity: 0; + transform: translateY(16px); + transition: opacity 0.55s cubic-bezier(0.16, 1, 0.3, 1), transform 0.55s cubic-bezier(0.16, 1, 0.3, 1); + will-change: opacity, transform; +} + +.reveal.is-visible { + opacity: 1; + transform: none; } @media (min-width: 1024px) { .container { - padding-inline: 48px; + padding-inline: 40px; } } @media (max-width: 640px) { body::before, body::after { - opacity: 0.72; + opacity: 0.45; } .section { - margin-top: 48px; + margin-top: 64px; } } @@ -283,8 +327,7 @@ code { } .reveal { - opacity: 1; - transform: none; - transition: none; + opacity: 1 !important; + transform: none !important; } }