chore: archive unused images and add storage feature components
- Moved 35 legacy image assets to archive directory for cleanup - Created StorageCoreValue component showcasing Digital Me blueprint with logo grid - Added Encrypted animation component visualizing secure storage with data movement
This commit is contained in:
193
src/pages/storage/animation/Encrypted.tsx
Normal file
193
src/pages/storage/animation/Encrypted.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
"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 Encrypted({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
gridStroke = "#e5e7eb",
|
||||
stroke = "#111111",
|
||||
}: Props) {
|
||||
const prefers = useReducedMotion();
|
||||
|
||||
// positions of storage vaults (cylindrical stack illusion)
|
||||
const vaults = [
|
||||
{ x: 200, y: 220 },
|
||||
{ x: 380, y: 180 },
|
||||
{ x: 560, y: 220 },
|
||||
];
|
||||
|
||||
// path representing encrypted data moving across storage nodes
|
||||
const dataPath = `M ${vaults[0].x + 60} ${vaults[0].y}
|
||||
C 280 160, 480 160, ${vaults[2].x} ${vaults[2].y}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="Mycelium Storage: encrypted and verifiable at rest and in motion"
|
||||
style={{ background: "transparent" }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
{/* === Background Grid (light mode) === */}
|
||||
<defs>
|
||||
<pattern id="grid-light" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke={gridStroke} strokeWidth="1" opacity="0.6" />
|
||||
</pattern>
|
||||
|
||||
<filter id="cyan-glow">
|
||||
<feGaussianBlur stdDeviation="3" result="blur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect width={W} height={H} fill="url(#grid-light)" />
|
||||
|
||||
{/* === Storage Vaults (cylinders) === */}
|
||||
{vaults.map((v, i) => (
|
||||
<g key={i} transform={`translate(${v.x}, ${v.y})`}>
|
||||
{/* Top ellipse */}
|
||||
<ellipse cx="0" cy="-30" rx="50" ry="14" fill="none" stroke={stroke} strokeWidth="2" />
|
||||
{/* Body */}
|
||||
<rect
|
||||
x="-50"
|
||||
y="-30"
|
||||
width="100"
|
||||
height="60"
|
||||
rx="12"
|
||||
fill="none"
|
||||
stroke={stroke}
|
||||
strokeWidth="2"
|
||||
/>
|
||||
{/* Bottom ellipse (front face) */}
|
||||
<ellipse cx="0" cy="30" rx="50" ry="14" fill="none" stroke={stroke} strokeWidth="2" />
|
||||
{/* subtle cyan glow ring to mark encryption */}
|
||||
<motion.ellipse
|
||||
cx="0"
|
||||
cy="0"
|
||||
rx="55"
|
||||
ry="18"
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth="2"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: [0.15, 0.4, 0.15] }}
|
||||
transition={{
|
||||
delay: i * 0.3,
|
||||
duration: 2.2,
|
||||
repeat: Infinity,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
|
||||
{/* === Encrypted data movement path === */}
|
||||
<motion.path
|
||||
d={dataPath}
|
||||
fill="none"
|
||||
stroke={stroke}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeDasharray="6"
|
||||
opacity="0.5"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
|
||||
{/* === Encrypted packets traveling === */}
|
||||
{!prefers && (
|
||||
<motion.circle
|
||||
r={6}
|
||||
fill={accent}
|
||||
style={{ offsetPath: `path('${dataPath}')` }}
|
||||
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0.3, 1, 0.3],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2.8,
|
||||
repeat: Infinity,
|
||||
repeatType: "loop",
|
||||
ease: "linear",
|
||||
}}
|
||||
filter="url(#cyan-glow)"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* === Verification shield (top-right) === */}
|
||||
<motion.path
|
||||
d={`M 620 120
|
||||
L 645 135
|
||||
L 640 165
|
||||
L 620 175
|
||||
L 600 165
|
||||
L 595 135
|
||||
Z`}
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={2.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: 1, delay: 0.6, ease: [0.22, 1, 0.36, 1] }}
|
||||
filter="url(#cyan-glow)"
|
||||
/>
|
||||
|
||||
{/* === Verification checkmark === */}
|
||||
<motion.path
|
||||
d="M 606 150 L 617 162 L 635 140"
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 0.8, delay: 1.2 }}
|
||||
filter="url(#cyan-glow)"
|
||||
/>
|
||||
|
||||
{/* === Cyan verification pulse === */}
|
||||
{!prefers && (
|
||||
<motion.circle
|
||||
cx="620"
|
||||
cy="150"
|
||||
r="24"
|
||||
stroke={accent}
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
initial={{ scale: 1, opacity: 0 }}
|
||||
animate={{
|
||||
scale: [1, 1.25, 1.4],
|
||||
opacity: [0.25, 0.6, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.8,
|
||||
repeat: Infinity,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user