Compare commits
5 Commits
32dab06220
...
5def494bf4
| Author | SHA1 | Date | |
|---|---|---|---|
| 5def494bf4 | |||
| b4688bb4f2 | |||
| bd693aa4bc | |||
| 7694eb5a97 | |||
| f9cf19c23f |
@@ -1,4 +1,6 @@
|
||||
.footerSection {
|
||||
--footer-grid-line: rgba(21, 87, 166, 0.1);
|
||||
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
width: 100%;
|
||||
@@ -12,3 +14,50 @@
|
||||
justify-content: flex-start;
|
||||
background-color: var(--bg-page, #ffffff);
|
||||
}
|
||||
|
||||
.footerSection::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 100vw;
|
||||
transform: translateX(-50%);
|
||||
z-index: 0;
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
background-image:
|
||||
repeating-linear-gradient(
|
||||
26.565deg,
|
||||
transparent 0 35px,
|
||||
var(--footer-grid-line) 35px 36px
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
153.435deg,
|
||||
transparent 0 35px,
|
||||
var(--footer-grid-line) 35px 36px
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
#000 18%,
|
||||
#000 80%,
|
||||
transparent 100%
|
||||
);
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
#000 18%,
|
||||
#000 80%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.footerSection > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
:global([data-theme='dark']) .footerSection {
|
||||
--footer-grid-line: rgba(130, 181, 255, 0.09);
|
||||
}
|
||||
|
||||
@@ -348,6 +348,7 @@ export function IsoSignpost({
|
||||
return (
|
||||
<g
|
||||
className={`iso-signpost ${isActive ? 'active' : ''}`}
|
||||
data-footer-contact={_id}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={onClick}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
max-width: 780px;
|
||||
max-width: 1100px;
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
@@ -35,7 +35,7 @@
|
||||
line-height: 1.35;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 clamp(16px, 2.5vw, 24px) 0;
|
||||
word-break: break-word;
|
||||
word-break: normal;
|
||||
animation: footerFadeIn 0.7s ease-out forwards;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,66 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Draw the circuit once on scroll, then reveal its four connected signposts. */
|
||||
:global(html[data-motion-ready='true'])
|
||||
.sceneContainer[data-footer-reveal]
|
||||
:global(.footer-circuit-path) {
|
||||
stroke-dasharray: 1;
|
||||
stroke-dashoffset: 1;
|
||||
transition: stroke-dashoffset var(--motion-duration-reveal) var(--motion-ease-in-out);
|
||||
}
|
||||
|
||||
:global(html[data-motion-ready='true'])
|
||||
.sceneContainer[data-footer-reveal][data-visible='true']
|
||||
:global(.footer-circuit-path) {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
|
||||
:global(html[data-motion-ready='true'])
|
||||
.sceneContainer[data-footer-reveal]
|
||||
:global(.iso-signpost) {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
transform-box: fill-box;
|
||||
transform-origin: center bottom;
|
||||
transition:
|
||||
opacity var(--motion-duration-default) var(--motion-ease-out),
|
||||
transform var(--motion-duration-default) var(--motion-ease-out);
|
||||
transition-delay: var(--motion-duration-reveal);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
:global(html[data-motion-ready='true'])
|
||||
.sceneContainer[data-footer-reveal]
|
||||
:global(.iso-signpost[data-footer-contact='linkedin']) {
|
||||
transition-delay: calc(var(--motion-duration-reveal) + var(--motion-stagger));
|
||||
}
|
||||
|
||||
:global(html[data-motion-ready='true'])
|
||||
.sceneContainer[data-footer-reveal]
|
||||
:global(.iso-signpost[data-footer-contact='github']) {
|
||||
transition-delay: calc(
|
||||
var(--motion-duration-reveal) + var(--motion-stagger) + var(--motion-stagger)
|
||||
);
|
||||
}
|
||||
|
||||
:global(html[data-motion-ready='true'])
|
||||
.sceneContainer[data-footer-reveal]
|
||||
:global(.iso-signpost[data-footer-contact='telegram']) {
|
||||
transition-delay: calc(
|
||||
var(--motion-duration-reveal) + var(--motion-stagger) + var(--motion-stagger) +
|
||||
var(--motion-stagger)
|
||||
);
|
||||
}
|
||||
|
||||
:global(html[data-motion-ready='true'])
|
||||
.sceneContainer[data-footer-reveal][data-visible='true']
|
||||
:global(.iso-signpost) {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
will-change: auto;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
Synchronized Contact Cards Grid
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
@@ -85,7 +85,7 @@ export default function IsometricFooter() {
|
||||
</header>
|
||||
|
||||
{/* 3D Isometric SVG Scene */}
|
||||
<div className={styles.sceneContainer}>
|
||||
<div className={styles.sceneContainer} data-footer-reveal>
|
||||
<IsometricScene
|
||||
activePost={activePost}
|
||||
onPostHover={setActivePost}
|
||||
|
||||
@@ -13,59 +13,6 @@ import {
|
||||
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,
|
||||
@@ -107,24 +54,24 @@ function SteppedTerraces() {
|
||||
{/* 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} />
|
||||
<polygon points="200,360 280,400 280,416 200,376" fill="var(--hero-illus-cube-front, #2066AB)" stroke="var(--hero-illus-cube-side, #0E4073)" strokeWidth={1.2} />
|
||||
<polygon points="360,360 280,400 280,416 360,376" fill="var(--hero-illus-cube-side, #0E4073)" stroke="var(--hero-illus-cube-shadow, #0C234F)" 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} />
|
||||
<polygon points="360,360 440,400 440,416 360,376" fill="var(--hero-illus-cube-front, #2066AB)" stroke="var(--hero-illus-cube-side, #0E4073)" strokeWidth={1.2} />
|
||||
<polygon points="520,360 440,400 440,416 520,376" fill="var(--hero-illus-cube-side, #0E4073)" stroke="var(--hero-illus-cube-shadow, #0C234F)" 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} />
|
||||
<polygon points="520,360 600,400 600,416 520,376" fill="var(--hero-illus-cube-front, #2066AB)" stroke="var(--hero-illus-cube-side, #0E4073)" strokeWidth={1.2} />
|
||||
<polygon points="680,360 600,400 600,416 680,376" fill="var(--hero-illus-cube-side, #0E4073)" stroke="var(--hero-illus-cube-shadow, #0C234F)" 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} />
|
||||
<polygon points="680,360 760,400 760,416 680,376" fill="var(--hero-illus-cube-front, #2066AB)" stroke="var(--hero-illus-cube-side, #0E4073)" strokeWidth={1.2} />
|
||||
<polygon points="840,360 760,400 760,416 840,376" fill="var(--hero-illus-cube-side, #0E4073)" stroke="var(--hero-illus-cube-shadow, #0C234F)" 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} />
|
||||
<polygon points="840,360 920,400 920,416 840,376" fill="var(--hero-illus-cube-front, #2066AB)" stroke="var(--hero-illus-cube-side, #0E4073)" strokeWidth={1.2} />
|
||||
<polygon points="1000,360 920,400 920,416 1000,376" fill="var(--hero-illus-cube-side, #0E4073)" stroke="var(--hero-illus-cube-shadow, #0C234F)" 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} />
|
||||
@@ -277,7 +224,9 @@ function CircuitConduits({ activePost }: { activePost: string | null }) {
|
||||
|
||||
{/* 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"
|
||||
d="M 514,323 L 440,360 L 360,320 L 280,360"
|
||||
className="footer-circuit-path"
|
||||
pathLength={1}
|
||||
fill="none"
|
||||
stroke="var(--accent-default, #3D8AFA)"
|
||||
strokeWidth={2.5}
|
||||
@@ -290,6 +239,8 @@ function CircuitConduits({ activePost }: { activePost: string | null }) {
|
||||
{/* 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"
|
||||
className="footer-circuit-path"
|
||||
pathLength={1}
|
||||
fill="none"
|
||||
stroke="var(--accent-default, #3D8AFA)"
|
||||
strokeWidth={2.5}
|
||||
@@ -319,22 +270,19 @@ export default function IsometricScene({
|
||||
role="img"
|
||||
aria-label="Interactive 3D isometric footer scene"
|
||||
>
|
||||
{/* 1. Faint Isometric Ground Grid */}
|
||||
<IsometricBackgroundGrid />
|
||||
|
||||
{/* 2. Solid 3D Architectural Stepped Terraces */}
|
||||
{/* 1. Solid 3D Architectural Stepped Terraces */}
|
||||
<SteppedTerraces />
|
||||
|
||||
{/* 3. Glowing Royal Blue Circuit Conduits */}
|
||||
{/* 2. Glowing Royal Blue Circuit Conduits */}
|
||||
<CircuitConduits activePost={activePost} />
|
||||
|
||||
{/* 4. Left Mountain Cluster */}
|
||||
{/* 3. Left Mountain Cluster */}
|
||||
<MountainCluster x={80} y={250} scale={0.92} />
|
||||
|
||||
{/* 5. Right Mountain Cluster (mirrored) */}
|
||||
{/* 4. Right Mountain Cluster (mirrored) */}
|
||||
<MountainCluster x={1120} y={250} flip={true} scale={0.92} />
|
||||
|
||||
{/* 6. Pine Trees on the floor along ground line */}
|
||||
{/* 5. 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} />
|
||||
@@ -342,7 +290,7 @@ export default function IsometricScene({
|
||||
<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 */}
|
||||
{/* 6. 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} />
|
||||
@@ -352,7 +300,7 @@ export default function IsometricScene({
|
||||
<IsoPCTower x={642} y={290} size={20} height={40} />
|
||||
</g>
|
||||
|
||||
{/* 8. The 4 Interactive Isometric Signposts rotated in 3D */}
|
||||
{/* 7. The 4 Interactive Isometric Signposts rotated in 3D */}
|
||||
<IsoSignpost
|
||||
id="phone"
|
||||
x={280}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const REVEAL_SELECTOR = '[data-reveal]';
|
||||
const REVEAL_SELECTOR = '[data-reveal], [data-footer-reveal]';
|
||||
|
||||
export const MotionController = () => {
|
||||
useEffect(() => {
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.aboutScene canvas {
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.aboutScene {
|
||||
z-index: 0;
|
||||
}
|
||||
@@ -58,7 +62,7 @@
|
||||
}
|
||||
|
||||
.aboutScene canvas {
|
||||
opacity: 0.68;
|
||||
opacity: 0.56;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user