Skip to content

Commit 48c5956

Browse files
committed
feat(animations): add Swiss-style intro/scroll animations with variants and stagger
- Add .reveal-group/.reveal-child utilities and variant classes (left/right/up/zoom/fade)\n- Hero intro sequence via .intro-item with reduced-motion support\n- Apply variant animations across components; remove footer animations fix(lint): update forEach callback style in enhancements.ts for ultracite chore(build): suppress Astro internal unused import warnings via Vite logger plugin and rollup onwarn
1 parent 88c5f72 commit 48c5956

File tree

10 files changed

+213
-35
lines changed

10 files changed

+213
-35
lines changed

astro.config.mjs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,27 @@ export default defineConfig({
3535
},
3636
},
3737
},
38+
plugins: [
39+
{
40+
name: "suppress-astro-unused-internal-import-warnings",
41+
configResolved(config) {
42+
const originalWarn = config.logger.warn;
43+
config.logger.warn = (msg, options) => {
44+
try {
45+
const text = typeof msg === "string" ? msg : msg?.message || "";
46+
if (
47+
text.includes("@astrojs/internal-helpers/remote") ||
48+
text.includes("node_modules/astro/dist/assets/utils/remotePattern.js") ||
49+
text.includes("node_modules/astro/dist/assets/services/service.js")
50+
) {
51+
return; // swallow these specific warnings
52+
}
53+
} catch {}
54+
return originalWarn(msg, options);
55+
};
56+
},
57+
},
58+
],
3859
css: {
3960
devSourcemap: false,
4061
},

bun.lock

Lines changed: 60 additions & 4 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
"astro": "5.15.2"
1616
},
1717
"devDependencies": {
18-
"@types/node": "^24.9.2",
19-
"knip": "^5.66.4",
18+
"@types/node": "^24.10.0",
19+
"knip": "^5.67.1",
2020
"typescript": "^5.9.3",
2121
"@astrojs/check": "^0.9.5",
22-
"ultracite": "^6.1.0"
22+
"ultracite": "^6.3.0"
2323
}
2424
}

src/components/about.astro

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="shape-bar"></div>
66
</div>
77
<div class="swiss-container">
8-
<header class="head section-header-spacing">
8+
<header class="head section-header-spacing reveal-group">
99
<span class="section-eyebrow">About</span>
1010
<h2 class="title swiss-heading-2">Practical UI engineering</h2>
1111
<p class="desc">
@@ -14,8 +14,8 @@
1414
</p>
1515
</header>
1616

17-
<div class="about-grid">
18-
<div class="about-main">
17+
<div class="about-grid reveal-group" style="--reveal-stagger: 90ms;">
18+
<div class="about-main reveal-child reveal-left">
1919
<h3 class="swiss-heading-4">Profile</h3>
2020
<p class="swiss-body">
2121
Web developer based in Greece. I focus on reliable <span class="khl"
@@ -44,7 +44,7 @@
4444
</ul>
4545
</div>
4646

47-
<aside class="about-aside">
47+
<aside class="about-aside reveal-child reveal-right">
4848
<dl class="facts">
4949
<div class="row">
5050
<dt class="label label-normal">Role</dt><dd>Web Developer</dd>

src/components/contact.astro

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import SectionHeader from "./section-header.astro";
1616
description="Best way to reach me is email. I usually reply within 24–48 hours."
1717
/>
1818

19-
<div class="contact-grid">
20-
<a class="contact-item primary" href="mailto:[email protected]">
19+
<div class="contact-grid reveal-group" style="--reveal-stagger: 85ms;">
20+
<a class="contact-item primary reveal-child reveal-zoom" href="mailto:[email protected]">
2121
<span class="icon" aria-hidden="true">
2222
<svg
2323
width="22"
@@ -49,7 +49,7 @@ import SectionHeader from "./section-header.astro";
4949
</a>
5050

5151
<a
52-
class="contact-item"
52+
class="contact-item reveal-child reveal-left"
5353
href="https://github.com/dacrab"
5454
target="_blank"
5555
rel="noopener noreferrer"
@@ -74,7 +74,7 @@ import SectionHeader from "./section-header.astro";
7474
</a>
7575

7676
<a
77-
class="contact-item"
77+
class="contact-item reveal-child reveal-right"
7878
href="https://linkedin.com/in/vkavouras"
7979
target="_blank"
8080
rel="noopener noreferrer"
@@ -99,9 +99,9 @@ import SectionHeader from "./section-header.astro";
9999
</a>
100100
</div>
101101

102-
<div class="contact-extra">
103-
<span class="swiss-caption">CV</span>
104-
<CvLinks />
102+
<div class="contact-extra reveal-group" style="--reveal-stagger: 60ms;">
103+
<span class="swiss-caption reveal-child reveal-fade">CV</span>
104+
<div class="reveal-child reveal-up"><CvLinks /></div>
105105
</div>
106106
</div>
107107
</section>

src/components/hero.astro

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,24 @@ import CvLinks from "./cv-links.astro";
1717
<div class="hero-content">
1818
<div class="stack">
1919
<div class="eyebrow-wrap">
20-
<span class="section-eyebrow">Web Developer</span>
20+
<span class="section-eyebrow intro-item">Web Developer</span>
2121
</div>
22-
<div class="hero-rule"></div>
22+
<div class="hero-rule intro-item"></div>
2323
<h1 class="title">
24-
<span class="title-line">Vaggelis</span>
25-
<span class="title-line">Kavouras</span>
24+
<span class="title-line intro-item">Vaggelis</span>
25+
<span class="title-line intro-item">Kavouras</span>
2626
</h1>
27-
<p class="lead">
27+
<p class="lead intro-item">
2828
I build fast, accessible interfaces focused on clarity, performance,
2929
and maintainability.
3030
</p>
3131
</div>
3232
<div class="actions">
33-
<a href="#projects" class="cta-link">View projects</a>
34-
<a href="#contact" class="cta-link cta-muted">Get in touch</a>
33+
<a href="#projects" class="cta-link intro-item">View projects</a>
34+
<a href="#contact" class="cta-link cta-muted intro-item">Get in touch</a>
3535
</div>
3636
</div>
37-
<div class="hero-aside">
37+
<div class="hero-aside intro-item">
3838
<CvLinks englishLabel="CV—EN" greekLabel="CV—EL" />
3939
</div>
4040
</div>

src/components/projects.astro

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import SectionHeader from "./section-header.astro";
1313
description="Minimal case cards with quick preview and gallery."
1414
/>
1515

16-
<div class="proj-grid">
16+
<div class="proj-grid reveal-group" style="--reveal-stagger: 70ms;">
1717
{
1818
projects.map((p) => {
1919
const gallery = JSON.stringify(
@@ -25,7 +25,7 @@ import SectionHeader from "./section-header.astro";
2525
);
2626
return (
2727
<article
28-
class="proj-card"
28+
class="proj-card reveal-child reveal-zoom"
2929
data-project={p.id}
3030
data-gallery={gallery}
3131
data-title={p.title}
@@ -165,7 +165,7 @@ import SectionHeader from "./section-header.astro";
165165
<span class="work-badge u-caps-xs-10" aria-hidden="true">LIVE</span>
166166
</div>
167167
<div
168-
class="current-work-grid"
168+
class="current-work-grid reveal-group"
169169
id="current-work-grid"
170170
aria-live="polite"
171171
>
@@ -218,7 +218,9 @@ import SectionHeader from "./section-header.astro";
218218
.slice(0, 8)
219219
.forEach((repo: any) => {
220220
const el = document.createElement("article");
221-
el.className = "current-work-card";
221+
// Alternate left/right variants for visual rhythm
222+
const variant = grid?.children?.length % 2 === 0 ? "reveal-left" : "reveal-right";
223+
el.className = `current-work-card reveal-child ${variant}`;
222224
el.innerHTML = `
223225
<div class="work-header">
224226
<h4 class="work-title">${repo.name}</h4>

src/components/section-header.astro

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ interface Props {
99
const { eyebrow, title, description, className = "" } = Astro.props as Props;
1010
---
1111

12-
<header class={`section-head section-header-spacing ${className}`}>
12+
<header class={`section-head section-header-spacing reveal-group ${className}`}>
1313
<div class="section-head-grid">
14-
<div class="section-head-content">
14+
<div class="section-head-content reveal-child reveal-left">
1515
<div class="eyebrow-line">
1616
{eyebrow && <span class="section-eyebrow">{eyebrow}</span>}
1717
<div class="divider-line" aria-hidden="true"></div>
@@ -20,7 +20,7 @@ const { eyebrow, title, description, className = "" } = Astro.props as Props;
2020
{description && <p class="desc">{description}</p>}
2121
<slot />
2222
</div>
23-
<div class="section-head-mark" aria-hidden="true">
23+
<div class="section-head-mark reveal-child reveal-right" aria-hidden="true">
2424
<span class="mark-text">—</span>
2525
</div>
2626
</div>

src/styles/globals.css

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,59 @@
11441144
.reveal-element.revealed,
11451145
.animate-in {
11461146
opacity: 1 !important;
1147-
transform: translateY(0) !important;
1147+
transform: none !important;
1148+
}
1149+
1150+
/* Reveal groups with staggered children */
1151+
.reveal-group .reveal-child {
1152+
opacity: 0;
1153+
transform: translateY(16px);
1154+
transition:
1155+
opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1),
1156+
transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
1157+
will-change: opacity, transform;
1158+
}
1159+
1160+
.reveal-group.revealed .reveal-child {
1161+
opacity: 1;
1162+
transform: none;
1163+
}
1164+
1165+
/* Stagger via CSS variable set by JS: --i (index), --reveal-stagger (ms) */
1166+
.reveal-group.revealed .reveal-child {
1167+
transition-delay: calc(var(--reveal-stagger, 80ms) * var(--i, 0));
1168+
}
1169+
1170+
/* Directional/variant reveals (initial states) */
1171+
.reveal-left {
1172+
transform: translateX(-16px);
1173+
}
1174+
.reveal-right {
1175+
transform: translateX(16px);
1176+
}
1177+
.reveal-up {
1178+
transform: translateY(16px);
1179+
}
1180+
.reveal-zoom {
1181+
transform: scale(0.98);
1182+
}
1183+
.reveal-fade {
1184+
transform: none;
1185+
}
1186+
1187+
/* Intro sequence items (e.g., hero) */
1188+
.intro-item {
1189+
opacity: 0;
1190+
transform: translateY(14px);
1191+
transition:
1192+
opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
1193+
transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
1194+
will-change: opacity, transform;
1195+
}
1196+
1197+
.intro-item.animate-in {
1198+
opacity: 1;
1199+
transform: translateY(0);
11481200
}
11491201

11501202
*:focus-visible {

src/utils/enhancements.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function initIndexEnhancements() {
2424
});
2525
}
2626

27-
// Section reveal animations
27+
// Section reveal animations (entire sections)
2828
try {
2929
const observer = new IntersectionObserver(
3030
(entries) => {
@@ -44,5 +44,52 @@ export function initIndexEnhancements() {
4444
} catch {
4545
// IntersectionObserver unsupported or DOM not ready; skip enhancements
4646
}
47+
48+
// Grouped reveal animations with staggered children
49+
try {
50+
const prefersReducedMotion = window.matchMedia(
51+
"(prefers-reduced-motion: reduce)",
52+
).matches;
53+
const groupObserver = new IntersectionObserver(
54+
(entries) => {
55+
for (const entry of entries) {
56+
if (!entry.isIntersecting) continue;
57+
const group = entry.target as HTMLElement;
58+
group.classList.add("revealed");
59+
const children =
60+
group.querySelectorAll<HTMLElement>(".reveal-child");
61+
children.forEach((el, index) => {
62+
// Set index for CSS calc-based delay
63+
el.style.setProperty("--i", String(index));
64+
if (prefersReducedMotion) {
65+
el.classList.add("animate-in");
66+
}
67+
});
68+
}
69+
},
70+
{ threshold: 0.12, rootMargin: "0px 0px -10% 0px" },
71+
);
72+
document.querySelectorAll<HTMLElement>(".reveal-group").forEach((g) => {
73+
groupObserver.observe(g);
74+
});
75+
} catch {}
76+
77+
// Hero intro sequence (subtle Swiss-style stagger)
78+
try {
79+
const prefersReducedMotion = window.matchMedia(
80+
"(prefers-reduced-motion: reduce)",
81+
).matches;
82+
const items = document.querySelectorAll<HTMLElement>("#home .intro-item");
83+
if (items.length) {
84+
const baseDelay = 80; // ms between items
85+
items.forEach((el, i) => {
86+
if (prefersReducedMotion) {
87+
el.classList.add("animate-in");
88+
return;
89+
}
90+
setTimeout(() => el.classList.add("animate-in"), i * baseDelay + 100);
91+
});
92+
}
93+
} catch {}
4794
});
4895
}

0 commit comments

Comments
 (0)