From 813675c26c829f24aceff07eec68f65237ee4c2b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 13:02:36 +0000 Subject: [PATCH 1/9] feat(web): animated hero stats + 3D tilt on project cards Hero: stats now count up from 0 when they enter the viewport (using motion animate), and six product glyphs float gently in the background as a constellation backdrop (respects prefers-reduced-motion). ProjectGrid: each card gains a perspective tilt that follows the cursor (useSpring-smoothed), snapping back on mouse-leave. The hover y-lift moves to a motion whileHover so transforms compose cleanly. https://claude.ai/code/session_01D2raHvxTxUpokiB7nLZnP9 --- apps/web/app/_components/Hero.tsx | 84 ++++++++++++++++++++---- apps/web/app/_components/ProjectGrid.tsx | 46 +++++++++++-- 2 files changed, 112 insertions(+), 18 deletions(-) diff --git a/apps/web/app/_components/Hero.tsx b/apps/web/app/_components/Hero.tsx index f648312..ce16796 100644 --- a/apps/web/app/_components/Hero.tsx +++ b/apps/web/app/_components/Hero.tsx @@ -1,18 +1,83 @@ "use client"; -import { motion } from "motion/react"; +import { motion, animate, useInView, useReducedMotion } from "motion/react"; +import { useState, useEffect, useRef } from "react"; import { ease } from "@konjoai/ui"; -const STATS = [ - { value: "14", label: "components", color: "text-konjo-accent" }, - { value: "9", label: "products", color: "text-konjo-violet" }, - { value: "79", label: "tests", color: "text-konjo-good" }, - { value: "v0.2", label: "current", color: "text-konjo-warm" }, +type Stat = { display: string; label: string; color: string; countTo?: number }; + +const STATS: Stat[] = [ + { display: "14", countTo: 14, label: "components", color: "text-konjo-accent" }, + { display: "9", countTo: 9, label: "products", color: "text-konjo-violet" }, + { display: "79", countTo: 79, label: "tests", color: "text-konjo-good" }, + { display: "v0.2", label: "current", color: "text-konjo-warm" }, +]; + +/** Positions, durations, and delays are fixed so SSR and client agree. */ +const GLYPH_CONFIG = [ + { glyph: "◐", x: "6%", top: "22%", dur: 3.8, delay: 0.0 }, + { glyph: "◇", x: "89%", top: "14%", dur: 4.5, delay: 0.5 }, + { glyph: "✸", x: "77%", top: "68%", dur: 4.2, delay: 0.9 }, + { glyph: "▲", x: "12%", top: "74%", dur: 3.6, delay: 1.4 }, + { glyph: "⬡", x: "48%", top: "88%", dur: 5.0, delay: 0.3 }, + { glyph: "◈", x: "93%", top: "44%", dur: 4.0, delay: 1.0 }, ] as const; +/** Softly breathing product glyphs — constellation backdrop for the hero. */ +function FloatingGlyphs() { + const reduce = useReducedMotion(); + if (reduce) return null; + + return ( +
+ {GLYPH_CONFIG.map(({ glyph, x, top, dur, delay }) => ( + + {glyph} + + ))} +
+ ); +} + +/** Counts from 0 to `stat.countTo` once the element enters the viewport. */ +function AnimatedStat({ stat }: { stat: Stat }) { + const ref = useRef(null); + const inView = useInView(ref, { once: true }); + const [display, setDisplay] = useState(stat.countTo !== undefined ? "0" : stat.display); + + useEffect(() => { + if (!inView || stat.countTo === undefined) return; + const controls = animate(0, stat.countTo, { + duration: 1.2, + ease: "easeOut", + onUpdate: (v) => setDisplay(String(Math.round(v))), + }); + return () => controls.stop(); + }, [inView, stat.countTo]); + + return ( +
+ + {display} + + {stat.label} +
+ ); +} + +/** Homepage hero — animated headline, count-up stats, floating glyph constellation. */ export function Hero() { return (
+ + - {STATS.map((s, i) => ( -
- {s.value} - {s.label} -
+ {STATS.map((s) => ( + ))}
diff --git a/apps/web/app/_components/ProjectGrid.tsx b/apps/web/app/_components/ProjectGrid.tsx index 0c15061..4b8db3b 100644 --- a/apps/web/app/_components/ProjectGrid.tsx +++ b/apps/web/app/_components/ProjectGrid.tsx @@ -1,9 +1,11 @@ "use client"; -import { motion } from "motion/react"; +import { useRef } from "react"; +import { motion, useMotionValue, useSpring, useReducedMotion } from "motion/react"; import { ease, StatusBadge, severity as sevColor } from "@konjoai/ui"; import { PRODUCTS, type Product } from "@/lib/products"; +/** Portfolio grid — nine animated product cards with 3-D tilt on hover. */ export function ProjectGrid() { return (
@@ -30,24 +32,51 @@ export function ProjectGrid() { ); } +/** Single product card with entrance animation and 3-D perspective tilt on hover. */ function ProjectCard({ project, index }: { project: Product; index: number }) { + const reduce = useReducedMotion(); + const cardRef = useRef(null); + const rawX = useMotionValue(0); + const rawY = useMotionValue(0); + const rotateX = useSpring(rawX, { stiffness: 300, damping: 25 }); + const rotateY = useSpring(rawY, { stiffness: 300, damping: 25 }); + const metricColor = sevColor[project.metric.severity]; - const metricDisplay = - Number.isInteger(project.metric.value) - ? String(project.metric.value) - : project.metric.value.toFixed(1); + const metricDisplay = Number.isInteger(project.metric.value) + ? String(project.metric.value) + : project.metric.value.toFixed(1); + + function handleMouseMove(e: React.MouseEvent) { + if (reduce) return; + const rect = cardRef.current?.getBoundingClientRect(); + if (!rect) return; + const x = (e.clientX - rect.left) / rect.width - 0.5; + const y = (e.clientY - rect.top) / rect.height - 0.5; + rawY.set(x * 8); + rawX.set(-y * 6); + } + + function handleMouseLeave() { + rawX.set(0); + rawY.set(0); + } return ( {/* Top shimmer on hover */}
{/* Headline metric */} -
+
Date: Sun, 14 Jun 2026 13:05:22 +0000 Subject: [PATCH 2/9] feat(web): scroll progress bar + cycling ProductHero in ShellSection Adds a 2px brand-gradient progress bar fixed to the top of every page that fills left-to-right as the user scrolls (useScroll + scaleX). ShellSection ProductHero now cycles through all nine products on a 3.2s interval with AnimatePresence cross-fade. Manual dot-indicator lets visitors jump to any product. Respects prefers-reduced-motion. https://claude.ai/code/session_01D2raHvxTxUpokiB7nLZnP9 --- .../web/app/_components/ScrollProgressBar.tsx | 23 +++++ .../app/_components/showcase/ShellSection.tsx | 93 ++++++++++++++----- apps/web/app/layout.tsx | 2 + 3 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 apps/web/app/_components/ScrollProgressBar.tsx diff --git a/apps/web/app/_components/ScrollProgressBar.tsx b/apps/web/app/_components/ScrollProgressBar.tsx new file mode 100644 index 0000000..403ee4b --- /dev/null +++ b/apps/web/app/_components/ScrollProgressBar.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { useScroll, motion } from "motion/react"; + +/** + * Thin brand-gradient line fixed to the top of the viewport that fills + * left-to-right as the user scrolls down the page. + */ +export function ScrollProgressBar() { + const { scrollYProgress } = useScroll(); + + return ( + + ); +} diff --git a/apps/web/app/_components/showcase/ShellSection.tsx b/apps/web/app/_components/showcase/ShellSection.tsx index a568044..6fb36ad 100644 --- a/apps/web/app/_components/showcase/ShellSection.tsx +++ b/apps/web/app/_components/showcase/ShellSection.tsx @@ -1,7 +1,10 @@ "use client"; +import { useState, useEffect } from "react"; +import { motion, AnimatePresence, useReducedMotion } from "motion/react"; import { StatusBadge, FeatureCard, ProductHero } from "@konjoai/ui"; import type { StatusLevel } from "@konjoai/ui"; +import { PRODUCTS } from "@/lib/products"; const STATUS_LEVELS: StatusLevel[] = [ "operational", @@ -35,11 +38,23 @@ const FEATURE_CARDS = [ }, ] as const; +const CYCLE_MS = 3200; + /** * Showcase for shell & layout primitives: StatusBadge (all levels), - * FeatureCard grid, and ProductHero preview. + * FeatureCard grid, and a ProductHero preview that cycles all nine products. */ export function ShellSection() { + const reduce = useReducedMotion(); + const [idx, setIdx] = useState(0); + + useEffect(() => { + const id = setInterval(() => setIdx((i) => (i + 1) % PRODUCTS.length), CYCLE_MS); + return () => clearInterval(id); + }, []); + + const product = PRODUCTS[idx]; + return (
{/* StatusBadge */} @@ -74,33 +89,61 @@ export function ShellSection() {
- {/* ProductHero preview */} + {/* ProductHero cycling preview */}
-

- ProductHero · product page header -

+
+

+ ProductHero · cycling all 9 products +

+
+ {PRODUCTS.map((_, i) => ( +
+
+
- {/* Aurora bg for the preview */}
- } - actions={ - - github.com/konjoai/kyro ↗ - - } - className="pt-10 pb-8 sm:pt-12" - /> + + + } + actions={ + + github.com/konjoai/{product.slug} ↗ + + } + className="pt-10 pb-8 sm:pt-12" + /> + +
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index d1ed6e4..59407fc 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { SiteNav } from "./_components/SiteNav"; +import { ScrollProgressBar } from "./_components/ScrollProgressBar"; import "./globals.css"; export const metadata: Metadata = { @@ -25,6 +26,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( + {children} From c6dc67c861636783a6befc0df7b0f017c4fc1e6e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 13:08:40 +0000 Subject: [PATCH 3/9] feat(web): AnimatedSection on product pages + live ComplianceSection AnimatedSection: a thin "use client" motion.section wrapper that whileInView-animates any server-rendered content into the viewport. Used on product page about, features grid, and CTA sections so every scroll stop on /products/[slug] has a smooth entrance. ComplianceSection: RiskRing now cycles through three audit-scan scenarios every 6 s (EU AI Act / NIST / OWASP). Marked live: true in DesignPreview so all five showcase sections now show the live dot. https://claude.ai/code/session_01D2raHvxTxUpokiB7nLZnP9 --- apps/web/app/_components/AnimatedSection.tsx | 33 +++++++++++++ apps/web/app/_components/DesignPreview.tsx | 4 +- .../showcase/ComplianceSection.tsx | 46 +++++++++++++++---- apps/web/app/products/[slug]/page.tsx | 13 +++--- 4 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 apps/web/app/_components/AnimatedSection.tsx diff --git a/apps/web/app/_components/AnimatedSection.tsx b/apps/web/app/_components/AnimatedSection.tsx new file mode 100644 index 0000000..714048d --- /dev/null +++ b/apps/web/app/_components/AnimatedSection.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { motion } from "motion/react"; +import { ease } from "@konjoai/ui"; + +interface AnimatedSectionProps { + children: React.ReactNode; + className?: string; + /** Extra entrance delay in seconds. */ + delay?: number; +} + +/** + * Transparent wrapper that animates children in as they enter the viewport. + * Renders as a `
` element; safe to use in server component pages. + */ +export function AnimatedSection({ + children, + className, + delay = 0, +}: AnimatedSectionProps) { + return ( + + {children} + + ); +} diff --git a/apps/web/app/_components/DesignPreview.tsx b/apps/web/app/_components/DesignPreview.tsx index c01ab78..3fb4e2f 100644 --- a/apps/web/app/_components/DesignPreview.tsx +++ b/apps/web/app/_components/DesignPreview.tsx @@ -37,9 +37,9 @@ const BLOCKS: Block[] = [ { id: "compliance", title: "Compliance Monitor", - description: "EU AI Act article grid, concentric risk arcs, score vs. threshold.", + description: "EU AI Act article grid, cycling RiskRing re-assessments, score vs. threshold.", tag: "squash", - live: false, + live: true, Section: ComplianceSection, }, { diff --git a/apps/web/app/_components/showcase/ComplianceSection.tsx b/apps/web/app/_components/showcase/ComplianceSection.tsx index 08fbf64..2fd48bc 100644 --- a/apps/web/app/_components/showcase/ComplianceSection.tsx +++ b/apps/web/app/_components/showcase/ComplianceSection.tsx @@ -1,7 +1,8 @@ "use client"; +import { useState, useEffect } from "react"; import { RiskRing, StatusMatrix, ComparisonBar } from "@konjoai/ui"; -import type { StatusMatrixRow, ComparisonBarItem } from "@konjoai/ui"; +import type { StatusMatrixRow, ComparisonBarItem, RiskRingItem } from "@konjoai/ui"; const MATRIX_ROWS: StatusMatrixRow[] = [ { @@ -45,27 +46,52 @@ const BENCHMARKS: ComparisonBarItem[] = [ { label: "ISO 42001", value: 67, baseline: 70 }, ]; -/** Compliance Monitor: RiskRing, StatusMatrix, ComparisonBar — squash palette. */ +/** Rotating audit scenarios — simulates periodic re-assessment scans. */ +const RING_SCENARIOS: RiskRingItem[][] = [ + [ + { label: "EU AI Act", value: 0.88, severity: "ok" }, + { label: "NIST AI RMF", value: 0.74, severity: "warn" }, + { label: "OWASP LLM Top-10", value: 0.91, severity: "info" }, + ], + [ + { label: "EU AI Act", value: 0.92, severity: "ok" }, + { label: "NIST AI RMF", value: 0.76, severity: "warn" }, + { label: "OWASP LLM Top-10", value: 0.89, severity: "info" }, + ], + [ + { label: "EU AI Act", value: 0.85, severity: "ok" }, + { label: "NIST AI RMF", value: 0.79, severity: "warn" }, + { label: "OWASP LLM Top-10", value: 0.94, severity: "ok" }, + ], +]; + +/** Compliance Monitor: live-cycling RiskRing, StatusMatrix, ComparisonBar — squash palette. */ export function ComplianceSection() { + const [scenario, setScenario] = useState(0); + + useEffect(() => { + const id = setInterval( + () => setScenario((s) => (s + 1) % RING_SCENARIOS.length), + 6000, + ); + return () => clearInterval(id); + }, []); + return (

- RiskRing · concentric arcs + RiskRing · periodic re-assessment

-
+

StatusMatrix · article compliance grid

diff --git a/apps/web/app/products/[slug]/page.tsx b/apps/web/app/products/[slug]/page.tsx index 61ee563..778c950 100644 --- a/apps/web/app/products/[slug]/page.tsx +++ b/apps/web/app/products/[slug]/page.tsx @@ -3,6 +3,7 @@ import { notFound } from "next/navigation"; import { FeatureCard, ProductHero, StatusBadge } from "@konjoai/ui"; import { Footer } from "@/app/_components/Footer"; import { Breadcrumbs } from "@/app/_components/Breadcrumbs"; +import { AnimatedSection } from "@/app/_components/AnimatedSection"; import { ProductDashboard } from "@/app/products/_components/ProductDashboard"; import { PRODUCTS, PRODUCT_BY_SLUG } from "@/lib/products"; @@ -82,13 +83,13 @@ export default async function ProductPage({ } /> -
+

{product.about}

-
+ -
+

What it does

@@ -102,11 +103,11 @@ export default async function ProductPage({ /> ))}
-
+ -
+

@@ -125,7 +126,7 @@ export default async function ProductPage({ github.com/konjoai/{product.slug} ↗

-
+