diff --git a/src/app/(main)/art-direction-demo/compute-genome-hero.tsx b/src/app/(main)/art-direction-demo/compute-genome-hero.tsx
new file mode 100644
index 00000000..290b6f09
--- /dev/null
+++ b/src/app/(main)/art-direction-demo/compute-genome-hero.tsx
@@ -0,0 +1,694 @@
+'use client'
+
+import {
+ Canvas,
+ ThreeEvent,
+ useFrame,
+ useLoader,
+ useThree,
+} from '@react-three/fiber'
+import { useMemo, useRef, useState } from 'react'
+import type { CSSProperties } from 'react'
+import * as THREE from 'three'
+
+import { useWebGLSupport } from '@/components/product/webgl-support'
+
+type ChipAsset = {
+ category: 'CPU' | 'GPU' | 'SoC' | 'TPU'
+ label: string
+ tee: string
+ url: string
+ vendor: string
+}
+
+type SceneChip = ChipAsset & {
+ id: string
+ kind: 'node' | 'rung'
+ position: THREE.Vector3
+ quaternion: THREE.Quaternion
+ scale: number
+}
+
+const brand = '#c5ff2e'
+const runCount = 16
+const helixRadius = 3.2
+const helixPitch = 1.05
+const turnPerRung = (Math.PI * 2) / 10.5
+const ignoreRaycast = () => undefined
+
+const chipAssets: ChipAsset[] = [
+ {
+ category: 'GPU',
+ label: 'NVIDIA H100 SXM5',
+ tee: 'CC Mode (Hopper)',
+ url: '/chips_final_webp/nvidia_h100.webp',
+ vendor: 'NVIDIA',
+ },
+ {
+ category: 'CPU',
+ label: 'Intel Xeon 6980P',
+ tee: 'TDX (Granite Rapids)',
+ url: '/chips_final_webp/intel_xeon.webp',
+ vendor: 'INTEL',
+ },
+ {
+ category: 'GPU',
+ label: 'AMD Instinct MI300X',
+ tee: 'Infinity Guard',
+ url: '/chips_final_webp/amd_instinct.webp',
+ vendor: 'AMD',
+ },
+ {
+ category: 'SoC',
+ label: 'Apple M4',
+ tee: 'Secure Enclave',
+ url: '/chips_final_webp/apple_m4.webp',
+ vendor: 'APPLE',
+ },
+ {
+ category: 'TPU',
+ label: 'Google TPU v5p',
+ tee: 'Confidential VM (TPU)',
+ url: '/chips_final_webp/google_tpu.webp',
+ vendor: 'GOOGLE',
+ },
+ {
+ category: 'GPU',
+ label: 'NVIDIA B300 (HGX SXM6)',
+ tee: 'CC Mode (Blackwell)',
+ url: '/chips_final_webp/nvidia_b300.webp',
+ vendor: 'NVIDIA',
+ },
+ {
+ category: 'CPU',
+ label: 'Intel Xeon CPU Max 9480',
+ tee: 'TDX + SGX',
+ url: '/chips_final_webp/intel_xeon_cpu_max.webp',
+ vendor: 'INTEL',
+ },
+ {
+ category: 'CPU',
+ label: 'AMD EPYC 9654',
+ tee: 'SEV-SNP + TDISP',
+ url: '/chips_final_webp/amd_epyc.webp',
+ vendor: 'AMD',
+ },
+ {
+ category: 'SoC',
+ label: 'Apple A17 Pro',
+ tee: 'Secure Enclave',
+ url: '/chips_final_webp/apple_a17.webp',
+ vendor: 'APPLE',
+ },
+ {
+ category: 'TPU',
+ label: 'Google TPU v5e',
+ tee: 'Confidential VM (TPU)',
+ url: '/chips_final_webp/google_tpu_2.webp',
+ vendor: 'GOOGLE',
+ },
+ {
+ category: 'GPU',
+ label: 'NVIDIA H200 SXM',
+ tee: 'CC Mode (Hopper)',
+ url: '/chips_final_webp/nvidia_h200.webp',
+ vendor: 'NVIDIA',
+ },
+ {
+ category: 'GPU',
+ label: 'NVIDIA RTX PRO 6000 Blackwell',
+ tee: 'CC Mode (Blackwell)',
+ url: '/chips_final_webp/nvidia_rtx.webp',
+ vendor: 'NVIDIA',
+ },
+ {
+ category: 'SoC',
+ label: 'Apple T2 Security Chip',
+ tee: 'Secure Enclave',
+ url: '/chips_final_webp/apple_t2.webp',
+ vendor: 'APPLE',
+ },
+]
+
+function helixPosition(index: number, phaseOffset: number) {
+ const angle = index * turnPerRung + phaseOffset
+ return new THREE.Vector3(
+ Math.cos(angle) * helixRadius,
+ (index - (runCount - 1) / 2) * helixPitch,
+ Math.sin(angle) * helixRadius,
+ )
+}
+
+function chipQuaternion(position: THREE.Vector3) {
+ const radial = new THREE.Vector3(position.x, 0, position.z).normalize()
+ const worldUp = new THREE.Vector3(0, 1, 0)
+ const xAxis = new THREE.Vector3().crossVectors(worldUp, radial).normalize()
+ const yAxis = new THREE.Vector3().crossVectors(radial, xAxis).normalize()
+ const matrix = new THREE.Matrix4().makeBasis(xAxis, yAxis, radial)
+
+ return new THREE.Quaternion().setFromRotationMatrix(matrix)
+}
+
+function rungQuaternion(index: number) {
+ const a = helixPosition(index, 0)
+ const b = helixPosition(index, Math.PI)
+ const direction = new THREE.Vector3().subVectors(a, b).normalize()
+ const up = new THREE.Vector3(0, 1, 0)
+ const normal = new THREE.Vector3().crossVectors(direction, up).normalize()
+ const matrix = new THREE.Matrix4().makeBasis(direction, up, normal)
+
+ return new THREE.Quaternion().setFromRotationMatrix(matrix)
+}
+
+function createChipLayout() {
+ const chips: SceneChip[] = []
+
+ for (const strand of [0, 1] as const) {
+ const phase = strand === 0 ? 0 : Math.PI
+ const offset = strand === 0 ? 0 : 5
+
+ for (let index = 0; index < runCount; index += 1) {
+ const asset = chipAssets[(index + offset) % chipAssets.length]
+ const position = helixPosition(index, phase)
+ const radial = new THREE.Vector3(position.x, 0, position.z).normalize()
+
+ chips.push({
+ ...asset,
+ id: `node-${strand}-${index}`,
+ kind: 'node',
+ position: position.clone().add(radial.multiplyScalar(0.02)),
+ quaternion: chipQuaternion(position),
+ scale: asset.category === 'GPU' ? 1.28 : 1.16,
+ })
+ }
+ }
+
+ for (let index = 0; index < runCount; index += 1) {
+ const asset = chipAssets[(index + 9) % chipAssets.length]
+ chips.push({
+ ...asset,
+ id: `rung-${index}`,
+ kind: 'rung',
+ position: new THREE.Vector3(
+ 0,
+ (index - (runCount - 1) / 2) * helixPitch,
+ 0,
+ ),
+ quaternion: rungQuaternion(index),
+ scale: asset.category === 'GPU' ? 0.88 : 0.78,
+ })
+ }
+
+ return chips
+}
+
+function createBackboneSegmentGeometry(
+ phaseOffset: number,
+ segmentIndex: number,
+) {
+ const points = []
+ const chipGap = 0.34
+
+ for (let index = 0; index <= 24; index += 1) {
+ const t = segmentIndex + chipGap + (1 - chipGap * 2) * (index / 24)
+ const angle = t * turnPerRung + phaseOffset
+ points.push(
+ new THREE.Vector3(
+ Math.cos(angle) * helixRadius,
+ (t - (runCount - 1) / 2) * helixPitch,
+ Math.sin(angle) * helixRadius,
+ ),
+ )
+ }
+
+ return new THREE.TubeGeometry(
+ new THREE.CatmullRomCurve3(points),
+ 24,
+ 0.072,
+ 14,
+ false,
+ )
+}
+
+function createRungGeometry(index: number) {
+ const a = helixPosition(index, 0)
+ const b = helixPosition(index, Math.PI)
+ const direction = new THREE.Vector3().subVectors(b, a).normalize()
+ const inset = 0.9
+ const start = a.clone().add(direction.clone().multiplyScalar(inset))
+ const end = b.clone().add(direction.clone().multiplyScalar(-inset))
+ const mid = start.clone().lerp(end, 0.5)
+
+ return new THREE.TubeGeometry(
+ new THREE.CatmullRomCurve3([
+ start,
+ mid.clone().add(new THREE.Vector3(0, Math.sin(index * 1.37) * 0.08, 0)),
+ end,
+ ]),
+ 16,
+ 0.04,
+ 10,
+ false,
+ )
+}
+
+function createRoundedPlate(size: number, radius: number, depth: number) {
+ const half = size / 2
+ const shape = new THREE.Shape()
+ shape.moveTo(-half + radius, -half)
+ shape.lineTo(half - radius, -half)
+ shape.quadraticCurveTo(half, -half, half, -half + radius)
+ shape.lineTo(half, half - radius)
+ shape.quadraticCurveTo(half, half, half - radius, half)
+ shape.lineTo(-half + radius, half)
+ shape.quadraticCurveTo(-half, half, -half, half - radius)
+ shape.lineTo(-half, -half + radius)
+ shape.quadraticCurveTo(-half, -half, -half + radius, -half)
+
+ const geometry = new THREE.ExtrudeGeometry(shape, {
+ bevelEnabled: true,
+ bevelSegments: 2,
+ bevelSize: 0.012,
+ bevelThickness: 0.012,
+ depth,
+ steps: 1,
+ })
+ geometry.translate(0, 0, -depth / 2)
+
+ const uv = geometry.attributes.uv
+ const position = geometry.attributes.position
+ for (let index = 0; index < position.count; index += 1) {
+ uv.setXY(
+ index,
+ (position.getX(index) + half) / size,
+ (position.getY(index) + half) / size,
+ )
+ }
+ uv.needsUpdate = true
+
+ return geometry
+}
+
+function DnaLineMaterial({ opacity = 1 }: { opacity?: number }) {
+ return (
+
+ )
+}
+
+function BackboneSegment({
+ index,
+ phaseOffset,
+}: {
+ index: number
+ phaseOffset: number
+}) {
+ const geometry = useMemo(
+ () => createBackboneSegmentGeometry(phaseOffset, index),
+ [index, phaseOffset],
+ )
+
+ return (
+
+
+
+ )
+}
+
+function RungWire({ index }: { index: number }) {
+ const geometry = useMemo(() => createRungGeometry(index), [index])
+
+ return (
+
+
+
+ )
+}
+
+function ChipMesh({
+ active,
+ chip,
+ onActivate,
+ onHover,
+ texture,
+}: {
+ active: boolean
+ chip: SceneChip
+ onActivate: (chip: SceneChip) => void
+ onHover: (chip: SceneChip | null) => void
+ texture: THREE.Texture
+}) {
+ const meshRef = useRef(null)
+ const glowRef = useRef(null)
+ const geometry = useMemo(
+ () =>
+ createRoundedPlate(
+ chip.kind === 'node' ? 1.32 : 0.92,
+ chip.kind === 'node' ? 0.08 : 0.06,
+ chip.kind === 'node' ? 0.16 : 0.12,
+ ),
+ [chip.kind],
+ )
+ const hitGeometry = useMemo(
+ () =>
+ createRoundedPlate(
+ chip.kind === 'node' ? 1.72 : 1.22,
+ chip.kind === 'node' ? 0.1 : 0.07,
+ 0.08,
+ ),
+ [chip.kind],
+ )
+
+ useFrame(() => {
+ const mesh = meshRef.current
+ const glow = glowRef.current
+ if (!mesh) return
+
+ mesh.scale.lerp(new THREE.Vector3(chip.scale, chip.scale, chip.scale), 0.12)
+ ;(mesh.material as THREE.MeshPhysicalMaterial).emissiveIntensity +=
+ ((active ? 0.75 : 0.42) -
+ (mesh.material as THREE.MeshPhysicalMaterial).emissiveIntensity) *
+ 0.15
+
+ if (glow) {
+ glow.scale.copy(mesh.scale).multiplyScalar(active ? 1.18 : 1.08)
+ ;(glow.material as THREE.MeshBasicMaterial).opacity +=
+ ((active ? 0.22 : 0.035) -
+ (glow.material as THREE.MeshBasicMaterial).opacity) *
+ 0.12
+ }
+ })
+
+ const handleOver = (event: ThreeEvent) => {
+ event.stopPropagation()
+ document.body.style.cursor = 'pointer'
+ onHover(chip)
+ }
+
+ const handleOut = (event: ThreeEvent) => {
+ event.stopPropagation()
+ document.body.style.cursor = ''
+ onHover(null)
+ }
+
+ const handleClick = (event: ThreeEvent) => {
+ event.stopPropagation()
+ onActivate(chip)
+ }
+
+ const handlePointerDown = (event: ThreeEvent) => {
+ event.stopPropagation()
+ onActivate(chip)
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function DnaScene({
+ activeChip,
+ onActivate,
+ onHover,
+}: {
+ activeChip: SceneChip | null
+ onActivate: (chip: SceneChip) => void
+ onHover: (chip: SceneChip | null) => void
+}) {
+ const outerRef = useRef(null)
+ const axisRef = useRef(null)
+ const { size, viewport } = useThree()
+ const isMobile = size.width < 640
+ const chips = useMemo(() => createChipLayout(), [])
+ const urls = useMemo(() => chipAssets.map((asset) => asset.url), [])
+ const textures = useLoader(THREE.TextureLoader, urls)
+ const textureMap = useMemo(() => {
+ const map = new Map()
+ textures.forEach((texture, index) => {
+ texture.colorSpace = THREE.SRGBColorSpace
+ texture.anisotropy = 4
+ map.set(chipAssets[index].url, texture)
+ })
+ return map
+ }, [textures])
+
+ // Anchor the helix's right edge to a safe fraction of the viewport so chips
+ // never clip off-screen. The helix has radius 3.2 in unscaled coords; after
+ // rotation.z tilt (~-32°), its effective horizontal extent is roughly
+ // helixRadius * 1.15 from the cluster center. We solve for the cluster
+ // center such that center + extent ≤ rightSafeLimit * halfWidth.
+ const halfWidth = viewport.width / 2
+ const aspect = viewport.width / viewport.height
+ const aspectScale = THREE.MathUtils.clamp((aspect - 0.6) / 1.4, 0, 1)
+
+ useFrame(({ clock, pointer }, delta) => {
+ if (!outerRef.current || !axisRef.current) return
+
+ axisRef.current.rotation.y += delta * 0.18
+ axisRef.current.rotation.x = Math.sin(clock.elapsedTime * 0.15) * 0.035
+
+ const tilt = isMobile
+ ? -0.48
+ : THREE.MathUtils.lerp(-0.52, -0.56, aspectScale)
+ const baseScale = isMobile
+ ? THREE.MathUtils.lerp(0.4, 0.5, aspectScale)
+ : THREE.MathUtils.lerp(0.46, 0.68, aspectScale)
+ // helixRadius (3.2) scaled and inflated for the diagonal tilt → ≈ 1.15× radius.
+ const clusterExtent = 3.2 * 1.15 * baseScale
+ // Target the cluster center so its right edge sits at 95% of halfWidth.
+ // Keep a hard upper cap at the original desktop value (7.15) so chips
+ // don't drift further right than the design intends on ultra-wide screens.
+ const baseX = Math.min(halfWidth * 0.95 - clusterExtent, 7.15)
+ const baseY = isMobile
+ ? -1.02
+ : THREE.MathUtils.lerp(-0.4, -0.2, aspectScale)
+
+ outerRef.current.rotation.x = pointer.y * 0.035
+ outerRef.current.rotation.y = -0.22 + pointer.x * 0.035
+ outerRef.current.rotation.z =
+ tilt + Math.sin(clock.elapsedTime * 0.12) * 0.01
+ outerRef.current.position.x = baseX + pointer.x * 0.08
+ outerRef.current.position.y = baseY
+ outerRef.current.scale.setScalar(baseScale)
+ })
+
+ return (
+
+
+ {Array.from({ length: runCount - 1 }, (_, index) => (
+
+ ))}
+ {Array.from({ length: runCount - 1 }, (_, index) => (
+
+ ))}
+ {Array.from({ length: runCount }, (_, index) => (
+
+ ))}
+ {chips.map((chip) => (
+
+ ))}
+
+
+ )
+}
+
+function ComputeGenomeFallback() {
+ const fallbackChips = chipAssets.slice(0, 9)
+
+ return (
+
+
+
+
+
+
+ {fallbackChips.map((chip, index) => {
+ const top = 8 + index * 9.6
+ const left = index % 2 === 0 ? 26 + index * 2.2 : 47 + index * 1.4
+ const scale = index < 3 ? 1.22 : index < 6 ? 1.02 : 0.86
+
+ return (
+

+ )
+ })}
+
+
+
+ Secured Hardware
+
+
DNA visual fallback
+
+
+ )
+}
+
+export function ComputeGenomeHero({
+ backgroundColor = '#f7f8f3',
+}: {
+ backgroundColor?: string
+}) {
+ const [hoveredChip, setHoveredChip] = useState(null)
+ const [pinnedChip, setPinnedChip] = useState(null)
+ const webglSupported = useWebGLSupport()
+ const activeChip = hoveredChip ?? pinnedChip
+
+ return (
+
+
+
+
+ {webglSupported === false ? (
+
+ ) : (
+
+
}
+ gl={{
+ alpha: true,
+ antialias: true,
+ powerPreference: 'high-performance',
+ }}
+ onPointerMissed={() => setPinnedChip(null)}
+ >
+
+
+
+
+
+
+
+ )}
+
+ {webglSupported !== false && activeChip ? (
+
+
+
+ Secured Hardware
+
+
+ {activeChip.label}
+
+
+
+ Vendor
+
+ {activeChip.vendor}
+
+ Type
+
+ {activeChip.category}
+
+
+
+ TEE Type
+
+
+ {activeChip.tee}
+
+
+
+ ) : null}
+
+ )
+}
diff --git a/src/app/(main)/art-direction-demo/demo-interactions.tsx b/src/app/(main)/art-direction-demo/demo-interactions.tsx
new file mode 100644
index 00000000..d2cb27a8
--- /dev/null
+++ b/src/app/(main)/art-direction-demo/demo-interactions.tsx
@@ -0,0 +1,506 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+
+import { TypingAnimation } from '@/components/magicui/terminal'
+import { InlineCommandButton } from '@/components/product/marketing'
+
+const tabs = [
+ {
+ title: 'AI Agents',
+ eyebrow: 'private tools + memory',
+ copy: 'Run autonomous agents with sealed credentials and verifiable runtime state.',
+ artifact: 'phala deploy -c agent-compose.yml',
+ cta: 'Deploy agent',
+ href: '/confidential-vm',
+ },
+ {
+ title: 'Private AI Data',
+ eyebrow: 'compute to data',
+ copy: 'Let models work on sensitive datasets without exposing raw records.',
+ artifact: 'policy: data never leaves enclave',
+ cta: 'Design data workflow',
+ href: '/contact',
+ },
+ {
+ title: 'Private Inference',
+ eyebrow: 'OpenAI compatible',
+ copy: 'Route prompts to private LLM endpoints with proof attached.',
+ artifact: 'base_url=https://api.redpill.ai/v1',
+ cta: 'Call private model',
+ href: '/confidential-ai-models',
+ },
+]
+
+export function CopyInstallButton() {
+ const [copied, setCopied] = useState(false)
+
+ return (
+
+ )
+}
+
+export function CopyCliButton({
+ command,
+ className = '',
+ displayText,
+ prefix = '$',
+}: {
+ command: string
+ className?: string
+ displayText?: string
+ prefix?: string
+}) {
+ return (
+
+ )
+}
+
+export function SolutionTabsClient() {
+ const [active, setActive] = useState(0)
+ const tab = tabs[active]
+
+ return (
+
+
+ {tabs.map((item, index) => (
+
+ ))}
+
+
+
+
+
+ ACTIVE SOLUTION
+
+
{tab.copy}
+
+ $
+ {tab.artifact}
+
+
+ {tab.cta}
+
+
+
+
+
+
+
{tab.eyebrow}
+
+ {Array.from({ length: 24 }).map((_, index) => (
+
+ ))}
+
+
+
+ secrets sealed · proof attached
+
+ {active === 1
+ ? 'policy valid'
+ : active === 2
+ ? 'model verified'
+ : 'agent live'}
+
+
+
+
+
+ )
+}
+
+export function PlatformFeatureSwitch({
+ cards,
+}: {
+ cards: Array<{ title: string; subtitle: string; copy: string }>
+}) {
+ const [active, setActive] = useState(0)
+ const card = cards[active]
+
+ return (
+
+
+
+ Platform
+
+
+ Built for private AI work
+
+
+ {card.copy}
+
+
+
+ {cards.map((item, index) => (
+
+ ))}
+
+
+
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+ ))}
+
+
+ {active === 0 &&
}
+ {active === 1 &&
}
+ {active === 2 &&
}
+
+
+
+ )
+}
+
+function EasyMockup() {
+ const typeDuration = 14
+ const lineGap = 160
+ const prompt = 'deploy this agent privately on Phala and prove what ran'
+ const terminalLines = [
+ ['cmd', 'phala deploy -c docker-compose.yml -n ai-agent'],
+ ['ok', 'CVM created · encrypted env loaded · app booting'],
+ ['cmd', 'phala cvms get ai-agent --json'],
+ [
+ 'out',
+ 'public_urls[0].app → https://ai-agent-7bd8.dstack-prod.phala.network',
+ ],
+ ['cmd', 'phala cvms attestation '],
+ ['ok', 'Attestation valid. Image digest matches. Safe to use.'],
+ ]
+ const terminalTextLines = terminalLines.map(([, line]) => line)
+ const terminalStart = prompt.length * typeDuration + 320
+ const terminalDelay = (index: number) =>
+ terminalStart +
+ terminalTextLines
+ .slice(0, index)
+ .reduce(
+ (delay, line) => delay + line.length * typeDuration + lineGap,
+ 0,
+ )
+
+ return (
+
+
+
+
+
+
+
+
+
+ marvin - Claude Code - bun ‹ claude
+
+
+
+
+ [marvin@Mac ~/phala-ai-agent] % claude
+
+
+
+ ›
+
+ {prompt}
+
+
+
+
+
+ {terminalLines.map(([kind, line], index) => (
+
+
+ {kind === 'cmd' ? '$' : kind}
+
+
+
+ {line}
+
+
+
+ ))}
+
+
+
+
+ )
+}
+
+function OpenMockup() {
+ const dimensions = [
+ ['Code provenance', 92],
+ ['Verified execution', 88],
+ ['Hardware isolation', 95],
+ ['Network trust', 56],
+ ['Upgrade safety', 48],
+ ] as const
+
+ return (
+
+
+
+
App
+
+ Sovereign AI Life
+
+
+ {[
+ ['app id', '0x530f...7bd8'],
+ ['creator', '0x10f2...adcf'],
+ ['report', 'attestation.json'],
+ ].map(([label, value]) => (
+
+ {label}
+
+ {value}
+
+
+ ))}
+
+
+
+
+ Sovereign level
+
+ 76
+
+
+
+ {[true, true, true, true, false].map((complete, index) => (
+
+ ))}
+
+
+
+
+
+
+
+ Verification dimensions
+
+
+ attestation.json
+
+
+
+ {[
+ ['3/5', 'complete'],
+ ['76', 'sovereign level'],
+ ['2', 'pending controls'],
+ ].map(([value, label]) => (
+
+
+ {value}
+
+
{label}
+
+ ))}
+
+
+ {dimensions.map(([label, value]) => (
+
+
+ {label
+ .replace('Code provenance', 'Code proof')
+ .replace('Verified execution', 'Verified run')
+ .replace('Hardware isolation', 'Hardware lock')
+ .replace('Network trust', 'Network trust')
+ .replace('Upgrade safety', 'Upgrade delay')}
+
+
+
+
+ = 80
+ ? 'demo-mono text-right text-[#088660]'
+ : 'demo-mono text-right text-base-500'
+ }
+ >
+ {value}
+
+
+ ))}
+
+
+
+
+ )
+}
+
+function PrivateMockup() {
+ return (
+
+
+ Phala TEE
+ Native GPU
+ Plain cloud
+
+
+ {[
+ ['Native GPU', 100, 'bg-base-300', '%'],
+ ['GPU TEE', 95, 'bg-[#083fd1]', '%'],
+ ['Proof coverage', 100, 'bg-primary', '%'],
+ ].map(([label, value, color, note], index) => (
+
+ ))}
+
+
+ )
+}
+
+export function JumpNumber({
+ value,
+ prefix = '',
+ suffix = '',
+}: {
+ value: number
+ prefix?: string
+ suffix?: string
+}) {
+ const [display, setDisplay] = useState(0)
+
+ useEffect(() => {
+ let frame = 0
+ const totalFrames = 42
+ let timer: number | undefined
+
+ const start = () => {
+ frame = 0
+ setDisplay(0)
+ timer = window.setInterval(() => {
+ frame += 1
+ const progress = Math.min(frame / totalFrames, 1)
+ setDisplay(Math.round(value * (1 - (1 - progress) ** 3)))
+ if (frame >= totalFrames && timer) window.clearInterval(timer)
+ }, 24)
+ }
+
+ start()
+ const loop = window.setInterval(start, 6400)
+
+ return () => {
+ if (timer) window.clearInterval(timer)
+ window.clearInterval(loop)
+ }
+ }, [value])
+
+ return (
+
+ {prefix}
+ {display.toLocaleString()}
+ {suffix}
+
+ )
+}
diff --git a/src/app/(main)/art-direction-demo/page.tsx b/src/app/(main)/art-direction-demo/page.tsx
new file mode 100644
index 00000000..eaabd742
--- /dev/null
+++ b/src/app/(main)/art-direction-demo/page.tsx
@@ -0,0 +1,10 @@
+import LandingPageContent, {
+ dynamic,
+ metadata,
+} from '../(home)/sections/content'
+
+export { dynamic, metadata }
+
+export default function ArtDirectionDemoPage() {
+ return
+}
diff --git a/src/app/(main)/blog/banner.tsx b/src/app/(main)/blog/banner.tsx
index 3c6b9770..6a6a4e54 100644
--- a/src/app/(main)/blog/banner.tsx
+++ b/src/app/(main)/blog/banner.tsx
@@ -1,123 +1,105 @@
-'use client'
-
import { format } from 'date-fns'
+import { ArrowRight } from 'lucide-react'
import Link from 'next/link'
-import { useEffect, useState } from 'react'
-import TagLink from '@/components/tag-link'
-import {
- Carousel,
- type CarouselApi,
- CarouselContent,
- CarouselItem,
-} from '@/components/ui/carousel'
import { CdnImage } from '@/components/ui/cdn-image'
import { buildCoverUrl } from '@/lib/image-url'
import type { ParsedListPage } from '@/lib/notion-client'
-import { cn } from '@/lib/utils'
-
-export default function Banners({ pages }: { pages: ParsedListPage[] }) {
- const [api, setApi] = useState()
- const [selectedIndex, setSelectedIndex] = useState(0)
- useEffect(() => {
- if (!api) return
-
- const onSelect = () => {
- setSelectedIndex(api.selectedScrollSnap())
- }
+function ArticleImage({
+ page,
+ priority = false,
+}: {
+ page: ParsedListPage
+ priority?: boolean
+}) {
+ return (
+
+
+
+ )
+}
- onSelect()
- api.on('select', onSelect)
+function ArticleMeta({ page }: { page: ParsedListPage }) {
+ return (
+
+ {page.tags.slice(0, 2).map((tag, i) => (
+
+ {tag}
+
+ ))}
+
+ )
+}
- return () => {
- api.off('select', onSelect)
- }
- }, [api])
+function ArticleDate({ page }: { page: ParsedListPage }) {
+ if (!page.publishedDate) return null
- const scrollTo = (index: number) => {
- api?.scrollTo(index)
- }
+ return (
+
+ {format(new Date(page.publishedDate), 'MMM dd, yyyy')}
+
+ )
+}
- if (!pages) {
+export default function Banners({ pages }: { pages: ParsedListPage[] }) {
+ if (!pages || pages.length === 0) {
return null
}
+ const [major, ...secondary] = pages.slice(0, 3)
+
return (
-
-
-
- {pages.map((page) => (
-
-
-
-
-
-
- {page.tags.map((tag, i) => (
-
- {tag}
-
- ))}
-
-
-
- {page.publishedDate && (
-
-
- {format(new Date(page.publishedDate), 'MMM dd, yyyy')}
-
-
- )}
-
-
-
- ))}
-
-
-
-
- {pages.map((_, index) => (
-
+
)
}
diff --git a/src/app/(main)/blog/list.tsx b/src/app/(main)/blog/list.tsx
index f473d361..b7143903 100644
--- a/src/app/(main)/blog/list.tsx
+++ b/src/app/(main)/blog/list.tsx
@@ -17,18 +17,24 @@ export default function List({ initialPages, initialCursor }: Props) {
const [pages, setPages] = useState(initialPages)
const [cursor, setCursor] = useState(initialCursor)
const [isPending, setIsPending] = useState(false)
+ const [error, setError] = useState
(null)
const loadMore = async () => {
if (isPending || !cursor) return
setIsPending(true)
+ setError(null)
try {
- const res = await fetch(`/api/posts/${cursor}`)
- if (!res.ok) throw new Error('Failed to fetch')
+ const res = await fetch(`/api/posts/${encodeURIComponent(cursor)}`)
+ if (!res.ok) {
+ const result = await res.json().catch(() => null)
+ throw new Error(result?.error || 'Failed to fetch')
+ }
const result = await res.json()
- setPages([...pages, ...result.pages])
+ setPages((currentPages) => [...currentPages, ...result.pages])
setCursor(result.nextCursor || null)
} catch (error) {
console.error('Failed to load more posts:', error)
+ setError('Could not load more posts. Try again.')
} finally {
setIsPending(false)
}
@@ -36,26 +42,34 @@ export default function List({ initialPages, initialCursor }: Props) {
return (
<>
-
+
{pages.map((page) => (
))}
-
+
{cursor ? (
-
+
+
+ {error && (
+
+ {error}
+
+ )}
+
) : null}
>
diff --git a/src/app/(main)/blog/page.tsx b/src/app/(main)/blog/page.tsx
index c1d97d73..081d6bf9 100644
--- a/src/app/(main)/blog/page.tsx
+++ b/src/app/(main)/blog/page.tsx
@@ -1,101 +1,173 @@
+import { ArrowRight, Rss } from 'lucide-react'
import type { Metadata } from 'next'
-import { BiRss } from 'react-icons/bi'
+import { ExaSurface } from '@/components/product/exa-typography'
import TagSearch from '@/components/TagSearch'
-import { Button } from '@/components/ui/button'
import { env } from '@/env'
import { queryDatabase } from '@/lib/notion-client'
import { retrieveTags } from '@/lib/post'
-import { cn } from '@/lib/utils'
import Banners from './banner'
import List from './list'
export const revalidate = 7200
-async function getBlogData() {
- const tags = await retrieveTags()
- const queryBannerPages = await queryDatabase(
- {
- database_id: env.NOTION_POSTS_DATABASE_ID,
- filter: {
- or: [
- {
- property: 'Tags',
- multi_select: {
- contains: 'Weekly report',
+function countTags(pages: Awaited
>['pages']) {
+ const counts: Record = {}
+ for (const page of pages) {
+ for (const tag of page.tags) {
+ if (tag === 'Changelog' || tag === 'Pinned' || tag === 'not-listed') {
+ continue
+ }
+ counts[tag] = (counts[tag] || 0) + 1
+ }
+ }
+ return counts
+}
+
+async function getAllPublishedPostPages() {
+ const pages = []
+ let startCursor: string | null = null
+
+ do {
+ const result = await queryDatabase(
+ {
+ database_id: env.NOTION_POSTS_DATABASE_ID,
+ filter: {
+ and: [
+ {
+ property: 'Status',
+ status: {
+ equals: 'Published',
+ },
},
- },
- {
- property: 'Tags',
- multi_select: {
- contains: 'Monthly report',
+ {
+ property: 'Post Type',
+ select: {
+ equals: 'Post',
+ },
},
- },
- {
- property: 'Tags',
- multi_select: {
- contains: 'Pinned',
+ {
+ property: 'Tags',
+ multi_select: {
+ does_not_contain: 'Changelog',
+ },
+ },
+ {
+ property: 'Tags',
+ multi_select: {
+ does_not_contain: 'not-listed',
+ },
},
+ ],
+ },
+ sorts: [
+ {
+ property: 'Published Time',
+ direction: 'descending',
},
],
+ page_size: 100,
+ ...(startCursor ? { start_cursor: startCursor } : {}),
},
- sorts: [
- {
- property: 'Published Time',
- direction: 'descending',
- },
- ],
- page_size: 5,
- },
- { tags: ['blog', 'blog-banners'] },
- )
+ { tags: ['blog', 'blog-tag-counts'] },
+ )
- const { next_cursor, pages } = await queryDatabase(
- {
- database_id: env.NOTION_POSTS_DATABASE_ID,
- filter: {
- and: [
- {
- property: 'Status',
- status: {
- equals: 'Published',
- },
- },
- {
- property: 'Post Type',
- select: {
- equals: 'Post',
- },
+ pages.push(...result.pages)
+ startCursor = result.next_cursor
+ } while (startCursor)
+
+ return pages
+}
+
+async function getBlogData() {
+ const [tags, queryBannerPages, postsResult, allPublishedPages] =
+ await Promise.all([
+ retrieveTags(),
+ queryDatabase(
+ {
+ database_id: env.NOTION_POSTS_DATABASE_ID,
+ filter: {
+ or: [
+ {
+ property: 'Tags',
+ multi_select: {
+ contains: 'Weekly report',
+ },
+ },
+ {
+ property: 'Tags',
+ multi_select: {
+ contains: 'Monthly report',
+ },
+ },
+ {
+ property: 'Tags',
+ multi_select: {
+ contains: 'Pinned',
+ },
+ },
+ ],
},
- {
- property: 'Tags',
- multi_select: {
- does_not_contain: 'Changelog',
+ sorts: [
+ {
+ property: 'Published Time',
+ direction: 'descending',
},
+ ],
+ page_size: 5,
+ },
+ { tags: ['blog', 'blog-banners'] },
+ ),
+ queryDatabase(
+ {
+ database_id: env.NOTION_POSTS_DATABASE_ID,
+ filter: {
+ and: [
+ {
+ property: 'Status',
+ status: {
+ equals: 'Published',
+ },
+ },
+ {
+ property: 'Post Type',
+ select: {
+ equals: 'Post',
+ },
+ },
+ {
+ property: 'Tags',
+ multi_select: {
+ does_not_contain: 'Changelog',
+ },
+ },
+ {
+ property: 'Tags',
+ multi_select: {
+ does_not_contain: 'not-listed',
+ },
+ },
+ ],
},
- {
- property: 'Tags',
- multi_select: {
- does_not_contain: 'not-listed',
+ sorts: [
+ {
+ property: 'Published Time',
+ direction: 'descending',
},
- },
- ],
- },
- sorts: [
- {
- property: 'Published Time',
- direction: 'descending',
+ ],
+ page_size: 18,
},
- ],
- page_size: 18,
- },
- { tags: ['blog', 'blog-posts'] },
- )
+ { tags: ['blog', 'blog-posts'] },
+ ),
+ getAllPublishedPostPages(),
+ ])
+ const tagCounts = countTags(allPublishedPages)
return {
- tags,
- initialPages: pages,
- initialCursor: next_cursor,
+ tags: tags.filter((tag) => tagCounts[tag] > 0),
+ tagCounts,
+ initialPages: postsResult.pages,
+ initialCursor: postsResult.next_cursor,
bannerPages: queryBannerPages ? queryBannerPages.pages : [],
}
}
@@ -105,51 +177,64 @@ export const metadata: Metadata = {
}
export default async function BlogPage() {
- const { tags, initialPages, initialCursor, bannerPages } = await getBlogData()
+ const { tags, tagCounts, initialPages, initialCursor, bannerPages } =
+ await getBlogData()
return (
-
-
-
-
-
-
-
- Phala Blog
-
-
-
-
+
+
+
+
+
+
+ Blog
+
+ Notes from the private compute frontier.
+
+
+
+
+ Product updates, engineering reports, ecosystem news, and
+ writing from the Phala team.
+
+
+
+ RSS feed
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+ Latest posts
+
+
+
+
+
+
)
}
diff --git a/src/app/(main)/compare/[slug]/compare.tsx b/src/app/(main)/compare/[slug]/compare.tsx
index fbed7f5d..d63193ca 100644
--- a/src/app/(main)/compare/[slug]/compare.tsx
+++ b/src/app/(main)/compare/[slug]/compare.tsx
@@ -1,23 +1,67 @@
-import { AlertCircle, CheckCircle, CircleMinus } from 'lucide-react'
+import {
+ AlertCircle,
+ ArrowRight,
+ CheckCircle2,
+ CircleMinus,
+ Cpu,
+ ExternalLink,
+ LockKeyhole,
+ Network,
+ ShieldCheck,
+} from 'lucide-react'
import Image from 'next/image'
-import { Fragment } from 'react'
+import Link from 'next/link'
+import type { CSSProperties, ReactNode } from 'react'
import FinalCTA from '@/components/final-cta'
-import { Feature13 } from '@/components/shadcn-blocks/feature13'
-import { Badge } from '@/components/ui/badge'
-import { Button } from '@/components/ui/button'
+import { CLOUD_MIGRATION_PROMPT } from '@/lib/skills'
import type { ComparisonData, ComparisonStatus } from '@/data/comparisons'
+import { cn } from '@/lib/utils'
+import DotMatrixLogoMorph from './dot-matrix-logo-morph'
-function StatusIcon({ status }: { status: ComparisonStatus }) {
- switch (status) {
- case 'good':
- return
- case 'bad':
- return
- case 'partial':
- return
- }
-}
+type ComparisonFeature = ComparisonData['features'][number]
+type CompetitorLogoKey = 'aws' | 'gcp' | 'tinfoil'
+
+const compareTypeVars = {
+ fontFamily: 'var(--compare-body)',
+ '--compare-heading': 'var(--v3-heading)',
+ '--compare-body': 'var(--v3-body)',
+ '--compare-ui': 'var(--v3-ui)',
+ '--compare-mono': 'var(--v3-mono)',
+} as CSSProperties
+
+const allComparisons = [
+ {
+ slug: 'phala-vs-aws-nitro',
+ heading: 'Phala vs AWS Nitro',
+ label: 'Enclave isolation',
+ logoKey: 'aws',
+ description:
+ 'Compare dstack and Phala Cloud against AWS Nitro Enclaves for confidential computing and AI infrastructure.',
+ image: '/compare/compare-aws.png',
+ url: '/compare/phala-vs-aws-nitro',
+ },
+ {
+ slug: 'phala-vs-gcp',
+ heading: 'Phala vs Google Cloud',
+ label: 'Confidential VMs',
+ logoKey: 'gcp',
+ description:
+ 'Compare public verification, GPU confidential computing, and cloud portability against Google Cloud Confidential VM.',
+ image: '/compare/compare-gcp.png',
+ url: '/compare/phala-vs-gcp',
+ },
+ {
+ slug: 'phala-vs-tinfoil',
+ heading: 'Phala vs Tinfoil',
+ label: 'Confidential AI',
+ logoKey: 'tinfoil',
+ description:
+ 'Compare managed confidential AI with Phala infrastructure control, open-source proofs, and private agent support.',
+ image: '/compare/compare-tinfoil.png',
+ url: '/compare/phala-vs-tinfoil',
+ },
+]
interface CompareProps {
data: ComparisonData
@@ -25,365 +69,1136 @@ interface CompareProps {
}
export default function Compare({ data, currentSlug }: CompareProps) {
- // Define all comparison pages with Feature13 format using hero banner images
- const allComparisons = [
- {
- id: 'compare-aws',
- slug: 'phala-vs-aws-nitro',
- heading: 'Phala vs AWS Nitro',
- label: 'CONFIDENTIAL COMPUTING',
- description:
- 'Compare confidential computing features, pricing, and deployment options. See how Phala stacks up against AWS Nitro Enclaves.',
- image: '/compare/compare-aws.png', // Using hero banner image
- url: '/compare/phala-vs-aws-nitro',
- },
- {
- id: 'compare-gcp',
- slug: 'phala-vs-gcp',
- heading: 'Phala vs Google Cloud',
- label: 'TEE TECHNOLOGY',
- description:
- 'See the differences in TEE technology, GPU support, and pricing. Detailed comparison with Google Cloud Confidential Computing.',
- image: '/compare/compare-gcp.png', // Using hero banner image
- url: '/compare/phala-vs-gcp',
- },
- {
- id: 'compare-tinfoil',
- slug: 'phala-vs-tinfoil',
- heading: 'Phala vs Tinfoil',
- label: 'INFRASTRUCTURE',
- description:
- 'Explore the key differences in deployment, features, and infrastructure. Full comparison between Phala and Tinfoil.',
- image: '/compare/compare-tinfoil.png', // Using hero banner image
- url: '/compare/phala-vs-tinfoil',
- },
- ]
-
- // Filter out current page from comparisons
const otherComparisons = allComparisons.filter(
- (comp) => comp.slug !== currentSlug,
+ (comparison) => comparison.slug !== currentSlug,
)
return (
-
- {/* Hero Section */}
-
- {/* Gradient Background */}
-
-
-
-
-
-
-
-
- Platform Comparison
-
-
- {data.hero.title}
-
- {data.hero.alternativeText && (
-
- {data.hero.alternativeText}
-
- )}
- {data.hero.subtitle && (
-
- {data.hero.subtitle}
-
- )}
-
-
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function HeroSection({ data }: { data: ComparisonData }) {
+ return (
+
+
+
+
+
Platform comparison
+
+ {data.hero.title}
+
+
+ {data.hero.alternativeText ??
+ `A practical comparison for teams choosing between Phala Cloud and ${data.competitor.name}.`}
+
+ {data.hero.subtitle ? (
+
+ {data.hero.subtitle}
+
+ ) : null}
+
+
+ {data.cta.text}
+
+
+ Compare details
+
+
+ Talk to sales
+
- {data.hero.bannerImage && (
-
- )}
+
+
+
+
+
+
+ )
+}
+
+function HeroMedia({ data }: { data: ComparisonData }) {
+ const competitorKey = getCompetitorLogoKey(data.competitor.name)
+
+ return (
+
+
+
+
-
-
- {/* Main Content */}
-
-
- {/* Quick Takeaways */}
-
-
-
- Quick Overview
-
-
- Why Choose Phala?
-
+
+
+ )
+}
+
+function getCompetitorLogoKey(name: string): CompetitorLogoKey {
+ const normalized = name.toLowerCase()
+ if (normalized.includes('gcp') || normalized.includes('google')) return 'gcp'
+ if (normalized.includes('tinfoil')) return 'tinfoil'
+ return 'aws'
+}
+
+function VerdictSection({ data }: { data: ComparisonData }) {
+ const proofPoints = data.proofPoints ?? []
+ const score = { bad: 0, partial: 1, good: 2 } satisfies Record<
+ ComparisonStatus,
+ number
+ >
+ const phalaWins = data.features.filter(
+ (feature) => score[feature.phala] > score[feature.competitor],
+ )
+ const competitorWins = data.features.filter(
+ (feature) => score[feature.competitor] > score[feature.phala],
+ )
+ const phalaHighlights = phalaWins.slice(0, 3)
+ const competitorHighlights = competitorWins.slice(0, 3)
+ const decisionPoints = proofPoints.length
+ ? proofPoints
+ : [
+ { label: 'Trust primitive', value: 'public verification' },
+ { label: 'Runtime', value: 'portable deployment' },
+ { label: 'AI shape', value: 'confidential capacity' },
+ ]
+
+ return (
+
+
+
+
+
Decision map
+
+ Own the proof, not just the account.
+
+
+ {data.competitor.name} keeps trust close to its cloud control
+ plane. Phala turns the workload, attestation, and AI runtime into
+ portable evidence you can verify outside a single account.
+
+
+
+
+
+ Phala
+
+
+ {phalaWins.length}
+
+
+
+
+ Other
+
+
+ {competitorWins.length}
+
+
-
- {data.quickTakeaways.map((takeaway) => (
-
-
-
+
+
+ {phalaHighlights.map((feature) => (
+
+
-
- {takeaway}
-
+
+ {feature.feature}
+
+
+ {feature.phalaText}
+
))}
-
-
- {/* Comparison Table */}
-
-
-
- Feature Comparison
-
-
- See How We Stack Up
-
-
-
-
-
- {/* Header Row */}
-
-
-
-
Phala
-
- Open-source confidential AI
-
-
-
+
+
+
+
+
+
+
+
{data.competitor.name}
-
- {data.competitor.description}
-
+
+ Cloud-bound control
+
-
- {/* Feature Rows */}
- {data.features.map((feature, index) => (
-
+
+
+
+
+ {(competitorHighlights.length
+ ? competitorHighlights
+ : data.features.slice(0, 3)
+ ).map((feature) => (
-
+
{feature.feature}
-
-
-
-
-
- {feature.phalaText}
-
+
+
+ {feature.competitorText}
+
+ ))}
+
+
+
+
+
+
+
Phala
+
+ Public proof path
+
+
+
+
+
+
+ {decisionPoints.map((point) => (
-
-
- {feature.competitorText}
-
+
+ {point.label}
+
+
+ {point.value}
+
-
- ))}
-
- {/* CTA Row */}
-
-
-
-
-
+
+
+
+
+ )
+}
- {/* What does Phala do? */}
-
-
- {data.sections.whatIsPhala.title}
+function DecisionFlow({
+ steps,
+ tone,
+}: {
+ steps: string[]
+ tone: 'phala' | 'neutral'
+}) {
+ return (
+
+ {steps.map((step, index) => (
+
+
+ {index + 1}
+
+
+ {step}
+
+ {index < steps.length - 1 ? (
+
+ ) : null}
+
+ ))}
+
+ )
+}
+function ProofMapSection({ data }: { data: ComparisonData }) {
+ return (
+
+
+
+
+
Trust path
+
+ Less reading. Follow the proof.
-
-
- {data.sections.whatIsPhala.content}
+
+
+
+
+
+
+ )
+}
+
+function ProofLane({
+ title,
+ steps,
+ tone = 'neutral',
+}: {
+ title: string
+ steps: string[]
+ tone?: 'phala' | 'neutral'
+}) {
+ return (
+
+
+
{title}
+
+ {tone === 'phala' ? 'verifiable' : 'provider-native'}
+
+
+
+ {steps.map((step, index) => (
+
+ {index > 0 ? (
+
+ ) : null}
+
+
+ {String(index + 1).padStart(2, '0')}
+
+
+ {step}
-
+
+ ))}
+
+
+ )
+}
- {/* What does Competitor do? */}
-
-
- {data.sections.whatIsCompetitor.title}
+function SignalBoardSection({ data }: { data: ComparisonData }) {
+ const signals = selectSignalFeatures(data.features)
+
+ return (
+
+
+
+
+
Signal board
+
+ Four things to scan.
-
-
- {data.sections.whatIsCompetitor.content}
-
+
+
+ official sources
+
+
+
+
+
+ {signals.map((feature) => (
+
+ ))}
+
+
+
+ )
+}
+
+function SignalCard({
+ feature,
+ competitorName,
+}: {
+ feature: ComparisonFeature
+ competitorName: string
+}) {
+ return (
+
+
{feature.feature}
+
+
+
+
+
+ )
+}
+
+function SignalMeter({
+ label,
+ status,
+ value,
+}: {
+ label: string
+ status: ComparisonStatus
+ value: string
+}) {
+ return (
+
+
+ {label}
+
+
+
+
{value}
+
+ )
+}
+
+function PositioningSection({ data }: { data: ComparisonData }) {
+ return (
+
+
+
+
+
Plain English
+
+ Two products with different centers of gravity.
+
+
+
+ }
+ label="Phala"
+ title={data.sections.whatIsPhala.title}
+ copy={data.sections.whatIsPhala.content}
+ />
+ }
+ label="Alternative"
+ title={data.sections.whatIsCompetitor.title}
+ copy={data.sections.whatIsCompetitor.content}
+ />
+
+
+
+
+ )
+}
+
+function MatrixSection({ data }: { data: ComparisonData }) {
+ return (
+
+
+
+
+
+ {data.features.map((feature) => (
+
+ ))}
+
+
+
+
+
+ Capability
+ }
+ >
+ Phala Cloud
+
+ {data.competitor.name}
-
-
- {/* Key Differentiators */}
-
-
-
- Key Advantages
-
-
- {data.sections.differentiators.title}
-
+ {data.features.map((feature) => (
+
+ ))}
+
+
+
+
+ )
+}
+
+function MobileFeatureCard({
+ feature,
+ competitorName,
+}: {
+ feature: ComparisonFeature
+ competitorName: string
+}) {
+ return (
+
+
+ {feature.feature}
+
+
+
+
+
+
+ )
+}
+
+function MobileStatusLine({
+ label,
+ status,
+ value,
+}: {
+ label: string
+ status: ComparisonStatus
+ value: string
+}) {
+ return (
+
+ )
+}
+
+function FeatureRow({ feature }: { feature: ComparisonFeature }) {
+ return (
+
+
+
+ {feature.feature}
+
+
+
{feature.phalaText}
+
+ {feature.competitorText}
+
+
+ )
+}
+
+function HeaderCell({
+ children,
+ icon,
+}: {
+ children: ReactNode
+ icon?: ReactNode
+}) {
+ return (
+
+ )
+}
+
+function StatusCell({
+ status,
+ children,
+}: {
+ status: ComparisonStatus
+ children: ReactNode
+}) {
+ return (
+
+ )
+}
+
+function StatusIcon({ status }: { status: ComparisonStatus }) {
+ if (status === 'good') {
+ return
+ }
+ if (status === 'partial') {
+ return
+ }
+ return
+}
+
+function ExplainerPanel({
+ icon,
+ label,
+ title,
+ copy,
+}: {
+ icon: ReactNode
+ label: string
+ title: string
+ copy: string
+}) {
+ return (
+
+
+
+ {title}
+
+
+ {copy}
+
+
+ )
+}
+
+function DifferentiatorsSection({ data }: { data: ComparisonData }) {
+ const [lead, ...rest] = data.sections.differentiators.content
+
+ return (
+
+
+
+
+ {lead ? (
+
+
+
Most important
+
+ {lead.title}
+
+
+ {lead.description}
+
+ ) : null}
+
+ {rest.map((item, index) => (
+
+
+ {String(index + 2).padStart(2, '0')}
+
+
+ {item.title}
+
+
+ {item.description}
+
+
+ ))}
+
+
+
+
+ )
+}
-
- {data.sections.differentiators.content.map((item, index) => (
+function DecisionSection({ data }: { data: ComparisonData }) {
+ return (
+
+
+
+
+
Decision guide
+
+ {data.sections.howToChoose.title}
+
+
+ {data.sections.howToChoose.content.map((item, index) => (
-
-
-
-
-
-
-
- {item.title}
-
-
-
-
- {item.description}
-
-
-
+
+ {String(index + 1).padStart(2, '0')}
+
+
{item}
))}
-
+
- {/* How to Choose */}
-
-
- {data.sections.howToChoose.title}
-
-
- {data.sections.howToChoose.content.map((item) => (
- -
-
-
- ))}
-
-
-
- {/* Pricing Comparison */}
-
-
-
- Pricing
-
-
- {data.sections.pricing.title}
-
+
+
-
-
-
- Recommended
-
-
-
- Phala Cloud
-
-
-
- {data.sections.pricing.phalaContent}
-
-
-
-
-
- {data.competitor.name}
-
-
-
- {data.sections.pricing.competitorContent}
-
-
-
+
+ {data.sections.pricing.title}
+
+
+
+ {data.sections.pricing.phalaContent}
+
+
+ {data.sections.pricing.competitorContent}
+
-
-
- {/* Why Choose Section */}
- {data.sections.whyChoose && (
-
-
- {data.sections.whyChoose.title}
-
-
- {data.sections.whyChoose.content.map((item) => (
-
-
-
+ {data.sections.whyChoose ? (
+
+
+ {data.sections.whyChoose.title}
+
+
+ {data.sections.whyChoose.content.map((item) => (
+
-
- ))}
+ ))}
+
-
- )}
+ ) : null}
+
+
+
+
+ )
+}
+function PriceBlock({
+ title,
+ children,
+ highlight = false,
+}: {
+ title: string
+ children: ReactNode
+ highlight?: boolean
+}) {
+ return (
+
+ )
+}
+
+function SourcesSection({ data }: { data: ComparisonData }) {
+ const sourceNotes = data.sections.sourceNotes
+
+ if (!sourceNotes?.length) {
+ return null
+ }
+
+ return (
+
+
+
+
+
Source notes
+
+ Official docs only.
+
+
+
-
+
+
+ )
+}
- {/* Compare with Others Section - Cross-linking using Feature13 */}
- {otherComparisons.length > 0 && (
-
+function MoreComparisons({
+ comparisons,
+}: {
+ comparisons: typeof allComparisons
+}) {
+ if (!comparisons.length) {
+ return null
+ }
+
+ return (
+
+
+
+ {comparisons.map((comparison) => (
+
+
+
+
+ {comparison.label}
+
+
+ {comparison.heading}
+
+
+
+ {comparison.description}
+
+
+
+ ))}
+
+
+
+ )
+}
+
+function CompareSection({
+ children,
+ className,
+ id,
+}: {
+ children: ReactNode
+ className?: string
+ id?: string
+}) {
+ return (
+
+ )
+}
+
+function CompareContainer({
+ children,
+ className,
+}: {
+ children: ReactNode
+ className?: string
+}) {
+ return (
+
+ {children}
+
+ )
+}
- {/* Final CTA */}
-
+function SectionHeading({
+ label,
+ title,
+ copy,
+ invert = false,
+}: {
+ label: string
+ title: string
+ copy: string
+ invert?: boolean
+}) {
+ return (
+
+
+
+ {label}
+
+
+ {title}
+
+
+
+ {copy}
+
)
}
+
+function ActionLink({
+ href,
+ children,
+ variant = 'primary',
+ external,
+}: {
+ href: string
+ children: ReactNode
+ variant?: 'primary' | 'outline' | 'ghost'
+ external?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function possessiveName(name: string) {
+ return name.endsWith('s') ? `${name}'` : `${name}'s`
+}
+
+function selectSignalFeatures(features: ComparisonFeature[]) {
+ const preferred = [
+ ['Open Source'],
+ ['GPU TEE Support'],
+ ['Decentralized Trust', 'Decentralized Governance'],
+ ['Multi-Cloud Support', 'Public Verifiability', 'Public Auditability'],
+ ]
+
+ return preferred
+ .map((names) => features.find((feature) => names.includes(feature.feature)))
+ .filter((feature): feature is ComparisonFeature => Boolean(feature))
+}
+
+function statusWidth(status: ComparisonStatus) {
+ if (status === 'good') return 100
+ if (status === 'partial') return 58
+ return 18
+}
+
+function shortenSignal(value: string) {
+ return value.length > 34 ? `${value.slice(0, 31)}...` : value
+}
+
+function summarizeChoice(value: string) {
+ return value
+ .replace(/^Choose Phala Cloud if you /, 'Use Phala when you ')
+ .replace(/^Choose .* if you're /, "Use it when you're ")
+ .replace(/^Choose .* if you /, 'Use it when you ')
+}
+
+function CompareStyles() {
+ return (
+
+ )
+}
diff --git a/src/app/(main)/compare/[slug]/dot-matrix-logo-morph.tsx b/src/app/(main)/compare/[slug]/dot-matrix-logo-morph.tsx
new file mode 100644
index 00000000..a22942ff
--- /dev/null
+++ b/src/app/(main)/compare/[slug]/dot-matrix-logo-morph.tsx
@@ -0,0 +1,221 @@
+'use client'
+
+import { useEffect, useMemo, useState, type CSSProperties } from 'react'
+
+export const dotMatrixLogoPaths = {
+ phala: '/compare/dot-matrix/phala.svg',
+ aws: '/compare/dot-matrix/aws.svg',
+ gcp: '/compare/dot-matrix/gcp.svg',
+ tinfoil: '/compare/dot-matrix/tinfoil.svg',
+} as const
+
+export type DotMatrixLogoKey = keyof typeof dotMatrixLogoPaths
+
+type Dot = {
+ x: number
+ y: number
+ width: number
+ height: number
+ fill: string
+}
+
+type SvgDots = {
+ viewBoxWidth: number
+ viewBoxHeight: number
+ dots: Dot[]
+}
+
+const dotCache = new Map
>()
+
+function parseSvgDots(svgText: string): SvgDots {
+ const document = new DOMParser().parseFromString(svgText, 'image/svg+xml')
+ const svg = document.querySelector('svg')
+ const viewBox = svg?.getAttribute('viewBox')?.split(/\s+/).map(Number)
+ const viewBoxWidth = viewBox?.[2] || Number(svg?.getAttribute('width')) || 1024
+ const viewBoxHeight =
+ viewBox?.[3] || Number(svg?.getAttribute('height')) || 1024
+
+ const dots = Array.from(document.querySelectorAll('rect[x][y]')).map(
+ (rect) => ({
+ x: Number(rect.getAttribute('x')) || 0,
+ y: Number(rect.getAttribute('y')) || 0,
+ width: Number(rect.getAttribute('width')) || 12,
+ height: Number(rect.getAttribute('height')) || 12,
+ fill: rect.getAttribute('fill') || '#c5ff2e',
+ }),
+ )
+
+ return { viewBoxWidth, viewBoxHeight, dots }
+}
+
+function loadSvgDots(path: string) {
+ const cached = dotCache.get(path)
+ if (cached) return cached
+
+ const request = fetch(path)
+ .then((response) => {
+ if (!response.ok) throw new Error(`Failed to load ${path}`)
+ return response.text()
+ })
+ .then(parseSvgDots)
+
+ dotCache.set(path, request)
+ return request
+}
+
+function sampleDots(dots: Dot[], count: number) {
+ if (dots.length <= count) return dots
+
+ return Array.from({ length: count }, (_, index) => {
+ const sourceIndex = Math.floor(
+ (index / Math.max(count - 1, 1)) * (dots.length - 1),
+ )
+ return dots[sourceIndex]
+ })
+}
+
+function fitDotsToCount(dots: Dot[], count: number) {
+ if (dots.length === count) return dots
+ if (dots.length > count) return sampleDots(dots, count)
+
+ return Array.from({ length: count }, (_, index) => {
+ const sourceIndex = Math.floor(
+ (index / Math.max(count - 1, 1)) * Math.max(dots.length - 1, 0),
+ )
+ return dots[sourceIndex]
+ })
+}
+
+export default function DotMatrixLogoMorph({
+ competitorKey,
+ compact = false,
+}: {
+ competitorKey: Exclude
+ compact?: boolean
+}) {
+ const [source, setSource] = useState(null)
+ const [target, setTarget] = useState(null)
+
+ useEffect(() => {
+ let cancelled = false
+
+ Promise.all([
+ loadSvgDots(dotMatrixLogoPaths.phala),
+ loadSvgDots(dotMatrixLogoPaths[competitorKey]),
+ ]).then(([sourceDots, targetDots]) => {
+ if (cancelled) return
+ setSource(sourceDots)
+ setTarget(targetDots)
+ })
+
+ return () => {
+ cancelled = true
+ }
+ }, [competitorKey])
+
+ const morphDots = useMemo(() => {
+ if (!source || !target) return []
+
+ const count = source.dots.length
+ const sourceDots = source.dots
+ const targetDots = fitDotsToCount(target.dots, count)
+
+ return sourceDots.map((start, index) => ({
+ start,
+ end: targetDots[index],
+ delay: (index % 17) * 0.026 + Math.floor(index / 17) * 0.002,
+ }))
+ }, [source, target])
+
+ return (
+
+ {morphDots.length === 0 ? (
+
+ ) : (
+ morphDots.map((dot, index) => (
+
+ ))
+ )}
+
+
+
+ )
+}
diff --git a/src/app/(main)/confidential-ai-models/benefits.tsx b/src/app/(main)/confidential-ai-models/benefits.tsx
index e607c1b4..16a98232 100644
--- a/src/app/(main)/confidential-ai-models/benefits.tsx
+++ b/src/app/(main)/confidential-ai-models/benefits.tsx
@@ -1,105 +1,502 @@
'use client'
-import Image from 'next/image'
+
+import { useState } from 'react'
+
+import {
+ DnaManifestSnake,
+ type ManifestLines,
+} from '@/components/product/dna-manifest-snake'
const traditionalAI = [
- 'Model provider can read your data',
- 'Cloud provider has full access',
- 'Your conversations are exposed',
+ 'Prompts, tools, keys, and memory can become provider-visible state',
+ 'Security depends on policy, contracts, and audit logs after the fact',
+ 'The model result cannot carry a hardware-backed runtime receipt',
]
+
const confidentialAI = [
- 'True end-to-end encryption',
- 'Al runs in isolated secure enclave',
- 'Nobody can access your data',
+ 'Inference runs inside TEE-backed infrastructure',
+ 'The runtime can be verified before the result is trusted',
+ 'Customers can inspect proof without operating on-prem GPUs',
]
-const Benefits = () => {
+const confidentialAiManifest: ManifestLines = [
+ [
+ { text: 'Private AI', size: 'hero' },
+ { text: 'calls', size: 'hero' },
+ ],
+ [
+ { text: 'prompts,' },
+ { text: 'keys,' },
+ { text: 'tools,' },
+ { text: 'and' },
+ { text: 'memory' },
+ { text: 'stay' },
+ { text: 'inside' },
+ { text: 'the' },
+ { text: 'runtime.' },
+ ],
+ [
+ { text: 'providers' },
+ { text: 'route' },
+ { text: 'the' },
+ { text: 'request' },
+ { text: 'without' },
+ { text: 'becoming' },
+ { text: 'the' },
+ { text: 'trust' },
+ { text: 'boundary.' },
+ ],
+ [
+ { text: 'Proof follows', size: 'wide' },
+ { text: 'the' },
+ { text: 'answer.' },
+ ],
+ [
+ { text: 'verify' },
+ { text: 'GPU,' },
+ { text: 'container,' },
+ { text: 'model' },
+ { text: 'route,' },
+ { text: 'and' },
+ { text: 'response.' },
+ ],
+ [
+ { text: 'same' },
+ { text: 'API' },
+ { text: 'shape,' },
+ { text: 'hardware-backed' },
+ { text: 'receipt,' },
+ { text: 'private' },
+ { text: 'inference.' },
+ ],
+ [
+ { text: 'streaming,' },
+ { text: 'tool' },
+ { text: 'calls,' },
+ { text: 'and' },
+ { text: 'agent' },
+ { text: 'memory' },
+ { text: 'keep' },
+ { text: 'their' },
+ { text: 'normal' },
+ { text: 'developer' },
+ { text: 'flow.' },
+ ],
+ [
+ { text: 'auditors' },
+ { text: 'can' },
+ { text: 'inspect' },
+ { text: 'evidence' },
+ { text: 'without' },
+ { text: 'reading' },
+ { text: 'the' },
+ { text: 'prompt.' },
+ ],
+ [
+ { text: 'users' },
+ { text: 'get' },
+ { text: 'answers' },
+ { text: 'plus' },
+ { text: 'runtime' },
+ { text: 'proof,' },
+ { text: 'not' },
+ { text: 'another' },
+ { text: 'black' },
+ { text: 'box.' },
+ ],
+]
+
+const graphModes = {
+ thirdParty: {
+ title: 'Third-party LLM',
+ eyebrow: 'Provider boundary',
+ state: 'exposed',
+ proof: ['same SDK', 'external runtime', 'policy trust'],
+ heroTitle: 'Cloud API',
+ heroSub: 'model provider runtime',
+ status: 'Data exposed',
+ providerSub: 'Sees request state',
+ infraSub: 'Sees model traffic',
+ receipt: 'no receipt',
+ },
+ privateLlm: {
+ title: 'Phala private LLM',
+ eyebrow: 'TEE boundary',
+ state: 'secure',
+ proof: ['same SDK', 'TEE endpoint', 'hardware receipt'],
+ heroTitle: 'AI Model',
+ heroSub: 'secured in TEE',
+ status: 'Verified proof',
+ providerSub: 'Cannot see data',
+ infraSub: 'Cannot see data',
+ receipt: 'hardware receipt',
+ },
+} as const
+
+type GraphMode = keyof typeof graphModes
+
+const modelMosaic = [
+ '/confidential-ai-models/deepseek.svg',
+ '/confidential-ai-models/qwen.svg',
+ '/confidential-ai-models/meta.svg',
+ '/confidential-ai-models/openai.svg',
+ '/confidential-ai-models/mistral.svg',
+ '/confidential-ai-models/gemini.svg',
+]
+
+function LockGlyph() {
return (
-
-
-
-
-
-
-
- Win Your Users' Trust
-
-
- Differentiate with verifiable privacy, build customer confidence
- with audit-ready cryptographic proofs, and enter regulated markets
- instantly.
-
+
+ )
+}
+
+function WarningGlyph() {
+ return (
+
+ )
+}
+
+function ProviderMark({ type }: { type: 'openai' | 'cloud' }) {
+ if (type === 'cloud') {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+}
+
+function MethodGraph() {
+ const [mode, setMode] = useState
('privateLlm')
+ const active = graphModes[mode]
+ const secure = mode === 'privateLlm'
+
+ return (
+
+
+
+
+ {active.eyebrow}
+
+
+ {active.title}
+
+
+
+
+ {[
+ ['thirdParty', 'Third-party API'],
+ ['privateLlm', 'Private LLM'],
+ ].map(([key, label]) => (
+
+ ))}
+
+
+
+
+
+ {active.proof.map((item, index) => (
+
0 ? 'text-[#8a8780]' : 'text-[#161513]'
+ }`}
+ >
+ 0 ? 'bg-[#8a8780]/40' : 'bg-primary'
+ }`}
+ />
+ {item}
+
+ ))}
+
+
+