Compare commits

..

5 Commits

Author SHA1 Message Date
5def494bf4 style(about): soften decorative scene
Some checks failed
Test and deploy / Verify production build (push) Has been cancelled
Test and deploy / Deploy production (push) Has been cancelled
2026-09-08 12:32:48 +03:30
b4688bb4f2 fix(footer): delay headline wrapping 2026-09-08 12:32:38 +03:30
bd693aa4bc fix(footer): extend isometric grid across section 2026-09-08 12:32:18 +03:30
7694eb5a97 fix(footer): reuse hero palette for terrace depth 2026-09-08 12:31:53 +03:30
f9cf19c23f feat(footer): reveal circuit contacts on scroll 2026-09-08 12:30:55 +03:30
7 changed files with 141 additions and 79 deletions

View File

@@ -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);
}

View File

@@ -348,6 +348,7 @@ export function IsoSignpost({
return (
<g
className={`iso-signpost ${isActive ? 'active' : ''}`}
data-footer-contact={_id}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={onClick}

View File

@@ -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
-------------------------------------------------------------------------- */

View File

@@ -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}

View File

@@ -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}

View File

@@ -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(() => {

View File

@@ -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;
}
}