diff --git a/src/components/ui/Cube.tsx b/src/components/ui/Cube.tsx new file mode 100644 index 0000000..fc5de2b --- /dev/null +++ b/src/components/ui/Cube.tsx @@ -0,0 +1,131 @@ +"use client"; + +import React from "react"; +import { motion } from "framer-motion"; + +interface CubeProps { + title: string; + descriptionTitle: string; + description: string; + isActive: boolean; + index: number; + onHover: () => void; + onLeave: () => void; + onClick: () => void; +} + +const CubeSvg: React.FC & { index: number }> = ({ index, ...props }) => ( + + + + + + + + + +); + +export function Cube({ title, descriptionTitle, description, isActive, index, onHover, onLeave, onClick }: CubeProps) { + return ( +
+ + {/* SVG Cube */} + + + {/* Title overlay */} +
+

+ {title} +

+
+ + {/* Description with arrow line - Desktop */} + {isActive && ( + + {/* Arrow line */} + + + + + {/* Description text */} +
+

+ {descriptionTitle} +

+

+ {description} +

+
+
+ )} + + {/* Description for Mobile - Below cube */} +
+
+ ); +} diff --git a/src/components/ui/CubeLight.tsx b/src/components/ui/CubeLight.tsx new file mode 100644 index 0000000..556d2f2 --- /dev/null +++ b/src/components/ui/CubeLight.tsx @@ -0,0 +1,131 @@ +"use client"; + +import React from "react"; +import { motion } from "framer-motion"; + +interface CubeProps { + title: string; + descriptionTitle: string; + description: string; + isActive: boolean; + index: number; + onHover: () => void; + onLeave: () => void; + onClick: () => void; +} + +const CubeSvg: React.FC & { index: number }> = ({ index, ...props }) => ( + + + + + + + + + +); + +export function CubeLight({ title, descriptionTitle, description, isActive, index, onHover, onLeave, onClick }: CubeProps) { + return ( +
+ + {/* SVG Cube */} + + + {/* Title overlay */} +
+

+ {title} +

+
+ + {/* Description with arrow line - Desktop */} + {isActive && ( + + {/* Arrow line */} + + + + + {/* Description text */} +
+

+ {descriptionTitle} +

+

+ {description} +

+
+
+ )} + + {/* Description for Mobile - Below cube */} +
+
+ ); +} diff --git a/src/components/ui/ScrollDown.tsx b/src/components/ui/ScrollDown.tsx new file mode 100644 index 0000000..567bc28 --- /dev/null +++ b/src/components/ui/ScrollDown.tsx @@ -0,0 +1,22 @@ +'use client' + +import { ChevronDoubleDownIcon } from '@heroicons/react/24/outline' +import { useScroll } from '@/hooks/useScroll' + +export function ScrollDown() { + const { isAtBottom, scrollToNext } = useScroll() + + if (isAtBottom) { + return null + } + + return ( + + ) +} diff --git a/src/components/ui/ScrollUp.tsx b/src/components/ui/ScrollUp.tsx new file mode 100644 index 0000000..1fe1c70 --- /dev/null +++ b/src/components/ui/ScrollUp.tsx @@ -0,0 +1,22 @@ +'use client' + +import { ChevronDoubleUpIcon } from '@heroicons/react/24/outline' +import { useScroll } from '@/hooks/useScroll' + +export function ScrollUp() { + const { isAtBottom, scrollToTop } = useScroll() + + if (!isAtBottom) { + return null + } + + return ( + + ) +} diff --git a/src/components/ui/StackedCubes.tsx b/src/components/ui/StackedCubes.tsx new file mode 100644 index 0000000..c993d7a --- /dev/null +++ b/src/components/ui/StackedCubes.tsx @@ -0,0 +1,95 @@ +"use client"; + +import { useState } from "react"; +import { motion } from "framer-motion"; +import { Cube } from "@/components/ui/Cube" + +const stackData = [ + { + id: "agent", + title: "Agent Layer", + descriptionTitle: "Your sovereign agent with private memory and permissioned data access—always under your control.", + description: + "Choose from a wide library of open-source LLMs, paired with built-in semantic search and retrieval.\nIt coordinates across people, apps, and other agents to plan, create, and execute.\nIt operates inside a compliant legal & financial sandbox, ready for real-world transactions and operations.\nMore than just an assistant—an intelligent partner that learns and does your way.", + position: "top", + }, + { + id: "network", + title: "Network Layer", + descriptionTitle: "A global, end-to-end encrypted overlay that simply doesn’t break.", + description: + "Shortest-path routing moves your traffic the fastest way, every time.\nInstant discovery with integrated DNS, semantic search, and indexing.\nA distributed CDN and edge delivery keep content available and tamper-resistant worldwide.\nBuilt-in tool services and secure coding sandboxes—seamless on phones, desktops, and edge.", + position: "middle", + }, + { + id: "cloud", + title: "Cloud Layer", + descriptionTitle: "An autonomous, stateless OS that enforces pre-deterministic deployments you define.", + description: + "Workloads are cryptographically bound to your private key—location and access are yours.\nNo cloud vendor or middleman in the path: end-to-end ownership and isolation by default.\nGeo-aware placement delivers locality, compliance, and ultra-low latency where it matters.\nEncrypted, erasure-coded storage, decentralized compute and GPU on demand—including LLMs.", + position: "bottom", + }, +]; + +export function StackedCubes() { + const [active, setActive] = useState("agent"); + const [selectedForMobile, setSelectedForMobile] = useState("agent"); + + const handleCubeClick = (id: string) => { + setSelectedForMobile(prev => (prev === id ? null : id)); + }; + + const selectedMobileLayer = stackData.find(layer => layer.id === selectedForMobile); + + return ( +
+
setActive("agent")} + > + + {stackData.map((layer, index) => ( +
+ setActive(layer.id)} + onLeave={() => {}} + onClick={() => handleCubeClick(layer.id)} + /> +
+ ))} +
+
+ {selectedMobileLayer && ( +
+

+ {selectedMobileLayer.descriptionTitle} +

+

+ {selectedMobileLayer.description} +

+
+ )} +
+ ); +} diff --git a/src/components/ui/StackedCubesLight.tsx b/src/components/ui/StackedCubesLight.tsx new file mode 100644 index 0000000..03c2ce5 --- /dev/null +++ b/src/components/ui/StackedCubesLight.tsx @@ -0,0 +1,95 @@ +"use client"; + +import { useState } from "react"; +import { motion } from "framer-motion"; +import { CubeLight } from "@/components/ui/CubeLight" + +const stackData = [ + { + id: "agent", + title: "Agent Layer", + descriptionTitle: "Your sovereign agent with private memory and permissioned data access—always under your control.", + description: + "Choose from a wide library of open-source LLMs, paired with built-in semantic search and retrieval.\nIt coordinates across people, apps, and other agents to plan, create, and execute.\nIt operates inside a compliant legal & financial sandbox, ready for real-world transactions and operations.\nMore than just an assistant—an intelligent partner that learns and does your way.", + position: "top", + }, + { + id: "network", + title: "Network Layer", + descriptionTitle: "A global, end-to-end encrypted overlay that simply doesn’t break.", + description: + "Shortest-path routing moves your traffic the fastest way, every time.\nInstant discovery with integrated DNS, semantic search, and indexing.\nA distributed CDN and edge delivery keep content available and tamper-resistant worldwide.\nBuilt-in tool services and secure coding sandboxes—seamless on phones, desktops, and edge.", + position: "middle", + }, + { + id: "cloud", + title: "Cloud Layer", + descriptionTitle: "An autonomous, stateless OS that enforces pre-deterministic deployments you define.", + description: + "Workloads are cryptographically bound to your private key—location and access are yours.\nNo cloud vendor or middleman in the path: end-to-end ownership and isolation by default.\nGeo-aware placement delivers locality, compliance, and ultra-low latency where it matters.\nEncrypted, erasure-coded storage, decentralized compute and GPU on demand—including LLMs.", + position: "bottom", + }, +]; + +export function StackedCubesLight() { + const [active, setActive] = useState("agent"); + const [selectedForMobile, setSelectedForMobile] = useState("agent"); + + const handleCubeClick = (id: string) => { + setSelectedForMobile(prev => (prev === id ? null : id)); + }; + + const selectedMobileLayer = stackData.find(layer => layer.id === selectedForMobile); + + return ( +
+
setActive("agent")} + > + + {stackData.map((layer, index) => ( +
+ setActive(layer.id)} + onLeave={() => {}} + onClick={() => handleCubeClick(layer.id)} + /> +
+ ))} +
+
+ {selectedMobileLayer && ( +
+

+ {selectedMobileLayer.descriptionTitle} +

+

+ {selectedMobileLayer.description} +

+
+ )} +
+ ); +} diff --git a/src/components/ui/bento-grid.tsx b/src/components/ui/bento-grid.tsx new file mode 100644 index 0000000..6499c16 --- /dev/null +++ b/src/components/ui/bento-grid.tsx @@ -0,0 +1,79 @@ +import { cn } from "@/lib/utils"; +import { CT, CP } from "@/components/Texts"; +import Image from 'next/image'; +import React from 'react'; +import { motion } from 'framer-motion'; + +export const BentoGrid = ({ + className, + children, +}: { + className?: string; + children?: React.ReactNode; +}) => { + return ( +
+ {children} +
+ ); +}; + +interface BentoGridItemProps { + className?: string; + title?: string | React.ReactNode; + subtitle?: string | React.ReactNode; + description?: string | React.ReactNode; + img?: string; + video?: string; + rowHeight?: string; +} + +export const BentoGridItem = React.forwardRef( + ({ className, title, subtitle, description, img, video, rowHeight }, ref) => { + return ( +
+
+ {video ? ( +
+
+ {title} + {subtitle} + {description} +
+
+ ); + } +); + +BentoGridItem.displayName = "BentoGridItem"; + +export const MotionBentoGridItem = motion(BentoGridItem); diff --git a/src/components/ui/dotted-glow-background.tsx b/src/components/ui/dotted-glow-background.tsx new file mode 100644 index 0000000..85b6960 --- /dev/null +++ b/src/components/ui/dotted-glow-background.tsx @@ -0,0 +1,308 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; + +type DottedGlowBackgroundProps = { + className?: string; + /** distance between dot centers in pixels */ + gap?: number; + /** base radius of each dot in CSS px */ + radius?: number; + /** dot color (will pulse by alpha) */ + color?: string; + /** optional dot color for dark mode */ + darkColor?: string; + /** shadow/glow color for bright dots */ + glowColor?: string; + /** optional glow color for dark mode */ + darkGlowColor?: string; + /** optional CSS variable name for light dot color (e.g. --color-zinc-900) */ + colorLightVar?: string; + /** optional CSS variable name for dark dot color (e.g. --color-zinc-100) */ + colorDarkVar?: string; + /** optional CSS variable name for light glow color */ + glowColorLightVar?: string; + /** optional CSS variable name for dark glow color */ + glowColorDarkVar?: string; + /** global opacity for the whole layer */ + opacity?: number; + /** background radial fade opacity (0 = transparent background) */ + backgroundOpacity?: number; + /** minimum per-dot speed in rad/s */ + speedMin?: number; + /** maximum per-dot speed in rad/s */ + speedMax?: number; + /** global speed multiplier for all dots */ + speedScale?: number; +}; + +/** + * Canvas-based dotted background that randomly glows and dims. + * - Uses a stable grid of dots. + * - Each dot gets its own phase + speed producing organic shimmering. + * - Handles high-DPI and resizes via ResizeObserver. + */ +export function DottedGlowBackground({ + className, + gap = 12, + radius = 2, + color = "rgba(0,0,0,0.7)", + darkColor, + glowColor = "rgba(0, 170, 255, 0.85)", + darkGlowColor, + colorLightVar, + colorDarkVar, + glowColorLightVar, + glowColorDarkVar, + opacity = 0.6, + backgroundOpacity = 0, + speedMin = 0.4, + speedMax = 1.3, + speedScale = 1, +}: DottedGlowBackgroundProps) { + const canvasRef = useRef(null); + const containerRef = useRef(null); + const [resolvedColor, setResolvedColor] = useState(color); + const [resolvedGlowColor, setResolvedGlowColor] = useState(glowColor); + + // Resolve CSS variable value from the container or root + const resolveCssVariable = ( + el: Element, + variableName?: string, + ): string | null => { + if (!variableName) return null; + const normalized = variableName.startsWith("--") + ? variableName + : `--${variableName}`; + const fromEl = getComputedStyle(el as Element) + .getPropertyValue(normalized) + .trim(); + if (fromEl) return fromEl; + const root = document.documentElement; + const fromRoot = getComputedStyle(root).getPropertyValue(normalized).trim(); + return fromRoot || null; + }; + + const detectDarkMode = (): boolean => { + const root = document.documentElement; + if (root.classList.contains("dark")) return true; + if (root.classList.contains("light")) return false; + return ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ); + }; + + // Keep resolved colors in sync with theme changes and prop updates + useEffect(() => { + const container = containerRef.current ?? document.documentElement; + + const compute = () => { + const isDark = detectDarkMode(); + + let nextColor: string = color; + let nextGlow: string = glowColor; + + if (isDark) { + const varDot = resolveCssVariable(container, colorDarkVar); + const varGlow = resolveCssVariable(container, glowColorDarkVar); + nextColor = varDot || darkColor || nextColor; + nextGlow = varGlow || darkGlowColor || nextGlow; + } else { + const varDot = resolveCssVariable(container, colorLightVar); + const varGlow = resolveCssVariable(container, glowColorLightVar); + nextColor = varDot || nextColor; + nextGlow = varGlow || nextGlow; + } + + setResolvedColor(nextColor); + setResolvedGlowColor(nextGlow); + }; + + compute(); + + const mql = window.matchMedia + ? window.matchMedia("(prefers-color-scheme: dark)") + : null; + const handleMql = () => compute(); + mql?.addEventListener?.("change", handleMql); + + const mo = new MutationObserver(() => compute()); + mo.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class", "style"], + }); + + return () => { + mql?.removeEventListener?.("change", handleMql); + mo.disconnect(); + }; + }, [ + color, + darkColor, + glowColor, + darkGlowColor, + colorLightVar, + colorDarkVar, + glowColorLightVar, + glowColorDarkVar, + ]); + + useEffect(() => { + const el = canvasRef.current; + const container = containerRef.current; + if (!el || !container) return; + + const ctx = el.getContext("2d"); + if (!ctx) return; + + let raf = 0; + let stopped = false; + + const dpr = Math.max(1, window.devicePixelRatio || 1); + + const resize = () => { + const { width, height } = container.getBoundingClientRect(); + el.width = Math.max(1, Math.floor(width * dpr)); + el.height = Math.max(1, Math.floor(height * dpr)); + el.style.width = `${Math.floor(width)}px`; + el.style.height = `${Math.floor(height)}px`; + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + }; + + const ro = new ResizeObserver(resize); + ro.observe(container); + resize(); + + // Precompute dot metadata for a medium-sized grid and regenerate on resize + let dots: { x: number; y: number; phase: number; speed: number }[] = []; + + const regenDots = () => { + dots = []; + const { width, height } = container.getBoundingClientRect(); + const cols = Math.ceil(width / gap) + 2; + const rows = Math.ceil(height / gap) + 2; + const min = Math.min(speedMin, speedMax); + const max = Math.max(speedMin, speedMax); + for (let i = -1; i < cols; i++) { + for (let j = -1; j < rows; j++) { + const x = i * gap + (j % 2 === 0 ? 0 : gap * 0.5); // offset every other row + const y = j * gap; + // Randomize phase and speed slightly per dot + const phase = Math.random() * Math.PI * 2; + const span = Math.max(max - min, 0); + const speed = min + Math.random() * span; // configurable rad/s + dots.push({ x, y, phase, speed }); + } + } + }; + + const regenThrottled = () => { + regenDots(); + }; + + regenDots(); + + let last = performance.now(); + + const draw = (now: number) => { + if (stopped) return; + const dt = (now - last) / 1000; // seconds + last = now; + const { width, height } = container.getBoundingClientRect(); + + ctx.clearRect(0, 0, el.width, el.height); + ctx.globalAlpha = opacity; + + // optional subtle background fade for depth (defaults to 0 = transparent) + if (backgroundOpacity > 0) { + const grad = ctx.createRadialGradient( + width * 0.5, + height * 0.4, + Math.min(width, height) * 0.1, + width * 0.5, + height * 0.5, + Math.max(width, height) * 0.7, + ); + grad.addColorStop(0, "rgba(0,0,0,0)"); + grad.addColorStop( + 1, + `rgba(0,0,0,${Math.min(Math.max(backgroundOpacity, 0), 1)})`, + ); + ctx.fillStyle = grad as unknown as CanvasGradient; + ctx.fillRect(0, 0, width, height); + } + + // animate dots + ctx.save(); + ctx.fillStyle = resolvedColor; + + const time = (now / 1000) * Math.max(speedScale, 0); + for (let i = 0; i < dots.length; i++) { + const d = dots[i]; + // Linear triangle wave 0..1..0 for linear glow/dim + const mod = (time * d.speed + d.phase) % 2; + const lin = mod < 1 ? mod : 2 - mod; // 0..1..0 + const a = 0.25 + 0.55 * lin; // 0.25..0.8 linearly + + // draw glow when bright + if (a > 0.6) { + const glow = (a - 0.6) / 0.4; // 0..1 + ctx.shadowColor = resolvedGlowColor; + ctx.shadowBlur = 6 * glow; + } else { + ctx.shadowColor = "transparent"; + ctx.shadowBlur = 0; + } + + ctx.globalAlpha = a * opacity; + ctx.beginPath(); + ctx.arc(d.x, d.y, radius, 0, Math.PI * 2); + ctx.fill(); + } + ctx.restore(); + + raf = requestAnimationFrame(draw); + }; + + const handleResize = () => { + resize(); + regenThrottled(); + }; + + window.addEventListener("resize", handleResize); + raf = requestAnimationFrame(draw); + + return () => { + stopped = true; + cancelAnimationFrame(raf); + window.removeEventListener("resize", handleResize); + ro.disconnect(); + }; + }, [ + gap, + radius, + resolvedColor, + resolvedGlowColor, + opacity, + backgroundOpacity, + speedMin, + speedMax, + speedScale, + ]); + + return ( +
+ +
+ ); +} + +export default DottedGlowBackground;