Interaction Patterns Documentation
40 Days of Existential Threats - Interactive Experience Collection
Design System Overview
Core Philosophy
The interaction design for “40 Days of Existential Threats” must create a cohesive, immersive journey that feels like a single unified experience rather than 40 separate applications. All interactions should feel intentional, purposeful, and contribute to the dark, luminous aesthetic with bioluminescent accents.
Timing Philosophy
- Fast (100-150ms): Micro-interactions, state changes that need to feel instant
- Standard (200-300ms): Most UI transitions, hover states, reveals
- Dramatic (400-600ms): Entrance animations, surprise reveals, page transitions
- Ambient (800-1500ms): Background animations, subtle glow pulses
Easing Library
:root {
/* Standard easings */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
/* Dramatic easings */
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-dramatic: cubic-bezier(0.87, 0, 0.13, 1);
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* Subtle easings */
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
--ease-gentle: cubic-bezier(0.25, 0.1, 0.25, 1);
}Color Tokens for Interactions
:root {
/* Bioluminescent accent colors */
--glow-cyan: rgba(0, 255, 255, 0.6);
--glow-magenta: rgba(255, 0, 255, 0.6);
--glow-amber: rgba(255, 191, 0, 0.6);
--glow-danger: rgba(255, 50, 50, 0.6);
/* Surface colors */
--surface-dark: #0a0a0f;
--surface-elevated: #12121a;
--surface-card: #1a1a24;
/* Text colors */
--text-primary: rgba(255, 255, 255, 0.95);
--text-secondary: rgba(255, 255, 255, 0.7);
--text-muted: rgba(255, 255, 255, 0.5);
}1. Button States & Behaviors
Primary Button
Visual Specification
.btn-primary {
/* Default State */
background: linear-gradient(135deg, #00d4ff 0%, #0099cc 100%);
color: #0a0a0f;
padding: 14px 28px;
border-radius: 8px;
font-weight: 600;
font-size: 16px;
border: none;
cursor: pointer;
position: relative;
overflow: hidden;
/* Glow effect */
box-shadow:
0 0 0 0 transparent,
0 4px 20px rgba(0, 212, 255, 0.3);
/* Transition setup */
transition:
transform 150ms var(--ease-out),
box-shadow 200ms var(--ease-smooth),
background 200ms var(--ease-smooth);
}State Definitions
Default State
- Background: Gradient from cyan (#00d4ff) to darker cyan (#0099cc)
- Text: Dark (#0a0a0f) for contrast
- Shadow: Subtle glow (0 4px 20px rgba(0, 212, 255, 0.3))
- Scale: 1.0
Hover State
.btn-primary:hover {
transform: scale(1.03);
box-shadow:
0 0 30px rgba(0, 212, 255, 0.5),
0 8px 30px rgba(0, 212, 255, 0.4);
}- Timing: 200ms
- Easing: var(—ease-out)
- Enhanced glow effect
- Subtle scale increase (1.03)
Active/Pressed State
.btn-primary:active {
transform: scale(0.97);
box-shadow:
0 0 15px rgba(0, 212, 255, 0.4),
inset 0 2px 8px rgba(0, 0, 0, 0.3);
}- Timing: 100ms
- Easing: var(—ease-in)
- Scale decrease (0.97)
- Inset shadow for pressed feel
Focus State
.btn-primary:focus-visible {
outline: none;
box-shadow:
0 0 0 3px rgba(0, 212, 255, 0.3),
0 0 30px rgba(0, 212, 255, 0.5);
}- Visible focus ring for accessibility
- 3px outline with 30% opacity
- Maintains hover glow
Disabled State
.btn-primary:disabled {
background: linear-gradient(135deg, #3a3a4a 0%, #2a2a3a 100%);
color: rgba(255, 255, 255, 0.3);
cursor: not-allowed;
box-shadow: none;
transform: none;
}- Muted colors
- No hover effects
- Not-allowed cursor
Loading State
.btn-primary.loading {
color: transparent;
pointer-events: none;
}
.btn-primary.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
border: 2px solid transparent;
border-top-color: #0a0a0f;
border-radius: 50%;
animation: btn-spin 0.8s linear infinite;
}
@keyframes btn-spin {
to { transform: rotate(360deg); }
}- Text hidden, spinner visible
- Spinner: 20px, 2px border
- Animation: 0.8s linear infinite
Secondary Button
.btn-secondary {
background: transparent;
color: #00d4ff;
padding: 14px 28px;
border-radius: 8px;
font-weight: 600;
font-size: 16px;
border: 2px solid rgba(0, 212, 255, 0.5);
cursor: pointer;
transition:
background 200ms var(--ease-smooth),
border-color 200ms var(--ease-smooth),
box-shadow 200ms var(--ease-smooth),
transform 150ms var(--ease-out);
}
.btn-secondary:hover {
background: rgba(0, 212, 255, 0.1);
border-color: rgba(0, 212, 255, 0.8);
box-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
transform: scale(1.02);
}
.btn-secondary:active {
transform: scale(0.98);
background: rgba(0, 212, 255, 0.15);
}Ghost Button
.btn-ghost {
background: transparent;
color: rgba(255, 255, 255, 0.7);
padding: 12px 20px;
border-radius: 6px;
font-weight: 500;
font-size: 14px;
border: none;
cursor: pointer;
transition:
background 150ms var(--ease-smooth),
color 150ms var(--ease-smooth),
transform 100ms var(--ease-out);
}
.btn-ghost:hover {
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.95);
}
.btn-ghost:active {
transform: scale(0.98);
background: rgba(255, 255, 255, 0.12);
}Danger Button
.btn-danger {
background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
color: #ffffff;
padding: 14px 28px;
border-radius: 8px;
font-weight: 600;
border: none;
cursor: pointer;
box-shadow: 0 4px 20px rgba(255, 68, 68, 0.3);
transition:
transform 150ms var(--ease-out),
box-shadow 200ms var(--ease-smooth);
}
.btn-danger:hover {
transform: scale(1.03);
box-shadow:
0 0 30px rgba(255, 68, 68, 0.5),
0 8px 30px rgba(255, 68, 68, 0.4);
}
.btn-danger:active {
transform: scale(0.97);
}Icon Button
.btn-icon {
width: 44px;
height: 44px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
border: none;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition:
background 200ms var(--ease-smooth),
color 200ms var(--ease-smooth),
transform 150ms var(--ease-spring),
box-shadow 200ms var(--ease-smooth);
}
.btn-icon:hover {
background: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.95);
transform: scale(1.1);
}
.btn-icon:active {
transform: scale(0.95);
}
.btn-icon:focus-visible {
outline: none;
box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.5);
}Button Group Patterns
.btn-group {
display: flex;
gap: 12px;
}
.btn-group .btn {
/* Stagger animation for group */
animation: btn-enter 300ms var(--ease-out) backwards;
}
.btn-group .btn:nth-child(1) { animation-delay: 0ms; }
.btn-group .btn:nth-child(2) { animation-delay: 50ms; }
.btn-group .btn:nth-child(3) { animation-delay: 100ms; }
@keyframes btn-enter {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}2. Card Layouts & Interactions
Static Card (Information Display)
.card-static {
background: var(--surface-card);
border-radius: 12px;
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.06);
/* Subtle ambient glow */
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.03);
}Hover Card (Elevated on Interaction)
.card-hover {
background: var(--surface-card);
border-radius: 12px;
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.06);
cursor: default;
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.03);
transition:
transform 300ms var(--ease-out),
box-shadow 300ms var(--ease-smooth),
border-color 300ms var(--ease-smooth);
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow:
0 12px 40px rgba(0, 0, 0, 0.4),
0 0 30px rgba(0, 212, 255, 0.1);
border-color: rgba(0, 212, 255, 0.2);
}Clickable Card
.card-clickable {
background: var(--surface-card);
border-radius: 12px;
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.06);
cursor: pointer;
position: relative;
overflow: hidden;
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.3);
transition:
transform 200ms var(--ease-out),
box-shadow 300ms var(--ease-smooth),
border-color 200ms var(--ease-smooth);
}
.card-clickable:hover {
transform: translateY(-2px) scale(1.01);
box-shadow:
0 8px 30px rgba(0, 0, 0, 0.4),
0 0 40px rgba(0, 212, 255, 0.15);
border-color: rgba(0, 212, 255, 0.3);
}
.card-clickable:active {
transform: translateY(0) scale(0.99);
}
/* Ripple effect on click */
.card-clickable::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(0, 212, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 400ms var(--ease-out), height 400ms var(--ease-out), opacity 400ms;
opacity: 0;
}
.card-clickable:active::after {
width: 300px;
height: 300px;
opacity: 1;
transition: 0ms;
}Card with Content Reveal
.card-reveal {
background: var(--surface-card);
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.06);
}
.card-reveal .card-header {
padding: 20px 24px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-reveal .card-content {
max-height: 0;
overflow: hidden;
padding: 0 24px;
transition:
max-height 400ms var(--ease-out),
padding 400ms var(--ease-out),
opacity 300ms var(--ease-smooth);
opacity: 0;
}
.card-reveal.expanded .card-content {
max-height: 500px;
padding: 0 24px 24px;
opacity: 1;
}
.card-reveal .expand-icon {
transition: transform 300ms var(--ease-spring);
}
.card-reveal.expanded .expand-icon {
transform: rotate(180deg);
}Card Grid with Stagger Animation
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.card-grid .card {
animation: card-enter 500ms var(--ease-out) backwards;
}
/* Stagger delays for up to 12 cards */
.card-grid .card:nth-child(1) { animation-delay: 0ms; }
.card-grid .card:nth-child(2) { animation-delay: 50ms; }
.card-grid .card:nth-child(3) { animation-delay: 100ms; }
.card-grid .card:nth-child(4) { animation-delay: 150ms; }
.card-grid .card:nth-child(5) { animation-delay: 200ms; }
.card-grid .card:nth-child(6) { animation-delay: 250ms; }
.card-grid .card:nth-child(7) { animation-delay: 300ms; }
.card-grid .card:nth-child(8) { animation-delay: 350ms; }
.card-grid .card:nth-child(9) { animation-delay: 400ms; }
.card-grid .card:nth-child(10) { animation-delay: 450ms; }
.card-grid .card:nth-child(11) { animation-delay: 500ms; }
.card-grid .card:nth-child(12) { animation-delay: 550ms; }
@keyframes card-enter {
from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}Day Card (Specific to 40 Days)
.day-card {
background: var(--surface-card);
border-radius: 16px;
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.06);
cursor: pointer;
position: relative;
overflow: hidden;
transition:
transform 300ms var(--ease-out),
box-shadow 400ms var(--ease-smooth),
border-color 300ms var(--ease-smooth);
}
.day-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #00d4ff, #ff00ff);
opacity: 0;
transition: opacity 300ms var(--ease-smooth);
}
.day-card:hover::before {
opacity: 1;
}
.day-card:hover {
transform: translateY(-6px);
box-shadow:
0 16px 50px rgba(0, 0, 0, 0.5),
0 0 50px rgba(0, 212, 255, 0.15);
border-color: rgba(0, 212, 255, 0.25);
}
.day-card.locked {
opacity: 0.5;
cursor: not-allowed;
}
.day-card.locked:hover {
transform: none;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.day-card.completed::after {
content: '✓';
position: absolute;
top: 16px;
right: 16px;
width: 28px;
height: 28px;
background: linear-gradient(135deg, #00d4ff, #0099cc);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #0a0a0f;
animation: check-pop 400ms var(--ease-spring);
}
@keyframes check-pop {
0% { transform: scale(0); }
70% { transform: scale(1.2); }
100% { transform: scale(1); }
}3. Slider/Range Behaviors
Custom Range Slider
.range-slider {
position: relative;
width: 100%;
height: 40px;
display: flex;
align-items: center;
}
.range-slider input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
background: transparent;
cursor: pointer;
position: relative;
z-index: 2;
}
/* Track */
.range-slider input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
transition: background 200ms var(--ease-smooth);
}
.range-slider:hover input[type="range"]::-webkit-slider-runnable-track {
background: rgba(255, 255, 255, 0.15);
}
/* Thumb */
.range-slider input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: linear-gradient(135deg, #00d4ff, #0099cc);
border-radius: 50%;
margin-top: -7px;
cursor: grab;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.3),
0 0 0 0 rgba(0, 212, 255, 0.4);
transition:
transform 150ms var(--ease-spring),
box-shadow 200ms var(--ease-smooth);
}
.range-slider input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.4),
0 0 20px rgba(0, 212, 255, 0.6);
}
.range-slider input[type="range"]::-webkit-slider-thumb:active {
cursor: grabbing;
transform: scale(1.1);
}
/* Firefox styles */
.range-slider input[type="range"]::-moz-range-track {
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
.range-slider input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: linear-gradient(135deg, #00d4ff, #0099cc);
border-radius: 50%;
border: none;
cursor: grab;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
transition: transform 150ms var(--ease-spring);
}Range with Fill Animation
.range-slider-fill {
position: relative;
}
.range-slider-fill .track-fill {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 6px;
background: linear-gradient(90deg, #00d4ff, #0099cc);
border-radius: 3px;
pointer-events: none;
transition: width 50ms linear;
}
.range-slider-fill .track-glow {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 6px;
background: linear-gradient(90deg, #00d4ff, #0099cc);
border-radius: 3px;
filter: blur(8px);
opacity: 0.5;
pointer-events: none;
transition: width 50ms linear;
}Range with Value Display
.range-with-value {
display: flex;
align-items: center;
gap: 16px;
}
.range-with-value .value-display {
min-width: 60px;
padding: 8px 12px;
background: rgba(0, 212, 255, 0.1);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 6px;
color: #00d4ff;
font-weight: 600;
text-align: center;
font-variant-numeric: tabular-nums;
transition:
background 200ms var(--ease-smooth),
transform 150ms var(--ease-spring);
}
.range-with-value input[type="range"]:active ~ .value-display,
.range-with-value:hover .value-display {
background: rgba(0, 212, 255, 0.15);
transform: scale(1.05);
}Stepped Range Slider
.range-stepped {
position: relative;
}
.range-stepped .step-marks {
position: absolute;
top: 24px;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: 0 10px;
}
.range-stepped .step-mark {
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
transition:
background 200ms var(--ease-smooth),
transform 200ms var(--ease-spring);
}
.range-stepped .step-mark.active {
background: #00d4ff;
transform: scale(1.5);
}
.range-stepped .step-labels {
display: flex;
justify-content: space-between;
margin-top: 16px;
padding: 0 6px;
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
}Touch-Optimized Range
@media (pointer: coarse) {
.range-slider input[type="range"]::-webkit-slider-thumb {
width: 28px;
height: 28px;
margin-top: -11px;
}
.range-slider input[type="range"] {
height: 44px;
}
.range-slider input[type="range"]::-webkit-slider-runnable-track {
height: 8px;
}
}4. Transition Patterns
Page/Screen Transitions
Fade Transition
.page-fade-enter {
opacity: 0;
}
.page-fade-enter-active {
opacity: 1;
transition: opacity 400ms var(--ease-smooth);
}
.page-fade-exit {
opacity: 1;
}
.page-fade-exit-active {
opacity: 0;
transition: opacity 300ms var(--ease-smooth);
}Slide Transition
.page-slide-enter {
opacity: 0;
transform: translateX(30px);
}
.page-slide-enter-active {
opacity: 1;
transform: translateX(0);
transition:
opacity 400ms var(--ease-out),
transform 400ms var(--ease-out);
}
.page-slide-exit {
opacity: 1;
transform: translateX(0);
}
.page-slide-exit-active {
opacity: 0;
transform: translateX(-30px);
transition:
opacity 300ms var(--ease-in),
transform 300ms var(--ease-in);
}Scale Transition (For Modals/Overlays)
.page-scale-enter {
opacity: 0;
transform: scale(0.95);
}
.page-scale-enter-active {
opacity: 1;
transform: scale(1);
transition:
opacity 300ms var(--ease-out),
transform 400ms var(--ease-spring);
}
.page-scale-exit {
opacity: 1;
transform: scale(1);
}
.page-scale-exit-active {
opacity: 0;
transform: scale(0.95);
transition:
opacity 200ms var(--ease-in),
transform 300ms var(--ease-in);
}Content Replacement Animation
.content-replace {
position: relative;
overflow: hidden;
}
.content-replace .content-old {
animation: content-out 300ms var(--ease-in) forwards;
}
.content-replace .content-new {
animation: content-in 400ms var(--ease-out) 200ms backwards;
}
@keyframes content-out {
to {
opacity: 0;
transform: translateY(-20px);
}
}
@keyframes content-in {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}Stagger Patterns for Lists
/* Standard stagger */
.stagger-list .item {
animation: stagger-enter 400ms var(--ease-out) backwards;
}
.stagger-list .item:nth-child(1) { animation-delay: 0ms; }
.stagger-list .item:nth-child(2) { animation-delay: 50ms; }
.stagger-list .item:nth-child(3) { animation-delay: 100ms; }
.stagger-list .item:nth-child(4) { animation-delay: 150ms; }
.stagger-list .item:nth-child(5) { animation-delay: 200ms; }
@keyframes stagger-enter {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Cascade stagger (for grid layouts) */
.cascade-grid .item {
animation: cascade-enter 500ms var(--ease-out) backwards;
}
.cascade-grid .item:nth-child(1) { animation-delay: 0ms; }
.cascade-grid .item:nth-child(2) { animation-delay: 80ms; }
.cascade-grid .item:nth-child(3) { animation-delay: 160ms; }
.cascade-grid .item:nth-child(4) { animation-delay: 240ms; }
@keyframes cascade-enter {
from {
opacity: 0;
transform: translateY(40px) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}Exit Animations
/* Slide out left */
.exit-left {
animation: exit-left 300ms var(--ease-in) forwards;
}
@keyframes exit-left {
to {
opacity: 0;
transform: translateX(-100%);
}
}
/* Slide out right */
.exit-right {
animation: exit-right 300ms var(--ease-in) forwards;
}
@keyframes exit-right {
to {
opacity: 0;
transform: translateX(100%);
}
}
/* Scale out */
.exit-scale {
animation: exit-scale 250ms var(--ease-in) forwards;
}
@keyframes exit-scale {
to {
opacity: 0;
transform: scale(0.8);
}
}
/* Fade out with blur */
.exit-blur {
animation: exit-blur 400ms var(--ease-smooth) forwards;
}
@keyframes exit-blur {
to {
opacity: 0;
filter: blur(10px);
}
}5. Progress Indicators
Linear Progress Bar (Determinate)
.progress-linear {
width: 100%;
height: 8px;
background: rgba(255, 255, 255, 0.08);
border-radius: 4px;
overflow: hidden;
position: relative;
}
.progress-linear .progress-fill {
height: 100%;
background: linear-gradient(90deg, #00d4ff, #0099cc);
border-radius: 4px;
width: 0%;
transition: width 500ms var(--ease-out);
}
.progress-linear .progress-glow {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: linear-gradient(90deg, #00d4ff, #0099cc);
border-radius: 4px;
filter: blur(8px);
opacity: 0.5;
width: 0%;
transition: width 500ms var(--ease-out);
}
/* Striped animation for active state */
.progress-linear.active .progress-fill {
background: linear-gradient(
90deg,
#00d4ff 0%,
#0099cc 50%,
#00d4ff 100%
);
background-size: 200% 100%;
animation: progress-stripes 1.5s linear infinite;
}
@keyframes progress-stripes {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Linear Progress (Indeterminate)
.progress-linear-indeterminate {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.08);
border-radius: 2px;
overflow: hidden;
position: relative;
}
.progress-linear-indeterminate::after {
content: '';
position: absolute;
top: 0;
left: -40%;
width: 40%;
height: 100%;
background: linear-gradient(90deg, transparent, #00d4ff, transparent);
border-radius: 2px;
animation: indeterminate-slide 1.5s var(--ease-in-out) infinite;
}
@keyframes indeterminate-slide {
0% { left: -40%; }
50% { left: 100%; }
100% { left: 100%; }
}Circular Progress/Spinner
.progress-circular {
width: 48px;
height: 48px;
}
.progress-circular svg {
transform: rotate(-90deg);
}
.progress-circular .track {
fill: none;
stroke: rgba(255, 255, 255, 0.08);
stroke-width: 4;
}
.progress-circular .fill {
fill: none;
stroke: url(#progress-gradient);
stroke-width: 4;
stroke-linecap: round;
stroke-dasharray: 126;
stroke-dashoffset: 126;
transition: stroke-dashoffset 500ms var(--ease-out);
}
/* Indeterminate spinner */
.spinner {
width: 40px;
height: 40px;
animation: spinner-rotate 1s linear infinite;
}
.spinner circle {
fill: none;
stroke: #00d4ff;
stroke-width: 4;
stroke-linecap: round;
stroke-dasharray: 80;
stroke-dashoffset: 60;
}
@keyframes spinner-rotate {
to { transform: rotate(360deg); }
}Step Indicators
.step-indicator {
display: flex;
align-items: center;
gap: 8px;
}
.step {
display: flex;
align-items: center;
gap: 8px;
}
.step-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.15);
border: 2px solid transparent;
transition:
background 300ms var(--ease-smooth),
border-color 300ms var(--ease-smooth),
transform 300ms var(--ease-spring);
}
.step.active .step-dot {
background: #00d4ff;
box-shadow: 0 0 15px rgba(0, 212, 255, 0.5);
transform: scale(1.2);
}
.step.completed .step-dot {
background: #00d4ff;
}
.step-line {
width: 40px;
height: 2px;
background: rgba(255, 255, 255, 0.1);
border-radius: 1px;
overflow: hidden;
position: relative;
}
.step-line::after {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 0%;
background: linear-gradient(90deg, #00d4ff, #0099cc);
transition: width 400ms var(--ease-out);
}
.step.completed .step-line::after {
width: 100%;
}40-Day Journey Progress
.journey-progress {
padding: 20px;
background: var(--surface-card);
border-radius: 16px;
}
.journey-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.journey-title {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
}
.journey-count {
font-size: 18px;
font-weight: 700;
color: #00d4ff;
}
.journey-bar {
height: 12px;
background: rgba(255, 255, 255, 0.06);
border-radius: 6px;
overflow: hidden;
position: relative;
}
.journey-fill {
height: 100%;
background: linear-gradient(90deg, #00d4ff, #ff00ff, #ffbf00);
background-size: 200% 100%;
border-radius: 6px;
width: 0%;
transition: width 800ms var(--ease-out);
animation: journey-shimmer 3s linear infinite;
}
@keyframes journey-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.journey-days {
display: flex;
justify-content: space-between;
margin-top: 12px;
padding: 0 4px;
}
.journey-day {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
transition:
background 300ms var(--ease-smooth),
transform 300ms var(--ease-spring),
box-shadow 300ms var(--ease-smooth);
}
.journey-day.completed {
background: #00d4ff;
box-shadow: 0 0 8px rgba(0, 212, 255, 0.5);
}
.journey-day.current {
background: #ffbf00;
transform: scale(1.5);
box-shadow: 0 0 12px rgba(255, 191, 0, 0.6);
}Micro-Progress (Individual Experience)
.micro-progress {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 3px;
background: rgba(255, 255, 255, 0.05);
z-index: 1000;
}
.micro-progress .fill {
height: 100%;
background: linear-gradient(90deg, #00d4ff, #0099cc);
width: 0%;
transition: width 100ms linear;
}
.micro-progress .glow {
position: absolute;
top: 0;
right: 0;
width: 100px;
height: 100%;
background: linear-gradient(90deg, transparent, #00d4ff);
filter: blur(4px);
opacity: 0.8;
transform: translateX(-100%);
animation: micro-glow 2s var(--ease-in-out) infinite;
}
@keyframes micro-glow {
0%, 100% { transform: translateX(-100%); opacity: 0.8; }
50% { transform: translateX(0%); opacity: 0.4; }
}6. Modal & Overlay Behaviors
Modal Container
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition:
opacity 300ms var(--ease-smooth),
visibility 300ms var(--ease-smooth);
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}Modal Content Animation
.modal-content {
background: var(--surface-elevated);
border-radius: 20px;
padding: 32px;
max-width: 500px;
width: 100%;
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow:
0 25px 80px rgba(0, 0, 0, 0.6),
0 0 60px rgba(0, 212, 255, 0.1);
transform: scale(0.9) translateY(20px);
opacity: 0;
transition:
transform 400ms var(--ease-spring),
opacity 300ms var(--ease-out);
}
.modal-overlay.active .modal-content {
transform: scale(1) translateY(0);
opacity: 1;
}
/* Exit animation */
.modal-overlay.closing {
opacity: 0;
visibility: hidden;
}
.modal-overlay.closing .modal-content {
transform: scale(0.95) translateY(10px);
opacity: 0;
}Modal Header & Close Button
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.modal-title {
font-size: 24px;
font-weight: 700;
color: var(--text-primary);
}
.modal-close {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
border: none;
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
transition:
background 200ms var(--ease-smooth),
color 200ms var(--ease-smooth),
transform 150ms var(--ease-spring);
}
.modal-close:hover {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.9);
transform: rotate(90deg);
}Backdrop Interactions
/* Click outside to close */
.modal-overlay {
cursor: pointer;
}
.modal-content {
cursor: default;
}
/* Prevent body scroll when modal is open */
body.modal-open {
overflow: hidden;
padding-right: var(--scrollbar-width, 0px);
}Focus Trap Behavior
// Focus trap implementation pattern
function trapFocus(element) {
const focusableElements = element.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
element.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
} else if (!e.shiftKey && document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
if (e.key === 'Escape') {
closeModal();
}
});
// Focus first element on open
firstFocusable?.focus();
}Toast/Notification Overlay
.toast-container {
position: fixed;
bottom: 24px;
right: 24px;
display: flex;
flex-direction: column;
gap: 12px;
z-index: 1100;
}
.toast {
background: var(--surface-elevated);
border-radius: 12px;
padding: 16px 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
min-width: 300px;
max-width: 400px;
display: flex;
align-items: center;
gap: 12px;
box-shadow:
0 10px 40px rgba(0, 0, 0, 0.4),
0 0 30px rgba(0, 212, 255, 0.1);
animation: toast-enter 400ms var(--ease-spring);
}
.toast.exiting {
animation: toast-exit 300ms var(--ease-in) forwards;
}
@keyframes toast-enter {
from {
opacity: 0;
transform: translateX(100%) scale(0.9);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes toast-exit {
to {
opacity: 0;
transform: translateX(100%) scale(0.9);
}
}
.toast-icon {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.toast-icon.success {
background: rgba(0, 255, 136, 0.2);
color: #00ff88;
}
.toast-icon.error {
background: rgba(255, 68, 68, 0.2);
color: #ff4444;
}7. Form Input Interactions
Text Input
.input-group {
margin-bottom: 20px;
}
.input-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
font-weight: 500;
transition: color 200ms var(--ease-smooth);
}
.input-group:focus-within .input-label {
color: #00d4ff;
}
.text-input {
width: 100%;
padding: 14px 16px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: var(--text-primary);
font-size: 16px;
transition:
background 200ms var(--ease-smooth),
border-color 200ms var(--ease-smooth),
box-shadow 200ms var(--ease-smooth);
}
.text-input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.text-input:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.15);
}
.text-input:focus {
outline: none;
background: rgba(255, 255, 255, 0.05);
border-color: #00d4ff;
box-shadow:
0 0 0 3px rgba(0, 212, 255, 0.15),
0 0 20px rgba(0, 212, 255, 0.1);
}Input Validation States
/* Error state */
.text-input.error {
border-color: #ff4444;
background: rgba(255, 68, 68, 0.05);
}
.text-input.error:focus {
box-shadow:
0 0 0 3px rgba(255, 68, 68, 0.15),
0 0 20px rgba(255, 68, 68, 0.1);
}
/* Success state */
.text-input.success {
border-color: #00ff88;
background: rgba(0, 255, 136, 0.05);
}
.text-input.success:focus {
box-shadow:
0 0 0 3px rgba(0, 255, 136, 0.15),
0 0 20px rgba(0, 255, 136, 0.1);
}Error Message Animation
.error-message {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
font-size: 13px;
color: #ff4444;
animation: error-shake 400ms var(--ease-out);
}
@keyframes error-shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-5px); }
40% { transform: translateX(5px); }
60% { transform: translateX(-3px); }
80% { transform: translateX(3px); }
}
.error-icon {
width: 16px;
height: 16px;
animation: error-icon-pop 300ms var(--ease-spring);
}
@keyframes error-icon-pop {
0% { transform: scale(0); }
70% { transform: scale(1.3); }
100% { transform: scale(1); }
}Success Confirmation
.success-checkmark {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #00ff88, #00cc6a);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
animation: success-pop 500ms var(--ease-spring);
}
@keyframes success-pop {
0% { transform: scale(0); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.success-checkmark svg {
width: 32px;
height: 32px;
stroke: #0a0a0f;
stroke-width: 3;
fill: none;
stroke-dasharray: 30;
stroke-dashoffset: 30;
animation: check-draw 400ms var(--ease-out) 200ms forwards;
}
@keyframes check-draw {
to { stroke-dashoffset: 0; }
}
.success-message {
text-align: center;
animation: success-fade 400ms var(--ease-out) 300ms backwards;
}
@keyframes success-fade {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}Textarea
.textarea {
width: 100%;
min-height: 120px;
padding: 14px 16px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: var(--text-primary);
font-size: 16px;
resize: vertical;
font-family: inherit;
line-height: 1.6;
transition:
background 200ms var(--ease-smooth),
border-color 200ms var(--ease-smooth),
box-shadow 200ms var(--ease-smooth);
}
.textarea:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.15);
}
.textarea:focus {
outline: none;
background: rgba(255, 255, 255, 0.05);
border-color: #00d4ff;
box-shadow:
0 0 0 3px rgba(0, 212, 255, 0.15),
0 0 20px rgba(0, 212, 255, 0.1);
}8. Reveal & Surprise Patterns
Content Reveal Timing
/* Staggered text reveal */
.reveal-text .word {
display: inline-block;
opacity: 0;
transform: translateY(20px);
animation: word-reveal 500ms var(--ease-out) forwards;
}
.reveal-text .word:nth-child(1) { animation-delay: 0ms; }
.reveal-text .word:nth-child(2) { animation-delay: 80ms; }
.reveal-text .word:nth-child(3) { animation-delay: 160ms; }
.reveal-text .word:nth-child(4) { animation-delay: 240ms; }
.reveal-text .word:nth-child(5) { animation-delay: 320ms; }
@keyframes word-reveal {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Character-by-character reveal */
.reveal-char .char {
display: inline-block;
opacity: 0;
animation: char-reveal 400ms var(--ease-out) forwards;
}
@keyframes char-reveal {
0% {
opacity: 0;
transform: translateY(10px) rotateX(-90deg);
}
100% {
opacity: 1;
transform: translateY(0) rotateX(0);
}
}Dramatic Entrance Animations
/* Scale and glow entrance */
.entrance-dramatic {
opacity: 0;
transform: scale(0.8);
filter: blur(10px);
animation: entrance-dramatic 800ms var(--ease-out) forwards;
}
@keyframes entrance-dramatic {
0% {
opacity: 0;
transform: scale(0.8);
filter: blur(10px);
}
50% {
opacity: 1;
transform: scale(1.02);
filter: blur(0);
}
100% {
opacity: 1;
transform: scale(1);
filter: blur(0);
}
}
/* Slide from darkness */
.entrance-emerge {
opacity: 0;
transform: translateY(60px);
animation: entrance-emerge 700ms var(--ease-out) forwards;
}
@keyframes entrance-emerge {
0% {
opacity: 0;
transform: translateY(60px);
box-shadow: 0 0 0 rgba(0, 212, 255, 0);
}
60% {
opacity: 1;
transform: translateY(-5px);
box-shadow: 0 0 60px rgba(0, 212, 255, 0.3);
}
100% {
opacity: 1;
transform: translateY(0);
box-shadow: 0 0 30px rgba(0, 212, 255, 0.15);
}
}
/* Ripple reveal */
.entrance-ripple {
position: relative;
overflow: hidden;
}
.entrance-ripple::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(0, 212, 255, 0.2);
border-radius: 50%;
transform: translate(-50%, -50%);
animation: ripple-expand 1000ms var(--ease-out) forwards;
}
@keyframes ripple-expand {
to {
width: 300%;
height: 300%;
opacity: 0;
}
}Particle/Confetti Effects
/* CSS-only confetti burst */
.confetti-container {
position: absolute;
pointer-events: none;
}
.confetti-piece {
position: absolute;
width: 8px;
height: 8px;
opacity: 0;
}
.confetti-piece.square {
border-radius: 2px;
}
.confetti-piece.circle {
border-radius: 50%;
}
.confetti-piece.animate {
animation: confetti-fall 2000ms var(--ease-out) forwards;
}
@keyframes confetti-fall {
0% {
opacity: 1;
transform:
translate(0, 0)
rotate(0deg)
scale(1);
}
100% {
opacity: 0;
transform:
translate(var(--tx), var(--ty))
rotate(var(--rot))
scale(0.5);
}
}
/* Glow burst effect */
.glow-burst {
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: radial-gradient(circle, rgba(0, 212, 255, 0.6), transparent);
transform: translate(-50%, -50%);
pointer-events: none;
}
.glow-burst.animate {
animation: glow-burst-expand 800ms var(--ease-out) forwards;
}
@keyframes glow-burst-expand {
0% {
width: 0;
height: 0;
opacity: 1;
}
100% {
width: 400px;
height: 400px;
opacity: 0;
}
}Sound Cue Visual Indicators
/* Visual indicator for sound cue */
.sound-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
background: rgba(0, 212, 255, 0.1);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 20px;
color: #00d4ff;
font-size: 13px;
}
.sound-indicator .icon {
animation: sound-pulse 1.5s var(--ease-in-out) infinite;
}
@keyframes sound-pulse {
0%, 100% {
opacity: 0.6;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.1);
}
}
/* Audio wave visualization */
.audio-waves {
display: flex;
align-items: center;
gap: 3px;
height: 20px;
}
.audio-wave {
width: 3px;
background: #00d4ff;
border-radius: 2px;
animation: wave-dance 1s var(--ease-in-out) infinite;
}
.audio-wave:nth-child(1) { height: 8px; animation-delay: 0ms; }
.audio-wave:nth-child(2) { height: 14px; animation-delay: 100ms; }
.audio-wave:nth-child(3) { height: 10px; animation-delay: 200ms; }
.audio-wave:nth-child(4) { height: 18px; animation-delay: 300ms; }
.audio-wave:nth-child(5) { height: 12px; animation-delay: 400ms; }
@keyframes wave-dance {
0%, 100% { transform: scaleY(1); opacity: 0.6; }
50% { transform: scaleY(1.3); opacity: 1; }
}9. Micro-interactions
Toggle Switch
.toggle-switch {
position: relative;
width: 52px;
height: 28px;
background: rgba(255, 255, 255, 0.1);
border-radius: 14px;
cursor: pointer;
transition: background 300ms var(--ease-smooth);
}
.toggle-switch.active {
background: linear-gradient(135deg, #00d4ff, #0099cc);
}
.toggle-switch .thumb {
position: absolute;
top: 3px;
left: 3px;
width: 22px;
height: 22px;
background: white;
border-radius: 50%;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
transition: transform 300ms var(--ease-spring);
}
.toggle-switch.active .thumb {
transform: translateX(24px);
}
/* Glow effect when active */
.toggle-switch.active {
box-shadow: 0 0 20px rgba(0, 212, 255, 0.4);
}Checkbox
.checkbox {
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
}
.checkbox-input {
display: none;
}
.checkbox-box {
width: 22px;
height: 22px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
transition:
background 200ms var(--ease-smooth),
border-color 200ms var(--ease-smooth),
box-shadow 200ms var(--ease-smooth);
}
.checkbox:hover .checkbox-box {
border-color: rgba(255, 255, 255, 0.5);
}
.checkbox-input:checked + .checkbox-box {
background: linear-gradient(135deg, #00d4ff, #0099cc);
border-color: transparent;
box-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
}
.checkbox-box svg {
width: 14px;
height: 14px;
stroke: #0a0a0f;
stroke-width: 3;
fill: none;
stroke-dasharray: 20;
stroke-dashoffset: 20;
transition: stroke-dashoffset 200ms var(--ease-out);
}
.checkbox-input:checked + .checkbox-box svg {
stroke-dashoffset: 0;
}
/* Check animation */
.checkbox-input:checked + .checkbox-box {
animation: checkbox-pop 300ms var(--ease-spring);
}
@keyframes checkbox-pop {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}Radio Button
.radio {
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
}
.radio-input {
display: none;
}
.radio-circle {
width: 22px;
height: 22px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: border-color 200ms var(--ease-smooth);
}
.radio:hover .radio-circle {
border-color: rgba(255, 255, 255, 0.5);
}
.radio-dot {
width: 10px;
height: 10px;
background: #00d4ff;
border-radius: 50%;
transform: scale(0);
box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
transition: transform 200ms var(--ease-spring);
}
.radio-input:checked + .radio-circle {
border-color: #00d4ff;
}
.radio-input:checked + .radio-circle .radio-dot {
transform: scale(1);
}Tooltip
.tooltip-container {
position: relative;
display: inline-block;
}
.tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%) translateY(5px);
padding: 10px 14px;
background: var(--surface-elevated);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
font-size: 13px;
color: var(--text-primary);
white-space: nowrap;
opacity: 0;
visibility: hidden;
pointer-events: none;
z-index: 100;
box-shadow:
0 8px 30px rgba(0, 0, 0, 0.4),
0 0 20px rgba(0, 212, 255, 0.1);
transition:
opacity 200ms var(--ease-smooth),
visibility 200ms var(--ease-smooth),
transform 200ms var(--ease-out);
}
.tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: var(--surface-elevated);
}
.tooltip-container:hover .tooltip {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(0);
}
/* Bottom tooltip variant */
.tooltip.bottom {
bottom: auto;
top: calc(100% + 10px);
transform: translateX(-50%) translateY(-5px);
}
.tooltip.bottom::after {
top: auto;
bottom: 100%;
border-top-color: transparent;
border-bottom-color: var(--surface-elevated);
}
.tooltip-container:hover .tooltip.bottom {
transform: translateX(-50%) translateY(0);
}10. Gesture Patterns (Mobile)
Swipe Behaviors
/* Swipeable container */
.swipe-container {
overflow: hidden;
touch-action: pan-y;
}
.swipe-item {
transform: translateX(0);
transition: transform 300ms var(--ease-out);
}
.swipe-item.swiped-left {
transform: translateX(-80px);
}
.swipe-item.swiped-right {
transform: translateX(80px);
}
/* Swipe actions revealed */
.swipe-actions {
position: absolute;
top: 0;
bottom: 0;
display: flex;
align-items: center;
opacity: 0;
transition: opacity 200ms var(--ease-smooth);
}
.swipe-actions.left {
right: 0;
}
.swipe-actions.right {
left: 0;
}
.swipe-item.swiped-left ~ .swipe-actions.left,
.swipe-item.swiped-right ~ .swipe-actions.right {
opacity: 1;
}Tap Feedback
/* Ripple tap effect */
.tap-ripple {
position: relative;
overflow: hidden;
}
.tap-ripple::after {
content: '';
position: absolute;
top: var(--tap-y, 50%);
left: var(--tap-x, 50%);
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
.tap-ripple.tapped::after {
animation: tap-ripple-effect 400ms var(--ease-out);
}
@keyframes tap-ripple-effect {
to {
width: 300px;
height: 300px;
opacity: 0;
}
}
/* Scale tap feedback */
.tap-scale {
transition: transform 100ms var(--ease-out);
}
.tap-scale:active {
transform: scale(0.97);
}Pull-to-Refresh
.ptr-container {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.ptr-indicator {
height: 0;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
transition: height 200ms var(--ease-smooth);
}
.ptr-indicator.pulling {
height: 60px;
}
.ptr-indicator.refreshing {
height: 60px;
}
.ptr-spinner {
width: 24px;
height: 24px;
opacity: 0;
transform: scale(0.5);
transition:
opacity 200ms var(--ease-smooth),
transform 200ms var(--ease-spring);
}
.ptr-indicator.pulling .ptr-spinner,
.ptr-indicator.refreshing .ptr-spinner {
opacity: 1;
transform: scale(1);
}
.ptr-indicator.refreshing .ptr-spinner {
animation: ptr-spin 1s linear infinite;
}
@keyframes ptr-spin {
to { transform: rotate(360deg); }
}
/* Arrow indicator */
.ptr-arrow {
width: 20px;
height: 20px;
transition: transform 200ms var(--ease-spring);
}
.ptr-indicator.pulling .ptr-arrow {
transform: rotate(180deg);
}Touch-Optimized Targets
/* Minimum touch target size */
.touch-target {
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
/* Increased spacing for touch */
@media (pointer: coarse) {
.button-group {
gap: 16px;
}
.card-grid {
gap: 24px;
}
.form-input {
padding: 16px 18px;
font-size: 16px; /* Prevents zoom on iOS */
}
.checkbox-box,
.radio-circle {
width: 26px;
height: 26px;
}
.toggle-switch {
width: 56px;
height: 32px;
}
.toggle-switch .thumb {
width: 26px;
height: 26px;
}
.toggle-switch.active .thumb {
transform: translateX(24px);
}
}Haptic Feedback Visual Indicators
/* Visual feedback for haptic events */
.haptic-pulse {
animation: haptic-visual 100ms var(--ease-out);
}
@keyframes haptic-visual {
0% { transform: scale(1); }
50% { transform: scale(0.98); }
100% { transform: scale(1); }
}
/* Success haptic visual */
.haptic-success {
animation: haptic-success-visual 300ms var(--ease-spring);
}
@keyframes haptic-success-visual {
0% { transform: scale(1); }
30% { transform: scale(1.02); }
60% { transform: scale(0.99); }
100% { transform: scale(1); }
}Implementation Notes
Performance Considerations
- Use
transformandopacityfor animations - they don’t trigger layout recalculations - Apply
will-changesparingly to elements that will animate:.will-animate { will-change: transform, opacity; } - Remove
will-changeafter animation completes to free up resources - Use CSS animations over JavaScript animations when possible
- Implement
prefers-reduced-motionsupport:@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } }
Accessibility Requirements
- All interactive elements must have visible focus states
- Color alone should not convey information - use icons, text, or patterns
- Animation should not be required to understand content
- Respect
prefers-reduced-motionuser preference - Maintain keyboard navigation for all interactive elements
Browser Support
- Modern browsers (Chrome 80+, Firefox 75+, Safari 13+, Edge 80+)
- Use autoprefixer for vendor prefixes
- Provide fallbacks for older browsers where necessary
Quick Reference: Timing Values
| Animation Type | Duration | Easing |
|---|---|---|
| Button hover | 200ms | ease-out |
| Button active | 100ms | ease-in |
| Card hover | 300ms | ease-out |
| Modal open | 400ms | spring |
| Modal close | 300ms | ease-in |
| Page transition | 400ms | ease-out |
| List stagger | 50ms delay per item | ease-out |
| Progress bar | 500ms | ease-out |
| Toast enter | 400ms | spring |
| Toast exit | 300ms | ease-in |
| Tooltip | 200ms | ease-out |
| Checkbox check | 200ms | spring |
| Toggle switch | 300ms | spring |
| Content reveal | 400-600ms | ease-out |
| Confetti burst | 2000ms | ease-out |
| Glow pulse | 1500ms | ease-in-out |
Document Version: 1.0 Last Updated: 2024 For: 40 Days of Existential Threats Interactive Experience