feat: redesign storage page with interactive components and dark theme

- Added interactive architecture section with tabbed navigation and smooth transitions
- Implemented horizontal scrolling capabilities carousel with image backgrounds
- Updated call-to-action section with bordered container layout and improved button styling
- Replaced core value section with animated self-healing storage features
- Applied consistent dark theme (#111111, #121212) with cyan accents across all storage components
This commit is contained in:
2025-11-07 22:28:03 +01:00
parent 0b6bcfedd0
commit 451c1f5c56
13 changed files with 718 additions and 120 deletions

View File

@@ -0,0 +1,223 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
import clsx from "clsx";
type Props = {
className?: string;
accent?: string;
gridStroke?: string;
stroke?: string;
};
const W = 760;
const H = 420;
export default function Residency({
className,
accent = "#00b8db",
gridStroke = "#e5e7eb",
stroke = "#111111",
}: Props) {
const prefers = useReducedMotion();
// Layout: central governance node + 3 regional nodes
const center = { x: 380, y: 200 };
const regions = [
{ x: 220, y: 120 },
{ x: 540, y: 120 },
{ x: 380, y: 300 },
];
// Path for data transfer (circular motion between regions)
const flowPath = `M ${regions[0].x} ${regions[0].y}
C 300 80, 460 80, ${regions[1].x} ${regions[1].y}
C 480 160, 420 260, ${regions[2].x} ${regions[2].y}
C 340 260, 280 160, ${regions[0].x} ${regions[0].y} Z`;
return (
<div
className={clsx("relative overflow-hidden", className)}
aria-hidden="true"
role="img"
style={{ background: "transparent" }}
>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
{/* ✅ Subtle light grid (same as Encrypted.tsx) */}
<defs>
<pattern id="grid-residency" width="28" height="28" patternUnits="userSpaceOnUse">
<path
d="M 28 0 L 0 0 0 28"
fill="none"
stroke={gridStroke}
strokeWidth="1"
opacity="0.4"
/>
</pattern>
<filter id="res-glow">
<feGaussianBlur stdDeviation="3" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<rect width={W} height={H} fill="url(#grid-residency)" />
{/* ✅ Base dotted jurisdiction boundary */}
<circle
cx={center.x}
cy={center.y}
r={140}
fill="none"
stroke={stroke}
strokeWidth={2}
strokeDasharray="6 6"
opacity="0.3"
/>
{/* ✅ Cyan policy ring expanding + fading */}
{!prefers && (
<motion.circle
cx={center.x}
cy={center.y}
r={140}
stroke={accent}
strokeWidth={2}
fill="none"
initial={{ scale: 0.9, opacity: 0 }}
animate={{
scale: [1, 1.15, 1.3],
opacity: [0.15, 0.4, 0],
}}
transition={{
duration: 2.8,
repeat: Infinity,
ease: [0.22, 1, 0.36, 1],
}}
filter="url(#res-glow)"
/>
)}
{/* ✅ Cyan glow radius (policy control zone) */}
{!prefers && (
<motion.circle
cx={center.x}
cy={center.y}
r={60}
fill={accent}
opacity={0.08}
initial={{ scale: 1, opacity: 0.05 }}
animate={{
opacity: [0.05, 0.15, 0.05],
scale: [1, 1.05, 1],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: [0.22, 1, 0.36, 1],
}}
/>
)}
{/* ✅ Central governance node */}
<circle
cx={center.x}
cy={center.y}
r={28}
fill="#fff"
stroke={stroke}
strokeWidth={2}
/>
<circle
cx={center.x}
cy={center.y}
r={12}
fill={accent}
stroke={stroke}
strokeWidth={2}
filter="url(#res-glow)"
/>
{/* ✅ Regional nodes */}
{regions.map((r, i) => (
<g key={i}>
<circle
cx={r.x}
cy={r.y}
r={22}
fill="#fff"
stroke={stroke}
strokeWidth={2}
/>
<circle
cx={r.x}
cy={r.y}
r={10}
fill="none"
stroke={stroke}
strokeWidth={1.5}
opacity="0.6"
/>
</g>
))}
{/* ✅ Data transfer flow (light dotted path) */}
<motion.path
d={flowPath}
fill="none"
stroke={stroke}
strokeWidth={1.5}
strokeDasharray="4 4"
opacity="0.3"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1.4 }}
/>
{/* ✅ Cyan packet traveling within jurisdiction */}
{!prefers && (
<motion.circle
r={6}
fill={accent}
style={{ offsetPath: `path('${flowPath}')` }}
initial={{ offsetDistance: "0%", opacity: 0 }}
animate={{
offsetDistance: ["0%", "100%"],
opacity: [0.3, 1, 0.3],
}}
transition={{
duration: 4,
repeat: Infinity,
ease: "linear",
}}
filter="url(#res-glow)"
/>
)}
{/* ✅ Governance shield icon */}
<motion.path
d={`M ${center.x} ${center.y - 70}
L ${center.x + 20} ${center.y - 60}
L ${center.x + 16} ${center.y - 35}
L ${center.x} ${center.y - 25}
L ${center.x - 16} ${center.y - 35}
L ${center.x - 20} ${center.y - 60}
Z`}
fill="none"
stroke={accent}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
filter="url(#res-glow)"
/>
</svg>
</div>
);
}

View File

@@ -0,0 +1,214 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
import clsx from "clsx";
type Props = {
className?: string;
accent?: string;
gridStroke?: string;
stroke?: string;
};
const W = 760;
const H = 420;
export default function SelfHealing({
className,
accent = "#00b8db",
gridStroke = "#e5e7eb",
stroke = "#111111",
}: Props) {
const prefers = useReducedMotion();
// diamond node layout
const nodes = [
{ x: 380, y: 130 }, // top
{ x: 240, y: 240 }, // left
{ x: 520, y: 240 }, // right
{ x: 380, y: 320 }, // bottom
];
// connection paths
const links = [
[0, 1],
[0, 2],
[1, 3],
[2, 3],
[1, 2],
];
// helper for path drawing
const drawLine = (i: number, j: number) => {
const a = nodes[i];
const b = nodes[j];
return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
};
return (
<div
className={clsx("relative overflow-hidden", className)}
aria-hidden="true"
role="img"
style={{ background: "transparent" }}
>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
{/* ✅ Subtle grid (same as Encrypted.tsx) */}
<defs>
<pattern id="grid-heal" width="28" height="28" patternUnits="userSpaceOnUse">
<path
d="M 28 0 L 0 0 0 28"
fill="none"
stroke={gridStroke}
strokeWidth="1"
opacity="0.4"
/>
</pattern>
<filter id="glow">
<feGaussianBlur stdDeviation="3" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<rect width={W} height={H} fill="url(#grid-heal)" />
{/* ✅ Static network links */}
{links.map(([i, j], idx) => (
<motion.path
key={idx}
d={drawLine(i, j)}
stroke={stroke}
strokeWidth={2}
strokeLinecap="round"
fill="none"
opacity="0.25"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.8, delay: idx * 0.1 }}
/>
))}
{/* ✅ Circulating health-check pulse */}
{!prefers &&
[0, 1, 2, 3].map((i) => {
const next = (i + 1) % 4;
const path = drawLine(i, next);
return (
<motion.circle
key={`pulse-${i}`}
r={5}
fill={accent}
style={{ offsetPath: `path('${path}')` }}
initial={{ offsetDistance: "0%", opacity: 0 }}
animate={{
offsetDistance: ["0%", "100%"],
opacity: [0.1, 0.9, 0.1],
}}
transition={{
duration: 2.8,
delay: i * 0.4,
repeat: Infinity,
ease: "linear",
}}
filter="url(#glow)"
/>
);
})}
{/* ✅ Nodes */}
{nodes.map((n, i) => (
<g key={`node-${i}`}>
<circle
cx={n.x}
cy={n.y}
r={26}
fill="none"
stroke={stroke}
strokeWidth={2}
opacity="0.8"
/>
<circle
cx={n.x}
cy={n.y}
r={12}
fill="#fff"
stroke={stroke}
strokeWidth={2}
/>
</g>
))}
{/* ✅ Simulated failure (bottom node flickers out, then heals) */}
{!prefers && (
<motion.circle
cx={nodes[3].x}
cy={nodes[3].y}
r={12}
fill="#fff"
stroke={stroke}
strokeWidth={2}
animate={{
opacity: [1, 0.2, 1, 1],
scale: [1, 0.9, 1, 1],
}}
transition={{
duration: 4,
repeat: Infinity,
ease: "easeInOut",
}}
/>
)}
{/* ✅ Healing pulse wave from neighbors → bottom node */}
{!prefers &&
[nodes[1], nodes[2]].map((n, i) => {
const path = `M ${n.x} ${n.y} L ${nodes[3].x} ${nodes[3].y}`;
return (
<motion.circle
key={`heal-${i}`}
r={5}
fill={accent}
style={{ offsetPath: `path('${path}')` }}
initial={{ offsetDistance: "0%", opacity: 0 }}
animate={{
offsetDistance: ["0%", "100%"],
opacity: [0, 1, 0],
}}
transition={{
delay: 1.5 + i * 0.2,
duration: 1.8,
repeat: Infinity,
repeatDelay: 2.2,
ease: "linear",
}}
filter="url(#glow)"
/>
);
})}
{/* ✅ Integrity halo on healed node */}
{!prefers && (
<motion.circle
cx={nodes[3].x}
cy={nodes[3].y}
r={32}
fill="none"
stroke={accent}
strokeWidth={2}
initial={{ opacity: 0 }}
animate={{ opacity: [0.15, 0.4, 0.15], scale: [1, 1.12, 1] }}
transition={{
duration: 2.8,
repeat: Infinity,
ease: [0.22, 1, 0.36, 1],
}}
filter="url(#glow)"
/>
)}
</svg>
</div>
);
}