Add animated foundation icons with ambient + hover states

Each of the 7 dimension icons now has a continuous subtle animation
(breathing draws, pulsing circles, bobbing ball, blinking cursor,
toggle wobble) plus an enhanced hover effect. Responsive icon uses
transform-box: fill-box for a staggered desktop-to-mobile reflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-03 12:15:20 -07:00
co-authored by Claude Opus 4.6
parent 50e5c9af68
commit 3c9f705d49
4 changed files with 5816 additions and 31 deletions
+167 -24
View File
@@ -987,56 +987,199 @@ code {
}
.foundation-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
display: flex;
align-items: flex-end;
height: 600px;
gap: var(--spacing-sm);
margin-top: -80px; /* Pull up into the header whitespace */
position: relative;
}
@media (max-width: 900px) {
.foundation-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.foundation-grid {
grid-template-columns: 1fr;
}
.foundation-column {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
justify-content: flex-end;
cursor: pointer;
min-width: 0;
}
.foundation-card {
padding: var(--spacing-md);
background: white;
border-radius: 8px;
border: 1px solid var(--color-mist);
border-radius: 12px;
display: flex;
flex-direction: column;
height: 280px;
margin-bottom: var(--spacing-xs);
transition: transform var(--duration-base) var(--ease-spring), border-color var(--duration-base) var(--ease-out), box-shadow var(--duration-base) var(--ease-out);
position: relative;
z-index: 2;
box-sizing: border-box;
}
.foundation-column:hover .foundation-card {
transform: translateY(-12px);
border-color: var(--color-accent);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
}
.foundation-plinth {
width: 100%;
background: repeating-linear-gradient(45deg, var(--color-bg), var(--color-bg) 2px, transparent 2px, transparent 10px);
border: 1px solid var(--color-mist);
border-bottom: none;
border-radius: 8px 8px 0 0;
transition: all var(--duration-base) var(--ease-out);
}
.foundation-column:hover .foundation-plinth {
background: var(--color-mist);
opacity: 0.7;
}
/* Plinth Heights - More dramatic steps */
.plinth-1 { height: 4%; }
.plinth-2 { height: 12%; }
.plinth-3 { height: 20%; }
.plinth-4 { height: 28%; }
.plinth-5 { height: 36%; }
.plinth-6 { height: 44%; }
.plinth-7 { height: 52%; }
.foundation-card-viz {
height: 52px;
display: flex;
align-items: center;
justify-content: flex-start;
color: var(--color-ink);
margin-bottom: var(--spacing-md);
}
.foundation-card-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: var(--spacing-xs);
align-items: flex-start;
margin-bottom: 8px;
}
.foundation-card-label {
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
font-family: var(--font-display);
font-size: 1.35rem;
color: var(--color-ink);
line-height: 1.1;
}
.foundation-card-count {
font-family: var(--font-mono);
font-size: 0.625rem;
font-size: 0.7rem;
color: var(--color-accent);
margin-top: 4px;
}
.foundation-card-detail {
font-size: 0.8125rem;
line-height: 1.5;
font-size: 0.875rem;
color: var(--color-ash);
margin: 0;
line-height: 1.4;
margin: auto 0 0 0;
}
.foundation-svg {
width: 44px;
height: 44px;
}
/* Typography & Spatial (Breathing draw + full draw on hover) */
.anim-draw { stroke-dasharray: 100; stroke-dashoffset: 100; animation: draw-breathe 4s ease-in-out infinite; }
.foundation-column:hover .anim-draw { animation: draw-in 0.8s var(--ease-out) forwards; }
.anim-draw-delay { stroke-dasharray: 100; stroke-dashoffset: 100; }
.foundation-column:hover .anim-draw-delay { animation: draw-in 1s var(--ease-out) 0.2s forwards; }
@keyframes draw-breathe { 0%, 100% { stroke-dashoffset: 100; } 50% { stroke-dashoffset: 40; } }
@keyframes draw-in { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
/* Color & Contrast (Breathing pulse + spread on hover) */
.anim-move-x { animation: pulse-x 3s ease-in-out infinite; }
.foundation-column:hover .anim-move-x { animation: spread-x 0.6s var(--ease-in-out) forwards; }
.anim-move-x-opp { animation: pulse-x-opp 3s ease-in-out infinite; }
.foundation-column:hover .anim-move-x-opp { animation: spread-x-opp 0.6s var(--ease-in-out) forwards; }
.anim-fade-in { opacity: 0; transition: opacity 0.6s var(--ease-in-out); }
.foundation-column:hover .anim-fade-in { opacity: 1; }
@keyframes pulse-x { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(1.5px); } }
@keyframes pulse-x-opp { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(-1.5px); } }
@keyframes spread-x { from { transform: translateX(0); } to { transform: translateX(4px); } }
@keyframes spread-x-opp { from { transform: translateX(0); } to { transform: translateX(-4px); } }
/* Responsive (Staggered reflow from desktop 2-col to mobile stack) */
.anim-res-frame, .anim-res-img, .anim-res-title, .anim-res-line-1, .anim-res-line-2 {
transform-box: fill-box;
transform-origin: 50% 50%;
transition: transform 0.4s var(--ease-in-out);
}
.anim-res-frame { transition-delay: 0s; }
.anim-res-img { transition-delay: 0s; }
.anim-res-title { transition-delay: 0s; }
.anim-res-line-1 { transition-delay: 0s; }
.anim-res-line-2 { transition-delay: 0s; }
.foundation-column:hover .anim-res-frame { transform: scaleX(0.57) scaleY(1.17); transition-delay: 0s; }
.foundation-column:hover .anim-res-img { transform: translate(6px, -4px) scale(0.65); transition-delay: 0.05s; }
.foundation-column:hover .anim-res-title { transform: translate(-7px, 6px) scaleX(0.65); transition-delay: 0.1s; }
.foundation-column:hover .anim-res-line-1 { transform: translate(-7px, 4.75px) scaleX(0.65); transition-delay: 0.15s; }
.foundation-column:hover .anim-res-line-2 { transform: translate(-6px, 4.25px) scaleX(0.6); transition-delay: 0.2s; }
/* Interaction (Gentle wobble + full toggle on hover) */
.anim-toggle-move { animation: toggle-wobble 3s ease-in-out infinite; }
.foundation-column:hover .anim-toggle-move { animation: toggle-snap 0.35s var(--ease-in-out) forwards; }
@keyframes toggle-wobble { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(2px); } }
@keyframes toggle-snap { from { transform: translateX(0); fill: var(--color-mist); } to { transform: translateX(8px); fill: var(--color-accent); } }
/* Motion (Gentle bob + full bounce on hover) */
.anim-squash-ball { transform-origin: 20px 20px; animation: ball-bob 2.5s ease-in-out infinite; }
.foundation-column:hover .anim-squash-ball { animation: bounce-ball 1.5s linear infinite; }
@keyframes ball-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(3px); } }
@keyframes bounce-ball {
0% { transform: translateY(0); }
6% { transform: translateY(0.5px); }
18% { transform: translateY(4px); }
35% { transform: translateY(12px); }
42% { transform: translateY(12px) scaleX(1.3) scaleY(0.6); }
48% { transform: translateY(12px); }
65% { transform: translateY(4px); }
78% { transform: translateY(0.5px); }
88%, 100% { transform: translateY(0); }
}
/* UX Writing (Cursor always blinks) */
.anim-blink { animation: blink-key 1s step-end infinite; }
@keyframes blink-key { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
@media (max-width: 1024px) {
.foundation-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
height: auto;
gap: var(--spacing-md);
margin-top: var(--spacing-xl);
}
.foundation-column { height: auto; }
.foundation-card { height: auto; min-height: 220px; }
.foundation-plinth { display: none; }
.foundation-column:hover .foundation-card { transform: translateY(-4px); }
/* Enable hover animations on card hover for mobile grid */
.foundation-card:hover .anim-draw { animation: draw-in 0.8s var(--ease-out) forwards; }
.foundation-card:hover .anim-draw-delay { animation: draw-in 1s var(--ease-out) 0.2s forwards; }
.foundation-card:hover .anim-move-x { animation: spread-x 0.6s var(--ease-in-out) forwards; }
.foundation-card:hover .anim-move-x-opp { animation: spread-x-opp 0.6s var(--ease-in-out) forwards; }
.foundation-card:hover .anim-fade-in { opacity: 1; }
.foundation-card:hover .anim-res-frame { transform: scaleX(0.57) scaleY(1.17); }
.foundation-card:hover .anim-res-img { transform: translate(6px, -4px) scale(0.65); }
.foundation-card:hover .anim-res-title { transform: translate(-7px, 6px) scaleX(0.65); }
.foundation-card:hover .anim-res-line-1 { transform: translate(-7px, 4.75px) scaleX(0.65); }
.foundation-card:hover .anim-res-line-2 { transform: translate(-6px, 4.25px) scaleX(0.6); }
.foundation-card:hover .anim-toggle-move { animation: toggle-snap 0.35s var(--ease-in-out) forwards; }
.foundation-card:hover .anim-squash-ball { animation: bounce-ball 1.5s linear infinite; }
}
.foundation-cta {
+5583 -1
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,53 @@
export const foundationAnimations = {
'Typography': `
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="foundation-svg">
<path d="M10 30L20 10L30 30" stroke="var(--color-mist)" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 30L20 10L30 30" stroke="var(--color-accent)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="anim-draw"/>
<path d="M15 22H25" stroke="var(--color-accent)" stroke-width="1.5" stroke-linecap="round" class="anim-draw-delay"/>
</svg>
`,
'Color & Contrast': `
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="foundation-svg">
<circle cx="16" cy="20" r="8" stroke="var(--color-ink)" stroke-width="1.5" class="anim-move-x"/>
<circle cx="24" cy="20" r="8" stroke="var(--color-accent)" stroke-width="1.5" class="anim-move-x-opp"/>
<path d="M20 14.5C21.5 16 22.5 18 22.5 20C22.5 22 21.5 24 20 25.5C18.5 24 17.5 22 17.5 20C17.5 18 18.5 16 20 14.5Z" fill="var(--color-accent)" class="anim-fade-in"/>
</svg>
`,
'Spatial Design': `
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="foundation-svg">
<rect x="5" y="10" width="30" height="18.5" stroke="var(--color-mist)" stroke-width="1"/>
<line x1="23.5" y1="10" x2="23.5" y2="28.5" stroke="var(--color-mist)" stroke-width="1"/>
<line x1="23.5" y1="21.5" x2="35" y2="21.5" stroke="var(--color-mist)" stroke-width="1"/>
<path d="M5 28.5C5 18.28 13.28 10 23.5 10C29.85 10 35 15.15 35 21.5C35 25.42 31.82 28.5 27.9 28.5" fill="none" stroke="var(--color-accent)" stroke-width="1.5" stroke-linecap="round" class="anim-draw"/>
</svg>
`,
'Responsive': `
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="foundation-svg">
<rect x="6" y="8" width="28" height="24" rx="2" stroke="var(--color-ink)" stroke-width="1.5" class="anim-res-frame"/>
<rect x="9" y="12" width="10" height="8" rx="1" fill="var(--color-accent)" class="anim-res-img"/>
<rect x="22" y="12" width="10" height="2" rx="0.5" fill="var(--color-ink)" class="anim-res-title"/>
<rect x="22" y="16.5" width="10" height="1.5" rx="0.5" fill="var(--color-ash)" class="anim-res-line-1"/>
<rect x="22" y="20" width="8" height="1.5" rx="0.5" fill="var(--color-ash)" class="anim-res-line-2"/>
</svg>
`,
'Interaction': `
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="foundation-svg">
<rect x="10" y="14" width="20" height="12" rx="6" stroke="var(--color-ink)" stroke-width="1.5"/>
<circle cx="16" cy="20" r="4" fill="var(--color-mist)" class="anim-toggle-move"/>
</svg>
`,
'Motion': `
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="foundation-svg">
<line x1="5" y1="32" x2="35" y2="32" stroke="var(--color-ink)" stroke-width="1.5"/>
<circle cx="20" cy="15" r="5" fill="var(--color-accent)" class="anim-squash-ball"/>
</svg>
`,
'UX Writing': `
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="foundation-svg">
<rect x="8" y="12" width="18" height="2" rx="1" fill="var(--color-ink)"/>
<rect x="8" y="18" width="22" height="2" rx="1" fill="var(--color-ash)"/>
<rect x="8" y="24" width="14" height="2" rx="1" fill="var(--color-accent)"/>
<line x1="24" y1="23" x2="24" y2="27" stroke="var(--color-accent)" stroke-width="1.5" class="anim-blink"/>
</svg>
`
};
+13 -6
View File
@@ -1,4 +1,5 @@
import { skillFocusAreas, dimensionGuidelineCounts } from '../data.js';
import { foundationAnimations } from './foundation-animations.js';
export function initFoundationGrid() {
const container = document.querySelector('.foundation-grid');
@@ -7,13 +8,19 @@ export function initFoundationGrid() {
const dimensions = skillFocusAreas['impeccable'];
if (!dimensions) return;
container.innerHTML = dimensions.map(dim => `
<div class="foundation-card">
<div class="foundation-card-header">
<span class="foundation-card-label">${dim.area}</span>
<span class="foundation-card-count">${dimensionGuidelineCounts[dim.area] || ''}</span>
container.innerHTML = dimensions.map((dim, i) => `
<div class="foundation-column">
<div class="foundation-card">
<div class="foundation-card-viz">
${foundationAnimations[dim.area] || ''}
</div>
<div class="foundation-card-header">
<span class="foundation-card-label">${dim.area}</span>
<span class="foundation-card-count">${dimensionGuidelineCounts[dim.area] || ''}</span>
</div>
<p class="foundation-card-detail">${dim.detail}</p>
</div>
<p class="foundation-card-detail">${dim.detail}</p>
<div class="foundation-plinth plinth-${i + 1}"></div>
</div>
`).join('');
}