mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 08:36:25 +03:00
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:
co-authored by
Claude Opus 4.6
parent
50e5c9af68
commit
3c9f705d49
+167
-24
@@ -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
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>
|
||||
`
|
||||
};
|
||||
@@ -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('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user