feat(footer): implement responsive 3D isometric SVG footer scene with theme tokens
This commit is contained in:
417
src/components/IsometricFooter/IsometricScene.tsx
Normal file
417
src/components/IsometricFooter/IsometricScene.tsx
Normal file
@@ -0,0 +1,417 @@
|
||||
import React from 'react';
|
||||
import type { IsometricSceneProps } from './IsoTypes';
|
||||
import {
|
||||
MountainCluster,
|
||||
PineTree,
|
||||
IsoUprightPencil,
|
||||
IsoMonitorDisplay,
|
||||
IsoPCTower,
|
||||
IsoSignpost,
|
||||
PhoneIcon,
|
||||
LinkedInIcon,
|
||||
GitHubIcon,
|
||||
TelegramIcon,
|
||||
} from './IsoElements';
|
||||
|
||||
/**
|
||||
* Faint Isometric Background Grid
|
||||
* Fine architectural diamond grid lines covering the ground (slope 0.5, 2:1 isometric)
|
||||
*/
|
||||
function IsometricBackgroundGrid() {
|
||||
const lines = [];
|
||||
const spacing = 40;
|
||||
const yStart = 150;
|
||||
const yEnd = 500;
|
||||
|
||||
// Axis 1: / lines (slope 0.5, dx = 2 * dy)
|
||||
for (let c = -600; c <= 1600; c += spacing * 2) {
|
||||
const x1 = c;
|
||||
const y1 = yStart;
|
||||
const x2 = c + (yEnd - yStart) * 2;
|
||||
const y2 = yEnd;
|
||||
lines.push(
|
||||
<line
|
||||
key={`a1-${c}`}
|
||||
x1={x1}
|
||||
y1={y1}
|
||||
x2={x2}
|
||||
y2={y2}
|
||||
stroke="var(--border-default, #D9E2EF)"
|
||||
strokeWidth={1}
|
||||
opacity={0.65}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Axis 2: \ lines (slope -0.5, dx = -2 * dy)
|
||||
for (let c = -400; c <= 1800; c += spacing * 2) {
|
||||
const x1 = c;
|
||||
const y1 = yStart;
|
||||
const x2 = c - (yEnd - yStart) * 2;
|
||||
const y2 = yEnd;
|
||||
lines.push(
|
||||
<line
|
||||
key={`a2-${c}`}
|
||||
x1={x1}
|
||||
y1={y1}
|
||||
x2={x2}
|
||||
y2={y2}
|
||||
stroke="var(--border-default, #D9E2EF)"
|
||||
strokeWidth={1}
|
||||
opacity={0.65}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <g className="iso-ground-grid">{lines}</g>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Architectural Stepped Terraces:
|
||||
* 5 ground steps on the floor along the 2:1 isometric grid,
|
||||
* plus the full-sized large center workstation platform with bold 20px blue drop apron
|
||||
* and the elevated stepped workstation tier.
|
||||
*/
|
||||
function SteppedTerraces() {
|
||||
return (
|
||||
<g className="stepped-terraces">
|
||||
<defs>
|
||||
{/* Floor Step Fade Mask: solid at front (y=400..360), smooth fade backwards (y=210) */}
|
||||
<linearGradient
|
||||
id="floorFadeMaskGrad"
|
||||
x1="0"
|
||||
y1="410"
|
||||
x2="0"
|
||||
y2="210"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0%" stopColor="var(--bg-page, #F8FAFF)" stopOpacity="1" />
|
||||
<stop offset="20%" stopColor="var(--bg-page, #F8FAFF)" stopOpacity="0.95" />
|
||||
<stop offset="45%" stopColor="var(--bg-page, #F8FAFF)" stopOpacity="0.65" />
|
||||
<stop offset="70%" stopColor="var(--bg-page, #F8FAFF)" stopOpacity="0.25" />
|
||||
<stop offset="90%" stopColor="var(--bg-page, #F8FAFF)" stopOpacity="0.05" />
|
||||
<stop offset="100%" stopColor="var(--bg-page, #F8FAFF)" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
<mask
|
||||
id="floorFadeMask"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="-100"
|
||||
y="180"
|
||||
width="1400"
|
||||
height="260"
|
||||
>
|
||||
<rect x="-100" y="180" width="1400" height="260" fill="url(#floorFadeMaskGrad)" />
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
{/* 1. 3D FRONT VERTICAL DROP APRON (5 STEPS: Steps 2 to 6, x=200 to 1000) */}
|
||||
<g id="terrace-front-drop-apron">
|
||||
{/* Step 2 (Phone) */}
|
||||
<polygon points="200,360 280,400 280,416 200,376" fill="var(--bg-subtle, #E6F0FE)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
<polygon points="360,360 280,400 280,416 360,376" fill="var(--color-border-light, #D9E2EF)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
|
||||
{/* Step 3 (LinkedIn) */}
|
||||
<polygon points="360,360 440,400 440,416 360,376" fill="var(--bg-subtle, #E6F0FE)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
<polygon points="520,360 440,400 440,416 520,376" fill="var(--color-border-light, #D9E2EF)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
|
||||
{/* Step 4 (Center Platform) */}
|
||||
<polygon points="520,360 600,400 600,416 520,376" fill="var(--bg-subtle, #E6F0FE)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
<polygon points="680,360 600,400 600,416 680,376" fill="var(--color-border-light, #D9E2EF)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
|
||||
{/* Step 5 (GitHub) */}
|
||||
<polygon points="680,360 760,400 760,416 680,376" fill="var(--bg-subtle, #E6F0FE)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
<polygon points="840,360 760,400 760,416 840,376" fill="var(--color-border-light, #D9E2EF)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
|
||||
{/* Step 6 (Telegram) */}
|
||||
<polygon points="840,360 920,400 920,416 840,376" fill="var(--bg-subtle, #E6F0FE)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
<polygon points="1000,360 920,400 920,416 1000,376" fill="var(--color-border-light, #D9E2EF)" stroke="var(--border-default, #D9E2EF)" strokeWidth={1.2} />
|
||||
|
||||
{/* Vertical corner edges at peaks */}
|
||||
<line x1={280} y1={400} x2={280} y2={416} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={440} y1={400} x2={440} y2={416} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={600} y1={400} x2={600} y2={416} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={760} y1={400} x2={760} y2={416} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={920} y1={400} x2={920} y2={416} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
|
||||
{/* Vertical corner edges at valleys and outer ends */}
|
||||
<line x1={200} y1={360} x2={200} y2={376} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={360} y1={360} x2={360} y2={376} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={520} y1={360} x2={520} y2={376} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={680} y1={360} x2={680} y2={376} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={840} y1={360} x2={840} y2={376} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
<line x1={1000} y1={360} x2={1000} y2={376} stroke="var(--border-strong, #A9BEDA)" strokeWidth={1.2} />
|
||||
</g>
|
||||
|
||||
{/* 2. FADING GROUND FLOOR TOP SURFACE & OUTLINE BORDERS */}
|
||||
<g className="terrace-fading-top-surface" mask="url(#floorFadeMask)">
|
||||
<polygon
|
||||
points="200,360 280,400 360,360 440,400 520,360 600,400 680,360 760,400 840,360 920,400 1000,360 1000,200 200,200"
|
||||
fill="var(--bg-page, #F8FAFF)"
|
||||
/>
|
||||
|
||||
{/* Fading Diamond Tile Outlines */}
|
||||
<g stroke="var(--border-default, #D9E2EF)" strokeWidth={1.05} opacity={0.65}>
|
||||
<line x1={200} y1={360} x2={40} y2={280} />
|
||||
<line x1={360} y1={360} x2={200} y2={280} />
|
||||
<line x1={520} y1={360} x2={360} y2={280} />
|
||||
<line x1={680} y1={360} x2={520} y2={280} />
|
||||
<line x1={840} y1={360} x2={680} y2={280} />
|
||||
<line x1={1000} y1={360} x2={840} y2={280} />
|
||||
|
||||
<line x1={280} y1={400} x2={440} y2={320} />
|
||||
<line x1={440} y1={400} x2={600} y2={320} />
|
||||
<line x1={600} y1={400} x2={760} y2={320} />
|
||||
<line x1={760} y1={400} x2={920} y2={320} />
|
||||
<line x1={920} y1={400} x2={1080} y2={320} />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
{/* 3. CENTER ELEVATED WORKSTATION PLATFORM (TIER 1 & TIER 2) */}
|
||||
<g id="workstation-elevated-platform">
|
||||
{/* Tier 1 3D Base Diamond Platform with Sleek 15px Blue Drop Edge (248px wide) */}
|
||||
{/* Left Front Drop Face (Slope +0.5 from 476,290 to 600,352, Drop Height = 15px) */}
|
||||
<polygon
|
||||
points="476,290 600,352 600,367 476,305"
|
||||
fill="var(--hero-illus-cube-front, #2066AB)"
|
||||
stroke="var(--hero-illus-cube-side, #0E4073)"
|
||||
strokeWidth={0.9}
|
||||
/>
|
||||
{/* Right Front Drop Face (Slope -0.5 from 600,352 to 724,290, Drop Height = 15px) */}
|
||||
<polygon
|
||||
points="600,352 724,290 724,305 600,367"
|
||||
fill="var(--hero-illus-cube-side, #0E4073)"
|
||||
stroke="var(--hero-illus-cube-shadow, #0C234F)"
|
||||
strokeWidth={0.9}
|
||||
/>
|
||||
{/* Center Vertical Ridge Line */}
|
||||
<line x1={600} y1={352} x2={600} y2={367} stroke="var(--hero-illus-cube-shadow, #0C234F)" strokeWidth={1.2} />
|
||||
|
||||
{/* Tier 1 White Diamond Top Surface (Well-Proportioned 248px wide: x=476 to 724, y=228 to 352) */}
|
||||
<polygon
|
||||
points="476,290 600,352 724,290 600,228"
|
||||
fill="var(--bg-raised, #FCFDFF)"
|
||||
stroke="var(--border-strong, #A9BEDA)"
|
||||
strokeWidth={1.1}
|
||||
/>
|
||||
|
||||
{/* Tier 2: Refined & Compact 3D Elevated Stepped Workstation Platform (Drop Height = 12px) */}
|
||||
{/* Front-left drop 1 (under monitor) */}
|
||||
<polygon
|
||||
points="506,285 538,301 538,313 506,297"
|
||||
fill="var(--hero-illus-cube-front, #2066AB)"
|
||||
stroke="var(--hero-illus-cube-side, #0E4073)"
|
||||
strokeWidth={0.75}
|
||||
/>
|
||||
{/* Front-left drop 2 (middle step in front of pencil) */}
|
||||
<polygon
|
||||
points="538,301 570,317 570,329 538,313"
|
||||
fill="var(--hero-illus-cube-front, #2066AB)"
|
||||
stroke="var(--hero-illus-cube-side, #0E4073)"
|
||||
strokeWidth={0.75}
|
||||
/>
|
||||
{/* Front-right drop (middle step facing SE) */}
|
||||
<polygon
|
||||
points="570,317 602,301 602,313 570,329"
|
||||
fill="var(--hero-illus-cube-side, #0E4073)"
|
||||
stroke="var(--hero-illus-cube-shadow, #0C234F)"
|
||||
strokeWidth={0.75}
|
||||
/>
|
||||
{/* Inner corner step drop (facing right towards PC Tower) */}
|
||||
<polygon
|
||||
points="602,301 570,285 570,297 602,313"
|
||||
fill="var(--hero-illus-cube-side, #0E4073)"
|
||||
stroke="var(--hero-illus-cube-shadow, #0C234F)"
|
||||
strokeWidth={0.75}
|
||||
/>
|
||||
<polygon
|
||||
points="570,285 602,269 602,281 570,297"
|
||||
fill="var(--hero-illus-cube-shadow, #0C234F)"
|
||||
stroke="var(--color-black, #07132F)"
|
||||
strokeWidth={0.75}
|
||||
/>
|
||||
|
||||
{/* Tier 2: White Top Surfaces of the 3 Workstation Step Tiles */}
|
||||
{/* Left Tile (holding Monitor) */}
|
||||
<polygon
|
||||
points="506,285 538,269 570,285 538,301"
|
||||
fill="var(--bg-raised, #FCFDFF)"
|
||||
stroke="var(--border-strong, #A9BEDA)"
|
||||
strokeWidth={0.9}
|
||||
/>
|
||||
{/* Center Middle Tile */}
|
||||
<polygon
|
||||
points="538,301 570,285 602,301 570,317"
|
||||
fill="var(--bg-raised, #FCFDFF)"
|
||||
stroke="var(--border-strong, #A9BEDA)"
|
||||
strokeWidth={0.9}
|
||||
/>
|
||||
{/* Back Tile (holding Pencil) */}
|
||||
<polygon
|
||||
points="538,269 570,253 602,269 570,285"
|
||||
fill="var(--bg-raised, #FCFDFF)"
|
||||
stroke="var(--border-strong, #A9BEDA)"
|
||||
strokeWidth={0.9}
|
||||
/>
|
||||
|
||||
{/* Dividing Grid Lines on the Step */}
|
||||
<line x1={538} y1={301} x2={570} y2={285} stroke="var(--border-default, #D9E2EF)" strokeWidth={0.8} />
|
||||
<line x1={570} y1={285} x2={602} y2={301} stroke="var(--border-default, #D9E2EF)" strokeWidth={0.8} />
|
||||
<line x1={538} y1={269} x2={570} y2={285} stroke="var(--border-default, #D9E2EF)" strokeWidth={0.8} />
|
||||
</g>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Glowing Blue Circuit Lines
|
||||
* Completely uniform 2:1 isometric slope (26.565°, dy/dx = ±0.5) across ALL conduit segments
|
||||
*/
|
||||
function CircuitConduits({ activePost }: { activePost: string | null }) {
|
||||
return (
|
||||
<g className="circuit-conduits">
|
||||
<defs>
|
||||
<filter id="circuitGlow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="0" stdDeviation="2" floodColor="var(--accent-default, #3D8AFA)" floodOpacity="0.6" />
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
{/* Left Branch: Phone -> Step 3 -> LinkedIn -> Center Platform with exact ±0.5 slope */}
|
||||
<path
|
||||
d="M 280,360 L 360,320 L 440,360 L 514,323"
|
||||
fill="none"
|
||||
stroke="var(--accent-default, #3D8AFA)"
|
||||
strokeWidth={2.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="miter"
|
||||
filter="url(#circuitGlow)"
|
||||
opacity={activePost === 'phone' || activePost === 'linkedin' ? 1 : 0.85}
|
||||
/>
|
||||
|
||||
{/* Right Branch: Center Platform -> GitHub -> Step 5 -> Telegram with exact ±0.5 slope */}
|
||||
<path
|
||||
d="M 686,323 L 760,360 L 840,320 L 920,360"
|
||||
fill="none"
|
||||
stroke="var(--accent-default, #3D8AFA)"
|
||||
strokeWidth={2.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="miter"
|
||||
filter="url(#circuitGlow)"
|
||||
opacity={activePost === 'github' || activePost === 'telegram' ? 1 : 0.85}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main Isometric Footer Scene SVG Component
|
||||
*/
|
||||
export default function IsometricScene({
|
||||
activePost,
|
||||
onPostHover,
|
||||
onPostClick,
|
||||
}: IsometricSceneProps) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 175 1200 290"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="isometric-svg"
|
||||
preserveAspectRatio="xMidYMax meet"
|
||||
role="img"
|
||||
aria-label="Interactive 3D isometric footer scene"
|
||||
>
|
||||
{/* 1. Faint Isometric Ground Grid */}
|
||||
<IsometricBackgroundGrid />
|
||||
|
||||
{/* 2. Solid 3D Architectural Stepped Terraces */}
|
||||
<SteppedTerraces />
|
||||
|
||||
{/* 3. Glowing Royal Blue Circuit Conduits */}
|
||||
<CircuitConduits activePost={activePost} />
|
||||
|
||||
{/* 4. Left Mountain Cluster */}
|
||||
<MountainCluster x={80} y={250} scale={0.92} />
|
||||
|
||||
{/* 5. Right Mountain Cluster (mirrored) */}
|
||||
<MountainCluster x={1120} y={250} flip={true} scale={0.92} />
|
||||
|
||||
{/* 6. Pine Trees on the floor along ground line */}
|
||||
<PineTree x={335} y={425} scale={0.84} dark={true} />
|
||||
<PineTree x={395} y={422} scale={0.96} dark={false} />
|
||||
<PineTree x={490} y={422} scale={0.92} />
|
||||
<PineTree x={810} y={422} scale={0.96} dark={false} />
|
||||
<PineTree x={865} y={425} scale={0.84} dark={true} />
|
||||
<PineTree x={965} y={422} scale={0.92} />
|
||||
|
||||
{/* 7. Central Workstation Illustrations resting flush on the large platform */}
|
||||
<g className="central-workstation">
|
||||
{/* Prominent Monitor Display resting on left elevated step */}
|
||||
<IsoMonitorDisplay x={534} y={285} />
|
||||
{/* Majestic Upright Faceted Pencil resting on back elevated step */}
|
||||
<IsoUprightPencil x={570} y={269} height={80} width={17} />
|
||||
{/* Vertical PC Tower on Pedestal Plate resting on Tier 1 */}
|
||||
<IsoPCTower x={642} y={290} size={20} height={40} />
|
||||
</g>
|
||||
|
||||
{/* 8. The 4 Interactive Isometric Signposts rotated in 3D */}
|
||||
<IsoSignpost
|
||||
id="phone"
|
||||
x={280}
|
||||
y={360}
|
||||
postHeight={48}
|
||||
facing="right"
|
||||
backdropDirection="right-top"
|
||||
icon={<PhoneIcon />}
|
||||
label="09388263077"
|
||||
isActive={activePost === 'phone'}
|
||||
onMouseEnter={() => onPostHover?.('phone')}
|
||||
onMouseLeave={() => onPostHover?.(null)}
|
||||
onClick={() => onPostClick?.('phone')}
|
||||
/>
|
||||
|
||||
<IsoSignpost
|
||||
id="linkedin"
|
||||
x={440}
|
||||
y={360}
|
||||
postHeight={48}
|
||||
facing="right"
|
||||
backdropDirection="right-top"
|
||||
icon={<LinkedInIcon />}
|
||||
label="@amiirkhl"
|
||||
isActive={activePost === 'linkedin'}
|
||||
onMouseEnter={() => onPostHover?.('linkedin')}
|
||||
onMouseLeave={() => onPostHover?.(null)}
|
||||
onClick={() => onPostClick?.('linkedin')}
|
||||
/>
|
||||
|
||||
<IsoSignpost
|
||||
id="github"
|
||||
x={760}
|
||||
y={360}
|
||||
postHeight={48}
|
||||
facing="left"
|
||||
backdropDirection="left-top"
|
||||
icon={<GitHubIcon />}
|
||||
label="git.amiirkhl.ir"
|
||||
isActive={activePost === 'github'}
|
||||
onMouseEnter={() => onPostHover?.('github')}
|
||||
onMouseLeave={() => onPostHover?.(null)}
|
||||
onClick={() => onPostClick?.('github')}
|
||||
/>
|
||||
|
||||
<IsoSignpost
|
||||
id="telegram"
|
||||
x={920}
|
||||
y={360}
|
||||
postHeight={48}
|
||||
facing="left"
|
||||
backdropDirection="left-top"
|
||||
icon={<TelegramIcon />}
|
||||
label="@amiirkhl"
|
||||
isActive={activePost === 'telegram'}
|
||||
onMouseEnter={() => onPostHover?.('telegram')}
|
||||
onMouseLeave={() => onPostHover?.(null)}
|
||||
onClick={() => onPostClick?.('telegram')}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user