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:
223
src/pages/storage/animation/Residency.tsx
Normal file
223
src/pages/storage/animation/Residency.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
214
src/pages/storage/animation/SelfHealing.tsx
Normal file
214
src/pages/storage/animation/SelfHealing.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user