Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a72be6317b | ||
|
|
8aac3f10e4 | ||
|
|
f8c8cffb4d | ||
|
|
2ff0e9202f |
No files matched your search
@@ -1,315 +0,0 @@
|
|||||||
---
|
|
||||||
name: motion-design
|
|
||||||
description: >
|
|
||||||
Applies motion design principles to create emotionally-driven, technically sound animations and transitions.
|
|
||||||
Provides timing, easing, choreography, and Disney animation principles adapted for UI.
|
|
||||||
Use when creating animations, transitions, micro-interactions, loading states, page transitions,
|
|
||||||
scroll-triggered effects, or any motion work. Works with CSS, Framer Motion, GSAP, Lottie, Spring,
|
|
||||||
or any animation system.
|
|
||||||
license: MIT
|
|
||||||
metadata:
|
|
||||||
author: LottieFiles
|
|
||||||
version: "1.0.0"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Motion Design Skill
|
|
||||||
|
|
||||||
## When to Apply
|
|
||||||
|
|
||||||
Use this skill when:
|
|
||||||
- Creating UI animations (buttons, cards, modals, page transitions)
|
|
||||||
- Designing micro-interactions and feedback animations
|
|
||||||
- Building loading, success, or error states
|
|
||||||
- Animating illustrations or decorative elements
|
|
||||||
- Planning scroll-triggered or progress-based animations
|
|
||||||
- Establishing brand motion identity
|
|
||||||
- Choreographing multi-element sequences
|
|
||||||
|
|
||||||
**Decision tree:**
|
|
||||||
1. Does it serve a functional purpose (feedback, guidance)? → Timing rules for responsiveness
|
|
||||||
2. Does it express brand personality? → Motion Personality archetypes
|
|
||||||
3. Does it tell a story or guide attention? → Disney principles + choreography
|
|
||||||
4. Is this a complex multi-element scene? → 1/3 Rule + stagger patterns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Reference: 8-Step Checklist
|
|
||||||
|
|
||||||
Before creating any animation:
|
|
||||||
|
|
||||||
1. **Emotional target?** — joy, calm, urgency, elegance
|
|
||||||
2. **Motion Personality?** — Playful, Premium, Corporate, Energetic
|
|
||||||
3. **Primary property?** — position, scale, rotation, opacity
|
|
||||||
4. **Duration?** — see duration table below
|
|
||||||
5. **Easing family?** — entrance=decelerate, exit=accelerate
|
|
||||||
6. **Hero element?** — apply staging principles
|
|
||||||
7. **Secondary + ambient layers?** — add richness
|
|
||||||
8. **1/3 rules?** — motion distance, simultaneous elements
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Three Pillars (CRITICAL)
|
|
||||||
|
|
||||||
Every animation must satisfy three pillars before any technical decisions:
|
|
||||||
|
|
||||||
| Pillar | Question | Drives |
|
|
||||||
|--------|----------|--------|
|
|
||||||
| **Emotional Intent** | What should the viewer FEEL? | Easing, timing, amplitude |
|
|
||||||
| **Visual Narrative** | What's the micro-story? | Setup → Action → Resolution |
|
|
||||||
| **Motion Craft** | How do we make it believable? | Physics, secondary motion, paths |
|
|
||||||
|
|
||||||
**Three motion layers** (flat animation = missing layers):
|
|
||||||
- **Primary**: Main action the viewer follows
|
|
||||||
- **Secondary**: Supporting richness (shadows, icons shifting)
|
|
||||||
- **Ambient**: Background life (gradients, subtle pulses)
|
|
||||||
|
|
||||||
> Deep dive: [director/core-philosophy.md](director/core-philosophy.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Motion Personality
|
|
||||||
|
|
||||||
Select ONE archetype per project. Apply consistently.
|
|
||||||
|
|
||||||
| Archetype | Duration | Easing | Overshoot | Keywords |
|
|
||||||
|-----------|----------|--------|-----------|----------|
|
|
||||||
| **Playful** | 150-300ms | ease-out-back | 10-20% | fun, whimsical, bouncy, cute |
|
|
||||||
| **Premium** | 350-600ms | cubic-bezier(0.4,0,0.2,1) | 0% | elegant, minimal, luxury, sophisticated |
|
|
||||||
| **Corporate** | 200-400ms | cubic-bezier(0.2,0,0,1) | 0-3% | clean, professional, business, dashboard |
|
|
||||||
| **Energetic** | 100-250ms | ease-out-expo | 15-30% | dynamic, energetic, bold, exciting |
|
|
||||||
|
|
||||||
**Default**: Corporate for UI, Playful for illustrations.
|
|
||||||
|
|
||||||
**Brand Motion Identity** — define three constants:
|
|
||||||
1. **Signature easing**: One curve for 80% of animations
|
|
||||||
2. **Duration palette**: 3 durations (quick / standard / slow)
|
|
||||||
3. **Entrance pattern**: One consistent entry style
|
|
||||||
|
|
||||||
> Deep dive: [director/motion-personality.md](director/motion-personality.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Property Selection
|
|
||||||
|
|
||||||
| Effect Goal | Primary Property | Secondary Properties |
|
|
||||||
|-------------|------------------|---------------------|
|
|
||||||
| Entrance/Exit | position | opacity, scale |
|
|
||||||
| Emphasis/Attention | scale | rotation (subtle), opacity pulse |
|
|
||||||
| State Change | opacity, color | scale (press feedback) |
|
|
||||||
| Direction/Flow | position | rotation (follow path) |
|
|
||||||
| Depth/3D Feel | scale + shadow | position (parallax) |
|
|
||||||
| Loading/Progress | rotation (spinner) | scale, opacity pulse |
|
|
||||||
| Success | scale (pop) | color, rotation (checkmark draw) |
|
|
||||||
| Error/Alert | position (shake) | color, rotation (wobble) |
|
|
||||||
|
|
||||||
**Simplicity threshold**: Use the minimum properties needed. One = direct. Two = polished. Three+ = potentially overwhelming.
|
|
||||||
|
|
||||||
> Deep dive: [reference/property-selection.md](reference/property-selection.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Duration Table
|
|
||||||
|
|
||||||
| Element Type | Duration | Rationale |
|
|
||||||
|-------------|----------|-----------|
|
|
||||||
| Tooltip / micro-feedback | 80-120ms | Must feel instant |
|
|
||||||
| Button press / toggle | 120-180ms | Responsive feedback |
|
|
||||||
| Icon transition | 150-250ms | Clear state change |
|
|
||||||
| Card enter / exit | 200-350ms | Spatial awareness |
|
|
||||||
| Modal / dialog | 300-400ms | Focus shift |
|
|
||||||
| Page transition | 400-600ms | Context switch |
|
|
||||||
| Dramatic reveal | 600-1200ms | Theatrical build |
|
|
||||||
|
|
||||||
**Distance scales duration**: 100px = base. 200px = 1.3x. 400px = 1.6x.
|
|
||||||
|
|
||||||
**Enter > Exit**: Entrances 30-50% longer than exits. Users care about what appears.
|
|
||||||
|
|
||||||
**Interactive feedback**:
|
|
||||||
- Hover: <100ms
|
|
||||||
- Press: <150ms
|
|
||||||
- Release/settle: 200-300ms
|
|
||||||
- Error shake: 300-400ms (2-3 oscillations)
|
|
||||||
|
|
||||||
> Deep dive: [reference/timing-easing-tables.md](reference/timing-easing-tables.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Easing Selection
|
|
||||||
|
|
||||||
**Directional rules**:
|
|
||||||
- **Entrance** → decelerate (fast start, gentle landing): ease-out family
|
|
||||||
- **Exit** → accelerate (gentle start, fast departure): ease-in family
|
|
||||||
- **On-screen** → smooth both ends: ease-in-out family
|
|
||||||
- **Looping ambient** → seamless: sine-based ease-in-out
|
|
||||||
|
|
||||||
**Industry standards**:
|
|
||||||
|
|
||||||
| Standard | Cubic Bezier | Use For |
|
|
||||||
|----------|-------------|---------|
|
|
||||||
| Material Design 3 | (0.2, 0, 0, 1) | Default on-screen |
|
|
||||||
| MD3 Emphasized | (0.05, 0.7, 0.1, 1) | Entrances, attention |
|
|
||||||
| MD3 Accelerate | (0.3, 0, 1, 1) | Exits, dismissals |
|
|
||||||
| Apple HIG | (0.25, 0.1, 0.25, 1) | Standard iOS |
|
|
||||||
| Snappy UI | (0.2, 0, 0, 1) | Fast, decisive |
|
|
||||||
| Gentle float | (0.4, 0, 0.2, 1) | Ambient, background |
|
|
||||||
| Bounce settle | (0.175, 0.885, 0.32, 1.275) | Overshoot, playful |
|
|
||||||
|
|
||||||
**Material-based easing**:
|
|
||||||
|
|
||||||
| Material | Duration Scale | Overshoot |
|
|
||||||
|----------|---------------|-----------|
|
|
||||||
| Rigid (metal, stone) | 1.2x | 0% |
|
|
||||||
| Elastic (rubber, gel) | 0.8x | 15-25% |
|
|
||||||
| Fluid (water, paint) | 1.5x | 5% |
|
|
||||||
| Paper (cards, sheets) | 1.0x | 3-5% |
|
|
||||||
| Gas (smoke, fog) | 2.0x | 0% |
|
|
||||||
| Glass (brittle) | 0.9x | 0% |
|
|
||||||
|
|
||||||
> Deep dive: [reference/timing-easing-tables.md](reference/timing-easing-tables.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Button Press (Playful)
|
|
||||||
1. **Anticipation**: Scale to 0.97 (50ms, ease-out)
|
|
||||||
2. **Squash**: Scale to [1.04, 0.96] (100ms, ease-in)
|
|
||||||
3. **Follow through**: Overshoots to 1.02, settles to 1.0 (spring, 200ms)
|
|
||||||
4. **Secondary**: Shadow shrinks during press, icon shifts down 2px
|
|
||||||
5. **Total**: ~150ms press + 200ms settle
|
|
||||||
|
|
||||||
### Card Entrance (Premium)
|
|
||||||
1. **Start**: 20px below target, opacity 0
|
|
||||||
2. **Path**: Slight curve (10px X offset at midpoint)
|
|
||||||
3. **Easing**: ease-out-cubic deceleration
|
|
||||||
4. **Follow through**: Shadow arrives 50ms after card
|
|
||||||
5. **Secondary**: Content fades in 100ms after card lands
|
|
||||||
6. **Staging**: Other cards dim to 80%
|
|
||||||
|
|
||||||
### Success State (Playful)
|
|
||||||
1. **Primary**: Scale pop with ease-out-back
|
|
||||||
2. **Secondary**: Checkmark draws in
|
|
||||||
3. **Ambient**: Subtle particle burst
|
|
||||||
4. **Color**: Green fill
|
|
||||||
5. **Total**: 300-400ms
|
|
||||||
|
|
||||||
### Error Shake (Corporate)
|
|
||||||
1. **Primary**: Position oscillates 2-3 times, ±10-15px horizontal
|
|
||||||
2. **Easing**: ease-in-out for sharp stops
|
|
||||||
3. **Color**: Red tint
|
|
||||||
4. **Total**: 300-400ms
|
|
||||||
5. **No overshoot**: Errors feel firm
|
|
||||||
|
|
||||||
> More patterns: [patterns/entrance-exit.md](patterns/entrance-exit.md) | [patterns/state-feedback.md](patterns/state-feedback.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Choreography Essentials
|
|
||||||
|
|
||||||
**Coordinated entry**:
|
|
||||||
- Lead with the hero — primary element enters first or most prominently
|
|
||||||
- Spatial consistency — all elements enter from same direction
|
|
||||||
- Counter-motion — hero moves right → ambient moves left at 20-30% speed
|
|
||||||
|
|
||||||
**1/3 Rule (distance)**: No motion travels more than 1/3 of screen without a keyframe change.
|
|
||||||
|
|
||||||
**1/3 Rule (elements)**: With 3+ elements, no more than 1/3 in active motion simultaneously.
|
|
||||||
|
|
||||||
**Stagger budgets**:
|
|
||||||
|
|
||||||
| Pattern | Delay | Total Budget | Use Case |
|
|
||||||
|---------|-------|-------------|----------|
|
|
||||||
| Micro cascade | 20-40ms | <200ms | List items, grid cells |
|
|
||||||
| Standard | 50-100ms | <400ms | Cards, panels, nav |
|
|
||||||
| Dramatic | 100-200ms | <600ms | Hero sections |
|
|
||||||
| Wave | 30-60ms | <500ms | Data visualizations |
|
|
||||||
|
|
||||||
**Critical**: Total stagger must stay under 500ms.
|
|
||||||
|
|
||||||
> Deep dive: [director/choreography.md](director/choreography.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Emotion-to-Motion Map
|
|
||||||
|
|
||||||
| Emotion | Character | Path | Easing | Duration |
|
|
||||||
|---------|-----------|------|--------|----------|
|
|
||||||
| Joy | Bouncy, arcs | Curved, upward | ease-out-back | 200-400ms |
|
|
||||||
| Calm | Smooth, flowing | Gentle curves | sine ease-in-out | 500-1000ms |
|
|
||||||
| Urgency | Sharp, fast | Straight lines | ease-out | 100-200ms |
|
|
||||||
| Sadness | Slow, downward | Drooping curves | cubic ease-in-out | 600-1200ms |
|
|
||||||
| Surprise | Sudden, expanding | Radial outward | ease-out-expo | 150-300ms |
|
|
||||||
| Elegance | Slow, controlled | Long arcs | (0.4,0,0.2,1) | 400-700ms |
|
|
||||||
| Playfulness | Bouncy, irregular | Arcs, squiggly | ease-out-back | 200-350ms |
|
|
||||||
|
|
||||||
**Path as language**: Angular = tense. Curved = friendly. Spiral = whimsical. Diagonal = purposeful. Vertical = growth/weight. Horizontal = progress.
|
|
||||||
|
|
||||||
> Deep dive: [director/emotion-mapping.md](director/emotion-mapping.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Weight Classification
|
|
||||||
|
|
||||||
| Weight | Examples | Duration | Overshoot | Easing |
|
|
||||||
|--------|----------|----------|-----------|--------|
|
|
||||||
| Heavy | Modals, overlays | 300-500ms | 0% | Gentle, high damping |
|
|
||||||
| Medium | Cards, panels | 200-350ms | 3-5% | Moderate |
|
|
||||||
| Light | Tooltips, badges, icons | 80-200ms | 5-15% | Responsive |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quality Rules
|
|
||||||
|
|
||||||
### CRITICAL — never break
|
|
||||||
1. **Never linear for spatial movement** — always use easing curves (linear only for spinners, progress bars)
|
|
||||||
2. **Never opacity-only** for important state changes — combine with position or scale
|
|
||||||
3. **Never exceed 1/3 screen** without intermediate keyframe
|
|
||||||
4. **Always three motion layers** — primary + secondary + ambient
|
|
||||||
|
|
||||||
### HIGH — strongly follow
|
|
||||||
1. Match duration to element type (see tables)
|
|
||||||
2. Use directional easing (ease-out entrance, ease-in exit)
|
|
||||||
3. Apply Disney principles (especially anticipation, follow-through)
|
|
||||||
4. Maintain consistent personality across scene
|
|
||||||
|
|
||||||
> Full checklist: [reference/quality-checklist.md](reference/quality-checklist.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting Quick Reference
|
|
||||||
|
|
||||||
| Problem | Likely Cause | Fix |
|
|
||||||
|---------|-------------|-----|
|
|
||||||
| Looks robotic | Linear easing or no arcs | Add easing curves + arc paths |
|
|
||||||
| Feels too slow | Duration too long for element type | Check duration table, use ease-out |
|
|
||||||
| Feels cheap/flat | Missing secondary + ambient | Add shadow motion + background life |
|
|
||||||
| Too distracting | Too many elements moving | Apply 1/3 rule, reduce amplitude |
|
|
||||||
| No personality | Generic easing everywhere | Apply personality archetype consistently |
|
|
||||||
|
|
||||||
> Deep dive: [reference/troubleshooting.md](reference/troubleshooting.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Reference
|
|
||||||
|
|
||||||
**Philosophy** (director/):
|
|
||||||
- [core-philosophy.md](director/core-philosophy.md) — Three Pillars deep dive
|
|
||||||
- [decision-framework.md](director/decision-framework.md) — Full decision pipeline
|
|
||||||
- [disney-principles.md](director/disney-principles.md) — 12 principles, UI-adapted
|
|
||||||
- [motion-personality.md](director/motion-personality.md) — 4 archetypes + brand identity
|
|
||||||
- [emotion-mapping.md](director/emotion-mapping.md) — Emotion → motion + color psychology
|
|
||||||
- [choreography.md](director/choreography.md) — Multi-element coordination
|
|
||||||
- [narrative-structure.md](director/narrative-structure.md) — Micro-story framework
|
|
||||||
- [context-adaptation.md](director/context-adaptation.md) — Platform, a11y, performance
|
|
||||||
|
|
||||||
**Reference** (reference/):
|
|
||||||
- [timing-easing-tables.md](reference/timing-easing-tables.md) — Duration + easing lookups
|
|
||||||
- [property-selection.md](reference/property-selection.md) — Property communication guide
|
|
||||||
- [troubleshooting.md](reference/troubleshooting.md) — Animation smells + fixes
|
|
||||||
- [quality-checklist.md](reference/quality-checklist.md) — Evaluation criteria
|
|
||||||
|
|
||||||
**Patterns** (patterns/):
|
|
||||||
- [entrance-exit.md](patterns/entrance-exit.md) — Entrance/exit recipes
|
|
||||||
- [state-feedback.md](patterns/state-feedback.md) — Success, error, loading, hover
|
|
||||||
- [ambient-continuous.md](patterns/ambient-continuous.md) — Looping, breathing, parallax
|
|
||||||
- [multi-element.md](patterns/multi-element.md) — Stagger + choreography recipes
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
# Choreography
|
|
||||||
|
|
||||||
## Coordinated Entry Rules
|
|
||||||
|
|
||||||
### 1. Lead with the Hero
|
|
||||||
- Hero gets largest displacement and most attention-grabbing easing
|
|
||||||
- Supporting elements are subtler in every dimension
|
|
||||||
|
|
||||||
### 2. Spatial Origin Consistency
|
|
||||||
All elements enter from same direction or shared origin. Mixed directions = chaos.
|
|
||||||
|
|
||||||
### 3. Counter-Motion
|
|
||||||
|
|
||||||
| Hero Motion | Counter-Motion | Speed Ratio |
|
|
||||||
|-------------|---------------|-------------|
|
|
||||||
| Enters left | Background shifts right | 20-30% |
|
|
||||||
| Scales up | Shadow scales down | 10-20% |
|
|
||||||
| Rotates CW | Ambient drifts CCW | 15-25% |
|
|
||||||
| Lifts (Y up) | Shadow spreads + softens | 20-30% |
|
|
||||||
|
|
||||||
## Sequence Structure
|
|
||||||
|
|
||||||
| Phase | Duration Share | What Happens |
|
|
||||||
|-------|--------------|-------------|
|
|
||||||
| Setup | 20-30% | Elements enter, scene establishes |
|
|
||||||
| Action | 30-40% | Primary motion, hero moment |
|
|
||||||
| Resolution | 30-40% | Settle, secondary reactions, breathing |
|
|
||||||
|
|
||||||
Leave 100-200ms stillness after resolution before new motion.
|
|
||||||
|
|
||||||
## The 1/3 Rules
|
|
||||||
|
|
||||||
**Distance**: No motion travels >1/3 screen without intermediate keyframe. Break with direction changes, speed variations, or arc adjustments.
|
|
||||||
|
|
||||||
**Elements**: With 3+ animated elements, max 1/3 active simultaneously. Stagger so element 1 settles as element 3 starts.
|
|
||||||
|
|
||||||
## Stagger Patterns
|
|
||||||
|
|
||||||
| Pattern | Description | Best For |
|
|
||||||
|---------|------------|----------|
|
|
||||||
| Sequential | Reading order | Lists, grids |
|
|
||||||
| Center-out | Radiating from center | Hero content, ripples |
|
|
||||||
| Random | Varied timing | Organic, particle-like |
|
|
||||||
| Wave | Sine-based | Data bars, continuous |
|
|
||||||
| Reverse | Bottom-to-top | Exits, backward nav |
|
|
||||||
|
|
||||||
- All staggered elements use same easing family
|
|
||||||
- Vary only start time, not curve
|
|
||||||
- Optional: last element gets slight overshoot (punctuation)
|
|
||||||
|
|
||||||
## Shared Motion Events
|
|
||||||
|
|
||||||
When multiple elements react to one trigger:
|
|
||||||
- All start within 50ms of each other
|
|
||||||
- Can arrive at different times (staggered landing)
|
|
||||||
- Same easing family; motion originates from trigger point
|
|
||||||
|
|
||||||
## Attention Direction
|
|
||||||
|
|
||||||
| Technique | Implementation |
|
|
||||||
|-----------|---------------|
|
|
||||||
| Leading motion | Animate target before context |
|
|
||||||
| Following motion | Settle on focal point |
|
|
||||||
| Ambient motion | Subtle continuous in periphery |
|
|
||||||
| Pointing motion | Directional toward CTA |
|
|
||||||
|
|
||||||
### Depth Through Speed
|
|
||||||
|
|
||||||
| Layer | Displacement | Speed |
|
|
||||||
|-------|-------------|-------|
|
|
||||||
| Foreground | 1.0x | Fastest |
|
|
||||||
| Midground | 0.5x | Medium |
|
|
||||||
| Background | 0.2x | Slowest |
|
|
||||||
|
|
||||||
## Common Recipes
|
|
||||||
|
|
||||||
### Dashboard Load
|
|
||||||
1. Skeletons fade in (100ms)
|
|
||||||
2. Hero metric (250ms, ease-out, 100ms delay)
|
|
||||||
3. Supporting cards stagger (50ms between, 200ms each)
|
|
||||||
4. Chart data draws in (300ms, starts with cards)
|
|
||||||
5. Ambient pulse on primary metric
|
|
||||||
|
|
||||||
### Modal Open
|
|
||||||
1. Background dims (200ms)
|
|
||||||
2. Modal scales 95%→100% + fades (300ms, 50ms delay)
|
|
||||||
3. Content fades in (200ms, 100ms after modal)
|
|
||||||
4. Close button last (150ms)
|
|
||||||
|
|
||||||
### List Update (item added)
|
|
||||||
1. Existing items shift down (200ms, ease-in-out)
|
|
||||||
2. New item fades+slides from top (250ms, ease-out, 50ms delay)
|
|
||||||
3. Subtle scale overshoot on land (3-5%)
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
# Context Adaptation
|
|
||||||
|
|
||||||
## Platform Scaling
|
|
||||||
|
|
||||||
| Platform | Duration Modifier | Complexity | Physics |
|
|
||||||
|----------|------------------|------------|---------|
|
|
||||||
| Desktop | 1.0x (baseline) | Full | All types |
|
|
||||||
| Tablet | 0.9x | Standard | Most types |
|
|
||||||
| Mobile | 0.8x | Reduced (1-2 properties) | Snappy only |
|
|
||||||
| Watch | 0.6x | Minimal (1 property) | None |
|
|
||||||
| TV/Kiosk | 1.3x | Full | All types |
|
|
||||||
|
|
||||||
**Mobile rules**: prefer opacity + transform; touch feedback <100ms; reduce stagger budgets by 30%; avoid parallax
|
|
||||||
**Desktop opportunities**: hover states, cursor tracking, multi-column stagger, spatial choreography
|
|
||||||
|
|
||||||
## Accessibility
|
|
||||||
|
|
||||||
### prefers-reduced-motion
|
|
||||||
|
|
||||||
| Original Motion | Reduced Alternative |
|
|
||||||
|----------------|-------------------|
|
|
||||||
| Slide entrance | Opacity fade only |
|
|
||||||
| Bounce/spring | Instant or simple ease-out |
|
|
||||||
| Parallax | Static positioning |
|
|
||||||
| Auto-playing | Paused, user-initiated |
|
|
||||||
| Complex choreography | Single fade |
|
|
||||||
| Continuous ambient | Static or subtle opacity pulse |
|
|
||||||
|
|
||||||
Reduced motion means: remove spatial movement, keep opacity, remove spring easing, reduce duration 50%+, never auto-play loops.
|
|
||||||
|
|
||||||
### Vestibular Triggers (avoid or provide alternatives)
|
|
||||||
- Large-scale zoom, full-screen position transitions
|
|
||||||
- Spinning elements >100px, parallax >2 layers, rapid direction changes
|
|
||||||
|
|
||||||
### Cognitive Accessibility
|
|
||||||
- Same interaction = same animation every time
|
|
||||||
- Pause controls for animations >5 seconds
|
|
||||||
- Don't convey critical info through motion alone
|
|
||||||
|
|
||||||
## Performance Budgets
|
|
||||||
|
|
||||||
| Tier | Properties | Max Elements |
|
|
||||||
|------|-----------|-------------|
|
|
||||||
| Optimal | transform, opacity | Unlimited (GPU) |
|
|
||||||
| Good | + color, clip-path | 10-15 |
|
|
||||||
| Acceptable | + width, height, margin | 5-8 |
|
|
||||||
| Avoid | box-shadow, border-radius, filter | 1-3 |
|
|
||||||
|
|
||||||
- Target 60fps (16.67ms/frame); animation logic <10ms/frame
|
|
||||||
- will-change sparingly; keep animated elements <20 per viewport
|
|
||||||
- Stagger reduces peak load vs simultaneous
|
|
||||||
- Fallback: 30fps acceptable for ambient
|
|
||||||
|
|
||||||
## Content Type Adaptation
|
|
||||||
|
|
||||||
| Content Type | Personality | Duration | Motion Density |
|
|
||||||
|-------------|-------------|----------|---------------|
|
|
||||||
| Financial | Corporate/Premium | 250-500ms | Low |
|
|
||||||
| Social media | Playful | 150-300ms | Medium |
|
|
||||||
| Enterprise SaaS | Corporate | 200-400ms | Low |
|
|
||||||
| Gaming | Energetic | 100-250ms | High |
|
|
||||||
| Healthcare | Corporate/Calm | 300-600ms | Very low |
|
|
||||||
| E-commerce | Varies | 200-400ms | Medium |
|
|
||||||
| Editorial | Premium | 350-600ms | Low |
|
|
||||||
| Children's apps | Playful | 150-300ms | High |
|
|
||||||
|
|
||||||
## Responsive Motion
|
|
||||||
|
|
||||||
| Container Width | Max Displacement | Duration |
|
|
||||||
|----------------|-----------------|----------|
|
|
||||||
| <400px | 20% of width | 0.8x |
|
|
||||||
| 400-800px | 25% of width | 1.0x |
|
|
||||||
| 800-1200px | 20% of width | 1.0x |
|
|
||||||
| >1200px | 15% of width | 1.1x |
|
|
||||||
|
|
||||||
- Small viewport: sequential, one element at a time
|
|
||||||
- Medium: standard stagger, 2-3 columns
|
|
||||||
- Large: full choreography, center-out stagger, parallax
|
|
||||||
|
|
||||||
## Dark Mode
|
|
||||||
- Reduce motion intensity 10-20% (bright on dark = more impact)
|
|
||||||
- Subtler ambient motion; careful with opacity values
|
|
||||||
- Avoid pure white flashes
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# Core Philosophy
|
|
||||||
|
|
||||||
## Three Pillars
|
|
||||||
|
|
||||||
### Pillar 1: Emotional Intent
|
|
||||||
Define target emotion before choosing any property.
|
|
||||||
|
|
||||||
| Emotion | Character | Timing | Easing |
|
|
||||||
|---------|----------|--------|--------|
|
|
||||||
| Trust | Smooth, predictable | 300-400ms | Gentle curves |
|
|
||||||
| Delight | Bouncy, surprising | 200-300ms | Overshoot |
|
|
||||||
| Urgency | Sharp, direct | 100-200ms | Snappy ease-out |
|
|
||||||
| Calm | Slow, flowing | 500-1000ms | Sine curves |
|
|
||||||
| Surprise | Sudden, explosive | 150-300ms | Exponential |
|
|
||||||
| Confidence | Direct, decisive | 200-400ms | Strong ease-out |
|
|
||||||
|
|
||||||
### Pillar 2: Visual Narrative
|
|
||||||
|
|
||||||
| Phase | Duration Share | Purpose |
|
|
||||||
|-------|--------------|---------|
|
|
||||||
| Setup | 20-30% | Establish context, prepare viewer |
|
|
||||||
| Action | 30-40% | Primary motion, hero moment |
|
|
||||||
| Resolution | 30-40% | Settle, breathe, confirm |
|
|
||||||
|
|
||||||
Even a 200ms tooltip fade has implicit setup→action→resolution.
|
|
||||||
|
|
||||||
### Pillar 3: Motion Craft
|
|
||||||
- Easing curves match emotional intent
|
|
||||||
- Duration proportional to element size and distance
|
|
||||||
- Arcs for organic, straight for mechanical
|
|
||||||
- Secondary motion (shadows, related elements)
|
|
||||||
- Nothing starts and stops all at once
|
|
||||||
|
|
||||||
## Three Motion Layers
|
|
||||||
|
|
||||||
| Layer | Role | Amplitude |
|
|
||||||
|-------|------|-----------|
|
|
||||||
| Primary | Main action viewer follows | 100% |
|
|
||||||
| Secondary | Supporting richness | 30-50% |
|
|
||||||
| Ambient | Background life | 10-20% |
|
|
||||||
|
|
||||||
- Secondary offset 50-100ms from primary, different easing
|
|
||||||
- Ambient is continuous/slow, never demands attention
|
|
||||||
- Primary-only animation feels flat; always add secondary + ambient
|
|
||||||
|
|
||||||
## The 1/3 Screen Rule
|
|
||||||
No motion travels >1/3 screen without intermediate keyframe. Break with direction changes, speed shifts, or arc adjustments.
|
|
||||||
|
|
||||||
## The Attention Budget
|
|
||||||
- One hero motion per scene moment
|
|
||||||
- Max 2-3 elements in active motion simultaneously
|
|
||||||
- Ambient doesn't count against budget
|
|
||||||
- Stagger rather than synchronize
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
# Decision Framework
|
|
||||||
|
|
||||||
## The First Questions
|
|
||||||
|
|
||||||
### 1. Purpose?
|
|
||||||
|
|
||||||
| Purpose | Approach | Priority |
|
|
||||||
|---------|----------|----------|
|
|
||||||
| Draw attention | Scale, color, pulse | Responsiveness |
|
|
||||||
| Guide sequence | Stagger, directional flow | Clarity |
|
|
||||||
| Provide feedback | Immediate response | Speed |
|
|
||||||
| Create atmosphere | Subtle, continuous | Subtlety |
|
|
||||||
| Explain relationship | Position, connection | Legibility |
|
|
||||||
| Celebrate/reward | Overshoot, particles | Delight |
|
|
||||||
|
|
||||||
### 2. Audience?
|
|
||||||
- Stressed → calm motion; Browsing → richer motion
|
|
||||||
- Task-focused → fast, minimal; Exploring → can be dramatic
|
|
||||||
- Seen 100x/day → fast, subtle; Seen once → can be dramatic
|
|
||||||
|
|
||||||
### 3. Context?
|
|
||||||
- Busy layout → simpler motion; Empty → can be dramatic
|
|
||||||
- Other animations active → coordinate or stagger
|
|
||||||
- Small container → small motion
|
|
||||||
|
|
||||||
## 4-Level Decision Hierarchy
|
|
||||||
|
|
||||||
### Level 1: Motion Category
|
|
||||||
|
|
||||||
| Category | When |
|
|
||||||
|----------|------|
|
|
||||||
| Revealing | Content appearing |
|
|
||||||
| Concealing | Content disappearing |
|
|
||||||
| Transitioning | Between states/views |
|
|
||||||
| Emphasizing | Drawing attention |
|
|
||||||
| Responding | Reacting to interaction |
|
|
||||||
| Ambient | Background atmosphere |
|
|
||||||
|
|
||||||
### Level 2: Motion Personality
|
|
||||||
Match to brand. See [motion-personality.md](motion-personality.md).
|
|
||||||
|
|
||||||
### Level 3: Motion Direction
|
|
||||||
|
|
||||||
| Direction | Communicates |
|
|
||||||
|-----------|-------------|
|
|
||||||
| Upward | Growth, aspiration |
|
|
||||||
| Downward | Settling, completion |
|
|
||||||
| Leftward | Regression, departure |
|
|
||||||
| Rightward | Progression, arrival |
|
|
||||||
| Outward (scale up) | Importance, emergence |
|
|
||||||
| Inward (scale down) | Dismissal, removal |
|
|
||||||
| Center-seeking | Focus, convergence |
|
|
||||||
| Center-fleeing | Distribution, release |
|
|
||||||
|
|
||||||
### Level 4: Implementation Properties
|
|
||||||
Choose animated properties. See [property-selection.md](../reference/property-selection.md).
|
|
||||||
|
|
||||||
## Decision Quick-Path
|
|
||||||
|
|
||||||
```
|
|
||||||
1. PURPOSE → What does this motion DO?
|
|
||||||
├── Feedback → short, ease-out, minimal
|
|
||||||
├── Entrance → ease-out, personality-matched
|
|
||||||
├── Exit → ease-in, 70% of entrance
|
|
||||||
├── Emphasis → scale or color pulse
|
|
||||||
└── Ambient → slow, continuous, sine
|
|
||||||
|
|
||||||
2. PERSONALITY → Match keywords
|
|
||||||
├── Fun/bouncy → Playful
|
|
||||||
├── Elegant/luxury → Premium
|
|
||||||
├── Clean/professional → Corporate
|
|
||||||
└── Bold/dynamic → Energetic
|
|
||||||
|
|
||||||
3. PROPERTIES → Minimum needed (two = sweet spot)
|
|
||||||
|
|
||||||
4. TIMING → Check duration table, adjust for distance/weight
|
|
||||||
|
|
||||||
5. EASING → Entering=ease-out, Leaving=ease-in, On-screen=ease-in-out, Looping=sine
|
|
||||||
```
|
|
||||||
|
|
||||||
## Evaluation Before Delivery
|
|
||||||
|
|
||||||
| Check | Question |
|
|
||||||
|-------|----------|
|
|
||||||
| Purpose | Clear function? |
|
|
||||||
| Personality | Matches brand/context? |
|
|
||||||
| Repetition | Fine on 100th viewing? |
|
|
||||||
| Attention | Respects viewer's focus? |
|
|
||||||
| Hierarchy | Reinforces importance? |
|
|
||||||
| Layers | Primary + secondary + ambient? |
|
|
||||||
| Timing | Duration appropriate? |
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
# Disney's 12 Principles — UI Adapted
|
|
||||||
|
|
||||||
## 1. Squash and Stretch
|
|
||||||
|
|
||||||
- Squash: scale ~[1.2, 0.8]; Stretch: ~[0.85, 1.15]
|
|
||||||
- Impact: 2-4 frames (30-65ms); Recovery: 4-8 frames (65-130ms)
|
|
||||||
- Preserve volume: width +20% → height decreases proportionally
|
|
||||||
- Skip for premium/luxury brands
|
|
||||||
|
|
||||||
## 2. Anticipation
|
|
||||||
|
|
||||||
- Small motion opposite to main direction before action
|
|
||||||
- Duration: 100-200ms, magnitude: 10-20% of main action
|
|
||||||
- Button: scale down 3% before expanding; Card: shift 5-10px away first
|
|
||||||
- Skip for micro-feedback (<150ms)
|
|
||||||
|
|
||||||
## 3. Staging
|
|
||||||
|
|
||||||
- Dim non-hero elements to 40-60% opacity; optional 2-4px blur
|
|
||||||
- Hero enters 100-200ms after supporting elements
|
|
||||||
- One primary action per timing beat
|
|
||||||
|
|
||||||
## 4. Straight Ahead vs. Pose to Pose
|
|
||||||
|
|
||||||
| Approach | Feel | Best For |
|
|
||||||
|----------|------|----------|
|
|
||||||
| Straight Ahead | Fluid, spontaneous | Particles, ambient, generative art |
|
|
||||||
| Pose to Pose | Planned, controlled | UI transitions, state changes |
|
|
||||||
|
|
||||||
## 5. Follow Through and Overlapping Action
|
|
||||||
|
|
||||||
- Child delay: 50-150ms behind parent
|
|
||||||
- Trailing elements: offset stop times by 100-200ms
|
|
||||||
- Use spring easing for trailing parts (lower stiffness = more trailing)
|
|
||||||
|
|
||||||
## 6. Slow In and Slow Out
|
|
||||||
|
|
||||||
| Context | Easing | Why |
|
|
||||||
|---------|--------|-----|
|
|
||||||
| Entrance | ease-out | Arrives smoothly |
|
|
||||||
| Exit | ease-in | Departs quickly |
|
|
||||||
| On-screen | ease-in-out | Smooth journey |
|
|
||||||
| Ambient loop | sine ease-in-out | Seamless |
|
|
||||||
|
|
||||||
**NEVER** linear for spatial movement. Linear only for: rotation, progress bars, timers.
|
|
||||||
|
|
||||||
## 7. Arcs
|
|
||||||
|
|
||||||
- Add 10-20px perpendicular offset at path midpoint
|
|
||||||
- Subtle (5px) for corporate, pronounced (20px+) for playful
|
|
||||||
- Mechanical UIs can use straight paths intentionally
|
|
||||||
|
|
||||||
## 8. Secondary Action
|
|
||||||
|
|
||||||
- Amplitude: 30-50% of primary; timing: 50-100ms after primary
|
|
||||||
- Different easing than primary
|
|
||||||
- Examples: card enters → shadow grows; button presses → ripple expands
|
|
||||||
|
|
||||||
## 9. Timing
|
|
||||||
|
|
||||||
| Weight/Mood | Duration |
|
|
||||||
|-------------|----------|
|
|
||||||
| Heavy (modals, pages) | 400-800ms |
|
|
||||||
| Light (tooltips, toggles) | 100-250ms |
|
|
||||||
| Sad/serious | 600ms+ |
|
|
||||||
| Happy/light | 200-400ms |
|
|
||||||
| Urgent | 100-200ms |
|
|
||||||
|
|
||||||
Enter-exit asymmetry: entrances 30-50% longer than exits.
|
|
||||||
|
|
||||||
## 10. Exaggeration
|
|
||||||
|
|
||||||
| Personality | Exaggeration |
|
|
||||||
|-------------|-------------|
|
|
||||||
| Playful | 15-25% |
|
|
||||||
| Energetic | 20-30% |
|
|
||||||
| Corporate | 0-5% |
|
|
||||||
| Premium | 0% |
|
|
||||||
|
|
||||||
- Scale overshoot: 10-30% beyond target; rotation: ±5-15°
|
|
||||||
|
|
||||||
## 11. Solid Drawing
|
|
||||||
|
|
||||||
- Maintain consistent proportions across keyframes
|
|
||||||
- Use scale + rotation together for depth
|
|
||||||
- Shadow behavior matches implied light source
|
|
||||||
|
|
||||||
## 12. Appeal
|
|
||||||
|
|
||||||
- Smooth curves over sharp angles; satisfying timing
|
|
||||||
- Personality consistency across all elements
|
|
||||||
- Appeal killers: jerky motion, inconsistent timing, abrupt stops, uniform animation
|
|
||||||
|
|
||||||
## Combining Principles
|
|
||||||
|
|
||||||
| Recipe | Principles Used |
|
|
||||||
|--------|----------------|
|
|
||||||
| Button press | Anticipation + Squash/Stretch + Follow-Through + Secondary + Timing |
|
|
||||||
| Card entrance | Anticipation + Arcs + Slow In/Out + Follow-Through + Staging |
|
|
||||||
| Success celebration | Exaggeration + Secondary + Timing + Squash/Stretch + Appeal |
|
|
||||||
| Error shake | Timing + Slow In/Out + Staging (no exaggeration) |
|
|
||||||
| Loading spinner | Timing + Slow In/Out + Appeal |
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# Emotion-to-Motion Mapping
|
|
||||||
|
|
||||||
## Core Table
|
|
||||||
|
|
||||||
| Emotion | Character | Path | Easing | Duration |
|
|
||||||
|---------|----------|------|--------|----------|
|
|
||||||
| Joy/Delight | Bouncy, arcs, overshoot | Curved, upward | ease-out-back | 200-400ms |
|
|
||||||
| Calm/Serenity | Smooth, flowing | Gentle curves | sine ease-in-out | 500-1000ms |
|
|
||||||
| Urgency/Alert | Sharp, fast, direct | Straight lines | ease-out | 100-200ms |
|
|
||||||
| Sadness/Weight | Slow, downward | Drooping curves | cubic ease-in-out | 600-1200ms |
|
|
||||||
| Surprise/Impact | Sudden, expanding | Radial outward | ease-out-expo | 150-300ms |
|
|
||||||
| Elegance/Grace | Slow, controlled | Long smooth arcs | (0.4, 0, 0.2, 1) | 400-700ms |
|
|
||||||
| Playfulness | Bouncy, irregular | Arcs, squiggly | ease-out-back | 200-350ms |
|
|
||||||
| Confidence | Direct, decisive | Straight, horizontal | ease-out | 200-400ms |
|
|
||||||
| Curiosity | Exploratory, varied | Mixed, circular | varied | 300-500ms |
|
|
||||||
| Tenderness | Soft, gentle | Very subtle curves | soft ease-in-out | 600-1000ms |
|
|
||||||
|
|
||||||
## Path as Emotional Language
|
|
||||||
|
|
||||||
| Path Type | Connotation |
|
|
||||||
|-----------|------------|
|
|
||||||
| Angular/sharp | Tense, urgent, mechanical |
|
|
||||||
| Curved/smooth | Relaxed, friendly, organic |
|
|
||||||
| Spiral | Playful, whimsical |
|
|
||||||
| Straight diagonal | Dynamic, purposeful |
|
|
||||||
| Vertical up | Growth, achievement |
|
|
||||||
| Vertical down | Settling, gravity |
|
|
||||||
| Horizontal | Journey, progress |
|
|
||||||
| Radial outward | Explosion, release |
|
|
||||||
| Radial inward | Focus, convergence |
|
|
||||||
|
|
||||||
## Emotional Intensity
|
|
||||||
|
|
||||||
| Intensity | Characteristics | When |
|
|
||||||
|-----------|----------------|------|
|
|
||||||
| Low | Subtle opacity, tiny shifts | Ambient, routine |
|
|
||||||
| Medium | Visible but not demanding | Most UI interactions |
|
|
||||||
| High | Demands attention, large displacement | Errors, celebrations, onboarding |
|
|
||||||
|
|
||||||
## Color Psychology
|
|
||||||
|
|
||||||
| Color | Emotion | Animation Pairing |
|
|
||||||
|-------|---------|------------------|
|
|
||||||
| Blue | Trust, calm | Smooth, medium transitions |
|
|
||||||
| Green | Success, growth | Upward, expansion, gentle overshoot |
|
|
||||||
| Red | Alert, urgency | Sharp, fast, horizontal shakes |
|
|
||||||
| Orange | Energy, warmth | Bouncy, diagonal paths |
|
|
||||||
| Purple | Premium, mystery | Slow reveals, elegant easing |
|
|
||||||
| Yellow | Optimism, caution | Quick pulses |
|
|
||||||
| Teal | Modern, clarity | Clean, snappy transitions |
|
|
||||||
|
|
||||||
### Color Transition Rules
|
|
||||||
- Success: transition TO green (don't start with it)
|
|
||||||
- Error: flash red then settle (don't sustain)
|
|
||||||
- Warning: pulse yellow/amber for urgency
|
|
||||||
- Neutral: use opacity rather than color change
|
|
||||||
|
|
||||||
## Context-Based Emotion Defaults
|
|
||||||
|
|
||||||
| Context | Default Emotion |
|
|
||||||
|---------|----------------|
|
|
||||||
| Form success | Joy + Confidence |
|
|
||||||
| Validation error | Mild urgency |
|
|
||||||
| Page load | Calm + Confidence |
|
|
||||||
| Navigation | Confidence |
|
|
||||||
| Notification | Mild surprise |
|
|
||||||
| Loading | Calm |
|
|
||||||
| Onboarding | Curiosity + Delight |
|
|
||||||
| Dashboard | Calm + Confidence |
|
|
||||||
| Purchase complete | Joy + Confidence |
|
|
||||||
| Delete/remove | Calm (respectful departure) |
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
# Motion Personality
|
|
||||||
|
|
||||||
## Four Archetypes
|
|
||||||
|
|
||||||
### Playful
|
|
||||||
|
|
||||||
| Parameter | Value |
|
|
||||||
|-----------|-------|
|
|
||||||
| Duration | 150-300ms |
|
|
||||||
| Easing | ease-out-back / bouncy springs |
|
|
||||||
| Overshoot | 10-20% |
|
|
||||||
| Paths | Arcs and curves, never straight |
|
|
||||||
| Squash-stretch | Yes, on impacts |
|
|
||||||
|
|
||||||
Signature: bounce settle, squash-stretch on press, rotation wobble, bright color pops, varied stagger timing.
|
|
||||||
Use for: children's apps, casual games, social media, celebrations, onboarding, creative tools.
|
|
||||||
|
|
||||||
### Premium / Luxury
|
|
||||||
|
|
||||||
| Parameter | Value |
|
|
||||||
|-----------|-------|
|
|
||||||
| Duration | 350-600ms |
|
|
||||||
| Easing | cubic-bezier(0.4, 0, 0.2, 1) |
|
|
||||||
| Overshoot | 0% |
|
|
||||||
| Paths | Smooth curves, subtle parallax |
|
|
||||||
| Squash-stretch | Never |
|
|
||||||
|
|
||||||
Signature: slow fades, subtle scale (98%→100%), generous pauses, minimal properties (opacity+one), ultra-smooth.
|
|
||||||
Use for: fashion, finance, luxury brands, premium SaaS, portfolios, editorial.
|
|
||||||
|
|
||||||
### Corporate / Professional
|
|
||||||
|
|
||||||
| Parameter | Value |
|
|
||||||
|-----------|-------|
|
|
||||||
| Duration | 200-400ms |
|
|
||||||
| Easing | cubic-bezier(0.2, 0, 0, 1) |
|
|
||||||
| Overshoot | 0-3% |
|
|
||||||
| Paths | Mostly straight, small arcs for emphasis |
|
|
||||||
| Squash-stretch | No |
|
|
||||||
|
|
||||||
Signature: consistent timing, clear state transitions, functional motion, predictable patterns, uniform stagger.
|
|
||||||
Use for: enterprise, dashboards, business tools, admin, healthcare, banking.
|
|
||||||
|
|
||||||
### Energetic / Dynamic
|
|
||||||
|
|
||||||
| Parameter | Value |
|
|
||||||
|-----------|-------|
|
|
||||||
| Duration | 100-250ms |
|
|
||||||
| Easing | ease-out-expo / elastic |
|
|
||||||
| Overshoot | 15-30% |
|
|
||||||
| Paths | Dramatic arcs, large displacement, diagonal |
|
|
||||||
| Squash-stretch | Yes, exaggerated |
|
|
||||||
|
|
||||||
Signature: large scale changes (50-150%), fast color transitions, particle bursts, accelerating stagger, bold edge entrances.
|
|
||||||
Use for: gaming, sports, music, events, marketing, fitness apps.
|
|
||||||
|
|
||||||
## Keyword Matching
|
|
||||||
|
|
||||||
| Keywords | Archetype |
|
|
||||||
|----------|-----------|
|
|
||||||
| fun, whimsical, bouncy, cute, friendly | Playful |
|
|
||||||
| elegant, minimal, luxury, sophisticated | Premium |
|
|
||||||
| clean, professional, business, dashboard | Corporate |
|
|
||||||
| dynamic, energetic, bold, exciting | Energetic |
|
|
||||||
| (unspecified) + UI | Corporate (default) |
|
|
||||||
| (unspecified) + illustration | Playful (default) |
|
|
||||||
|
|
||||||
## Brand Motion Identity
|
|
||||||
|
|
||||||
Define three constants for recognizable motion:
|
|
||||||
|
|
||||||
### 1. Signature Easing (80% of animations)
|
|
||||||
Playful: ease-out-back | Premium: (0.4,0,0.2,1) | Corporate: (0.2,0,0,1) | Energetic: ease-out-expo
|
|
||||||
|
|
||||||
### 2. Duration Palette
|
|
||||||
|
|
||||||
| Tier | Playful | Premium | Corporate | Energetic |
|
|
||||||
|------|---------|---------|-----------|-----------|
|
|
||||||
| Quick | 150ms | 350ms | 200ms | 100ms |
|
|
||||||
| Standard | 250ms | 500ms | 300ms | 180ms |
|
|
||||||
| Slow | 400ms | 800ms | 450ms | 300ms |
|
|
||||||
|
|
||||||
### 3. Entrance Pattern
|
|
||||||
Playful: bounce up from below | Premium: slow fade + scale 98%→100% | Corporate: slide right + opacity | Energetic: snap from edge + overshoot
|
|
||||||
|
|
||||||
## Mixing Archetypes
|
|
||||||
- 90% primary archetype; specific moments can borrow another
|
|
||||||
- Ease into personality shifts, don't snap
|
|
||||||
- Example: corporate dashboard borrows Playful for success state only
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# Narrative Structure
|
|
||||||
|
|
||||||
## Four-Act Structure
|
|
||||||
|
|
||||||
### Act 1: Anticipation (10-20%)
|
|
||||||
- Wind-up: small motion opposite to main direction
|
|
||||||
- Gathering: elements pull together before dispersing
|
|
||||||
- Dimming: context fades for focus; Tension: hold compressed ~50ms
|
|
||||||
- Skip for <150ms interactions and hover states
|
|
||||||
|
|
||||||
### Act 2: Action (30-50%)
|
|
||||||
Peak energy. The primary communicative motion.
|
|
||||||
- Fast+direct (sharp easing) → alerts
|
|
||||||
- Smooth+flowing (gentle easing, curves) → transitions
|
|
||||||
- Explosive+expanding (expo, radial) → celebrations
|
|
||||||
- Controlled+precise (linear-ish) → data charts
|
|
||||||
|
|
||||||
### Act 3: Reaction (10-20%)
|
|
||||||
- Shadows adjust: 50-100ms after primary
|
|
||||||
- Siblings shift: 50-150ms after primary
|
|
||||||
- Environment ripples: 100-200ms after primary
|
|
||||||
- Counter-motion: simultaneous with action
|
|
||||||
- Skip for simple toggles/checkboxes
|
|
||||||
|
|
||||||
### Act 4: Resolution (20-30%)
|
|
||||||
- Overshoot settle (spring/back-easing); opacity reaches final (ease-out)
|
|
||||||
- 100-200ms breathing room before next motion
|
|
||||||
- Even 50ms of settling transforms the feel
|
|
||||||
|
|
||||||
## Scaling to Duration
|
|
||||||
|
|
||||||
| Total | Anticipation | Action | Reaction | Resolution |
|
|
||||||
|-------|-------------|--------|----------|------------|
|
|
||||||
| 100-200ms | skip | 60-70% | skip | 30-40% |
|
|
||||||
| 200-400ms | 10-15% | 40-50% | 10-15% | 25-30% |
|
|
||||||
| 400-800ms | 15-20% | 30-40% | 15-20% | 20-25% |
|
|
||||||
| 800ms+ | 20% | 30-35% | 15-20% | 25-30% |
|
|
||||||
|
|
||||||
## Multi-Beat Narratives
|
|
||||||
|
|
||||||
Transitions: overlap (fluid) | sequential (clear) | simultaneous (parallel)
|
|
||||||
|
|
||||||
Progressions:
|
|
||||||
- **Build-Up**: low → rising → peak → settling
|
|
||||||
- **Cycle**: depart → peak → return → repeat
|
|
||||||
- **Impact**: sudden action → ripple → slow settle
|
|
||||||
|
|
||||||
## By Personality
|
|
||||||
|
|
||||||
| Personality | Anticipation | Action | Resolution |
|
|
||||||
|------------|-------------|--------|------------|
|
|
||||||
| Playful | Exaggerated wind-up | Bouncy, overshoot | Wobble settle |
|
|
||||||
| Premium | Subtle tension | Smooth, controlled | Elegant ease |
|
|
||||||
| Corporate | Minimal/none | Direct, efficient | Clean stop |
|
|
||||||
| Energetic | Quick gather | Explosive | Fast snap |
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
- **Reveal**: tension (dim, scale 95%) → emerge (100%, full opacity) → surroundings adjust → settled
|
|
||||||
- **Departure**: gather (scale 98%) → exit → close gap → layout settles
|
|
||||||
- **Transformation**: destabilize (vibration) → morph → secondary appears → new state breathing
|
|
||||||
- **Celebration**: compress (50-100ms) → burst (scale, particles) → settle → calm with positive state
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# Ambient & Continuous Patterns
|
|
||||||
|
|
||||||
Ambient amplitude: 10-20% of primary motion. Never compete for attention.
|
|
||||||
|
|
||||||
## Breathing / Pulse
|
|
||||||
|
|
||||||
- Scale oscillation 0.98-1.02, sine ease-in-out, 2000-4000ms/cycle
|
|
||||||
- Alt: opacity oscillation 0.7-1.0
|
|
||||||
|
|
||||||
| Context | Scale Range | Duration | Opacity Range |
|
|
||||||
|---------|-----------|----------|--------------|
|
|
||||||
| Active indicator | 0.95-1.05 | 2000ms | 0.6-1.0 |
|
|
||||||
| Waiting/idle | 0.98-1.02 | 3000ms | 0.8-1.0 |
|
|
||||||
| Background element | 0.99-1.01 | 4000ms | 0.9-1.0 |
|
|
||||||
| CTA attention | 0.97-1.03 | 2500ms | 0.7-1.0 |
|
|
||||||
|
|
||||||
Pulsing >±5% scale becomes attention-demanding.
|
|
||||||
|
|
||||||
## Floating / Hovering
|
|
||||||
|
|
||||||
- Y position ±5-15px, sine ease-in-out, 3000-5000ms/cycle
|
|
||||||
- Optional: slight rotation ±2-3°, offset 30% of position cycle
|
|
||||||
|
|
||||||
**Layered floating** (multiple elements — different durations prevent sync):
|
|
||||||
- Element 1: 4000ms, ±10px
|
|
||||||
- Element 2: 5500ms, ±8px (offset 30%)
|
|
||||||
- Element 3: 3500ms, ±12px (offset 60%)
|
|
||||||
|
|
||||||
## Gradient Shift
|
|
||||||
|
|
||||||
- Background-position or gradient angle shift
|
|
||||||
- Duration: 8000-20000ms/cycle; range: ±10-20% position or ±15° angle
|
|
||||||
- Easing: linear or sine; should be imperceptible at a glance
|
|
||||||
|
|
||||||
## Parallax
|
|
||||||
|
|
||||||
| Layer | Speed Ratio | Content |
|
|
||||||
|-------|------------|---------|
|
|
||||||
| Foreground | 1.0x | Interactive, text |
|
|
||||||
| Midground | 0.5x | Decorative, cards |
|
|
||||||
| Background | 0.2x | Patterns, scenery |
|
|
||||||
| Deep background | 0.1x | Texture, atmospheric |
|
|
||||||
|
|
||||||
**Scroll-driven**: total displacement <100px; avoid on mobile; never parallax text
|
|
||||||
**Mouse-driven**: foreground 10-20px max, background 5-10px opposite direction, 100-200ms interpolation
|
|
||||||
|
|
||||||
## Continuous Rotation
|
|
||||||
|
|
||||||
- Spinners: linear easing, 1000-2000ms/revolution
|
|
||||||
|
|
||||||
| Variant | Speed | Use Case |
|
|
||||||
|---------|-------|----------|
|
|
||||||
| Slow spin | 10-30s/rev | Background decoration |
|
|
||||||
| Gear-like | 3-5s/rev | Technical/mechanical |
|
|
||||||
| Orbital | 5-15s/rev | Space/science theme |
|
|
||||||
| Wobble | 2-4s/cycle, sine | Playful idle |
|
|
||||||
|
|
||||||
## Shimmer / Gleam
|
|
||||||
|
|
||||||
- Gradient sweep left-to-right, 1500-2500ms/sweep
|
|
||||||
- Pause 2000-5000ms between sweeps
|
|
||||||
- Opacity gradient: 0%→30%→0%
|
|
||||||
- Use for: skeleton loading, premium accents, "new" badges
|
|
||||||
|
|
||||||
## Particle Ambient
|
|
||||||
|
|
||||||
### Snow/Falling: 10-20 elements, 20-60px/s down, ±10-20px drift, 3-8px, 30-70% opacity
|
|
||||||
### Dust/Motes: 5-10 elements, 10-30px/s mixed, 2-5px, 20-50% opacity
|
|
||||||
### Sparkle/Stars: 8-15 elements, opacity pulse 0→100%→0, 500-1500ms/sparkle, random stagger
|
|
||||||
|
|
||||||
**Performance**: <20 ambient elements, transform+opacity only, larger/fewer over small/numerous
|
|
||||||
|
|
||||||
## Combining Ambient Layers
|
|
||||||
|
|
||||||
| Layer | Type | Example |
|
|
||||||
|-------|------|---------|
|
|
||||||
| Background | Gradient shift or parallax | Slow color temperature change |
|
|
||||||
| Midground | Floating or particles | Gentle floating elements |
|
|
||||||
| Foreground | Breathing or shimmer | Subtle pulse on content |
|
|
||||||
|
|
||||||
Total ambient: max 20% of primary motion's visual energy.
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
# Entrance & Exit Patterns
|
|
||||||
|
|
||||||
## Entrance Strategies
|
|
||||||
|
|
||||||
### 1. Direct Entrance (Slide In)
|
|
||||||
- Position + opacity; offset 20-40px + opacity 0 → final position + opacity 1
|
|
||||||
- Easing: ease-out; duration 200-350ms
|
|
||||||
|
|
||||||
| Personality | Offset | Easing | Overshoot |
|
|
||||||
|------------|--------|--------|-----------|
|
|
||||||
| Playful | 30-50px | ease-out-back | 10-15% |
|
|
||||||
| Premium | 15-25px | (0.4, 0, 0.2, 1) | 0% |
|
|
||||||
| Corporate | 20-30px | (0.2, 0, 0, 1) | 0-3% |
|
|
||||||
| Energetic | 40-80px | ease-out-expo | 15-25% |
|
|
||||||
|
|
||||||
Direction: below=arrival, right=forward, left=back, above=dropdown/authority.
|
|
||||||
|
|
||||||
### 2. Emergent Entrance (Scale In)
|
|
||||||
- Scale + opacity; start 85-95% + opacity 0 → 100% + opacity 1
|
|
||||||
- Duration 250-400ms
|
|
||||||
|
|
||||||
| Personality | Start Scale | Easing | Overshoot |
|
|
||||||
|------------|------------|--------|-----------|
|
|
||||||
| Playful | 70-80% | ease-out-back | 10-20% |
|
|
||||||
| Premium | 95-98% | (0.4, 0, 0.2, 1) | 0% |
|
|
||||||
| Corporate | 90-95% | (0.2, 0, 0, 1) | 0-3% |
|
|
||||||
| Energetic | 50-70% | ease-out-expo | 15-30% |
|
|
||||||
|
|
||||||
Best for: modals, dialogs, notifications, popovers, tooltips.
|
|
||||||
|
|
||||||
### 3. Reveal Entrance (Clip/Mask)
|
|
||||||
- clip-path or mask + opacity; 300-500ms, ease-out
|
|
||||||
- Directions: top-to-bottom (dramatic), L-to-R (reading order), center-out (focus), edge-in (contained)
|
|
||||||
|
|
||||||
### 4. Assembled Entrance (Multi-Part)
|
|
||||||
- Parts arrive from different positions; stagger 50-100ms; total 300-600ms
|
|
||||||
- Best for: icon assembly, logo builds, data viz construction
|
|
||||||
|
|
||||||
## Exit Strategies
|
|
||||||
|
|
||||||
**Rule**: Exits = 65-75% of entrance duration.
|
|
||||||
|
|
||||||
### 1. Direct Exit (Slide Out)
|
|
||||||
- Offset 20-40px + opacity 0; ease-in; 150-250ms
|
|
||||||
|
|
||||||
### 2. Dissolve Exit (Fade Out)
|
|
||||||
- Opacity (+ optional scale to 98%); ease-in; 150-250ms
|
|
||||||
- Best for: gentle departures, backgrounding, crossfades
|
|
||||||
|
|
||||||
### 3. Collapse Exit (Shrink Out)
|
|
||||||
- Scale 85-95% + opacity 0; ease-in; 150-250ms
|
|
||||||
- Best for: deletion, closing modals, dismissal
|
|
||||||
|
|
||||||
### 4. Transfer Exit (Move Away)
|
|
||||||
- Position toward destination + scale shrink; ease-in-out; 250-400ms
|
|
||||||
- Best for: add-to-cart, save-to-collection, move-to-folder
|
|
||||||
|
|
||||||
## Entrance-Exit Continuity
|
|
||||||
- Eye follows naturally from exit to entrance
|
|
||||||
- Exit point near entry point when possible
|
|
||||||
- 100-150ms timing overlap between exit and entrance
|
|
||||||
- Same easing family for paired entrance-exit
|
|
||||||
|
|
||||||
## Common Recipes
|
|
||||||
|
|
||||||
### Notification Slide-In
|
|
||||||
1. Slide from right + opacity (250ms, ease-out)
|
|
||||||
2. Overshoot 3-5% (corporate) or 10-15% (playful)
|
|
||||||
3. Icon appears (100ms, 50ms delay)
|
|
||||||
|
|
||||||
### Toast Dismiss
|
|
||||||
1. Slide toward edge + opacity (180ms, ease-in)
|
|
||||||
2. Remaining toasts shift up (200ms, ease-in-out)
|
|
||||||
|
|
||||||
### Dropdown Open
|
|
||||||
1. Scale Y 0→100% (200ms, ease-out)
|
|
||||||
2. Items stagger fade in (30ms apart, 50ms after container)
|
|
||||||
|
|
||||||
### Page Transition (Forward)
|
|
||||||
1. Current page slides left + fades (300ms, ease-in)
|
|
||||||
2. New page from right + fades in (400ms, ease-out, 100ms delay)
|
|
||||||
3. Shared elements morph (400ms, ease-in-out)
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# Multi-Element Patterns
|
|
||||||
|
|
||||||
## Stagger Recipes
|
|
||||||
|
|
||||||
### List Items
|
|
||||||
- Slide up 20px + fade (200ms, ease-out); stagger 40-60ms; total <400ms for 8 items
|
|
||||||
|
|
||||||
| Personality | Duration | Stagger | Easing |
|
|
||||||
|------------|---------|---------|--------|
|
|
||||||
| Playful | 250ms | 60ms | ease-out-back |
|
|
||||||
| Premium | 350ms | 80ms | (0.4, 0, 0.2, 1) |
|
|
||||||
| Corporate | 200ms | 50ms | (0.2, 0, 0, 1) |
|
|
||||||
| Energetic | 150ms | 30ms | ease-out-expo |
|
|
||||||
|
|
||||||
### Grid Cards
|
|
||||||
- Scale from 95% + fade (250ms, ease-out); stagger 50-80ms reading order; +20ms per new row
|
|
||||||
- Shadow 50ms after card. Alt patterns: center-out, column-first, random
|
|
||||||
|
|
||||||
### Navigation Items
|
|
||||||
- Slide from side + fade (180ms, ease-out); stagger 30-50ms; total <300ms
|
|
||||||
|
|
||||||
### Dashboard Widgets
|
|
||||||
```
|
|
||||||
0ms — Skeletons visible
|
|
||||||
100ms — Hero metric (250ms, ease-out)
|
|
||||||
200ms — Widgets begin (200ms each, 60ms stagger)
|
|
||||||
350ms — Hero complete, chart draws (300ms)
|
|
||||||
500ms — All landed
|
|
||||||
650ms — Ambient pulse begins
|
|
||||||
```
|
|
||||||
|
|
||||||
## Coordinated Sequences
|
|
||||||
|
|
||||||
### Modal with Content
|
|
||||||
0ms: backdrop dims (200ms) → 50ms: modal scales 95%→100% (300ms) → 200ms: title → 280ms: body → 350ms: buttons → 400ms: close button. Content in reading order.
|
|
||||||
|
|
||||||
### Tab Switch
|
|
||||||
0ms: indicator slides (250ms, ease-in-out) + old fades (150ms) → 100ms: new content from tab direction (200ms) → 150ms: elements stagger (40ms)
|
|
||||||
|
|
||||||
### Accordion
|
|
||||||
Expand: arrow rotates (150ms) + height expands (250ms) + content fades at 50ms (200ms); siblings shift (200ms). Collapse: reverse, ease-in.
|
|
||||||
|
|
||||||
### Page Transition
|
|
||||||
Current slides left+fades (300ms, ease-in) → new from right (400ms, ease-out, 100ms delay) → hero scales in → content staggers (50ms). Optional shared element morph (400ms).
|
|
||||||
|
|
||||||
### Drag and Drop
|
|
||||||
Drag: lift (scale 1.03, 150ms); others shift (200ms). Drop: settle (scale 1.0, 200ms); gaps close.
|
|
||||||
|
|
||||||
## Choreography Rules
|
|
||||||
|
|
||||||
### Timing Overlap
|
|
||||||
0%=methodical | 25%=brisk (standard) | 50%=fluid | 75%=rapid (energetic)
|
|
||||||
|
|
||||||
### Group Rules
|
|
||||||
- Same easing family; different durations/delays fine
|
|
||||||
- Exception: secondary/ambient can differ
|
|
||||||
|
|
||||||
### Shared Origin
|
|
||||||
Motion from trigger point; closer=first; farther=more delay
|
|
||||||
|
|
||||||
### Counter-Motion
|
|
||||||
|
|
||||||
| Hero Does | Counter-Motion |
|
|
||||||
|-----------|---------------|
|
|
||||||
| Slides right | Background left (20-30%) |
|
|
||||||
| Scales up | Shadow spreads |
|
|
||||||
| Lifts up | Shadow drops+softens |
|
|
||||||
| Rotates CW | Ambient CCW |
|
|
||||||
| Expands | Siblings compress |
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
# State & Feedback Patterns
|
|
||||||
|
|
||||||
## Button Press
|
|
||||||
|
|
||||||
### Playful
|
|
||||||
- Press: scale 0.95 (60ms, ease-out); Release: overshoot 1.05 (80ms); Settle: 1.0 (120ms, spring)
|
|
||||||
- Secondary: shadow shrinks/grows; color darkens/brightens. Total ~260ms
|
|
||||||
|
|
||||||
### Premium
|
|
||||||
- Press: scale 0.98 (80ms); Release: 1.0 (150ms, no overshoot)
|
|
||||||
- Opacity dims to 90% on press. Total ~230ms
|
|
||||||
|
|
||||||
### Corporate
|
|
||||||
- Press: scale 0.97 (60ms); Release: 1.0 (100ms); overshoot 0-2%
|
|
||||||
- Background darkens 10%. Total ~160ms
|
|
||||||
|
|
||||||
## Hover States
|
|
||||||
|
|
||||||
| Element | Effect | Duration |
|
|
||||||
|---------|--------|----------|
|
|
||||||
| Button | Scale 1.02-1.05 | <100ms |
|
|
||||||
| Card | Scale 1.01-1.02 + shadow lift | <100ms |
|
|
||||||
| Link | Color change + underline | <100ms |
|
|
||||||
| Icon | Scale 1.1 + rotation 2-5° | <100ms |
|
|
||||||
| Image | Scale 1.03 (overflow hidden) | 150ms |
|
|
||||||
|
|
||||||
Hover enter <100ms; hover exit 150-200ms (slower = polished).
|
|
||||||
|
|
||||||
## Toggle / Switch
|
|
||||||
- Thumb slides (120-180ms, ease-in-out); track color transitions simultaneously
|
|
||||||
- Slight squash in movement direction
|
|
||||||
- Playful: bounce at destination; Premium: smooth, no overshoot
|
|
||||||
|
|
||||||
## Success State
|
|
||||||
|
|
||||||
### Checkmark Success
|
|
||||||
1. Container: scale 0.9→1.0 (200ms, ease-out-back, 5-10% overshoot)
|
|
||||||
2. Checkmark: stroke draw (150ms, ease-out, 100ms delay)
|
|
||||||
3. Color: to green (200ms); ambient glow/particles (300ms)
|
|
||||||
4. Total: 400-500ms
|
|
||||||
|
|
||||||
### Confirmation Badge
|
|
||||||
1. Badge scales from 0 (200ms, ease-out-back)
|
|
||||||
2. Text fades in (150ms, 50ms delay)
|
|
||||||
3. Background pulse (300ms, sine)
|
|
||||||
|
|
||||||
### Payment Success
|
|
||||||
1. Spinner → checkmark crossfade (200ms)
|
|
||||||
2. Checkmark draws (200ms); container → success color (200ms)
|
|
||||||
3. Text fades in (200ms, 100ms delay); optional confetti (300-500ms)
|
|
||||||
|
|
||||||
## Error State
|
|
||||||
|
|
||||||
### Error Shake
|
|
||||||
- Horizontal oscillation ±10-15px, 2-3 cycles decreasing amplitude
|
|
||||||
- ease-in-out, 300-400ms total; red tint; no overshoot; settles at origin
|
|
||||||
|
|
||||||
### Inline Validation
|
|
||||||
1. Error text slides down + fades in (200ms)
|
|
||||||
2. Border → red (150ms); icon scales in (150ms, 50ms delay)
|
|
||||||
3. Optional single shake (200ms)
|
|
||||||
|
|
||||||
### Form Submission Error
|
|
||||||
1. Button returns to normal (200ms)
|
|
||||||
2. Error message slides in (250ms)
|
|
||||||
3. Affected fields highlight red (150ms, staggered 30ms)
|
|
||||||
4. Smooth scroll to first error (300ms, ease-in-out)
|
|
||||||
|
|
||||||
## Loading States
|
|
||||||
|
|
||||||
### Spinner
|
|
||||||
- Continuous 360°, linear, 1000-1500ms/rev; optional breathing pulse (2-3s)
|
|
||||||
|
|
||||||
### Skeleton
|
|
||||||
- Gradient sweep L→R, 1500-2000ms; base 10-20% opacity, peak 30-40%
|
|
||||||
|
|
||||||
### Progress Bar
|
|
||||||
- Width/transform, ease-in-out; optional color milestones + shimmer
|
|
||||||
|
|
||||||
### Indeterminate
|
|
||||||
- Oscillating position/width, 1500-2500ms, ease-in-out; continuous, never frantic
|
|
||||||
|
|
||||||
## Warning State
|
|
||||||
1. Yellow/amber border (150ms)
|
|
||||||
2. Warning icon scales in (150ms, subtle overshoot)
|
|
||||||
3. Optional icon pulse (2-3s, sine); text fades in (200ms, 50ms delay)
|
|
||||||
|
|
||||||
## Disabled / Enabled
|
|
||||||
|
|
||||||
- **Disabling**: opacity to 50-60% (200ms); optional scale to 98%
|
|
||||||
- **Enabling**: opacity to 100% (200ms); optional scale pulse 98%→100%
|
|
||||||
|
|
||||||
## Focus States
|
|
||||||
- Focus ring: scale 95%→100% + opacity (150ms)
|
|
||||||
- Card focus: scale 1.01, shadow increase (150ms)
|
|
||||||
- Tab nav focus: <100ms; must work with reduced-motion
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# Property Selection
|
|
||||||
|
|
||||||
## Position
|
|
||||||
|
|
||||||
| Direction | Meaning |
|
|
||||||
|-----------|---------|
|
|
||||||
| Upward | Growth, improvement, aspiration |
|
|
||||||
| Downward | Settling, completion, grounding |
|
|
||||||
| Leftward | Regression, backward, departure |
|
|
||||||
| Rightward | Progression, forward, arrival |
|
|
||||||
| Toward center | Focus, convergence |
|
|
||||||
| Away from center | Distribution, expansion |
|
|
||||||
|
|
||||||
## Scale
|
|
||||||
|
|
||||||
| Direction | Meaning |
|
|
||||||
|-----------|---------|
|
|
||||||
| Scale up | Importance, activation, proximity |
|
|
||||||
| Scale down | Deprioritization, distance |
|
|
||||||
| Pulse | Attention, heartbeat, life |
|
|
||||||
| Breathing | Presence, waiting |
|
|
||||||
|
|
||||||
## Rotation
|
|
||||||
|
|
||||||
| Range | Meaning |
|
|
||||||
|-------|---------|
|
|
||||||
| Small (5-15°) | Subtle adjustment |
|
|
||||||
| Medium (45-90°) | Transformation |
|
|
||||||
| Full (360°) | Completion, processing |
|
|
||||||
| Continuous | Ongoing activity |
|
|
||||||
|
|
||||||
## Opacity
|
|
||||||
|
|
||||||
| Direction | Meaning |
|
|
||||||
|-----------|---------|
|
|
||||||
| Fade in | Arrival, enablement |
|
|
||||||
| Fade out | Departure, disablement |
|
|
||||||
| Partial | Secondary, disabled state |
|
|
||||||
|
|
||||||
**Rule**: NEVER opacity alone for important state changes. Combine with position or scale.
|
|
||||||
|
|
||||||
## Color
|
|
||||||
|
|
||||||
| Transition | Meaning |
|
|
||||||
|-----------|---------|
|
|
||||||
| To green | Success, go |
|
|
||||||
| To red | Error, stop |
|
|
||||||
| To blue | Trust, active |
|
|
||||||
| To gray | Disabled, inactive |
|
|
||||||
| Brightening | Activation, focus |
|
|
||||||
| Dimming | Deactivation, background |
|
|
||||||
|
|
||||||
## Combined Properties
|
|
||||||
|
|
||||||
| Combination | Best For |
|
|
||||||
|-------------|----------|
|
|
||||||
| Position + Opacity | Content appearing/disappearing |
|
|
||||||
| Scale + Opacity | Cards, modals, notifications |
|
|
||||||
| Position + Scale | Selected items, focused content |
|
|
||||||
| Rotation + Scale | Celebrations, playful activation |
|
|
||||||
| Position + Rotation | Organic transitions |
|
|
||||||
| Color + Scale | State emphasis |
|
|
||||||
|
|
||||||
Primary property carries meaning; secondary adds polish. Two properties is the sweet spot.
|
|
||||||
|
|
||||||
## Property Selection by Goal
|
|
||||||
|
|
||||||
| Goal | Primary | Secondary | Avoid |
|
|
||||||
|------|---------|-----------|-------|
|
|
||||||
| Entrance | position | opacity | rotation |
|
|
||||||
| Exit | position | opacity | scale up |
|
|
||||||
| Button press | scale | color | position |
|
|
||||||
| Hover | scale or color | opacity | position |
|
|
||||||
| Success | scale | color + opacity | position |
|
|
||||||
| Error | position (shake) | color | scale |
|
|
||||||
| Loading | rotation | opacity | position |
|
|
||||||
| Toggle | position | color | rotation |
|
|
||||||
| Notification | position + scale | opacity | rotation |
|
|
||||||
| Delete | scale + opacity | position | grow |
|
|
||||||
| Selection | scale | color, opacity | rotation |
|
|
||||||
| Progress | position or scale | color | opacity |
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
| Property | Performance |
|
|
||||||
|----------|-------------|
|
|
||||||
| transform (translate, scale, rotate) | Excellent — GPU-accelerated |
|
|
||||||
| opacity | Excellent — GPU-accelerated |
|
|
||||||
| color / background-color | Good — triggers paint |
|
|
||||||
| clip-path | Good — GPU on modern browsers |
|
|
||||||
| width / height | Poor — triggers layout |
|
|
||||||
| margin / padding | Poor — triggers layout |
|
|
||||||
| box-shadow | Poor — expensive paint |
|
|
||||||
|
|
||||||
**Rule**: Prefer transform + opacity for all motion.
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Quality Checklist
|
|
||||||
|
|
||||||
## Visual Quality
|
|
||||||
- [ ] Elements >40px for motion, >100px for detail
|
|
||||||
- [ ] Readable at full speed without slow-motion
|
|
||||||
- [ ] Clear primary, secondary, ambient layers
|
|
||||||
- [ ] Counter-motion for balance where needed
|
|
||||||
- [ ] Natural arcs (unless intentionally mechanical)
|
|
||||||
- [ ] 1/3 rule (distance): no unbroken motion >1/3 container
|
|
||||||
- [ ] 1/3 rule (density): max 1/3 elements active simultaneously
|
|
||||||
|
|
||||||
## Technical Quality
|
|
||||||
- [ ] No linear easing on spatial movement
|
|
||||||
- [ ] Duration matches element type table
|
|
||||||
- [ ] Ease-out entrances, ease-in exits
|
|
||||||
- [ ] Duration proportional to distance
|
|
||||||
- [ ] Entrance duration >= exit duration
|
|
||||||
- [ ] Not opacity-only for important state changes
|
|
||||||
- [ ] Stagger total <500ms
|
|
||||||
- [ ] Follow-through: child elements offset 50-150ms
|
|
||||||
|
|
||||||
## Emotional Quality
|
|
||||||
- [ ] Target emotion identified before properties
|
|
||||||
- [ ] Personality archetype matches brand
|
|
||||||
- [ ] Setup → action → resolution structure
|
|
||||||
- [ ] Intensity matches interaction importance
|
|
||||||
- [ ] Consistent: same interaction = same motion
|
|
||||||
- [ ] Appropriate on 100th viewing
|
|
||||||
|
|
||||||
## Performance Quality
|
|
||||||
- [ ] Primary motion uses transform + opacity
|
|
||||||
- [ ] <20 animated elements per viewport
|
|
||||||
- [ ] No layout-triggering properties animated
|
|
||||||
- [ ] Elements staggered, not simultaneous
|
|
||||||
- [ ] Maintains 60fps (30fps acceptable for ambient)
|
|
||||||
|
|
||||||
## Accessibility Quality
|
|
||||||
- [ ] prefers-reduced-motion alternative provided
|
|
||||||
- [ ] No vestibular triggers without alternative
|
|
||||||
- [ ] Same interaction = same animation
|
|
||||||
- [ ] Critical info not motion-only
|
|
||||||
- [ ] Animations >5s are pausable
|
|
||||||
|
|
||||||
## Severity Tiers
|
|
||||||
|
|
||||||
### CRITICAL
|
|
||||||
- Linear easing on spatial movement
|
|
||||||
- Opacity-only for important states
|
|
||||||
- Exceeds 1/3 screen rule
|
|
||||||
- Missing primary layer
|
|
||||||
- Stagger >500ms
|
|
||||||
- Layout property animation causing jank
|
|
||||||
|
|
||||||
### HIGH
|
|
||||||
- Missing secondary layer
|
|
||||||
- Duration mismatch with element type
|
|
||||||
- Wrong directional easing
|
|
||||||
- Inconsistent personality
|
|
||||||
- No follow-through
|
|
||||||
- Missing reduced-motion alternative
|
|
||||||
|
|
||||||
### MEDIUM
|
|
||||||
- Missing ambient layer
|
|
||||||
- No anticipation phase
|
|
||||||
- Overshoot mismatch
|
|
||||||
- Could use better arcs
|
|
||||||
- Missing counter-motion
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
# Timing & Easing Tables
|
|
||||||
|
|
||||||
## Duration by Element Type
|
|
||||||
|
|
||||||
| Element | Duration |
|
|
||||||
|---------|----------|
|
|
||||||
| Tooltip / micro-feedback | 80-120ms |
|
|
||||||
| Button / toggle | 120-180ms |
|
|
||||||
| Icon transition | 150-250ms |
|
|
||||||
| Card enter/exit | 200-350ms |
|
|
||||||
| Modal / dialog | 300-400ms |
|
|
||||||
| Page transition | 400-600ms |
|
|
||||||
| Dramatic reveal | 600-1200ms |
|
|
||||||
| Ambient | 2000-20000ms |
|
|
||||||
|
|
||||||
## Distance-Duration Scaling
|
|
||||||
|
|
||||||
50px=0.8x | 100px=1.0x | 200px=1.3x | 300px=1.5x | 400px=1.6x | Full screen=1.8-2.0x
|
|
||||||
|
|
||||||
## Enter vs. Exit
|
|
||||||
Entrance = base (100%). Exit = 65-75% of entrance.
|
|
||||||
|
|
||||||
## Interactive Feedback
|
|
||||||
|
|
||||||
| Interaction | Max Latency |
|
|
||||||
|------------|-------------|
|
|
||||||
| Hover | <100ms |
|
|
||||||
| Press/tap | <150ms |
|
|
||||||
| Release/settle | 200-300ms |
|
|
||||||
| Error shake | 300-400ms |
|
|
||||||
| Long press | 500-800ms |
|
|
||||||
| Drag start | <50ms |
|
|
||||||
|
|
||||||
## Duration by Personality
|
|
||||||
|
|
||||||
| Personality | Quick | Standard | Slow |
|
|
||||||
|------------|-------|----------|------|
|
|
||||||
| Playful | 150ms | 250ms | 400ms |
|
|
||||||
| Premium | 350ms | 500ms | 800ms |
|
|
||||||
| Corporate | 200ms | 300ms | 450ms |
|
|
||||||
| Energetic | 100ms | 180ms | 300ms |
|
|
||||||
|
|
||||||
## Easing: Directional Rules
|
|
||||||
|
|
||||||
Entrance=ease-out | Exit=ease-in | On-screen=ease-in-out | Looping=sine | Rotation/progress=linear
|
|
||||||
|
|
||||||
## Easing: Industry Standards
|
|
||||||
|
|
||||||
| Name | Cubic Bezier | Use |
|
|
||||||
|------|-------------|-----|
|
|
||||||
| MD3 Standard | (0.2, 0, 0, 1) | Default on-screen |
|
|
||||||
| MD3 Emphasized | (0.05, 0.7, 0.1, 1) | Entrances |
|
|
||||||
| MD3 Accelerate | (0.3, 0, 1, 1) | Exits |
|
|
||||||
| MD3 Decelerate | (0, 0, 0, 1) | Entering |
|
|
||||||
| Apple HIG | (0.25, 0.1, 0.25, 1) | iOS default |
|
|
||||||
| Apple Spring | stiffness:300 damping:20 | Interactive |
|
|
||||||
| Snappy UI | (0.2, 0, 0, 1) | Fast, decisive |
|
|
||||||
| Gentle float | (0.4, 0, 0.2, 1) | Ambient |
|
|
||||||
| Bounce settle | (0.175, 0.885, 0.32, 1.275) | Playful |
|
|
||||||
| Elastic snap | (0.68, -0.55, 0.265, 1.55) | Dramatic |
|
|
||||||
|
|
||||||
## Material-Based Easing
|
|
||||||
|
|
||||||
| Material | Duration Scale | Overshoot |
|
|
||||||
|----------|---------------|-----------|
|
|
||||||
| Rigid (metal) | 1.2x | 0% |
|
|
||||||
| Elastic (rubber) | 0.8x | 15-25% |
|
|
||||||
| Fluid (water) | 1.5x | 5% |
|
|
||||||
| Paper (cards) | 1.0x | 3-5% |
|
|
||||||
| Gas (smoke) | 2.0x | 0% |
|
|
||||||
| Glass | 0.9x | 0% |
|
|
||||||
|
|
||||||
## Spring Parameters
|
|
||||||
|
|
||||||
| Feel | Stiffness | Damping |
|
|
||||||
|------|-----------|---------|
|
|
||||||
| Very stiff | 400+ | 25-30 |
|
|
||||||
| Standard | 250-350 | 18-24 |
|
|
||||||
| Bouncy | 150-250 | 10-15 |
|
|
||||||
| Very bouncy | 100-200 | 5-10 |
|
|
||||||
| Gentle | 100-150 | 20-25 |
|
|
||||||
|
|
||||||
Damping: <1.0=oscillates, 1.0=fastest no-oscillation, >1.0=slow settle.
|
|
||||||
|
|
||||||
## Stagger Patterns
|
|
||||||
|
|
||||||
| Pattern | Delay | Budget |
|
|
||||||
|---------|------|--------|
|
|
||||||
| Micro cascade | 20-40ms | <200ms |
|
|
||||||
| Standard | 50-100ms | <400ms |
|
|
||||||
| Dramatic | 100-200ms | <600ms |
|
|
||||||
| Wave | 30-60ms | <500ms |
|
|
||||||
|
|
||||||
Direction: top-to-bottom (lists) | L-to-R (horizontal) | center-out (hero) | random (organic) | reverse (exits)
|
|
||||||
|
|
||||||
Total stagger MUST stay <500ms.
|
|
||||||
|
|
||||||
## Overshoot Budget
|
|
||||||
|
|
||||||
| Context | Overshoot |
|
|
||||||
|---------|-----------|
|
|
||||||
| Success | 5-10% |
|
|
||||||
| Error | 0% |
|
|
||||||
| Feedback | 2-5% |
|
|
||||||
| Celebration | 15-25% |
|
|
||||||
| Premium | 0% |
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# Troubleshooting
|
|
||||||
|
|
||||||
## Looks Robotic
|
|
||||||
- Linear easing → use ease-out (entrance) or ease-in-out
|
|
||||||
- Straight paths → add 10-20px arc at midpoint
|
|
||||||
- Uniform timing → stagger 50-100ms between elements
|
|
||||||
- Everything synced → offset start/stop 50-150ms
|
|
||||||
- No secondary → add shadow, icon reaction, ambient
|
|
||||||
|
|
||||||
## Feels Too Slow
|
|
||||||
- Duration exceeds type budget → check duration table
|
|
||||||
- ease-in-out when ease-out works → ease-out feels faster
|
|
||||||
- Too much anticipation → reduce to 10% or remove
|
|
||||||
- Stagger exceeded → total <500ms
|
|
||||||
- Overshoot settle too long → increase damping
|
|
||||||
|
|
||||||
## Feels Too Fast / Jarring
|
|
||||||
- Duration below minimum → modals 300ms+, pages 400ms+
|
|
||||||
- No easing → add ease-out minimum
|
|
||||||
- Missing resolution → add 50-100ms settle
|
|
||||||
- No anticipation on large motion → add 100-200ms wind-up
|
|
||||||
|
|
||||||
## Feels Cheap / Flat
|
|
||||||
- Only primary motion → add secondary + ambient
|
|
||||||
- Opacity-only → combine with position or scale
|
|
||||||
- Same easing everywhere → vary primary vs secondary
|
|
||||||
- No follow-through → child elements trail 50-150ms
|
|
||||||
- No overshoot → add 3-10%
|
|
||||||
|
|
||||||
## Too Distracting
|
|
||||||
- Too many moving → 1/3 rule
|
|
||||||
- Amplitude too large → reduce to minimum
|
|
||||||
- Competing heroes → one per moment, dim rest
|
|
||||||
- Ambient too prominent → 10-20% amplitude, slower
|
|
||||||
- No breathing room → 100-200ms pause between beats
|
|
||||||
|
|
||||||
## No Personality
|
|
||||||
- Default easing → apply archetype's signature
|
|
||||||
- Same duration for all → use personality palette
|
|
||||||
- No consistent entrance → define one for project
|
|
||||||
- Mixed archetypes → pick one for 90%+
|
|
||||||
|
|
||||||
## Inconsistent Feel
|
|
||||||
- Different easing same-type → standardize per motion type
|
|
||||||
- Duration varies same type → use palette consistently
|
|
||||||
- Entry direction changes → one origin everywhere
|
|
||||||
- Overshoot inconsistent → apply rules consistently
|
|
||||||
|
|
||||||
## Performance (Dropped Frames)
|
|
||||||
- Animating width/height/margin → use transform
|
|
||||||
- Too many elements → <20 per viewport
|
|
||||||
- Complex shadows/filters → simplify or pre-render
|
|
||||||
- No GPU acceleration → transform + opacity
|
|
||||||
- All simultaneous → stagger to spread load
|
|
||||||
|
|
||||||
## Quick Diagnostic
|
|
||||||
|
|
||||||
1. No linear on spatial movement
|
|
||||||
2. Duration matches element type
|
|
||||||
3. Primary + secondary layers
|
|
||||||
4. Consistent personality
|
|
||||||
5. Directional easing correct
|
|
||||||
6. 1/3 screen rule
|
|
||||||
7. 1/3 element rule
|
|
||||||
8. Follow-through present
|
|
||||||
9. Every motion has purpose
|
|
||||||
10. Fine on 100th viewing
|
|
||||||
|
|
||||||
## Personality Mistakes
|
|
||||||
- **Playful**: overshoot >25% = broken; not everything bounces; short+bounce = glitchy
|
|
||||||
- **Premium**: too subtle = invisible; too slow = waiting; zero = broken
|
|
||||||
- **Corporate**: too conservative = boring; playful easing breaks trust; identical = monotonous
|
|
||||||
- **Energetic**: max everywhere = nothing stands out; too many particles; no settle = chaos
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/motion-design
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ff37d1cd
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.hallmark/shots/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
shots/
|
||||||
|
before/
|
||||||
|
before.json
|
||||||
|
before.txt
|
||||||
|
compare.html
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"shape": "Marquee Hero",
|
||||||
|
"theme": "squeeze",
|
||||||
|
"axes": "mid/geometric-sans/warm",
|
||||||
|
"hero": "Banner",
|
||||||
|
"nav": "edge-aligned",
|
||||||
|
"footer": "letter close",
|
||||||
|
"imagery": "photographs, library 1, natural",
|
||||||
|
"photos": [
|
||||||
|
"worm-bay"
|
||||||
|
],
|
||||||
|
"entry": "BeingOne front page",
|
||||||
|
"sections": [
|
||||||
|
"Hero",
|
||||||
|
"Path",
|
||||||
|
"Finder",
|
||||||
|
"Checks",
|
||||||
|
"Privacy",
|
||||||
|
"Talk",
|
||||||
|
"Family+Safety",
|
||||||
|
"Questions+Join",
|
||||||
|
"Footer"
|
||||||
|
],
|
||||||
|
"signature": "match finder with a tappable map of Australia and a moving score",
|
||||||
|
"waived": [],
|
||||||
|
"prompt": "BeingOne marriage site for Australians",
|
||||||
|
"date": "2026-10-23",
|
||||||
|
"mode": "build"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# BeingOne: plan (PRD)
|
||||||
|
|
||||||
|
Tagline: Real people. Real profiles. Real marriage.
|
||||||
|
For: Australians, and the families of Australians, who want to find a life partner. English only. Australia only.
|
||||||
|
Written for: Keith. Plain words on purpose.
|
||||||
|
|
||||||
|
## The big idea
|
||||||
|
|
||||||
|
BeingOne is a marriage website where every profile is checked to be a real person. People find matches, talk safely, and families can be part of it.
|
||||||
|
|
||||||
|
## The 13 core features (from Keith's plan)
|
||||||
|
|
||||||
|
1. Sign up and log in with email.
|
||||||
|
2. Make a profile: photos, age, city, job, things you love, a short "about me".
|
||||||
|
3. Check the profile is real: phone check, photo check, ID check, and a quick look by a person on the team.
|
||||||
|
4. Search by Australian city, age and shared interests.
|
||||||
|
5. Match score: a number showing how well two people fit.
|
||||||
|
6. Photo privacy: you decide who sees your photos.
|
||||||
|
7. Send an interest. The other person accepts or says no thanks.
|
||||||
|
8. Private chat that opens only when both say yes.
|
||||||
|
9. Family view: a parent or sibling helps look at profiles.
|
||||||
|
10. Safety tools: block, report, hide your profile.
|
||||||
|
11. Free plan and paid plan (payments).
|
||||||
|
12. Admin area for the BeingOne team: review profiles, handle reports, see numbers.
|
||||||
|
13. Emails and phone alerts: new matches, interests, messages.
|
||||||
|
|
||||||
|
## Phase 1 (today): the new look, with working demos
|
||||||
|
|
||||||
|
What you get: one front page in a new design. Everything on it is a demo that runs in your browser, with made-up sample people.
|
||||||
|
|
||||||
|
| Feature | Where it shows up on the page |
|
||||||
|
| --- | --- |
|
||||||
|
| 1 Sign up, log in | The join card at the bottom (Join and Log in tabs). It does not save anything yet. |
|
||||||
|
| 2 Profile | Shown through the sample people and the checks demo. The real profile editor is Phase 3. |
|
||||||
|
| 3 Checks | "Every profile is checked": run the four checks on a sample profile. |
|
||||||
|
| 4 Search | "Find your match across Australia": map of Australia, city, ages, interests. |
|
||||||
|
| 5 Match score | The score and bar on every sample person. |
|
||||||
|
| 6 Photo privacy | "You choose who sees your photos": pick who can see, pick who is looking. |
|
||||||
|
| 7 Interest, accept | "Chat opens when both say yes": accept or say no thanks. |
|
||||||
|
| 8 Private chat | Same section: the chat unlocks after Accept. |
|
||||||
|
| 9 Family view | "Family view" tile: pick who helps you look. |
|
||||||
|
| 10 Safety tools | "Safety tools" tile: block, report, hide. |
|
||||||
|
| 11 Plans | The "What does it cost?" answer. The paid price is left blank until you give it. |
|
||||||
|
| 12 Admin | Mentioned in "Who runs the site?". The real admin area is Phase 7 and is a separate private page. |
|
||||||
|
| 13 Alerts | The "Will I get alerts?" answer. Real alerts are Phase 6. |
|
||||||
|
|
||||||
|
Tests, in this order:
|
||||||
|
1. The page loads with no errors.
|
||||||
|
2. Every button, chip, slider, tab and form reacts.
|
||||||
|
3. Real connections: none yet, so nothing to test.
|
||||||
|
4. Security: no passwords, no keys, no calls to other servers for data. The forms only show a message on screen.
|
||||||
|
|
||||||
|
## Phases 2 to 8 (one at a time, each must work before the next)
|
||||||
|
|
||||||
|
2. Real sign-up and login (needs a host, a domain, a privacy policy and a consent step).
|
||||||
|
3. Real profiles and photos, with private photo storage.
|
||||||
|
4. Real checks (phone code, ID check through a trusted provider, team review screen).
|
||||||
|
5. Real search and matching on real profiles.
|
||||||
|
6. Private chat, block and report, email and phone alerts.
|
||||||
|
7. Payments (test mode first) and the admin area.
|
||||||
|
8. Launch checks: speed, phones, privacy rules, backups, a trial with friends and family.
|
||||||
|
|
||||||
|
## Important
|
||||||
|
|
||||||
|
Marriage sites hold very private details. In Australia some of them are "sensitive information" under the Privacy Act 1988, so people must clearly agree to how they are used. Plan the privacy policy and consent step in Phase 2. Please check with a lawyer before launch. This is not legal advice.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# BeingOne
|
||||||
|
|
||||||
|
Real people. Real profiles. Real marriage.
|
||||||
|
|
||||||
|
BeingOne is a marriage website for Australians and their families. Every profile is checked to be a real person. People find matches, talk safely, and families can be part of it.
|
||||||
|
|
||||||
|
This is **Phase 1**: the front page, in its new design, with working demos that run in the browser. The people on it are made up. Nothing is saved and nothing is sent anywhere yet.
|
||||||
|
|
||||||
|
## See it
|
||||||
|
|
||||||
|
There is no build step. Open `index.html` in a browser.
|
||||||
|
|
||||||
|
## What is in this folder
|
||||||
|
|
||||||
|
| File | What it is |
|
||||||
|
| --- | --- |
|
||||||
|
| `index.html` | The page |
|
||||||
|
| `style.css` | How it looks (colours, fonts, spacing, motion) |
|
||||||
|
| `tokens.css`, `base.css` | The colour and font settings, and the basic rules |
|
||||||
|
| `app.js` | What the buttons, map, search, checks, chat and forms do |
|
||||||
|
| `images/hero.jpg` | The photo at the top of the page (swap in the original) |
|
||||||
|
| `PRD.md` | The plan: 13 features and the 8 phases |
|
||||||
|
|
||||||
|
## What the demos do
|
||||||
|
|
||||||
|
- **Search bar and map of Australia:** pick a city, ages and interests. Made-up people appear with a match score.
|
||||||
|
- **Four checks:** run the phone, photo, ID and team checks on a sample profile.
|
||||||
|
- **Photo privacy:** choose who can see a photo, and who is looking.
|
||||||
|
- **Interest and chat:** accept an interest and a private chat opens.
|
||||||
|
- **Family view and safety tools:** pick who helps you look; block, report and hide.
|
||||||
|
- **Join and log in:** forms that do not send anything yet.
|
||||||
|
|
||||||
|
## Before it goes live
|
||||||
|
|
||||||
|
- The fonts load from Google Fonts. The beach photo loads from Wikimedia Commons (CC BY-SA 4.0, credited in the footer). Download both into this folder if you want the page to make no outside calls.
|
||||||
|
- Replace `images/hero.jpg` with the original photo file.
|
||||||
|
- The paid-plan price is a blank box on the page until you decide it.
|
||||||
|
- Marriage sites hold very private details. In Australia some of them count as "sensitive information" under the Privacy Act 1988, so people must clearly agree to how it is used. Plan the privacy policy and consent step in Phase 2, and check with a lawyer before launch. This is not legal advice.
|
||||||
|
|
||||||
|
## Next: Phase 2
|
||||||
|
|
||||||
|
Real sign-up and login. This needs a hosting place (for example Vercel and Supabase) and a domain name.
|
||||||
+313
@@ -0,0 +1,313 @@
|
|||||||
|
(() => {
|
||||||
|
'use strict';
|
||||||
|
const $ = (s, r = document) => r.querySelector(s);
|
||||||
|
const $$ = (s, r = document) => Array.from(r.querySelectorAll(s));
|
||||||
|
const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
||||||
|
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
|
/* made-up sample people; nothing here is a real person (g: w = woman, m = man) */
|
||||||
|
const PEOPLE = [
|
||||||
|
{ n: 'Mia', g: 'w', a: 28, c: 'Sydney', l: ['beach', 'coffee', 'travel'], t: 'a' },
|
||||||
|
{ n: 'Arjun', g: 'm', a: 31, c: 'Melbourne', l: ['footy', 'cooking', 'music'], t: 'b' },
|
||||||
|
{ n: 'Hannah', g: 'w', a: 27, c: 'Brisbane', l: ['beach', 'hiking', 'pets'], t: 'c' },
|
||||||
|
{ n: 'Wei', g: 'm', a: 30, c: 'Perth', l: ['coffee', 'travel', 'hiking'], t: 'd' },
|
||||||
|
{ n: 'Sana', g: 'w', a: 29, c: 'Adelaide', l: ['cooking', 'music', 'pets'], t: 'a' },
|
||||||
|
{ n: 'Tom', g: 'm', a: 33, c: 'Hobart', l: ['hiking', 'footy', 'coffee'], t: 'b' },
|
||||||
|
{ n: 'Priya', g: 'w', a: 26, c: 'Sydney', l: ['music', 'cooking', 'beach'], t: 'c' },
|
||||||
|
{ n: 'Jack', g: 'm', a: 35, c: 'Melbourne', l: ['footy', 'beach', 'pets'], t: 'd' },
|
||||||
|
{ n: 'Elena', g: 'w', a: 32, c: 'Brisbane', l: ['travel', 'coffee', 'music'], t: 'a' },
|
||||||
|
{ n: 'Dev', g: 'm', a: 34, c: 'Perth', l: ['footy', 'cooking', 'hiking'], t: 'b' },
|
||||||
|
{ n: 'Grace', g: 'w', a: 30, c: 'Canberra', l: ['hiking', 'coffee', 'pets'], t: 'c' },
|
||||||
|
{ n: 'Sam', g: 'm', a: 28, c: 'Darwin', l: ['beach', 'footy', 'travel'], t: 'd' },
|
||||||
|
{ n: 'Lena', g: 'w', a: 38, c: 'Sydney', l: ['travel', 'music', 'pets'], t: 'a' },
|
||||||
|
{ n: 'Omar', g: 'm', a: 29, c: 'Melbourne', l: ['coffee', 'cooking', 'hiking'], t: 'c' }
|
||||||
|
];
|
||||||
|
|
||||||
|
/* ---------- scroll reveal, once ---------- */
|
||||||
|
const revealEls = $$('[data-reveal]');
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
const io = new IntersectionObserver((es) => es.forEach((e) => {
|
||||||
|
if (e.isIntersecting) { e.target.classList.add('is-in'); io.unobserve(e.target); }
|
||||||
|
}), { rootMargin: '0px 0px -10% 0px' });
|
||||||
|
revealEls.forEach((el) => io.observe(el));
|
||||||
|
} else {
|
||||||
|
revealEls.forEach((el) => el.classList.add('is-in'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- mobile menu ---------- */
|
||||||
|
const menu = $('.nav__menu'), links = $('#nav-links');
|
||||||
|
menu.addEventListener('click', () => {
|
||||||
|
const open = links.classList.toggle('is-open');
|
||||||
|
menu.setAttribute('aria-expanded', String(open));
|
||||||
|
menu.textContent = open ? 'Close' : 'Menu';
|
||||||
|
});
|
||||||
|
links.addEventListener('click', (e) => {
|
||||||
|
if (e.target.closest('a')) { links.classList.remove('is-open'); menu.setAttribute('aria-expanded', 'false'); menu.textContent = 'Menu'; }
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ---------- match finder (the quick search bar drives it too) ---------- */
|
||||||
|
const st = { city: 'Anywhere', gender: 'any', from: 25, to: 40, likes: new Set(), sent: new Set() };
|
||||||
|
const from = $('#f-from'), to = $('#f-to'), oFrom = $('#o-from'), oTo = $('#o-to');
|
||||||
|
const results = $('#results'), status = $('#finder-status'), mapCity = $('#map-city');
|
||||||
|
const qFor = $('#q-for'), qAge = $('#q-age'), qCity = $('#q-city');
|
||||||
|
const pins = $$('.pin');
|
||||||
|
|
||||||
|
function scoreOf(p) {
|
||||||
|
const mid = (st.from + st.to) / 2, half = Math.max((st.to - st.from) / 2, 1);
|
||||||
|
const ageFit = Math.max(0, 1 - Math.abs(p.a - mid) / (half + 4));
|
||||||
|
const shared = p.l.filter((x) => st.likes.has(x)).length;
|
||||||
|
const likeFit = st.likes.size ? Math.min(1, shared / Math.min(st.likes.size, 3)) : 0.5;
|
||||||
|
const cityFit = st.city === 'Anywhere' ? 0.7 : 1;
|
||||||
|
return Math.round(55 + 45 * (0.5 * likeFit + 0.3 * ageFit + 0.2 * cityFit));
|
||||||
|
}
|
||||||
|
|
||||||
|
function tile(r, i) {
|
||||||
|
const p = r.p, sent = st.sent.has(p.n);
|
||||||
|
const tags = p.l.map((x) => '<li' + (st.likes.has(x) ? ' class="is-shared"' : '') + '>' + cap(x) + '</li>').join('');
|
||||||
|
return '<article class="person" style="--i:' + i + '">' +
|
||||||
|
'<div class="person__who"><span class="avatar" style="--tone:var(--tone-' + p.t + ')" aria-hidden="true">' + p.n.charAt(0) + '</span>' +
|
||||||
|
'<div><h3>' + p.n + ', ' + p.a + '</h3><p class="label">' + p.c + '</p></div></div>' +
|
||||||
|
'<div><div class="score"><b data-to="' + r.score + '">' + r.score + '</b><span class="label" title="Counts shared interests, city and age">match</span></div>' +
|
||||||
|
'<div class="bar" role="img" aria-label="Match score ' + r.score + ' out of 100"><i style="--s:' + r.score + '"></i></div></div>' +
|
||||||
|
'<ul class="tags">' + tags + '</ul>' +
|
||||||
|
'<button class="btn btn--sm" type="button" data-interest="' + p.n + '" aria-pressed="' + sent + '">' + (sent ? 'Interest sent' : 'Send interest') + '</button>' +
|
||||||
|
'</article>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scores count up from 55 to their value, once per render */
|
||||||
|
function countUp(el) {
|
||||||
|
const to = +el.dataset.to;
|
||||||
|
if (reduced) { el.textContent = to; return; }
|
||||||
|
const t0 = performance.now(), dur = 900, start = 55;
|
||||||
|
el.textContent = start;
|
||||||
|
(function step(now) {
|
||||||
|
const k = Math.min(1, (now - t0) / dur), e = 1 - Math.pow(1 - k, 3);
|
||||||
|
el.textContent = Math.round(start + (to - start) * e);
|
||||||
|
if (k < 1) requestAnimationFrame(step);
|
||||||
|
})(t0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFinder() {
|
||||||
|
const pool = PEOPLE
|
||||||
|
.filter((p) => (st.city === 'Anywhere' || p.c === st.city) && (st.gender === 'any' || p.g === st.gender) && p.a >= st.from && p.a <= st.to)
|
||||||
|
.map((p) => ({ p, score: scoreOf(p) }))
|
||||||
|
.sort((x, y) => y.score - x.score || x.p.n.localeCompare(y.p.n));
|
||||||
|
const top = pool.slice(0, 4);
|
||||||
|
results.innerHTML = top.length
|
||||||
|
? top.map(tile).join('')
|
||||||
|
: '<p class="results__empty">No one fits yet. Widen the ages or try another city.</p>';
|
||||||
|
$$('.score b', results).forEach(countUp);
|
||||||
|
status.textContent = top.length ? top.length + ' of ' + pool.length + ' shown.' : '';
|
||||||
|
mapCity.textContent = st.city === 'Anywhere' ? 'All of Australia' : st.city;
|
||||||
|
qCity.value = st.city;
|
||||||
|
pins.forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.city === st.city)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAges(a, b) {
|
||||||
|
st.from = a; st.to = b; from.value = a; to.value = b;
|
||||||
|
oFrom.textContent = a; oTo.textContent = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
pins.forEach((b) => b.addEventListener('click', () => {
|
||||||
|
st.city = st.city === b.dataset.city ? 'Anywhere' : b.dataset.city;
|
||||||
|
if (st.city === b.dataset.city && !reduced) {
|
||||||
|
const ping = document.createElement('span');
|
||||||
|
ping.className = 'ping';
|
||||||
|
ping.addEventListener('animationend', () => ping.remove(), { once: true });
|
||||||
|
b.appendChild(ping);
|
||||||
|
}
|
||||||
|
renderFinder();
|
||||||
|
}));
|
||||||
|
from.addEventListener('input', () => {
|
||||||
|
st.from = +from.value;
|
||||||
|
if (st.from > st.to) { st.to = st.from; to.value = st.to; }
|
||||||
|
oFrom.textContent = st.from; oTo.textContent = st.to; renderFinder();
|
||||||
|
});
|
||||||
|
to.addEventListener('input', () => {
|
||||||
|
st.to = +to.value;
|
||||||
|
if (st.to < st.from) { st.from = st.to; from.value = st.from; }
|
||||||
|
oFrom.textContent = st.from; oTo.textContent = st.to; renderFinder();
|
||||||
|
});
|
||||||
|
$$('#f-likes input').forEach((c) => c.addEventListener('change', () => {
|
||||||
|
c.checked ? st.likes.add(c.value) : st.likes.delete(c.value);
|
||||||
|
renderFinder();
|
||||||
|
}));
|
||||||
|
results.addEventListener('click', (e) => {
|
||||||
|
const b = e.target.closest('[data-interest]');
|
||||||
|
if (!b) return;
|
||||||
|
const name = b.dataset.interest;
|
||||||
|
st.sent.has(name) ? st.sent.delete(name) : st.sent.add(name);
|
||||||
|
const on = st.sent.has(name);
|
||||||
|
b.setAttribute('aria-pressed', String(on));
|
||||||
|
b.textContent = on ? 'Interest sent' : 'Send interest';
|
||||||
|
});
|
||||||
|
|
||||||
|
/* quick search bar: each choice updates the finder, the button also scrolls to the people */
|
||||||
|
function applyQuick() {
|
||||||
|
st.gender = qFor.value;
|
||||||
|
st.city = qCity.value;
|
||||||
|
const [a, b] = qAge.value.split('-').map(Number);
|
||||||
|
setAges(a, b);
|
||||||
|
renderFinder();
|
||||||
|
}
|
||||||
|
[qFor, qAge, qCity].forEach((el) => el.addEventListener('change', applyQuick));
|
||||||
|
$('#quick').addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
applyQuick();
|
||||||
|
results.scrollIntoView({ behavior: reduced ? 'auto' : 'smooth', block: 'center' });
|
||||||
|
});
|
||||||
|
renderFinder();
|
||||||
|
|
||||||
|
/* ---------- the four checks ---------- */
|
||||||
|
const rows = $$('[data-check]'), vStatus = $('#v-status'), vText = $('#v-status-text'), vBar = $('#v-progress');
|
||||||
|
const timers = new Map();
|
||||||
|
function updateChecks() {
|
||||||
|
const n = rows.filter((r) => r.classList.contains('is-done')).length;
|
||||||
|
vStatus.dataset.state = n === rows.length ? 'done' : 'idle';
|
||||||
|
vText.textContent = n === rows.length ? 'Verified' : n ? n + ' of 4 passed' : 'Not verified yet';
|
||||||
|
vBar.style.setProperty('--p', n / rows.length);
|
||||||
|
}
|
||||||
|
function runCheck(row) {
|
||||||
|
if (row.classList.contains('is-done') || timers.has(row)) return;
|
||||||
|
const btn = $('[data-run]', row);
|
||||||
|
btn.setAttribute('aria-busy', 'true'); btn.disabled = true; btn.textContent = 'Checking…';
|
||||||
|
timers.set(row, setTimeout(() => {
|
||||||
|
timers.delete(row);
|
||||||
|
row.classList.add('is-done');
|
||||||
|
btn.hidden = true; btn.removeAttribute('aria-busy');
|
||||||
|
$('.passed', row).hidden = false;
|
||||||
|
updateChecks();
|
||||||
|
}, 800));
|
||||||
|
}
|
||||||
|
rows.forEach((r) => $('[data-run]', r).addEventListener('click', () => runCheck(r)));
|
||||||
|
$('#v-all').addEventListener('click', () => rows.forEach((r, i) => setTimeout(() => runCheck(r), i * 250)));
|
||||||
|
$('#v-reset').addEventListener('click', () => {
|
||||||
|
timers.forEach((t) => clearTimeout(t)); timers.clear();
|
||||||
|
rows.forEach((r) => {
|
||||||
|
r.classList.remove('is-done');
|
||||||
|
const btn = $('[data-run]', r);
|
||||||
|
btn.hidden = false; btn.disabled = false; btn.removeAttribute('aria-busy'); btn.textContent = 'Run';
|
||||||
|
$('.passed', r).hidden = true;
|
||||||
|
});
|
||||||
|
updateChecks();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ---------- photo privacy ---------- */
|
||||||
|
const photo = $('#photo'), photoState = $('#photo-state');
|
||||||
|
const rank = { stranger: 0, verified: 1, accepted: 2 };
|
||||||
|
function renderPhoto() {
|
||||||
|
const setting = $('input[name="setting"]:checked').value;
|
||||||
|
const viewer = $('input[name="viewer"]:checked').value;
|
||||||
|
const can = setting === 'everyone' ||
|
||||||
|
(setting === 'verified' && rank[viewer] >= 1) ||
|
||||||
|
(setting === 'accepted' && rank[viewer] >= 2);
|
||||||
|
photo.classList.toggle('is-hidden', !can);
|
||||||
|
photoState.textContent = can ? 'Visible to this visitor' : 'Hidden from this visitor';
|
||||||
|
}
|
||||||
|
$$('input[name="setting"], input[name="viewer"]').forEach((r) => r.addEventListener('change', renderPhoto));
|
||||||
|
renderPhoto();
|
||||||
|
|
||||||
|
/* ---------- interest, accept, chat ---------- */
|
||||||
|
const chat = $('#chat'), log = $('#chat-log'), tResult = $('#t-result');
|
||||||
|
const accept = $('#t-accept'), decline = $('#t-decline'), tReset = $('#t-reset');
|
||||||
|
const chatForm = $('#chat-form'), chatInput = $('#chat-input');
|
||||||
|
function addLine(text, cls) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
if (cls) li.className = cls;
|
||||||
|
li.textContent = text;
|
||||||
|
log.appendChild(li);
|
||||||
|
log.scrollTop = log.scrollHeight;
|
||||||
|
}
|
||||||
|
function decide(yes) {
|
||||||
|
accept.hidden = true; decline.hidden = true; tReset.hidden = false;
|
||||||
|
tResult.textContent = yes ? 'You both said yes. Say hello.' : 'You said no thanks. Nothing opens.';
|
||||||
|
if (yes) {
|
||||||
|
chat.classList.remove('is-locked');
|
||||||
|
addLine('You and Priya both said yes.', 'note');
|
||||||
|
chatInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accept.addEventListener('click', () => decide(true));
|
||||||
|
decline.addEventListener('click', () => decide(false));
|
||||||
|
tReset.addEventListener('click', () => {
|
||||||
|
accept.hidden = false; decline.hidden = false; tReset.hidden = true;
|
||||||
|
tResult.textContent = ''; log.textContent = '';
|
||||||
|
chat.classList.add('is-locked'); chatInput.value = '';
|
||||||
|
});
|
||||||
|
chatForm.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const v = chatInput.value.trim();
|
||||||
|
if (!v) { chatInput.focus(); return; }
|
||||||
|
addLine(v, 'me'); chatInput.value = ''; chatInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ---------- family view and safety tools ---------- */
|
||||||
|
const famStatus = $('#fam-status');
|
||||||
|
function renderFam() {
|
||||||
|
const on = $$('#fam input:checked').map((c) => c.value);
|
||||||
|
famStatus.textContent = !on.length ? 'Just you for now.'
|
||||||
|
: (on.length === 1 ? on[0] : on.slice(0, -1).join(', ') + ' and ' + on[on.length - 1]) +
|
||||||
|
(on.length === 1 ? ' is' : ' are') + ' looking with you.';
|
||||||
|
}
|
||||||
|
$$('#fam input').forEach((c) => c.addEventListener('change', renderFam));
|
||||||
|
|
||||||
|
const safe = $('#safe-status'), sBlock = $('#s-block'), sHide = $('#s-hide'), sReport = $('#s-report');
|
||||||
|
let blocked = false;
|
||||||
|
sBlock.addEventListener('click', () => {
|
||||||
|
blocked = !blocked;
|
||||||
|
$('span', sBlock).textContent = blocked ? 'Unblock' : 'Block';
|
||||||
|
sBlock.setAttribute('aria-pressed', String(blocked));
|
||||||
|
safe.textContent = blocked ? 'Jack is blocked. He cannot message you.' : 'Jack is unblocked.';
|
||||||
|
});
|
||||||
|
sReport.addEventListener('click', () => { safe.textContent = 'Demo only: nothing was sent to our team.'; });
|
||||||
|
sHide.addEventListener('click', () => {
|
||||||
|
const on = sHide.getAttribute('aria-pressed') !== 'true';
|
||||||
|
sHide.setAttribute('aria-pressed', String(on));
|
||||||
|
$('span', sHide).textContent = on ? 'Show my profile' : 'Hide my profile';
|
||||||
|
safe.textContent = on ? 'Your profile is hidden from search.' : 'Your profile shows in search.';
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ---------- join card: tabs and forms that post nowhere yet ---------- */
|
||||||
|
const tabs = $$('[role="tab"]', $('#join-card'));
|
||||||
|
const panels = { join: $('#panel-join'), login: $('#panel-login') };
|
||||||
|
function openTab(name, focus) {
|
||||||
|
tabs.forEach((t) => {
|
||||||
|
const on = t.id === 'tab-' + name;
|
||||||
|
t.setAttribute('aria-selected', String(on));
|
||||||
|
t.tabIndex = on ? 0 : -1;
|
||||||
|
if (on && focus) t.focus();
|
||||||
|
});
|
||||||
|
Object.keys(panels).forEach((k) => { panels[k].hidden = k !== name; });
|
||||||
|
}
|
||||||
|
tabs.forEach((t, i) => {
|
||||||
|
t.addEventListener('click', () => openTab(t.id.replace('tab-', '')));
|
||||||
|
t.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
|
||||||
|
e.preventDefault();
|
||||||
|
const next = tabs[(i + (e.key === 'ArrowRight' ? 1 : tabs.length - 1)) % tabs.length];
|
||||||
|
openTab(next.id.replace('tab-', ''), true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$$('[data-open-tab]').forEach((a) => a.addEventListener('click', () => openTab(a.dataset.openTab)));
|
||||||
|
openTab('join');
|
||||||
|
|
||||||
|
$$('form[data-endpoint]').forEach((form) => {
|
||||||
|
const help = $('.form-help', form);
|
||||||
|
form.addEventListener('input', (e) => { e.target.removeAttribute && e.target.removeAttribute('aria-invalid'); });
|
||||||
|
form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
help.classList.remove('is-error');
|
||||||
|
const bad = $$('input', form).filter((i) => !i.checkValidity());
|
||||||
|
$$('input', form).forEach((i) => i.toggleAttribute('aria-invalid', bad.includes(i)));
|
||||||
|
if (bad.length) {
|
||||||
|
help.classList.add('is-error');
|
||||||
|
help.textContent = bad[0].type === 'email'
|
||||||
|
? 'That email looks incomplete. Write it as name@example.com.'
|
||||||
|
: 'Please fill in the highlighted field.';
|
||||||
|
bad[0].focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!form.dataset.endpoint) { help.textContent = 'Nothing was sent: this form is not connected yet.'; return; }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
|
html, body { overflow-x: clip; }
|
||||||
|
html { scrollbar-color: var(--color-muted) var(--color-paper); scroll-padding-top: var(--space-3xl); }
|
||||||
|
body { margin: 0; background: var(--color-paper); color: var(--color-ink); font-family: var(--font-body); font-size: var(--text-md); line-height: 1.55; -webkit-font-smoothing: antialiased; caret-color: var(--color-accent); }
|
||||||
|
::selection { background: color-mix(in oklab, var(--color-accent) 22%, transparent); color: var(--color-ink); }
|
||||||
|
:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
|
||||||
|
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.16em; }
|
||||||
|
h1, h2, h3 { margin: 0; font-family: var(--font-display); font-synthesis: none; overflow-wrap: break-word; min-width: 0; text-wrap: balance; }
|
||||||
|
p { margin: 0; text-wrap: pretty; }
|
||||||
|
p:not([class]) { max-width: 65ch; }
|
||||||
|
img, video { max-width: 100%; height: auto; display: block; }
|
||||||
|
svg { display: block; }
|
||||||
|
[hidden] { display: none !important; }
|
||||||
|
button, input, select, textarea { font: inherit; }
|
||||||
|
.container { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); box-sizing: border-box; }
|
||||||
|
.num, .figure, td.num { font-variant-numeric: tabular-nums; }
|
||||||
|
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
||||||
|
[data-slot] { position: relative; overflow: hidden; background: linear-gradient(160deg, var(--color-paper-2, var(--color-paper)), var(--color-paper-3, var(--color-paper))); }
|
||||||
|
[data-slot] > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; }
|
||||||
|
[data-slot]:has(> img) > :not(img):not([data-keep]) { display: none !important; }
|
||||||
|
@media (max-width: 60rem) { :root { --gutter: 24px; --section-gap-s: 80px; --section-gap: 96px; --section-gap-l: 120px; } }
|
||||||
|
@media (max-width: 40rem) { :root { --gutter: 16px; --section-gap-s: 56px; --section-gap: 72px; --section-gap-l: 96px; --gap-columns: 16px; } }
|
||||||
|
@media (prefers-reduced-motion: reduce) { *:not(.keep-motion), *:not(.keep-motion)::before, *:not(.keep-motion)::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,346 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-AU">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>BeingOne: marriage profiles for Australians, every one checked</title>
|
||||||
|
<meta name="description" content="BeingOne is a marriage website for Australians and their families where every profile is checked to be a real person.">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Figtree:wght@500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script>document.documentElement.classList.add('js');</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
|
||||||
|
<symbol id="i-check" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></symbol>
|
||||||
|
<symbol id="i-arrow" viewBox="0 0 24 24"><path d="M5 12h14M13 6l6 6-6 6"/></symbol>
|
||||||
|
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></symbol>
|
||||||
|
<symbol id="i-lock" viewBox="0 0 24 24"><rect width="18" height="11" x="3" y="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||||
|
<symbol id="i-eye" viewBox="0 0 24 24"><path d="M2.06 12.35a1 1 0 0 1 0-.7 10.75 10.75 0 0 1 19.88 0 1 1 0 0 1 0 .7 10.75 10.75 0 0 1-19.88 0"/><circle cx="12" cy="12" r="3"/></symbol>
|
||||||
|
<symbol id="i-shield" viewBox="0 0 24 24"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></symbol>
|
||||||
|
<symbol id="i-phone" viewBox="0 0 24 24"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z"/></symbol>
|
||||||
|
<symbol id="i-camera" viewBox="0 0 24 24"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></symbol>
|
||||||
|
<symbol id="i-id" viewBox="0 0 24 24"><rect x="2" y="5" width="20" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M5.5 16c.5-1.5 2-2.5 3.5-2.5s3 1 3.5 2.5M15 10h4M15 14h3"/></symbol>
|
||||||
|
<symbol id="i-users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></symbol>
|
||||||
|
<symbol id="i-ban" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="m4.9 4.9 14.2 14.2"/></symbol>
|
||||||
|
<symbol id="i-flag" viewBox="0 0 24 24"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><path d="M4 22v-7"/></symbol>
|
||||||
|
<symbol id="i-send" viewBox="0 0 24 24"><path d="m22 2-7 20-4-9-9-4z"/><path d="M22 2 11 13"/></symbol>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<a class="skip" href="#main">Skip to content</a>
|
||||||
|
|
||||||
|
<header class="nav">
|
||||||
|
<div class="container nav__bar">
|
||||||
|
<a class="wordmark" href="#top" aria-label="BeingOne, back to the top">
|
||||||
|
<svg class="mark" viewBox="0 0 32 32" aria-hidden="true"><circle cx="12" cy="16" r="8"/><circle cx="20" cy="16" r="8"/></svg>
|
||||||
|
<span>Being<b>One</b></span>
|
||||||
|
</a>
|
||||||
|
<nav class="nav__links" id="nav-links" aria-label="Main">
|
||||||
|
<a href="#find">Discover</a>
|
||||||
|
<a href="#results">Curated matches</a>
|
||||||
|
<a href="#talk">Messages</a>
|
||||||
|
<a href="#safety">Safety centre</a>
|
||||||
|
<a href="#help">Help</a>
|
||||||
|
<a class="only-sm" href="#join" data-open-tab="login">Sign in</a>
|
||||||
|
</nav>
|
||||||
|
<div class="nav__actions">
|
||||||
|
<a class="nav__signin" href="#join" data-open-tab="login">Sign in</a>
|
||||||
|
<a class="btn btn--sm" href="#join" data-open-tab="join">Join</a>
|
||||||
|
<button class="nav__menu btn btn--sm" type="button" aria-expanded="false" aria-controls="nav-links">Menu</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
|
||||||
|
<section class="hero" id="top" aria-labelledby="hero-title">
|
||||||
|
<figure class="hero__photo" data-slot="hero" data-alt="A bride and groom holding hands outdoors as guests throw petals">
|
||||||
|
<img src="images/hero.jpg" width="998" height="770" alt="A bride and groom holding hands outdoors as guests throw petals" fetchpriority="high">
|
||||||
|
</figure>
|
||||||
|
<div class="container hero__inner">
|
||||||
|
<div class="hero__text">
|
||||||
|
<h1 id="hero-title">
|
||||||
|
<span class="line" style="--i:0"><span>Real people.</span></span>
|
||||||
|
<span class="line" style="--i:1"><span>Real profiles.</span></span>
|
||||||
|
<span class="line line--it" style="--i:2"><span>Real marriage.</span></span>
|
||||||
|
</h1>
|
||||||
|
<p class="lead hero__lead">Every profile is checked, so you meet real people across Australia.</p>
|
||||||
|
<a class="btn btn--primary hero__cta" href="#join" data-open-tab="join">Create your profile<svg class="icon" aria-hidden="true"><use href="#i-arrow"/></svg></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container quick__wrap">
|
||||||
|
<form class="quick" id="quick" onsubmit="return false" aria-label="Quick search">
|
||||||
|
<div class="quick__field">
|
||||||
|
<label for="q-for">I’m looking for</label>
|
||||||
|
<select id="q-for"><option value="any">Anyone</option><option value="w">A woman</option><option value="m">A man</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="quick__field">
|
||||||
|
<label for="q-age">Age</label>
|
||||||
|
<select id="q-age"><option value="21-60">Any age</option><option value="21-29">21 to 29</option><option value="30-39">30 to 39</option><option value="40-49">40 to 49</option><option value="50-60">50 to 60</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="quick__field">
|
||||||
|
<label for="q-city">Location</label>
|
||||||
|
<select id="q-city"><option>Anywhere</option><option>Adelaide</option><option>Brisbane</option><option>Canberra</option><option>Darwin</option><option>Hobart</option><option>Melbourne</option><option>Perth</option><option>Sydney</option></select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn--primary quick__go" type="submit"><svg class="icon" aria-hidden="true"><use href="#i-search"/></svg>Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="band band--path" id="path" aria-label="How it goes">
|
||||||
|
<div class="container">
|
||||||
|
<ol class="path">
|
||||||
|
<li data-reveal style="--i:0"><a href="#join">Join</a></li>
|
||||||
|
<li data-reveal style="--i:1"><a href="#verified">Get checked</a></li>
|
||||||
|
<li data-reveal style="--i:2"><a href="#find">Find</a></li>
|
||||||
|
<li data-reveal style="--i:3"><a href="#talk">Say yes</a></li>
|
||||||
|
<li data-reveal style="--i:4"><a href="#talk">Chat</a></li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sec finder" id="find" aria-labelledby="find-title">
|
||||||
|
<div class="container">
|
||||||
|
<div class="grid finder__top">
|
||||||
|
<h2 id="find-title" data-reveal>Find your match across Australia</h2>
|
||||||
|
<p class="lead" data-reveal style="--i:1">Pick a city, ages and what you love.</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid finder__body">
|
||||||
|
<div class="map" data-reveal role="group" aria-label="Map of Australia. Tap a city to search there.">
|
||||||
|
<svg class="map__svg" viewBox="-40 -20 605 516" role="img" aria-label="Outline of Australia">
|
||||||
|
<path d="M381.3 9.8L393.8 56.0L416.3 70.0L422.5 96.6L435.0 130.2L458.7 144.2L465.0 155.4L485.0 187.6L505.0 208.6L513.7 224.0L512.5 245.0L520.0 260.4L512.5 284.2L506.3 308.0L490.0 334.6L485.0 348.6L476.2 366.8L473.8 385.0L450.0 392.0L430.0 407.4L412.5 397.6L393.8 403.2L362.5 393.4L350.0 385.0L343.8 358.4L326.2 359.1L331.3 347.2L321.2 336.0L300.0 347.2L277.5 322.0L237.5 301.0L212.5 303.8L175.0 312.2L143.8 333.2L123.8 334.6L87.5 348.6L73.8 350.0L38.7 341.6L46.3 326.2L47.5 308.0L32.5 263.2L17.5 224.0L18.8 196.0L26.2 165.2L58.8 149.8L82.5 144.2L118.8 126.0L127.5 112.0L147.5 91.0L168.8 77.0L186.3 54.6L212.5 70.0L221.2 68.6L225.0 46.2L235.0 35.0L256.3 21.0L275.0 28.0L310.0 30.8L306.3 49.0L297.5 72.8L322.5 91.0L343.8 105.0L360.0 103.6L370.0 77.0L368.8 49.0L376.2 30.8ZM408.7 429.8L453.8 432.6L453.8 450.8L441.3 469.0L425.0 470.4L415.0 450.8Z"/>
|
||||||
|
</svg>
|
||||||
|
<button class="pin" type="button" data-city="Perth" aria-pressed="false" aria-label="Perth" title="Perth" style="--x:14.6%;--y:63.4%;--i:0"></button>
|
||||||
|
<button class="pin" type="button" data-city="Adelaide" aria-pressed="false" aria-label="Adelaide" title="Adelaide" style="--x:61.6%;--y:71.5%;--i:1"></button>
|
||||||
|
<button class="pin" type="button" data-city="Darwin" aria-pressed="false" aria-label="Darwin" title="Darwin" style="--x:45.5%;--y:10.5%;--i:2"></button>
|
||||||
|
<button class="pin" type="button" data-city="Brisbane" aria-pressed="false" aria-label="Brisbane" title="Brisbane" style="--x:91.4%;--y:51.3%;--i:3"></button>
|
||||||
|
<button class="pin" type="button" data-city="Sydney" aria-pressed="false" aria-label="Sydney" title="Sydney" style="--x:87.6%;--y:68.6%;--i:4"></button>
|
||||||
|
<button class="pin" type="button" data-city="Canberra" aria-pressed="false" aria-label="Canberra" title="Canberra" style="--x:83.3%;--y:72.5%;--i:5"></button>
|
||||||
|
<button class="pin" type="button" data-city="Melbourne" aria-pressed="false" aria-label="Melbourne" title="Melbourne" style="--x:74.7%;--y:79.3%;--i:6"></button>
|
||||||
|
<button class="pin" type="button" data-city="Hobart" aria-pressed="false" aria-label="Hobart" title="Hobart" style="--x:79.6%;--y:93.1%;--i:7"></button>
|
||||||
|
<p class="map__city" id="map-city">All of Australia</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="controls" id="finder-form" data-reveal style="--i:1" onsubmit="return false" aria-label="Match finder">
|
||||||
|
<div class="controls__ages">
|
||||||
|
<div class="field">
|
||||||
|
<div class="field__row"><label for="f-from">From</label><output id="o-from" for="f-from">25</output></div>
|
||||||
|
<input id="f-from" type="range" min="21" max="60" value="25">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="field__row"><label for="f-to">To</label><output id="o-to" for="f-to">40</output></div>
|
||||||
|
<input id="f-to" type="range" min="21" max="60" value="40">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<fieldset class="choices" id="f-likes">
|
||||||
|
<legend>You love</legend>
|
||||||
|
<input type="checkbox" id="l-beach" value="beach"><label for="l-beach">Beach</label>
|
||||||
|
<input type="checkbox" id="l-footy" value="footy"><label for="l-footy">Footy</label>
|
||||||
|
<input type="checkbox" id="l-coffee" value="coffee"><label for="l-coffee">Coffee</label>
|
||||||
|
<input type="checkbox" id="l-hiking" value="hiking"><label for="l-hiking">Hiking</label>
|
||||||
|
<input type="checkbox" id="l-cooking" value="cooking"><label for="l-cooking">Cooking</label>
|
||||||
|
<input type="checkbox" id="l-travel" value="travel"><label for="l-travel">Travel</label>
|
||||||
|
<input type="checkbox" id="l-music" value="music"><label for="l-music">Music</label>
|
||||||
|
<input type="checkbox" id="l-pets" value="pets"><label for="l-pets">Pets</label>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="results" id="results" aria-live="polite"></div>
|
||||||
|
<p class="visually-hidden" id="finder-status" role="status"></p>
|
||||||
|
<p class="finder__note">Sample people, made up for this demo.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sec band--navy on-dark checks" id="verified" aria-labelledby="verified-title">
|
||||||
|
<div class="container grid">
|
||||||
|
<div class="head">
|
||||||
|
<h2 id="verified-title" data-reveal>Every profile is checked</h2>
|
||||||
|
<p class="lead" data-reveal style="--i:1">Try the four checks.</p>
|
||||||
|
</div>
|
||||||
|
<div class="checks__panel" data-reveal style="--i:2">
|
||||||
|
<div class="checks__who">
|
||||||
|
<span class="avatar" style="--tone:var(--tone-a)" aria-hidden="true">M</span>
|
||||||
|
<div><h3>Mia, 28</h3><p class="label">Sydney</p></div>
|
||||||
|
<span class="status" id="v-status" data-state="idle" role="status"><svg class="icon icon--sm" aria-hidden="true"><use href="#i-shield"/></svg><span id="v-status-text">Not verified yet</span></span>
|
||||||
|
</div>
|
||||||
|
<div class="progress" aria-hidden="true"><i id="v-progress" style="--p:0"></i></div>
|
||||||
|
<ul class="rows" id="v-rows">
|
||||||
|
<li data-check>
|
||||||
|
<svg class="icon idle" aria-hidden="true"><use href="#i-phone"/></svg><svg class="icon tick" aria-hidden="true"><use href="#i-check"/></svg>
|
||||||
|
<div><h3>Phone check</h3><p>A code arrives by text.</p></div>
|
||||||
|
<button class="btn btn--sm" type="button" data-run>Run</button><span class="passed" hidden>Passed</span>
|
||||||
|
</li>
|
||||||
|
<li data-check>
|
||||||
|
<svg class="icon idle" aria-hidden="true"><use href="#i-camera"/></svg><svg class="icon tick" aria-hidden="true"><use href="#i-check"/></svg>
|
||||||
|
<div><h3>Photo check</h3><p>Checked as real.</p></div>
|
||||||
|
<button class="btn btn--sm" type="button" data-run>Run</button><span class="passed" hidden>Passed</span>
|
||||||
|
</li>
|
||||||
|
<li data-check>
|
||||||
|
<svg class="icon idle" aria-hidden="true"><use href="#i-id"/></svg><svg class="icon tick" aria-hidden="true"><use href="#i-check"/></svg>
|
||||||
|
<div><h3>ID check</h3><p>Matched to your photo.</p></div>
|
||||||
|
<button class="btn btn--sm" type="button" data-run>Run</button><span class="passed" hidden>Passed</span>
|
||||||
|
</li>
|
||||||
|
<li data-check>
|
||||||
|
<svg class="icon idle" aria-hidden="true"><use href="#i-users"/></svg><svg class="icon tick" aria-hidden="true"><use href="#i-check"/></svg>
|
||||||
|
<div><h3>Team look</h3><p>A quick look by our team.</p></div>
|
||||||
|
<button class="btn btn--sm" type="button" data-run>Run</button><span class="passed" hidden>Passed</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="checks__foot">
|
||||||
|
<button class="btn btn--sm" type="button" id="v-all">Run all four</button>
|
||||||
|
<button class="btn btn--sm" type="button" id="v-reset">Start again</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sec sec--tall" id="privacy" aria-labelledby="privacy-title">
|
||||||
|
<div class="container grid">
|
||||||
|
<figure class="privacy__photo" id="photo" data-reveal>
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg/1280px-Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg" srcset="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg/960px-Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg 960w, https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg/1280px-Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg 1280w, https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg/1920px-Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg 1920w" sizes="(min-width: 60rem) 58vw, 100vw" width="6718" height="4479" alt="A beach below orange sandstone cliffs, standing in for a profile photo" loading="lazy">
|
||||||
|
<img class="blur" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg/960px-Peterborough_%28AU%29%2C_Port_Campbell_National_Park%2C_Worm_Bay_--_2019_--_0863.jpg" width="6718" height="4479" alt="" aria-hidden="true" loading="lazy">
|
||||||
|
<span class="privacy__lock" aria-hidden="true"><span class="privacy__disc"><svg class="icon"><use href="#i-lock"/></svg></span></span>
|
||||||
|
<span class="privacy__badge" role="status"><svg class="icon icon--sm icon-open" aria-hidden="true"><use href="#i-eye"/></svg><svg class="icon icon--sm icon-shut" aria-hidden="true"><use href="#i-lock"/></svg><span id="photo-state">Hidden from this visitor</span></span>
|
||||||
|
</figure>
|
||||||
|
<form class="privacy__panel" onsubmit="return false" aria-label="Photo privacy">
|
||||||
|
<h2 id="privacy-title" data-reveal>You choose who sees your photos</h2>
|
||||||
|
<fieldset class="choices choices--stack" id="p-setting">
|
||||||
|
<legend>Photos are visible to</legend>
|
||||||
|
<input type="radio" name="setting" id="s-everyone" value="everyone"><label for="s-everyone">Everyone</label>
|
||||||
|
<input type="radio" name="setting" id="s-verified" value="verified" checked><label for="s-verified">Verified members</label>
|
||||||
|
<input type="radio" name="setting" id="s-accepted" value="accepted"><label for="s-accepted">People I accept</label>
|
||||||
|
<input type="radio" name="setting" id="s-me" value="me"><label for="s-me">Only me</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="choices" id="p-viewer">
|
||||||
|
<legend>Right now, a visitor is</legend>
|
||||||
|
<input type="radio" name="viewer" id="v-stranger" value="stranger" checked><label for="v-stranger">A stranger</label>
|
||||||
|
<input type="radio" name="viewer" id="v-member" value="verified"><label for="v-member">A verified member</label>
|
||||||
|
<input type="radio" name="viewer" id="v-accepted" value="accepted"><label for="v-accepted">Someone I accepted</label>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="band talk" id="talk" aria-labelledby="talk-title">
|
||||||
|
<div class="container">
|
||||||
|
<div class="talk__top"><h2 id="talk-title" data-reveal>Chat opens when both say yes</h2></div>
|
||||||
|
<div class="grid talk__body">
|
||||||
|
<div class="invite" data-reveal>
|
||||||
|
<div class="invite__who">
|
||||||
|
<span class="avatar" style="--tone:var(--tone-c)" aria-hidden="true">P</span>
|
||||||
|
<div><h3>Priya sent you an interest</h3><p class="label">26, Sydney</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="invite__actions" id="invite-actions">
|
||||||
|
<button class="btn" type="button" id="t-accept">Accept</button>
|
||||||
|
<button class="btn" type="button" id="t-decline">No thanks</button>
|
||||||
|
<button class="btn btn--sm" type="button" id="t-reset" hidden>Start again</button>
|
||||||
|
</div>
|
||||||
|
<p class="invite__result" id="t-result" role="status"></p>
|
||||||
|
</div>
|
||||||
|
<div class="chat on-dark is-locked" id="chat" data-reveal style="--i:1">
|
||||||
|
<ul class="chat__log" id="chat-log" role="log" aria-label="Chat with Priya"></ul>
|
||||||
|
<form class="chat__form" id="chat-form" autocomplete="off">
|
||||||
|
<label class="visually-hidden" for="chat-input">Message</label>
|
||||||
|
<input id="chat-input" name="message" type="text" maxlength="300" enterkeyhint="send">
|
||||||
|
<button class="btn btn--sm btn--primary" type="submit"><svg class="icon" aria-hidden="true"><use href="#i-send"/></svg>Send</button>
|
||||||
|
</form>
|
||||||
|
<div class="chat__lock"><svg class="icon" aria-hidden="true"><use href="#i-lock"/></svg><span>Chat opens when you both say yes.</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sec sec--tall" id="family" aria-label="Family view and safety tools">
|
||||||
|
<div class="container grid together">
|
||||||
|
<div class="tile tile--wide" data-reveal>
|
||||||
|
<h2>Family view</h2>
|
||||||
|
<p class="lead">A parent or sibling can look at profiles with you.</p>
|
||||||
|
<fieldset class="choices" id="fam">
|
||||||
|
<legend>Who helps you look</legend>
|
||||||
|
<input type="checkbox" id="h-mum" value="Mum"><label for="h-mum">Mum</label>
|
||||||
|
<input type="checkbox" id="h-dad" value="Dad"><label for="h-dad">Dad</label>
|
||||||
|
<input type="checkbox" id="h-sister" value="Sister"><label for="h-sister">Sister</label>
|
||||||
|
<input type="checkbox" id="h-brother" value="Brother"><label for="h-brother">Brother</label>
|
||||||
|
</fieldset>
|
||||||
|
<p class="live" id="fam-status" role="status">Just you for now.</p>
|
||||||
|
</div>
|
||||||
|
<div class="tile tile--narrow on-dark" id="safety" data-reveal style="--i:1">
|
||||||
|
<h2>Safety tools</h2>
|
||||||
|
<div class="safety__who">
|
||||||
|
<span class="avatar" style="--tone:var(--tone-d)" aria-hidden="true">J</span>
|
||||||
|
<div><h3>Jack, 35</h3><p class="label">Melbourne</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="safety__actions">
|
||||||
|
<button class="btn btn--sm" type="button" id="s-block"><svg class="icon" aria-hidden="true"><use href="#i-ban"/></svg><span>Block</span></button>
|
||||||
|
<button class="btn btn--sm" type="button" id="s-report"><svg class="icon" aria-hidden="true"><use href="#i-flag"/></svg><span>Report</span></button>
|
||||||
|
<button class="btn btn--sm" type="button" id="s-hide" aria-pressed="false"><svg class="icon" aria-hidden="true"><use href="#i-lock"/></svg><span>Hide my profile</span></button>
|
||||||
|
</div>
|
||||||
|
<p class="live" id="safe-status" role="status"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="band join" id="join" aria-labelledby="join-title">
|
||||||
|
<div class="container grid">
|
||||||
|
<div class="head">
|
||||||
|
<h2 id="join-title" data-reveal>Make your profile</h2>
|
||||||
|
<dl class="qa" id="help" data-reveal style="--i:1">
|
||||||
|
<div><dt>What does it cost?</dt><dd>A free plan and a paid plan. Paid price: <span class="fill" data-fill="paid-price">to be announced</span></dd></div>
|
||||||
|
<div><dt>Will I get alerts?</dt><dd>Email and phone alerts for matches, interests and messages.</dd></div>
|
||||||
|
<div><dt>Who runs the site?</dt><dd>Our team reviews profiles and reports.</dd></div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="card" id="join-card" data-reveal style="--i:2">
|
||||||
|
<div class="tabs" role="tablist" aria-label="Join or log in">
|
||||||
|
<button type="button" role="tab" id="tab-join" aria-selected="true" aria-controls="panel-join">Join</button>
|
||||||
|
<button type="button" role="tab" id="tab-login" aria-selected="false" aria-controls="panel-login" tabindex="-1">Log in</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel" role="tabpanel" id="panel-join" aria-labelledby="tab-join">
|
||||||
|
<form data-endpoint="" novalidate class="panel">
|
||||||
|
<div class="field"><label for="j-name">First name</label><input id="j-name" name="name" type="text" autocomplete="given-name" required></div>
|
||||||
|
<div class="field"><label for="j-email">Email address</label><input id="j-email" name="email" type="email" autocomplete="email" inputmode="email" required></div>
|
||||||
|
<div class="field"><label for="j-for">Joining for</label>
|
||||||
|
<select id="j-for" name="for"><option>Myself</option><option>My son</option><option>My daughter</option><option>My brother or sister</option></select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn--primary" type="submit">Create your profile<svg class="icon" aria-hidden="true"><use href="#i-arrow"/></svg></button>
|
||||||
|
<p class="form-help" aria-live="polite"></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="panel" role="tabpanel" id="panel-login" aria-labelledby="tab-login">
|
||||||
|
<form data-endpoint="" novalidate class="panel">
|
||||||
|
<div class="field"><label for="l-email">Email address</label><input id="l-email" name="email" type="email" autocomplete="email" inputmode="email" required></div>
|
||||||
|
<button class="btn btn--primary" type="submit">Log in<svg class="icon" aria-hidden="true"><use href="#i-arrow"/></svg></button>
|
||||||
|
<p class="form-help" aria-live="polite"></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="foot on-dark">
|
||||||
|
<div class="container">
|
||||||
|
<p class="foot__sign">Warmly, the BeingOne team.</p>
|
||||||
|
<div class="foot__row">
|
||||||
|
<p>For Australians and their families. <time datetime="2026-10">October 2026</time></p>
|
||||||
|
<ul class="foot__links">
|
||||||
|
<li><a href="#find">Discover</a></li>
|
||||||
|
<li><a href="#verified">Checks</a></li>
|
||||||
|
<li><a href="#privacy">Photos</a></li>
|
||||||
|
<li><a href="#talk">Messages</a></li>
|
||||||
|
<li><a href="#join">Join</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p data-credits>Photographs: beach by <a href="https://commons.wikimedia.org/wiki/File:Peterborough_(AU),_Port_Campbell_National_Park,_Worm_Bay_--_2019_--_0863.jpg">Dietmar Rabich</a> (CC BY-SA 4.0, Wikimedia Commons)</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>document.querySelectorAll('[data-slot] img').forEach((i) => { if (i.complete && !i.naturalWidth) i.remove(); else i.addEventListener('error', () => i.remove(), { once: true }); });</script>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
/* Hallmark · shape: Split Hero With Floating Search · genre: editorial · theme: custom (by request: the user's own reference screenshot) · job: sign up · idea: "a marriage site that opens like a wedding morning: warm paper, a serif that speaks plainly, and every demo a visitor can touch" · vibe: "warm cream, navy ink, quietly lively"
|
||||||
|
* paper: oklch(98.4% 0.012 85) · accent: oklch(56% 0.15 38) · display: DM Serif Display · body: Figtree · hero: text left, the couple's photo bleeding off the right edge, a search bar floating across its foot · nav: edge-aligned · footer: letter close · imagery: photographs, user's own 1, library 1, natural · signature: the floating search bar that drives the Australia map and the scored matches
|
||||||
|
* axes: light/roman-serif/warm · posture: restrained · contrast: pass · fold: pass · v2.0.0 */
|
||||||
|
/* Hallmark · pre-emit critique: P4 H4 E4 S4 R3 V3 · weakest: R, the page now holds a lot of motion; cut every loop and kept only one-time entrances, hover lifts and state changes */
|
||||||
|
/* Hallmark · waive 65 · the user's own reference screenshot pins cream paper, a high-contrast serif and a terracotta accent for BeingOne · the look is the client's brief, not a model default */
|
||||||
|
/* Hallmark · waive 38ii · guard: true italic of the display face is loaded, font-synthesis none, one line of one h1 only · the closing line of the hero head mirrors the BeingOne reference the user supplied */
|
||||||
|
/* Hallmark · waive 38iii · one italic closing line of the hero head, once on the page · it mirrors the BeingOne reference the user supplied */
|
||||||
|
/* Hallmark · dismissed 59 · the path strip under the search bar is one row of five links that shows how the visitor moves through BeingOne, so it is wayfinding and not a content section */
|
||||||
|
/* Hallmark · dismissed F7 · the pill radius sits on controls only: nav links, choice chips, map pins and their ping ring are all pressed to act */
|
||||||
|
@import url("./tokens.css");
|
||||||
|
@import url("./base.css");
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-navy-3: oklch(20% 0.06 255);
|
||||||
|
--color-accent-on-dark: oklch(70% 0.14 38);
|
||||||
|
--nav-h: 76px;
|
||||||
|
--color-rule-on-navy: oklch(44% 0.05 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* type */
|
||||||
|
h1, h2 { font-weight: 400; letter-spacing: var(--tracking-display); }
|
||||||
|
h1 { max-width: none; font-size: clamp(3.25rem, 6.4vw, 6rem); line-height: 1; }
|
||||||
|
h2 { max-width: 22ch; font-size: clamp(2.25rem, 3.6vw, 3.5rem); line-height: 1.04; }
|
||||||
|
h3 { max-width: 40ch; font-weight: 400; font-size: var(--text-title); line-height: 1.2; letter-spacing: -0.01em; }
|
||||||
|
.lead { font-size: var(--text-lead); line-height: 1.45; max-width: 46ch; color: var(--color-ink-2); }
|
||||||
|
.label { font-size: var(--text-sm); font-weight: 700; color: var(--color-muted); }
|
||||||
|
.muted { color: var(--color-muted); }
|
||||||
|
body { font-weight: 500; }
|
||||||
|
|
||||||
|
/* local dark surfaces: components keep using the same tokens */
|
||||||
|
.on-dark { --color-ink: var(--color-on-navy); --color-ink-2: var(--color-on-navy); --color-muted: var(--color-on-navy-muted); --color-paper-2: var(--color-navy-2); --color-paper-3: var(--color-navy-3); --color-rule: var(--color-rule-on-navy); --color-card: var(--color-navy-2); --color-card-ink: var(--color-on-navy); background: var(--color-navy); color: var(--color-on-navy); }
|
||||||
|
|
||||||
|
/* frame */
|
||||||
|
.skip { position: absolute; left: var(--space-md); top: calc(-1 * var(--space-3xl)); z-index: var(--z-toast); padding: var(--space-xs) var(--space-md); background: var(--color-card); color: var(--color-card-ink); border-radius: var(--radius-control); font-weight: 700; }
|
||||||
|
.skip:focus-visible { top: var(--space-md); }
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap-columns); }
|
||||||
|
.sec { padding-block: var(--space-3xl); }
|
||||||
|
.sec--tall { padding-block: var(--space-4xl); }
|
||||||
|
.band { background: var(--color-paper-2); padding-block: var(--section-gap-s); }
|
||||||
|
.band--navy { background: var(--color-navy); color: var(--color-on-navy); padding-block: var(--section-gap-s); }
|
||||||
|
.head { display: grid; gap: var(--space-md); align-content: start; }
|
||||||
|
.icon { width: 24px; height: 24px; flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
.icon--sm { width: 20px; height: 20px; }
|
||||||
|
.fill { padding: 0 0.25em; border: 1px dashed color-mix(in oklch, var(--color-muted) 70%, transparent); border-radius: 3px; color: var(--color-muted); -webkit-box-decoration-break: clone; box-decoration-break: clone; }
|
||||||
|
|
||||||
|
/* nav: edge-aligned */
|
||||||
|
.nav { position: sticky; top: 0; z-index: var(--z-sticky-nav); height: var(--nav-h); background: var(--color-paper); border-bottom: var(--rule-hair) solid var(--color-rule); }
|
||||||
|
.nav__bar { height: 100%; display: flex; align-items: center; justify-content: space-between; gap: var(--space-lg); }
|
||||||
|
.wordmark { display: inline-flex; align-items: center; gap: var(--space-xs); font: 400 var(--text-2xl)/1 var(--font-display); letter-spacing: -0.01em; text-decoration: none; color: var(--color-ink); }
|
||||||
|
.wordmark b { font-weight: 400; color: var(--color-accent); }
|
||||||
|
.mark { width: 34px; height: 34px; fill: none; stroke: var(--color-accent); stroke-width: 2.5; transition: transform var(--dur-long) var(--ease-out); }
|
||||||
|
.wordmark:hover .mark { transform: rotate(-18deg); }
|
||||||
|
.nav__links { display: flex; align-items: center; gap: var(--space-xs); }
|
||||||
|
.nav__links a { padding: var(--space-xs) var(--space-md); border-radius: var(--radius-pill); text-decoration: none; font-weight: 700; white-space: nowrap; transition: background-color var(--dur-short) var(--ease-out); }
|
||||||
|
.nav__links a:hover { background: var(--color-paper-2); }
|
||||||
|
.nav__actions { display: flex; align-items: center; gap: var(--space-md); }
|
||||||
|
.nav__signin { white-space: nowrap; font-weight: 700; color: var(--color-accent); text-decoration: none; padding: var(--space-xs); }
|
||||||
|
.nav__signin:hover { text-decoration: underline; text-decoration-thickness: 2px; }
|
||||||
|
.nav__actions .nav__menu { display: none; }
|
||||||
|
.nav__links .only-sm { display: none; }
|
||||||
|
@media (max-width: 68rem) {
|
||||||
|
.nav__actions .nav__menu { display: inline-flex; }
|
||||||
|
.nav__links { position: absolute; top: var(--nav-h); left: 0; right: 0; flex-direction: column; align-items: stretch; gap: 0; padding: var(--space-md) var(--gutter); background: var(--color-paper); border-bottom: var(--rule-hair) solid var(--color-rule); display: none; }
|
||||||
|
.nav__links.is-open { display: flex; }
|
||||||
|
.nav__links a { padding: var(--space-md) 0; border-radius: 0; border-top: var(--rule-hair) solid var(--color-rule); }
|
||||||
|
.nav__links a:first-child { border-top: 0; }
|
||||||
|
}
|
||||||
|
@media (max-width: 40rem) { .nav__signin { display: none; } .nav__links .only-sm { display: block; } .nav__actions { gap: var(--space-xs); } }
|
||||||
|
|
||||||
|
/* controls */
|
||||||
|
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-xs); min-height: 52px; padding: 0 var(--space-lg); border: var(--rule-bar) solid var(--color-ink); border-radius: var(--radius-control); background: transparent; color: var(--color-ink); font: 700 var(--text-md)/1.25 var(--font-body); text-decoration: none; white-space: nowrap; cursor: pointer; transition: transform var(--dur-short) var(--ease-out), background-color var(--dur-short) var(--ease-out), color var(--dur-short) var(--ease-out), border-color var(--dur-short) var(--ease-out), box-shadow var(--dur-short) var(--ease-out); }
|
||||||
|
.btn:hover { background: var(--color-paper-3); }
|
||||||
|
.btn:active { transform: translateY(1px); }
|
||||||
|
.btn--sm { min-height: 44px; padding: 0 var(--space-md); font-size: var(--text-sm); }
|
||||||
|
.btn--primary { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-accent-ink); box-shadow: 0 14px 24px -14px color-mix(in oklab, var(--color-accent) 80%, transparent); }
|
||||||
|
.btn--primary:hover { background: var(--color-accent-2); border-color: var(--color-accent-2); transform: translateY(-2px); }
|
||||||
|
.btn--primary:active { transform: translateY(1px); }
|
||||||
|
.btn .icon { width: 20px; height: 20px; transition: transform var(--dur-short) var(--ease-out); }
|
||||||
|
.btn:hover .icon { transform: translateX(4px); }
|
||||||
|
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; transform: none; }
|
||||||
|
.btn[aria-busy="true"] { cursor: progress; }
|
||||||
|
.btn[aria-pressed="true"] { background: var(--color-paper-3); box-shadow: inset 0 0 0 2px var(--color-ink); }
|
||||||
|
.on-dark .btn--primary { color: var(--color-accent-ink); }
|
||||||
|
.on-dark .btn--primary:hover { background: var(--color-accent-2); }
|
||||||
|
|
||||||
|
.choices { position: relative; display: flex; flex-wrap: wrap; gap: var(--space-xs); border: 0; margin: 0; padding: 0; min-width: 0; }
|
||||||
|
.choices legend { padding: 0; margin-bottom: var(--space-sm); font-size: var(--text-sm); font-weight: 700; color: var(--color-muted); }
|
||||||
|
.choices input { position: absolute; opacity: 0; width: 1px; height: 1px; }
|
||||||
|
.choices label { display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--space-md); border: var(--rule-bar) solid var(--color-rule); border-radius: var(--radius-pill); background: var(--color-card); font-weight: 700; font-size: var(--text-sm); cursor: pointer; transition: transform var(--dur-short) var(--ease-out), background-color var(--dur-short) var(--ease-out), color var(--dur-short) var(--ease-out), border-color var(--dur-short) var(--ease-out); }
|
||||||
|
.choices label:hover { border-color: var(--color-ink); }
|
||||||
|
.choices label:active { transform: translateY(1px); }
|
||||||
|
.choices input:checked + label { background: var(--color-ink); border-color: var(--color-ink); color: var(--color-paper); }
|
||||||
|
.on-dark .choices input:checked + label { color: var(--color-navy); }
|
||||||
|
.choices input:focus-visible + label { outline: 2px solid var(--color-focus); outline-offset: 3px; }
|
||||||
|
.choices--stack { flex-direction: column; flex-wrap: nowrap; }
|
||||||
|
.choices--stack label { border-radius: var(--radius-control); width: 100%; }
|
||||||
|
|
||||||
|
.field { display: grid; gap: var(--space-xs); }
|
||||||
|
.field label { font-size: var(--text-sm); font-weight: 700; }
|
||||||
|
.field input:not([type="range"]), .field select, .chat__form input { width: 100%; min-height: 52px; padding: 0 var(--space-md); border: var(--rule-bar) solid var(--color-muted); border-radius: var(--radius-control); background: var(--color-card); color: var(--color-card-ink); outline: 2px solid transparent; outline-offset: -3px; transition: border-color var(--dur-short) var(--ease-out); }
|
||||||
|
.field input:not([type="range"]):hover, .field select:hover { border-color: var(--color-ink); }
|
||||||
|
.field input:not([type="range"]):focus-visible, .field select:focus-visible, .chat__form input:focus-visible { outline-color: var(--color-focus); border-color: var(--color-focus); }
|
||||||
|
.field input[aria-invalid] { border-color: var(--color-error); }
|
||||||
|
.field input[type="range"] { width: 100%; accent-color: var(--color-accent); min-height: 32px; cursor: pointer; }
|
||||||
|
.field output { font: 400 var(--text-xl)/1 var(--font-display); font-variant-numeric: tabular-nums; }
|
||||||
|
.field__row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md); }
|
||||||
|
|
||||||
|
/* hero: photo bleeds off the right edge, head on the left */
|
||||||
|
.hero { position: relative; background: var(--color-paper); }
|
||||||
|
.hero__inner { position: relative; z-index: 2; padding-block: var(--space-2xl) var(--space-xl); }
|
||||||
|
.hero__text { display: grid; gap: var(--space-lg); justify-items: start; max-width: 46%; }
|
||||||
|
.hero h1 .line { display: block; overflow: hidden; padding: 0 0.12em 0.12em 0; margin-bottom: -0.12em; }
|
||||||
|
.hero h1 .line > span { display: block; animation: line-up 1000ms var(--ease-out) backwards; animation-delay: calc(var(--i) * 120ms + 120ms); }
|
||||||
|
.hero h1 .line--it > span { font-style: italic; }
|
||||||
|
.hero__lead { animation: rise 800ms var(--ease-out) 650ms backwards; }
|
||||||
|
.hero__cta { animation: rise 800ms var(--ease-out) 780ms backwards; }
|
||||||
|
.hero__photo { position: absolute; z-index: 1; top: 0; right: 0; bottom: var(--space-2xl); width: 50%; margin: 0; border-radius: var(--radius-card) 0 0 var(--radius-card); animation: curtain 1300ms var(--ease-in-out) 200ms backwards; }
|
||||||
|
.hero__photo > img { object-position: 60% 40%; animation: settle 2200ms var(--ease-out) 200ms backwards; }
|
||||||
|
@keyframes line-up { from { transform: translateY(110%); } }
|
||||||
|
@keyframes rise { from { opacity: 0; transform: translateY(16px); } }
|
||||||
|
@keyframes curtain { from { clip-path: inset(0 0 0 100% round var(--radius-card)); } to { clip-path: inset(0 0 0 0 round var(--radius-card)); } }
|
||||||
|
@keyframes settle { from { transform: scale(1.16); } }
|
||||||
|
@media (max-width: 60rem) {
|
||||||
|
.hero__inner { padding-block: var(--space-2xl) var(--space-xl); }
|
||||||
|
.hero__text { max-width: none; }
|
||||||
|
.hero__photo { position: relative; width: 100%; aspect-ratio: 4 / 3; border-radius: 0; }
|
||||||
|
.hero { display: flex; flex-direction: column; }
|
||||||
|
.hero__inner { order: 1; }
|
||||||
|
.hero__photo { order: 2; }
|
||||||
|
.quick__wrap { order: 3; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* floating search bar */
|
||||||
|
.quick__wrap { position: relative; z-index: 5; margin-bottom: calc(-1 * var(--space-2xl)); }
|
||||||
|
.quick { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) auto; gap: var(--space-md); align-items: end; max-width: 1120px; margin-inline: auto; padding: var(--space-lg); background: var(--color-card); border-radius: var(--radius-card); box-shadow: var(--shadow-card); animation: rise-lg 900ms var(--ease-out) 900ms backwards; }
|
||||||
|
.quick__field { display: grid; gap: var(--space-2xs); }
|
||||||
|
.quick__field label { font-size: var(--text-sm); font-weight: 700; color: var(--color-muted); }
|
||||||
|
.quick select { width: 100%; min-height: 52px; padding: 0 var(--space-md); border: var(--rule-bar) solid var(--color-muted); border-radius: var(--radius-control); background: var(--color-card); color: var(--color-card-ink); font-weight: 700; transition: border-color var(--dur-short) var(--ease-out); }
|
||||||
|
.quick select:hover { border-color: var(--color-ink); }
|
||||||
|
.quick select:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
|
||||||
|
.quick__go { min-width: 160px; }
|
||||||
|
@keyframes rise-lg { from { opacity: 0; transform: translateY(40px); } }
|
||||||
|
@media (max-width: 60rem) { .quick__wrap { margin-bottom: 0; padding-block: var(--space-lg); } .quick { grid-template-columns: minmax(0, 1fr); } }
|
||||||
|
|
||||||
|
/* path: a real ol */
|
||||||
|
.band--path { padding-block: calc(var(--space-2xl) + var(--space-md)) var(--space-2xl); }
|
||||||
|
.path { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--gap-columns); counter-reset: step; }
|
||||||
|
.path li { counter-increment: step; position: relative; display: grid; gap: var(--space-2xs); align-content: start; padding-top: var(--space-lg); }
|
||||||
|
.path li::after { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--color-ink); transform-origin: left; transition: transform 1000ms var(--ease-out); transition-delay: calc(var(--i, 0) * 120ms + 150ms); }
|
||||||
|
.js .path li:not(.is-in)::after { transform: scaleX(0); }
|
||||||
|
.path li::before { content: counter(step); font: 400 clamp(3.5rem, 6vw, 5.25rem)/0.95 var(--font-display); color: var(--color-accent); font-variant-numeric: lining-nums tabular-nums; }
|
||||||
|
.path a { font: 700 var(--text-lg)/1.15 var(--font-body); text-decoration: none; }
|
||||||
|
.path a:hover { text-decoration: underline; text-decoration-color: var(--color-accent); text-decoration-thickness: 3px; text-underline-offset: 0.25em; }
|
||||||
|
@media (max-width: 60rem) { .path { grid-template-columns: minmax(0, 1fr); gap: var(--space-md); } .path li { grid-template-columns: 72px minmax(0, 1fr); align-items: center; } .path li::before { font-size: var(--text-3xl); } }
|
||||||
|
|
||||||
|
/* finder */
|
||||||
|
.finder__top { align-items: end; margin-bottom: var(--space-2xl); }
|
||||||
|
.finder__top h2 { grid-column: 1 / 8; }
|
||||||
|
.finder__top .lead { grid-column: 8 / 13; }
|
||||||
|
.finder__body { align-items: stretch; }
|
||||||
|
.map { grid-column: 1 / 6; position: relative; aspect-ratio: 605 / 516; background: var(--color-paper-2); border-radius: var(--radius-card); }
|
||||||
|
.map svg { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||||
|
.map path { fill: var(--color-land); stroke: var(--color-card); stroke-width: 3; stroke-linejoin: round; }
|
||||||
|
.pin { position: absolute; left: var(--x); top: var(--y); width: 36px; height: 36px; transform: translate(-50%, -50%); padding: 0; border: 0; background: transparent; cursor: pointer; border-radius: var(--radius-pill); }
|
||||||
|
.pin::before { content: ""; position: absolute; inset: 10px; border-radius: var(--radius-pill); background: var(--color-ink); box-shadow: 0 0 0 3px var(--color-card); transition: transform var(--dur-short) var(--ease-out), background-color var(--dur-short) var(--ease-out); }
|
||||||
|
.js .map.is-in .pin::before { animation: pop 600ms var(--ease-out) backwards; animation-delay: calc(var(--i) * 90ms + 250ms); }
|
||||||
|
.pin:hover::before { transform: scale(1.4); }
|
||||||
|
.pin[aria-pressed="true"]::before { background: var(--color-accent); transform: scale(1.9); }
|
||||||
|
.pin:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 0; }
|
||||||
|
.ping { position: absolute; inset: 8px; border-radius: var(--radius-pill); border: 2px solid var(--color-accent); pointer-events: none; animation: ping 800ms var(--ease-out) both; }
|
||||||
|
.map__city { position: absolute; left: var(--space-md); bottom: var(--space-md); padding: var(--space-xs) var(--space-md); border-radius: var(--radius-control); background: var(--color-card); box-shadow: var(--shadow-card); font-size: var(--text-sm); font-weight: 700; }
|
||||||
|
@keyframes pop { from { transform: scale(0); } }
|
||||||
|
@keyframes ping { from { opacity: 0.9; transform: scale(0.6); } to { opacity: 0; transform: scale(2.8); } }
|
||||||
|
.controls { grid-column: 6 / 13; display: grid; gap: var(--space-xl); align-content: center; padding: var(--space-xl); background: var(--color-paper-2); border-radius: var(--radius-card); }
|
||||||
|
.controls__ages { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap-columns); }
|
||||||
|
.results { margin-top: var(--space-xl); display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--gap-columns); min-height: 300px; scroll-margin-top: var(--space-3xl); }
|
||||||
|
.person { display: grid; gap: var(--space-md); align-content: space-between; padding: var(--space-lg); background: var(--color-card); border-radius: var(--radius-card); box-shadow: var(--shadow-card); animation: rise var(--dur-long) var(--ease-out) backwards; animation-delay: calc(var(--i, 0) * 80ms); transition: transform var(--dur-short) var(--ease-out), box-shadow var(--dur-short) var(--ease-out); }
|
||||||
|
.person:hover { transform: translateY(-5px); box-shadow: var(--shadow-lift); }
|
||||||
|
.person__who { display: flex; align-items: center; gap: var(--space-md); }
|
||||||
|
.person h3 { font-size: var(--text-xl); }
|
||||||
|
.avatar { width: 56px; height: 56px; flex-shrink: 0; display: grid; place-items: center; border-radius: var(--radius-pill); background: var(--tone, var(--tone-a)); color: var(--color-navy); font: 400 var(--text-xl)/1 var(--font-display); }
|
||||||
|
.score { display: flex; align-items: baseline; gap: var(--space-xs); }
|
||||||
|
.score b { font: 400 var(--text-3xl)/1 var(--font-display); font-variant-numeric: lining-nums tabular-nums; }
|
||||||
|
.bar { height: 8px; margin-top: var(--space-xs); border-radius: var(--radius-pill); background: var(--color-paper-3); overflow: hidden; }
|
||||||
|
.bar i { display: block; height: 100%; background: var(--color-accent); transform-origin: left; transform: scaleX(calc(var(--s, 0) / 100)); animation: grow 1100ms var(--ease-out) backwards; animation-delay: calc(var(--i, 0) * 80ms + 200ms); }
|
||||||
|
.tags { display: flex; flex-wrap: wrap; gap: var(--space-xs); padding: 0; margin: 0; list-style: none; }
|
||||||
|
.tags li { padding: var(--space-3xs) var(--space-sm); border-radius: var(--radius-pill); border: var(--rule-bar) solid var(--color-rule); font-size: var(--text-sm); font-weight: 700; transition: background-color var(--dur-short) var(--ease-out); }
|
||||||
|
.tags li.is-shared { background: var(--color-paper-3); border-color: var(--color-paper-3); }
|
||||||
|
.results__empty { grid-column: 1 / -1; align-self: center; font: 400 var(--text-2xl)/1.2 var(--font-display); max-width: 30ch; animation: rise var(--dur-long) var(--ease-out) backwards; }
|
||||||
|
.finder__note { margin-top: var(--space-md); font-size: var(--text-sm); color: var(--color-muted); }
|
||||||
|
@keyframes grow { from { transform: scaleX(0); } }
|
||||||
|
@media (max-width: 60rem) {
|
||||||
|
.finder__top h2, .finder__top .lead, .map, .controls { grid-column: 1 / -1; }
|
||||||
|
.finder__top .lead { margin-top: var(--space-md); }
|
||||||
|
.controls { margin-top: var(--space-lg); padding: var(--space-lg); }
|
||||||
|
.results { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
}
|
||||||
|
@media (max-width: 40rem) { .results { grid-template-columns: minmax(0, 1fr); } .controls__ages { grid-template-columns: minmax(0, 1fr); } }
|
||||||
|
|
||||||
|
/* checks */
|
||||||
|
.checks .head { grid-column: 1 / 6; }
|
||||||
|
.checks__panel { grid-column: 7 / 13; background: var(--color-paper-2); border-radius: var(--radius-card); padding: var(--space-lg); display: grid; gap: var(--space-md); }
|
||||||
|
.checks__who { display: flex; align-items: center; gap: var(--space-md); }
|
||||||
|
.checks__who h3 { font-size: var(--text-xl); }
|
||||||
|
.status { margin-left: auto; display: inline-flex; align-items: center; gap: var(--space-xs); padding: var(--space-xs) var(--space-md); border-radius: var(--radius-pill); background: var(--color-paper-3); font-size: var(--text-sm); font-weight: 700; }
|
||||||
|
.status .icon { display: none; }
|
||||||
|
.status[data-state="done"] { background: var(--color-on-navy); color: var(--color-navy); animation: settle-in 600ms var(--ease-out) both; }
|
||||||
|
.status[data-state="done"] .icon { display: block; }
|
||||||
|
@keyframes settle-in { from { transform: scale(0.9); opacity: 0.4; } }
|
||||||
|
.progress { height: 6px; border-radius: var(--radius-pill); background: var(--color-paper-3); overflow: hidden; }
|
||||||
|
.progress i { display: block; height: 100%; background: var(--color-accent-on-dark); transform-origin: left; transform: scaleX(var(--p, 0)); transition: transform 700ms var(--ease-out); }
|
||||||
|
.rows { list-style: none; margin: 0; padding: 0; }
|
||||||
|
.rows > li { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; gap: var(--space-md); align-items: center; padding-block: var(--space-md); border-top: var(--rule-hair) solid var(--color-rule); }
|
||||||
|
.rows h3 { font: 700 var(--text-md)/1.3 var(--font-body); letter-spacing: 0; }
|
||||||
|
.rows p { font-size: var(--text-sm); color: var(--color-muted); }
|
||||||
|
.rows .tick { display: none; color: var(--color-accent-on-dark); }
|
||||||
|
.rows .is-done .tick { display: block; animation: pop 500ms var(--ease-out) backwards; }
|
||||||
|
.rows .is-done .idle { display: none; }
|
||||||
|
.rows .passed { font-weight: 700; font-size: var(--text-sm); }
|
||||||
|
.checks__foot { display: flex; justify-content: space-between; align-items: center; gap: var(--space-md); border-top: var(--rule-hair) solid var(--color-rule); padding-top: var(--space-md); }
|
||||||
|
@media (max-width: 60rem) { .checks .head, .checks__panel { grid-column: 1 / -1; } .checks__panel { margin-top: var(--space-xl); } }
|
||||||
|
@media (max-width: 40rem) { .rows > li { grid-template-columns: 24px minmax(0, 1fr); } .rows .btn, .rows .passed { grid-column: 2; justify-self: start; } .checks__who { flex-wrap: wrap; } }
|
||||||
|
|
||||||
|
/* photo privacy */
|
||||||
|
.privacy__photo { grid-column: 1 / 8; position: relative; aspect-ratio: 4 / 3; border-radius: var(--radius-card); overflow: hidden; background: var(--color-paper-3); box-shadow: var(--shadow-card); margin: 0; }
|
||||||
|
.privacy__photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 35% 40%; filter: saturate(1.06) contrast(1.04); }
|
||||||
|
.privacy__photo .blur { filter: blur(28px) saturate(1.06); transform: scale(1.15); transition: opacity var(--dur-long) var(--ease-in-out); }
|
||||||
|
.js .privacy__photo:not(.is-hidden) .blur { opacity: 0; }
|
||||||
|
.privacy__badge { position: absolute; left: var(--space-lg); bottom: var(--space-lg); display: inline-flex; align-items: center; gap: var(--space-xs); padding: var(--space-xs) var(--space-md); border-radius: var(--radius-pill); background: var(--color-card); font-size: var(--text-sm); font-weight: 700; z-index: 2; box-shadow: var(--shadow-card); }
|
||||||
|
.privacy__lock { position: absolute; inset: 0; display: grid; place-items: center; z-index: 2; transition: opacity var(--dur-long) var(--ease-in-out); }
|
||||||
|
.privacy__disc { display: grid; place-items: center; width: 112px; height: 112px; border-radius: var(--radius-pill); background: var(--color-card); box-shadow: var(--shadow-card); transition: transform var(--dur-long) var(--ease-out); }
|
||||||
|
.privacy__disc .icon { width: 44px; height: 44px; }
|
||||||
|
.js .privacy__photo:not(.is-hidden) .privacy__lock { opacity: 0; }
|
||||||
|
.js .privacy__photo:not(.is-hidden) .privacy__disc { transform: scale(0.7); }
|
||||||
|
.privacy__badge .icon-open, .privacy__badge .icon-shut { display: none; }
|
||||||
|
.privacy__photo:not(.is-hidden) .icon-open { display: block; }
|
||||||
|
.privacy__photo.is-hidden .icon-shut { display: block; }
|
||||||
|
.privacy__panel { grid-column: 9 / 13; display: grid; gap: var(--space-lg); align-content: center; }
|
||||||
|
@media (max-width: 60rem) { .privacy__photo, .privacy__panel { grid-column: 1 / -1; } .privacy__panel { margin-top: var(--space-xl); } }
|
||||||
|
|
||||||
|
/* talk */
|
||||||
|
.talk__top { margin-bottom: var(--space-2xl); }
|
||||||
|
.talk__body { align-items: stretch; }
|
||||||
|
.invite { grid-column: 1 / 6; display: grid; gap: var(--space-lg); align-content: space-between; padding: var(--space-xl); background: var(--color-card); border-radius: var(--radius-card); box-shadow: var(--shadow-card); }
|
||||||
|
.invite__who { display: flex; align-items: center; gap: var(--space-md); }
|
||||||
|
.invite h3 { font-size: var(--text-xl); }
|
||||||
|
.invite__actions { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
|
||||||
|
.invite__result { min-height: 1lh; font-weight: 700; }
|
||||||
|
.chat { grid-column: 6 / 13; position: relative; display: grid; grid-template-rows: minmax(0, 1fr) auto; min-height: 380px; padding: var(--space-lg); border-radius: var(--radius-card); }
|
||||||
|
.chat__log { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; justify-content: flex-end; gap: var(--space-xs); overflow-y: auto; }
|
||||||
|
.chat__log li { max-width: 80%; padding: var(--space-xs) var(--space-md); border-radius: var(--radius-control); background: var(--color-paper-2); animation: rise var(--dur-short) var(--ease-out) backwards; overflow-wrap: break-word; }
|
||||||
|
.chat__log li.me { align-self: flex-end; background: var(--color-accent); color: var(--color-accent-ink); }
|
||||||
|
.chat__log li.note { align-self: center; max-width: 100%; background: transparent; color: var(--color-muted); font-size: var(--text-sm); font-weight: 700; }
|
||||||
|
.chat__form { display: flex; gap: var(--space-sm); margin-top: var(--space-md); }
|
||||||
|
.chat__form input { flex: 1; min-width: 0; }
|
||||||
|
.chat.is-locked .chat__log, .chat.is-locked .chat__form { visibility: hidden; }
|
||||||
|
.chat__lock { position: absolute; inset: 0; display: none; place-items: center; align-content: center; gap: var(--space-sm); text-align: center; padding: var(--space-lg); font: 400 var(--text-2xl)/1.2 var(--font-display); }
|
||||||
|
.chat__lock .icon { width: 48px; height: 48px; margin-inline: auto; }
|
||||||
|
.chat.is-locked .chat__lock { display: grid; }
|
||||||
|
@media (max-width: 60rem) { .invite, .chat { grid-column: 1 / -1; } .chat { margin-top: var(--space-lg); } }
|
||||||
|
|
||||||
|
/* family and safety */
|
||||||
|
.together { align-items: stretch; row-gap: var(--gap-columns); }
|
||||||
|
.tile { display: grid; gap: var(--space-lg); align-content: start; padding: var(--space-xl); background: var(--color-paper-2); border-radius: var(--radius-card); }
|
||||||
|
.tile--wide { grid-column: 1 / 8; }
|
||||||
|
.tile--narrow { grid-column: 8 / 13; }
|
||||||
|
.tile h2 { font-size: clamp(2rem, 3vw, 3rem); }
|
||||||
|
.tile .lead { color: var(--color-ink-2); }
|
||||||
|
.safety__who { display: flex; align-items: center; gap: var(--space-md); }
|
||||||
|
.safety__who h3 { font-size: var(--text-xl); }
|
||||||
|
.safety__actions { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
|
||||||
|
.live { min-height: 1lh; font-weight: 700; }
|
||||||
|
@media (max-width: 60rem) { .tile--wide, .tile--narrow { grid-column: 1 / -1; } .tile { padding: var(--space-lg); } }
|
||||||
|
|
||||||
|
/* questions and join */
|
||||||
|
.join .head { grid-column: 1 / 7; align-content: start; gap: var(--space-xl); }
|
||||||
|
.qa { margin: 0; display: grid; }
|
||||||
|
.qa > div { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--space-md); padding-block: var(--space-md); border-top: var(--rule-hair) solid var(--color-rule); }
|
||||||
|
.qa dt { font: 700 var(--text-md)/1.3 var(--font-body); }
|
||||||
|
.qa dd { margin: 0; color: var(--color-ink-2); }
|
||||||
|
.card { grid-column: 8 / 13; background: var(--color-card); color: var(--color-card-ink); border-radius: var(--radius-card); box-shadow: var(--shadow-card); padding: var(--space-xl); display: grid; gap: var(--space-lg); align-content: start; }
|
||||||
|
.tabs { display: flex; gap: var(--space-xs); border-bottom: var(--rule-bar) solid var(--color-rule); }
|
||||||
|
.tabs button { min-height: 44px; padding: 0 var(--space-md); border: 0; border-bottom: 4px solid transparent; margin-bottom: calc(-1 * var(--space-3xs)); background: transparent; color: inherit; font-weight: 700; cursor: pointer; transition: border-color var(--dur-short) var(--ease-out); }
|
||||||
|
.tabs button:hover { border-bottom-color: var(--color-rule); }
|
||||||
|
.tabs button[aria-selected="true"] { border-bottom-color: var(--color-accent); }
|
||||||
|
.form-help { min-height: 1lh; font-size: var(--text-sm); }
|
||||||
|
.form-help.is-error { color: var(--color-error); font-weight: 700; }
|
||||||
|
.panel { display: grid; gap: var(--space-md); }
|
||||||
|
@media (max-width: 60rem) { .join .head, .card { grid-column: 1 / -1; } .card { margin-top: var(--space-xl); padding: var(--space-lg); } .qa > div { grid-template-columns: minmax(0, 1fr); gap: var(--space-2xs); } }
|
||||||
|
|
||||||
|
/* footer: letter close */
|
||||||
|
.foot { padding-block: var(--space-3xl) var(--space-xl); }
|
||||||
|
.foot__sign { font: 400 var(--text-display-s)/1.05 var(--font-display); letter-spacing: var(--tracking-display); max-width: 18ch; text-wrap: balance; }
|
||||||
|
.foot__row { margin-top: var(--space-2xl); display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-lg); font-size: var(--text-sm); }
|
||||||
|
.foot__links { display: flex; flex-wrap: wrap; gap: var(--space-lg); list-style: none; margin: 0; padding: 0; }
|
||||||
|
.foot__links a { font-weight: 700; }
|
||||||
|
.foot__links a:hover { text-decoration-color: var(--color-accent-on-dark); text-decoration-thickness: 3px; }
|
||||||
|
.foot [data-credits] { margin-top: var(--space-lg); color: var(--color-muted); font-size: var(--text-xs); max-width: 80ch; }
|
||||||
|
|
||||||
|
/* one scroll reveal, visible without script */
|
||||||
|
.js [data-reveal] { opacity: 0; transform: translateY(18px); transition: opacity 800ms var(--ease-out), transform 800ms var(--ease-out); transition-delay: calc(var(--i, 0) * 110ms); }
|
||||||
|
.js [data-reveal].is-in { opacity: 1; transform: none; }
|
||||||
|
.js .person[data-reveal], .js li[data-reveal] { transition-property: opacity, transform; }
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.js [data-reveal] { opacity: 1; transform: none; transition: none; }
|
||||||
|
.hero h1 .line > span, .hero__lead, .hero__cta, .hero__photo, .hero__photo > img, .quick, .person, .bar i, .chat__log li, .ping, .status, .rows .is-done .tick, .js .map.is-in .pin::before { animation: none; }
|
||||||
|
.bar i { transform: scaleX(calc(var(--s, 0) / 100)); }
|
||||||
|
.js .path li:not(.is-in)::after { transform: none; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
:root {
|
||||||
|
--space-3xs: 2px; --space-2xs: 4px; --space-xs: 8px; --space-sm: 12px; --space-md: 16px; --space-lg: 24px; --space-xl: 40px; --space-2xl: 64px; --space-3xl: 96px; --space-4xl: 144px;
|
||||||
|
--text-xs: 0.8125rem; --text-sm: 0.875rem; --text-md: 1rem; --text-lg: 1.25rem; --text-xl: 1.5625rem; --text-2xl: 1.953rem; --text-3xl: 2.441rem;
|
||||||
|
--text-display-s: clamp(2.25rem, 4vw + 0.5rem, 3.75rem); --text-display: clamp(2.75rem, 5vw + 1rem, 5.25rem);
|
||||||
|
--text-section: clamp(2rem, 2.5vw + 1rem, 3.5rem); --text-title: clamp(1.25rem, 0.75vw + 0.875rem, 1.625rem); --text-lead: clamp(1.125rem, 0.35vw + 1rem, 1.375rem);
|
||||||
|
--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);
|
||||||
|
--dur-micro: 120ms; --dur-short: 220ms; --dur-long: 420ms;
|
||||||
|
--color-error: oklch(52% 0.19 27); --color-success: oklch(50% 0.12 150);
|
||||||
|
--container: 1440px; --gutter: 64px; --gap-columns: 32px; --section-gap-s: 120px; --section-gap: 144px; --section-gap-l: 176px;
|
||||||
|
--z-base: 1; --z-raised: 10; --z-dropdown: 100; --z-sticky: 200; --z-sticky-nav: 300; --z-modal: 400; --z-toast: 500; --z-tooltip: 600;
|
||||||
|
--color-paper: oklch(98.4% 0.012 85); --color-paper-2: oklch(95.6% 0.02 80); --color-paper-3: oklch(91.5% 0.03 75);
|
||||||
|
--color-ink: oklch(25% 0.07 255); --color-ink-2: oklch(36% 0.065 255); --color-muted: oklch(46% 0.045 255);
|
||||||
|
--color-rule: oklch(87% 0.025 80);
|
||||||
|
--color-accent: oklch(56% 0.15 38); --color-accent-2: oklch(49% 0.15 38); --color-accent-ink: oklch(99% 0.008 85); --color-focus: oklch(58% 0.19 38);
|
||||||
|
--color-card: oklch(99.6% 0.006 85); --color-card-ink: oklch(25% 0.07 255);
|
||||||
|
--color-navy: oklch(25% 0.07 255); --color-navy-2: oklch(31% 0.07 255); --color-on-navy: oklch(97% 0.015 85); --color-on-navy-muted: oklch(82% 0.03 255);
|
||||||
|
--color-land: oklch(88% 0.04 250);
|
||||||
|
--font-display: "DM Serif Display", ui-serif, Georgia, serif; --font-body: "Figtree", ui-sans-serif, system-ui, sans-serif; --font-outlier: var(--font-display);
|
||||||
|
--radius-card: 24px; --radius-control: 14px; --radius-pill: 999px; --rule-hair: 1px; --rule-bar: 2px;
|
||||||
|
--shadow-card: 0 18px 40px -22px oklch(25% 0.07 255 / 0.35), 0 2px 6px oklch(25% 0.07 255 / 0.06);
|
||||||
|
--shadow-lift: 0 26px 48px -24px oklch(25% 0.07 255 / 0.45), 0 3px 8px oklch(25% 0.07 255 / 0.08);
|
||||||
|
--tracking-display: -0.025em;
|
||||||
|
--tone-a: oklch(86% 0.08 20); --tone-b: oklch(92% 0.09 95); --tone-c: oklch(88% 0.07 150); --tone-d: oklch(86% 0.06 235);
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"skills": {
|
|
||||||
"motion-design": {
|
|
||||||
"source": "LottieFiles/motion-design-skill",
|
|
||||||
"sourceType": "github",
|
|
||||||
"skillPath": "skills/motion-design/SKILL.md",
|
|
||||||
"computedHash": "9fcf9130cc46ee8fd1d8e09a9263347a9d76efbcbfa46591cc4cefaf19bbb453"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user