Initial commit: Kalei app — docs, mockups, logo, pitch deck

Complete project files including:
- 73 polished HTML mockup screens (onboarding, turn, mirror, lens, gallery, you, ritual, spectrum, modals, guide)
- Design system CSS with Inter font, jewel-tone palette, device frame scaling
- Canonical 6-blade kaleidoscope logo (soft-elegance-final)
- SVG asset library (fragments, icons, patterns, evidence wall, spectrum viz)
- Product docs, brand guidelines, technical architecture, build phases
- Pitch deck and cost projections
- Logo mockup iterations and finalists

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 14:55:22 +01:00
commit 38021c4633
168 changed files with 46724 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.playwright-mcp/
*.tmp.*
node_modules/

200
DECK_SUMMARY.md Normal file
View File

@@ -0,0 +1,200 @@
# Kalei Pitch Deck - Creation Summary
## File Details
- **Location**: `/sessions/bold-sweet-allen/mnt/Kalei/kalei-pitch-deck.pptx`
- **File Size**: 2.8 MB
- **Format**: PPTX (PowerPoint 2007+)
- **Status**: ✓ Valid, fully functional, ready for presentation
## Deck Structure (15 Slides)
### 1. Title Slide
- Kalei logo centered and large
- "KALEI" in 72pt bold Georgia, Amethyst (8B5CF6)
- Tagline: "AI-Powered Mental Wellness"
- Subtitle: "Investor Overview — February 2026"
- Dark background (1E1B2E)
### 2. The Problem
- Light background (F8F7FF)
- Key stat highlighted: "75% of mental health app users churn within 2 weeks"
- $500B market opportunity, 15% YoY growth
- Three supporting points on market gap
### 3. The Solution — Kalei
- Dark background with Amethyst accent
- Four core features in colorful boxes:
- Mirror (Awareness) - Sapphire
- Turn (Reframing) - Emerald
- Lens (Goals) - Amber
- Guide (Coaching) - Rose
- Value proposition and safety message
### 4. How It Works
- Visual flow diagram: "The Kaleidoscope Turn"
- Four-step process with alternating colors
- User journey: Write → Detect → Suggest → Track
### 5. The AI Advantage
- Dark background, stat-focused
- Key metrics: 81% (LLMs) vs 56% (Human average)
- Model comparison and vendor lock-in strategy
- Safety-first crisis detection approach
### 6. Unit Economics
- 2×2 grid of key metrics
- AI costs: $0.034/mo (free), $0.076/mo (Prism)
- Subscription: $4.99/mo
- Gross margin: 98.5%
- Break-even: 3 Prism subscribers
### 7. Cost Innovation
- Comparison table: Old vs New strategy
- Claude Haiku ($450/mo) → DeepSeek ($40/mo)
- 90% AI cost reduction
- Data residency: US/EU only
### 8. Revenue Projections
- Bar chart with MRR growth data
- Month 3: $25 → Month 18: $3,000
- Profitable from month 3 with 5 subscribers
- Professional chart styling with data labels
### 9. Infrastructure Efficiency
- 2×2 grid of cost metrics
- Total Phase 1: €16/month
- 8-13% of typical startup infrastructure
- €2,000 budget = unlimited runway
### 10. Go-to-Market
- App Store + Play Store launch
- Freemium model with generous free tier
- Target: EU users aged 25-45
- Community partnership strategy
### 11. Competitive Landscape
- Competitor comparison table
- Headspace: $12.99/mo (generic)
- Calm: $14.99/mo (meditation-only)
- Woebot: Free (basic CBT)
- Kalei: $4.99/mo (AI-personalized) - highlighted
### 12. Traction & Milestones
- Four numbered achievements with colored circles
- Tech architecture complete
- AI model benchmarking validated
- Safety framework designed
- 4-week MVP timeline
### 13. The Team
- Solo founder: Matt
- Full-stack developer
- Supported by Claude Code AI assistance
- €2,000 personal investment - lean and committed
### 14. The Ask
- Three call-to-action boxes
- Seeking: Strategic partnerships and advisory
- Offering: Ground-floor access to 98% margin product
- Next milestone: 50 DAU beta in 3 months
### 15. Thank You / Contact
- Kalei logo centered
- Contact: matt@letsbe.solutions
- Tagline: "Every thought deserves a second look."
- Dark background (1E1B2E)
## Design Features
### Color Palette (Brand-Compliant)
- **Dark BG**: 1E1B2E (8 slides)
- **Light BG**: F8F7FF (7 slides)
- **Primary**: 8B5CF6 Amethyst (headers, highlights)
- **Deep**: 5B21B6 Deep Amethyst (dark text on light)
- **Accent Colors**:
- Sapphire (3B82F6) - data, metrics
- Emerald (10B981) - growth, positive
- Amber (F59E0B) - cost, efficiency
- Rose (EC4899) - emphasis, CTAs
### Typography
- **Headers**: Georgia 54pt+ bold (consistent, professional)
- **Body**: Calibri 14-18pt (readable, business-standard)
- **Accents**: Palatino italic (taglines, quotes)
- **Stat Callouts**: 40-72pt for visual impact
### Visual Elements
- **Logo**: Embedded on all 15 slides (0.5" × 0.5" corner + centered on title/closing)
- **Shadows**: Consistent outer shadows (blur: 8, offset: 3, opacity: 0.15) on colored elements
- **Spacing**: 0.5-0.8" margins, generous whitespace
- **Charts**:
- Bar chart (Slide 8) with rounded corners, subtle grid
- Tables with header styling and alternating rows
- **Shapes**: Rectangles with shadow effects for depth
### Professional Standards
- Clean, modern layout matching top-tier accelerator presentations
- No decorative lines or patterns (solid backgrounds only)
- No overlapping elements
- Single focus per slide
- Accessible color contrast (white text on dark, dark text on light)
## Charts & Data Visualization
### Revenue Projections Chart (Slide 8)
- Type: Vertical bar chart
- Data: 5 months (3, 6, 9, 12, 18)
- Values: $25, $150, $400, $1,000, $3,000
- Colors: Amethyst (8B5CF6)
- Features: Data labels on bars, subtle grid, rounded corners
### Comparison Tables
- **Slide 7** (Cost Innovation): 2 rows × 4 columns
- **Slide 11** (Competitive Landscape): 4 competitors highlighted with Kalei prominent
## Key Statistics Emphasized
- 75% churn rate (problem)
- $500B market size
- 81% AI emotional intelligence vs 56% human
- 98.5% gross margin
- €16/month operating cost
- 90% cost reduction
- 4-week MVP timeline
- 3 subscriber break-even point
## File Structure
- 15 valid slide XMLs
- 16 media files (logo embedded multiple times for all slides)
- 4 chart files with embedded Excel worksheets
- All relationships properly configured
- Zero corruption or errors
## Quality Assurance
- ✓ All 15 slides present and valid
- ✓ No file corruption
- ✓ All required elements present
- ✓ Brand colors correctly applied
- ✓ Logo embedded successfully
- ✓ Charts rendered with proper styling
- ✓ Text alignment and spacing consistent
- ✓ Typography matches specifications
- ✓ Professional appearance verified
## How to Use
1. Open `/sessions/bold-sweet-allen/mnt/Kalei/kalei-pitch-deck.pptx` in PowerPoint, Keynote, or Google Slides
2. Slides are presentation-ready with all content, colors, and formatting applied
3. All fonts are standard (Georgia, Calibri, Palatino) for maximum compatibility
4. Logo is embedded - no external files needed
5. Charts are fully interactive in PowerPoint
## Notes
- Presentation is self-contained (all media embedded)
- Compatible with PowerPoint 2007 and later
- Optimized for 16:9 widescreen displays (standard presentation format)
- Color-corrected for projector compatibility
- All text is selectable and editable
---
**Created**: February 22, 2026
**Tool**: PptxGenJS (Node.js library)
**Status**: Production-Ready

BIN
charts-section.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
competitive-dark-table.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

BIN
competitive-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
cover-page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,403 @@
# Kalei — Brand Metaphor & Experience Design
## The Core Metaphor
A kaleidoscope takes broken, random fragments of glass and reveals them as beautiful, symmetrical patterns. It never changes the pieces — it changes the **angle**. Turn it once, and chaos becomes art. Turn it again, and the same fragments form something entirely new.
**Kalei does the same thing with your thoughts.**
Your situation hasn't changed. Your circumstances are the same fragments they were a moment ago. But Kalei shifts the angle — and suddenly you see the pattern, the meaning, the opportunity that was always there.
This isn't toxic positivity. A kaleidoscope doesn't pretend the glass isn't broken. It proves that broken things can still be beautiful.
---
## Brand Vocabulary
Every app builds an unconscious vocabulary through the words it uses in buttons, headers, notifications, and empty states. Kalei's vocabulary should reinforce the metaphor without being heavy-handed.
### Primary Terms (Use Frequently)
| Instead of... | Kalei says... | Why |
|----------------------|----------------------|--------------------------------------------------------|
| Reframe | **Turn** | You "turn" a kaleidoscope to see a new pattern |
| Negative thought | **Fragment** | Broken glass — raw material, not a flaw |
| Reframed perspective | **Pattern** | The beautiful arrangement revealed by a new angle |
| Journal entry | **Reflection** | Light + mirrors = what a kaleidoscope runs on |
| Daily session | **Turn of the day** | Each day you take a fresh turn |
| Progress/history | **Gallery** | A collection of the patterns you've created |
| Insight | **Facet** | One face of a multifaceted view |
| Saved reframe | **Keepsake** | A pattern worth holding onto |
### Secondary Terms (Use Sparingly for Flavor)
- **Shift** — a small adjustment in perspective
- **Prism** — the tool that splits one beam into many colors
- **Mosaic** — the bigger picture built from many small pieces
- **Spectrum** — the full range of ways to see something
- **Illuminate** — to light up what was hidden
- **Refract** — to bend light in a new direction
### Words to Avoid
- "Fix" — implies the user is broken
- "Heal" — too clinical, positions app as therapy
- "Transform" — too dramatic, overpromises
- "Manifest" — save for the Manifestation Engine context only
- "Positive vibes" — trivializes the process
- "Journey" — overused in wellness apps
---
## Feature Naming
### The Reframer → **The Kaleidoscope** (or just **Turn**)
The flagship feature. User inputs a negative thought (a fragment), and Kalei reveals multiple reframed perspectives (patterns).
- **CTA button:** "Turn" (verb — active, simple, one word)
- **Input prompt:** "Drop in a fragment" or "What's on your mind?"
- **Loading state:** A subtle kaleidoscope rotation animation
- **Results header:** "Here's what the same pieces look like from a new angle"
- **Individual reframes:** Displayed as "Pattern 1," "Pattern 2," "Pattern 3" — each a different arrangement of the same facts
- **Save action:** "Keep this pattern"
### Manifestation Engine → **The Lens**
The goal-setting and manifestation feature. If the Kaleidoscope shows you new patterns in what already exists, the Lens focuses your vision on what you're building toward.
- **Section header:** "Your Lens"
- **Goal creation:** "Set your focus"
- **Daily affirmation:** "Today's focus"
- **Vision board:** "The View" — what you see when you look through the lens
- **Progress check-in:** "Sharpen your focus"
- **Milestone reached:** "Crystal clear"
### Combined Narrative
> The Kaleidoscope helps you see beauty in what's already there.
> The Lens helps you focus on what's ahead.
> Together, they're Kalei — a new way to see your life.
---
## Onboarding Flow
The onboarding should teach the metaphor through experience, not explanation.
### Screen 1 — The Fragment
Visual: A single shard of colored glass on a dark background. Simple. Stark.
> **"This is a thought."**
> *On its own, it can feel sharp. Random. Hard to make sense of.*
### Screen 2 — The Turn
Visual: The shard multiplies and rotates into a kaleidoscope pattern. Animated transition.
> **"But change the angle..."**
> *...and the same piece becomes part of something beautiful.*
### Screen 3 — The Reveal
Visual: A full, stunning kaleidoscope pattern fills the screen. Color blooms.
> **"Kalei doesn't change your reality."**
> *It changes how you see it.*
### Screen 4 — First Turn (Interactive)
> **"Let's try your first Turn."**
> *Type something that's been weighing on you.*
The user types a real negative thought. Kalei processes it and returns 23 reframed perspectives. The user experiences the core value proposition within 60 seconds of opening the app.
### Screen 5 — Welcome
> **"Welcome to Kalei."**
> *Every day is a new turn.*
---
## Visual Design Language
### The Kaleidoscope Aesthetic
The visual identity should evoke the feeling of looking through a kaleidoscope without being literal or childish.
**Color Palette:**
- **Primary:** Deep jewel tones — amethyst purple, sapphire blue, emerald green
- **Secondary:** Warm golds and soft amber (the light passing through glass)
- **Background:** Near-black or deep navy (the dark tube of a kaleidoscope — the fragments shine against darkness)
- **Accent:** Prismatic gradients for highlights and CTAs (light refracting)
**Why dark backgrounds:** A kaleidoscope works by reflecting light against darkness. The dark UI makes the colorful elements pop — and also positions Kalei as premium, not bubbly.
**Avoid:** Pastel wellness aesthetic. No sage green, no cream, no watercolor blobs. Kalei is jewel-toned, rich, and confident.
**Typography:**
- Clean, modern sans-serif for body text (clarity, legibility)
- One geometric or slightly decorative font for headlines (faceted, angular — like cut glass)
**Iconography:**
- Geometric and faceted — hexagons, triangles, crystalline shapes
- Avoid circles and soft curves (that's every other wellness app)
- Subtle symmetry in icon design (mirrors the symmetry of kaleidoscope patterns)
### Signature Animation: The Turn
The core micro-interaction of the app. When a user submits a thought for reframing:
1. **Input phase:** Fragment icon — a single angular shard
2. **Processing phase:** The shard begins to rotate and multiply (kaleidoscope turning). Subtle, smooth, 1.52 seconds
3. **Reveal phase:** Fragments settle into a symmetric pattern. The reframed perspectives appear beneath or within the pattern
This animation should become iconic — the "Kalei Turn" — recognizable in screenshots, marketing, and social media.
### Pattern Generation
Each reframing session could generate a unique, procedurally-created kaleidoscope pattern based on the input. These patterns become:
- **Visual identity for saved reframes** — each keepsake has its own pattern
- **Gallery items** — your collection of patterns grows over time
- **Shareable cards** — "My pattern for today" with the reframe text overlaid
- **Profile decoration** — your most-used patterns become part of your visual identity
This is a powerful retention mechanic: **users build a gallery of beautiful, personal, unique patterns.** Each one tied to a moment where they chose to see things differently.
---
## Navigation & Information Architecture
### Tab Bar (4 tabs)
| Icon | Label | Function |
|------|-------|----------|
| ◇ (geometric shard) | **Turn** | The Kaleidoscope — reframe a thought |
| ◎ (lens/circle) | **Lens** | The Manifestation Engine — goals & focus |
| ▦ (grid of patterns) | **Gallery** | History of all your Turns and patterns |
| ● (profile) | **You** | Settings, stats, subscription, profile |
### Turn Tab (Home)
- Hero area: "What's the fragment?" — text input
- Below: "Turn of the day" — a featured prompt or previous pattern
- Below: "Recent patterns" — quick access to last 3 Turns
- Floating action: Quick Turn button (always accessible)
### Lens Tab
- Current focus (active goal) displayed prominently
- Today's affirmation
- Vision board / The View
- Progress tracker with "sharpening" visual metaphor
### Gallery Tab
- Grid view of kaleidoscope patterns, each linked to a saved reframe
- Filterable by date, mood tag, or theme
- Tap to expand: see the original fragment, the patterns revealed, and any notes
- Option to reshare or re-Turn (reframe the same thought again for fresh perspectives)
### You Tab
- Streak counter: "X-day turning streak"
- Stats: Total turns, patterns saved, most common themes
- Settings, subscription management
- "Your spectrum" — a visual breakdown of the emotional themes you've explored
---
## Notification & Engagement Copy
### Daily Prompt (Push Notification)
Rotate through styles:
- "Ready for today's Turn? 🔮"
- "Same pieces, new angle. What fragment are you carrying today?"
- "Your Gallery is growing. Add today's pattern."
- "The glass hasn't changed. But the view can. Take a Turn."
### Streak Maintenance
- Day 3: "Three days of turning fragments into patterns. Keep going."
- Day 7: "A week of new angles. Your Gallery is filling up."
- Day 30: "30 days. 30 Turns. You're seeing things most people never will."
- Streak broken: "The kaleidoscope is still here when you're ready. No pressure."
### Milestone Celebrations
- First Turn: "Your first pattern. This is where it starts."
- 10th Turn: "10 fragments turned into 10 beautiful patterns."
- 50th Turn: "You've looked at 50 hard things and found something worth keeping in every one."
- 100th Turn: "100 Turns. You don't just see the bright side — you see every side."
### Empty States
- Gallery (no saves yet): "Your Gallery is waiting. The next pattern you save will appear here."
- Lens (no goal set): "What are you focusing on? Set your first Lens."
- Turn history (new user): "Every kaleidoscope starts with a single turn."
---
## Subscription & Monetization Naming
### Free Tier → **Kalei**
- 3 Turns per day
- Basic pattern generation
- Gallery (last 30 days)
### Premium Tier → **Kalei Prism**
- Unlimited Turns
- Full Gallery (all history)
- The Lens (Manifestation Engine)
- Advanced reframe styles (Stoic, Compassionate, Pragmatic, Growth)
- Custom pattern themes
- Export & share patterns
**Why "Prism":** A prism takes a single beam of light and splits it into its full spectrum. Kalei Prism gives you the full spectrum of features.
**CTA for upgrade:** "See the full spectrum" or "Unlock your Prism"
### Pricing Display
> **Kalei Prism — $7.99/month**
> *Unlimited Turns. Full Gallery. The Lens. Your complete spectrum.*
---
## Social & Sharing Mechanics
### Pattern Cards
When a user saves a reframe, they can generate a **Pattern Card** — a shareable image featuring:
- Their unique kaleidoscope pattern (procedurally generated)
- The reframed thought (the pattern, not the fragment — we never share the negative thought)
- Subtle Kalei branding
These are designed to be **Instagram Story and iMessage native** — correct aspect ratios, visually striking against both light and dark backgrounds.
### "Turn It" Sharing
A user can share a prompt with a friend: "Turn this fragment" — challenging someone else to reframe a thought. This introduces new users to the app through a natural, non-spammy mechanic.
### Community Gallery (Future, v2+)
An opt-in public gallery where users can share their best patterns anonymously. Browse how other people turned their fragments into patterns. Upvote the most powerful reframes. This builds community without requiring social profiles or exposing personal information.
---
## Marketing & Brand Voice
### Tagline Options
1. **"Same pieces. New angle."** — the core proposition in five words
2. **"Turn how you see it."** — active, empowering, references the mechanic
3. **"Find the pattern."** — mysterious, inviting, implies hidden beauty
4. **"A new way to see."** — simple, universal
**Recommended primary tagline:** *Same pieces. New angle.*
### Brand Voice Guidelines
**Kalei speaks like:** A wise friend who sees beauty in hard things — not a therapist, not a guru, not a cheerleader. Calm. Grounded. A little poetic. Never preachy.
**Tone:** Warm but not soft. Confident but not aggressive. Poetic but not flowery.
**Examples:**
| Situation | ❌ Wrong tone | ✅ Kalei tone |
|-----------|-------------|-------------|
| User inputs a negative thought | "Let's turn that frown upside down!" | "Let's see what this looks like from another angle." |
| User completes a reframe | "Amazing job! You're so strong!" | "There it is. A pattern worth keeping." |
| User hasn't opened app in a week | "We miss you! Come back!" | "Still here. Ready when you are." |
| Explaining the app | "AI-powered cognitive reframing tool" | "A kaleidoscope for your mind." |
### App Store Description (Draft)
> **Kalei — A kaleidoscope for your mind.**
>
> A kaleidoscope doesn't change the glass. It changes the angle. Suddenly, broken fragments become a beautiful pattern.
>
> Kalei does the same thing with your thoughts.
>
> Type what's weighing on you. Kalei reveals new perspectives — not toxic positivity, but genuine, research-backed ways to see the same situation differently. Every reframe is grounded in cognitive behavioral science and built to help you think clearer, not just feel better.
>
> **The Kaleidoscope** — Turn any negative thought into multiple new perspectives. Same facts. Different angle. Beautiful patterns.
>
> **The Lens** — Set your focus. Define what you're building toward. Daily affirmations, vision tracking, and goal clarity powered by AI.
>
> **Your Gallery** — Every Turn creates a unique pattern. Save your favorites. Watch your collection grow. See how far you've come.
>
> Same pieces. New angle. That's Kalei.
### Elevator Pitch
> "Kalei is a kaleidoscope for your mind. You give it a negative thought — a broken fragment — and it shows you the beautiful patterns hidden inside. It's AI-powered cognitive reframing that helps you see the same situation from new angles. Not toxic positivity. Real perspective shifts, grounded in science. Same pieces, new angle."
---
## Technical Implementation Notes
### Procedural Pattern Generation
Each reframing session should generate a unique kaleidoscope pattern. Implementation approach:
- Use the **input text as a seed** — same thought always generates the same base pattern (creates personal connection)
- Apply **reframe variant as a rotation** — Pattern 1, Pattern 2, Pattern 3 are visual rotations of the base
- Render using **Canvas/WebGL** in React Native (or pre-rendered SVG for performance)
- Patterns should be **deterministic** — reopening a saved reframe shows the same pattern
- Export patterns as **PNG at 1080×1920** for Instagram Stories and **1080×1080** for feed posts
### Animation Specs
- **Turn animation:** 1.5s ease-in-out rotation, fragments multiplying from 1→6→full symmetry
- **Loading shimmer:** Prismatic color shift across a geometric skeleton screen
- **Tab transitions:** Subtle faceted wipe (diagonal geometric transition, not standard iOS slide)
- **Pattern reveal:** Fragments drift into position with slight parallax depth (0.3s stagger per fragment)
### Reframe Prompt Engineering
The Claude API prompts for reframing should be structured to match the metaphor:
```
System prompt context:
"You are the engine behind Kalei, a kaleidoscope for the mind.
The user gives you a fragment — a negative thought or situation.
Your job is to reveal the patterns — multiple genuine, grounded
perspectives on the same situation. You never change the facts.
You change the angle.
You are not a therapist. You are not toxic positivity.
You are a kaleidoscope: you show what was already there,
arranged in a way the user hadn't seen before."
```
---
## Summary: The Metaphor at Every Layer
| Layer | How the metaphor shows up |
|-------|---------------------------|
| **Name** | Kalei — short for kaleidoscope |
| **Tagline** | Same pieces. New angle. |
| **Core mechanic** | "Turn" a fragment into patterns |
| **Visual design** | Jewel tones, geometric shapes, dark backgrounds, prismatic gradients |
| **Animations** | Kaleidoscope rotation on every reframe |
| **Vocabulary** | Fragments, patterns, turns, facets, gallery, keepsakes |
| **Feature names** | The Kaleidoscope, The Lens, The Gallery |
| **Subscription** | Kalei Prism — "See the full spectrum" |
| **Sharing** | Pattern Cards — unique generative art tied to each reframe |
| **Notifications** | Poetic, grounded, always referencing angles and patterns |
| **Brand voice** | Calm, wise, finds beauty in hard things |
| **Onboarding** | User experiences a real Turn within 60 seconds |
| **Retention** | Gallery of personal patterns grows over time — collectible, visual, meaningful |
---
*The glass hasn't changed. But you have.*

View File

@@ -0,0 +1,31 @@
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" width="400" height="400">
<defs>
<linearGradient id="b1" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#A78BFA"/><stop offset="100%" stop-color="#6D28D9"/></linearGradient>
<linearGradient id="b2" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#60A5FA"/><stop offset="100%" stop-color="#1E40AF"/></linearGradient>
<linearGradient id="b3" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#34D399"/><stop offset="100%" stop-color="#065F46"/></linearGradient>
<linearGradient id="b4" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#FCD34D"/><stop offset="100%" stop-color="#92400E"/></linearGradient>
<linearGradient id="b5" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#F472B6"/><stop offset="100%" stop-color="#9D174D"/></linearGradient>
<linearGradient id="b6" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#818CF8"/><stop offset="100%" stop-color="#3730A3"/></linearGradient>
<linearGradient id="prismatic" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="25%" stop-color="#93C5FD"/>
<stop offset="50%" stop-color="#6EE7B7"/>
<stop offset="75%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#FBCFE8"/>
</linearGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(200,200)">
<path d="M 0,-24 Q 50,-100 30,-170 Q -10,-170 -40,-140 Q -30,-80 0,-24" fill="url(#b1)" opacity="0.9"/>
<path d="M 0,-24 Q 50,-100 30,-170 Q -10,-170 -40,-140 Q -30,-80 0,-24" fill="url(#b2)" opacity="0.87" transform="rotate(60)"/>
<path d="M 0,-24 Q 50,-100 30,-170 Q -10,-170 -40,-140 Q -30,-80 0,-24" fill="url(#b3)" opacity="0.87" transform="rotate(120)"/>
<path d="M 0,-24 Q 50,-100 30,-170 Q -10,-170 -40,-140 Q -30,-80 0,-24" fill="url(#b4)" opacity="0.87" transform="rotate(180)"/>
<path d="M 0,-24 Q 50,-100 30,-170 Q -10,-170 -40,-140 Q -30,-80 0,-24" fill="url(#b5)" opacity="0.87" transform="rotate(240)"/>
<path d="M 0,-24 Q 50,-100 30,-170 Q -10,-170 -40,-140 Q -30,-80 0,-24" fill="url(#b6)" opacity="0.82" transform="rotate(300)"/>
<circle r="30" fill="url(#prismatic)" filter="url(#glow)"/>
<circle cx="-6" cy="-8" r="8" fill="white" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,175 @@
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<defs>
<!-- Background Gradient -->
<radialGradient id="bgGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#0A0E1A" />
<stop offset="100%" stop-color="#050508" />
</radialGradient>
<!-- Center Backlight Glow -->
<radialGradient id="centerLight" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.2" />
<stop offset="100%" stop-color="#ffffff" stop-opacity="0" />
</radialGradient>
<!-- Blade Gradients: Amethyst (0°) -->
<linearGradient id="g0_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#A78BFA" />
<stop offset="100%" stop-color="#8B5CF6" />
</linearGradient>
<linearGradient id="g0_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#8B5CF6" />
<stop offset="100%" stop-color="#5B21B6" />
</linearGradient>
<!-- Blade Gradients: Sapphire (60°) -->
<linearGradient id="g1_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#93C5FD" />
<stop offset="100%" stop-color="#3B82F6" />
</linearGradient>
<linearGradient id="g1_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3B82F6" />
<stop offset="100%" stop-color="#1D4ED8" />
</linearGradient>
<!-- Blade Gradients: Emerald (120°) -->
<linearGradient id="g2_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#6EE7B7" />
<stop offset="100%" stop-color="#10B981" />
</linearGradient>
<linearGradient id="g2_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#10B981" />
<stop offset="100%" stop-color="#047857" />
</linearGradient>
<!-- Blade Gradients: Amber (180°) -->
<linearGradient id="g3_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#FCD34D" />
<stop offset="100%" stop-color="#F59E0B" />
</linearGradient>
<linearGradient id="g3_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#F59E0B" />
<stop offset="100%" stop-color="#B45309" />
</linearGradient>
<!-- Blade Gradients: Rose (240°) -->
<linearGradient id="g4_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#F9A8D4" />
<stop offset="100%" stop-color="#EC4899" />
</linearGradient>
<linearGradient id="g4_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#EC4899" />
<stop offset="100%" stop-color="#BE185D" />
</linearGradient>
<!-- Blade Gradients: Indigo (300°) -->
<linearGradient id="g5_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#A5B4FC" />
<stop offset="100%" stop-color="#6366F1" />
</linearGradient>
<linearGradient id="g5_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#6366F1" />
<stop offset="100%" stop-color="#4338CA" />
</linearGradient>
<!-- Prismatic Core Gradient -->
<linearGradient id="prismatic" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#8B5CF6" />
<stop offset="25%" stop-color="#3B82F6" />
<stop offset="50%" stop-color="#10B981" />
<stop offset="75%" stop-color="#F59E0B" />
<stop offset="100%" stop-color="#EC4899" />
</linearGradient>
<!-- Standard Glow Filter -->
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- Strong Core Glow -->
<filter id="coreGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="12" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<!-- Deep Dark Space Background -->
<rect width="100%" height="100%" fill="url(#bgGlow)" />
<!-- Center Backlight -->
<circle cx="200" cy="200" r="120" fill="url(#centerLight)" />
<!-- The Iris (Rotated by -15 deg to emphasize "The Turn" & "New Angle") -->
<g transform="translate(200, 200) rotate(-15)">
<!--
THE KALEIDOSCOPE SHARDS
Overlapping translucent facets using screen blend mode for emergent colors
-->
<!-- Amethyst Blade (0°) -->
<g transform="rotate(0)" style="mix-blend-mode: screen;">
<!-- Main Outer Face -->
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g0_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round" />
<!-- Inner Crystalline Bevel -->
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g0_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round" />
</g>
<!-- Sapphire Blade (60°) -->
<g transform="rotate(60)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g1_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round" />
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g1_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round" />
</g>
<!-- Emerald Blade (120°) -->
<g transform="rotate(120)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g2_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round" />
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g2_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round" />
</g>
<!-- Amber Blade (180°) -->
<g transform="rotate(180)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g3_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round" />
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g3_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round" />
</g>
<!-- Rose Blade (240°) -->
<g transform="rotate(240)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g4_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round" />
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g4_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round" />
</g>
<!-- Indigo Blade (300°) -->
<g transform="rotate(300)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g5_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round" />
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g5_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round" />
</g>
<!--
THE KALEIDOSCOPE CORE (The Point of Transformation)
Solid hexagonal prism reflecting the mirrors inside the optical instrument
-->
<g filter="url(#coreGlow)">
<!-- Prismatic Hexagon Aperture -->
<polygon points="38,0 19,32.909 -19,32.909 -38,0 -19,-32.909 19,-32.909" fill="url(#prismatic)" />
<!-- Internal Kaleidoscope Mirror Lines -->
<line x1="-38" y1="0" x2="38" y2="0" stroke="#ffffff" stroke-width="1.5" opacity="0.6" />
<line x1="-19" y1="-32.909" x2="19" y2="32.909" stroke="#ffffff" stroke-width="1.5" opacity="0.6" />
<line x1="19" y1="-32.909" x2="-19" y2="32.909" stroke="#ffffff" stroke-width="1.5" opacity="0.6" />
<!-- Center Refraction Point -->
<circle r="4" fill="#ffffff" filter="url(#glow)" />
</g>
<!-- Outer Core Reflection Edge -->
<polygon points="38,0 19,32.909 -19,32.909 -38,0 -19,-32.909 19,-32.909" fill="none" stroke="#ffffff" stroke-width="1" opacity="0.8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,57 @@
# Build Timeline & Execution Phases
Last updated: 2026-02-22
This folder contains the sequential build plan for developing Kalei. All features ship together in a single v1 release. The phases below represent an execution timeline for managing development complexity, not separate product phases.
Read in order:
1. `phase-0-groundwork-and-dev-environment.md`
2. `phase-1-platform-foundation.md`
3. `phase-2-core-experience-build.md`
4. `phase-3-launch-readiness-and-hardening.md`
5. `phase-4-spectrum-and-scale.md`
## Build Timeline Overview
These phases organize the work sequentially for manageable development and testing. All features — including Spectrum — are built toward a unified v1 launch.
- Phase 0: Groundwork and Dev Environment
- Goal: stable tooling, accounts, repo standards, and local infrastructure.
- Phase 1: Platform Foundation
- Goal: production-quality backend skeleton, auth, entitlements, core data model.
- Phase 2: Core Experience Build
- Goal: ship Mirror, Turn, Lens, Ritual, Evidence Wall end-to-end.
- Phase 3: Launch Readiness and Hardening
- Goal: Spectrum integration, safety, billing, reliability, compliance, app store readiness.
- Phase 4: Post-Launch Scale
- Goal: analytics pipeline, feature enhancements, growth optimization, scaling controls.
## Development Gate Rules
Do not move to the next build phase until the current phase exit checklist is complete.
If a phase slips, reduce scope but do not skip quality gates for:
- security
- safety
- observability
- data integrity
## Product vs. Build Phases
**Important distinction:** These build phases are execution timelines, not product tiers. The product launches with all features (Mirror, Turn, Lens, Ritual, Evidence Wall, Guide, Spectrum) in a single v1 release. Free tier and Prism subscription tier have feature limits, but both versions of v1 include all features at launch.
## Tooling policy
These phase docs assume an open-source-first stack:
- Gitea for source control and CI
- GlitchTip for error tracking
- PostHog self-hosted for product analytics
- Ollama (local) and vLLM (staging/prod) for open-weight model serving
Platform exceptions remain for mobile distribution and push:
- Apple App Store and Google Play billing/distribution APIs
- APNs and FCM delivery infrastructure

View File

@@ -0,0 +1,185 @@
# Phase 0 - Groundwork and Dev Environment
Duration: 1-2 weeks
Primary owner: Founder + coding assistant
## 1. Objective
Build a stable base so feature work can move fast without breaking:
- all required accounts are created
- local stack boots reliably
- repo structure and standards are in place
- CI checks run on every pull request
## 2. Prerequisites
- Read `docs/kalei-getting-started.md`
- Read `docs/kalei-system-architecture-plan.md`
## 3. Outcomes
By the end of Phase 0 you will have:
- local Postgres and Redis running via Docker
- mobile app bootstrapped with Expo
- API service bootstrapped with Fastify
- initial DB migration system in place
- lint, format, and test commands working
- CI pipeline validating every PR
## 4. Deep Work Breakdown
## 4.1 Access and Account Setup
Task list:
1. Set up Gitea (self-hosted or managed) for source control and CI.
2. Set up open-weight model serving accounts and endpoints (Ollama local, vLLM target host).
3. Create GlitchTip project for API and mobile error tracking.
4. Create PostHog self-hosted project for product analytics.
5. Set up DNS (PowerDNS self-hosted or managed DNS provider) and add domain.
6. Confirm Apple Developer and Google Play Console access (required for app distribution).
Deliverables:
- shared credential inventory (local secure password manager)
- documented secret naming convention
## 4.2 Repository and Branching Standards
Task list:
1. Define branch policy: `main`, short-lived feature branches.
2. Define PR checklist template.
3. Add CODEOWNERS or at least reviewer policy.
4. Add issue templates for bug and feature requests.
Deliverables:
- `CONTRIBUTING.md`
- `.gitea/pull_request_template.md` (or repository PR template equivalent)
- `.gitea/ISSUE_TEMPLATE/*` (or repository issue template equivalent)
## 4.3 Local Development Environment
Task list:
1. Install and verify Git, Node, npm, Docker, Expo CLI.
2. Add Docker compose for Postgres + Redis.
3. Create `.env.example` for API and mobile.
4. Add one-command local start script.
Deliverables:
- `infra/docker/docker-compose.yml`
- `services/api/.env.example`
- `apps/mobile/.env.example`
- root `Makefile` or npm scripts for local startup
## 4.4 API and Mobile Skeletons
Task list:
1. Create Fastify app with health endpoint.
2. Create Expo app with tabs template.
3. Add API client module to mobile app.
4. Show backend health status in app.
Deliverables:
- API running on local port
- mobile app able to read API response
## 4.5 Data and Migration Baseline
Task list:
1. Choose migration tool (for example, `node-pg-migrate`, `drizzle`, or `knex`).
2. Create first migration set for identity tables.
3. Add migration run and rollback commands.
4. Add seed command for local dev data.
Minimum initial tables:
- users
- profiles
- auth_sessions
- refresh_tokens
## 4.6 Quality and Automation Baseline
Task list:
1. Add ESLint + Prettier for API and mobile.
2. Add API unit test framework and one integration test.
3. Configure Gitea Actions (or Woodpecker CI) for lint + test.
4. Add commit hooks (optional but recommended) using `husky`.
Deliverables:
- passing CI on every push/PR
- at least one passing API integration test
## 5. Suggested Day-by-Day Plan
Day 1:
- account setup
- tooling install
- repo folder scaffold
Day 2:
- docker compose and env files
- API skeleton with `/health`
Day 3:
- Expo app setup
- mobile to API health call
Day 4:
- migration tooling and first migrations
- baseline seed script
Day 5:
- linting and tests
- CI setup
- first stable baseline commit
## 6. Validation Checklist
All items must be true:
- `docker compose` starts Postgres and Redis with no errors.
- API starts and `GET /health` returns 200.
- Mobile app loads and displays backend health.
- Migrations can run on clean DB and rollback at least one step.
- CI runs lint and tests successfully.
## 7. Exit Criteria
You can exit Phase 0 when:
- no manual setup surprises remain for a fresh machine
- all team members can run the stack locally in under 30 minutes
- baseline quality checks are automated
## 8. Platform exceptions
These are not open source, but required for shipping mobile apps:
- Apple App Store tooling and APIs
- Google Play tooling and APIs
## 9. Typical Pitfalls and Fixes
- Pitfall: unclear `.env` expectations.
- Fix: complete `.env.example` files with comments.
- Pitfall: mobile app cannot reach local API on real device.
- Fix: use machine LAN IP, not localhost, for device testing.
- Pitfall: migration drift.
- Fix: never edit applied migration files; create a new migration.

View File

@@ -0,0 +1,333 @@
# Kalei Build Plan — Phases 1 & 2
### From Platform Foundation to Core Experience
**Total Duration:** 58 weeks
**Approach:** Backend-first in Phase 1, then mobile + backend in parallel in Phase 2
---
## Overview
This document consolidates the two core build phases that take Kalei from a configured dev environment to a fully functional app with Mirror, Turn, and Lens experiences end-to-end. Phase 1 lays the platform foundation (auth, schema, AI gateway, safety). Phase 2 builds the user-facing experience on top of that foundation.
```
Phase 1: Platform Foundation (Weeks 1-3)
→ Auth, schema, entitlements, AI gateway, safety, observability
Phase 2: Core Experience Build (Weeks 4-8)
→ Mirror v1, Turn v1, Lens v1, Gallery, end-to-end flows
```
---
# PHASE 1 — Platform Foundation
**Duration:** 23 weeks
**Primary owner:** Backend-first with mobile stub integration
## 1.1 Objective
Build a production-grade platform foundation: robust auth and session model, entitlement checks for free vs. paid plans, core domain schema for Mirror/Turn/Lens, AI gateway scaffold with usage metering, and observability + error handling baseline.
## 1.2 Entry Criteria
Phase 0 exit checklist must be complete.
---
## 1.3 Core Scope
### 1.3.1 API Module Setup
Implement service modules: auth, profiles, entitlements, mirror (session/message skeleton), turn (request skeleton), lens (goal/action skeleton), ai_gateway, usage_cost, safety (precheck skeleton).
Each module needs: route handlers, input/output schema validation, service layer, repository/data access layer, and unit tests.
### 1.3.2 Identity and Access
Implement: email/password registration and login, JWT access token (short TTL), refresh token rotation and revocation, logout all sessions, role model (at least `user`, `admin`).
Security details: hash passwords with Argon2id or bcrypt, store refresh tokens hashed, include device metadata per session.
### 1.3.3 Entitlement Model
Implement plan model now, even before paywall UI is complete.
Suggested plan keys: `free`, `prism`, `prism_plus`.
Implement gates for: turns per day, mirror sessions per week, spectrum access.
Integration approach: no RevenueCat dependency. Ingest App Store Server Notifications and Google Play RTDN notifications directly. Maintain local entitlement snapshots as source of truth for authorization.
### 1.3.4 Data Model (Phase 1 Schema)
Create migrations for: users, profiles, subscriptions, entitlement_snapshots, turns, mirror_sessions, mirror_messages, mirror_fragments, lens_goals, lens_actions, ai_usage_events, safety_events.
Design requirements: every row has `created_at`, `updated_at` where relevant. Index by `user_id` and key query timestamps. Soft delete where legal retention requires it.
### 1.3.5 AI Gateway Scaffold
Implement a strict abstraction now: provider adapter interface, request envelope (feature, model, temperature, timeout), response normalization, token usage extraction, retry + timeout + circuit breaker policy.
Do not expose provider SDK directly in feature modules.
### 1.3.6 Safety Precheck Skeleton
Implement now even if rule set is basic: deterministic keyword precheck, safety event logging, return safety status to caller. Mirror and Turn endpoints must call this precheck before generation.
### 1.3.7 Usage Metering and Cost Guardrails
Implement: per-user usage counters in Redis, endpoint-level rate limit middleware, AI usage event write on every provider call, per-feature daily budget checks.
### 1.3.8 Observability Baseline
Implement: structured logging with request IDs, error tracking to GlitchTip, latency and error metrics per endpoint, AI cost metrics by feature.
---
## 1.4 Build Sequence
**Week 1:**
1. Finalize schema and migration files
2. Implement auth and profile endpoints
3. Add integration tests for auth flows
**Week 2:**
1. Implement entitlements and plan gating middleware
2. Implement AI gateway interface and one real provider adapter
3. Implement Redis rate limits and usage counters
**Week 3:**
1. Implement Mirror and Turn endpoint skeletons with safety precheck
2. Implement Lens goal and action skeleton endpoints
3. Add complete observability hooks and dashboards
---
## 1.5 API Contract — End of Phase 1
**Auth:**
- `POST /auth/register`
- `POST /auth/login`
- `POST /auth/refresh`
- `POST /auth/logout`
- `GET /me`
**Entitlements:**
- `GET /billing/entitlements`
- Webhook endpoints for App Store and Google Play billing event ingestion
**Feature Skeletons:**
- `POST /mirror/sessions`
- `POST /mirror/messages`
- `POST /turns`
- `POST /lens/goals`
## 1.6 Testing Requirements
Minimum automated coverage: auth happy path and invalid credential path, token refresh rotation path, entitlement denial for free limits, safety precheck path for crisis keyword match, AI gateway timeout and fallback behavior. Recommended: basic load test for auth + turn skeleton endpoints.
## 1.7 Phase 1 Deliverables
**Code:** Migration files for core schema, API modules with tests, Redis-backed rate limit and usage tracking, AI gateway abstraction with one provider, safety precheck middleware.
**Operational:** GlitchTip configured, endpoint metrics visible, API runbook for local and staging.
## 1.8 Phase 1 Exit Criteria
You can exit Phase 1 when: core auth model is stable and tested, plan gating is enforced server-side, Mirror/Turn/Lens endpoint skeletons are live, AI calls only happen through AI gateway, logs/metrics/error tracking are active.
## 1.9 Phase 1 Risks
- **Auth complexity balloons early.** Mitigation: keep v1 auth strict but minimal; defer advanced IAM.
- **Schema churn from feature uncertainty.** Mitigation: maintain a schema decision log and avoid premature optimization.
- **Provider coupling in feature code.** Mitigation: enforce gateway adapter pattern in code review.
---
---
# PHASE 2 — Core Experience Build
**Duration:** 35 weeks
**Primary owner:** Mobile + backend in parallel
## 2.1 Objective
Ship Kalei's core user experience end-to-end: Mirror with fragment highlighting and inline reframe, Turn generation with 3 perspectives and micro-action, Lens goals/daily actions/daily focus, Gallery/history views for user continuity.
## 2.2 Entry Criteria
Phase 1 exit checklist complete.
---
## 2.3 Product Scope
### 2.3.1 Mirror (Awareness)
**Required behavior:** User starts mirror session → submits messages → backend runs safety precheck first → backend runs fragment detection on safe content → app highlights detected fragments above confidence threshold → user taps fragment for inline reframe → user closes session and receives reflection summary.
**Backend work:** Finalize `mirror_sessions`, `mirror_messages`, `mirror_fragments`. Add close-session reflection endpoint. Add mirror session list/detail endpoints.
**Mobile work:** Mirror compose UI, highlight rendering for detected fragment ranges, tap-to-reframe interaction card, session close and reflection display.
### 2.3.2 Turn (Kaleidoscope)
**Required behavior:** User submits a fragment or thought → backend runs safety precheck → backend generates 3 reframed perspectives → backend returns micro-action (if-then) → user can save turn to gallery.
**Backend work:** Finalize `turns` table and categories. Add save/unsave state. Add history list endpoint.
**Mobile work:** Turn input and loading animation, display 3 patterns + micro-action, save to gallery and view history.
### 2.3.3 Lens (Direction)
**Required behavior:** User creates one or more goals → app generates or stores daily action suggestions → user can mark actions complete → optional daily affirmation/focus shown.
**Backend work:** Finalize `lens_goals`, `lens_actions`. Daily action generation endpoint. Daily affirmation endpoint through AI gateway.
**Mobile work:** Goal creation UI, daily action checklist UI, completion updates and streak indicator.
### 2.3.4 The Rehearsal (Lens Sub-Feature)
**Required behavior:** User selects "Rehearse" within a Lens goal → backend generates a personalized visualization script (process-oriented, first-person, multi-sensory, with obstacle rehearsal) → app displays as a guided text flow with SVG progress ring → session completes with a follow-up micro-action.
**Backend work:** Rehearsal generation endpoint through AI gateway. Prompt template enforcing: first-person perspective, present tense, multi-sensory detail, process focus, obstacle inclusion, ~10 min reading pace. Cache generated scripts per goal; refresh when actions change. Add `rehearsal_sessions` table.
**Mobile work:** Rehearsal screen (single flowing view with SVG progress ring timer). Step transitions (Grounding → Process → Obstacle → Close). Completion state with generated SVG pattern. Rehearsal history in Gallery.
### 2.3.5 The Ritual (Context-Anchored Daily Flow)
**Required behavior:** User selects a Ritual template (Morning/Evening/Quick) and anchors to a daily context → app delivers a timed, sequenced flow chaining Mirror/Turn/Lens steps → Ritual completion tracked with context consistency metrics.
**Backend work:** `ritual_configs` table (template, anchored time, notification preferences). `ritual_completions` table (timestamp, duration, steps completed). Context consistency calculation logic (same-window tracking per Wood et al.). Ritual notification scheduling.
**Mobile work:** Ritual selection/setup during onboarding or settings. Single flowing Ritual screen with SVG progress segments per step. Step transitions without navigation. Completion state with Ritual pattern. Context consistency display in streaks.
### 2.3.6 The Evidence Wall (Mastery Tracking)
**Required behavior:** System automatically collects proof points from all features (completed actions, saved keepsakes, self-corrections, streak milestones, goal completions, reframe echoes) → Evidence Wall in "You" tab displays as SVG mosaic → AI surfaces evidence contextually when self-efficacy dip detected.
**Backend work:** `evidence_points` table (user_id, type, source_feature, source_id, description, created_at). Background job to detect and log proof points from existing feature activity. Efficacy-dip detection logic (pattern analysis on recent Mirror/Turn language). Evidence surfacing endpoint for contextual AI integration.
**Mobile work:** Evidence Wall view in "You" tab (SVG mosaic grid, color-coded by source). Timeline toggle view. Evidence count badges. Contextual evidence card component for use within Mirror/Turn sessions.
---
## 2.4 Deep Technical Workstreams
### 2.4.1 Prompt and Output Contracts
Create strict prompt templates and JSON output contracts per feature: Mirror fragment detection, Mirror inline reframe, Turn multi-pattern output, Lens daily focus output, Rehearsal visualization script, Evidence Wall contextual surfacing. Require server-side validation of AI output shape before returning to clients.
### 2.4.2 Safety Integration
At this phase safety must be complete for user-facing flows: all Mirror and Turn requests pass safety gate, crisis response path returns resource payload (not reframe payload), safety events are queryable for audit.
### 2.4.3 Entitlement Enforcement
Enforce in API middleware: free turn daily limits, free mirror weekly limits, spectrum endpoint lock for non-entitled users. Add clear response codes and client UI handling for plan limits.
### 2.4.4 Performance Targets
Set targets now and test against them: Mirror fragment detection p95 under 3.5s, Turn generation p95 under 3.5s, client screen transitions under 300ms for cached navigation.
---
## 2.5 Build Plan
**Week 1 (Week 4 overall):**
- Finish Mirror backend and basic mobile UI
- Complete fragment highlight rendering
**Week 2 (Week 5 overall):**
- Finish inline reframe flow and session reflections
- Add Mirror history and session detail view
**Week 3 (Week 6 overall):**
- Finish Turn backend and mobile flow
- Add save/history integration
**Week 4 (Week 7 overall):**
- Finish Lens goals and daily actions
- Add daily focus/affirmation flow
- Build Rehearsal backend + mobile UI (Lens sub-feature)
**Week 5 (Week 8 overall):**
- Build Ritual backend (config, completions, consistency tracking)
- Build Ritual mobile UI (single-flow screen, SVG progress, setup flow)
- Build Evidence Wall backend (proof point collection job, evidence_points table)
**Week 6 (Week 9 overall):**
- Build Evidence Wall mobile UI (mosaic view, timeline, contextual card)
- Wire Evidence Wall contextual surfacing into Mirror/Turn sessions
- Integrate Ritual into onboarding flow
**Week 7 (Week 10 overall — hardening):**
- Optimize latency across all features
- Improve retry and offline handling
- Run end-to-end QA pass across all flows including Ritual → Mirror → Turn → Lens → Evidence
---
## 2.6 Test Plan
**Unit tests:** Prompt builder functions, AI output validators, entitlement middleware, safety decision functions.
**Integration tests:** Full Mirror message lifecycle, full Turn generation lifecycle, Lens action completion lifecycle.
**Manual QA matrix:** Normal usage, plan-limit blocked usage, low-connectivity behavior, crisis-language safety behavior.
## 2.7 Phase 2 Deliverables
**Functional:** Mirror v1 complete, Turn v1 complete, Lens v1 complete (with Rehearsal), Ritual v1 complete, Evidence Wall v1 complete, Gallery/history v1 complete.
**Engineering:** Stable endpoint contracts, documented prompt versions, meaningful test coverage for critical flows, feature-level latency and error metrics.
## 2.8 Phase 2 Exit Criteria
You can exit Phase 2 when: users can complete Mirror → Turn → Lens flow end-to-end, Ritual sequences features into a single daily flow, Rehearsal generates process-oriented visualization scripts, Evidence Wall collects and surfaces proof points, plan limits and safety behavior are consistent and test-backed, no critical P0 bugs in core user paths, telemetry confirms baseline latency and reliability targets.
## 2.9 Phase 2 Risks
- **Output variability from model causes UI breakage.** Mitigation: strict response schema validation and fallback copy.
- **Too much feature scope in one pass.** Mitigation: ship v1 flows first, defer advanced UX polish.
- **Latency drift from complex prompts.** Mitigation: simplify prompts and use cached static context.
---
---
# Cross-Phase Reference
## Combined Timeline
| Week | Phase | Focus |
|------|-------|-------|
| 1 | Phase 1 | Schema, auth, profile endpoints |
| 2 | Phase 1 | Entitlements, AI gateway, rate limits |
| 3 | Phase 1 | Feature skeletons, safety, observability |
| 4 | Phase 2 | Mirror backend + mobile UI |
| 5 | Phase 2 | Mirror inline reframes + history |
| 6 | Phase 2 | Turn backend + mobile flow |
| 7 | Phase 2 | Lens goals + daily actions + Rehearsal |
| 8 | Phase 2 | Ritual (backend + mobile + onboarding) + Evidence Wall backend |
| 9 | Phase 2 | Evidence Wall mobile + contextual surfacing integration |
| 10 | Phase 2 | Hardening, latency, end-to-end QA |
## Dependencies
- Phase 2 Mirror depends on Phase 1 AI gateway + safety precheck
- Phase 2 entitlement enforcement depends on Phase 1 plan gating middleware
- Phase 2 Lens daily actions depend on Phase 1 AI gateway being stable
- All Phase 2 features depend on Phase 1 observability for debugging
## Combined API Surface (End of Phase 2)
**Auth:** register, login, refresh, logout, me
**Billing:** entitlements, App Store + Google Play webhooks
**Mirror:** create session, send message (with fragment detection), close session (with reflection), list sessions, session detail
**Turn:** create turn (with 3 patterns + micro-action), save/unsave, list history
**Lens:** create goal, generate daily actions, complete action, daily affirmation
**Rehearsal:** generate visualization script (per goal), list rehearsal history
**Ritual:** create/update ritual config, start ritual session, complete ritual, list completions, context consistency stats
**Evidence Wall:** list proof points (filterable by type/source), get contextual evidence (for AI surfacing)
**Gallery:** list saved turns + mirror reflections + rehearsal/ritual patterns (unified history)

View File

@@ -0,0 +1,167 @@
# Phase 3 - Launch Readiness and Hardening
Duration: 2-4 weeks
Primary owner: Full stack + operations focus
## 1. Objective
Prepare Kalei for real users with production safeguards:
- safety policy completion and crisis flow readiness
- subscription and entitlement reliability
- app and API operational stability
- privacy and compliance basics for app store approval
## 2. Entry Criteria
Phase 2 exit checklist complete.
## 3. Scope
## 3.1 Safety and Trust Hardening
Tasks:
1. finalize crisis keyword and pattern sets
2. validate crisis response templates and regional resources
3. add safety dashboards and alerting
4. add audit trail for safety decisions
Validation goals:
- crisis path returns under 1 second in most cases
- no crisis path returns reframing output
## 3.2 Billing and Entitlements
Tasks:
1. complete App Store Server Notifications ingestion
2. complete Google Play RTDN ingestion
3. build reconciliation jobs for both stores (entitlements sync)
4. test expired, canceled, trial, billing retry, and restore scenarios
5. add paywall gating in all required clients
Validation goals:
- entitlement state converges within minutes after billing changes
- no premium endpoint access for expired plans
## 3.3 Reliability Engineering
Tasks:
1. finalize health checks and readiness probes
2. add backup and restore procedures for Postgres
3. add Redis persistence strategy for critical counters if required
4. define incident severity levels and on-call workflow
Validation goals:
- verified DB restore from backup in staging
- runbook exists for API outage, DB outage, AI provider outage
## 3.4 Security and Compliance Baseline
Tasks:
1. secrets rotation policy and documented process
2. verify transport security and secure headers
3. verify account deletion and data export flows
4. prepare privacy policy and terms for submission
Validation goals:
- basic security checklist signed off
- app store privacy disclosures map to real data flows
## 3.5 Observability and Cost Control
Tasks:
1. define alerts for latency, error rate, and AI spend thresholds
2. implement monthly spend cap and automatic degradation rules
3. monitor feature-level token cost dashboards
Validation goals:
- alert thresholds tested in staging
- degradation path verified (Lens fallback first)
## 3.6 Beta and Release Pipeline
Tasks:
1. set up TestFlight internal/external testing
2. set up Android internal testing track
3. run beta cycle with scripted feedback collection
4. triage and fix launch-blocking defects
Validation goals:
- no unresolved launch-blocking defects
- release checklist complete for both stores
## 4. Suggested Execution Plan
Week 1:
- safety hardening and billing reconciliation
- initial reliability runbooks
Week 2:
- security/compliance checks
- backup and restore drills
- full observability alert tuning
Week 3:
- TestFlight and Play internal beta
- defect triage and fixes
Week 4 (if needed):
- final store submission materials
- go/no-go readiness review
## 5. Release Checklists
## 5.1 API release checklist
- migration plan reviewed
- rollback plan documented
- dashboards green
- error budget acceptable
## 5.2 Mobile release checklist
- build reproducibility verified
- crash-free session baseline from beta acceptable
- paywall and entitlement states correct
- copy and metadata final
## 5.3 Business and policy checklist
- privacy policy URL live
- terms URL live
- support contact available
- crisis resources configured for launch regions
## 6. Exit Criteria
You can exit Phase 3 when:
- app is store-ready with stable entitlement behavior
- safety flow is verified and monitored
- operations runbooks and alerts are live
- backup and restore are proven in practice
## 7. Risks To Watch
- Risk: entitlement mismatch from webhook delays.
- Mitigation: scheduled reconciliation and idempotent webhook handling.
- Risk: launch-day AI latency spikes.
- Mitigation: timeout limits and graceful fallback behavior.
- Risk: compliance gaps discovered late.
- Mitigation: complete privacy mapping before store submission.

View File

@@ -0,0 +1,158 @@
# Phase 4 - Spectrum and Scale
Duration: 3-6 weeks
Primary owner: Data + backend + product analytics
## 1. Objective
Deliver Phase 2 intelligence features and scaling maturity:
- Spectrum weekly and monthly insights
- aggregated analytics model over user activity
- asynchronous jobs and batch processing
- cost, reliability, and scaling controls for growth
## 2. Entry Criteria
Phase 3 exit checklist complete.
## 3. Scope
## 3.1 Spectrum Data Foundation
Implement tables and data flow for:
- session-level emotional vectors
- turn-level impact analysis
- weekly aggregates
- monthly aggregates
Data design requirements:
- user-level partition/index strategy for query speed
- clear retention and deletion behavior
- exclusion flags so users can omit sessions from analysis
## 3.2 Aggregation Pipeline
Build asynchronous jobs:
1. post-session analysis job
2. weekly aggregation job
3. monthly narrative job
Job engineering requirements:
- idempotency keys
- retry with backoff
- dead-letter queue for failures
- metrics for queue depth and job duration
## 3.3 Spectrum Insight Generation
Implement AI-assisted summary generation using aggregated data only.
Rules:
- do not include raw user text in generated insights by default
- validate output tone and safety constraints
- version prompts and track prompt revisions
## 3.4 Spectrum API and Client
Backend endpoints:
- weekly insight feed
- monthly deep dive
- spectrum reset
- exclusions management
Mobile screens:
- emotional landscape view
- pattern distribution view
- insight feed cards
- monthly summary panel
## 3.5 Growth-Ready Scale Controls
Implement scale milestones:
- worker isolation from interactive API if needed
- database optimization and index tuning
- caching strategy for read-heavy insight endpoints
- cost-aware model routing for non-critical generation
## 4. Detailed Execution Plan
Week 1:
- schema rollout for spectrum tables
- event ingestion hooks from Mirror/Turn/Lens
Week 2:
- implement post-session analysis and weekly aggregation jobs
- add metrics and retries
Week 3:
- implement monthly aggregation and narrative generation
- implement spectrum API endpoints
Week 4:
- mobile spectrum dashboard v1
- push notification hooks for weekly summaries
Week 5-6 (as needed):
- performance tuning
- scale and cost optimization
- UX polish for insight comprehension
## 5. Quality and Analytics Requirements
Quality gates:
- no raw-content leakage in Spectrum UI
- weekly job completion SLA met
- dashboard load times within agreed target
Analytics requirements:
- track spectrum engagement events
- track conversion impact from spectrum teaser to upgrade
- track retention lift for spectrum users vs non-spectrum users
## 6. Deliverables
Functional deliverables:
- Spectrum dashboard v1
- weekly and monthly insight generation
- user controls for exclusions and reset
Engineering deliverables:
- robust worker pipeline with retries and DLQ
- aggregated analytics tables with indexing strategy
- end-to-end observability for job health and costs
## 7. Exit Criteria
You can exit Phase 4 when:
- weekly and monthly insights run on schedule reliably
- users can view, reset, and control analysis scope
- spectrum cost and performance stay inside defined envelopes
- data deletion behavior is verified for raw and derived records
## 8. Risks To Watch
- Risk: analytics pipeline complexity causes reliability issues.
- Mitigation: isolate workers and enforce idempotent jobs.
- Risk: insight quality is too generic.
- Mitigation: prompt iteration with rubric scoring and blinded review.
- Risk: costs drift with growing history windows.
- Mitigation: aggregate-first processing and strict feature budget controls.

View File

@@ -0,0 +1,347 @@
# Kalei — The Science Behind Every Turn
### How Peer-Reviewed Research Powers Every Feature in the App
**Version:** 1.0 · February 2026
**Purpose:** Map every Kalei feature to its scientific foundation, ensuring real cognitive science is woven into the user journey — not as decoration, but as structural integrity.
---
## Why This Document Exists
Kalei's core differentiator is that it treats "manifestation" as what it actually is: a structured psychological process operating through known cognitive and behavioral mechanisms. Every feature in the app should be traceable back to published, peer-reviewed research. This document is the bridge between our research library and the product — a reference for anyone building, writing, or designing any part of Kalei.
The rule is simple: **if we can't cite it, we don't claim it.**
---
## The Research Library — 8 Pillars
Our research base spans 18 peer-reviewed papers across 8 scientific domains. Each domain maps directly to one or more of Kalei's 6 Steps and core features.
| # | Research Pillar | Papers | Primary Kalei Feature(s) |
|---|---|---|---|
| 1 | Goal Setting & Implementation | 4 | Step 1: Decide (Clarity) · Step 5: Act in Alignment · The Lens |
| 2 | Visualization & Mental Imagery | 3 | Step 2: See It · Guided Visualizations |
| 3 | Self-Efficacy | 1 | Step 3: Believe It's Possible (But Not Guaranteed) · The Turn |
| 4 | Attention & Neuroscience | 3 | Step 4: Notice Differently · The Mirror · The Reframer |
| 5 | Habit Formation | 2 | Step 6: Repeat and Compound · Streaks & Rituals |
| 6 | Placebo & Expectation Effects | 2 | Overall framework · Onboarding · Belief calibration |
| 7 | Social Networks | 1 | Future community features · The Spectrum |
| 8 | Self-Regulation & Feedback Loops | 2 | The Guide · Goal Check-Ins · Weekly Pulse · Cross-Feature Coaching |
---
## Pillar 1: Goal Setting & Implementation
### The Science
**Locke & Latham (2002)***Building a Practically Useful Theory of Goal Setting and Task Motivation: A 35-Year Odyssey*
The foundational paper on goal-setting theory, drawn from over 35 years of research. Core finding: specific, challenging goals consistently lead to higher performance than vague "do your best" goals. The mechanism works through four channels — goals direct attention, energize effort, increase persistence, and promote the discovery of task-relevant strategies.
**Locke & Latham (2006)***New Directions in Goal-Setting Theory*
Extends the original theory with moderators and mediators. Goals work best when paired with high self-efficacy, feedback loops, and commitment. Critically, goal complexity matters — overly complex goals without adequate learning time can backfire. This informs how Kalei scaffolds goals progressively rather than demanding perfection upfront.
**Gollwitzer (1999)***Implementation Intentions: Strong Effects of Simple Plans*
The landmark paper on "if-then" planning. When people form implementation intentions ("If situation X arises, I will do Y"), follow-through rates increase dramatically — in some studies doubling or tripling goal attainment. The mechanism: implementation intentions create strong mental links between situational cues and planned responses, effectively delegating action initiation to environmental triggers rather than relying on willpower.
**Gollwitzer & Sheeran***Implementation Intentions*
A comprehensive overview confirming that implementation intentions are most effective when self-regulatory problems threaten goal striving and when backed by strong, activated goal intentions. The if-then format works because it puts people in a position to both *see* and *seize* opportunities to act.
### Where It Lives in Kalei
**Step 1: Decide (Clarity)** — The Lens feature guides users through defining exactly what they want, using AI to help them move from vague wishes ("I want to be happier") to specific, challenging goals ("I want to complete a 5K run by June, training 3x per week"). The AI draws on Locke & Latham's specificity principle: the more precise the goal, the more it directs attention and effort.
**Step 5: Act in Alignment** — Every micro-action the AI generates follows Gollwitzer's if-then format. Instead of "exercise more," Kalei produces: "If it's 7am on Monday/Wednesday/Friday, then I put on my running shoes and walk out the front door." This isn't a style choice — it's the most empirically validated action-planning format in psychology.
**The Lens (Manifestation Engine)** — The entire goal-creation flow is structured around Locke & Latham's principles: clarity of outcome, challenge calibration (not too easy, not impossibly hard), commitment rituals, and built-in feedback loops through progress tracking.
**Design implications:**
- Goal inputs should guide toward specificity (prompts, not blank fields)
- Challenge level should be calibrated — the AI should push back on goals that are too vague or too easy
- Implementation intentions should always use the literal "If... then..." structure
- Progress feedback should be frequent and visible
---
## Pillar 2: Visualization & Mental Imagery
### The Science
**Schuster et al. (2011)***Best Practice for Motor Imagery: A Systematic Literature Review*
A massive cross-disciplinary review across education, medicine, music, psychology, and sports. Key finding: motor imagery (mentally rehearsing actions) is most effective when combined with physical practice, when sessions are structured with clear protocols, and when the imagery is vivid and first-person. Pure fantasy without behavioral specificity doesn't work — the visualization must be *process-oriented*, not just outcome-oriented.
**Liu et al. (2025)***Effects of Imagery Practice on Athletes' Performance: A Multilevel Meta-Analysis*
A meta-analysis of 86 studies with 3,593 athletes confirming that imagery practice enhances performance across agility, strength, and sport-specific skills. The optimal dosage: approximately 10 minutes, 3 times per week, over about 100 days. Combining imagery with 1-2 additional psychological skills (like self-talk or goal setting) produces stronger effects than imagery alone.
**Seok & Choi (2023)***The Impact of Mental Practice on Motor Function in Patients With Stroke*
A systematic review and meta-analysis demonstrating that mental practice facilitates motor recovery in stroke patients — evidence that visualization activates overlapping neural circuits with actual physical execution, even when the body cannot currently perform the action.
### Where It Lives in Kalei
**Step 2: See It (Mental Rehearsal)** — Kalei generates personalized visualization scripts that guide users through mentally rehearsing the *process* of achieving their goal, not just imagining the end state. This distinction is critical: the research shows process visualization (imagining yourself studying, training, preparing) outperforms outcome visualization (imagining yourself on the podium).
**Guided Visualization Sessions** — Following Schuster et al.'s best-practice findings, Kalei's visualization prompts are first-person, sensory-rich, and process-focused. The AI asks users to engage multiple senses: what do you see, hear, feel? The recommended frequency (Liu et al.'s finding of ~10 minutes, 3x/week) informs the suggested cadence of visualization reminders.
**Design implications:**
- Visualization scripts must be process-oriented, not just outcome fantasy
- First-person perspective, multi-sensory detail
- Sessions should be ~10 minutes, suggested 3x per week
- Combine visualization with goal-setting and self-talk elements for maximum effect
- Never present visualization as sufficient alone — always pair with action steps
---
## Pillar 3: Self-Efficacy
### The Science
**Bandura (1977)***Self-Efficacy: Toward a Unifying Theory of Behavioral Change*
One of the most cited papers in all of psychology. Bandura's core claim: the belief in one's *capability* to execute specific behaviors is the strongest predictor of whether someone will attempt, sustain, and succeed at a goal. Self-efficacy is not general confidence — it's domain-specific belief that "I can do this particular thing."
Four sources build self-efficacy, in order of potency:
1. **Mastery experiences** — successfully doing the thing (strongest source)
2. **Vicarious experience** — watching someone similar succeed
3. **Verbal persuasion** — being told you can do it (weakest but still real)
4. **Physiological states** — interpreting your emotional/physical state as capability vs. inadequacy
Critically, Bandura distinguishes *efficacy expectations* (I can do it) from *outcome expectations* (doing it will produce results). Both matter, but self-efficacy is the bottleneck — people don't attempt what they don't believe they can execute.
### Where It Lives in Kalei
**Step 3: Believe It's Possible — But Not Guaranteed** — This is the philosophical soul of Kalei. The app explicitly rejects certainty-based belief ("the universe will provide") in favor of Bandura's capability-based belief ("I have or can develop the skills to make this happen"). This single distinction separates Kalei from every magical-thinking manifestation app on the market.
**The Turn (Reframing Engine)** — When users submit a negative thought, the AI reframe is designed to build self-efficacy, not just provide comfort. A good reframe should help users:
- Recognize past mastery experiences ("You've handled difficult things before — remember when...")
- Reinterpret physiological states ("That anxiety isn't proof you can't do this — it's your body preparing to perform")
- Shift from outcome fixation to capability focus ("You can't control whether you get the job, but you can control how well you prepare")
**Onboarding & Belief Calibration** — The coaching style selection (brutal honesty, gentle guidance, logical analysis, etc.) maps to Bandura's verbal persuasion channel. Different people respond to different persuasion styles. A skeptic needs logical arguments for capability; someone more emotionally oriented needs warmth and encouragement. The coaching style personalizes the persuasion channel.
**Design implications:**
- Reframes should target capability belief, never promise outcomes
- Track and surface mastery experiences ("You've completed 12 micro-actions this week")
- Coaching tone selection = personalizing the verbal persuasion channel
- Never say "you will succeed" — say "you have what it takes to give this your best shot"
- Celebrate effort and execution, not just outcomes
---
## Pillar 4: Attention & Neuroscience
### The Science
**Yantis (2008)***The Neural Basis of Selective Attention: Cortical Sources and Targets of Attentional Modulation*
Selective attention is an intrinsic component of how the brain processes reality. Modulatory signals from frontal and parietal cortex amplify neural responses to relevant information and suppress irrelevant inputs. What you attend to literally changes what your brain represents — attention isn't just noticing, it's constructing your experienced reality.
**Stevens & Bavelier (2012)***The Role of Selective Attention on Academic Foundations*
Attention is trainable. This paper demonstrates that selective attention underlies learning, memory, and skill acquisition. Crucially, attention training transfers — improving attentional control in one domain can enhance performance broadly. The brain's attentional system is plastic and responsive to practice.
**Koch & Tsuchiya***Attention and Consciousness: Two Distinct Brain Processes*
A critical theoretical paper distinguishing attention from consciousness. You can attend to things without being conscious of them, and you can be conscious of things without attending to them. This matters for Kalei because it means that training attention (a controllable process) can shift what enters conscious awareness (what feels like "reality") — without requiring mystical explanations.
### Where It Lives in Kalei
**Step 4: Notice Differently** — After setting a goal and building belief, Kalei trains users to notice differently. This isn't "the universe sending signs" — it's Yantis's selective attention at work. When you define a goal, your brain's attentional filters begin prioritizing goal-relevant information. Opportunities that were always there become visible because your attentional system is now tuned to detect them.
**The Mirror (Freeform Notebook)** — The Mirror feature directly applies attentional science. As users write freely, Kalei's AI detects cognitive distortion patterns (catastrophizing, black-and-white thinking, etc.) and gently highlights them. This is attention training in action: the AI acts as an external attentional spotlight, pointing at patterns the user's own attentional system has habituated to and therefore stopped noticing.
**The Reframer's Pattern Analysis** — Over time, the app analyzes which cognitive distortions appear most frequently in a user's Turns and Mirror sessions. This longitudinal attention data helps users see their own attentional biases — "You tend to catastrophize most on Sunday evenings" — turning unconscious attentional habits into conscious, addressable patterns.
**Design implications:**
- Frame "noticing" in neurological terms, never mystical ones
- The Mirror's highlighting is literally externalized attentional modulation
- Pattern analytics should reveal attentional biases over time
- Use language like "your brain is filtering for this now" not "the universe is showing you signs"
---
## Pillar 5: Habit Formation
### The Science
**Wood & Neal (2007)***A New Look at Habits and the Habit-Goal Interface*
Habits form through the gradual learning of associations between responses and context features (physical settings, time of day, preceding actions). Once formed, perception of the context triggers the habitual response *without a mediating goal* — the behavior becomes automatic. Goals can direct habit formation (by motivating repetition) but once habits are established, they run on context cues, not intentions.
**Wood, Mazar & Neal (2021)***Habits and Goals in Human Behavior: Separate but Interacting Systems*
Extends the 2007 model: habits and goals are separate cognitive systems that interact. ~43% of daily behavior is habitual. Habit change requires disrupting the context-response link — either by changing contexts, or by introducing friction into the habitual response. For building new habits, the key is consistent repetition in stable contexts until the behavior becomes automatic.
### Where It Lives in Kalei
**Step 6: Repeat and Compound** — The final step in Kalei's manifestation system is explicitly about habit formation. The app helps users build daily rituals — consistent, context-anchored micro-actions that compound over time. The AI generates context-specific triggers ("Every morning after your first coffee, open Kalei and do one Turn") because Wood's research shows that context stability is the single biggest predictor of habit formation.
**Streaks & Ritual Tracking** — Kalei's streak system isn't gamification for its own sake — it's measuring the repetition that Wood et al. show is necessary for habit crystallization. The app tracks not just frequency but context consistency ("You've done your morning Turn at roughly the same time for 18 days — this is becoming automatic").
**The Mirror as Habitual Practice** — Regular Mirror sessions train the habit of self-reflection. Over time, the act of writing and examining thoughts becomes an automatic response to stress or uncertainty, rather than requiring conscious effort each time.
**Design implications:**
- Always pair actions with specific context cues (time, location, preceding action)
- Streak tracking should emphasize context consistency, not just count
- Frame habit formation as ~66 days of consistent repetition (Lally et al.'s median)
- Celebrate automaticity milestones ("This is becoming second nature")
- When habits break, help users rebuild the context-response link rather than relying on willpower
---
## Pillar 6: Placebo & Expectation Effects
### The Science
**Pardo-Cabello et al. (2022)***Placebo: A Brief Updated Review*
A comprehensive review of placebo/nocebo effects across medicine. The placebo effect has been observed across multiple medical conditions and administration routes. Key finding: expectations directly influence physiological and behavioral outcomes. The doctor-patient relationship (or in Kalei's case, the app-user relationship) is the most important factor in whether expectation effects materialize. The psycho-neurobiological mechanisms are real and measurable.
**Stetler (2014)***Adherence, Expectations, and the Placebo Response*
Investigates why adherence to even inert treatments produces health benefits. The model: initial expectations shape behavior and physiological responses, and consistent adherence reinforces those expectations in a positive feedback loop. This is not "it's all in your head" — it's "what's in your head measurably changes what happens in your body and behavior."
### Where It Lives in Kalei
**The Overall Framework** — Kalei's entire approach leverages expectation effects honestly. We don't hide the mechanism — we explain it. Telling users "structured positive expectation, when combined with action, measurably improves outcomes" is both scientifically accurate and itself a form of positive expectation setting. The transparency is the feature.
**Onboarding & Science Education** — When users first encounter Kalei, the app explains *why* it works, citing real research. This serves two purposes: (1) it builds credibility with our skeptic-friendly audience, and (2) it primes legitimate expectation effects. Understanding that these mechanisms are real makes them more effective, not less — unlike placebos in medicine, where disclosure sometimes weakens the effect, in behavioral change, understanding the mechanism often strengthens engagement.
**Belief Calibration** — Stetler's finding about adherence reinforcing expectations informs Kalei's emphasis on daily practice. The more consistently users engage, the stronger their expectation of benefit becomes, which in turn increases the actual benefit. This is not circular logic — it's a documented feedback loop.
**Design implications:**
- Always explain the science behind features — transparency strengthens engagement
- The app-user relationship quality matters (tone, personalization, responsiveness)
- Consistent engagement creates a positive expectation-behavior feedback loop
- Frame this honestly: "Expectation shapes behavior. We're using that deliberately and transparently."
- Never hide the mechanism or pretend Kalei works through unknown forces
---
## Pillar 7: Social Networks & Community
### The Science
**Granovetter (1973)***The Strength of Weak Ties*
One of the most influential papers in sociology. Granovetter demonstrates that transformative opportunities — new jobs, novel information, unexpected connections — come disproportionately from *weak ties* (acquaintances, distant contacts) rather than strong ties (close friends, family). Strong ties tend to share overlapping information; weak ties bridge different social worlds and provide access to non-redundant resources.
### Where It Lives in Kalei
**Future Community Features (The Spectrum)** — When Kalei eventually adds social features, Granovetter's weak-ties theory informs the design. Anonymous sharing of reframes, goals, and breakthroughs creates a network of weak ties — users inspiring other users they'll never meet. The value isn't in building close friendships within the app (strong ties) but in being unexpectedly inspired by someone else's Turn on a problem you share (weak ties).
**Design implications:**
- Community features should optimize for weak-tie connections (diverse, anonymous, cross-context)
- Don't try to build a social network — build a constellation of shared perspectives
- Anonymous or pseudonymous sharing preserves the weak-tie benefit (no obligation, no social pressure)
- Surface unexpected resonance: "42 other people Turned a similar thought this week"
---
## Pillar 8: Self-Regulation & Feedback Loops
### The Science
**Carver & Scheier (1998)***On the Self-Regulation of Behavior*
The foundational text on self-regulation theory. Carver & Scheier model all goal-directed behavior as a feedback loop — the "test-operate-test-exit" (TOTE) cycle. People continuously compare their current state to a reference value (their goal), and when a discrepancy is detected, they take corrective action. The cycle repeats until the discrepancy is eliminated or the person disengages.
Critically, the theory shows that **without feedback, self-regulation fails.** People cannot close the gap between where they are and where they want to be if they have no mechanism for detecting the gap. This is why goals without progress monitoring produce no better outcomes than no goals at all. The feedback loop is not optional — it is the mechanism through which goals produce behavior change.
Carver & Scheier also distinguish between two types of feedback loops: **discrepancy-reducing** (moving toward a goal) and **discrepancy-enlarging** (moving away from a threat). Both are relevant to Kalei: the Lens operates primarily through discrepancy-reducing loops (closing the gap toward a desired state), while the Mirror and Turn operate through discrepancy-enlarging loops (moving away from distorted thinking patterns).
**Locke & Latham (2006)***New Directions in Goal-Setting Theory* (extended application)
While primarily cited in Pillar 1, Locke & Latham's 2006 paper explicitly identifies **feedback** as a necessary moderator of goal effectiveness. Goals combined with feedback produce significantly better performance than goals alone. The feedback must be: (1) timely — delivered close to the relevant behavior, (2) specific — referencing concrete actions, not vague impressions, (3) self-relevant — connected to the individual's personal goals and values, and (4) actionable — pointing toward what to do differently, not just what went wrong.
This directly informs how the Guide delivers coaching: not through generic encouragement, but through timely, specific, self-relevant, and actionable observations drawn from the user's own data across all features.
### Where It Lives in Kalei
**The Guide (Active Coaching Layer)** — The Guide is the primary implementation of self-regulation theory in Kalei. Without it, the app provides tools (Mirror, Turn, Lens) but no feedback loop connecting them. The Guide implements Carver & Scheier's TOTE cycle across the entire user experience:
- **Test:** The Guide monitors the user's state across all features — Mirror session language, Turn topics, Lens goal progress, Ritual consistency, Evidence Wall accumulation
- **Operate:** When discrepancies are detected (goal progress stalling, self-efficacy dipping, patterns recurring), the Guide surfaces targeted interventions — check-ins, bridges, evidence, attention prompts
- **Test again:** The Weekly Pulse provides a structured moment for the user to self-report their state, while the AI provides its own read — closing the loop between subjective experience and objective data
- **Exit (or recalibrate):** Goals that are completed celebrate and close. Goals that need adjustment get recalibrated through check-in conversations. Goals that have been abandoned are addressed directly.
**Goal Check-Ins** — These implement Locke & Latham's feedback moderator directly. The AI reviews specific actions taken (not vague impressions), references concrete data (Evidence Wall), and proposes adjustments (revised if-then plans). The feedback is timely (scheduled at the user's chosen interval), specific ("you completed 18 of 22 sessions"), self-relevant (tied to the user's chosen goal), and actionable ("what if we add a backup plan for high-stress days?").
**Cross-Feature Bridges** — These implement a form of self-regulation that no single feature can achieve alone: connecting emotional processing (Mirror/Turn) with goal-directed behavior (Lens). When the Guide notices that Mirror sessions keep circling a theme that maps to a Lens goal, it's detecting a discrepancy between the user's emotional state and their goal state — and offering to close it.
**Weekly Pulse** — This is the explicit feedback loop closure. The user reports their subjective state; the AI reports the objective data; the gap (or alignment) between the two becomes the most coaching-rich moment in the entire app. Carver & Scheier's research shows that awareness of discrepancy is the primary driver of corrective action — the Pulse creates that awareness weekly.
**Attention Prompts** — These implement the "operate" phase of the TOTE cycle for Step 4 (Notice Differently). Rather than waiting for the user to naturally redirect attention, the Guide actively trains attention toward goal-relevant information through daily micro-exercises. This accelerates the attentional retraining that the Mirror provides passively.
**Design implications:**
- The Guide must be proactive, not reactive — it initiates contact, not just responds to it
- Feedback must reference concrete, specific, user-generated data — never generic
- The gap between self-report and AI-read (in Weekly Pulse) is a feature, not a bug — surfacing discrepancy is the mechanism of change
- The Guide must adapt its cadence to the user — too frequent feels surveillance-like, too infrequent loses the feedback loop
- Every Guide interaction should close with forward momentum — "here's what to focus on" — not just analysis
---
## The Chain: How the 8 Pillars Create the Manifestation Mechanism
The research pillars aren't independent — they form a causal chain, and the self-regulation feedback loop (Pillar 8) monitors and steers the entire process:
```
Clear Goal (Locke & Latham)
→ biases attention toward goal-relevant information (Yantis, Stevens & Bavelier)
→ mental rehearsal primes execution (Schuster, Liu, Seok)
→ capability belief sustains effort through setbacks (Bandura)
→ if-then plans automate action initiation (Gollwitzer)
→ repetition builds automatic habits (Wood & Neal)
→ consistent practice reinforces positive expectations (Stetler, Pardo-Cabello)
→ broader action increases exposure to opportunity (Granovetter)
→ probability of desired outcome increases
↑ ↓
└── FEEDBACK LOOP (Carver & Scheier) ──────────┘
The Guide monitors progress at every step,
detects when the chain stalls, and steers
the user back on track through check-ins,
bridges, evidence, and attention prompts.
```
This is what "manifestation" actually is: a chain of well-documented psychological mechanisms that compound to tilt probability in your favor — **plus a feedback loop that keeps the chain running.** Not magic. Not metaphysics. Not "the universe." Just your brain doing what brains do when properly directed, with an AI ensuring you don't get stuck.
---
## Quick Reference: Feature → Science Map
| Kalei Feature | Primary Research | Key Principle |
|---|---|---|
| **The Lens** (goal creation) | Locke & Latham 2002, 2006 | Specific, challenging goals with feedback |
| **The Turn** (reframing) | Bandura 1977; Yantis 2008 | Capability belief + attentional retraining |
| **The Mirror** (freeform notebook) | Stevens & Bavelier 2012; Koch & Tsuchiya | Externalized attentional spotlight |
| **The Rehearsal** (guided visualization) | Schuster 2011; Liu 2025; Seok 2023 | Process-oriented mental rehearsal (~10min, 3x/week) |
| **The Ritual** (daily flow) | Wood & Neal 2007; Wood et al. 2021 | Context-anchored habit formation; context stability |
| **The Evidence Wall** (mastery tracking) | Bandura 1977 | Mastery experiences (strongest efficacy source) |
| **If-Then Micro-Actions** | Gollwitzer 1999; Gollwitzer & Sheeran | Implementation intentions |
| **Coaching Styles** | Bandura 1977 | Personalized verbal persuasion |
| **Pattern Analytics** | Yantis 2008; Stevens & Bavelier 2012 | Revealing attentional biases |
| **Science Explanations** | Pardo-Cabello 2022; Stetler 2014 | Transparent expectation effects |
| **Community (future)** | Granovetter 1973 | Weak-tie opportunity exposure |
| **The Guide** (active coaching) | Carver & Scheier 1998; Locke & Latham 2006 | Feedback loops + self-regulation TOTE cycle |
| **Goal Check-Ins** | Locke & Latham 2006 | Timely, specific, actionable feedback |
| **Cross-Feature Bridges** | Carver & Scheier 1998 | Discrepancy detection across systems |
| **Weekly Pulse** | Carver & Scheier 1998 | Subjective-objective gap awareness |
| **Attention Prompts** | Stevens & Bavelier 2012; Carver & Scheier 1998 | Active attentional retraining via feedback |
---
## Guardrails: What the Science Does NOT Support
Just as important as what we cite is what we explicitly reject:
1. **"The universe responds to your thoughts"** — No research supports metaphysical causation. We never imply it.
2. **"Visualize and it will happen"** — Outcome-only visualization without action can actually *decrease* performance (by providing premature satisfaction). We always pair visualization with process focus and action steps.
3. **"Believe hard enough and you'll succeed"** — Bandura's self-efficacy is about capability belief, not outcome certainty. We always say "possible, not guaranteed."
4. **"Positive thinking cures everything"** — Toxic positivity. Kalei acknowledges real constraints, structural barriers, and randomness. The science improves odds — it doesn't eliminate uncertainty.
5. **"You attracted your problems"** — Victim-blaming disguised as empowerment. Never. The attentional science explains perception, not causation.
---
## How to Use This Document
**For developers:** When building a feature, check this doc to understand the scientific principle behind it. The AI prompts, UX copy, and interaction patterns should all reflect the cited research.
**For AI prompt engineering:** Every reframe, visualization script, and goal scaffold should be traceable to a specific pillar. If a prompt produces output that contradicts the research (e.g., promising guaranteed outcomes), it needs revision.
**For content and copy:** When writing user-facing text — onboarding, tooltips, push notifications, feature descriptions — ground it in the relevant pillar. Users should feel the science without needing to read papers.
**For marketing:** "Science-backed" is not a buzzword for Kalei. It's a specific claim backed by 18 peer-reviewed papers across 8 research domains. This document is the receipt.
---
*Same pieces. New angle. Real science.*

View File

@@ -0,0 +1,112 @@
Final Conclusion: What “Manifesting” Really Is (Scientifically)
When stripped of spiritual language, “manifesting” is a structured psychological process that increases the probability of desired outcomes through well-documented cognitive and behavioral mechanisms.
It does not mean:
Reality bends to thought.
The universe delivers outcomes on demand.
Belief overrides randomness.
It does mean:
1⃣ Clear Goals Change Performance
Research on goal-setting shows that specific, challenging goals direct attention, increase effort, and improve persistence.
Clarity is not motivational fluff — it changes how the brain allocates cognitive resources.
2⃣ Mental Rehearsal Primes Execution
Visualization activates overlapping neural circuits used in real performance.
When used as behavioral rehearsal (not fantasy), it improves readiness, confidence, and execution quality.
3⃣ Self-Efficacy Changes Behavior
Belief in capability (self-efficacy) predicts:
Greater effort
Longer persistence
Better stress tolerance
Faster recovery from setbacks
Belief influences behavior — behavior influences outcomes.
4⃣ Attention Is Biased by Goals
The brain filters information according to relevance.
When you define a goal, you increase the likelihood of noticing:
Opportunities
Relevant information
Signals
Threats
You dont create opportunities out of nothing — you detect them more effectively.
5⃣ Planning Automates Action
Implementation intentions (“If X happens, I do Y”) dramatically increase follow-through.
This reduces reliance on willpower and increases consistency.
6⃣ Repetition Builds Habits
Repeated, goal-aligned behaviors become automatic over time.
Once habits form, less conscious effort is required — increasing long-term probability of success.
7⃣ Exposure Increases Opportunity
Network research shows that broader exposure (especially weak ties) increases access to opportunities.
Repeated aligned action increases surface area for luck.
The Core Mechanism
Clear intention
→ biases attention
→ increases belief-driven persistence
→ improves quality and frequency of action
→ expands exposure to opportunity
→ increases probability of desired outcomes
Thats it.
The Real Conclusion
“Manifesting” is not mystical causation.
It is:
Goal-directed cognition
Expectancy effects
Behavioral alignment
Habit formation
Increased opportunity exposure
Compounded probability
It works because it operates through known psychological and social mechanisms, not because it overrides reality.
The Honest Boundary
This process:
Improves odds
Does not guarantee outcomes
Cannot eliminate randomness
Cannot override structural constraints
But over time, it systematically tilts probability in your favor.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,792 @@
# Kalei — Complete User Journey Map
> Version 2.0 — February 2026
> Updated to include Ritual, Rehearsal, Evidence Wall, and full cross-feature integration
---
## Overview
This document maps every user-facing flow in Kalei from first launch to long-term mastery. It serves as the single source of truth for what the user experiences, when, and why — covering all 7 features across the 5-tab architecture.
**Navigation Architecture:** Turn ◇ | Mirror ✦ | Lens ◎ | Gallery ▦ | You ●
**Core Features (4 Pillars):**
- The Turn (Kaleidoscope) — Perspective shifting via cognitive reframing
- The Mirror — Awareness through freeform journaling with AI fragment detection
- The Lens — Direction through goal setting, visualization, and action planning
- Gallery — Pattern collection and history
**Connector Features (4 Bridges):**
- The Ritual — Context-anchored daily habit sequences chaining Mirror → Turn → Lens
- The Rehearsal — Guided multi-sensory visualization (Lens sub-feature)
- The Evidence Wall — Mastery tracking mosaic (You tab sub-feature)
- The Guide — Active coaching layer connecting all features with proactive check-ins, cross-feature bridges, attention prompts, evidence interventions, and weekly pulse
**Intelligence Layer:**
- The Spectrum — AI-powered self-knowledge dashboard
---
## Journey Stage 1: First Launch & Onboarding
### Screen 1: Splash
- Breathing logo animation (soft-elegance iris, slow rotation, core pulse)
- Background: Void (#050508) with subtle breathing aura
- Duration: 2 seconds, auto-advance
### Screen 2: Welcome — "Same pieces. New angle."
- Hero kaleidoscope pattern (6-blade prismatic, screen blend mode)
- Tagline in Space Grotesk display font
- Single CTA: "See how it works" (Amethyst shimmer button)
### Screen 3: The Metaphor — Fragment Introduction
- Visual: A single thought fragment (◇) appears, glowing Amber
- Copy: "Your thoughts are like pieces of glass in a kaleidoscope."
- Interaction: User taps the fragment → it pulses with detected-state animation
- Copy continues: "Sometimes you see sharp edges. Sometimes beautiful patterns."
### Screen 4: The Turn Demo — Live Reframe
- Pre-populated negative thought: "I always mess everything up"
- Auto-animated Turn sequence (1.5s): collapse → multiply → crystallize → settle
- Three reframed perspectives appear as jewel-toned cards (Amethyst, Sapphire, Emerald)
- Copy: "Same pieces. New angle. That's a Turn."
### Screen 5: Choose Your Style
- 4 coaching style cards with fragment icons:
- Stoic Sage (Sapphire ◇) — "Clear-eyed perspective"
- Compassionate Friend (Rose ◇) — "Gentle understanding"
- Pragmatic Coach (Emerald ◇) — "Practical next steps"
- Growth Catalyst (Amber ◇) — "Opportunity in everything"
- User selects default (can change later)
### Screen 6: Notification Permission
- Copy: "When would you like a gentle nudge to check in?"
- Time picker with morning/evening presets
- Skip option available
### Screen 7: Account Creation
- Email + password OR Apple/Google SSO
- Minimal fields — name optional at this stage
- Privacy assurance: "Your thoughts stay yours. Always encrypted."
### Screen 8: First Real Turn
- Empty Turn input with prompt: "What's weighing on you right now?"
- User types their first real negative thought
- Full Turn animation plays
- 3 reframed perspectives appear
- User can save favorites (→ Gallery) or dismiss
- Success burst animation on save
### Screen 9: Welcome Complete
- Copy: "Welcome to Kalei. Your kaleidoscope is ready."
- Mini kaleidoscope pattern generated from their first Turn (deterministic, seeded from input)
- CTA: "Start exploring" → Tab bar appears, Turn tab active
---
## Journey Stage 2: Daily Core Loop
### 2A: The Turn (Tab 1 — Amethyst ◇)
**Entry:** User opens app → Turn tab is default home
**Empty State:**
- Breathing logo at center, subtle floating shards in background
- Rotating prompts: "What thought keeps circling?", "What would you like to see differently?", "What feels heavy right now?"
- Single large text input area
**Active Flow:**
1. User types or speaks a negative thought
2. Tap "Turn it" button (Amethyst shimmer)
3. Turn animation plays (1.5s kaleidoscope rotation)
4. 3 reframed perspectives appear as cards:
- Each card has a coaching style label, the reframe text, and a fragment icon
- Cards use jewel tone gradients matching their style
5. Below perspectives: "If-Then Micro-Action" card
- Format: "If [situation], then I will [specific action]"
- Emerald accent, action-oriented
6. User actions:
- Save any perspective → goes to Gallery as a Keepsake
- Save micro-action → goes to Lens as a suggested action
- Share → generates Pattern Card (kaleidoscope pattern + reframe text)
- Dismiss → confirmation, thought is discarded
**Turn History:**
- Scrollable list below input showing today's Turns
- Each entry: timestamp, first line of thought, fragment count badge, pattern thumbnail
**Rate Limiting (Free tier):**
- 3 Turns per day
- After limit: "You've used your 3 Turns today. Upgrade to Kalei Prism for unlimited Turns."
- Gentle, never punishing
### 2B: The Mirror (Tab 2 — Amber ✦)
**Entry:** Mirror tab → New session or continue existing
**Empty State:**
- Soft amber glow background
- Copy: "What's on your mind? Write freely — no judgment, no rules."
- Suggested prompts rotate: "How are you feeling right now?", "What happened today?", "What's been on repeat in your head?"
**Active Session Flow:**
1. User writes freely in chat-like interface (user messages appear as dark bubbles)
2. After each message, AI processes silently (AI thinking animation: 3 oscillating fragments)
3. AI responds with warm, reflective prompts (light bubbles with subtle amber border)
4. Simultaneously, AI detects cognitive distortions in user's text
5. Detected fragments appear as inline highlights:
- Amber glow underline beneath the distorted phrase
- Small ◇ icon at start of highlight
- Highlight appears post-message (never while typing)
6. User taps a highlight → Half-sheet modal:
- Distortion type name + icon (from icons-distortions.svg)
- Brief explanation: "Catastrophizing: Assuming the worst possible outcome"
- 1-2 quick reframes
- "Take to Turn" button → opens Turn with this thought pre-filled
- Dismiss to continue writing
**Session Close:**
- User taps "End Session" or navigates away
- AI generates Reflection:
- Themes detected
- Fragment count and types
- One-line insight
- Unique kaleidoscope pattern (seeded from session content)
- Reflection saved to Gallery
**Nudge System:**
- If user ignores 5+ highlighted fragments, ONE gentle offer:
- "I noticed a few patterns in what you wrote. Want to look at them together?"
- Max once per session
- Dismissible
**Rate Limiting (Free):** 2 sessions per week, 3 distortion types detected (Catastrophizing, Black-and-White, Should Statements). Prism: unlimited sessions, all 10 types.
### 2C: The Lens (Tab 3 — Emerald ◎)
**Entry:** Lens tab → Goal dashboard
**Dashboard View:**
- Active goals displayed as cards with progress rings
- Each goal card shows: title, progress percentage, next action, streak indicator
- "Add Goal" floating action button (Emerald gradient)
**Goal Creation Flow (6 Steps):**
**Step 1: Decide — Set a SMART Goal**
- AI-guided conversation to refine a vague desire into a specific goal
- Template: "I want to [specific outcome] by [date] measured by [metric]"
- AI suggests refinements if goal is vague
**Step 2: See It — The View (Vision Board)**
- AI generates a visualization description based on goal
- User can add personal images or AI-generated imagery
- "The View" appears as a full-screen card they can revisit
**Step 3: Believe — Capability Building**
- Evidence Wall integration: surfaces past achievements relevant to this goal
- AI generates affirmations based on goal + user's history
- Daily affirmation card appears at Lens tab top
**Step 4: Notice — Attention Training**
- AI prompts awareness exercises: "Today, notice one moment where you [goal-related behavior]"
- Prompts delivered via notification at user-chosen time
- User logs noticed moments → feeds Evidence Wall
**Step 5: Act — If-Then Micro-Actions**
- AI generates situation-specific implementation intentions
- Format: "If [context], then [specific action]"
- User can mark actions complete → feeds Evidence Wall
- Action completion streak tracking
**Step 6: Compound — Habit Tracking**
- Visual habit tracker (fragment-shaped step indicators)
- Streak counter with flame icon
- Weekly review of consistency
**The Rehearsal (Lens Sub-Feature):**
- Accessed from goal detail screen → "Rehearse" button
- Timer ring appears (default: 10 minutes)
- AI generates personalized visualization script:
- First-person perspective
- Multi-sensory (see, hear, feel, smell)
- Process-oriented (not just outcome)
- Includes obstacle rehearsal ("When X happens, I will Y")
- Script plays as text cards with breathing animation pacing
- Progress ring counts down
- Completion → Success burst → logged to Evidence Wall
- Free: 1 Rehearsal per week. Prism: unlimited.
### 2D: Gallery (Tab 4 — Sapphire ▦)
**Entry:** Gallery tab → Collection view
**Views:**
- **All Patterns** (default): Reverse-chronological grid of kaleidoscope pattern thumbnails
- **Keepsakes**: Saved reframes, reflections, and insights
- **By Feature**: Filter by Turn / Mirror / Lens source
- **By Distortion**: Filter by cognitive distortion type
**Pattern Card Detail:**
- Full kaleidoscope pattern (hero variant, animated)
- Source content (the reframe or reflection that generated it)
- Date, feature source, distortion types tagged
- Share button → exports as Pattern Card image
- Delete with confirmation
**Search:**
- Text search across all saved content
- Filter chips: date range, feature, distortion type, favorites
### 2E: You (Tab 5 — Soft Light ●)
**Entry:** You tab → Profile dashboard
**Sections:**
- **Profile**: Name, avatar, member since
- **Stats Overview**: Total Turns, Mirror sessions, Goals active, Streak count
- **Evidence Wall** (prominent card → opens full view)
- **Settings**: Coaching style, notification times, theme (dark only for now), data export
- **Subscription**: Current plan, upgrade CTA (if free)
- **Support**: FAQ, contact, crisis resources
**The Evidence Wall (You Sub-Feature):**
- Accessed from You tab → "Your Evidence Wall" card
- Opens full-screen mosaic view
**Evidence Wall States:**
*Empty State (0-2 items):*
- Ghost tile outlines (dashed borders) showing where tiles will appear
- Central fragment icon with breathing animation
- Copy: "Start collecting evidence. Each Turn adds a tile to your wall."
*Early State (3-7 items):*
- Small cluster of tiles, connections forming
- Tiles are mixed shapes (diamond, hex, rectangle, pentagon, triangle)
- Each tile represents one proof point:
- Completed action (Emerald border)
- Saved keepsake (Sapphire border)
- Self-correction in Mirror (Amber border)
- Streak milestone (Amethyst border)
- Goal completion (Emerald border, larger tile)
- Reframe echo (Indigo border) — when user's later writing echoes a saved reframe
*Mid State (8-20 items):*
- Mosaic takes shape, dashed connection lines between related tiles
- Tiles glow softly when tapped → detail half-sheet
*Full State (20+ items):*
- Dense mosaic with visible connection web
- Zoom/pan enabled
- Most impactful tiles glow brighter
**Contextual Surfacing:**
- During low self-efficacy moments (detected in Mirror or Turn), the Evidence Wall surfaces 1-2 relevant tiles
- Example: User writes "I can never stick to anything" → Evidence Wall suggests: "You completed 12 actions in the last month and maintained a 7-day streak"
- Presented as a gentle card, not a correction
---
## Journey Stage 3: The Ritual (Connector Feature)
The Ritual chains Mirror → Turn → Lens into a single context-anchored daily flow.
**Access:** Dedicated "Start Ritual" button at top of Turn tab, or via notification
**Template Selection:**
*Morning Ritual (15-20 min):*
1. Mirror check-in: "How are you waking up today?" (3 min writing)
2. Turn: AI identifies strongest fragment from Mirror → offers reframe (2 min)
3. Lens: Today's priority action from active goal (1 min review)
4. Affirmation: Daily affirmation card
5. Set intention: One sentence for the day
*Evening Ritual (10-15 min):*
1. Mirror reflection: "What stood out about today?" (3 min writing)
2. Turn: Process any unresolved thought from the day (2 min)
3. Lens review: Mark completed actions, log noticed moments
4. Gratitude: One thing from today (saved to Gallery)
*Quick Ritual (5 min):*
1. One-line check-in
2. Fastest Turn (single perspective)
3. One action reminder
**Ritual Flow UI:**
- Step indicators using fragment-shaped progress bar (from progress-indicators.svg)
- Each step has a timer (visible but not pressuring)
- Smooth transitions between steps (fragment scatter/converge animation)
- Completion → Success burst → streak updated
**Ritual Tracking:**
- Streak calendar (7-day week view, Amber jewel tone)
- Context consistency tracking (Wood et al.): same time, same place → stronger habit
- Ritual completion logged to Evidence Wall
**Rate Limiting (Free):** Quick Ritual only. Prism: all 3 templates.
---
## Journey Stage 3B: The Guide (Active Coaching Layer)
The Guide is not a tab or a destination — it's an intelligence layer that surfaces across all features through five interaction patterns. These screens show how each pattern manifests in the UI.
### Guide Pattern 1: Goal Check-In (Lens)
**Access:** "Check in" button on goal detail screen, or via notification at user's chosen check-in time
**Screen 65: Goal Check-In Conversation**
A chat-like interface within the goal detail screen. The Guide has full context from the user's Lens activity, Mirror sessions, and Turn history.
**Flow:**
1. Guide opens with a recognition of recent progress (evidence-first)
2. Guide asks about specific milestones or actions since last check-in
3. User responds conversationally
4. Guide reviews relevant if-then plans — did the situations arise? Did the plans work?
5. If plans need adjustment, Guide proposes modifications collaboratively
6. Guide closes with a concrete Evidence Wall proof point
**UI Elements:**
- Chat interface within goal detail (not a separate screen — slides up from goal card)
- Guide messages use prismatic gradient border (distinguishing from Mirror's amber)
- User messages in dark bubbles (consistent with Mirror style)
- At bottom: typing area with send button
- Check-in history accessible via "Past check-ins" link
**Screen 66: Check-In Summary**
After the conversation ends, a summary card appears:
- What was reviewed
- Plan adjustments made (if any)
- Evidence highlighted
- Next check-in date
- "Added to your coaching history" confirmation
**Rate Limiting (Free):** 1 check-in per month per goal. Prism: weekly per goal + on-demand.
---
### Guide Pattern 2: Cross-Feature Bridge Cards
**Access:** Appear automatically at the top of Turn, Mirror, or Lens tabs when the Guide detects a cross-feature pattern
**Screen 67: Discovery Bridge**
Appears when 3+ Mirror sessions or Turns share a theme that doesn't map to any existing Lens goal.
**Layout:**
- Half-height card at top of screen (below nav header, above feature content)
- Prismatic gradient border (thin, cycling amethyst → sapphire → emerald → amber)
- Header: "◇ Something keeps coming up" (or "A pattern is forming")
- Body: 1-2 sentences referencing the theme, with quoted user text in italics
- CTAs: Primary action (e.g., "Open Lens" / "Start a goal") + Dismiss ("Just noticing")
- Dismissible with swipe or tap
**Screen 68: Reinforcement Bridge**
Appears when Mirror/Turn content directly relates to an existing Lens goal.
**Layout:** Same card format as discovery bridge.
- Header: "◇ This connects to something you're building"
- Body: References the specific goal and how the current processing connects to it
- CTAs: "Start Rehearsal" / "Check in on goal" + Dismiss
**Screen 69: Integration Bridge**
Appears when current Mirror/Turn writing contradicts a previously saved keepsake.
**Layout:** Same card format, but includes a quoted keepsake.
- Header: "◇ You've seen this differently before"
- Body: Shows the saved keepsake text, then the current contradicting sentiment
- CTAs: "See your Evidence Wall" / "Full Turn" + "Continue writing"
**Rules:** Maximum one bridge per day. Never appears mid-Mirror session. Always dismissible.
---
### Guide Pattern 3: Attention Prompts (Lens)
**Access:** Daily notification → opens in Lens tab. Also accessible from Lens dashboard as a card.
**Screen 70: Daily Attention Prompt**
**Layout:**
- Card in Lens tab (below goals, above rehearsals)
- Emerald accent border (Lens color family)
- Header: "Today's Focus: [Prompt Type]" (Notice / Reflect / Act / Envision)
- Body: The specific prompt, 1-2 sentences, tied to the active goal
- Goal reference: "For your goal: [goal title]"
- CTA: "Got it" (acknowledges) + "Log a moment" (appears later in the day)
- Prompt type rotates based on which step of the manifestation chain the user is in
**Screen 71: Moment Log**
When user taps "Log a moment" (later in the day or from notification):
**Layout:**
- Simple text input: "What did you notice?"
- Below: context reminder of today's prompt
- Submit → confirmation: "That's evidence. Added to your Evidence Wall."
- The logged moment appears as a new Evidence Wall tile
**Rate Limiting (Free):** 3 attention prompts per week. Prism: daily.
---
### Guide Pattern 4: Evidence Intervention
**Access:** Surfaces automatically during Mirror sessions or after Turns when low self-efficacy is detected
**Screen 72: Evidence Intervention Card (Mirror)**
Appears after a Mirror session ends (never mid-session) when the session contained significant self-efficacy dip language.
**Layout:**
- Card at bottom of Mirror reflection screen
- Prismatic border
- Header: "◇ Here's what I've seen"
- Body: 2-3 specific, numbered proof points from Evidence Wall that directly counter the expressed doubt
- Each proof point includes a specific number, date, or action
- CTA: "See your full Evidence Wall" + Dismiss
- Tone: Presenting evidence, not cheerleading. "You said X. Your data shows Y."
**Screen 73: Evidence Intervention Card (Turn)**
Appears below Turn results when the original thought contained capability doubt on a topic where the user has evidence.
**Layout:**
- Same card format as Mirror intervention
- Positioned below the 3 reframe cards, above the action buttons
- Contextually references the Turn's topic
**Rules:** Maximum one intervention per session. Only surfaces when meaningful evidence exists. Never fabricates or exaggerates.
**Rate Limiting (Free):** Not available. Prism: full evidence interventions.
---
### Guide Pattern 5: Weekly Pulse
**Access:** Weekly notification on user's chosen day (default: Sunday evening) → opens dedicated Pulse flow
**Screen 74: Pulse — Self-Report**
Step 1 of 3 in the Weekly Pulse flow.
**Layout:**
- Full-screen flow (no tab bar — immersive like Ritual)
- Header: "Your Weekly Pulse"
- Subheader: "How did this week feel?"
- 5-point fragment scale (SVG diamonds at increasing glow/facet levels):
- ◇ dim, cracked — "Rough"
- ◇ muted — "Harder than usual"
- ◇ neutral — "Steady"
- ◇ glowing — "Good momentum"
- ◇ brilliant, faceted — "Breakthrough week"
- Below scale: optional one-sentence write-in
- Progress indicator: Step 1 of 3
**Screen 75: Pulse — AI Read**
Step 2 of 3.
**Layout:**
- Header: "Here's what I noticed this week"
- 3-5 bullet observations from the AI, each with a jewel-tone accent dot:
- Turn count and theme
- Mirror session emotional trajectory
- Lens goal progress
- Distortion pattern changes
- Streak/consistency data
- If self-report diverges from data: a highlighted callout — "You said this was a rough week, but your data shows progress on two fronts. Sometimes the feeling lags behind the evidence."
- Progress indicator: Step 2 of 3
**Screen 76: Pulse — Next Week Focus**
Step 3 of 3.
**Layout:**
- Header: "For next week"
- 2-3 suggested focus areas as cards:
- Each card: one-sentence suggestion + the feature it relates to (Lens, Mirror, Rehearsal, etc.)
- Examples: "Do a Rehearsal for your 5K — you haven't done one in 10 days" / "Your Mirror streak is at 14 days — keep it going"
- CTAs: "Sounds good" (accepts) / "Adjust" (opens edit)
- Completion: "Pulse complete. See you next week."
- Pulse data saved → feeds Spectrum
**Rate Limiting (Free):** Self-report step only (no AI read, no next-week focus). Prism: full 3-step Pulse.
---
### Guide — Enhanced Turn Results (Updated Screen 13)
The existing Turn Results screen (13) is enhanced with two new elements:
**Addition 1: If-Then Micro-Action Card**
Positioned between the reframe cards and the action buttons:
- Emerald accent border
- Format: "If [situation from the thought], then I will [specific action]"
- CTA: "Save to Lens" → creates an action item on the most relevant active goal
- If no active goal exists: "Start a Lens goal around this"
**Addition 2: Goal Connection (when relevant)**
If the Turn's topic maps to an active Lens goal:
- Small card below the micro-action: "This connects to your goal: [goal title]"
- CTA: "Check in on this goal" / Dismiss
---
### Guide — Enhanced Mirror Reflection (Updated Screen 19)
The existing Mirror Session Reflection screen (19) is enhanced:
**Addition: "The Guide noticed..." section**
Below the existing reflection content (themes, fragment count, patterns, insight):
- Prismatic-bordered card
- Header: "The Guide noticed..."
- 1-2 cross-feature observations:
- Theme connections to Lens goals
- Pattern changes compared to recent sessions
- Integration bridge opportunities (if a saved keepsake was contradicted)
- CTAs appropriate to the observation (e.g., "Open Lens" / "See your Evidence Wall" / Dismiss)
---
## Journey Stage 4: Spectrum (Intelligence Layer)
**Unlock:** After 2 weeks of active use (minimum 5 Turns, 2 Mirror sessions)
**Teaser Period:**
- Notification: "Something is forming... Your Spectrum is almost ready."
- Small locked card on You tab with shimmer animation
**Launch Reveal:**
- Full-screen animation: fragments converge into prismatic kaleidoscope
- User's first Spectrum dashboard appears
**Dashboard Components:**
**The River (Emotional Flow):**
- Flowing prismatic gradient band showing emotional valence over time
- Data points as fragment icons at key moments
- X-axis: days/weeks, Y-axis: emotional valence
- Hover/tap any point → detail card with source Turn/Mirror session
**Your Glass (Distortion Distribution):**
- Radar/spider chart showing which of the 10 distortion types appear most
- Amber jewel tone data shape on hex grid
- Vertices as fragment icons
- Evolves weekly as patterns shift
**Turn Impact (Before/After):**
- Bar chart pairs showing emotional metrics before and after Turns
- Metrics: Distress level, Clarity, Hope
- Ruby bars (before) vs Emerald bars (after)
- Rolling 30-day average
**Rhythm Detection (Your Cycles):**
- Time-of-day engagement pattern
- Bubble sizes represent intensity
- Peak labels with fragment accents
- Helps user identify best times for practice
**Growth Trajectory (The Long View):**
- Line chart with fragment data points
- Y-axis: Resilience Score (composite of fragment density, self-correction rate, reframe adoption, distortion diversity, Turn-to-insight ratio)
- Milestone markers (10th Turn, 30-day streak, etc.)
- Monthly trend with prismatic gradient fill under curve
**Cadence:**
- Weekly summary: Sunday evening notification with 1 key insight
- Monthly deep dive: First of month with month-over-month comparison
- In-context nudges: Insights surface within Mirror/Turn/Lens at natural moments
**Rate Limiting (Free):** Simplified weekly summary (1 insight, no visuals, basic fragment counts). Prism: full dashboard, all 5 components, weekly/monthly deep dives, growth trajectory, export.
---
## Journey Stage 5: Engagement Deepening & Retention
### Streak System
- Daily streak counter (consecutive days with at least 1 Turn or Ritual)
- Visual: flame icon with Amber gradient, pulse animation
- Milestones: 3, 7, 14, 30, 60, 90, 180, 365 days
- Each milestone → special pattern generated, saved to Gallery
- Streak freeze: 1 free per week (Prism: 3 per week)
### Push Notifications
- Daily check-in at user's chosen time
- Streak maintenance reminders (if about to break)
- Milestone celebrations
- Weekly Spectrum insights (Prism)
- Ritual reminders at consistent time/place
- Never more than 2 per day
### Empty States
- Every screen has a warm, encouraging empty state
- Uses breathing logo animation or floating shard clusters
- Copy examples:
- Turn: "What would you like to see differently today?"
- Mirror: "Ready to write? There's no wrong way to start."
- Lens: "What are you working toward? Let's build a path."
- Gallery: "Your first pattern is waiting to be created."
- Evidence Wall: "Every small step is evidence. Start collecting."
### Upgrade Moments (Free → Prism)
- After hitting 3 Turn limit: "You're on a roll. Unlock unlimited Turns."
- After 2nd Mirror session: "Want to explore all 10 distortion types?"
- After first Rehearsal: "That felt good, right? Get unlimited Rehearsals."
- After Evidence Wall shows 10+ tiles: "Your evidence is growing. See the full picture with Spectrum."
- Never blocks current action — always shows after completion
---
## Journey Stage 6: System States
### Loading States
- Initial load: Breathing logo animation
- Feature transitions: Fragment scatter/converge
- AI processing: 3-fragment oscillation (AI thinking bubble)
- Data loading: Skeleton shimmer (text lines + card shapes)
- Long operations: Iris spinner with progress text
### Error States
- Network error: "Lost connection. Your data is safe — we'll sync when you're back."
- AI error: "Our thinking engine needs a moment. Try again in a few seconds."
- Rate limit: Feature-specific messaging (see each feature above)
- Generic: Ruby-accent toast with retry option
### Success States
- Turn saved: Emerald toast "Turn saved" with fragment icon
- Goal completed: Success burst animation (expanding rings + particle fragments)
- Streak milestone: Special celebration with pattern generation
- Ritual complete: Prismatic ring completion animation
### Offline Mode
- Turn input cached locally, syncs when online
- Mirror sessions continue with local fragment detection (basic)
- Gallery browsable offline
- Clear indicator: "Offline — your work will sync automatically"
---
## Monetization Tiers
### Kalei (Free)
| Feature | Limit |
|---------|-------|
| Turn | 3 per day |
| Mirror | 2 sessions per week, 3 distortion types |
| Lens | 1 active goal, basic actions |
| Rehearsal | 1 per week |
| Ritual | Quick template only |
| Evidence Wall | 30-day window |
| Guide | Discovery bridges only, 1 check-in/month/goal, 3 attention prompts/week, self-report Pulse only |
| Gallery | Full access |
| Spectrum | Simplified weekly summary (text only) |
### Kalei Prism ($7.99/month)
| Feature | Access |
|---------|--------|
| Turn | Unlimited + if-then micro-action cards |
| Mirror | Unlimited sessions, all 10 distortion types, unlimited inline reframes + evidence interventions |
| Lens | Unlimited goals, AI-refined actions + weekly check-ins + daily attention prompts |
| Rehearsal | Unlimited |
| Ritual | All 3 templates |
| Evidence Wall | Full history, no time window + contextual AI surfacing |
| Guide | All 5 patterns: full check-ins, all bridge types, daily prompts, evidence interventions, full Pulse |
| Gallery | Full access + export |
| Spectrum | Full dashboard, all 5 components, weekly/monthly insights, growth trajectory |
---
## Appendix: Screen Inventory
| # | Screen | Tab | Feature |
|---|--------|-----|---------|
| 1 | Splash | — | System |
| 2 | Welcome | — | Onboarding |
| 3 | Fragment Intro | — | Onboarding |
| 4 | Turn Demo | — | Onboarding |
| 5 | Style Selection | — | Onboarding |
| 6 | Notification Permission | — | Onboarding |
| 7 | Account Creation | — | Onboarding |
| 8 | First Turn | — | Onboarding |
| 9 | Welcome Complete | — | Onboarding |
| 10 | Turn Home (empty) | Turn | Turn |
| 11 | Turn Input Active | Turn | Turn |
| 12 | Turn Animation | Turn | Turn |
| 13 | Turn Results | Turn | Turn |
| 14 | Turn History | Turn | Turn |
| 15 | Mirror Home (empty) | Mirror | Mirror |
| 16 | Mirror Session Active | Mirror | Mirror |
| 17 | Mirror Fragment Highlight | Mirror | Mirror |
| 18 | Mirror Fragment Detail (half-sheet) | Mirror | Mirror |
| 19 | Mirror Session Reflection | Mirror | Mirror |
| 20 | Lens Dashboard | Lens | Lens |
| 21 | Lens Goal Creation Step 1 | Lens | Lens |
| 22 | Lens Goal Creation Step 2 | Lens | Lens |
| 23 | Lens Goal Creation Step 3 | Lens | Lens |
| 24 | Lens Goal Creation Step 4 | Lens | Lens |
| 25 | Lens Goal Creation Step 5 | Lens | Lens |
| 26 | Lens Goal Creation Step 6 | Lens | Lens |
| 27 | Lens Goal Detail | Lens | Lens |
| 28 | Lens Daily Affirmation | Lens | Lens |
| 29 | Rehearsal Session | Lens | Rehearsal |
| 30 | Rehearsal Complete | Lens | Rehearsal |
| 31 | Gallery All Patterns | Gallery | Gallery |
| 32 | Gallery Keepsakes | Gallery | Gallery |
| 33 | Gallery Pattern Detail | Gallery | Gallery |
| 34 | Gallery Search/Filter | Gallery | Gallery |
| 35 | You Profile | You | You |
| 36 | You Stats | You | You |
| 37 | You Settings | You | Settings |
| 38 | You Subscription | You | Billing |
| 39 | Evidence Wall (empty) | You | Evidence Wall |
| 40 | Evidence Wall (early) | You | Evidence Wall |
| 41 | Evidence Wall (mid) | You | Evidence Wall |
| 42 | Evidence Wall (full) | You | Evidence Wall |
| 43 | Evidence Wall Tile Detail | You | Evidence Wall |
| 44 | Ritual Template Selection | Turn | Ritual |
| 45 | Ritual Morning Flow | Turn | Ritual |
| 46 | Ritual Evening Flow | Turn | Ritual |
| 47 | Ritual Quick Flow | Turn | Ritual |
| 48 | Ritual Complete | Turn | Ritual |
| 49 | Ritual Streak View | Turn | Ritual |
| 50 | Spectrum Dashboard | You | Spectrum |
| 51 | Spectrum The River | You | Spectrum |
| 52 | Spectrum Your Glass | You | Spectrum |
| 53 | Spectrum Turn Impact | You | Spectrum |
| 54 | Spectrum Rhythm | You | Spectrum |
| 55 | Spectrum Growth | You | Spectrum |
| 56 | Spectrum Weekly Summary | You | Spectrum |
| 57 | Spectrum Monthly Deep Dive | You | Spectrum |
| 58 | Upgrade Modal | — | Billing |
| 59 | Rate Limit Notice | — | System |
| 60 | Crisis Response | — | Safety |
| 61 | Pattern Card Share | — | Social |
| 62 | Notification Settings | You | Settings |
| 63 | Data Export | You | Settings |
| 64 | Account Deletion Confirm | You | Settings |
| 65 | Goal Check-In Conversation | Lens | Guide |
| 66 | Check-In Summary | Lens | Guide |
| 67 | Discovery Bridge Card | Turn/Mirror/Lens | Guide |
| 68 | Reinforcement Bridge Card | Turn/Mirror/Lens | Guide |
| 69 | Integration Bridge Card | Mirror | Guide |
| 70 | Daily Attention Prompt | Lens | Guide |
| 71 | Moment Log | Lens | Guide |
| 72 | Evidence Intervention (Mirror) | Mirror | Guide |
| 73 | Evidence Intervention (Turn) | Turn | Guide |
| 74 | Pulse — Self-Report | — | Guide |
| 75 | Pulse — AI Read | — | Guide |
| 76 | Pulse — Next Week Focus | — | Guide |
| 13* | Turn Results (Enhanced) | Turn | Guide + Turn |
| 19* | Mirror Reflection (Enhanced) | Mirror | Guide + Mirror |

View File

@@ -0,0 +1,374 @@
# The Mirror — Kalei's Notebook Feature
## Scientific Foundation
The Mirror is Kalei's most direct application of attention and neuroscience research.
**Selective Attention as the Core Mechanism:** Yantis (2008) showed that selective attention operates through modulatory signals that amplify relevant information and suppress irrelevant inputs. The Mirror externalizes this process — Kalei's AI acts as an attentional amplifier, highlighting cognitive patterns the user's own system has habituated to and stopped noticing. The highlighted fragments aren't new information; they're existing patterns made visible through redirected attention.
**Attention Is Trainable:** Stevens & Bavelier (2012) demonstrated that attentional control improves with practice and transfers across domains. Regular Mirror use trains the user to notice their own cognitive distortions — first with AI assistance, eventually independently. The Spectrum's "self-correction rate" metric (Phase 2) directly measures this training effect.
**Attention vs. Consciousness:** Koch & Tsuchiya's distinction between attention and consciousness is operationally important. The Mirror works at the attention level: it doesn't require the user to be consciously aware of their patterns (consciousness) — it simply redirects attention toward them. The conscious recognition follows naturally.
**Habit Formation Through Consistent Practice:** Wood & Neal (2007) showed that habits form through context-response associations. Regular Mirror sessions — anchored to consistent contexts (time of day, emotional state) — train the habit of reflective self-examination until it becomes automatic.
---
---
## The Concept
The Kaleidoscope (Turn) is structured: one fragment in, patterns out. It works when you **know** what's bothering you.
But most of the time, people don't. They're carrying a vague heaviness — a bad day, an argument replaying in their head, a worry they can't articulate. They don't need a tool yet. They need a space to **think out loud** first.
**The Mirror** is that space.
It's a freeform notebook with a chat-like interface where you write whatever's on your mind — stream of consciousness, venting, processing. As you write, Kalei's AI reads along quietly and does two things:
1. **Highlights fragments** — gently underlines or marks phrases that carry negative cognitive patterns (catastrophizing, black-and-white thinking, personalization, fortune-telling, etc.)
2. **Offers to Turn them** — tapping a highlighted fragment opens a mini-reframe inline, without leaving the flow of writing
You're not journaling into a void. You're writing into a mirror that reflects back what you can't see yourself.
---
## Why "The Mirror"
A kaleidoscope is built from mirrors. The mirrors are what create the symmetry — what take a random fragment and reveal the pattern. Without the mirrors, it's just broken glass.
The Mirror feature is the reflective surface of Kalei. The Kaleidoscope (Turn) is the active tool. The Mirror is the quiet awareness that makes the tool work.
**Metaphor alignment:**
- You write freely → you're pouring fragments onto the table
- Kalei highlights patterns → the mirror reflects back what you couldn't see
- You tap to reframe → you choose which fragments to Turn
- The session becomes a Reflection → saved to your Gallery with its own pattern
---
## How It Works — User Flow
### Entry Point
The Mirror lives as the **fifth element** in the app's navigation, or as a secondary action within the Turn tab. Two options:
**Option A — Dedicated tab (recommended):**
| Icon | Label | Function |
|------|-------|----------|
| ◇ | **Turn** | Quick structured reframe |
| ✦ | **Mirror** | Freeform notebook with AI awareness |
| ◎ | **Lens** | Manifestation Engine |
| ▦ | **Gallery** | History of patterns and reflections |
| ● | **You** | Profile and settings |
**Option B — Nested under Turn:**
Turn tab has two modes: "Quick Turn" (current structured input) and "Open Mirror" (freeform). Toggle at top.
**Recommendation:** Option A. The Mirror is different enough in intent and behavior that it deserves its own space. Users will develop separate habits — quick Turns for specific thoughts, Mirror sessions for processing.
### The Writing Experience
**Visual:** Chat-style interface. The user's messages appear as bubbles or blocks on one side. Clean, minimal, dark background consistent with Kalei's aesthetic. No AI responses appear unprompted — the AI is **listening**, not talking.
**Prompt on empty state:**
> "Start writing. Say whatever's on your mind. I'll listen."
> *Kalei will gently highlight patterns it notices. You decide what to do with them.*
**User writes freely.** They can send multiple messages in sequence, like texting a friend or writing in a stream. No character limits. No structure required. Just write.
### The Highlighting — "Fragment Detection"
As the user writes (or after each message is sent), Kalei's AI analyzes the text for **cognitive distortion patterns** — the same patterns that cognitive behavioral therapy identifies as drivers of negative thinking:
| Distortion | Example | What Kalei detects |
|---|---|---|
| Catastrophizing | "This is going to ruin everything" | Absolutist prediction language |
| Black-and-white thinking | "I always fail at this" | Always/never, all-or-nothing |
| Mind reading | "They probably think I'm an idiot" | Assuming others' thoughts |
| Fortune telling | "This will never get better" | Predicting negative outcomes |
| Personalization | "It's all my fault" | Taking undue responsibility |
| Discounting positives | "That win was just luck" | Minimizing good things |
| Emotional reasoning | "I feel like a failure so I must be one" | Feelings presented as facts |
| Should statements | "I should be further along by now" | Rigid self-imposed rules |
| Labeling | "I'm such a loser" | Identity-level negative labels |
| Overgeneralization | "Nothing ever works out for me" | One event → universal pattern |
**How highlighting appears:**
- Detected phrases get a **subtle underline or soft glow** in a warm amber/gold color — the color of light catching a fragment
- The highlight is gentle, not aggressive. It shouldn't feel like a red pen correcting homework. It should feel like sunlight falling on a piece of glass — drawing attention naturally
- A small **◇ icon** (fragment symbol) appears at the end of the highlighted phrase, indicating this fragment can be Turned
- Highlights appear **after the user finishes a message** (not while typing — that would be intrusive and anxiety-inducing)
**Critical UX principle:** The highlighting must feel like **noticing**, not **judging**. The AI is a mirror, not a critic. The user should feel seen, not corrected. This distinction is scientifically grounded — Bandura (1977) showed that perceived criticism undermines self-efficacy, while neutral observation preserves it. The Mirror builds capability awareness, not self-judgment.
### Tapping a Fragment — Inline Reframing
When the user taps a highlighted fragment:
1. A **mini-card slides up** from below (half-sheet modal, not full screen — user can still see their writing above)
2. The card shows:
- The original fragment, quoted
- The cognitive pattern name in plain language (e.g., "This sounds like catastrophizing — predicting the worst outcome")
- **12 reframed alternatives** — shorter and lighter than a full Turn, designed for quick insight
- A "Full Turn" button if they want to take this fragment into the Kaleidoscope for deeper exploration
- A "Dismiss" option — user can say "I see it, moving on" without reframing
**Example interaction:**
> **User writes:** "Had a terrible meeting today. My manager barely acknowledged my presentation. She probably thinks I'm not cut out for this role. I should just start looking for another job."
> **Kalei highlights:** "She probably thinks I'm not cut out for this role"
> **User taps the highlight. Card appears:**
> **◇ Fragment detected**
> *"She probably thinks I'm not cut out for this role"*
>
> This looks like **mind reading** — assuming someone else's thoughts without evidence.
>
> **A different angle:**
> There are many reasons a manager might seem distracted that have nothing to do with your performance. What you observed was her behavior. What she thinks is something you don't have access to yet — but you could ask.
>
> **[Full Turn ◇]** · **[Dismiss]**
### The AI's Role — Passive, Not Conversational
**This is critical.** The Mirror is NOT a chatbot. The AI does not:
- Respond to every message
- Ask follow-up questions unprompted
- Inject unsolicited advice
- Break the user's flow with interjections
The AI **only** does three things:
1. Highlights fragments (passively, after each message)
2. Provides reframes when the user taps a highlight (on demand)
3. Generates a session summary when the user ends the session (see below)
**Why not make it a chatbot?** Because the whole point is that the user is thinking out loud. Inserting AI responses between every message turns it into a conversation with a bot, which changes the psychology entirely. The user stops introspecting and starts performing. The Mirror should feel like writing in a journal that occasionally catches the light — not like talking to a therapist.
**Exception — The Nudge:** If the user has written 5+ messages with zero taps on any highlights and significant negative patterns are accumulating, Kalei can offer ONE gentle nudge at the end of the stream:
> "I noticed a few fragments in what you wrote. Want to look at them together?"
> **[Show me]** · **[Not now]**
This is the only time the AI initiates. Once per session maximum.
---
## Session Wrap-Up — The Reflection
When the user signals they're done writing (closes the Mirror, presses a "Done" button, or after a period of inactivity), Kalei generates a **Reflection** — a brief session summary.
**The Reflection includes:**
1. **The Mosaic** — a high-level summary of what the user wrote about (themes, not specifics)
> "Today's Mirror covered: work frustration, self-doubt about career, and a conflict with your manager."
2. **Fragments Found** — count of cognitive patterns detected
> "4 fragments noticed. You explored 2 of them."
3. **Patterns Revealed** — the reframes the user chose to engage with
> "You looked at mind reading and catastrophizing from new angles."
4. **A Generated Pattern** — a unique kaleidoscope visual for this session, saved to the Gallery alongside their Turn patterns. Mirror sessions get their own visual style — perhaps slightly different geometry (softer, more organic) to distinguish them from structured Turns
5. **An optional one-line insight** — the AI's single most important observation from the session
> "You were hardest on yourself about things you haven't confirmed are true."
**The Reflection is saved to the Gallery** as a distinct type: a Mirror Reflection. Users can revisit their sessions, re-read what they wrote, see which fragments they explored, and track how their patterns evolve over time.
---
## Where the Mirror Fits in the User's Journey
The three core features now form a **progression**:
```
THE MIRROR THE KALEIDOSCOPE THE LENS
(Awareness) → (Perspective) → (Direction)
"What am I "How else can I "What am I
feeling?" see this?" building toward?"
Freeform Structured Goal-focused
writing reframing manifestation
Fragments Fragments → Patterns Patterns → Focus
detected revealed applied
```
**The natural user flow:**
1. **Mirror** — User dumps their raw thoughts. AI highlights the fragments they can't see themselves
2. **Turn** — User takes the most charged fragment and gives it a full Turn in the Kaleidoscope, getting deep, multi-angle reframes
3. **Lens** — The insights from reframing inform the user's goals. What they thought was a setback becomes fuel for what they're building toward
Not every session follows this sequence. Some days you just need a quick Turn. Some days you just need to write in the Mirror. Some days you go straight to the Lens. But when a user does flow through all three, that's the **Kalei experience** at its deepest.
---
## Engagement & Retention Mechanics
### Mirror Streaks
Track separately from Turn streaks:
- "You've written in the Mirror 5 days in a row"
- Mirror sessions tend to be longer and more personal → higher engagement signal
**Science note:** Wood et al. (2021) found that context stability is the single biggest predictor of habit formation. Mirror streaks should track not just frequency but context consistency — "You've written in the Mirror at roughly the same time for 14 days" is a stronger habit signal than "14 sessions total across random times."
### Fragment Tracking Over Time
The Gallery can show **fragment patterns over time**:
- "This month, your most common fragment type was **should statements**"
- "You've reduced catastrophizing by 40% compared to last month"
- A visual "spectrum" chart showing which cognitive distortions appear most frequently
This turns the Mirror from a journal into a **self-awareness engine**. Users can literally see their thinking patterns change over time.
**Science note:** This longitudinal tracking implements Stevens & Bavelier's (2012) finding that attention training transfers and compounds. The fragment density decline over time is a measurable proxy for improved attentional self-awareness — the user is literally catching patterns earlier and more often because their attentional system has been retrained.
### Mirror Prompts
For days when the user opens the Mirror but doesn't know what to write:
- "What happened today that you're still thinking about?"
- "What would you say to a friend if they were feeling what you're feeling?"
- "What's one thing you're avoiding thinking about?"
- "Describe your mood in a sentence. Then ask yourself why."
These are optional, dismissible, and only shown on empty-state.
---
## Monetization Placement
| Feature | Free (Kalei) | Premium (Kalei Prism) |
|---|---|---|
| Mirror access | 2 sessions/week | Unlimited |
| Fragment highlighting | Basic (3 distortion types) | Full spectrum (all 10 types) |
| Inline reframes | 1 per session | Unlimited |
| Session Reflections | Summary only | Full Reflection with insight |
| Fragment tracking over time | ✗ | ✓ |
| Export Mirror sessions | ✗ | ✓ |
The free tier gives enough Mirror access to experience the value. The paywall hits at the point where the user wants **depth and consistency** — which is exactly when they're most likely to convert.
---
## Technical Implementation Notes
### AI Processing Pipeline
Each message the user sends in the Mirror triggers a lightweight AI analysis:
```
User message → Claude API call → Returns:
{
"fragments": [
{
"text": "She probably thinks I'm not cut out for this role",
"start_index": 89,
"end_index": 143,
"distortion_type": "mind_reading",
"distortion_label": "Mind reading",
"distortion_description": "Assuming someone else's thoughts without evidence",
"confidence": 0.87
}
]
}
```
**Confidence threshold:** Only highlight fragments with confidence > 0.75 to avoid false positives. A false positive (highlighting something that isn't actually distorted thinking) would erode trust quickly.
**Latency:** Analysis should complete within 12 seconds after message sent. Highlights appear with a subtle fade-in animation — fragments "catching the light."
### Reframe Generation (On Tap)
When user taps a highlighted fragment, a second API call generates the inline reframe:
```
Input: fragment text + surrounding context + distortion type
Output: {
"distortion_explanation": "Plain language explanation",
"reframe": "1-2 sentence alternative perspective",
"full_turn_prompt": "Pre-filled prompt for Kaleidoscope if user wants deeper exploration"
}
```
### Data Storage
Mirror sessions stored in Supabase:
```sql
-- Mirror sessions table
CREATE TABLE mirror_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
started_at TIMESTAMPTZ DEFAULT NOW(),
ended_at TIMESTAMPTZ,
reflection_summary TEXT,
reflection_insight TEXT,
pattern_seed TEXT, -- for generating the visual pattern
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Mirror messages table
CREATE TABLE mirror_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID REFERENCES mirror_sessions(id),
content TEXT NOT NULL,
sequence_order INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Detected fragments within messages
CREATE TABLE mirror_fragments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID REFERENCES mirror_messages(id),
fragment_text TEXT NOT NULL,
start_index INTEGER,
end_index INTEGER,
distortion_type VARCHAR(50),
confidence FLOAT,
was_tapped BOOLEAN DEFAULT FALSE,
was_reframed BOOLEAN DEFAULT FALSE,
reframe_text TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### Privacy & Sensitivity
Mirror content is the most personal data in the app. Requirements:
- **End-to-end encryption** for all Mirror content at rest
- **No Mirror content used for model training** — explicit policy
- **Local-first option** (future): Allow users to keep Mirror data on-device only
- **Easy deletion**: User can delete any session or all Mirror data
- **Content safety**: If AI detects crisis language (self-harm, suicidal ideation), surface crisis resources immediately — not as a highlight/reframe, but as a dedicated intervention with hotline numbers and a warm handoff message
---
## Updated App Structure
With the Mirror, Kalei now has three pillars that map to a complete mental model:
| Pillar | Feature | Metaphor | User need | Interaction style |
|--------|---------|----------|-----------|-------------------|
| **Awareness** | The Mirror | Reflective surface that shows you your fragments | "I need to process" | Freeform writing, passive AI |
| **Perspective** | The Kaleidoscope | The turn that reveals patterns in fragments | "I need to see this differently" | Structured input → output |
| **Direction** | The Lens | Focused vision toward what you're building | "I need to move forward" | Goal setting, affirmations, tracking |
**Together:** You become aware of your patterns (Mirror), you learn to see them differently (Kaleidoscope), and you channel that clarity into what you're building (Lens).
**Tagline still holds:** *Same pieces. New angle.*
**Elevator pitch (updated):**
> "Kalei is a kaleidoscope for your mind. Write freely in the Mirror and Kalei gently highlights the negative thinking patterns you can't see yourself. Take any thought into the Kaleidoscope and see it from entirely new angles. Then focus your clarity through the Lens toward the goals that matter to you. Same pieces. New angle. That's Kalei."
---
*The mirror doesn't tell you what to see. It shows you what's already there.*

View File

@@ -0,0 +1,427 @@
# The Spectrum — Kalei v1
## Scientific Foundation
The Spectrum is where multiple research pillars converge into a single intelligence layer.
**Expectation Effects (Stetler 2014, Pardo-Cabello et al. 2022):** The Turn Impact component ("Before & After") is a deliberate evidence engine. Stetler demonstrated that consistent adherence to a process reinforces positive expectations, which in turn improve outcomes — a documented feedback loop. By showing users concrete proof that reframing measurably shifts their subsequent emotional state, the Spectrum accelerates this cycle. The transparency doesn't weaken the effect; it strengthens it.
**Habit Formation (Wood & Neal 2007, Wood et al. 2021):** Rhythm Detection and streak mechanics implement Wood's finding that ~43% of daily behavior is habitual and that context stability predicts habit formation. The Spectrum tracks context patterns (time-of-day rhythms, weekly cycles) to help users understand and leverage their own behavioral patterns.
**Selective Attention (Yantis 2008, Stevens & Bavelier 2012):** Fragment Pattern tracking ("Your Glass") operates the Mirror's attentional principle at a longitudinal scale. Instead of highlighting individual distortions in real-time, it reveals macro-patterns: which cognitive biases dominate, how they shift over time, and which respond most to reframing. This is attentional self-knowledge — seeing your own perceptual filters from the outside.
**Self-Efficacy (Bandura 1977):** The Growth Trajectory ("The Long View") directly implements Bandura's most potent self-efficacy source: mastery experiences. By tracking fragment density decline, self-correction rate, and reframe adoption, the Spectrum provides concrete evidence of growing capability — "You are getting better at this" backed by data, not platitudes.
---
## Emotional Intelligence, Not Mood Tracking
Every wellness app asks you to rate your mood on a scale. Tap a smiley face. Drag a slider. It's self-reported, inaccurate, and most people stop doing it after two weeks because it feels like homework.
Kalei doesn't need to ask how you feel. **It already knows.**
Over time, users accumulate weeks or months of Mirror sessions, Turns, and Lens activity. Every word they've written, every fragment detected, every pattern revealed, every reframe they saved or dismissed — it's all data. Rich, personal, longitudinal emotional data that the user generated naturally while using features they already love.
The Spectrum turns that data into **self-knowledge**.
---
## Why "The Spectrum"
Light enters a prism and exits as a spectrum — the full range of colors that were always present but invisible to the naked eye. The Spectrum takes the raw light of your daily Kalei usage and separates it into its component colors so you can see what's really going on inside.
It also completes the optical metaphor system:
| Feature | Optical element | What it does |
|---------|----------------|--------------|
| The Mirror | Mirror | Reflects your thoughts back to you |
| The Kaleidoscope | Kaleidoscope | Rearranges fragments into patterns |
| The Lens | Lens | Focuses your vision on what's ahead |
| The Spectrum | Prism | Reveals the full range of what you're feeling |
---
## What The Spectrum Shows
### 1. The Emotional Landscape
A visual representation of your emotional state over time — not from self-reporting, but from **AI analysis of your Mirror sessions, Turns, and Lens check-ins.**
**How it works:**
Every Mirror message and Turn input is analyzed for emotional signatures across multiple dimensions:
- **Valence:** Positive ↔ Negative
- **Arousal:** Calm ↔ Activated
- **Certainty:** Confident ↔ Uncertain
- **Agency:** In control ↔ Helpless
- **Social orientation:** Connected ↔ Isolated
- **Temporal focus:** Past-dwelling ↔ Present ↔ Future-focused
These dimensions are plotted over time as a **flowing gradient visualization** — not a line chart, but a river of color that shifts and blends. Warm colors for activated states, cool for calm, dark for negative, bright for positive. The result looks like light passing through a prism: your emotional spectrum, laid out across days and weeks.
**The user sees:**
- The overall color/tone of their week at a glance
- Shifts and transitions (Tuesday was dark and activated → Wednesday calmed down after a Turn)
- Long-term trends (past month trending brighter, or a slow slide they hadn't noticed)
**What they don't see:** Numbers, scores, or ratings. The Spectrum is visual and intuitive, not clinical. You look at it and *feel* whether things are moving in the right direction.
### 2. Fragment Patterns — "Your Glass"
A breakdown of which cognitive distortion types appear most frequently in the user's writing.
**Visualization:** A faceted gem or crystal with different faces representing different distortion types. The larger the face, the more frequently that pattern appears. The gem evolves over time as patterns shift.
**Insights delivered in plain language:**
> "This month, **should statements** made up 34% of your fragments — up from 22% last month. You're putting more pressure on yourself than usual."
> "**Mind reading** dropped significantly since you started Turning those fragments. You assumed others' thoughts 8 times in January, only twice in February."
> "Your top 3 fragment types this month: catastrophizing, discounting positives, and black-and-white thinking."
**Why this matters:** Most people have 2-3 dominant cognitive distortions they don't know about. Seeing them named and tracked over time is genuinely transformative — it's the kind of insight you'd normally get after months of therapy.
### 3. Turn Impact — "Before & After"
Tracks the measurable effect of reframing on subsequent emotional state.
**How it works:**
The AI compares the emotional tone of Mirror sessions **before and after** a Turn:
- User writes in Mirror (frustrated, catastrophizing)
- User takes a fragment to the Kaleidoscope
- User writes in Mirror again later that day or the next day
- The Spectrum measures the shift
**What the user sees:**
> "After Turning a fragment, your next Mirror session is 62% more likely to show increased agency and reduced catastrophizing."
> "Your most impactful Turn this month was on Feb 3 — the shift in your writing afterward was significant."
> "Turns on work-related fragments have the strongest positive effect for you. Relationship fragments take 2-3 Turns before the shift shows up."
This is the **evidence engine** for Kalei's core thesis: that changing the angle actually changes how you feel. Users can see the proof in their own data.
**Science note:** This directly implements Stetler's (2014) adherence-expectation model. When users see measurable shifts in their own emotional data after Turns, it reinforces the expectation that reframing works — which increases future engagement and actual benefit. Pardo-Cabello et al. (2022) confirmed that the quality of the therapeutic relationship (or in Kalei's case, the app-user relationship) is the strongest predictor of whether expectation effects materialize. The Spectrum builds that trust through evidence.
### 4. Rhythm Detection — "Your Cycles"
Identifies recurring emotional patterns tied to time.
**Weekly rhythms:**
> "Your Mirror sessions on Mondays contain 3x more should statements than any other day."
> "Fridays tend to be your most positive writing days."
**Monthly rhythms:**
> "The last week of each month shows elevated anxiety patterns — possibly tied to deadlines or financial cycles."
**Event correlation (Lens integration):**
> "When you check in with your Lens goals in the morning, your afternoon Mirror sessions show 40% fewer negative fragments."
**Contextual patterns:**
> "After writing about [work] topics, catastrophizing spikes. After writing about [relationships], personalization is more common."
The user starts to see their emotional life as a **landscape with terrain** rather than random weather. Some hills are always there. Some valleys are seasonal. That awareness alone is a superpower.
**Science note:** Rhythm Detection operationalizes Wood et al.'s (2021) finding that habits are triggered by context cues. By revealing temporal patterns ("Mondays are heavy on should-statements"), the Spectrum helps users anticipate and prepare for predictable emotional terrain — turning reactive coping into proactive awareness.
### 5. Growth Trajectory — "The Long View"
The headline metric: **how is this person's relationship with their own thinking changing over time?**
**Tracked indicators:**
- Fragment density: How many distortions per 100 words in Mirror sessions (trending down = growth)
- Self-correction rate: How often the user identifies their own fragments before Kalei highlights them (measured by editing/deleting mid-message)
- Reframe adoption: How often saved patterns from Turns echo in subsequent Mirror writing (user naturally using new perspectives)
- Distortion diversity: Whether the user is getting stuck on one pattern or successfully addressing multiple types
- Turn-to-insight ratio: How many Turns result in a saved keepsake vs. dismissed patterns
**Visualization:** A single, evolving kaleidoscope pattern that represents your overall growth. The more you use Kalei, the more complex, colorful, and beautiful the pattern becomes. At month 1, it might be simple and muted. At month 6, it's intricate and vivid.
This becomes the **centerpiece of the Spectrum dashboard** — your personal growth, visualized as a living kaleidoscope pattern.
**Science note:** Every tracked indicator maps to Bandura's (1977) self-efficacy sources. Fragment density decline and self-correction rate are mastery experiences (the strongest efficacy source). Reframe adoption shows vicarious learning internalized. The evolving pattern visualization provides a visceral, non-numerical representation of growing capability — "I am getting better at this" made visible.
**Milestone moments:**
> "Your fragment density has dropped 30% since you started. You're catching your own patterns now."
> "This week, you naturally reframed a catastrophizing thought in your Mirror session without needing a Turn. That's new."
> "You've explored all 10 fragment types. You're seeing the full spectrum."
---
## The Spectrum Dashboard — Layout
### Top Section: The River
Your emotional landscape as a flowing color gradient. Swipe horizontally to scroll through time. Tap any point to see the Mirror session or Turn from that day.
### Middle Section: Your Glass
The faceted gem visualization showing fragment type distribution. Toggle between "This week," "This month," "All time." Tap any facet for the distortion deep-dive.
### Bottom Section: Insights Feed
A scrollable feed of AI-generated insights, refreshed weekly. Each insight is a card with:
- A one-line observation
- Supporting data (subtle, not overwhelming)
- An action suggestion when relevant
### Floating Element: Your Pattern
Your evolving kaleidoscope pattern, accessible from the top corner. Tap to expand full-screen. Shareable as a "growth snapshot."
---
## When Insights Are Delivered
The Spectrum doesn't bombard users with data. Insights surface at natural moments:
### Weekly Reflection (Push Notification)
Every Sunday evening (or user-configured day):
> "Your Spectrum updated. See what this week's light revealed. 🔮"
Opens to a **Weekly Spectrum Summary:**
- Dominant emotional color this week
- Top fragment type
- Most impactful Turn
- One insight
- The week's addition to your evolving pattern
### Monthly Deep Dive
First of each month:
> "January's Spectrum is ready. See how your light shifted."
A richer summary with month-over-month comparisons, rhythm detection insights, and growth trajectory updates.
### In-Context Nudges
Subtle, non-intrusive insights surfaced within other features:
- In the Mirror: "You've used the phrase 'I should' 4 times this session. That's a pattern worth noticing."
- After a Turn: "This is the 3rd time you've Turned a work-related fragment this week. The Spectrum can show you more about this pattern."
- In the Lens: "Your Lens focus on [career growth] aligns with the fragments you've been processing. You're working on the right things."
---
## Monetization
The Spectrum is a **Kalei Prism exclusive feature**. It's the single strongest reason to upgrade.
**Free tier gets:**
- A simplified weekly emotional summary (1 insight, no visualizations)
- Fragment type counts (basic numbers only)
- A teaser of what the full Spectrum shows: "Upgrade to see your full Spectrum"
**Prism tier gets:**
- Full Spectrum dashboard with all 5 sections
- Weekly and monthly deep dives
- Growth trajectory and evolving pattern
- Rhythm detection
- Turn impact analysis
- Export and sharing of Spectrum snapshots
**Upgrade CTA:**
> "You've written 47 Mirror sessions and completed 23 Turns. There's a story in that data. See your full Spectrum."
This is a natural paywall because the Spectrum **requires usage history to be valuable.** By the time a user has enough data for the Spectrum to be meaningful, they've already experienced Kalei's value through the free tier and are primed to convert.
---
## Privacy Architecture
The Spectrum analyzes deeply personal data. Trust is non-negotiable.
### Principles
1. **All analysis happens on aggregated patterns, never exposed raw content.** The Spectrum shows "your catastrophizing increased this week" — it never shows "you wrote 'my life is falling apart' on Tuesday"
2. **No Spectrum data leaves the user's account.** Not for model training, not for anonymized research, not for anything
3. **Users control the window.** They can exclude any Mirror session or Turn from Spectrum analysis. They can set the Spectrum to only analyze the last 30/60/90 days
4. **Full deletion.** "Reset my Spectrum" erases all analyzed data and starts fresh
5. **Transparency.** A "How this works" section explains exactly what the AI analyzes and what it doesn't
### Data Processing
Spectrum analysis runs as a **background job**, not in real-time:
- After each Mirror session ends, emotional dimensions are computed and stored as numerical vectors — not raw text
- Fragment types are already captured during Mirror sessions
- Weekly aggregation job runs to compute trends, rhythms, and insights
- The Spectrum dashboard reads from aggregated data only
```sql
-- Emotional analysis per Mirror session
CREATE TABLE spectrum_session_analysis (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
session_id UUID REFERENCES mirror_sessions(id),
session_date DATE NOT NULL,
valence FLOAT, -- -1 (negative) to 1 (positive)
arousal FLOAT, -- -1 (calm) to 1 (activated)
certainty FLOAT, -- -1 (uncertain) to 1 (confident)
agency FLOAT, -- -1 (helpless) to 1 (in control)
social_orientation FLOAT, -- -1 (isolated) to 1 (connected)
temporal_focus FLOAT, -- -1 (past) to 0 (present) to 1 (future)
fragment_count INTEGER,
word_count INTEGER,
dominant_distortion VARCHAR(50),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Emotional analysis per Turn
CREATE TABLE spectrum_turn_analysis (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
turn_id UUID REFERENCES turns(id),
turn_date DATE NOT NULL,
pre_valence FLOAT, -- emotional state of input
post_valence FLOAT, -- emotional state after reframe engagement
distortion_type VARCHAR(50),
reframe_saved BOOLEAN,
topic_cluster VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Weekly aggregated insights
CREATE TABLE spectrum_weekly (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
week_start DATE NOT NULL,
avg_valence FLOAT,
avg_arousal FLOAT,
avg_agency FLOAT,
total_fragments INTEGER,
total_turns INTEGER,
total_mirror_sessions INTEGER,
dominant_distortion VARCHAR(50),
distortion_distribution JSONB, -- {"catastrophizing": 5, "mind_reading": 3, ...}
fragment_density FLOAT, -- fragments per 100 words
turn_impact_score FLOAT, -- measured shift after turns
insight_text TEXT, -- AI-generated weekly insight
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, week_start)
);
-- Monthly deep dive
CREATE TABLE spectrum_monthly (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
month_start DATE NOT NULL,
growth_score FLOAT, -- composite improvement metric
rhythm_insights JSONB, -- detected patterns tied to time
month_over_month_delta JSONB, -- comparison with previous month
top_fragment_types JSONB, -- ranked list
most_impactful_turn UUID, -- references turn with biggest shift
pattern_complexity_score FLOAT, -- drives evolving visual pattern
narrative_summary TEXT, -- AI-generated monthly narrative
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, month_start)
);
```
---
## Notification Copy — Spectrum Voice
The Spectrum speaks with slightly more authority than other Kalei features — it has data behind it. But still warm, still poetic.
### Weekly
- "Your Spectrum shifted this week. Come see the colors."
- "7 days of fragments and patterns. Here's what the light reveals."
- "This week had a rhythm. The Spectrum caught it."
### Monthly
- "A month of Turns. Your Spectrum has a story to tell."
- "January's light, separated into its colors. Your monthly Spectrum is ready."
### Milestone Insights
- "First month of Spectrum data. Your baseline is set — now watch it evolve."
- "Your fragment density dropped below 5 per 100 words for the first time. You're catching yourself."
- "3 consecutive weeks of increasing agency in your writing. Something shifted."
- "You haven't catastrophized in 12 days. That's your longest streak."
### Growth Pattern Evolution
- "Your pattern grew a new layer this month. Tap to see it."
- "Remember your first pattern? Compare it to today's. Look how far you've come."
---
## The Spectrum as Retention Engine
The Spectrum solves the biggest problem in wellness apps: **the drop-off after the initial novelty fades.**
**Week 1-2:** Users are engaged with the Mirror and Kaleidoscope. Everything is new.
**Week 3-4:** Novelty fades. This is where most wellness apps lose people.
**With the Spectrum (timed with early user data accumulation):**
- "Your first Spectrum is ready" re-engages users with a new reason to open the app
- The evolving pattern creates **collection psychology** — users want to see it grow
- Weekly insights create a **recurring appointment** with the app
- Growth trajectory shows **concrete progress** — "this is working" evidence
- Fragment tracking creates **self-competition** — users try to beat their own patterns
- Monthly deep dives become **anticipated events** — not notifications to dismiss
The Spectrum turns Kalei from a tool you use when you feel bad into a **dashboard you check because you're curious about yourself.** That's the difference between reactive usage (declining) and proactive usage (compounding).
**Science note:** This retention mechanic is grounded in both habit formation and expectation effects. Wood et al. (2021) showed that shifting behavior from goal-directed (conscious, effortful) to habitual (automatic, context-triggered) is what sustains long-term change. Stetler (2014) showed that consistent engagement reinforces positive expectations. The Spectrum provides the evidence that keeps both loops spinning.
---
## Spectrum Rollout Sequence
### Pre-Launch (2 weeks before)
Notification to existing users:
> "You've completed [X] Turns and [Y] Mirror sessions. Something new is coming that turns all of that into self-knowledge. Stay tuned."
### Launch Day
> "The Spectrum is here. Every Turn you've taken, every fragment you've noticed — it all means something. See your full emotional landscape for the first time."
Open to a dramatic reveal of their personal Spectrum for the first time — the river visualization populating with their historical data, the gem forming its facets, the evolving pattern appearing.
This should be a **wow moment.** The user's own emotional history, visualized beautifully for the first time. Data they generated without thinking about it, now reflecting back as genuine self-knowledge.
### Post-Launch (ongoing)
Weekly and monthly cadence takes over. The Spectrum becomes a background engine that surfaces insights at the right moments and gives users a reason to maintain their Mirror and Turn habits.
---
## Updated Feature Map — Full Kalei Ecosystem
```
PHASE 1 PHASE 2
───────────────────────────────── ──────────────────────
THE MIRROR (Awareness) ──→ feeds data to ──→ THE SPECTRUM
Write freely (Intelligence)
AI highlights fragments See your patterns
Inline reframes Track growth
Discover rhythms
THE KALEIDOSCOPE (Perspective) ──→ feeds data to ──→ Measure impact
Structured reframing Evolving visual
Fragment → Patterns
Save keepsakes
THE LENS (Direction) ──→ informed by ──→
Goal setting
Daily affirmations
Vision tracking
◇ ◇
Kalei Free Kalei Prism
3 Turns/day Unlimited everything
2 Mirror/week + Full Spectrum
Basic Lens + Weekly/monthly insights
+ Growth trajectory
+ Fragment analytics
```
---
*White light looks simple. The Spectrum shows you everything it's made of.*

View File

@@ -0,0 +1,162 @@
# Kalei — AI Model Selection: Unbiased Analysis
## The Question
Which AI model should power a mental wellness app that needs to detect emotional fragments, generate empathetic perspective reframes, produce personalized affirmations, detect crisis signals, and analyze behavioral patterns over time?
---
## What Kalei Actually Needs From Its AI
| Task | Quality Bar | Frequency | Latency Tolerance |
|------|------------|-----------|-------------------|
| **Mirror** — detect emotional fragments in freeform writing | High empathy + precision | 2-7x/week per user | 2-3s acceptable |
| **Kaleidoscope** — generate 3 perspective reframes | Highest — this IS the product | 3-10x/day per user | 2-3s acceptable |
| **Lens** — daily affirmation generation | Medium — structured output | 1x/day per user | 5s acceptable |
| **Crisis Detection** — flag self-harm/distress signals | Critical safety — zero false negatives | Every interaction | <1s preferred |
| **Spectrum** — weekly/monthly pattern analysis | High analytical depth | 1x/week batch | Minutes acceptable |
The Kaleidoscope reframes are the core product experience. If they feel generic, robotic, or tone-deaf, users churn. This is the task where model quality matters most.
---
## Venice.ai API — What You Get
Since you already have Venice Pro ($10 one-time API credit), here are the relevant models and their pricing:
### Best Venice Models for Kalei
| Model | Input/MTok | Output/MTok | Cache Read | Context | Privacy | Notes |
|-------|-----------|------------|------------|---------|---------|-------|
| **DeepSeek V3.2** | $0.40 | $1.00 | $0.20 | 164K | Private | Strongest general model on Venice |
| **Qwen3 235B A22B** | $0.15 | $0.75 | — | 131K | Private | Best price-to-quality ratio |
| **Llama 3.3 70B** | $0.70 | $2.80 | — | 131K | Private | Meta's flagship open model |
| **Gemma 3 27B** | $0.12 | $0.20 | — | 203K | Private | Ultra-cheap, Google's open model |
| **Venice Small (Qwen3 4B)** | $0.05 | $0.15 | — | 33K | Private | Affirmation-tier only |
### Venice Advantages
- **Privacy-first architecture** — no data retention, critical for mental health
- **OpenAI-compatible API** — trivial to swap in/out, same SDK
- **Prompt caching** on select models (DeepSeek V3.2 confirmed)
- **You already pay for Pro** — $10 free API credit to test
- **No minimum commitment** — pure pay-per-use
### Venice Limitations
- **No batch API** — can't get 50% off for Spectrum overnight processing
- **"Uncensored" default posture** — Venice optimizes for no guardrails, which is the OPPOSITE of what a mental health app needs. We must disable Venice system prompts and provide our own safety layer
- **No equivalent to Anthropic's constitutional AI** — crisis detection safety net is entirely on us
- **Smaller infrastructure** — less battle-tested at scale than Anthropic/OpenAI
- **Rate limits not publicly documented** — could be a problem at scale
---
## Head-to-Head: Venice Models vs Claude Haiku 4.5
### Cost Per User Per Month
Calculated using our established usage model: Free user = 3 Turns/day, 2 Mirror/week, daily Lens.
| Model (via) | Free User/mo | Prism User/mo | vs Claude Haiku |
|-------------|-------------|--------------|-----------------|
| **Claude Haiku 4.5** (Anthropic) | $0.31 | $0.63 | baseline |
| **DeepSeek V3.2** (Venice) | ~$0.07 | ~$0.15 | **78% cheaper** |
| **Qwen3 235B** (Venice) | ~$0.05 | ~$0.10 | **84% cheaper** |
| **Llama 3.3 70B** (Venice) | ~$0.16 | ~$0.33 | **48% cheaper** |
| **Gemma 3 27B** (Venice) | ~$0.02 | ~$0.04 | **94% cheaper** |
The cost difference is massive. At 200 DAU (traction), monthly AI cost drops from ~$50 to ~$10-15.
### Quality Comparison for Emotional Tasks
This is the critical question. Here's what the research and benchmarks tell us:
**Emotional Intelligence (EI) Benchmarks:**
- A 2025 Nature study tested LLMs on 5 standard EI tests. GPT-4, Claude 3.5 Haiku, and DeepSeek V3 all outperformed humans (81% avg vs 56% human avg)
- GPT-4 scored highest with a Z-score of 4.26 on the LEAS emotional awareness scale
- Claude models are specifically noted for "endless empathy" — excellent for therapeutic contexts but with dependency risk
- A blinded study found AI-generated psychological advice was rated MORE empathetic than human expert advice
**Model-Specific Emotional Qualities:**
| Model | Empathy Quality | Tone Consistency | Creative Reframing | Safety/Guardrails |
|-------|----------------|-----------------|-------------------|-------------------|
| Claude Haiku 4.5 | ★★★★☆ | ★★★★★ | ★★★★☆ | ★★★★★ |
| DeepSeek V3.2 | ★★★☆☆ | ★★★★☆ | ★★★☆☆ | ★★☆☆☆ |
| Qwen3 235B | ★★★★☆ | ★★★★☆ | ★★★☆☆ | ★★☆☆☆ |
| Llama 3.3 70B | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ |
| Gemma 3 27B | ★★☆☆☆ | ★★★☆☆ | ★★☆☆☆ | ★★★☆☆ |
**Key findings:**
- DeepSeek V3.2 is described as "slightly more mechanical in tone" with "repetition in phrasing" — problematic for daily therapeutic interactions
- Qwen3 is praised for "coherent extended conversations" and "tone consistency over long interactions" — actually quite good for our use case
- Llama 3.3 is solid but unremarkable for emotional tasks
- Gemma 3 27B is too small for the nuance we need in Mirror and Kaleidoscope
- Claude's constitutional AI training makes crisis detection significantly more reliable out-of-the-box
---
## The Final Decision (Updated February 2026)
After evaluating all options including Venice, Claude-first, and various hybrid strategies, the decision is:
### ★ Chosen: DeepSeek V3.2 via OpenRouter + Non-Chinese Providers
**Primary:** DeepSeek V3.2 routed through DeepInfra/Fireworks (US/EU infrastructure) via OpenRouter
**Fallback:** Claude Haiku 4.5 via OpenRouter (automatic failover on provider outage)
**Single model for all features** — no tiering until 5,000+ DAU justifies the complexity
| | DeepInfra (via OpenRouter) | Claude Haiku 4.5 (fallback) |
|---|---|---|
| Input (cache miss) | $0.26/M | $1.00/M |
| Input (cache hit) | $0.216/M | $0.10/M |
| Output | $0.38/M | $5.00/M |
**Monthly AI cost at 200 DAU: ~$8** (vs $50 with Claude Haiku, vs $12-18 Venice hybrid)
### Why This Beats All Other Options
1. **Data privacy solved** — DeepInfra/Fireworks host on US/EU infrastructure. No data through Chinese servers. Critical for a mental wellness app.
2. **8590% cheaper than Claude** — per-user AI cost drops from $0.33 to ~$0.034/month (free users).
3. **Automatic failover** — OpenRouter routes to Claude Haiku if DeepInfra goes down. No code changes, no downtime.
4. **No vendor lock-in** — one API key, switch models/providers via config. OpenRouter's API is OpenAI-compatible.
5. **Single model simplicity** — one prompt set to tune, one quality bar to maintain. Solo founder can manage this.
6. **Emotional intelligence validated** — Nature 2025 study shows DeepSeek V3 scores comparably to Claude on standardized EI tests (81% avg vs 56% human avg).
### Why Not Venice, Groq, or Direct DeepSeek
- **Venice:** No batch API, "uncensored" default posture requires extra safety work, rate limits undocumented, smaller infrastructure.
- **Groq:** Great speed but limited model selection. Useful as a future tier for structured generation at 5,000+ DAU.
- **DeepSeek Direct API:** Cheapest option ($0.028 cache hits) but routes all data through Chinese servers. Non-starter for mental health data.
- **Tiered hybrid (Option D):** Saves ~$30-50/month over single-model approach but adds 4 separate prompt configs, routing logic, and quality benchmarks. Not worth the complexity at current scale.
### Safety Layer (Non-Negotiable Regardless of Provider)
```
User input → Keyword crisis detector (local, instant)
→ If flagged: hardcoded crisis response (no LLM needed)
→ If clear: send to OpenRouter with safety-focused system prompt
→ Post-process: scan output for harmful patterns before showing to user
```
We build our own safety layer regardless of provider. This gives us MORE control than relying on any provider's built-in guardrails.
---
## Final Cost Model (OpenRouter + DeepInfra)
| Stage | DAU | AI Cost/mo | Total Infra/mo | Break-even Subscribers |
|-------|-----|-----------|----------------|----------------------|
| Launch (0-500 users) | ~50 | ~$2 | ~$16 | **3 Prism @ $4.99** |
| Traction (500-2K) | ~200 | ~$8 | ~$53 | **11 Prism** |
| Growth (2K-10K) | ~1K | ~$40 | ~$216 | **43 Prism** |
Compare to Claude-first: launch was $26/mo, now $16. growth was $425, now $216. AI went from 60% of total spend to 19%.
---
## Scaling Roadmap
1. **Launch → 600 DAU:** Single model (DeepSeek V3.2 via OpenRouter/DeepInfra). Focus on prompt quality.
2. **600+ DAU:** Evaluate self-hosted Qwen3-30B-A3B on GPU ($245/month fixed) — cheaper than API at this volume, full data control.
3. **5,000+ DAU:** Introduce tiered model routing if usage data shows certain features benefit from specialized models.
4. **Build the safety layer regardless** — multi-stage crisis filter is a day-one requirement, not a provider feature.

View File

@@ -0,0 +1,641 @@
# Kalei — Architecture & User Journey Diagrams
All diagrams below are valid Mermaid syntax. Paste into any Mermaid renderer (mermaid.live, GitHub markdown, VS Code preview) to visualize.
---
## 1. Complete User Journey
Shows the full lifecycle from app discovery through onboarding, daily habit loops, conversion, and long-term retention.
```mermaid
flowchart LR
subgraph Acquisition["Acquisition"]
A1["App Store Discovery"] --> A2["Download App"]
A2 --> A3["Open for First Time"]
end
subgraph Onboarding["Onboarding Flow"]
A3 --> O1["Welcome Screen"]
O1 --> O2["3 Swipeable Intro Cards"]
O2 --> O3["Choose Reframe Style\n(Brutal / Gentle / Logical / Philosophical / Humor)"]
O3 --> O4["Set Daily Check-in Time"]
O4 --> O5["Create Account\n(Email / Google / Apple)"]
O5 --> O6["First Reframe — WOW Moment"]
O6 --> O7["Land on Home Screen"]
end
subgraph DailyLoop["Daily Habit Loop"]
O7 --> D1["Push Notification\n'What's weighing on you?'"]
D1 --> D2{"User Intent"}
D2 -->|"Quick reframe"| T1["The Turn\n(Kaleidoscope)"]
D2 -->|"Need to process"| M1["The Mirror\n(Freeform Writing)"]
D2 -->|"Goal focus"| L1["The Lens\n(Manifestation)"]
D2 -->|"Review history"| G1["The Gallery"]
T1 --> D3["Save Reframe / Keepsake"]
M1 --> D3
L1 --> D3
D3 --> D4["Streak Updated"]
D4 --> D5["Return Tomorrow"]
D5 --> D1
end
subgraph Deepening["Engagement Deepening"]
G1 --> E1["See Thought Patterns Over Time"]
E1 --> E2["Weekly Summary\n(Sunday Push)"]
E2 --> E3["Discover Recurring Fragments"]
E3 --> E4["Motivation to Continue"]
E4 --> D1
end
subgraph Conversion["Free → Premium"]
D4 --> C1{"Hit Free Tier Limit?"}
C1 -->|"Yes"| C2["Soft Paywall\n'Unlock unlimited Turns'"]
C2 --> C3{"Convert?"}
C3 -->|"Yes"| C4["Subscribe to Prism\n($4.99/mo)"]
C3 -->|"Not yet"| D5
C4 --> C5["Full Access Unlocked"]
C5 --> D1
end
subgraph Retention["Long-Term Retention"]
C5 --> R1["Spectrum Dashboard\n(Prism+)"]
R1 --> R2["Monthly AI Insights"]
R2 --> R3["Growth Trajectory"]
R3 --> R4["Deep Self-Knowledge"]
R4 --> D1
end
```
---
## 2. Backend System Architecture
The full backend from client through edge, API services, AI layer, data stores, and external integrations.
```mermaid
flowchart TB
subgraph Client["Client Layer"]
APP["React Native + Expo App"]
TURN_UI["Turn Screen"]
MIRROR_UI["Mirror Screen"]
LENS_UI["Lens Screen"]
GALLERY_UI["Gallery Screen"]
SPECTRUM_UI["Spectrum Dashboard"]
PROFILE_UI["Profile & Settings"]
end
subgraph Edge["Edge Layer"]
CF["Cloudflare\nDNS / CDN / DDoS"]
NGINX["Nginx\nReverse Proxy / SSL / Rate Limit"]
end
subgraph API["API Layer — Modular Monolith (Fastify)"]
GW["API Gateway & Auth\n(JWT + Refresh Rotation)"]
TURN_SVC["Turn Service"]
MIRROR_SVC["Mirror Service"]
LENS_SVC["Lens Service"]
SPECTRUM_SVC["Spectrum Service"]
SAFETY_SVC["Safety Service\n(Crisis Detection)"]
ENT_SVC["Entitlement Service\n(Plan Gating)"]
COST_SVC["Usage Meter &\nCost Guard"]
JOBS["Job Scheduler\n& Workers"]
NOTIF["Notification Service"]
end
subgraph AI["AI Layer (via OpenRouter Gateway)"]
AI_GW["AI Gateway\n(OpenRouter Provider Routing)"]
DEEPSEEK["DeepSeek V3.2\nvia DeepInfra/Fireworks\n(US/EU — Primary)"]
CLAUDE_FALLBACK["Claude Haiku 4.5\n(Automatic Fallback)"]
end
subgraph Data["Data Layer"]
PG["PostgreSQL 16\n(Source of Truth)"]
REDIS["Redis\n(Cache / Rate Limits / Counters)"]
OBJ["Object Storage\n(Spectrum Exports)"]
end
subgraph External["External Services"]
APPLE["Apple App Store\nBilling API"]
GOOGLE["Google Play\nBilling API"]
APNS["APNs"]
FCM["FCM"]
POSTHOG["PostHog\n(Self-Hosted Analytics)"]
GLITCHTIP["GlitchTip\n(Error Tracking)"]
end
APP --> CF --> NGINX --> GW
GW --> TURN_SVC
GW --> MIRROR_SVC
GW --> LENS_SVC
GW --> SPECTRUM_SVC
GW --> ENT_SVC
TURN_SVC --> SAFETY_SVC
MIRROR_SVC --> SAFETY_SVC
LENS_SVC --> SAFETY_SVC
TURN_SVC --> AI_GW
MIRROR_SVC --> AI_GW
LENS_SVC --> AI_GW
SPECTRUM_SVC --> AI_GW
AI_GW --> DEEPSEEK
AI_GW --> CLAUDE_FALLBACK
TURN_SVC --> COST_SVC
MIRROR_SVC --> COST_SVC
LENS_SVC --> COST_SVC
COST_SVC --> REDIS
TURN_SVC --> PG
MIRROR_SVC --> PG
LENS_SVC --> PG
SPECTRUM_SVC --> PG
ENT_SVC --> PG
JOBS --> PG
ENT_SVC --> APPLE
ENT_SVC --> GOOGLE
NOTIF --> APNS
NOTIF --> FCM
GW --> POSTHOG
GW --> GLITCHTIP
```
---
## 3. The Mirror (Awareness) — Sequence Diagram
Complete sequence from session start through writing, fragment detection, inline reframing, and session reflection.
```mermaid
sequenceDiagram
participant U as User
participant App as Mobile App
participant API as Kalei API
participant Safety as Safety Service
participant Ent as Entitlement Service
participant AI as AI Gateway
participant Model as DeepSeek V3.2 via OpenRouter
participant DB as PostgreSQL
participant R as Redis
Note over U,R: Session Start
U->>App: Opens Mirror tab
App->>API: POST /mirror/sessions
API->>Ent: Check plan (free: 2/week, prism: unlimited)
Ent->>R: Read session counter
R-->>Ent: Counter value
Ent-->>API: Allowed / Denied
API->>DB: Create mirror_session row
API-->>App: Session ID + empty state prompt
Note over U,R: Writing & Fragment Detection Loop
U->>App: Writes message freely
App->>API: POST /mirror/messages
API->>Safety: Crisis precheck on text
alt Crisis Detected
Safety->>DB: Log safety_event
API-->>App: Crisis resources (hotlines, warm message)
else Safe Content
API->>AI: Fragment detection prompt + user text
AI->>Model: Inference request (cached system prompt)
Model-->>AI: JSON with fragments + confidence scores
AI-->>API: Validated structured result
API->>DB: Save message + fragments (confidence > 0.75)
API->>R: Increment usage counters
API-->>App: Message with highlighted fragments (amber glow + ◇ icons)
end
Note over U,R: User Taps a Fragment
U->>App: Taps highlighted fragment ◇
App->>API: POST /mirror/fragments/{id}/reframe
API->>AI: Reframe prompt + fragment + surrounding context
AI->>Model: Inference request
Model-->>AI: Reframe + distortion explanation
AI-->>API: Validated reframe response
API->>DB: Update fragment (was_tapped, was_reframed, reframe_text)
API-->>App: Mini-card slides up with reframe
App-->>U: Shows pattern name + alternative angle + Full Turn option
Note over U,R: Session Close & Reflection
U->>App: Presses Done / closes Mirror
App->>API: POST /mirror/sessions/{id}/close
API->>AI: Generate Reflection from all messages + fragments
AI->>Model: Batch summary request
Model-->>AI: Mosaic themes + fragment count + insight
AI-->>API: Reflection payload
API->>DB: Update session with reflection + pattern_seed
API-->>App: Reflection card (Mosaic + fragments found + patterns + one-line insight)
App-->>U: Reflection saved to Gallery
```
---
## 4. The Turn (Kaleidoscope) — Sequence Diagram
The structured reframing flow with entitlement gating, safety checks, and multi-perspective AI generation.
```mermaid
sequenceDiagram
participant U as User
participant App as Mobile App
participant API as Kalei API
participant Ent as Entitlement Service
participant Safety as Safety Service
participant AI as AI Gateway
participant Model as DeepSeek V3.2 via OpenRouter
participant DB as PostgreSQL
participant Cost as Cost Guard
participant R as Redis
Note over U,R: User Submits a Fragment for Turning
U->>App: Types negative thought + selects style
App->>API: POST /turns {text, style, context}
Note over API,R: Validation & Gating
API->>Ent: Validate tier + daily Turn cap
Ent->>R: Check daily counter (free: 3/day)
R-->>Ent: Current count
Ent-->>API: Allowed / Limit reached
alt Limit Reached
API-->>App: Soft paywall prompt
App-->>U: "Unlock unlimited Turns with Prism"
else Allowed
Note over API,R: Safety Gate
API->>Safety: Crisis precheck on text
alt Crisis Detected
Safety->>DB: Log safety_event
API-->>App: Crisis resources response
App-->>U: Hotline numbers + warm message
else Safe Content
Note over API,R: AI Reframe Generation
API->>AI: Build prompt (cached system + user style + fragment + history context)
AI->>Model: Streaming inference request
Model-->>AI: 3 reframe perspectives + micro-action (if-then)
AI-->>API: Validated structured response + token count
Note over API,R: Record & Respond
API->>Cost: Record token usage + budget check
Cost->>R: Update per-user + global counters
API->>DB: Save turn + reframes + metadata
API-->>App: Stream final Turn result
Note over U,App: User Sees the Turn Card
App-->>U: Original fragment quoted
App-->>U: 3 perspective reframes in chosen style
App-->>U: Micro-action (Gollwitzer if-then)
App-->>U: "Why This Works" expandable science drawer
Note over U,App: Post-Turn Actions
U->>App: Save as Keepsake / Try Different Style / Share
App->>API: POST /turns/{id}/save
API->>DB: Mark as saved keepsake
API-->>App: Streak counter updated
end
end
```
---
## 5. The Lens (Manifestation Engine) — 6-Step Flow
The complete goal creation and daily action system mapped to the 6 research-backed steps.
```mermaid
flowchart TB
subgraph Step1["Step 1: DECIDE — Clarity"]
S1A["User taps 'Create a Manifestation'"] --> S1B["AI Conversation (3-5 exchanges)\n'What do you want to achieve?'"]
S1B --> S1C["SMART Goal Refinement"]
S1C --> S1D["Output: Clarity Statement Card"]
end
subgraph Step2["Step 2: SEE IT — Mental Rehearsal"]
S1D --> S2A["AI generates personalized\nvisualization script"]
S2A --> S2B["First-person, sensory-rich,\nprocess-focused imagery"]
S2B --> S2C["User reads/listens\nMarks as complete"]
S2C --> S2D["Output: Vision Summary\n(revisitable daily)"]
end
subgraph Step3["Step 3: BELIEVE — Self-Efficacy"]
S2D --> S3A["AI asks:\n'What makes you doubt\nthis is possible?'"]
S3A --> S3B["User lists doubts"]
S3B --> S3C["AI addresses each with:\npast successes + transferable skills\n+ role models + small wins"]
S3C --> S3D["Output: Belief Statement Card\n'You CAN — here's the evidence'"]
end
subgraph Step4["Step 4: NOTICE — Attention Training"]
S3D --> S4A["AI sets up daily\nattention prompts"]
S4A --> S4B["Daily push:\n'Notice one thing aligned\nwith your goal today'"]
S4B --> S4C["User logs observations\nBuilds Evidence Journal"]
S4C --> S4D["AI surfaces patterns:\n'23 alignment instances\nthis month'"]
end
subgraph Step5["Step 5: ACT — Implementation Intentions"]
S4D --> S5A["AI generates weekly\nmicro-actions"]
S5A --> S5B["Gollwitzer if-then format:\n'If [time/situation],\nthen I will [15-min action]'"]
S5B --> S5C["User checks off\ncompleted actions"]
S5C --> S5D["AI adapts difficulty:\n2-min actions → scales up\nas habit solidifies"]
end
subgraph Step6["Step 6: REPEAT — Compound"]
S5D --> S6A["Habit Tracking Dashboard"]
S6A --> S6B["Visual growth charts\nMilestone celebrations"]
S6B --> S6C["Weekly AI Summary:\nreframes + patterns +\nprogress + adjustments"]
S6C --> S6D["Celebrates PROCESS\nnot just outcomes"]
S6D --> S4B
end
```
---
## 6. AI Processing Pipeline & Cost Routing
How every AI request flows through the gateway with prompt caching, safety guards, and cost-aware provider routing.
```mermaid
flowchart LR
subgraph Input["Request Sources"]
MIRROR["Mirror\n(Fragment Detection)"]
TURN["Turn\n(3 Reframes)"]
LENS["Lens\n(Affirmations)"]
SPECTRUM["Spectrum\n(Weekly Insights)"]
end
subgraph Pipeline["AI Gateway Pipeline"]
PROMPT["Prompt Builder\n(System + User Context + Research Grounding)"]
CACHE["Prompt Cache Check\n(~20% savings on cached system prompts)"]
SAFETY_CHECK["Anti-Toxicity Guard\n(No toxic positivity / magical thinking)"]
ROUTER{"Cost Router\nBudget Check"}
end
subgraph PrimaryLane["Primary Lane (via OpenRouter)"]
DEEPSEEK_RT["DeepSeek V3.2\nvia DeepInfra/Fireworks\n($0.26/$0.38 per MTok)"]
end
subgraph FallbackLane["Fallback Lane (Auto-Failover)"]
CLAUDE_FB["Claude Haiku 4.5\n(Automatic on provider outage)"]
end
subgraph TemplateLane["Template Fallback (Budget Pressure)"]
TEMPLATE["Local Template System\n(Pre-written, no AI cost)"]
end
subgraph Output["Response Handling"]
VALIDATE["Structured Output\nValidation (JSON)"]
TOKEN_LOG["Token Usage Logger"]
BUDGET["Budget Tracker\n(Per-user daily + Global monthly)"]
RESPONSE["Stream Response\nto Client"]
end
subgraph Alerts["Cost Controls"]
ALERT50["50% Budget Alert"]
ALERT80["80% Budget Alert\nDegrade Lens to templates"]
ALERT95["95% Budget Alert\nPause Spectrum generation"]
HARDCAP["100% Hard Cap\nGraceful service message"]
end
MIRROR --> PROMPT
TURN --> PROMPT
LENS --> PROMPT
SPECTRUM --> PROMPT
PROMPT --> CACHE
CACHE --> SAFETY_CHECK
SAFETY_CHECK --> ROUTER
ROUTER -->|"All features\n(primary)"| DEEPSEEK_RT
ROUTER -->|"Provider outage\n(auto-failover)"| CLAUDE_FB
ROUTER -->|"Lens / basic content\n(budget pressure)"| TEMPLATE
DEEPSEEK_RT --> VALIDATE
CLAUDE_FB --> VALIDATE
TEMPLATE --> VALIDATE
VALIDATE --> TOKEN_LOG
TOKEN_LOG --> BUDGET
BUDGET --> RESPONSE
BUDGET --> ALERT50
BUDGET --> ALERT80
BUDGET --> ALERT95
BUDGET --> HARDCAP
```
---
## 7. Safety & Crisis Detection Flow
The multi-stage safety pipeline that ensures crisis content is never reframed.
```mermaid
flowchart TB
subgraph Input["User Input Arrives"]
MSG["User text from\nMirror / Turn / Lens"]
end
subgraph Stage1["Stage 1: Deterministic Keyword Scan"]
KW["Keyword & Pattern Matcher\n(Regex-based, zero latency)"]
KW_YES{"Crisis keywords\ndetected?"}
end
subgraph Stage2["Stage 2: AI Confirmation"]
AI_CHECK["Low-latency AI model\nconfirms severity"]
AI_YES{"Confirmed\ncrisis?"}
end
subgraph CrisisResponse["Crisis Response — NEVER Reframed"]
CR1["Hardcoded crisis template\n(Not AI-generated)"]
CR2["Display local hotline numbers\n(988 Lifeline, Crisis Text Line)"]
CR3["Warm handoff message:\n'You matter. Help is available\nright now.'"]
CR4["Log safety_event to DB\nfor monitoring"]
end
subgraph NormalFlow["Normal Processing"]
PROCEED["Continue to AI Gateway\nfor reframing / detection"]
end
subgraph Monitoring["Safety Monitoring"]
FP["Track false positives\nfor filter tuning"]
FN["Track false negatives\nvia user reports"]
REVIEW["Weekly safety review loop"]
end
MSG --> KW
KW --> KW_YES
KW_YES -->|"Yes — high confidence\n(explicit phrases)"| CR1
KW_YES -->|"Maybe — ambiguous"| AI_CHECK
KW_YES -->|"No"| PROCEED
AI_CHECK --> AI_YES
AI_YES -->|"Yes"| CR1
AI_YES -->|"No — false alarm"| PROCEED
CR1 --> CR2
CR2 --> CR3
CR3 --> CR4
PROCEED --> FP
CR4 --> FN
FP --> REVIEW
FN --> REVIEW
```
---
## 8. Subscription & Billing State Machine
All user states from anonymous through free, Prism, and Prism+ with renewal, grace period, and downgrade flows.
```mermaid
stateDiagram-v2
[*] --> Anonymous: App Downloaded
Anonymous --> FreeUser: Account Created
state FreeUser {
[*] --> ActiveFree
ActiveFree --> LimitHit: Daily/weekly cap reached
LimitHit --> ActiveFree: Next day/week resets
LimitHit --> PaywallShown: Soft upgrade prompt
}
state PrismSubscriber {
[*] --> ActivePrism
ActivePrism --> PrismRenewal: Monthly renewal
PrismRenewal --> ActivePrism: Payment success
PrismRenewal --> GracePeriod: Payment failed
GracePeriod --> ActivePrism: Retry success
GracePeriod --> Expired: Grace period ends
}
state PrismPlusSubscriber {
[*] --> ActivePrismPlus
ActivePrismPlus --> PlusMRenewal: Monthly renewal
PlusMRenewal --> ActivePrismPlus: Payment success
PlusMRenewal --> PlusGrace: Payment failed
PlusGrace --> ActivePrismPlus: Retry success
PlusGrace --> PlusExpired: Grace period ends
}
FreeUser --> PrismSubscriber: Subscribe $4.99/mo
FreeUser --> PrismPlusSubscriber: Subscribe $9.99/mo
PrismSubscriber --> PrismPlusSubscriber: Upgrade
PrismPlusSubscriber --> PrismSubscriber: Downgrade
PrismSubscriber --> FreeUser: Cancel / Expired
PrismPlusSubscriber --> FreeUser: Cancel / PlusExpired
state EntitlementCheck {
[*] --> CheckPlan
CheckPlan --> FreeTier: No active subscription
CheckPlan --> PrismTier: Active Prism
CheckPlan --> PrismPlusTier: Active Prism+
FreeTier --> ApplyLimits: 3 Turns/day, 2 Mirror/week
PrismTier --> FullAccess: Unlimited Turns + Mirror
PrismPlusTier --> FullPlusAccess: Full + Spectrum + Insights
}
```
---
## 9. Data Entity Relationship Model
All database domains showing how Users connect to every feature, commerce, and ops table.
```mermaid
flowchart LR
subgraph Identity["Identity Domain"]
USERS["USERS\nid, email, password_hash"]
PROFILES["PROFILES\nuser_id, display_name\nreframe_style, checkin_time"]
AUTH["AUTH_SESSIONS\nid, user_id, device_info"]
REFRESH["REFRESH_TOKENS\nid, user_id, token_hash"]
end
subgraph Mirror["Mirror Domain"]
M_SESS["MIRROR_SESSIONS\nid, user_id, started_at\nreflection_summary"]
M_MSG["MIRROR_MESSAGES\nid, session_id\ncontent (encrypted)"]
M_FRAG["MIRROR_FRAGMENTS\nid, message_id\ndistortion_type, confidence\nwas_tapped, reframe_text"]
end
subgraph Turn["Turn Domain"]
TURNS["TURNS\nid, user_id, input_text\nreframe_style, reframes\nmicro_action, is_saved"]
end
subgraph Lens["Lens Domain"]
GOALS["LENS_GOALS\nid, user_id, goal_text\nclarity_statement\nbelief_statement"]
ACTIONS["LENS_ACTIONS\nid, goal_id, action_text\nif_then_format, completed"]
end
subgraph Spectrum["Spectrum"]
S_WEEK["SPECTRUM_WEEKLY\nuser_id, week_start\naggregates, insight"]
S_MONTH["SPECTRUM_MONTHLY\nuser_id, month\ngrowth_trajectory"]
end
subgraph Commerce["Commerce"]
SUBS["SUBSCRIPTIONS\nid, user_id, plan\nstore, status, expires_at"]
end
subgraph Ops["Safety and Ops"]
SAFE["SAFETY_EVENTS\nid, user_id, trigger_text\ndetection_stage"]
AI_USE["AI_USAGE_EVENTS\nid, user_id, feature\nmodel, tokens, cost"]
end
USERS --> PROFILES
USERS --> AUTH
USERS --> REFRESH
USERS --> M_SESS
M_SESS --> M_MSG
M_MSG --> M_FRAG
USERS --> TURNS
USERS --> GOALS
GOALS --> ACTIONS
USERS --> S_WEEK
USERS --> S_MONTH
USERS --> SUBS
USERS --> SAFE
USERS --> AI_USE
```
---
## 10. Deployment Topology & Scaling Path
How the infrastructure evolves from a single EUR 8.45/mo VPS to a multi-node architecture as DAU grows.
```mermaid
flowchart TB
subgraph Launch["Launch: Single VPS — 50 DAU — ~EUR 16/mo"]
P1_CF["Cloudflare (Free)"] --> P1_NGINX["Nginx"]
P1_NGINX --> P1_API["Node.js API + Workers"]
P1_API --> P1_PG["PostgreSQL 16"]
P1_API --> P1_REDIS["Redis"]
P1_API --> P1_AI["DeepSeek V3.2\nvia OpenRouter/DeepInfra"]
end
subgraph Traction["Traction: Split DB — 200 DAU — ~EUR 53/mo"]
P2_CF["Cloudflare"] --> P2_NGINX["Nginx"]
P2_NGINX --> P2_API["Node.js API + Workers"]
P2_API --> P2_PG["PostgreSQL (Separate VPS)"]
P2_API --> P2_REDIS["Redis"]
P2_API --> P2_AI["DeepSeek V3.2 via OpenRouter\n+ Claude Haiku fallback"]
end
subgraph Growth["Growth: Scale API — 1000 DAU — ~EUR 216/mo"]
P3_CF["Cloudflare"] --> P3_LB["Load Balancer"]
P3_LB --> P3_API1["API Replica 1"]
P3_LB --> P3_API2["API Replica 2"]
P3_API1 --> P3_PG["PostgreSQL (Dedicated)"]
P3_API2 --> P3_PG
P3_API1 --> P3_REDIS["Redis Cluster"]
P3_API2 --> P3_REDIS
P3_WORKER["Spectrum Workers"] --> P3_PG
P3_API1 --> P3_AI["OpenRouter AI Gateway\n(DeepSeek + Claude fallback)"]
P3_API2 --> P3_AI
end
Launch -->|"p95 latency > 120ms\nor storage > 70%"| Traction
Traction -->|"CPU > 70% sustained\nor p95 > SLO"| Growth
```

View File

@@ -0,0 +1,972 @@
# Kalei — Claude Code Build Framework
Version: 2.0
Date: 2026-02-20
Status: Implementation-ready technical framework
Author: Matt + Claude
This document is the operational build bible for constructing Kalei from zero to production using Claude Code as the primary development tool. It translates all existing architecture, design, and phase documents into a practical, session-by-session execution framework.
This is not a summary of what exists. This is the missing piece: how you actually sit down and build it.
---
## 1. How This Framework Works
Every section in this document maps to a concrete Claude Code session. Each session has a clear input (what you tell Claude Code), a clear output (what gets committed), and a definition of done (how you know it worked). You work through them in order. If a session fails validation, you fix it before moving on.
The framework is organized into build tracks that run roughly in sequence but with deliberate parallelism where safe. Each track contains numbered sessions. Each session is designed to be completable in a single focused Claude Code interaction (30-90 minutes of wall time).
### 1.1 Session Anatomy
Every session follows this structure:
1. Context: what Claude Code needs to know before starting.
2. Prompt: what you paste or describe to Claude Code.
3. Deliverables: what files get created or modified.
4. Validation: how you confirm it works.
5. Commit: what the git message should look like.
### 1.2 Working Principles
These rules apply to every session:
- Never skip validation. A session is not done until validation passes.
- Commit after every successful session. Small, clean commits.
- If Claude Code generates something that does not match the architecture docs, the architecture docs win.
- Every AI call goes through the AI gateway. No direct provider SDK imports in feature code. Ever.
- Every user-facing AI path goes through the safety gate first. No exceptions.
- TypeScript strict mode everywhere. No `any` unless explicitly justified.
- Tests are not optional. Every endpoint gets at least one happy-path and one failure-path test.
---
## 2. Toolchain Decisions (Locked)
These are final. Do not revisit during build.
### Backend
| Layer | Tool | Version Target | Why |
|---|---|---|---|
| Language | TypeScript | 5.x strict | Type safety across full stack, shared types |
| Runtime | Node.js | 22 LTS | Current LTS, native fetch, native test runner fallback |
| API framework | Fastify | 5.x | Fastest Node framework, plugin encapsulation model maps perfectly to our modular monolith. Note: v5 prohibits decorating request/reply with reference types — use onRequest hooks or getter decorators instead. |
| ORM / query | Drizzle ORM | Latest stable | Type-safe schemas as code, zero-overhead SQL, migration generation via drizzle-kit. Use prepared statements for hot-path queries (auth lookups, usage checks). |
| Database | PostgreSQL | 16 | JSONB, RLS, pgcrypto, mature ecosystem |
| Cache / counters | Redis | 7.x | Rate limiting, usage counters, session cache, idempotency store |
| AI SDK | openai (OpenAI-compatible SDK) | Latest | OpenRouter uses OpenAI-compatible API. Native streaming, structured output. Single SDK works for DeepSeek V3.2 (primary) and Claude Haiku (fallback) via OpenRouter gateway. |
| Password hashing | @node-rs/argon2 | Latest | Argon2id via native Rust binding, fastest safe option |
| JWT | jose | Latest | Standards-compliant, no native deps, supports all JWT operations |
### Mobile
| Layer | Tool | Version Target | Why |
|---|---|---|---|
| Framework | React Native + Expo | SDK 54+ (New Architecture enabled by default) | Fabric renderer + TurboModules = 60fps UI, precompiled iOS builds (10x faster), EAS builds + OTA updates |
| Navigation | Expo Router | v4+ | File-system routing, typed routes, deep linking, shared element transitions |
| Server state | TanStack Query (React Query) | v5 | Industry standard for data fetching in production React Native apps. Provides caching (staleTime/gcTime), background refetching, optimistic mutations, offline persistence via `networkMode: 'offlineFirst'`, and paused mutation resume. Replaces raw HTTP clients as the data layer. |
| Client state | Zustand | Latest | Minimal boilerplate, persist middleware with MMKV adapter for offline-first state |
| Local storage | react-native-mmkv | v4 | 30x faster than AsyncStorage, built-in AES encryption, synchronous reads. Used for auth tokens, user preferences, offline cache. Premium apps universally use MMKV over AsyncStorage. |
| Animations | react-native-reanimated | v4+ | UI-thread animations via shared values + useAnimatedStyle. Spring physics, layout animations, entering/exiting transitions, shared element transitions. Required for premium-feeling interactions. |
| Gestures | react-native-gesture-handler | Latest | Native gesture system — pan, pinch, rotation, tap. Composes with Reanimated for gesture-driven animations (swipe-to-dismiss, pull-to-refresh, card interactions). |
| Haptics | expo-haptics | Latest | Tactile feedback on key interactions: Turn save (success notification), action completion (light impact), fragment tap (selection), onboarding progression (soft impact). Standard in premium wellness apps. |
| Images | expo-image | Latest | Faster than React Native Image, aggressive caching, blurhash placeholders, animated image support. |
| Lists | @shopify/flash-list | Latest | 60fps scrolling for long lists, view recycling, superior to FlatList. Used for Gallery timeline, Turn history, and any scrollable content with 50+ items. |
### Shared / Tooling
| Layer | Tool | Version Target | Why |
|---|---|---|---|
| Validation | Zod | 3.x | Shared schemas between API and mobile, runtime + compile-time safety |
| Linting | Biome | Latest | Single tool for lint + format, faster than ESLint + Prettier combined |
| Unit testing | Vitest | Latest | Vite-powered, native TypeScript, compatible with Fastify test patterns |
| E2E testing | Maestro | Latest | Industry standard for mobile E2E testing. YAML-based flow definitions, visual regression, CI-friendly. Tests critical paths: onboarding, Turn flow, Mirror session, purchase. |
| CI | Gitea Actions (self-hosted, free) or GitHub Actions (2,000 free mins/month private, unlimited public) | N/A | Lint + test + type-check on every PR. Gitea is fully self-hosted on the VPS at zero cost. |
| Monorepo | pnpm workspaces | Native | Strict dependency resolution, disk-efficient, prevents phantom deps |
### 2.1 Package Manager
Use `pnpm` as the package manager. It is faster, more disk-efficient, and has strict dependency resolution that prevents phantom dependencies. Install globally:
```bash
npm install -g pnpm
```
### 2.2 What We Are NOT Using (And Why)
| Rejected | Reason |
|---|---|
| Express | Slower, no schema validation, no encapsulation model |
| Prisma | Heavy runtime, slower queries, migration lock-in |
| Supabase Cloud | 3x cost of self-hosting, unnecessary service overhead |
| RevenueCat | Third-party dependency in billing critical path |
| Redux / MobX | Overkill for this app's state complexity. Zustand + TanStack Query covers both client and server state |
| AsyncStorage | 30x slower than MMKV, no encryption, async-only reads. Not used in premium apps |
| Axios / ky / ofetch | TanStack Query is the data layer now. Raw HTTP clients are only used inside query/mutation functions, not directly in components. Use native fetch inside TanStack Query's queryFn. |
| React Navigation (standalone) | Expo Router wraps React Navigation with file-based routing — no reason to use React Navigation directly |
| Animated (built-in) | React Native's built-in Animated API runs on the JS thread. Reanimated runs on the UI thread — mandatory for 60fps premium animations. |
| Jest | Slower, heavier config, Vitest is drop-in compatible |
| ESLint + Prettier | Two tools where Biome does both faster |
| tRPC | Over-engineering for a mobile + API monorepo with Zod already shared |
| Detox | Slower, more complex setup than Maestro for E2E testing. Maestro's YAML flows are faster to write and maintain |
### 2.3 Zero-Cost Development Guarantee
Every tool and dependency in this stack can be used at zero cost during development. This section maps each component to its free path.
**All npm packages are free and open source.** Every library in the Backend, Mobile, and Shared toolchain tables is MIT, Apache 2.0, or BSD licensed. This includes Fastify, Drizzle ORM, TanStack Query, Zustand, MMKV, Reanimated, FlashList, Zod, Biome, Vitest, Pino, jose, @node-rs/argon2, and ioredis. Zero license costs, ever.
**Local infrastructure is free.** PostgreSQL and Redis run locally via Docker Compose (Docker Engine is free and open source). No cloud database or managed Redis required during development.
**Mobile builds are free locally.** Use `npx expo run:ios` and `npx expo run:android` for development builds — these compile using your local Xcode and Android Studio (both free). For production builds, use `eas build --local --platform ios` and `eas build --local --platform android` to build .ipa and .aab files on your machine at zero cost. EAS Cloud Build's free tier (30 builds/month) is available but optional. OTA updates via EAS Update are free for up to 1,000 active users.
**AI API costs are zero during development.** The mock AI provider (`mock.provider.ts`) returns deterministic, schema-valid responses for all unit and integration tests. You never hit real AI APIs during normal development. For manual testing against real AI, use OpenRouter credits — DeepSeek V3.2 via DeepInfra is so inexpensive ($0.26/$0.38 per MTok) that even extensive manual testing costs pennies. During development, you will spend effectively $0 on AI.
**Testing is free.** Vitest (MIT) for unit/integration tests. Maestro CLI (Apache 2.0) runs locally against simulators/emulators at no cost — do not use Maestro Cloud. k6 (Apache 2.0) for load testing runs locally.
**Error tracking is free.** GlitchTip is self-hosted via Docker Compose on your dev machine (or later on the VPS). It is Sentry-compatible and 100% open source. No SaaS subscription needed.
**Analytics is free.** PostHog can be self-hosted via Docker for up to ~100k events/month, or use their cloud free tier (1M events/month, no credit card required). For development, self-hosted PostHog in Docker Compose alongside Postgres and Redis costs nothing.
**Push notifications are free.** Expo Push Notification API is free with a 600 notifications/second/project rate limit. No paid tier required.
**CI/CD is free.** Gitea Actions is self-hosted and free. GitHub Actions offers 2,000 minutes/month free for private repos, unlimited for public repos.
**SSL/HTTPS is free.** Caddy auto-provisions Let's Encrypt certificates. Let's Encrypt is a free certificate authority.
**Costs that only apply at launch (not during development):**
| Item | Cost | When |
|---|---|---|
| Netcup VPS 1000 G12 | €8.45/month | Production deployment |
| Apple Developer Program | $99/year | App Store submission |
| Google Play Developer | $25 one-time | Play Store submission |
| OpenRouter API — DeepSeek V3.2 via DeepInfra (production traffic) | ~$2-8/month at launch scale | Live users hitting AI endpoints |
| Domain name | ~$10-15/year | Production domain |
Total development cost: **$0.** Total launch cost: approximately **€16/month** + one-time store fees.
---
## 3. Repository Architecture
This is the exact folder structure. Claude Code should scaffold this in the first session.
```
kalei/
package.json # Workspace root
pnpm-workspace.yaml # Workspace config
tsconfig.base.json # Shared TS config
biome.json # Lint + format config
.env.example # Root env template
.gitignore
apps/
mobile/ # Expo React Native app
app/ # Expo Router file-based routes
(tabs)/ # Tab navigator group
_layout.tsx # Tab layout with 5 tabs
index.tsx # Home / daily focus
mirror.tsx # Mirror entry
turn.tsx # Kaleidoscope entry
lens.tsx # Lens goals
gallery.tsx # Gallery / history
(auth)/ # Auth flow group
_layout.tsx
login.tsx
register.tsx
onboarding.tsx
(spectrum)/ # Spectrum (ships in v1)
_layout.tsx
dashboard.tsx
weekly.tsx
monthly.tsx
_layout.tsx # Root layout (auth gate)
src/
api/ # API layer
client.ts # Base fetch wrapper with auth header injection
query-client.ts # TanStack Query client config (staleTime, gcTime, offline persistence)
queries/ # TanStack Query hooks organized by feature
use-auth.ts # useLogin, useRegister, useRefresh mutations
use-mirror.ts # useMirrorSession, useSendMessage, useReframe queries/mutations
use-turn.ts # useCreateTurn, useTurns, useSaveTurn
use-lens.ts # useGoals, useCreateGoal, useCompleteAction, useDailyAffirmation
use-spectrum.ts # useWeeklyInsight, useMonthlyInsight
use-billing.ts # useEntitlements
keys.ts # Query key factory for organized cache invalidation
stores/ # Zustand stores (client state only — NOT server state)
auth.store.ts # Token state, persisted to MMKV
ui.store.ts # UI preferences, theme mode, onboarding state
offline-queue.store.ts # Queued mutations for offline-first
lib/ # Core utilities
storage.ts # MMKV instance setup with encryption
haptics.ts # Centralized haptic feedback helpers (tapHaptic, successHaptic, etc.)
analytics.ts # PostHog event tracking (self-hosted or cloud free tier — 1M events/month free)
components/ # Reusable UI components
ui/ # Primitives (Button, Card, Input, TextArea, etc.)
animations/ # Reanimated animation components (FadeIn, SlideUp, PulseGlow, KaleidoscopeSpinner)
mirror/ # Mirror-specific components
turn/ # Turn-specific components
lens/ # Lens-specific components
spectrum/ # Spectrum-specific components
hooks/ # Custom React hooks
use-keyboard-aware.ts # Keyboard avoidance for writing screens
use-network-state.ts # Online/offline detection
use-app-state.ts # Foreground/background lifecycle
utils/ # Helpers, formatters, constants
theme/ # Colors, typography, spacing tokens, dark mode support
types/ # Mobile-specific types
assets/ # Images, fonts, Lottie animations
e2e/ # Maestro E2E test flows
onboarding.yaml
turn-flow.yaml
mirror-session.yaml
purchase-flow.yaml
app.config.ts # Expo config (newArchEnabled: true is default in SDK 54)
package.json
tsconfig.json
.env.example
services/
api/ # Fastify API server
src/
server.ts # Fastify app factory
config.ts # Environment config with Zod validation
modules/ # Feature modules (Fastify plugins)
auth/
auth.routes.ts
auth.service.ts
auth.schemas.ts
auth.test.ts
mirror/
mirror.routes.ts
mirror.service.ts
mirror.schemas.ts
mirror.test.ts
turn/
turn.routes.ts
turn.service.ts
turn.schemas.ts
turn.test.ts
lens/
lens.routes.ts
lens.service.ts
lens.schemas.ts
lens.test.ts
spectrum/
spectrum.routes.ts
spectrum.service.ts
spectrum.schemas.ts
spectrum.test.ts
billing/
billing.routes.ts
billing.service.ts
billing.schemas.ts
billing.test.ts
plugins/ # Cross-cutting Fastify plugins
auth.plugin.ts # JWT verification decorator
entitlement.plugin.ts # Plan gating middleware
rate-limit.plugin.ts # Redis-backed rate limiting
request-id.plugin.ts # Request ID injection
error-handler.plugin.ts
gateway/ # AI Gateway abstraction
ai-gateway.ts # Main gateway interface
providers/
openrouter.provider.ts # Primary: DeepSeek V3.2 via DeepInfra/Fireworks, Fallback: Claude Haiku
prompts/
mirror-detect.prompt.ts
mirror-reframe.prompt.ts
turn-reframe.prompt.ts
lens-affirmation.prompt.ts
spectrum-weekly.prompt.ts
spectrum-monthly.prompt.ts
output-schemas/ # Zod schemas for AI output validation
mirror-fragments.schema.ts
turn-perspectives.schema.ts
lens-output.schema.ts
safety/ # Safety service
safety.service.ts # Multi-stage crisis filter
crisis-keywords.ts # Deterministic keyword sets
crisis-resources.ts # Regional crisis hotline data
safety.test.ts
db/ # Database layer
client.ts # Drizzle client setup
schema/ # Drizzle schema definitions
users.ts
auth.ts
mirror.ts
turn.ts
lens.ts
spectrum.ts
billing.ts
safety.ts
usage.ts
index.ts # Re-exports all schemas
migrations/ # Generated by drizzle-kit
seed.ts # Dev seed data
redis/
client.ts # Redis connection
usage-counter.ts # Per-user usage tracking
rate-limiter.ts # Sliding window rate limiter
idempotency.ts # Idempotency key store
workers/ # Background job processors
spectrum-weekly.worker.ts
spectrum-monthly.worker.ts
push-notification.worker.ts
billing-reconciliation.worker.ts
utils/
logger.ts # Pino structured logger
crypto.ts # Column-level encryption helpers
errors.ts # Custom error classes
drizzle.config.ts # Drizzle Kit configuration
package.json
tsconfig.json
vitest.config.ts
.env.example
packages/
shared/ # Shared types and schemas
src/
schemas/ # Zod schemas shared between API and mobile
auth.schema.ts
mirror.schema.ts
turn.schema.ts
lens.schema.ts
spectrum.schema.ts
billing.schema.ts
types/ # TypeScript type exports
index.ts
constants/ # Shared constants
plans.ts # Plan definitions and limits
cognitive-distortions.ts # Fragment type taxonomy
crisis-patterns.ts # Shared crisis detection patterns
package.json
tsconfig.json
infra/
docker/
docker-compose.yml # Postgres + Redis for local dev
docker-compose.prod.yml # Production compose (VPS deployment)
scripts/
setup-local.sh # One-command local environment setup
deploy.sh # Production deployment script
backup-db.sh # PostgreSQL backup script
restore-db.sh # PostgreSQL restore script
nginx/
kalei.conf # Nginx reverse proxy config
caddy/
Caddyfile # Alternative: Caddy auto-HTTPS config
docs/ # Existing documentation (unchanged)
```
### 3.1 Why This Structure
**Backend: Fastify plugin encapsulation.** Each module in `services/api/src/modules/` is a self-contained Fastify plugin that registers its own routes, schemas, and services. Fastify v5 enforces this cleanly — you cannot decorate request/reply with reference types directly (use onRequest hooks or getter decorators instead). Each module can be tested in isolation, and extraction to a separate service later is a matter of moving the plugin to its own process.
**Mobile: Separation of server state and client state.** This is the pattern every premium React Native app follows in 2026. TanStack Query owns all server state (API data, caching, background refetching, optimistic updates). Zustand owns only client state (auth tokens, UI preferences, offline queue). Components never call fetch directly — they use query hooks from `src/api/queries/`. This separation makes offline-first behavior trivial: TanStack Query's `networkMode: 'offlineFirst'` plus MMKV-backed Zustand persistence means the app works without network.
**Shared schemas.** The `packages/shared` workspace ensures that Zod schemas defined once are used identically for API request validation, TanStack Query response typing, and mobile form validation. When the API says a turn response has exactly 3 perspectives, the mobile app knows this at compile time.
**AI gateway isolation.** The `gateway/` directory is intentionally separate from `modules/`. Feature code calls the gateway; the gateway calls providers. Feature code never imports provider SDKs. This is the single most important architectural boundary in the entire codebase.
### 3.2 Premium UX Architecture Principles
These patterns separate a forgettable app from a premium one:
**60fps everywhere.** All animations run on the UI thread via Reanimated shared values. Never animate with React state or the built-in Animated API. Screen transitions use Reanimated's layout animations (FadeIn, SlideInRight). The Turn kaleidoscope animation uses withRepeat + withSpring for organic-feeling motion.
**Haptic vocabulary.** Define a consistent haptic language: `Haptics.selectionAsync()` for tapping fragments, `Haptics.impactAsync(Light)` for completing actions, `Haptics.notificationAsync(Success)` for saving turns, `Haptics.impactAsync(Soft)` for onboarding progression. Centralize in `src/lib/haptics.ts` so the haptic vocabulary stays consistent.
**Offline-first by default.** Every screen must work without network. MMKV persists Zustand state synchronously. TanStack Query caches API responses with configurable stale times. Mutations queue locally and resume when connectivity returns (via `resumePausedMutations()`). The Mirror writing experience must never be interrupted by network issues.
**Optimistic UI.** When the user saves a Turn, the UI updates immediately via TanStack Query's optimistic mutation pattern — the save icon fills instantly, and the server sync happens in the background. If it fails, it rolls back. The user never waits.
**Image optimization.** Use `expo-image` with blurhash placeholders for any loaded images. Profile avatars, onboarding illustrations, and Spectrum chart images all load with a color-blur placeholder that resolves to the full image.
**Accessibility from day one.** All interactive elements have accessibility labels. The Mirror writing area supports dynamic type sizes. Color contrast meets WCAG AA. VoiceOver/TalkBack navigation works for all core flows.
---
## 4. Build Track A — Foundation (Weeks 1-2)
Duration: 3-5 days
Goal: Everything runs. Nothing is broken. You can develop features.
### Session A1: Repository Scaffold
Context: Starting from empty repo.
Prompt to Claude Code:
> Initialize a pnpm monorepo with three workspaces: apps/mobile, services/api, and packages/shared. Set up tsconfig.base.json with strict mode. Add biome.json with reasonable defaults. Add .gitignore covering node_modules, .env, dist, .expo, and drizzle migration artifacts. Create package.json files for each workspace. The API workspace should use Fastify 5, Drizzle ORM with postgres-js driver, zod, pino, ioredis, jose, and @node-rs/argon2. The mobile workspace should use Expo SDK 54 with expo-router, and these critical dependencies: @tanstack/react-query for server state, zustand for client state, react-native-mmkv v4 for encrypted local storage (use createMMKV API), react-native-reanimated v4 for UI-thread animations, react-native-gesture-handler for native gestures, expo-haptics for tactile feedback, expo-image for optimized image loading, and zod for shared validation. The shared workspace should export Zod schemas and TypeScript types. Add a pnpm-workspace.yaml. Make the whole thing build and type-check cleanly.
Deliverables:
- All package.json files with correct dependencies
- tsconfig files with project references
- biome.json
- pnpm-workspace.yaml
- Clean `pnpm install` and `pnpm -r run type-check`
Validation:
```bash
pnpm install
pnpm -r run type-check # Zero errors
pnpm biome check . # Zero errors
```
### Session A2: Docker Infrastructure
Context: Repo scaffold exists.
Prompt to Claude Code:
> Create infra/docker/docker-compose.yml with PostgreSQL 16 and Redis 7. Postgres should use kalei/kalei credentials with a kalei database. Add health checks for both services. Create a root-level Makefile with targets: up (start docker), down (stop docker), reset-db (drop and recreate), and logs. Also create .env.example files for both API and mobile workspaces with all expected environment variables documented with comments.
Deliverables:
- docker-compose.yml with health checks
- Makefile
- .env.example files
Validation:
```bash
make up
docker compose -f infra/docker/docker-compose.yml ps # Both healthy
make down
```
### Session A3: API Server Skeleton
Context: Docker running, dependencies installed.
Prompt to Claude Code:
> Create the Fastify server factory in services/api/src/server.ts. It should: load config from environment using Zod validation (src/config.ts), register CORS, helmet, and sensible plugins, add a request ID plugin that generates UUIDs and injects them into pino logs, add a global error handler that logs errors and returns structured JSON errors, register a GET /health endpoint that checks Postgres and Redis connectivity and returns status/uptime/version. Add the database client (src/db/client.ts) using Drizzle ORM with postgres-js as the driver. Add the Redis client (src/redis/client.ts) using ioredis with reconnection strategy. Wire it all up in an index.ts that starts the server. Use Fastify's plugin encapsulation model — each capability should be a registered plugin.
Deliverables:
- server.ts (app factory)
- config.ts (Zod-validated env)
- db/client.ts (Drizzle + postgres-js)
- redis/client.ts (ioredis)
- plugins/request-id.plugin.ts
- plugins/error-handler.plugin.ts
- Health endpoint returning JSON
Validation:
```bash
make up
cd services/api && pnpm dev
curl http://localhost:8080/health # Returns JSON with status: ok
```
### Session A4: Mobile App Skeleton
Context: API server running.
Prompt to Claude Code:
> Create the Expo app in apps/mobile using Expo Router with file-based routing and the New Architecture (enabled by default in SDK 54). Set up the root layout (_layout.tsx) with: a TanStack Query provider (QueryClientProvider with defaults: staleTime 5 minutes, gcTime 10 minutes, networkMode offlineFirst), a GestureHandlerRootView wrapper, and an auth gate that checks for a stored token in MMKV. Set up MMKV storage in src/lib/storage.ts using createMMKV with encryption enabled. Set up the TanStack Query client in src/api/query-client.ts. Create a Zustand auth store in src/stores/auth.store.ts that persists tokens to MMKV (using zustand/middleware persist with a custom MMKV storage adapter). Create the (auth) group with login and register screens (just UI shells with Reanimated FadeIn entering animations). Create the (tabs) group with 5 tabs: Home, Mirror, Turn, Lens, Gallery. Each tab should show its name and a placeholder with a subtle FadeIn.duration(300) entering animation. Set up the theme directory with Kalei's color tokens (primary deep teal #1A3A3A, accent amber #D4A574, background warm off-white #FAF8F5, text near-black #1A1A1A) and typography scale. Create a TanStack Query hook in src/api/queries/use-health.ts that fetches /health. Create src/lib/haptics.ts with centralized haptic helpers (tapHaptic, successHaptic, errorHaptic, selectionHaptic) wrapping expo-haptics. Display the health status on the Home tab using the useHealth query hook.
Deliverables:
- Expo Router file structure with all route groups
- Root layout with TanStack Query provider, GestureHandlerRootView, auth gate
- MMKV storage setup with encryption
- TanStack Query client with offline-first defaults
- Zustand auth store persisted to MMKV
- Haptics helper library
- 5 tab screens with Reanimated entering animations
- Theme tokens and typography scale
- useHealth query hook calling /health
- Query key factory in src/api/queries/keys.ts
Validation:
```bash
cd apps/mobile && npx expo start
# Open on device (not Expo Go — need dev build for MMKV/Reanimated native modules)
# See tabs with fade-in animations, see health status from API
# Kill app and reopen — auth state persists from MMKV
```
### Session A5: Database Schema v1
Context: API server and Drizzle client working.
Prompt to Claude Code:
> Create the full Phase 1 database schema using Drizzle ORM for PostgreSQL. Define all tables in separate files under services/api/src/db/schema/. Tables needed: users (id uuid, email, password_hash, display_name, role, created_at, updated_at), profiles (user_id FK, onboarding_complete, timezone, preferred_reframe_style, created_at, updated_at), auth_sessions (id uuid, user_id FK, device_info jsonb, ip_address, created_at, expires_at), refresh_tokens (id uuid, user_id FK, token_hash, session_id FK, created_at, expires_at, revoked_at), subscriptions (id uuid, user_id FK, plan enum free/prism/prism_plus, store enum apple/google, store_subscription_id, status enum, current_period_start, current_period_end, created_at, updated_at), entitlement_snapshots (id uuid, user_id FK, plan, features jsonb, valid_from, valid_until), turns (id uuid, user_id FK, input_text encrypted, perspectives jsonb, micro_action text, reframe_style, saved boolean, created_at), mirror_sessions (id uuid, user_id FK, status enum, started_at, ended_at, reflection_text), mirror_messages (id uuid, session_id FK, user_id FK, content encrypted, sequence_number, created_at), mirror_fragments (id uuid, message_id FK, user_id FK, fragment_type, start_offset, end_offset, confidence, reframe_text, created_at), lens_goals (id uuid, user_id FK, title, description, status, created_at, updated_at), lens_actions (id uuid, goal_id FK, user_id FK, title, completed boolean, completed_at, due_date, created_at), ai_usage_events (id uuid, user_id FK, feature, model, provider, input_tokens, output_tokens, cost_usd, latency_ms, created_at), safety_events (id uuid, user_id FK, trigger_type, trigger_content_hash, action_taken, created_at). Use proper indexes on user_id and created_at for all tables. Use Drizzle relations for type-safe joins. Create an index.ts that re-exports everything. Then configure drizzle.config.ts and generate the initial migration with drizzle-kit generate.
Deliverables:
- Schema files for all tables
- Drizzle relations defined
- drizzle.config.ts
- Generated SQL migration
- Clean migration apply
Validation:
```bash
cd services/api
pnpm drizzle-kit generate # Generates migration SQL
pnpm drizzle-kit migrate # Applies to local Postgres
pnpm drizzle-kit studio # Opens Drizzle Studio, verify all tables exist
```
### Session A6: Shared Schemas and Quality Gates
Context: Schema exists, both apps scaffold complete.
Prompt to Claude Code:
> Create the shared Zod schemas in packages/shared/src/schemas/ for all API request/response contracts. Auth schemas: RegisterInput, LoginInput, TokenResponse, ProfileResponse. Mirror schemas: CreateSessionInput, SendMessageInput, FragmentResponse, ReframeResponse, ReflectionResponse. Turn schemas: CreateTurnInput (input_text, reframe_style enum), TurnResponse (3 perspectives array + micro_action). Lens schemas: CreateGoalInput, GoalResponse, CreateActionInput, DailyAffirmationResponse. Also create packages/shared/src/constants/plans.ts with plan definitions including feature limits (free: 3 turns/day, 2 mirror/week; prism: unlimited; prism_plus: unlimited + spectrum). Add packages/shared/src/constants/cognitive-distortions.ts with the 10 cognitive distortion types used for fragment detection. Then set up Vitest config for the API workspace with a test that imports the shared schemas. Set up Biome and add scripts to root package.json: lint, format, test, type-check, e2e (runs `maestro test apps/mobile/e2e/`). Add a CI-ready check script that runs lint + format + type-check + test (unit/integration). E2E tests via Maestro run separately in the release pipeline since they require a running device/emulator.
Deliverables:
- All shared Zod schemas
- Plan constants with limits
- Cognitive distortion taxonomy
- Vitest configuration
- Root-level quality scripts
- At least one passing test
Validation:
```bash
pnpm run check # Runs lint + format + type-check + test — all pass
```
---
## 5. Build Track B — Platform Core (Weeks 1-3)
Duration: 2-3 weeks
Goal: Auth, entitlements, AI gateway, safety, and rate limiting are production-grade.
### Session B1: Auth — Registration and Login
Prompt to Claude Code:
> Implement the auth module in services/api/src/modules/auth/. Create auth.routes.ts as a Fastify plugin that registers POST /auth/register and POST /auth/login. Registration should: validate input with shared Zod schema, check email uniqueness, hash password with Argon2id, create user + profile, generate JWT access token (15 min TTL) and refresh token (7 day TTL), store refresh token hash in DB, return both tokens. Login should: validate credentials, verify password, create new session with device info from user-agent, generate token pair, return tokens. Use jose for JWT signing with RS256 or HS256 based on config. Create auth.service.ts for business logic and use Drizzle for data access. Add auth.schemas.ts importing from shared schemas. Write tests for: successful registration, duplicate email rejection, successful login, wrong password rejection.
### Session B2: Auth — Token Refresh and Sessions
Prompt to Claude Code:
> Add POST /auth/refresh and POST /auth/logout to the auth module. Refresh should: accept refresh token, verify it exists and is not expired or revoked, rotate it (revoke old, issue new pair), return new tokens. This implements refresh token rotation — if a revoked token is reused, revoke ALL tokens for that user (compromise detection). Logout should: revoke the current refresh token and mark the session as ended. Add GET /me that returns the current user profile. Add PATCH /me/profile for updating display_name, timezone, and preferred_reframe_style. Create the auth.plugin.ts in plugins/ that adds a Fastify decorator `authenticate` — a preHandler hook that verifies the JWT access token, extracts user ID, and decorates the request with `request.user`. Test: full token lifecycle (register, use access token, refresh, use new token, logout, verify old refresh fails).
### Session B3: Entitlement and Plan Gating
Prompt to Claude Code:
> Implement the entitlement system. Create plugins/entitlement.plugin.ts as a Fastify plugin that adds a `requirePlan` decorator. This decorator takes a minimum plan level and returns a preHandler that: loads the user's current entitlement snapshot, checks if their plan meets the minimum, returns 403 with a clear upgrade message if not. Also implement feature-specific gates: a `checkTurnLimit` preHandler that reads today's turn count from Redis and enforces the daily cap for free users (3/day), and a `checkMirrorLimit` preHandler that reads this week's mirror session count and enforces the weekly cap for free users (2/week). Create billing.routes.ts with webhook endpoints for Apple and Google (POST /billing/webhooks/apple and /billing/webhooks/google) — these should parse notification payloads, update subscriptions table, and write new entitlement snapshots. For now, implement the webhook signature verification as a placeholder. Add GET /billing/entitlements that returns the user's current plan and feature limits with usage counts. Test: free user hitting turn limit, prism user bypassing limit, entitlement check after plan change.
### Session B4: AI Gateway — Core Abstraction
Prompt to Claude Code:
> Build the AI gateway in services/api/src/gateway/. Create ai-gateway.ts with a TypeScript interface: AIGateway with methods `generate(request: AIRequest): Promise<AIResponse>` and `stream(request: AIRequest): AsyncGenerator<AIChunk>`. The AIRequest type should include: feature (mirror_detect | mirror_reframe | turn_reframe | lens_affirmation | spectrum_weekly | spectrum_monthly), messages array, model override (optional), temperature, max_tokens, output_schema (Zod schema for structured output validation). Create providers/anthropic.provider.ts that implements this interface using @anthropic-ai/sdk. It should: use prompt caching by marking system message content blocks with `cache_control: { type: "ephemeral" }` (this tells Anthropic to cache the system prompt — subsequent calls within a 5-minute window pay only 10% of base input rate, saving 40-50% on input costs), support streaming via the SDK's stream method, extract token usage from response metadata, validate output against the provided Zod schema and retry once on validation failure. Create providers/openai-compatible.provider.ts as the Venice/Groq/OpenRouter adapter using the OpenAI-compatible API format. The gateway factory should read config to determine which provider to use per feature, with fallback chains. Log every call to ai_usage_events with feature, model, provider, token counts, cost estimate, and latency. Test: mock provider returns valid structured output, mock provider returns invalid output and retry succeeds, token usage logging works.
### Session B5: AI Gateway — Prompt Templates
Prompt to Claude Code:
> Create all prompt templates in services/api/src/gateway/prompts/. Each template exports a function that takes context and returns the messages array for the AI gateway. Mirror fragment detection (mirror-detect.prompt.ts): system prompt instructs the model to analyze freeform writing and identify cognitive distortions from our taxonomy (all-or-nothing, catastrophizing, emotional reasoning, fortune telling, labeling, magnification, mental filtering, mind reading, overgeneralization, should statements). Output must be a JSON array of fragments with: type, start_offset, end_offset, confidence (0-1), and a brief explanation. Mirror inline reframe (mirror-reframe.prompt.ts): takes the original text and a specific fragment, generates a gentle compassionate reframe (2-3 sentences). Turn reframe (turn-reframe.prompt.ts): system prompt generates exactly 3 perspective reframes (compassionate, rational, growth-oriented) plus one micro-action as an if-then implementation intention. Output is strictly structured JSON. Lens affirmation (lens-affirmation.prompt.ts): takes user's active goals and generates a personalized daily affirmation. Create corresponding output validation schemas in output-schemas/ using Zod. Each prompt template should include a version string (e.g., "mirror-detect-v1") for tracking prompt revisions. Test: each template produces valid messages arrays, output schemas validate correct and reject incorrect shapes.
### Session B6: Safety Service
Prompt to Claude Code:
> Build the safety service in services/api/src/safety/. Create crisis-keywords.ts with deterministic keyword and phrase sets for crisis detection: explicit self-harm language, suicidal ideation phrases, immediate danger phrases, and severe distress indicators. These must be comprehensive but avoid false positives on common expressions. Create safety.service.ts with a multi-stage crisis filter: Stage 1 is deterministic keyword/regex matching (instant, no AI call). If Stage 1 flags, immediately return crisis response — no further processing. Stage 2 (optional, for ambiguous cases): send flagged text to AI gateway for confirmation with a safety-specific prompt that returns a confidence score. If confirmed, return crisis response. Create crisis-resources.ts with structured data for crisis hotlines by region (start with US: 988 Suicide and Crisis Lifeline, Crisis Text Line). The crisis response payload should include: is_crisis boolean, resources array with name/phone/text/url per resource, a compassionate message, and explicit instruction that the content was NOT reframed. Wire the safety service as a preHandler on all AI-facing routes (mirror messages, turn creation, lens if accepting user input). Log every safety event to safety_events table. Test: known crisis phrases trigger immediate response, safe text passes through, ambiguous text reaches Stage 2, crisis response never contains reframed content.
### Session B7: Rate Limiting and Usage Metering
Prompt to Claude Code:
> Build Redis-backed rate limiting and usage tracking. Create redis/rate-limiter.ts implementing a sliding window rate limiter. It should support: per-IP rate limiting (general API protection, 100 req/min), per-user rate limiting (feature-specific, configurable), and burst allowance. Create redis/usage-counter.ts for tracking: daily turn count per user, weekly mirror session count per user, monthly token usage per user. Create redis/idempotency.ts that stores idempotency keys with TTL — if a request includes an X-Idempotency-Key header and we've seen it before, return the cached response. Create plugins/rate-limit.plugin.ts that registers the general rate limiter on all routes and exposes decorators for feature-specific limits. Create a usage cost tracking function that estimates USD cost from token counts based on current model pricing (configurable). Add middleware that records every AI-backed request to ai_usage_events. Test: rate limit kicks in at threshold, idempotency key returns cached response, usage counters increment correctly.
### Session B8: Push Notification Infrastructure
Prompt to Claude Code:
> Set up push notification infrastructure. Install expo-notifications in the mobile app and configure it to request permissions and register for push tokens on login. Create a notification service in the API (services/api/src/modules/notifications/) that: stores device push tokens per user session, sends push notifications via Expo Push API (which handles both APNs and FCM routing), supports notification types: daily_affirmation_reminder, mirror_nudge, weekly_spectrum_ready, turn_streak_reminder. Create a notification preferences table in the schema so users can toggle notification types on/off. Create a worker (workers/push-notification.worker.ts) that runs scheduled notifications: daily affirmation reminder at user's preferred time (default 8am local), weekly spectrum summary notification on Monday morning. For development, log notifications to console instead of sending. Test: push token registration, notification send via Expo Push API, user preference toggle, scheduled notification timing.
### Session B9: Observability Baseline
Prompt to Claude Code:
> Set up structured logging and error tracking. Configure Pino logger in utils/logger.ts with: JSON output in production, pretty output in development, request ID correlation, user ID hash (never log raw user ID), redaction of sensitive fields (authorization headers, passwords, tokens). Create a Fastify hook that logs request start (method, url, request_id) and response end (status, duration_ms, request_id). Add GlitchTip integration for error tracking — GlitchTip is free and open source, self-hosted via Docker Compose alongside our existing Postgres and Redis (add it to docker-compose.yml). Install @sentry/node (GlitchTip is Sentry-compatible, same SDK) and create an initialization plugin that points to the local GlitchTip instance. For development, GlitchTip runs on your machine; in production, it runs on the VPS. Zero SaaS cost. Create a /metrics endpoint that returns: request count by endpoint, error count by endpoint, AI token usage by feature (daily/monthly), AI cost by feature, active sessions count, rate limit denial count. For now this can be a simple JSON endpoint — we'll add Prometheus format later if needed. Test: verify request logs contain request_id, verify error tracking captures unhandled errors, verify metrics endpoint returns non-zero data after some requests.
---
## 6. Build Track C — Core Experience (Weeks 4-8)
Duration: 3-5 weeks
Goal: Users can complete the Mirror to Turn to Lens flow end-to-end.
### Session C1: Mirror — Session Lifecycle API
Prompt to Claude Code:
> Implement the Mirror module backend. POST /mirror/sessions creates a new session (status: active). POST /mirror/messages accepts message content, runs it through the safety gate, then through the AI gateway for fragment detection. The response includes the original message with an array of detected fragments (type, offsets, confidence). Each fragment above the confidence threshold (0.7) is stored in mirror_fragments. POST /mirror/fragments/:id/reframe triggers an inline reframe for a specific fragment — calls AI gateway with the original context and fragment, returns the reframed perspective. POST /mirror/sessions/:id/close ends the session and triggers a reflection generation — the AI summarizes the session's themes, patterns noticed, and a gentle closing thought. GET /mirror/sessions lists sessions with pagination. DELETE /mirror/sessions/:id soft-deletes a session. All endpoints enforce authentication, safety precheck, and entitlement limits. The message content should be encrypted at rest using the column-level encryption helper. Test: full session lifecycle, fragment detection returns valid offsets, reframe returns compassionate text, reflection summarizes themes, safety gate blocks crisis content.
### Session C2: Mirror — Mobile UI
Prompt to Claude Code:
> Build the Mirror screen in the mobile app as a premium writing experience. The compose UI should feel like a peaceful writing space — warm off-white background (#FAF8F5), gentle typography (system serif or Inter at 18px), generous padding, no distracting UI elements. Use keyboard-aware scrolling so the text input stays visible above the keyboard. As the user types and pauses (debounce 2 seconds after last keystroke), use a TanStack Query mutation (useSendMessage from src/api/queries/use-mirror.ts) to send content to the API for fragment detection. When fragments come back, render subtle highlights on the detected text — use a warm amber (#D4A574) underline with a Reanimated FadeIn.duration(400) animation, not aggressive red highlighting. Add Haptics.selectionAsync() when the user taps a highlighted fragment. Show a bottom sheet card (animated with Reanimated SlideInDown.springify()) with the fragment type name (e.g., "Catastrophizing"), a brief explanation, and a "See another perspective" button that triggers the reframe mutation. The reframe slides in as an animated card with FadeInUp. When the user closes the session, show the Reflection as a full-screen gentle summary with Reanimated FadeIn. All API calls should go through TanStack Query hooks — useMirrorSession, useSendMessage, useReframe, useCloseSession. Configure the useSendMessage mutation with optimistic updates so fragment highlights appear immediately from cached data while the server processes. Handle offline gracefully using TanStack Query's offline mutation persistence — queue messages locally in the mutation cache and resume when connectivity returns via resumePausedMutations.
### Session C3: Turn — Reframe Engine API
Prompt to Claude Code:
> Implement the Turn module backend. POST /turns accepts input_text and optional reframe_style (compassionate, rational, growth, or all). Runs safety precheck. Calls AI gateway with the turn-reframe prompt. Returns exactly 3 perspectives, each with: style label, reframed text (2-3 sentences), and a brief explanation of the cognitive shift. Also returns one micro_action as an if-then implementation intention (e.g., "If I notice myself catastrophizing about work, then I will take 3 breaths and name one thing I can control"). POST /turns/:id/save toggles the saved state. GET /turns returns paginated history. GET /turns/:id returns a single turn with full details. Support streaming for the reframe generation — the API should use Server-Sent Events so the mobile app can show perspectives appearing in real-time. Enforce daily turn limits for free users. Test: valid turn creates 3 perspectives, streaming delivers chunks, save toggle works, daily limit enforced, crisis content blocked.
### Session C4: Turn — Mobile UI
Prompt to Claude Code:
> Build the Turn (Kaleidoscope) screen as the premium signature experience. The input should be a focused single-purpose screen: a large text input with placeholder "What thought is weighing on you?" and a "Turn" button that pulses gently using Reanimated withRepeat(withTiming()) when the input has text. Add Haptics.impactAsync(Medium) when the Turn button is pressed. When processing, show a kaleidoscope animation built with Reanimated — rotate a geometric SVG pattern using useSharedValue + withRepeat(withSpring()) for organic, non-mechanical motion. This is the core product moment and must feel magical. As perspectives stream in via SSE, render them as cards that appear one at a time using Reanimated's layout animation FadeInDown.springify().delay(index * 200) — each card shows the perspective style icon, the reframed text, and the cognitive shift explanation. The micro-action appears last with a distinct card style and FadeInUp animation. The save button (bookmark icon) should use Reanimated withSpring for a satisfying scale bounce on tap, with Haptics.notificationAsync(Success) feedback. Use TanStack Query for all data: useCreateTurn mutation (with streaming SSE handling), useTurns query for history, useSaveTurn mutation with optimistic update (the save icon fills immediately, server syncs in background — rollback on failure). The Gallery tab should show saved turns in a timeline view using FlashList for performant scrolling. Implement pull-to-refresh with Reanimated-powered custom refresh indicator. All loading states must use skeleton placeholders with a Reanimated shimmer effect, never a spinner.
### Session C5: Lens — Goals and Actions API
Prompt to Claude Code:
> Implement the Lens module backend. POST /lens/goals creates a goal with title and optional description. GET /lens/goals returns active goals. PATCH /lens/goals/:id updates goal status. POST /lens/goals/:id/actions creates an action item. POST /lens/actions/:id/complete marks it done with timestamp. GET /lens/affirmation/today generates or returns cached daily affirmation — it calls the AI gateway with the user's active goals as context and generates a personalized affirmation. Cache the affirmation in Redis with a TTL that expires at midnight in the user's timezone. If the AI budget is constrained, fall back to a template-based affirmation system using pre-written affirmations matched to goal themes. Test: goal CRUD, action completion, affirmation generation, affirmation caching, template fallback.
### Session C6: Lens — Mobile UI
Prompt to Claude Code:
> Build the Lens screen as a calm, empowering daily direction hub. Top section shows the daily affirmation in a prominent card with gentle serif typography and a Reanimated FadeIn.delay(200).duration(600) entering animation — this card should feel like the first thing you notice. Use TanStack Query's useDailyAffirmation hook (from src/api/queries/use-lens.ts) with staleTime set to Infinity (the affirmation is cached for the full day). Below the affirmation, show active goals as expandable cards using Reanimated Layout animations (LayoutAnimation.springify()) for smooth expand/collapse. Each goal card shows its title and an animated circular progress indicator — use Reanimated useAnimatedProps to animate the SVG circle stroke-dashoffset as actions are completed. Tapping a goal expands it with a spring animation to show action items as a checklist. Completing an action triggers: Haptics.impactAsync(ImpactFeedbackStyle.Light), a Reanimated withSpring scale bounce on the checkbox (1 → 1.3 → 1), and an optimistic update via TanStack Query's useCompleteAction mutation (checkbox fills immediately, server syncs in background, rollback on failure). When all actions in a goal are complete, trigger Haptics.notificationAsync(NotificationFeedbackType.Success) and play a subtle confetti-like Reanimated animation. Add a floating action button with a Reanimated withSpring scale entrance animation. The create goal flow opens as a bottom sheet (animated with Reanimated SlideInDown.springify()) with title and optional description fields. All data flows through TanStack Query hooks: useGoals, useCreateGoal, useCompleteAction, useDailyAffirmation. Goals and actions persist offline via TanStack Query's cache + MMKV persistence — creating a goal while offline queues the mutation. Keep the UI minimal and focused — this is the "direction" pillar, it should feel empowering and clear, not like a corporate task manager.
### Session C7: Gallery and History Views
Prompt to Claude Code:
> Build the Gallery tab as a premium history experience using FlashList for buttery-smooth scrolling. Use @shopify/flash-list instead of FlatList — it recycles views for 60fps scrolling even with hundreds of items. The gallery shows a unified timeline with three content types: saved turns (with perspective previews), mirror session summaries (with fragment count and date), and lens achievements (completed actions and goals). Each type should have a distinct but cohesive card design using Reanimated entering animations — cards use FadeInUp.delay(index * 80) for staggered appearance as they scroll into view. Add filtering by content type and date range with an animated filter bar (Reanimated layout animations for smooth filter chip transitions). Add search across saved turn content and mirror reflections — the search bar slides in with Reanimated SlideInDown and uses debounced TanStack Query with the search term as a query key. For free users, gallery shows last 30 days; for prism users, full history. Implement infinite scroll with cursor-based pagination using TanStack Query's useInfiniteQuery — configure getNextPageParam for cursor-based pagination and flatMap pages for FlashList data. Add pull-to-refresh with a custom Reanimated-powered refresh indicator (not the default system spinner). Add a detail view that opens when tapping any gallery item with a shared element transition (Reanimated's SharedTransition) — the card expands into the full detail view. Turns show full perspectives, mirror sessions show the reflection, lens items show the goal with all actions. Add Haptics.selectionAsync() when tapping gallery items. All data through TanStack Query hooks: useGalleryItems (infinite query), useGallerySearch, useTurnDetail, useMirrorDetail. Stale time 2 minutes for gallery list, 5 minutes for detail views. Gallery works offline with cached data from TanStack Query's persisted cache.
### Session C8: Onboarding Flow
Prompt to Claude Code:
> Build the onboarding experience in the (auth) group as a premium first impression. After registration, guide the user through 3-4 screens using a horizontal pager with Reanimated-powered page transitions — each page slides in with a spring physics animation using withSpring({ damping: 20, stiffness: 90 }) for a soft, organic feel (not the stiff default transitions). Screen 1 explains the kaleidoscope metaphor — "Your thoughts are like light. Sometimes they get stuck in one pattern. Kalei helps you turn them and see new colors." Animate the kaleidoscope illustration with a gentle Reanimated rotation (withRepeat + withTiming over 8 seconds) so it feels alive. Screen 2 asks the user to set their preferred reframe style (compassionate, rational, growth) with brief descriptions of each — use Reanimated scale animations on the selection cards (withSpring scale 1 → 1.05 on selection) and Haptics.selectionAsync() on tap. Screen 3 invites the user to try their first Turn — embed a mini version of the Turn input with a pulsing "Turn" button (Reanimated withRepeat opacity animation). Screen 4 shows the result and introduces the daily rhythm (morning affirmation, journaling, reframing) — use staggered FadeInUp animations for each rhythm item. Add a progress indicator using Reanimated interpolation — animated dots that fill as the user progresses, with Haptics.impactAsync(ImpactFeedbackStyle.Soft) on each page transition. Store onboarding completion in the profile via a TanStack Query mutation (useCompleteOnboarding). Skip onboarding for returning users by checking the auth store's onboarding_complete flag (persisted in MMKV). The entire onboarding should feel like a guided meditation — slow, intentional, beautiful.
---
## 7. Build Track D — Launch Hardening (Weeks 9-10)
Duration: 2-4 weeks
Goal: Production-safe, store-ready, monitored.
### Session D1: Safety Hardening
> Expand crisis keyword sets with comprehensive coverage. Add regional crisis resources for all launch regions. Add a safety dashboard endpoint that returns: total safety events by type, false positive rate (manually tagged), response time percentiles. Add an admin-only endpoint to review and tag safety events for quality improvement. Verify that no code path can return reframed content when crisis is detected — write an integration test that traces every AI-facing route with crisis input and asserts the response is always the crisis resource payload.
### Session D2: Billing Integration — Apple
> Implement full App Store Server Notifications v2 handling. Verify notification signatures using Apple's public key. Parse all notification types: SUBSCRIBED, DID_RENEW, EXPIRED, DID_FAIL_TO_RENEW, REFUND, REVOKE, GRACE_PERIOD_EXPIRED. Update subscription status and entitlement snapshots accordingly. Implement a reconciliation job that periodically verifies subscription status directly with Apple's API to catch any missed webhooks. Test with Apple's sandbox environment.
### Session D3: Billing Integration — Google
> Implement Google Play Real-Time Developer Notifications. Verify Pub/Sub message authenticity. Handle all notification types: SUBSCRIPTION_RECOVERED, SUBSCRIPTION_RENEWED, SUBSCRIPTION_CANCELED, SUBSCRIPTION_PURCHASED, SUBSCRIPTION_ON_HOLD, SUBSCRIPTION_IN_GRACE_PERIOD, SUBSCRIPTION_EXPIRED, SUBSCRIPTION_REVOKED. Update subscription and entitlement records. Add reconciliation job for Google Play API verification.
### Session D4: Security Audit Pass
> Review and harden: verify all endpoints require authentication (except /health, /auth/register, /auth/login, /auth/refresh, billing webhooks). Verify password hashing uses Argon2id with secure parameters. Verify JWT tokens use appropriate algorithms and key sizes. Verify refresh token rotation and compromise detection work. Add secure HTTP headers (HSTS, CSP, X-Frame-Options). Verify no PII in logs. Verify column-level encryption on mirror message content and turn input text. Run a dependency audit (pnpm audit). Add rate limiting on auth endpoints (5 attempts per minute per IP for login). Add PostgreSQL Row-Level Security (RLS) policies as defense-in-depth: enable RLS on all user-data tables, create policies that restrict SELECT/UPDATE/DELETE to rows where user_id matches the authenticated user's ID from the JWT claim. This ensures that even if application-level ownership checks are bypassed, the database itself prevents cross-user data access. Create a migration that enables RLS and adds the policies. Test: verify a query with the wrong user_id returns zero rows when RLS is active.
### Session D5: Performance and Load Testing
> Create a load test script using k6 or autocannon that simulates: 50 concurrent users performing the Turn flow (the most AI-intensive path), 20 concurrent Mirror sessions with message submission, 100 concurrent health checks. Measure: p50/p95/p99 latency for each endpoint, error rate under load, AI gateway response time distribution, database query time distribution. Identify and fix any bottlenecks. Target: p95 under 3.5s for AI-backed endpoints, p95 under 200ms for non-AI endpoints. Additionally, run the full Maestro E2E suite (apps/mobile/e2e/) on both iOS simulator and Android emulator to verify all critical flows pass under realistic conditions. Measure: screen transition times (target under 300ms), animation frame drops (target zero dropped frames in Reanimated animations), TanStack Query cache hit rates, and offline-to-online mutation resume success rate.
### Session D6: Deployment Pipeline
> Create the production deployment setup. Docker Compose for the VPS (Fastify API, Postgres 16, Redis 7, Caddy reverse proxy with auto-HTTPS via Let's Encrypt — all free). Create deploy.sh that: builds the API, runs migrations, restarts the service with zero-downtime (using Fastify's graceful shutdown). Create backup-db.sh for automated PostgreSQL backups. Set up the mobile build pipeline using local builds as the primary path: configure eas.json with development, preview, and production profiles, then build locally using `eas build --local --platform ios --profile production` and `eas build --local --platform android --profile production`. This avoids EAS Cloud Build costs entirely — builds run on your machine using Xcode and Android Studio (both free). For development builds during day-to-day work, use `npx expo run:ios` and `npx expo run:android` which compile directly against local toolchains. EAS Cloud Build (30 free builds/month) is available as a convenience but not required. Create the app store metadata: screenshots, description, privacy policy URL, support URL.
### Session D7: Beta Testing Pipeline
> Configure TestFlight for iOS internal testing and Google Play internal testing track. Set up EAS Update for over-the-air updates during beta. Create a beta feedback mechanism — either in-app feedback button or a simple form. Add Maestro E2E tests to the release gate: configure the CI pipeline so that all Maestro flows (onboarding, turn-flow, mirror-session, purchase-flow) must pass on both iOS and Android before a TestFlight or Play Store upload proceeds. Run through the complete user journey manually: register, onboard, complete a Mirror session, do a Turn, set a Lens goal, complete an action, view Gallery. Verify that offline-first behavior works: enable airplane mode, create a turn, re-enable network, verify the turn syncs. Verify haptic feedback fires on all premium touch points (fragment tap, turn save, action complete, onboarding page transition). Document any issues. Fix launch-blocking bugs.
---
## 8. Build Track E — Spectrum Intelligence (Weeks 11-12)
Duration: 3-6 weeks
Goal: Weekly and monthly AI-powered insights from accumulated usage data.
### Session E1: Spectrum Schema and Data Pipeline
> Create Drizzle schema for spectrum tables: spectrum_session_analysis (session_id, emotional_vectors jsonb, fragment_distribution jsonb, created_at), spectrum_turn_analysis (turn_id, pre_emotional_state jsonb, post_emotional_state jsonb, impact_score float, created_at), spectrum_weekly (user_id, week_start, emotional_landscape jsonb, top_fragments jsonb, turn_impact_summary jsonb, rhythm_patterns jsonb, narrative text, created_at), spectrum_monthly (user_id, month_start, growth_trajectory jsonb, theme_evolution jsonb, narrative text, created_at). Add event hooks in Mirror and Turn services that write to spectrum analysis tables after each session/turn completes. Add exclusion flags so users can opt out specific sessions from analysis.
### Session E2: Aggregation Workers
> Build background workers for Spectrum aggregation. The weekly worker runs every Sunday night: loads all non-excluded sessions and turns from the past week, computes emotional vectors using the AI gateway (batch mode for cost savings), aggregates fragment distributions, calculates turn impact scores, generates a narrative summary of the week's patterns. The monthly worker runs on the 1st of each month: aggregates weekly data into monthly trends, identifies growth trajectory, generates a deeper narrative. Both workers must be idempotent (safe to re-run), use retry with backoff, and log failures to a dead-letter table. Use BullMQ or a simple PostgreSQL-based job queue for scheduling.
### Session E3: Spectrum API and Mobile Dashboard
> Create Spectrum API endpoints: GET /spectrum/weekly returns the latest weekly insight (or 404 if not enough data), GET /spectrum/monthly returns the latest monthly summary, POST /spectrum/reset clears all spectrum data for the user, POST /spectrum/exclusions manages session exclusions. Build the Spectrum mobile dashboard as the (spectrum) route group. The dashboard should show: an emotional landscape visualization (could be a simple radar chart or heatmap of the 6 emotional dimensions), fragment pattern distribution (which cognitive distortions appear most), turn impact summary (how much emotional shift the reframes produce), and the AI-generated narrative insight. Gate all Spectrum features behind the prism_plus entitlement.
---
## 9. AI Cost Control Strategy
This section defines the exact implementation of cost guardrails.
### 9.1 Budget Hierarchy
```
Global monthly cap ($50 Phase 1, scales with revenue)
└─ Per-feature daily budget
├─ Turn: 40% of daily budget (core product)
├─ Mirror: 35% of daily budget (core product)
├─ Lens: 10% of daily budget (can fall back to templates)
└─ Spectrum: 15% of daily budget (batch, can defer)
└─ Per-user daily token cap
├─ Free: 100k tokens/day
└─ Prism: 500k tokens/day
```
### 9.2 Degradation Order
When budget pressure hits (80% of daily budget consumed):
1. Lens affirmations switch to template-based (no AI cost).
2. Spectrum narrative generation deferred to next day.
3. Mirror fragment detection reduces max fragments per message from 10 to 5.
4. Turn reframes reduce from 3 perspectives to 2.
5. If 95% consumed: all non-critical AI paused, only safety detection continues.
### 9.3 Infrastructure Scaling Tiers
The build timeline is sequential but culminates in a single v1 launch. Infrastructure scales in tiers:
| Tier | DAU | Infrastructure | Key Milestone |
|------|-----|-----------------|-----------|
| **Launch** | 50 | Single VPS, API + DB + Redis | Initial release |
| **Traction** | 200 | Split DB, keep API monolith | First paid subscribers |
| **Growth** | 1K | Separate workers, scale API replicas | Growing active base |
| **Scale** | 10K+ | Full microservice extraction | Mature platform |
### 9.3 Provider Routing
The AI gateway routes all features through OpenRouter with provider pinning:
| Feature | Primary Provider | Fallback (provider outage) | Cost Pressure Fallback |
|---|---|---|---|
| Turn reframe | DeepSeek V3.2 via DeepInfra | DeepSeek V3.2 via Fireworks | Template system |
| Mirror detect | DeepSeek V3.2 via DeepInfra | DeepSeek V3.2 via Fireworks | — (critical) |
| Mirror reframe | DeepSeek V3.2 via DeepInfra | DeepSeek V3.2 via Fireworks | Template system |
| Lens affirmation | DeepSeek V3.2 via DeepInfra | DeepSeek V3.2 via Fireworks | Template system (no AI) |
| Crisis detection | Keyword + DeepSeek V3.2 | Keyword + Claude Haiku 4.5 | Keywords only (never skip) |
| Guide coaching | DeepSeek V3.2 via DeepInfra | DeepSeek V3.2 via Fireworks | Deferred |
| Spectrum weekly | DeepSeek V3.2 via DeepInfra | Claude Haiku 4.5 | Deferred |
| Spectrum monthly | DeepSeek V3.2 via DeepInfra | Claude Haiku 4.5 | Deferred |
OpenRouter handles failover automatically via the `order` array in API requests. Provider pinning ensures no data flows through Chinese servers (DeepInfra/Fireworks host on US/EU infrastructure).
### 9.4 Prompt Caching Strategy
System prompts for each feature are designed to be identical across users and requests. With DeepInfra's prompt caching:
- System prompt tokens (600-800 per feature) are cached after first call.
- Cache hits receive ~20% discount on input pricing ($0.216/M vs $0.26/M).
- While less dramatic than Anthropic's 90% cache discount, the base pricing is already so low (~$0.26/M input, $0.38/M output) that effective per-user costs are minimal (~$0.034/month for free users).
- Implementation: OpenRouter's OpenAI-compatible API handles caching transparently at the provider level.
---
## 10. Testing Strategy
### 10.1 Test Pyramid
| Layer | Tool | What | Coverage Target |
|---|---|---|---|
| Unit | Vitest | Pure functions, schemas, validators, prompt builders | High (80%+) |
| Integration | Vitest + Supertest | API routes with real DB (test container) | All endpoints |
| Safety | Vitest | Crisis detection accuracy | 100% of known patterns |
| AI Output | Vitest | Schema validation of AI responses | All prompt templates |
| E2E (Mobile) | Maestro | Critical mobile user flows | All happy paths + key error paths |
| Load | k6 | Concurrency and latency under load | pre-launch only |
### 10.2 Test Database Strategy
Use a separate PostgreSQL database for tests. Before each test suite: run migrations on the test DB. After each test suite: truncate all tables. Never run tests against the development database.
### 10.3 AI Mock Strategy
For unit and integration tests, mock the AI gateway at the provider level. Create a `mock.provider.ts` that returns deterministic, schema-valid responses. This means tests never hit real AI APIs, are fast, free, and deterministic. This is the primary reason AI costs are zero during development — you develop and test against the mock, not real providers. Add a small set of "golden file" tests that run against the real AI provider (gated behind an environment variable `REAL_AI_TESTS=true`) to catch prompt regression. These golden file tests should only be run occasionally during prompt engineering, using OpenRouter credits (DeepSeek V3.2 via DeepInfra is so cheap at $0.26/$0.38 per MTok that golden file test costs are negligible).
### 10.4 Maestro E2E Testing Strategy
Maestro is the industry standard for mobile E2E testing in 2026. It uses YAML-based flow definitions that are faster to write and more readable than code-based alternatives.
**Flow files live in `apps/mobile/e2e/`:**
```yaml
# e2e/onboarding.yaml — Verify new user onboarding flow
appId: com.kalei.app
---
- launchApp
- assertVisible: "Your thoughts are like light"
- swipeLeft
- assertVisible: "How would you like your reframes?"
- tapOn: "Compassionate"
- swipeLeft
- assertVisible: "Try your first Turn"
- tapOn: "What thought is weighing on you?"
- inputText: "I feel overwhelmed with everything"
- tapOn: "Turn"
- assertVisible: "perspective"
- swipeLeft
- assertVisible: "Your daily rhythm"
```
```yaml
# e2e/turn-flow.yaml — Verify complete Turn lifecycle
appId: com.kalei.app
---
- launchApp
- tapOn: "Turn"
- tapOn: "What thought is weighing on you?"
- inputText: "Nothing ever works out for me"
- tapOn: "Turn"
- assertVisible: "Compassionate"
- assertVisible: "Rational"
- assertVisible: "Growth"
- assertVisible: "If I notice"
- tapOn:
id: "save-turn-button"
- assertVisible: "Saved"
```
```yaml
# e2e/mirror-session.yaml — Verify Mirror writing and fragment detection
appId: com.kalei.app
---
- launchApp
- tapOn: "Mirror"
- tapOn: "Begin writing"
- inputText: "Everything is terrible and nothing will get better"
- wait: 3000
- assertVisible: "Catastrophizing"
- tapOn: "Catastrophizing"
- assertVisible: "See another perspective"
- tapOn: "See another perspective"
- assertVisible: "perspective"
```
```yaml
# e2e/purchase-flow.yaml — Verify paywall and upgrade path
appId: com.kalei.app
---
- launchApp
- tapOn: "Turn"
# Exhaust free tier (3 turns)
- repeat:
times: 4
commands:
- tapOn: "What thought is weighing on you?"
- inputText: "Test thought"
- tapOn: "Turn"
- wait: 2000
- assertVisible: "Upgrade to Prism"
```
**Running Maestro tests:**
```bash
# Run a single flow
maestro test apps/mobile/e2e/onboarding.yaml
# Run all flows
maestro test apps/mobile/e2e/
# Run with screenshot capture on failure
maestro test --format junit --output e2e-results/ apps/mobile/e2e/
# Run in CI (headless)
maestro test --no-ansi apps/mobile/e2e/
```
**Maestro in CI (Session D7):** Add Maestro to the CI pipeline by running the Maestro CLI locally on a CI runner with an emulator (Maestro CLI is free and open source — do not use Maestro Cloud which is a paid service). For self-hosted CI via Gitea Actions, install Maestro and an Android emulator on the runner. Gate releases on E2E pass — no TestFlight or Play Store upload if Maestro flows fail. Target: all 4 core flows pass on both iOS and Android before every release.
---
## 11. Deployment and Operations
### 11.1 Launch Deployment (Single VPS)
```
Netcup VPS 1000 G12 (€8.45/month)
├── Caddy (reverse proxy, auto-HTTPS)
├── Node.js API (Fastify, PM2 process manager)
├── PostgreSQL 16 (direct install)
├── Redis 7 (direct install)
└── Background workers (same process or separate PM2 app)
```
### 11.2 Deployment Checklist
Before every production deploy:
1. All tests pass locally.
2. Migration is tested on a staging copy of the database.
3. Rollback migration exists and is tested.
4. Health endpoint returns ok after deploy.
5. Error rate does not spike in first 15 minutes.
6. AI cost telemetry is within expected range.
### 11.3 Backup Strategy
- PostgreSQL: daily pg_dump to encrypted offsite storage.
- Redis: RDB snapshots daily (Redis data is ephemeral and rebuildable, but snapshots prevent cold-start recalculation of usage counters).
- Verified restore drill: run monthly.
---
## 12. Session Execution Checklist
Use this checklist for every Claude Code session:
```
[ ] Read the session description and understand the deliverables
[ ] Verify prerequisites from previous sessions are met
[ ] Execute the session with Claude Code
[ ] Run the validation steps — all must pass
[ ] Run the full quality check: pnpm run check
[ ] Review generated code for architecture compliance:
[ ] AI calls go through gateway only
[ ] Safety precheck on all AI-facing routes
[ ] Entitlement checks where required
[ ] Structured logging with request IDs
[ ] Zod validation on all inputs
[ ] TypeScript strict mode — no any
[ ] Commit with descriptive message
[ ] Update this checklist with any issues or deviations
```
---
## 13. Timeline Summary
| Track | Sessions | Duration | Weeks | Outcome |
|---|---|---|---|---|
| A: Foundation | A1-A6 | 3-5 days | 1-2 | Repo, infra, schema, skeletons |
| B: Platform Core | B1-B9 | 2-3 weeks | 1-3 | Auth, billing, AI gateway, safety, push, observability |
| C: Core Experience | C1-C8 | 3-5 weeks | 4-8 | Mirror, Turn, Lens, Gallery, Onboarding |
| D: Launch Hardening | D1-D7 | 2-4 weeks | 9-10 | Safety, billing, security, performance, deployment |
| E: Spectrum | E1-E3 | 3-6 weeks | 11-12 | Analytics pipeline, insights, dashboard |
Total to v1 launch (all features, end of Track E): 12 weeks.
Note: All features ship in a single v1 release. The build timeline is continuous (weeks 1-12) with sequential milestones, not separate "phases."
---
## 14. Critical Path Dependencies
```
A1 (repo) → A2 (docker) → A3 (API) → A5 (schema)
A4 (mobile) ←─── A3 (API)
A6 (shared schemas) → B1 (auth) → B2 (tokens) → B3 (entitlements)
B4 (AI gateway) → B5 (prompts) → B6 (safety) ──────→ C1 (mirror API)
B7 (rate limits) → B8 (push) → B9 (observability) ──→ C3 (turn API)
C1 → C2 (mirror UI) ─────────────────────────────→ C7 (gallery)
C3 → C4 (turn UI) ─────────────────────────────→ C7 (gallery)
C5 → C6 (lens UI) ─────────────────────────────→ C7 (gallery)
C8 (onboarding) → D1-D7 (hardening) → E1-E3 (spectrum)
```
---
## 15. What This Framework Deliberately Excludes
These are not in scope for the build framework and should be handled separately:
- Pixel-level UI specs (refer to kalei-complete-design.md and kalei-brand-guidelines.md)
- Marketing copy and App Store optimization (refer to app-blueprint.md section on ASO)
- Legal review of privacy policy and terms of service
- Detailed threat modeling (should be commissioned separately before pre-launch)
- Community building and growth hacking strategy
- Investor materials or pitch decks
---
*This framework is the operational bridge between Kalei's architecture documents and actual code. Follow it session by session. Deviate only when reality demands it, and document every deviation.*

View File

@@ -0,0 +1,326 @@
# Kalei Getting Started Guide (Beginner Friendly)
Last updated: 2026-02-10
Audience: First-time app builders
This guide explains the groundwork you need before coding, then gives you the exact first steps to start building Kalei.
Reference architecture: `docs/kalei-system-architecture-plan.md`
## 1. What You Are Building
Kalei is a mobile-first mental wellness product with four product pillars:
- Mirror: freeform writing with passive AI fragment detection.
- Turn (Kaleidoscope): structured AI reframing.
- Lens: goals, daily actions, and affirmations.
- Spectrum: weekly and monthly insight analytics.
At launch, your implementation target is:
- Mobile app: React Native + Expo.
- Backend API: Node.js + Fastify.
- Data: PostgreSQL + Redis.
- Source control and CI: Gitea + Gitea Actions (or Woodpecker CI).
- AI access: provider-agnostic AI Gateway using open-weight models (Ollama for local dev, vLLM for staging/prod).
- Billing and entitlements: self-hosted entitlement service (direct Apple App Store + Google Play verification, no RevenueCat dependency).
## 2. How To Use This Document Set
Read in this order:
1. `docs/kalei-getting-started.md` (this file)
2. `docs/codex phase documents/README.md`
3. `docs/codex phase documents/phase-0-groundwork-and-dev-environment.md`
4. `docs/codex phase documents/phase-1-platform-foundation.md`
5. `docs/codex phase documents/phase-2-core-experience-build.md`
6. `docs/codex phase documents/phase-3-launch-readiness-and-hardening.md`
7. `docs/codex phase documents/phase-4-spectrum-and-scale.md`
## 3. Groundwork Checklist (Before You Write Feature Code)
## 3.1 Accounts You Need
Create these accounts first so you do not block yourself later.
Must have for early development:
- Gitea (source control and CI)
- Apple Developer Program (iOS distribution, required)
- Google Play Console (Android distribution, required)
- DNS provider account (or self-hosted DNS using PowerDNS)
Strongly recommended now (not later):
- GlitchTip (open-source error tracking)
- PostHog self-hosted (open-source product analytics)
- Domain registrar account (for `kalei.ai`)
## 3.2 Local Tools You Need
Install this baseline stack:
- Git
- Node.js LTS (via `nvm`, recommended)
- npm (bundled with Node) or pnpm
- Docker Desktop (for PostgreSQL + Redis locally)
- VS Code (or equivalent IDE)
- Expo Go app on your phone (iOS/Android)
- Ollama (local open-weight model serving)
Install and verify:
```bash
# Git
git --version
# nvm + Node LTS
nvm install --lts
nvm use --lts
node -v
npm -v
# Docker
docker --version
docker compose version
# Expo CLI (optional global; npx is also fine)
npx expo --version
# Ollama
ollama --version
```
## 3.3 Decide Your Working Model
Set these rules now:
- Work in short feature slices with demoable outcomes.
- Every backend endpoint gets at least one automated test.
- No direct AI calls from client apps.
- No secrets in the repo, ever.
- Crisis-level text is never reframed.
## 4. Recommended Monorepo Structure
If you are starting from scratch, use this layout:
```text
Kalei/
apps/
mobile/
services/
api/
workers/
packages/
shared/
infra/
docker/
scripts/
docs/
```
Why this structure:
- Keeps mobile and backend isolated but coordinated.
- Lets you share schemas/types in `packages/shared`.
- Keeps infra scripts in one predictable place.
## 5. Step-By-Step Initial Setup
These are the first practical steps for week 1.
## Step 1: Initialize folders
```bash
mkdir -p apps/mobile services/api services/workers packages/shared infra/docker infra/scripts
```
## Step 2: Bootstrap the mobile app
```bash
npx create-expo-app@latest apps/mobile --template tabs
cd apps/mobile
npm install
cd ../..
```
## Step 3: Bootstrap the API service
```bash
mkdir -p services/api && cd services/api
npm init -y
npm install fastify @fastify/cors @fastify/helmet @fastify/sensible zod dotenv pino pino-pretty pg ioredis
npm install -D typescript tsx @types/node vitest supertest @types/supertest eslint prettier
npx tsc --init
cd ../..
```
## Step 4: Bring up local PostgreSQL and Redis
Create `infra/docker/docker-compose.yml`:
```yaml
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: kalei
POSTGRES_PASSWORD: kalei
POSTGRES_DB: kalei
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
redis:
image: redis:7
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
pg_data:
redis_data:
```
Start services:
```bash
docker compose -f infra/docker/docker-compose.yml up -d
```
## Step 5: Create environment files
Create:
- `services/api/.env`
- `apps/mobile/.env`
API `.env` minimum:
```env
NODE_ENV=development
PORT=8080
DATABASE_URL=postgres://kalei:kalei@localhost:5432/kalei
REDIS_URL=redis://localhost:6379
JWT_ACCESS_SECRET=replace_me
JWT_REFRESH_SECRET=replace_me
AI_PROVIDER=openai_compatible
AI_BASE_URL=http://localhost:11434/v1
AI_MODEL=qwen2.5:14b
AI_API_KEY=local-dev
GLITCHTIP_DSN=replace_me
POSTHOG_API_KEY=replace_me
POSTHOG_HOST=http://localhost:8000
APPLE_SHARED_SECRET=replace_me
GOOGLE_PLAY_PACKAGE_NAME=com.kalei.app
```
Mobile `.env` minimum:
```env
EXPO_PUBLIC_API_BASE_URL=http://localhost:8080
EXPO_PUBLIC_ERROR_TRACKING_DSN=replace_me
```
## Step 6: Create your first backend health endpoint
Create `/health` returning status, uptime, and version. This is your first proof that the API is running.
## Step 7: Connect mobile app to backend
Add a tiny service function in mobile that calls `/health` and shows the result on screen.
## Step 8: Add migrations baseline
Create migration folders and your first migration for:
- users
- profiles
- auth_sessions
- refresh_tokens
Add a migration script and run it on local Postgres.
## Step 9: Set up linting, formatting, and tests
At minimum:
- API: `npm run lint`, `npm run test`
- Mobile: `npm run lint`
## Step 10: Push a clean baseline commit
Your first stable commit should include:
- mobile app runs
- API runs
- db and redis run in Docker
- health endpoint tested
- env files templated
## 6. Non-Negotiable Ground Rules
These reduce rework and production risk.
- API-first contracts: Define backend request/response schema first.
- Version prompts: Keep prompt templates in source control with version tags.
- Idempotency: write endpoints should support idempotency keys.
- Structured logs: every request gets a request ID.
- Safety-first branching: crisis path is explicit and tested.
## 7. Open-Source-First Policy
Default to open-source tools unless there is a hard platform requirement.
Open-source defaults for Kalei:
- Git forge and CI: Gitea + Gitea Actions (or Woodpecker CI)
- Error tracking: GlitchTip
- Product analytics: PostHog self-hosted
- AI serving: Ollama (local), vLLM (staging/prod)
- Runtime and data: Fastify, PostgreSQL, Redis
Unavoidable non-open-source dependencies:
- Apple App Store distribution and StoreKit APIs
- Google Play distribution and Billing APIs
- APNs and FCM for push delivery
## 8. What "Done" Looks Like For Groundwork
Before the build starts, you should be able to demonstrate:
- Local stack boot with one command (`docker compose ... up -d`).
- API starts with no errors and serves `/health`.
- Mobile app opens and calls API successfully.
- Baseline DB migrations run and rollback cleanly.
- One CI pipeline runs lint + tests on pull requests.
## 9. Common Beginner Mistakes (Avoid These)
- Building UI screens before backend contracts exist.
- Calling AI provider directly from the app.
- Waiting too long to add tests and logs.
- Keeping architecture only in your head (not in docs).
- Delaying safety and privacy work until late phases.
## 10. Recommended Weekly Rhythm
Use this cycle every week:
1. Plan: define exact outcomes for the week.
2. Build: complete one vertical slice (API + mobile + data).
3. Verify: run tests, manual QA, and failure-path checks.
4. Demo: produce a short demo video for your own review.
5. Retrospective: capture blockers and adjust next week.
## 11. Next Step
Start with:
- `docs/codex phase documents/phase-0-groundwork-and-dev-environment.md`
Then execute each phase in order.

View File

@@ -0,0 +1,518 @@
# Kalei — Infrastructure & Financial Plan
## The Constraint
**Starting capital:** €0 €2,000 max
**Monthly burn target:** Under €30/month at launch, scaling only when revenue justifies it
**Goal:** Ship a production-quality AI mental wellness app that can serve its first 1,000 users without going broke
---
## 1. The AI Decision (This Is Everything)
AI is 7090% of Kalei's variable cost. Every other infrastructure decision is rounding error compared to this one.
### The Research That Changed Everything
A 2025 Nature study tested LLMs on 5 standardized emotional intelligence tests. DeepSeek V3, Claude 3.5 Haiku, and several other LLMs all outperformed humans (81% avg vs 56% human avg). The gap between Claude and cheaper open-weight models on emotion understanding is much smaller than originally assumed. This opened the door to a dramatically cheaper AI strategy.
### The Decision: OpenRouter Gateway + DeepSeek V3.2 (Non-Chinese Hosting)
**Primary engine:** DeepSeek V3.2 via OpenRouter, pinned to non-Chinese providers (DeepInfra / Fireworks)
**Automatic fallback:** Claude Haiku 4.5 via OpenRouter (activated if primary provider has an outage)
**Batch processing:** DeepSeek V3.2 for Spectrum analysis and weekly insights (no separate batch tier needed at this price point)
| | DeepInfra (via OpenRouter) | Claude Haiku 4.5 (fallback) | Savings |
|---|---|---|---|
| Input (cache miss) | $0.26/M | $1.00/M | 74% cheaper |
| Input (cache hit) | $0.216/M | $0.10/M | — |
| Output | $0.38/M | $5.00/M | 92% cheaper |
### Why OpenRouter + Non-Chinese Providers (Not DeepSeek Direct)
DeepSeek's direct API is cheaper ($0.028 cache hits, $0.42 output) but routes all data through Chinese servers. For a mental wellness app handling sensitive emotional content, this is a non-starter — both for user trust and GDPR considerations. Routing through DeepInfra/Fireworks (US/EU infrastructure) via OpenRouter costs ~23x more than the direct API but still delivers ~8590% savings vs Claude Haiku.
OpenRouter gives us:
- **Provider pinning** — deterministic routing to non-Chinese hosts via the `order` array in API calls
- **Automatic failover** — if DeepInfra goes down, routes to Fireworks or Claude Haiku automatically
- **One API, one billing** — no lock-in, switching models is a config change not a code change
- **No markup** on base provider pricing (OpenRouter doesn't add fees on paid models)
### Self-Hosted GPU: Not Yet
GPU self-hosting (Qwen3-30B-A3B on RTX 4090 at ~$245/month) only beats the API route at ~600+ DAU. Below that, APIs are cheaper. Revisit when user base justifies fixed GPU costs, or if data sovereignty becomes a hard requirement.
### Why Not Tiered Models (Option D)?
We evaluated a hybrid strategy using different models per feature tier (DeepSeek for emotional tasks, Qwen3 via Groq for structured generation, batch APIs for analytics). At our current scale, the complexity cost outweighs the savings: separate prompt tuning, multiple quality benchmarks, routing logic in the ai_gateway, and edge cases when tasks don't cleanly fit one tier. The ~$3050/month savings doesn't justify maintaining four model configurations as a solo founder. Introduce tiering only when usage data reveals which tasks genuinely benefit from a different model.
---
## 2. Per-User AI Cost Model
Here's what a real user session looks like in tokens:
### The Mirror (Freeform Writing + AI Highlights)
| Component | Input Tokens | Output Tokens |
|---|---|---|
| System prompt (cached after first call) | ~800 | — |
| User's writing (per session, ~300 words) | ~400 | — |
| Fragment detection (5 highlights avg) | — | ~500 |
| Inline reframe (per tap, user triggers ~2) | ~200 | ~150 |
| Session Reflection | ~300 | ~400 |
| **Total per Mirror session** | **~1,700** | **~1,050** |
With prompt caching (system prompt cached): effective input ≈ 980 tokens (800 cached at 0.1×) + 900 fresh = **~980 billable input tokens**
### The Kaleidoscope (One Turn)
| Component | Input Tokens | Output Tokens |
|---|---|---|
| System prompt (cached) | ~600 | — |
| User's fragment + context | ~300 | — |
| 3 reframe perspectives | — | ~450 |
| **Total per Turn** | **~900** | **~450** |
With caching: ~360 billable input tokens
### The Lens (Daily Affirmation)
| Component | Input Tokens | Output Tokens |
|---|---|---|
| System prompt (cached) | ~400 | — |
| User context + goals | ~200 | — |
| Generated affirmation | — | ~100 |
| **Total per daily affirmation** | **~600** | **~100** |
With caching: ~240 billable input tokens
### The Guide (Active Coaching Layer)
| Component | Input Tokens | Output Tokens |
|---|---|---|
| System prompt (cached after first call) | ~600 | — |
| Goal Check-In conversation (per check-in, ~4 exchanges) | ~1,200 | ~800 |
| Cross-Feature Bridge detection (per analysis pass) | ~500 | ~200 |
| Attention Prompt generation (per prompt) | ~300 | ~100 |
| Evidence Intervention (per intervention) | ~400 | ~300 |
| Weekly Pulse AI Read (per pulse) | ~800 | ~500 |
| **Total per weekly cycle (1 check-in + 7 prompts + 1 pulse + 1 bridge analysis)** | **~4,600** | **~2,600** |
With prompt caching: effective input ≈ 3,200 billable input tokens per week
**Note on Guide intelligence:** The Guide requires cross-feature context analysis — it reads Mirror sessions, Turn history, and Lens goals to generate bridges and check-ins. This makes its per-call token count higher than single-feature interactions, but the calls are less frequent (weekly check-ins, daily prompts, bridges max once/day). The Guide also benefits heavily from prompt caching since its system prompt and user context window are reused across multiple Guide interactions.
### Monthly Usage Per Active User Profile
**Free user** (3 Turns/day, 2 Mirror sessions/week, daily Lens, basic Guide):
| Feature | Sessions/Month | Billable Input Tokens | Output Tokens |
|---|---|---|---|
| Kaleidoscope | 90 Turns | 32,400 | 40,500 |
| Mirror | 8 sessions | 7,840 | 8,400 |
| Lens | 30 affirmations | 7,200 | 3,000 |
| Guide (basic: 1 check-in, 12 prompts, 4 pulses self-report, bridges) | ~15 interactions | 6,400 | 3,200 |
| **Total** | | **53,840** | **55,100** |
**Cost with DeepSeek V3.2 via DeepInfra:** (53,840 × ~$0.24 blended + 55,100 × $0.38) / 1,000,000 = **$0.013 + $0.021 = ~$0.034/month**
*(Previous Claude Haiku 4.5 estimate: $0.33/month — this is a ~90% reduction)*
**Prism subscriber** (unlimited usage, assume 2× free user + full Guide + Spectrum):
| Feature | Sessions/Month | Billable Input Tokens | Output Tokens |
|---|---|---|---|
| Kaleidoscope | 180 Turns | 64,800 | 81,000 |
| Mirror | 16 sessions | 15,680 | 16,800 |
| Lens | 30 affirmations | 7,200 | 3,000 |
| Guide (full: 4 check-ins, 30 prompts, 4 full pulses, evidence interventions, all bridges) | ~50 interactions | 22,400 | 14,000 |
| Spectrum (batch) | 4 analyses | 8,000 | 12,000 |
| **Total** | | **118,080** | **126,800** |
**Cost with DeepSeek V3.2 via DeepInfra:** (118,080 × ~$0.24 + 126,800 × $0.38) / 1,000,000 = **$0.028 + $0.048 = ~$0.076/month**
*(Previous Claude Haiku 4.5 estimate: $0.72/month — this is a ~89% reduction)*
**Reality check:** Most users won't hit max usage. Expect average active user cost of **$0.03$0.06/month.** The Guide adds ~$0.005$0.01/month for free users and ~$0.01$0.02/month for Prism subscribers — negligible cost for a significant retention benefit.
---
## 3. Infrastructure Stack
### Server: Netcup VPS 1000 G12
| Spec | Value |
|---|---|
| CPU | 4 vCores (AMD EPYC) |
| RAM | 8 GB DDR5 ECC |
| Storage | 256 GB NVMe |
| Bandwidth | Unlimited, 2.5 Gbps |
| Location | Nuremberg, Germany |
| **Price** | **€8.45/month** (~$9.20) |
This runs everything: API server, database, Redis cache, reverse proxy. Comfortably handles hundreds of concurrent users. Can upgrade to VPS 2000 (€15.59/mo) when we outgrow it.
**What runs on this box:**
- Node.js / Express API server (or Fastify for speed)
- PostgreSQL 16 (direct install, not Supabase overhead)
- Redis (session cache, rate limiting, prompt cache keys)
- Nginx (reverse proxy, SSL termination, rate limiting)
- Certbot (free SSL via Let's Encrypt)
### Why NOT Supabase Cloud
Supabase Cloud Pro is $25/month — that's 3× our VPS cost and we'd still need a separate server for the API layer. Self-hosting Supabase via Docker is possible but adds ~2GB RAM overhead for all the services (GoTrue, PostgREST, Realtime, Storage, Kong). On an 8GB VPS, that leaves very little room.
**Instead:** Run PostgreSQL directly. We get all the database functionality we need (Row Level Security, triggers, functions, JSON support) without the Supabase services overhead. We build our own auth layer (JWT-based, simple) and our own API. This is leaner, cheaper, and gives us full control.
If we later want Supabase features (real-time subscriptions, storage), we can self-host just the components we need.
### Domain & DNS
| Item | Cost |
|---|---|
| kalei.ai domain | ~$5070/year (~$5/month) |
| Cloudflare DNS (free tier) | $0 |
| Cloudflare CDN/DDoS (free tier) | $0 |
### App Deployment & Distribution
| Item | Cost |
|---|---|
| Expo / EAS Build (free tier) | $0 (limited builds, queue wait) |
| Apple Developer Program | $99/year (~$8.25/month) |
| Google Play Developer | $25 one-time |
| Push Notifications (Firebase Cloud Messaging) | $0 |
**Build strategy:** Use Expo free tier for development. For production releases, use EAS free tier (low priority queue, ~30 min wait) or build locally. 24 builds per month is fine for the free tier.
### Email & Transactional
| Item | Cost |
|---|---|
| Resend (transactional email, free tier) | $0 (up to 100 emails/day) |
| Or Brevo free tier | $0 (300 emails/day) |
### Monitoring & Error Tracking
| Item | Cost |
|---|---|
| Sentry (free tier) | $0 (5K errors/month) |
| UptimeRobot (free tier) | $0 (50 monitors) |
| Custom logging to PostgreSQL | $0 |
---
## 4. Total Monthly Cost Breakdown
### Development (Pre-Launch)
| Item | Monthly Cost |
|---|---|
| Netcup VPS 1000 G12 | €8.45 |
| Domain (kalei.ai) | ~€5.00 |
| OpenRouter API (dev/testing) | ~€5 |
| Expo Free Tier | €0 |
| Cloudflare, Sentry, email | €0 |
| **Total** | **~€18.50/month** |
**Upfront costs:** Apple Developer ($99) + Google Play ($25) + Domain (~$55/year) = **~€180 one-time**
### At Launch (0500 users, ~50 DAU)
All features ship in v1: Mirror, Turn, Lens, Spectrum, Rehearsal, Ritual, Evidence Wall, Guide. Assuming 50 daily active users, ~200 registered:
| Item | Monthly Cost |
|---|---|
| Netcup VPS 1000 G12 | €8.45 |
| Domain | ~€5.00 |
| AI via OpenRouter (~50 active × $0.04 avg) | ~€2.00 |
| Expo Free Tier | €0 |
| Infrastructure (Cloudflare, etc.) | €0 |
| **Total** | **~€15.50/month** |
### At Traction (5002,000 users, ~200 DAU)
| Item | Monthly Cost |
|---|---|
| Netcup VPS 2000 G12 (upgrade) | €15.59 |
| Domain | ~€5.00 |
| AI via OpenRouter (~200 active × $0.04 avg) | ~€8.00 |
| Expo Starter (if needed for OTA updates) | €19.00 |
| Email (may need paid tier) | €010 |
| **Total** | **~€4858/month** |
### At Growth (2,00010,000 users, ~1,000 DAU)
| Item | Monthly Cost |
|---|---|
| Netcup VPS 4000 G12 | €26.18 |
| Domain | ~€5.00 |
| AI via OpenRouter (~1,000 active × $0.04 avg) | ~€40.00 |
| Expo Production plan | €99.00 |
| Email paid tier | ~€20 |
| Sentry paid (if needed) | ~€26 |
| **Total** | **~€216/month** |
AI cost is now only ~19% of total spend at 1,000 DAU (down from ~60% under the Haiku-first plan). Infrastructure and app store tooling become the dominant costs at scale.
---
## 5. Pricing Reevaluation
### The Old Price: $7.99/month (Prism)
Based on the cost model above, let's check if this works:
**At 50 DAU (~10 paying subscribers):**
- Revenue: 10 × $7.99 = $79.90
- Costs: ~$28
- **Margin: +$52 (65%)**
**At 200 DAU (~40 paying subscribers @ 20% conversion):**
- Revenue: 40 × $7.99 = $319.60
- Costs: ~$100
- **Margin: +$220 (69%)**
**At 1,000 DAU (~150 paying subscribers @ 15% conversion):**
- Revenue: 150 × $7.99 = $1,198.50
- Costs: ~$425
- **Margin: +$773 (65%)**
The margins are healthy. But $7.99 feels like a lot for a brand-new app from an unknown brand in a competitive wellness space. Users compare against Headspace ($12.99), Calm ($14.99), but those have massive brand recognition and content libraries.
### The New Price: $4.99/month (Prism)
**Why $4.99:**
- Psychological barrier is much lower — impulse-buy territory
- Significantly undercuts major competitors while offering AI personalization they don't have
- At ~$0.08/month cost per Prism subscriber (including full Guide coaching), the margin is **98%**
- Annual option: $39.99/year ($3.33/month) — strong incentive to commit
- Free tier remains generous enough to demonstrate value (3 Turns/day, 2 Mirror/week, basic Guide)
**Revised projections at $4.99 (with OpenRouter + DeepSeek V3.2 AI strategy):**
| Scale | Paying Users | Monthly Revenue | Monthly Cost | Margin |
|---|---|---|---|---|
| Launch (~50 DAU) | 15 (higher conversion at lower price) | $74.85 | ~$16 | +$59 (79%) |
| Traction (~200 DAU) | 60 | $299.40 | ~$53 | +$246 (82%) |
| Growth (~1,000 DAU) | 250 | $1,247.50 | ~$216 | +$1,032 (83%) |
The AI cost reduction transforms the unit economics. Margins now exceed 79% at every stage, and break-even comes faster.
### Alternative: Tiered Pricing
| Tier | Price | What You Get |
|---|---|---|
| **Free** | $0 | 3 Turns/day, 2 Mirror/week, basic Lens, 30-day Gallery |
| **Prism** | $4.99/mo | Unlimited Turns + Mirror, advanced reframe styles, full Gallery, fragment tracking |
| **Prism+** | $9.99/mo | Everything in Prism + full Spectrum dashboard, weekly/monthly AI insights, export, priority processing |
This is smart because Spectrum is the most expensive feature (batch AI analysis of historical data) and the most valuable retention tool. Gating it behind a higher tier means only your most engaged (and willing-to-pay) users generate that cost, and they're paying for it.
---
## 6. Revenue Milestones & Sustainability
### Break-Even Analysis
**Monthly fixed costs (at launch):** ~€14 (VPS + domain)
**Variable cost per active user:** ~€0.04
Break-even on fixed costs alone: **3 Prism subscribers at $4.99** cover the infrastructure.
To cover Apple's annual fee ($99) and Google ($25 amortized): add ~$10/month → total of **5 subscribers** to fully break even.
### Path to Sustainability
| Milestone | Users | Paying | MRR | Costs | Profit |
|---|---|---|---|---|---|
| Month 3 | 100 | 5 | $25 | $17 | +$8 |
| Month 6 | 500 | 30 | $150 | $22 | +$128 |
| Month 9 | 1,500 | 80 | $400 | $35 | +$365 |
| Month 12 | 3,000 | 200 | $1,000 | $60 | +$940 |
| Month 18 | 8,000 | 600 | $3,000 | $150 | +$2,850 |
The model is profitable from **month 3** with just 5 paying subscribers. The 90% AI cost reduction means Kalei reaches profitability immediately at launch rather than needing a 45 month runway.
---
## 7. Technical Architecture Summary
```
┌─────────────────────────────────────────────────────┐
│ CLIENTS │
│ React Native (iOS + Android) │
│ via Expo / EAS │
└──────────────────┬──────────────────────────────────┘
│ HTTPS
┌─────────────────────────────────────────────────────┐
│ CLOUDFLARE (Free Tier) │
│ DNS · CDN · DDoS Protection · SSL │
└──────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ NETCUP VPS 1000 G12 (€8.45/mo) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
│ │ Nginx │→ │ Node.js │→ │ PostgreSQL 16 │ │
│ │ (proxy) │ │ API │ │ (all app data) │ │
│ └──────────┘ └─────┬─────┘ └──────────────────┘ │
│ │ ┌──────────────────┐ │
│ │ │ Redis │ │
│ │ │ (cache/sessions)│ │
│ │ └──────────────────┘ │
└──────────────────────┼──────────────────────────────┘
│ API Calls
┌──────────────────────────────┐
│ OPENROUTER GATEWAY │
│ (single API, one key) │
│ │
│ ┌────────────────────────┐ │
│ │ PRIMARY: DeepSeek V3.2 │ │
│ │ via DeepInfra/Fireworks │ │
│ │ (US/EU infrastructure) │ │
│ │ │ │
│ │ All features: │ │
│ │ • Mirror fragments │ │
│ │ • Kaleidoscope reframes│ │
│ │ • Lens affirmations │ │
│ │ • Crisis detection │ │
│ │ • Guide coaching │ │
│ │ • Spectrum analysis │ │
│ │ │ │
│ │ $0.26/$0.38 per MTok │ │
│ └────────────────────────┘ │
│ │ │
│ (automatic failover) │
│ │ │
│ ┌────────────────────────┐ │
│ │ FALLBACK: Claude Haiku │ │
│ │ 4.5 (Anthropic) │ │
│ │ $1.00/$5.00 per MTok │ │
│ │ Activated on outage │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
```
### Key Technical Decisions
**Auth:** Custom JWT-based auth built into our Node.js API. Uses bcrypt for password hashing, short-lived access tokens (15 min) + long-lived refresh tokens stored in PostgreSQL. Social login (Apple Sign-In, Google) via their SDKs — free.
**Database schema:** PostgreSQL with Row Level Security policies. Tables for users, mirror_sessions, mirror_fragments, turns, lens_goals, spectrum_analyses. All user content encrypted at rest (PostgreSQL `pgcrypto` extension).
**AI request pipeline:**
1. Client sends user text to our API
2. API constructs prompt with cached system prompt + user context
3. API calls DeepSeek V3.2 via OpenRouter (pinned to DeepInfra/Fireworks), streams response back to client
4. If primary provider fails, OpenRouter automatically fails over to Claude Haiku 4.5
5. API logs token usage for cost tracking
6. Response stored in PostgreSQL for Spectrum analysis
**Rate limiting:** Redis-based. Free tier: 3 Turns/day, 2 Mirror/week enforced server-side. Prism: unlimited but soft-capped at 50 Turns/day to prevent abuse (99.9% of users will never hit this).
**Prompt caching strategy:** System prompts for each feature (Mirror, Kaleidoscope, Lens, Guide) are designed to be identical across users. Only the user's specific content changes. DeepInfra supports prompt caching with ~20% discount on cached input tokens ($0.216/M vs $0.26/M). While less dramatic than Anthropic's 90% cache discount, the base pricing is already so low that effective costs remain minimal.
---
## 8. Cost Control Safeguards
These prevent a surprise API bill from killing the project:
1. **Hard spending cap** on OpenRouter dashboard (start at $20/month, increase as revenue grows)
2. **Per-user daily token budget** tracked in Redis. If a user somehow generates excessive requests, they get a "take a break" message (fits the wellness brand perfectly)
3. **Graceful degradation:** If API budget is 80% consumed, route Lens affirmations to local template system (pre-written affirmations, no AI needed). Mirror and Kaleidoscope get priority for remaining budget.
4. **Automatic failover:** OpenRouter handles provider switching transparently. If DeepInfra has an outage, requests route to Fireworks or Claude Haiku automatically — no code changes needed.
5. **Monitor daily:** Simple Telegram bot alerts if daily API spend exceeds threshold
---
## 9. Startup Budget Allocation
With a maximum €2,000 to spend wisely:
| Category | Amount | What It Covers |
|---|---|---|
| Apple Developer Account | €99 | Annual fee, required for App Store |
| Google Play Developer | €25 | One-time fee |
| Domain (kalei.ai, 1 year) | ~€55 | Annual registration |
| Netcup VPS (6 months prepaid) | ~€51 | Runway for half a year of hosting |
| OpenRouter API credits (initial deposit) | €50 | Covers dev + testing + first ~1,000+ active user-months at DeepSeek V3.2 pricing |
| Design assets (fonts, if not free) | €050 | Inter + custom weight = free. Icon set if needed. |
| Contingency | ~€120 | Unexpected costs |
| **Total startup spend** | **~€400450** | |
| **Remaining reserve** | **~€1,5501,600** | 100+ months of launch-scale operating costs |
This means the €2,000 budget gives us effectively **unlimited runway** at launch-scale costs (~€16/month). Even without a single paying customer, we could operate for over 8 years. The AI cost reduction transformed our runway from "comfortable" to "virtually infinite" at early scale.
---
## 10. When to Scale (And What Changes)
| Trigger | Action | Cost Impact |
|---|---|---|
| >200 concurrent connections | Upgrade to VPS 2000 (€15.59) | +€7/month |
| >500 DAU | Add Redis Cluster or separate DB VPS | +€58/month |
| >600 DAU | Evaluate self-hosted Qwen3-30B-A3B on GPU (~$245/mo) | Cheaper than API at this volume, full data control |
| >2,000 DAU | Upgrade to VPS 4000 (€26.18) | +€10/month |
| >5,000 DAU | Introduce tiered model routing (different models per feature) | Saves ~2030% on AI costs at scale |
| >10,000 DAU | Consider second VPS for API/DB separation | Architecture change |
| >$2,000/month revenue | Consider dedicated server or managed Postgres | Comfort/reliability upgrade |
The beauty of this architecture is that **nothing changes architecturally as we scale** — we just give the same VPS more resources, and the API costs scale linearly and predictably with users.
---
## 11. Competitive Cost Comparison
To put this in perspective — what would this cost on "standard" startup infrastructure?
| Our Stack | "Normal" Startup Stack | Monthly Cost |
|---|---|---|
| Netcup VPS (€8.45) | AWS EC2 t3.medium | $3550 |
| PostgreSQL on VPS ($0) | Supabase Pro or RDS | $2550 |
| Redis on VPS ($0) | Redis Cloud or ElastiCache | $1530 |
| Cloudflare free ($0) | AWS CloudFront + ALB | $2040 |
| DeepSeek V3.2 via OpenRouter (~$2) | Claude/GPT-4 API ($50+) | 96% cheaper |
| **Our total: ~$16/mo** | **Their total: ~$120200/mo** | |
We're running at **813%** of what a "typical" startup would spend by self-hosting on a European VPS and using cost-optimized AI routing instead of defaulting to AWS/GCP + expensive frontier models.
---
## 12. Final Pricing Recommendation
| | Free | Prism | Prism+ |
|---|---|---|---|
| **Price** | $0 | **$4.99/month** | **$9.99/month** |
| | | $39.99/year | $79.99/year |
| Turns/day | 3 | Unlimited | Unlimited |
| Mirror/week | 2 | Unlimited | Unlimited |
| Lens | Basic | Full | Full |
| Reframe styles | 1 (Compassionate) | All 4 | All 4 |
| Gallery | 30 days | Full history | Full history |
| Fragment tracking | No | Yes | Yes |
| Spectrum | No | No | **Full dashboard** |
| Weekly AI insights | No | No | **Yes** |
| Growth trajectory | No | No | **Yes** |
| Export | No | Basic | Full |
| **Our cost per user** | ~$0.02 | ~$0.06 | ~$0.08 |
| **Margin** | N/A (acquisition) | **99%** | **99%** |
### Why This Works
At **$4.99**, Kalei is:
- Cheaper than Headspace ($12.99), Calm ($14.99), Woebot (free but limited)
- More personalized than any of them (AI-powered, not pre-recorded content)
- Profitable from subscriber #6
- Self-sustaining from month ~5
- Fully funded for 12+ months on a €2,000 budget even with zero revenue
The model scales cleanly because **AI costs are the only meaningful variable cost**, and they scale linearly with usage at a rate that our pricing covers with 98%+ margins on the AI component. Even at scale, total infrastructure costs stay manageable because the OpenRouter + DeepInfra strategy keeps per-user AI spend under $0.10/month.
---
*Last updated: February 2026*
*All prices include VAT where applicable. USD/EUR conversions at approximate current rates.*

View File

@@ -0,0 +1,514 @@
# Kalei System Architecture Plan
Version: 1.0
Date: 2026-02-10
Status: Proposed canonical architecture for implementation
## 1. Purpose and Scope
This document consolidates the existing Kalei docs into one implementation-ready system architecture plan.
In scope:
- Core features: Mirror, Kaleidoscope (Turn), Lens, Gallery, Spectrum analytics, subscriptions (all ship in v1).
- Mobile-first architecture (iOS/Android via Expo) with optional web support.
- Production operations for safety, privacy, reliability, and cost control.
Out of scope:
- Pixel-level UI specs and brand copy details.
- Provider contract/legal details.
- Full threat model artifacts (to be produced separately).
## 2. Inputs Reviewed
- `docs/app-blueprint.md`
- `docs/kalei-infrastructure-plan.md`
- `docs/kalei-ai-model-comparison.md`
- `docs/kalei-mirror-feature.md`
- `docs/kalei-spectrum-phase2.md`
- `docs/kalei-complete-design.md`
- `docs/kalei-brand-guidelines.md`
## 3. Architecture Drivers
### 3.1 Product drivers
- Core loop quality: Mirror fragment detection and Turn reframes must feel high quality and emotionally calibrated.
- Daily habit loop: low friction, fast response, strong retention mechanics.
- Over time: longitudinal Spectrum insights from accumulated usage data.
### 3.2 Non-functional drivers
- Safety first: crisis language must bypass reframing and trigger support flow.
- Privacy first: personal reflective writing is highly sensitive.
- Cost discipline: launch target under ~EUR 30/month fixed infrastructure.
- Operability: architecture must be maintainable by a small team.
- Gradual scale: support ~50 DAU at launch and scale to ~10k DAU without full rewrite.
## 4. Canonical Decisions
This plan resolves conflicting guidance across current docs.
| Topic | Decision | Rationale |
|---|---|---|
| Backend platform | Self-hosted API-first modular monolith on Node.js (Fastify preferred) | Matches budget constraints and keeps full control of safety, rate limits, and AI routing. |
| Data layer | PostgreSQL 16 + Redis | Postgres for source-of-truth relational + analytics tables; Redis for counters, rate limits, caching, idempotency. |
| Auth | JWT auth service in API + refresh token rotation + social login (Apple/Google) | Aligns with self-hosted stack while preserving mobile auth UX. |
| Mobile | React Native + Expo (local/native builds) | Fastest path for iOS/Android while keeping build pipeline under direct control. |
| AI integration | AI Gateway abstraction via OpenRouter with provider pinning | Single API, automatic failover, no vendor lock-in, and deterministic routing to non-Chinese providers for data privacy. |
| AI default | DeepSeek V3.2 via OpenRouter, hosted on DeepInfra/Fireworks (US/EU infrastructure) | 8590% cheaper than Claude Haiku with comparable emotional intelligence benchmarks. Provider pinning ensures no data flows through Chinese servers. |
| AI fallback | Claude Haiku 4.5 via OpenRouter (automatic failover on provider outage) | Highest-quality safety net activated transparently when primary provider is unavailable. |
| Billing | Self-hosted entitlement authority (direct App Store + Google Play server APIs) | Keeps billing logic in-house and avoids closed SaaS dependency in core authorization path. |
| Analytics/monitoring | PostHog self-hosted + GlitchTip + centralized app logs + cost telemetry | Open-source-first observability stack with lower vendor lock-in. |
## 5. System Context
```mermaid
flowchart LR
user[User] --> app[Expo App]
app --> edge[Edge Proxy]
edge --> api[Kalei API]
api --> db[(PostgreSQL)]
api --> redis[(Redis)]
api --> ai[AI Providers]
api --> billing[Store Entitlements]
api --> push[Push Gateway]
api --> obs[Observability]
app --> analytics[Product Analytics]
```
## 6. Container Architecture
```mermaid
flowchart TB
subgraph Client
turn[Turn Screen]
mirror[Mirror Screen]
lens[Lens Screen]
spectrum_ui[Spectrum Dashboard]
profile_ui[Gallery and Profile]
end
subgraph Platform
gateway[API Gateway and Auth]
turn_service[Turn Service]
mirror_service[Mirror Service]
lens_service[Lens Service]
spectrum_service[Spectrum Service]
safety_service[Safety Service]
entitlement_service[Entitlement Service]
jobs[Job Scheduler and Workers]
ai_gateway[AI Gateway]
cost_guard[Usage Meter and Cost Guard]
end
subgraph Data
postgres[(PostgreSQL)]
redis[(Redis)]
object_storage[(Object Storage)]
end
subgraph External
ai_provider[DeepSeek V3.2 via OpenRouter + DeepInfra/Fireworks + Claude Haiku fallback]
store_billing[App Store and Play Billing APIs]
push_provider[APNs and FCM]
glitchtip[GlitchTip]
posthog[PostHog self-hosted]
end
turn --> gateway
mirror --> gateway
lens --> gateway
spectrum_ui --> gateway
profile_ui --> gateway
gateway --> turn_service
gateway --> mirror_service
gateway --> lens_service
gateway --> spectrum_service
gateway --> entitlement_service
mirror_service --> safety_service
turn_service --> safety_service
lens_service --> safety_service
spectrum_service --> safety_service
turn_service --> ai_gateway
mirror_service --> ai_gateway
lens_service --> ai_gateway
spectrum_service --> ai_gateway
ai_gateway --> ai_provider
turn_service --> cost_guard
mirror_service --> cost_guard
lens_service --> cost_guard
spectrum_service --> cost_guard
turn_service --> postgres
mirror_service --> postgres
lens_service --> postgres
spectrum_service --> postgres
entitlement_service --> postgres
jobs --> postgres
turn_service --> redis
mirror_service --> redis
lens_service --> redis
spectrum_service --> redis
cost_guard --> redis
jobs --> redis
entitlement_service --> store_billing
jobs --> push_provider
gateway --> glitchtip
gateway --> posthog
spectrum_service --> object_storage
```
## 7. Domain and Service Boundaries
### 7.1 Runtime modules
- `auth`: sign-up/sign-in, token issuance/rotation, device session management.
- `entitlements`: direct App Store + Google Play sync, plan gating (`free`, `prism`, `prism_plus`).
- `mirror`: session lifecycle, message ingestion, fragment detection, inline reframe, reflection.
- `turn`: structured reframing workflow and saved patterns.
- `lens`: goals, actions, daily focus generation, check-ins.
- `spectrum`: analytics feature store, weekly/monthly aggregation, insight generation.
- `safety`: crisis detection, escalation, crisis response policy.
- `ai_gateway`: prompt templates, OpenRouter API integration with provider pinning (DeepInfra/Fireworks primary, Claude Haiku fallback), retries/timeouts, structured output validation.
- `usage_cost`: token telemetry, per-user budgets, global spend controls.
- `notifications`: push scheduling, reminders, weekly summaries.
### 7.2 Why modular monolith first
- Lowest operational overhead at launch.
- Strong transaction boundaries in one codebase.
- Easy extraction path later for `spectrum` workers or `ai_gateway` if load increases.
## 8. Core Data Architecture
### 8.1 Data domains
- Identity: users, profiles, auth_sessions, refresh_tokens.
- Product interactions: turns, mirror_sessions, mirror_messages, mirror_fragments, lens_goals, lens_actions.
- Analytics: spectrum_session_analysis, spectrum_turn_analysis, spectrum_weekly, spectrum_monthly.
- Commerce: subscriptions, entitlement_snapshots, billing_events.
- Safety and operations: safety_events, ai_usage_events, request_logs, audit_events.
### 8.2 Entity relationship view
```mermaid
flowchart LR
users[USERS] --> profiles[PROFILES]
users --> auth_sessions[AUTH_SESSIONS]
users --> refresh_tokens[REFRESH_TOKENS]
users --> turns[TURNS]
users --> mirror_sessions[MIRROR_SESSIONS]
mirror_sessions --> mirror_messages[MIRROR_MESSAGES]
mirror_messages --> mirror_fragments[MIRROR_FRAGMENTS]
users --> lens_goals[LENS_GOALS]
lens_goals --> lens_actions[LENS_ACTIONS]
users --> spectrum_session[SPECTRUM_SESSION_ANALYSIS]
users --> spectrum_turn[SPECTRUM_TURN_ANALYSIS]
users --> spectrum_weekly[SPECTRUM_WEEKLY]
users --> spectrum_monthly[SPECTRUM_MONTHLY]
users --> subscriptions[SUBSCRIPTIONS]
users --> entitlement[ENTITLEMENT_SNAPSHOTS]
users --> safety_events[SAFETY_EVENTS]
users --> ai_usage[AI_USAGE_EVENTS]
```
### 8.3 Storage policy
- Raw reflective content remains in transactional tables, encrypted at rest.
- Spectrum dashboard reads aggregated tables only by default.
- Per-session exclusion flags allow users to opt out entries from analytics.
- Hard delete workflow removes raw + derived analytics for requested windows.
## 9. Key Runtime Sequences
### 9.1 Mirror message processing with safety gate
```mermaid
sequenceDiagram
participant App as Mobile App
participant API as Kalei API
participant Safety as Safety Service
participant Ent as Entitlement Service
participant AI as AI Gateway
participant Model as AI Provider
participant DB as PostgreSQL
participant Redis as Redis
App->>API: POST /mirror/messages
API->>Ent: Check plan/quota
Ent->>Redis: Read counters
Ent-->>API: Allowed
API->>Safety: Crisis precheck
alt Crisis detected
Safety->>DB: Insert safety_event
API-->>App: Crisis resources response
else Not crisis
API->>AI: Detect fragments prompt
AI->>Model: Inference request
Model-->>AI: Fragments with confidence
AI-->>API: Validated structured result
API->>DB: Save message + fragments
API->>Redis: Increment usage counters
API-->>App: Highlight payload
end
```
### 9.2 Turn (Kaleidoscope) request
```mermaid
sequenceDiagram
participant App as Mobile App
participant API as Kalei API
participant Ent as Entitlement Service
participant Safety as Safety Service
participant AI as AI Gateway
participant Model as AI Provider
participant DB as PostgreSQL
participant Cost as Cost Guard
App->>API: POST /turns
API->>Ent: Validate tier + daily cap
API->>Safety: Crisis precheck
alt Crisis detected
API-->>App: Crisis resources response
else Safe
API->>AI: Generate 3 reframes + micro-action
AI->>Model: Inference stream
Model-->>AI: Structured reframes
AI-->>API: Response + token usage
API->>Cost: Record token usage + budget check
API->>DB: Save turn + metadata
API-->>App: Stream final turn result
end
```
### 9.3 Weekly Spectrum aggregation (background)
```mermaid
sequenceDiagram
participant Cron as Scheduler
participant Worker as Spectrum Worker
participant DB as PostgreSQL
participant AI as AI Gateway
participant Model as Batch Provider
participant Push as Notification Service
Cron->>Worker: Trigger weekly job
Worker->>DB: Load eligible users + raw events
Worker->>DB: Compute vectors and weekly aggregates
Worker->>AI: Generate insight narratives from aggregates
AI->>Model: Batch request
Model-->>AI: Insight text
AI-->>Worker: Validated summaries
Worker->>DB: Upsert spectrum_weekly and monthly deltas
Worker->>Push: Enqueue spectrum updated notifications
```
## 10. API Surface (v1)
### 10.1 Auth and profile
- `POST /auth/register`
- `POST /auth/login`
- `POST /auth/refresh`
- `POST /auth/logout`
- `GET /me`
- `PATCH /me/profile`
### 10.2 Mirror
- `POST /mirror/sessions`
- `POST /mirror/messages`
- `POST /mirror/fragments/{id}/reframe`
- `POST /mirror/sessions/{id}/close`
- `GET /mirror/sessions`
- `DELETE /mirror/sessions/{id}`
### 10.3 Turn
- `POST /turns`
- `GET /turns`
- `GET /turns/{id}`
- `POST /turns/{id}/save`
### 10.4 Lens
- `POST /lens/goals`
- `GET /lens/goals`
- `POST /lens/goals/{id}/actions`
- `POST /lens/actions/{id}/complete`
- `GET /lens/affirmation/today`
### 10.5 Spectrum
- `GET /spectrum/weekly`
- `GET /spectrum/monthly`
- `POST /spectrum/reset`
- `POST /spectrum/exclusions`
### 10.6 Billing and entitlements
- `POST /billing/webhooks/apple`
- `POST /billing/webhooks/google`
- `GET /billing/entitlements`
## 11. Security, Safety, and Compliance Architecture
### 11.1 Security controls
- TLS everywhere (edge proxy to API origin and service egress).
- JWT access tokens (short TTL) + rotating refresh tokens.
- Password hashing with Argon2id (preferred) or bcrypt with strong cost factor.
- Row ownership checks enforced in API and optionally DB RLS for defense in depth.
- Secrets in environment vault; never in client bundle.
- Audit logging for auth events, entitlement changes, deletes, and safety events.
### 11.2 Data protection
- Encryption at rest for disk volumes and database backups.
- Column-level encryption for highly sensitive text fields (Mirror message content).
- Data minimization for analytics: Spectrum reads vectors and aggregates by default.
- User rights flows: export, per-item delete, account delete, Spectrum reset.
### 11.3 Safety architecture
- Multi-stage crisis filter:
1. Deterministic keyword and pattern pass.
2. Low-latency model confirmation where needed.
3. Hardcoded crisis response templates and hotline resources.
- Crisis-level content is never reframed.
- Safety events are logged and monitored for false-positive/false-negative tuning.
## 12. Reliability and Performance
### 12.1 Initial SLO targets
- API availability: 99.5% monthly at launch, 99.9% target at scale.
- Turn and Mirror response latency:
- p50 < 1.8s
- p95 < 3.5s
- Weekly Spectrum jobs completed within 2 hours of scheduled run.
### 12.2 Resilience patterns
- Idempotency keys on write endpoints.
- AI provider timeout + retry policy with circuit breaker.
- Graceful degradation hierarchy when budget/latency pressure occurs:
1. Degrade Lens generation first (template fallback).
2. Keep Turn and Mirror available.
3. Pause non-critical Spectrum generation if needed.
- Dead-letter queue for failed async jobs.
## 13. Observability and FinOps
### 13.1 Telemetry
- Structured logs with request ID, user ID hash, feature, model, token usage, cost.
- Metrics:
- request rate/error rate/latency by endpoint
- AI token usage and cost by feature
- quota denials and safety escalations
- Tracing across API -> AI Gateway -> provider call.
### 13.2 Cost controls
- Global monthly AI spend cap and alert thresholds (50%, 80%, 95%).
- Per-user daily token budget in Redis.
- Feature-level cost envelope with OpenRouter provider routing:
- All features: DeepSeek V3.2 via DeepInfra/Fireworks (US/EU, $0.26/$0.38 per MTok)
- Automatic failover: Claude Haiku 4.5 on provider outage ($1.00/$5.00 per MTok)
- Future: introduce tiered model routing at 5,000+ DAU when usage data justifies complexity
- Prompt caching for stable system prompts (DeepInfra ~20% cache hit discount).
## 14. Deployment Topology and Scaling Path
### 14.1 Launch deployment (single-node)
```mermaid
flowchart LR
EDGE[Caddy or Nginx Edge] --> NX[Nginx]
NX --> API[API + Workers]
API --> PG[(PostgreSQL)]
API --> R[(Redis)]
API --> AIP[AI Providers]
```
### 14.2 Scaling evolution
```mermaid
flowchart LR
launch[Launch single VPS API DB Redis] --> traction[Traction split DB keep API monolith]
traction --> growth[Growth separate workers and scale API]
growth --> scale[Scale optional service extraction]
```
### 14.3 Trigger-based scaling
- Move DB off app node when p95 query latency > 120ms sustained or storage > 70%.
- Add API replica when CPU > 70% sustained at peak and p95 latency breaches SLO.
- Split workers when Spectrum jobs impact interactive endpoints.
## 15. Delivery Plan
All features ship in a single unified v1 release. The build is a continuous 12-week effort:
### 15.1 Weeks 14: Platform Foundation
- API skeleton, auth, profile, entitlements integration.
- Postgres schema v1 and migrations.
- Mirror + Turn endpoints with safety pre-check.
- Usage metering and rate limiting.
### 15.2 Weeks 58: Core Experience
- Lens flows, Rehearsal, Ritual, Evidence Wall, and Gallery history.
- Push notifications and daily reminders.
- Full observability, alerting, and incident runbooks.
- Beta load testing and security hardening.
### 15.3 Weeks 912: Spectrum & Launch Readiness
- Spectrum: vector extraction pipeline, aggregated tables, weekly batch jobs, dashboard endpoints.
- Data exclusion controls and reset workflow.
- Cost optimization pass on AI routing.
- Final QA, store submission, beta launch.
## 16. Risks and Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| Reframe quality variance by provider/model | Core UX degradation | Keep AI Gateway abstraction + blind quality harness + model canary rollout. |
| Safety false negatives | High trust and user harm risk | Defense-in-depth crisis filter + explicit no-reframe crisis policy + monitoring and review loop. |
| AI cost spikes | Margin compression | Hard spend caps, per-feature budgets, degradation order, model fallback lanes. |
| Single-node bottlenecks | Latency and availability issues | Trigger-based scaling plan and early instrumentation. |
| Sensitive data handling errors | Compliance and trust risk | Encryption, strict retention controls, deletion workflows, audit logs. |
## 17. Decision Log and Open Items
### 17.1 Decided in this plan
- Self-hosted API + Postgres + Redis is the canonical launch architecture.
- AI provider routing is built in from day one.
- Safety is an explicit service and gate on all AI-facing paths.
- Spectrum runs asynchronously over aggregated data.
### 17.2 Resolved: AI Provider Strategy (February 2026)
- **Decided:** DeepSeek V3.2 via OpenRouter, pinned to non-Chinese providers (DeepInfra/Fireworks). Single model for all features at launch. Claude Haiku 4.5 as automatic fallback.
- **Rationale:** 8590% cost reduction vs Claude Haiku. Nature 2025 study confirms comparable emotional intelligence scores. Non-Chinese hosting avoids data sovereignty concerns. Single-model approach minimizes complexity for solo founder.
- **Revisit at:** 600+ DAU (evaluate self-hosting), 5,000+ DAU (evaluate tiered model routing).
### 17.3 Remaining open decisions
- Exact hosting target for DB scaling at traction stage (dedicated VPS vs managed Postgres).
- Regional crisis resource strategy (US-first or multi-region at launch).
---
If approved, this document should become the architecture source of truth and supersede conflicting details in older planning docs.

View File

@@ -0,0 +1,814 @@
# Kalei — User Journey Technical Map
> Version 1.0 — February 2026
> Maps every user-facing flow to backend API endpoints, database operations, frontend components, and AI calls
---
## Architecture Summary
**Backend:** Fastify 5.x (Node.js 22 LTS), Drizzle ORM, PostgreSQL 16, Redis 7
**Frontend:** React Native + Expo SDK 54+, Expo Router, TanStack Query v5, Zustand, MMKV v4
**AI:** DeepSeek V3.2 via OpenRouter (primary, hosted on DeepInfra/Fireworks US/EU), Claude Haiku 4.5 (automatic fallback), provider-agnostic AI Gateway
**Auth:** JWT with refresh token rotation, Apple/Google SSO
**Billing:** Direct App Store / Google Play webhook integration
---
## 1. Authentication & Onboarding
### 1.1 Account Registration
**User Action:** Enters email + password or taps Apple/Google SSO
| Layer | Detail |
|-------|--------|
| **API** | `POST /auth/register` — body: `{ email, password, provider? }` |
| **Validation** | Zod: email format, password 8+ chars, provider enum |
| **DB Write** | `INSERT INTO users (id, email, password_hash, created_at)` |
| **DB Write** | `INSERT INTO profiles (user_id, display_name, coaching_style, onboarding_complete)` |
| **DB Write** | `INSERT INTO subscriptions (user_id, plan, status, started_at)` — defaults to `free` |
| **Redis** | Set `rate:auth:{ip}` with TTL for brute-force protection |
| **Response** | `{ access_token, refresh_token, user: { id, email, profile } }` |
| **Frontend** | `AuthStore.setTokens()` → MMKV encrypted storage → navigate to onboarding |
### 1.2 Token Refresh
| Layer | Detail |
|-------|--------|
| **API** | `POST /auth/refresh` — body: `{ refresh_token }` |
| **DB Read** | `SELECT FROM refresh_tokens WHERE token = $1 AND revoked = false` |
| **DB Write** | Revoke old token, issue new pair (rotation) |
| **Redis** | Invalidate old session cache |
### 1.3 Onboarding Completion
**User Action:** Completes screens 2-9 (style selection, first Turn)
| Layer | Detail |
|-------|--------|
| **API** | `PATCH /me/profile` — body: `{ coaching_style, notification_time, onboarding_complete: true }` |
| **DB Write** | `UPDATE profiles SET coaching_style = $1, notification_time = $2, onboarding_complete = true` |
| **Push** | Schedule first notification via push service at chosen time |
| **Frontend** | `OnboardingStore.complete()` → navigate to main tab navigator |
---
## 2. The Turn (Reframing)
### 2.1 Submit a Turn
**User Action:** Types thought → taps "Turn it"
| Layer | Detail |
|-------|--------|
| **Frontend** | Validate non-empty input, show Turn animation (1.5s kaleidoscope rotation) |
| **API** | `POST /turns` — body: `{ input_text, coaching_style? }` |
| **Rate Check** | Redis: `INCR rate:turns:{user_id}:{date}` → reject if > 3 (free) or > 100 (prism) |
| **Entitlement** | `SELECT plan FROM subscriptions WHERE user_id = $1` → gate check |
| **Safety** | Deterministic keyword scan → if flagged: `INSERT INTO safety_events`, return crisis response |
| **AI Call** | AI Gateway → Claude Haiku 4.5: system prompt (coaching style + reframe instructions) + user input |
| **AI Response** | JSON: `{ perspectives: [{ style, text, emotion_before, emotion_after }], micro_action: { if_clause, then_clause }, fragments_detected: [{ type, phrase, confidence }] }` |
| **DB Write** | `INSERT INTO turns (id, user_id, input_text, perspectives, micro_action, fragments, emotion_vector, created_at)` |
| **DB Write** | `INSERT INTO ai_usage_events (user_id, feature, model, input_tokens, output_tokens, latency_ms, cost_usd)` |
| **Redis** | Increment daily counter, update streak cache |
| **Response** | `{ turn_id, perspectives, micro_action, fragments, pattern_seed }` |
| **Frontend** | Dismiss animation → render 3 perspective cards + micro-action card |
### 2.2 Save a Turn / Keepsake
**User Action:** Taps save on a perspective card
| Layer | Detail |
|-------|--------|
| **API** | `POST /turns/{id}/save` — body: `{ perspective_index, save_type: "keepsake" }` |
| **DB Write** | `UPDATE turns SET saved = true, saved_perspective_index = $1` |
| **DB Write** | `INSERT INTO evidence_wall_tiles (user_id, tile_type, source_feature, source_id, color_accent, created_at)` — type: `saved_keepsake` |
| **Response** | `{ saved: true, gallery_id, evidence_tile_id }` |
| **Frontend** | Success toast "Turn saved" → update Gallery cache via TanStack Query invalidation |
### 2.3 Get Turn History
| Layer | Detail |
|-------|--------|
| **API** | `GET /turns?limit=20&offset=0&date=2026-02-21` |
| **DB Read** | `SELECT id, input_text, perspectives, fragments, created_at FROM turns WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3` |
| **Cache** | Redis: cache recent 20 turns per user, 5 min TTL |
---
## 3. The Mirror (Journaling + Fragment Detection)
### 3.1 Start Mirror Session
**User Action:** Opens Mirror tab → starts new session
| Layer | Detail |
|-------|--------|
| **API** | `POST /mirror/sessions` — body: `{ prompt_id? }` |
| **Rate Check** | Redis: `INCR rate:mirror:{user_id}:{week}` → reject if > 2 (free) |
| **DB Write** | `INSERT INTO mirror_sessions (id, user_id, status, started_at)` — status: `active` |
| **Response** | `{ session_id, opening_prompt }` |
| **Frontend** | Navigate to session view, show opening prompt in AI bubble |
### 3.2 Send Message in Mirror
**User Action:** Types and sends a message
| Layer | Detail |
|-------|--------|
| **Frontend** | Append user message to local state, show AI thinking animation |
| **API** | `POST /mirror/sessions/{id}/messages` — body: `{ content, message_type: "user" }` |
| **DB Write** | `INSERT INTO mirror_messages (id, session_id, role, content, created_at)` |
| **AI Call #1** | Fragment Detection: system prompt (10 distortion types + detection rules) + session context + new message |
| **AI Response #1** | `{ fragments: [{ type, phrase, start_index, end_index, confidence }] }` |
| **Entitlement Gate** | Free: filter to 3 types (catastrophizing, black_and_white, should_statements). Prism: all 10 |
| **DB Write** | `INSERT INTO mirror_fragments (id, session_id, message_id, distortion_type, phrase, start_idx, end_idx, confidence)` |
| **AI Call #2** | Reflective Response: system prompt (warm, non-directive, Mirror voice) + session history + detected fragments |
| **AI Response #2** | `{ response_text, suggested_prompts: [] }` |
| **DB Write** | `INSERT INTO mirror_messages (id, session_id, role: "assistant", content)` |
| **Response** | `{ message_id, ai_response, fragments: [{ type, phrase, indices }] }` |
| **Frontend** | Render AI response bubble, apply amber highlight underlines to user's message at fragment positions |
### 3.3 Tap Fragment Highlight → Inline Reframe
**User Action:** Taps highlighted text in their message
| Layer | Detail |
|-------|--------|
| **Frontend** | Open half-sheet modal with distortion info (local data from fragment detection response) |
| **API** | `POST /mirror/fragments/{id}/reframe` — body: `{ fragment_id }` |
| **Rate Check** | Free: 1 inline reframe per session |
| **AI Call** | Quick reframe: system prompt + fragment context + distortion type |
| **AI Response** | `{ reframes: [{ text, style }], can_turn: true }` |
| **Response** | `{ reframes, turn_prefill }``turn_prefill` is the fragment phrase ready for Turn |
| **Frontend** | Render reframes in half-sheet. "Take to Turn" button navigates to Turn tab with `input_text` pre-filled |
### 3.4 Close Mirror Session → Generate Reflection
**User Action:** Taps "End Session" or navigates away
| Layer | Detail |
|-------|--------|
| **API** | `POST /mirror/sessions/{id}/close` |
| **DB Read** | Fetch all messages + fragments for session |
| **AI Call** | Reflection generation: full session transcript + all fragments → themes, insight, pattern seed |
| **AI Response** | `{ themes: [], fragment_summary: { total, by_type }, insight: "string", pattern_seed: "hash" }` |
| **DB Write** | `UPDATE mirror_sessions SET status = "closed", reflection = $1, pattern_seed = $2, closed_at = NOW()` |
| **DB Write** | `INSERT INTO evidence_wall_tiles (user_id, tile_type: "mirror_reflection", source_id, ...)` |
| **Response** | `{ reflection, pattern_seed, fragment_summary }` |
| **Frontend** | Show reflection card with generated kaleidoscope pattern thumbnail → auto-saved to Gallery |
---
## 4. The Lens (Goals + Actions)
### 4.1 Create Goal
| Layer | Detail |
|-------|--------|
| **API** | `POST /lens/goals` — body: `{ title, description, target_date?, category? }` |
| **AI Call** | Goal refinement: make SMART, suggest metrics, generate initial visualization description |
| **DB Write** | `INSERT INTO lens_goals (id, user_id, title, description, target_date, visualization_text, status, created_at)` |
| **Response** | `{ goal_id, refined_title, visualization, suggested_actions }` |
### 4.2 Get Daily Actions + Affirmation
| Layer | Detail |
|-------|--------|
| **API** | `GET /lens/today` |
| **DB Read** | Active goals + incomplete actions + today's affirmation |
| **AI Call** (if no affirmation cached) | Generate daily affirmation based on active goals + recent progress |
| **Redis** | Cache today's affirmation, 24h TTL |
| **Response** | `{ goals: [...], today_actions: [...], affirmation: { text, goal_id } }` |
### 4.3 Complete Action
| Layer | Detail |
|-------|--------|
| **API** | `POST /lens/actions/{id}/complete` |
| **DB Write** | `UPDATE lens_actions SET completed = true, completed_at = NOW()` |
| **DB Write** | `INSERT INTO evidence_wall_tiles (tile_type: "completed_action", source_id, color_accent: "emerald")` |
| **Redis** | Update streak counter |
| **Response** | `{ completed: true, goal_progress_pct, streak_count, evidence_tile_id }` |
### 4.4 Start Rehearsal Session
**User Action:** On goal detail → taps "Rehearse"
| Layer | Detail |
|-------|--------|
| **API** | `POST /lens/goals/{id}/rehearsal` |
| **Rate Check** | Free: 1/week, Prism: unlimited |
| **DB Read** | Fetch goal details + user's recent Mirror/Turn data for personalization |
| **AI Call** | Visualization script generation: first-person, multi-sensory, process-oriented, obstacle rehearsal |
| **AI Response** | `{ script_segments: [{ text, duration_seconds, breathing_cue? }], total_duration }` |
| **DB Write** | `INSERT INTO rehearsal_sessions (id, user_id, goal_id, script, duration, created_at)` |
| **Response** | `{ session_id, script_segments, total_duration }` |
| **Frontend** | Enter Rehearsal mode: timer ring, text card sequence with breathing animation pacing |
### 4.5 Complete Rehearsal
| Layer | Detail |
|-------|--------|
| **API** | `POST /lens/rehearsals/{id}/complete` |
| **DB Write** | `UPDATE rehearsal_sessions SET completed = true, completed_at = NOW()` |
| **DB Write** | `INSERT INTO evidence_wall_tiles (tile_type: "rehearsal_complete", source_id, color_accent: "amethyst")` |
| **Response** | `{ completed: true, evidence_tile_id }` |
| **Frontend** | Success burst animation → navigate back to goal detail |
---
## 5. The Ritual (Daily Habit Sequences)
### 5.1 Start Ritual
**User Action:** Taps "Start Ritual" on Turn tab or from notification
| Layer | Detail |
|-------|--------|
| **API** | `POST /rituals/start` — body: `{ template: "morning" | "evening" | "quick" }` |
| **Rate Check** | Free: only `quick` template allowed |
| **DB Write** | `INSERT INTO ritual_sessions (id, user_id, template, status, started_at)` |
| **DB Read** | Fetch user's active goals, recent fragments, streak data for personalization |
| **AI Call** | Personalize ritual prompts based on user context |
| **Response** | `{ session_id, steps: [{ type, prompt, duration_seconds }] }` |
| **Frontend** | Enter Ritual mode: fragment-shaped step progress bar, step-by-step flow |
### 5.2 Ritual Step Completion
Each step may trigger its own API call:
| Step | API Calls |
|------|-----------|
| Mirror check-in | `POST /mirror/sessions` + `POST /mirror/sessions/{id}/messages` (lightweight, 1-2 exchanges) |
| Turn | `POST /turns` (with ritual context flag) |
| Lens review | `POST /lens/actions/{id}/complete` (for each completed action) |
| Affirmation | `GET /lens/today` (cached) |
| Gratitude | `POST /rituals/{id}/gratitude` — body: `{ text }` |
### 5.3 Complete Ritual
| Layer | Detail |
|-------|--------|
| **API** | `POST /rituals/{id}/complete` |
| **DB Write** | `UPDATE ritual_sessions SET status = "completed", completed_at = NOW()` |
| **DB Write** | `INSERT INTO evidence_wall_tiles (tile_type: "ritual_complete", color_accent: "amber")` |
| **Redis** | Update ritual streak: `INCR streak:ritual:{user_id}`, check context consistency (same time ± 30 min) |
| **DB Write** | `UPDATE profiles SET ritual_streak = $1, ritual_consistency_score = $2` |
| **Response** | `{ completed: true, streak_count, consistency_score, evidence_tile_id }` |
| **Frontend** | Prismatic ring completion animation → streak view |
---
## 6. Gallery
### 6.1 Get All Patterns
| Layer | Detail |
|-------|--------|
| **API** | `GET /gallery?view=all&limit=20&offset=0` |
| **DB Read** | `SELECT id, source_feature, pattern_seed, preview_text, created_at FROM gallery_items WHERE user_id = $1 ORDER BY created_at DESC` |
| **Cache** | Redis: cache first page, 2 min TTL |
### 6.2 Get Pattern Detail
| Layer | Detail |
|-------|--------|
| **API** | `GET /gallery/{id}` |
| **DB Read** | Full gallery item with source content, fragments, pattern seed |
| **Frontend** | Render hero kaleidoscope pattern (deterministic from seed), source content, metadata |
### 6.3 Search / Filter
| Layer | Detail |
|-------|--------|
| **API** | `GET /gallery/search?q=text&feature=turn&distortion=catastrophizing&from=2026-01-01` |
| **DB Read** | Full-text search on gallery content + filter joins |
### 6.4 Generate Pattern Card (Share)
| Layer | Detail |
|-------|--------|
| **API** | `POST /gallery/{id}/share` |
| **Backend** | Generate Pattern Card image: kaleidoscope pattern + reframe text overlay + Kalei watermark |
| **Response** | `{ share_url, image_url }` |
| **Frontend** | Native share sheet with generated image |
---
## 7. Evidence Wall
### 7.1 Get Evidence Wall
| Layer | Detail |
|-------|--------|
| **API** | `GET /evidence-wall?limit=50` |
| **DB Read** | `SELECT * FROM evidence_wall_tiles WHERE user_id = $1 ORDER BY created_at DESC` |
| **Entitlement** | Free: 30-day window (`WHERE created_at > NOW() - INTERVAL '30 days'`). Prism: all history |
| **Response** | `{ tiles: [...], connections: [...], stage: "empty" | "early" | "mid" | "full" }` |
| **Frontend** | Render mosaic view based on stage, assign tile shapes (diamond, hex, rect, pentagon, triangle) based on tile_type |
### 7.2 Get Tile Detail
| Layer | Detail |
|-------|--------|
| **API** | `GET /evidence-wall/tiles/{id}` |
| **DB Read** | Tile + source data (join to turns/mirror_sessions/lens_actions/ritual_sessions/rehearsal_sessions) |
| **Response** | `{ tile, source_content, source_feature, created_at }` |
| **Frontend** | Half-sheet with tile detail, source content, link to source feature |
### 7.3 Contextual Evidence Surfacing
**Trigger:** AI detects low self-efficacy language in Mirror or Turn
| Layer | Detail |
|-------|--------|
| **AI Detection** | During Mirror fragment detection or Turn processing, flag self-efficacy score < threshold |
| **DB Read** | `SELECT * FROM evidence_wall_tiles WHERE user_id = $1 ORDER BY relevance_score DESC LIMIT 2` |
| **Response** | Included in Mirror/Turn response: `{ evidence_nudge: { tiles: [...], message: "..." } }` |
| **Frontend** | Render gentle card below main content with 1-2 evidence tiles |
---
## 8. Spectrum
### 8.1 Weekly Aggregation (Background Job)
| Layer | Detail |
|-------|--------|
| **Trigger** | Cron: Sunday 6pm UTC |
| **DB Read** | All turns + mirror_sessions + lens_actions for user's week |
| **AI Call** | Batch API (50% cost): analyze emotional vectors, fragment patterns, Turn impact |
| **DB Write** | `INSERT INTO spectrum_weekly (user_id, week_start, river_data, glass_data, impact_data, rhythm_data, growth_score, insight_text)` |
| **Push** | Notification: "Your weekly Spectrum insight is ready" |
### 8.2 Get Spectrum Dashboard
| Layer | Detail |
|-------|--------|
| **API** | `GET /spectrum/dashboard` |
| **Entitlement** | Free: simplified `{ weekly_insight_text, basic_fragment_count }`. Prism: full dashboard |
| **DB Read** | Latest weekly + monthly aggregates |
| **Response** | `{ river, glass, impact, rhythm, growth, weekly_insight, monthly_insight? }` |
| **Frontend** | Render 5 visualization components from spectrum-visualizations.svg data |
### 8.3 Monthly Deep Dive (Background Job)
| Layer | Detail |
|-------|--------|
| **Trigger** | Cron: 1st of month |
| **DB Read** | All weekly aggregates for the month |
| **AI Call** | Batch API: month-over-month narrative generation |
| **DB Write** | `INSERT INTO spectrum_monthly (user_id, month_start, narrative, growth_trajectory, milestone_events)` |
---
## 9. Billing & Entitlements
### 9.1 Entitlement Check (Middleware)
Every rate-limited endpoint runs this check:
```
1. Redis: GET entitlement:{user_id} → if cached, return
2. DB: SELECT plan, status FROM subscriptions WHERE user_id = $1 AND status = 'active'
3. Redis: SET entitlement:{user_id} = plan, EX 300 (5 min cache)
4. Return plan → middleware applies feature gates
```
### 9.2 App Store Webhook
| Layer | Detail |
|-------|--------|
| **API** | `POST /billing/webhooks/apple` |
| **Validation** | Verify Apple JWT signature |
| **DB Write** | `INSERT/UPDATE subscriptions SET plan = $1, status = $2, apple_transaction_id = $3` |
| **Redis** | Invalidate `entitlement:{user_id}` cache |
| **DB Write** | `INSERT INTO entitlement_snapshots (user_id, plan, event_type, timestamp)` |
### 9.3 Google Play Webhook
Same pattern as Apple, with Google-specific JWT validation and `google_purchase_token`.
---
## 10. Safety System
### 10.1 Crisis Detection Pipeline
Every AI-processed input runs through:
```
Stage 1: Deterministic keyword scan (regex, ~1ms)
→ If match: flag, skip AI, return crisis template
Stage 2: AI confirmation (during normal processing)
→ AI output includes safety_flag: boolean
→ If flagged: return hardcoded crisis response (never AI-generated)
Stage 3: Logging
→ INSERT INTO safety_events (user_id, input_hash, detection_stage, action_taken)
→ Alert: send to safety dashboard
```
### 10.2 Crisis Response
| Layer | Detail |
|-------|--------|
| **Response** | Hardcoded template: empathetic acknowledgment + 988 Suicide & Crisis Lifeline + Crisis Text Line |
| **UI** | Full-screen modal with prominent crisis resource links, "I'm OK" dismiss button |
| **Logging** | All crisis events logged, never the content itself |
---
## Database Schema Summary
### Core Tables
| Table | Key Columns | Indexes |
|-------|------------|---------|
| `users` | id (uuid), email, password_hash, created_at | email (unique) |
| `profiles` | user_id (FK), display_name, coaching_style, notification_time, onboarding_complete, ritual_streak, ritual_consistency_score | user_id (unique) |
| `subscriptions` | user_id (FK), plan (enum), status, started_at, expires_at, apple_transaction_id?, google_purchase_token? | user_id, status |
| `refresh_tokens` | id, user_id (FK), token_hash, revoked, expires_at | token_hash, user_id |
### Feature Tables
| Table | Key Columns | Indexes |
|-------|------------|---------|
| `turns` | id, user_id, input_text (encrypted), perspectives (jsonb), micro_action (jsonb), fragments (jsonb), emotion_vector (jsonb), saved, pattern_seed, created_at | user_id + created_at |
| `mirror_sessions` | id, user_id, status, reflection (jsonb), pattern_seed, started_at, closed_at | user_id + status |
| `mirror_messages` | id, session_id (FK), role, content (encrypted), created_at | session_id + created_at |
| `mirror_fragments` | id, session_id (FK), message_id (FK), distortion_type (enum), phrase, start_idx, end_idx, confidence | session_id, distortion_type |
| `lens_goals` | id, user_id, title, description, target_date, visualization_text, status, created_at | user_id + status |
| `lens_actions` | id, goal_id (FK), text, if_clause, then_clause, completed, completed_at | goal_id + completed |
| `rehearsal_sessions` | id, user_id, goal_id (FK), script (jsonb), duration, completed, started_at, completed_at | user_id + goal_id |
| `ritual_sessions` | id, user_id, template (enum), status, steps_completed (jsonb), started_at, completed_at | user_id + created_at |
| `gallery_items` | id, user_id, source_feature (enum), source_id, content_preview, pattern_seed, distortion_types (text[]), created_at | user_id + source_feature, full-text on content_preview |
| `evidence_wall_tiles` | id, user_id, tile_type (enum), source_feature, source_id, color_accent, metadata (jsonb), created_at | user_id + created_at, user_id + tile_type |
### Analytics Tables
| Table | Key Columns | Indexes |
|-------|------------|---------|
| `spectrum_weekly` | id, user_id, week_start, river_data (jsonb), glass_data (jsonb), impact_data (jsonb), rhythm_data (jsonb), growth_score, insight_text | user_id + week_start |
| `spectrum_monthly` | id, user_id, month_start, narrative (text), growth_trajectory (jsonb), milestone_events (jsonb) | user_id + month_start |
| `ai_usage_events` | id, user_id, feature, model, input_tokens, output_tokens, latency_ms, cost_usd, created_at | user_id + feature, created_at |
| `safety_events` | id, user_id, input_hash, detection_stage, action_taken, created_at | user_id, created_at |
### Enum Types
```sql
CREATE TYPE plan_type AS ENUM ('free', 'prism');
CREATE TYPE subscription_status AS ENUM ('active', 'expired', 'canceled', 'trial', 'grace_period');
CREATE TYPE distortion_type AS ENUM ('catastrophizing', 'black_and_white', 'mind_reading', 'fortune_telling', 'personalization', 'discounting_positives', 'emotional_reasoning', 'should_statements', 'labeling', 'overgeneralization');
CREATE TYPE tile_type AS ENUM ('saved_keepsake', 'mirror_reflection', 'completed_action', 'self_correction', 'streak_milestone', 'goal_completion', 'reframe_echo', 'rehearsal_complete', 'ritual_complete');
CREATE TYPE ritual_template AS ENUM ('morning', 'evening', 'quick');
CREATE TYPE source_feature AS ENUM ('turn', 'mirror', 'lens', 'rehearsal', 'ritual');
```
---
## Redis Key Patterns
| Key | Type | TTL | Purpose |
|-----|------|-----|---------|
| `rate:turns:{user_id}:{date}` | counter | 24h | Daily Turn rate limit |
| `rate:mirror:{user_id}:{week}` | counter | 7d | Weekly Mirror rate limit |
| `rate:rehearsal:{user_id}:{week}` | counter | 7d | Weekly Rehearsal rate limit |
| `entitlement:{user_id}` | string | 5 min | Cached subscription plan |
| `streak:daily:{user_id}` | hash | — | Current daily streak count + last active date |
| `streak:ritual:{user_id}` | hash | — | Ritual streak + consistency data |
| `affirmation:{user_id}:{date}` | string | 24h | Today's cached affirmation |
| `turns:recent:{user_id}` | list | 5 min | Cached recent turns |
| `session:{session_id}` | hash | 2h | Active Mirror session context |
---
## AI Gateway Call Patterns
### Cost Tiers
| Feature | Model | Est. Cost/Call | Caching |
|---------|-------|----------------|---------|
| Turn (reframe) | Claude Haiku 4.5 | ~$0.003 | System prompt cached (40% saving) |
| Mirror (fragment detection) | Claude Haiku 4.5 | ~$0.002 | System prompt cached |
| Mirror (reflective response) | Claude Haiku 4.5 | ~$0.003 | System prompt cached |
| Mirror (session reflection) | Claude Haiku 4.5 | ~$0.005 | — |
| Lens (goal refinement) | Claude Haiku 4.5 | ~$0.004 | — |
| Rehearsal (script gen) | Claude Haiku 4.5 | ~$0.008 | — |
| Ritual (personalization) | Claude Haiku 4.5 | ~$0.003 | System prompt cached |
| Spectrum (weekly) | Claude Batch API | ~$0.010 | Batch (50% off) |
| Spectrum (monthly) | Claude Batch API | ~$0.015 | Batch (50% off) |
| Safety (confirmation) | Included in feature call | $0 | Part of existing call |
### Prompt Template Versioning
All prompts stored as versioned templates: `prompts/{feature}/{version}.json`
| Prompt | Current Version |
|--------|----------------|
| turn_reframe | v1.0 |
| mirror_fragment_detect | v1.0 |
| mirror_reflect | v1.0 |
| mirror_session_close | v1.0 |
| lens_goal_refine | v1.0 |
| rehearsal_script | v1.0 |
| ritual_personalize | v1.0 |
| spectrum_weekly | v1.0 |
| spectrum_monthly | v1.0 |
| safety_check | v1.0 |
---
## Frontend Component Architecture
### Navigation
```
AppNavigator (Expo Router)
├── (auth)
│ ├── login.tsx
│ ├── register.tsx
│ └── onboarding/
│ ├── welcome.tsx
│ ├── metaphor.tsx
│ ├── turn-demo.tsx
│ ├── style-select.tsx
│ ├── notifications.tsx
│ └── first-turn.tsx
├── (tabs)
│ ├── turn/
│ │ ├── index.tsx — Turn home + input
│ │ ├── results.tsx — Turn results display
│ │ ├── ritual-select.tsx — Ritual template picker
│ │ └── ritual-flow.tsx — Active ritual flow
│ ├── mirror/
│ │ ├── index.tsx — Session list + new session
│ │ └── session.tsx — Active Mirror session
│ ├── lens/
│ │ ├── index.tsx — Goal dashboard
│ │ ├── create-goal.tsx — 6-step goal creation
│ │ ├── goal-detail.tsx — Goal detail + actions
│ │ └── rehearsal.tsx — Rehearsal session
│ ├── gallery/
│ │ ├── index.tsx — Pattern grid + filters
│ │ └── detail.tsx — Pattern detail + share
│ └── you/
│ ├── index.tsx — Profile + stats
│ ├── evidence-wall.tsx — Evidence Wall mosaic
│ ├── spectrum.tsx — Spectrum dashboard
│ ├── settings.tsx — App settings
│ └── subscription.tsx — Plan management
├── guide/
│ ├── checkin.tsx — Check-in conversation (nested in Lens goal detail)
│ ├── checkin-summary.tsx — Post check-in summary
│ ├── bridge-card.tsx — Cross-feature bridge card (shared component)
│ ├── attention-prompt.tsx — Daily attention prompt card
│ ├── moment-log.tsx — Log a noticed moment
│ ├── evidence-card.tsx — Evidence intervention card (used in Mirror + Turn)
│ └── pulse/
│ ├── index.tsx — Weekly Pulse container (3-step flow)
│ ├── self-report.tsx — Step 1: self-report
│ ├── ai-read.tsx — Step 2: AI observations
│ └── next-focus.tsx — Step 3: next week focus
└── (modals)
├── fragment-detail.tsx — Half-sheet for fragment info
├── upgrade.tsx — Prism upgrade prompt
├── crisis.tsx — Crisis response (safety)
├── share-card.tsx — Pattern card share sheet
└── rate-limit.tsx — Rate limit notice
```
### Shared Components
| Component | Usage | SVG Asset |
|-----------|-------|-----------|
| `FragmentIcon` | Everywhere — the core ◇ | `fragment-icons.svg` |
| `TabBar` | Main navigation | `icons-tab-bar.svg` |
| `DistortionBadge` | Mirror highlights, Gallery filters | `icons-distortions.svg` |
| `ActionIcon` | Buttons, list items, settings | `icons-actions.svg` |
| `KaleidoscopePattern` | Gallery, Turn result, share card | `patterns-kaleidoscope.svg` |
| `ProgressRing` | Lens goals, Rehearsal timer | `progress-indicators.svg` |
| `StepDots` | Lens 6-step, Ritual flow | `progress-indicators.svg` |
| `StreakCalendar` | Ritual tracking, You stats | `progress-indicators.svg` |
| `EvidenceMosaic` | Evidence Wall | `evidence-wall.svg` |
| `LoadingSpinner` | All loading states | `loading-animations.svg` |
| `SkeletonShimmer` | Data loading | `loading-animations.svg` |
| `TurnAnimation` | Turn processing | `loading-animations.svg` |
| `AIThinkingBubble` | Mirror AI processing | `loading-animations.svg` |
| `SuccessBurst` | Completion celebrations | `loading-animations.svg` |
| `BreathingLogo` | Splash, idle states | `loading-animations.svg` |
| `StatusBar` | Device chrome | `device-chrome.svg` |
| `NavHeader` | All screens | `device-chrome.svg` |
| `TabBarFrame` | Tab bar container | `device-chrome.svg` |
| `Toast` | Success/error feedback | `device-chrome.svg` |
| `InputAccessory` | Mirror, Turn text input | `device-chrome.svg` |
| `ShardCluster` | Empty states, backgrounds | `decorative-shards.svg` |
| `PrismaticDivider` | Section separators | `decorative-shards.svg` |
| `CornerAccent` | Card decorations | `decorative-shards.svg` |
| `SpectrumRiver` | Spectrum dashboard | `spectrum-visualizations.svg` |
| `SpectrumGlass` | Spectrum dashboard | `spectrum-visualizations.svg` |
| `SpectrumImpact` | Spectrum dashboard | `spectrum-visualizations.svg` |
| `SpectrumRhythm` | Spectrum dashboard | `spectrum-visualizations.svg` |
| `SpectrumGrowth` | Spectrum dashboard | `spectrum-visualizations.svg` |
| `GuideCard` | Bridge cards, evidence interventions, Guide notices | — (CSS-only prismatic border) |
| `GuideChat` | Check-in conversation interface | — (reuses Mirror chat styling) |
| `PulseScale` | Weekly Pulse self-report (5-point fragment scale) | `fragment-icons.svg` |
---
## 8. The Guide — Technical Specification
### 8.1 Database Schema
```sql
-- Guide check-in conversations
CREATE TABLE guide_checkins (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
goal_id UUID REFERENCES lens_goals(id),
started_at TIMESTAMPTZ DEFAULT NOW(),
ended_at TIMESTAMPTZ,
plan_adjustments JSONB, -- { old_plan, new_plan, reason }
evidence_surfaced JSONB, -- array of proof point IDs shown
summary TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Guide check-in messages (conversation history)
CREATE TABLE guide_checkin_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
checkin_id UUID REFERENCES guide_checkins(id),
role VARCHAR(10) NOT NULL CHECK (role IN ('guide', 'user')),
content TEXT NOT NULL,
sequence_order INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Cross-feature bridge events
CREATE TABLE guide_bridges (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
bridge_type VARCHAR(20) NOT NULL CHECK (bridge_type IN ('discovery', 'reinforcement', 'integration')),
source_feature VARCHAR(20) NOT NULL, -- 'mirror', 'turn', 'lens'
target_feature VARCHAR(20), -- what feature the bridge suggests
trigger_data JSONB, -- { session_ids, turn_ids, theme, confidence }
bridge_content TEXT, -- the displayed bridge text
was_shown BOOLEAN DEFAULT FALSE,
was_acted_on BOOLEAN DEFAULT FALSE,
action_taken VARCHAR(50), -- 'opened_lens', 'started_rehearsal', 'dismissed', etc.
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Daily attention prompts
CREATE TABLE guide_attention_prompts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
goal_id UUID REFERENCES lens_goals(id),
prompt_type VARCHAR(20) NOT NULL CHECK (prompt_type IN ('vision', 'capability', 'awareness', 'action', 'reflection')),
manifestation_step INTEGER NOT NULL CHECK (manifestation_step BETWEEN 2 AND 6),
prompt_text TEXT NOT NULL,
delivered_at TIMESTAMPTZ,
was_acknowledged BOOLEAN DEFAULT FALSE,
moment_logged BOOLEAN DEFAULT FALSE,
moment_text TEXT,
moment_logged_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Evidence interventions
CREATE TABLE guide_evidence_interventions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
trigger_source VARCHAR(20) NOT NULL, -- 'mirror', 'turn'
trigger_session_id UUID, -- mirror_session_id or turn_id
trigger_signals JSONB, -- { signals detected: helplessness_language, etc. }
evidence_shown JSONB, -- array of proof point IDs surfaced
intervention_text TEXT,
was_shown BOOLEAN DEFAULT FALSE,
was_acted_on BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Weekly pulse check-ins
CREATE TABLE guide_pulses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
week_start DATE NOT NULL,
self_report_score INTEGER CHECK (self_report_score BETWEEN 1 AND 5),
self_report_text TEXT,
ai_observations JSONB, -- array of { observation, source, accent_color }
divergence_note TEXT, -- when self-report != AI read
next_week_focus JSONB, -- array of { suggestion, feature, reasoning }
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, week_start)
);
```
### 8.2 API Endpoints
| Endpoint | Method | Description | Auth | Rate Limit |
|----------|--------|-------------|------|------------|
| `/guide/checkins/:goalId` | GET | Get check-in history for a goal | JWT | — |
| `/guide/checkins/:goalId/start` | POST | Start a new check-in conversation | JWT | Free: 1/mo/goal, Prism: unlimited |
| `/guide/checkins/:checkinId/message` | POST | Send a message in check-in conversation | JWT | — |
| `/guide/checkins/:checkinId/complete` | POST | End check-in, generate summary | JWT | — |
| `/guide/bridges` | GET | Get pending bridge cards for user | JWT | — |
| `/guide/bridges/:id/action` | POST | Record bridge action (shown, acted_on, dismissed) | JWT | — |
| `/guide/bridges/scan` | POST | Trigger cross-feature pattern scan (background job) | JWT | Auto: daily |
| `/guide/prompts/today` | GET | Get today's attention prompt | JWT | — |
| `/guide/prompts/:id/acknowledge` | POST | Mark prompt as seen | JWT | — |
| `/guide/prompts/:id/log-moment` | POST | Log a noticed moment | JWT | — |
| `/guide/prompts/generate` | POST | Generate next prompt (background job) | JWT | Auto: daily |
| `/guide/evidence/check` | POST | Check for evidence intervention triggers | JWT | Auto: after Mirror/Turn |
| `/guide/evidence/:id/action` | POST | Record intervention action | JWT | — |
| `/guide/pulse/current` | GET | Get current week's pulse (or create if needed) | JWT | — |
| `/guide/pulse/:id/self-report` | POST | Submit self-report score + text | JWT | 1/week |
| `/guide/pulse/:id/ai-read` | GET | Generate AI observations for the week | JWT | Prism only |
| `/guide/pulse/:id/focus` | GET | Generate next-week focus suggestions | JWT | Prism only |
| `/guide/pulse/:id/complete` | POST | Mark pulse as complete | JWT | — |
### 8.3 AI Pipeline
The Guide requires a cross-feature AI analysis pipeline that differs from single-feature calls:
**Cross-Feature Context Window:**
For check-ins, bridges, and the weekly pulse, the AI needs context from multiple features simultaneously:
```
Guide AI Context = {
user_profile: { coaching_style, member_since, preferences },
active_goals: [ { goal, milestones, if_then_plans, progress } ],
recent_mirror_sessions: [ last 7 days — themes, fragment types, emotional tone ],
recent_turns: [ last 7 days — topics, distortions, saved keepsakes ],
evidence_wall: [ last 30 days — proof points by type and source ],
ritual_data: { streak, consistency_score, last_completed },
previous_checkins: [ last 2 check-ins per goal — summaries only ],
previous_pulse: { last week's scores and focus areas }
}
```
**Token estimate for cross-feature context:** ~1,500-2,000 input tokens. With prompt caching (system prompt + stable user context cached), effective billable input drops to ~800-1,200 tokens per Guide call.
**Background Jobs:**
| Job | Schedule | Purpose | AI Model |
|-----|----------|---------|----------|
| Bridge Pattern Scan | Daily (2am user-local) | Analyze 7-day Mirror/Turn window for cross-feature patterns | Haiku 4.5 Batch |
| Attention Prompt Generation | Daily (5am user-local) | Generate personalized prompt based on goal progress and manifestation step | Haiku 4.5 (cached) |
| Evidence Trigger Check | After each Mirror session / Turn | Check session language for self-efficacy dip signals | Haiku 4.5 (inline, fast) |
| Weekly Pulse AI Read | On pulse open (lazy) | Generate weekly observations from all feature data | Haiku 4.5 |
**Prompt Engineering Notes:**
The Guide's system prompt must enforce:
- Evidence-first framing (always lead with what went well)
- Specific data references (numbers, dates, not vague encouragement)
- Collaborative tone (propose adjustments, don't dictate)
- Forward momentum (always end with next action)
- Cross-feature awareness (reference Mirror/Turn patterns when coaching on Lens goals)
- The user's selected coaching style (brutal honesty, gentle guidance, logical analysis, etc.)
### 8.4 Frontend Component Tree
```
screens/guide/
├── GuideCheckinScreen.tsx — Check-in conversation (within Lens goal detail)
├── GuideCheckinSummary.tsx — Post check-in summary card
├── GuideBridgeCard.tsx — Reusable bridge card component (3 variants)
├── GuideAttentionPrompt.tsx — Daily prompt card (within Lens dashboard)
├── GuideMomentLog.tsx — Log a noticed moment screen
├── GuideEvidenceCard.tsx — Evidence intervention card (used in Mirror + Turn)
├── GuidePulseScreen.tsx — 3-step weekly pulse flow
│ ├── PulseSelfReport.tsx — Step 1: self-report scale
│ ├── PulseAIRead.tsx — Step 2: AI observations
│ └── PulseNextFocus.tsx — Step 3: next week suggestions
└── shared/
├── GuideBorder.tsx — Prismatic gradient border component
├── GuideIcon.tsx — Faceted diamond with directional pulse
└── FragmentScale.tsx — 5-point fragment glow scale (for Pulse)
```
### 8.5 Rate Limiting (Guide-Specific)
| Feature | Free | Prism |
|---------|------|-------|
| Goal check-ins | `rate:guide:checkin:{userId}:{goalId}` — 1/month | Unlimited |
| Cross-feature bridges | Discovery only. `rate:guide:bridge:{userId}` — 1/day | All types, 1/day |
| Attention prompts | `rate:guide:prompt:{userId}` — 3/week | Daily |
| Evidence interventions | Blocked (Prism feature) | `rate:guide:evidence:{userId}` — 1/session |
| Weekly pulse | Self-report only | Full 3-step with AI read |
### 8.6 Push Notifications (Guide-Specific)
| Notification | Trigger | Copy |
|---|---|---|
| Check-in reminder | Scheduled per goal settings | "Time to check in on [goal name]. How's it going?" |
| Attention prompt | Daily at user's chosen time | "Your Lens has a focus for today." |
| Bridge surfaced | After daily bridge scan finds match | "◇ A pattern is forming. Take a look." |
| Weekly pulse | User's chosen day (default Sunday 7pm) | "Your weekly Pulse is ready." |
| Moment log reminder | 6 hours after prompt acknowledgment | "Did you notice anything today?" |

BIN
exec-summary-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

View File

@@ -0,0 +1,42 @@
# Kalei SVG Asset Library — Index
All assets derive their visual language from the **soft-elegance-final.svg** logo: crystalline geometry, jewel tone gradients, glow filters, breathing animations, and prismatic color cycling.
## Files
| File | Description | Elements |
|------|-------------|----------|
| `design-system.css` | Shared CSS with all design tokens, components, animations | Colors, typography, buttons, cards, inputs, utilities |
| `icons-tab-bar.svg` | 5 navigation tab icons | Turn ◇, Mirror ✦, Lens ◎, Gallery ▦, You ● — active + inactive states |
| `icons-distortions.svg` | 10 cognitive distortion type icons | Catastrophizing, Black-and-White, Mind Reading, Fortune Telling, Personalization, Discounting Positives, Emotional Reasoning, Should Statements, Labeling, Overgeneralization |
| `icons-actions.svg` | 30 action/UI icons | Navigation, actions, status, feature-specific, media controls |
| `fragment-icons.svg` | Fragment ◇ variants + highlight effects | 5 sizes (xs-xl), 8 color states, highlight underline, highlight bubble |
| `decorative-shards.svg` | Background decorative elements | Breathing aura, floating shards, prismatic divider, corner accent, hero shard, shard cluster, orbiting motes, edge shimmer |
| `patterns-kaleidoscope.svg` | 7 kaleidoscope pattern variants | Turn pattern, Mirror pattern, Ritual pattern, Simple, Complex, Thumbnail, Hero |
| `progress-indicators.svg` | 8 progress visualization types | Large/small rings, linear bar, step dots, streak calendar, ritual bar, timer, evidence counter |
| `evidence-wall.svg` | Evidence Wall mosaic system | 5 tile shapes, 3 mosaic stages (early/mid/full), connection lines, empty state |
| `loading-animations.svg` | 10 loading/animation states | Spinners, skeleton shimmers, Turn animation, breathing logo, AI thinking, success burst |
| `device-chrome.svg` | 8 device frame elements | Status bar, tab bar frame, nav header, iOS keyboard, device frame, modal scrim, toast, input accessory |
| `spectrum-visualizations.svg` | 5 Spectrum analytics visualizations | The River, Your Glass, Turn Impact, Rhythm Detection, Growth Trajectory |
## Color Reference
| Token | Hex | Usage |
|-------|-----|-------|
| Void | `#050508` | Deepest background |
| Kalei Navy | `#0A0E1A` | Primary surfaces |
| Deep Glass | `#121628` | Elevated surfaces, inputs |
| Twilight | `#1C2240` | Borders, dividers |
| Amethyst | `#8B5CF6` | Primary accent, Turn |
| Sapphire | `#3B82F6` | Info, Gallery |
| Emerald | `#10B981` | Success, Lens |
| Amber | `#F59E0B` | Fragments, Mirror |
| Rose | `#EC4899` | Social, sharing |
| Ruby | `#EF4444` | Error, destructive |
| Indigo | `#6366F1` | Extended palette |
## Total Asset Count
- **11 SVG files** containing **100+ individual elements**
- **1 CSS design system** with complete component library
- All assets use consistent prismatic color palette and crystalline design language

View File

@@ -0,0 +1,299 @@
<svg viewBox="0 0 700 880" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"><animate attributeName="stop-color" values="#C4B5FD;#DDD6FE;#C4B5FD" dur="4s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<linearGradient id="grIndigo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A5B4FC"/><stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<linearGradient id="prismaticH" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8B5CF6"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="8s" repeatCount="indefinite"/></stop>
<stop offset="50%" stop-color="#10B981"><animate attributeName="stop-color" values="#10B981;#F59E0B;#EC4899;#8B5CF6;#3B82F6;#10B981" dur="8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#EC4899"><animate attributeName="stop-color" values="#EC4899;#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899" dur="8s" repeatCount="indefinite"/></stop>
</linearGradient>
<radialGradient id="auraGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#8B5CF6" stop-opacity="0.15"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="10s" repeatCount="indefinite"/></stop>
<stop offset="60%" stop-color="#8B5CF6" stop-opacity="0.04"/>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"/>
</radialGradient>
<filter id="glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="8" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowXl" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="14" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="shimmer" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
.section-lbl { font-size: 11px; fill: #64748B; font-weight: 600; letter-spacing: 0.08em; }
</style>
</defs>
<rect width="700" height="880" fill="#050508"/>
<text x="350" y="28" font-size="13" font-weight="700" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">DECORATIVE SHARD ELEMENTS</text>
<text x="350" y="44" font-size="9" fill="#475569" text-anchor="middle">Background auras, floating shards, prismatic dividers, corner accents, edge shimmers</text>
<!-- 1. BREATHING AURA -->
<text x="24" y="74" class="section-lbl">BREATHING AURA</text>
<line x1="24" y1="80" x2="670" y2="80" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(175, 160)">
<circle r="70" fill="url(#auraGrad)">
<animate attributeName="r" values="60;75;60" dur="6s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.6;1;0.6" dur="6s" repeatCount="indefinite"/>
</circle>
<circle r="40" fill="#8B5CF6" opacity="0.05" filter="url(#glowXl)">
<animate attributeName="r" values="35;45;35" dur="4s" repeatCount="indefinite"/>
</circle>
<text x="0" y="100" class="lbl">Behind cards, hero sections</text>
</g>
<!-- 2. FLOATING SHARDS -->
<g transform="translate(475, 100)">
<text x="0" y="-14" class="section-lbl" text-anchor="middle">FLOATING SHARDS</text>
<!-- Shard 1 - large, slow drift -->
<g filter="url(#glowMd)">
<path d="M 0,-18 L 10,-4 L 2,16 L -8,6 Z" fill="url(#grAmethyst)" opacity="0.25">
<animate attributeName="opacity" values="0.15;0.35;0.15" dur="7s" repeatCount="indefinite"/>
</path>
<path d="M 0,-18 L 10,-4 L -8,6 Z" fill="#fff" opacity="0.06"/>
<animateTransform attributeName="transform" type="translate" values="0,0;5,-8;0,0" dur="12s" repeatCount="indefinite"/>
</g>
<!-- Shard 2 - medium, angled -->
<g transform="translate(50, 30)" filter="url(#glowSm)">
<path d="M 0,-10 L 7,2 L -2,12 L -6,0 Z" fill="url(#grSapphire)" opacity="0.2">
<animate attributeName="opacity" values="0.1;0.3;0.1" dur="5s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="50,30;55,22;50,30" dur="9s" repeatCount="indefinite"/>
</g>
<!-- Shard 3 - small, faster -->
<g transform="translate(-40, 50)" filter="url(#glowSm)">
<path d="M 0,-6 L 5,0 L 0,8 L -4,2 Z" fill="url(#grEmerald)" opacity="0.2">
<animate attributeName="opacity" values="0.1;0.25;0.1" dur="4s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="-40,50;-35,42;-40,50" dur="7s" repeatCount="indefinite"/>
</g>
<!-- Shard 4 - tiny mote -->
<g transform="translate(30, -20)" filter="url(#shimmer)">
<path d="M 0,-4 L 3,0 L 0,4 L -3,0 Z" fill="url(#grRose)" opacity="0.3">
<animate attributeName="opacity" values="0.15;0.4;0.15" dur="3s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="30,-20;33,-25;30,-20" dur="5s" repeatCount="indefinite"/>
</g>
<!-- Shard 5 - amber fleck -->
<g transform="translate(-60, -10)" filter="url(#shimmer)">
<path d="M 0,-5 L 4,0 L 0,5 L -4,0 Z" fill="url(#grAmber)" opacity="0.2">
<animate attributeName="opacity" values="0.1;0.3;0.1" dur="6s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="-60,-10;-58,-16;-60,-10" dur="8s" repeatCount="indefinite"/>
</g>
<text x="0" y="100" class="lbl">Scattered across backgrounds, parallax layers</text>
</g>
<!-- 3. PRISMATIC DIVIDER -->
<text x="24" y="274" class="section-lbl">PRISMATIC DIVIDER</text>
<line x1="24" y1="280" x2="670" y2="280" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 320)" filter="url(#glowMd)">
<!-- Main prismatic line -->
<line x1="-280" y1="0" x2="280" y2="0" stroke="url(#prismaticH)" stroke-width="1.5" opacity="0.6"/>
<!-- Glow underneath -->
<line x1="-280" y1="0" x2="280" y2="0" stroke="url(#prismaticH)" stroke-width="4" opacity="0.15"/>
<!-- Center diamond accent -->
<path d="M 0,-6 L 6,0 L 0,6 L -6,0 Z" fill="url(#grAmethyst)" opacity="0.7" filter="url(#shimmer)">
<animate attributeName="opacity" values="0.5;0.8;0.5" dur="3s" repeatCount="indefinite"/>
</path>
<path d="M 0,-6 L 6,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<!-- Shimmer sweep -->
<rect x="-280" y="-1" width="40" height="2" fill="#fff" opacity="0" rx="1">
<animate attributeName="opacity" values="0;0.3;0" dur="3s" repeatCount="indefinite"/>
<animate attributeName="x" values="-280;280" dur="3s" repeatCount="indefinite"/>
</rect>
<!-- End shards -->
<g transform="translate(-280, 0)" filter="url(#glowSm)">
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="#8B5CF6" opacity="0.4"/>
</g>
<g transform="translate(280, 0)" filter="url(#glowSm)">
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="#EC4899" opacity="0.4"/>
</g>
<text x="0" y="30" class="lbl">Section divider with cycling prismatic gradient</text>
</g>
<!-- 4. CORNER ACCENTS -->
<text x="24" y="384" class="section-lbl">CORNER ACCENTS</text>
<line x1="24" y1="390" x2="670" y2="390" stroke="#1C2240" stroke-width="0.5"/>
<!-- Demo card with corner accents -->
<g transform="translate(100, 420)">
<rect width="200" height="120" rx="12" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.8"/>
<!-- Top-left corner -->
<g filter="url(#glowSm)">
<path d="M 0,20 L 0,0 L 20,0" fill="none" stroke="#8B5CF6" stroke-width="1" opacity="0.5"/>
<path d="M 0,-2 L 4,2 L 0,6 L -4,2 Z" fill="url(#grAmethyst)" opacity="0.6" transform="translate(0,0)"/>
</g>
<!-- Top-right corner -->
<g filter="url(#glowSm)" transform="translate(200, 0)">
<path d="M 0,20 L 0,0 L -20,0" fill="none" stroke="#3B82F6" stroke-width="1" opacity="0.5"/>
<path d="M 0,-2 L 4,2 L 0,6 L -4,2 Z" fill="url(#grSapphire)" opacity="0.6"/>
</g>
<!-- Bottom-left corner -->
<g filter="url(#glowSm)" transform="translate(0, 120)">
<path d="M 0,-20 L 0,0 L 20,0" fill="none" stroke="#10B981" stroke-width="1" opacity="0.5"/>
<path d="M 0,-2 L 4,2 L 0,6 L -4,2 Z" fill="url(#grEmerald)" opacity="0.6"/>
</g>
<!-- Bottom-right corner -->
<g filter="url(#glowSm)" transform="translate(200, 120)">
<path d="M 0,-20 L 0,0 L -20,0" fill="none" stroke="#F59E0B" stroke-width="1" opacity="0.5"/>
<path d="M 0,-2 L 4,2 L 0,6 L -4,2 Z" fill="url(#grAmber)" opacity="0.6"/>
</g>
<text x="100" y="65" font-size="10" fill="#475569" text-anchor="middle">Card Content</text>
<text x="100" y="152" class="lbl">Corner accent marks on cards</text>
</g>
<!-- 5. HERO SHARD BURST -->
<g transform="translate(475, 490)">
<text x="0" y="-70" class="section-lbl" text-anchor="middle">HERO SHARD BURST</text>
<!-- Central glow -->
<circle r="30" fill="#8B5CF6" opacity="0.08" filter="url(#glowXl)">
<animate attributeName="r" values="25;35;25" dur="5s" repeatCount="indefinite"/>
</circle>
<!-- Radiating shards -->
<g filter="url(#glowMd)">
<path d="M 0,-40 L 4,-20 L 0,-15 L -4,-20 Z" fill="url(#grAmethyst)" opacity="0.5">
<animate attributeName="opacity" values="0.3;0.6;0.3" dur="4s" repeatCount="indefinite"/>
</path>
<path d="M 35,-20 L 20,-16 L 15,-20 L 20,-24 Z" fill="url(#grSapphire)" opacity="0.4" transform="rotate(30)">
<animate attributeName="opacity" values="0.2;0.5;0.2" dur="4.5s" repeatCount="indefinite"/>
</path>
<path d="M 40,0 L 22,4 L 18,0 L 22,-4 Z" fill="url(#grEmerald)" opacity="0.4">
<animate attributeName="opacity" values="0.25;0.5;0.25" dur="5s" repeatCount="indefinite"/>
</path>
<path d="M 20,35 L 16,18 L 20,14 L 24,18 Z" fill="url(#grAmber)" opacity="0.35" transform="rotate(-20)">
<animate attributeName="opacity" values="0.2;0.45;0.2" dur="3.8s" repeatCount="indefinite"/>
</path>
<path d="M -35,20 L -18,16 L -14,20 L -18,24 Z" fill="url(#grRose)" opacity="0.35">
<animate attributeName="opacity" values="0.2;0.5;0.2" dur="4.2s" repeatCount="indefinite"/>
</path>
<path d="M -20,-35 L -16,-18 L -20,-14 L -24,-18 Z" fill="url(#grIndigo)" opacity="0.4">
<animate attributeName="opacity" values="0.25;0.55;0.25" dur="3.5s" repeatCount="indefinite"/>
</path>
</g>
<!-- Orbiting mote -->
<g filter="url(#shimmer)">
<circle r="2" fill="#A78BFA" opacity="0">
<animateMotion dur="8s" repeatCount="indefinite" path="M0,-50 A50,50 0 1 1 -0.1,-50"/>
<animate attributeName="opacity" values="0;0.8;0;0;0" dur="8s" repeatCount="indefinite"/>
</circle>
<circle r="1.5" fill="#3B82F6" opacity="0">
<animateMotion dur="6s" repeatCount="indefinite" path="M0,-45 A45,45 0 1 0 -0.1,-45"/>
<animate attributeName="opacity" values="0;0;0.7;0;0" dur="6s" repeatCount="indefinite"/>
</circle>
</g>
<text x="0" y="80" class="lbl">Hero/splash background accent</text>
</g>
<!-- 6. SHARD CLUSTER -->
<text x="24" y="604" class="section-lbl">SHARD CLUSTER</text>
<line x1="24" y1="610" x2="670" y2="610" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(175, 690)">
<g filter="url(#glowMd)">
<!-- Primary large shard -->
<path d="M 0,-20 L 12,0 L 0,20 L -12,0 Z" fill="url(#grAmethyst)" opacity="0.5">
<animate attributeName="opacity" values="0.4;0.6;0.4" dur="5s" repeatCount="indefinite"/>
</path>
<path d="M 0,-20 L 12,0 L 0,0 Z" fill="#fff" opacity="0.1"/>
</g>
<g filter="url(#glowSm)">
<!-- Secondary shard, offset -->
<path d="M 18,-12 L 26,-2 L 20,10 L 14,0 Z" fill="url(#grSapphire)" opacity="0.35">
<animate attributeName="opacity" values="0.25;0.45;0.25" dur="4s" begin="0.5s" repeatCount="indefinite"/>
</path>
<!-- Tertiary small shard -->
<path d="M -16,8 L -10,14 L -14,22 L -20,16 Z" fill="url(#grEmerald)" opacity="0.3">
<animate attributeName="opacity" values="0.2;0.4;0.2" dur="3.5s" begin="1s" repeatCount="indefinite"/>
</path>
<!-- Tiny accent shards -->
<path d="M 8,-18 L 12,-14 L 10,-10 L 6,-14 Z" fill="url(#grAmber)" opacity="0.25"/>
<path d="M -20,-8 L -16,-12 L -12,-8 L -16,-4 Z" fill="url(#grRose)" opacity="0.2"/>
</g>
<text x="0" y="50" class="lbl">Decorative cluster for empty states, backgrounds</text>
</g>
<!-- 7. EDGE SHIMMER -->
<g transform="translate(475, 660)">
<text x="0" y="-34" class="section-lbl" text-anchor="middle">EDGE SHIMMER</text>
<!-- Demo card edge -->
<rect x="-80" y="-20" width="160" height="80" rx="10" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.8"/>
<!-- Left edge shimmer -->
<line x1="-80" y1="-18" x2="-80" y2="58" stroke="url(#prismaticH)" stroke-width="1.5" opacity="0.3"/>
<!-- Shimmer travel -->
<rect x="-81" y="-20" width="2" height="16" rx="1" fill="#fff" opacity="0">
<animate attributeName="opacity" values="0;0.4;0" dur="2.5s" repeatCount="indefinite"/>
<animate attributeName="y" values="-20;60" dur="2.5s" repeatCount="indefinite"/>
</rect>
<!-- Top edge subtle shimmer -->
<line x1="-78" y1="-20" x2="78" y2="-20" stroke="#8B5CF6" stroke-width="0.5" opacity="0.15"/>
<rect x="-78" y="-21" width="24" height="2" rx="1" fill="#C4B5FD" opacity="0">
<animate attributeName="opacity" values="0;0.25;0" dur="3s" repeatCount="indefinite"/>
<animate attributeName="x" values="-78;78" dur="3s" repeatCount="indefinite"/>
</rect>
<text x="0" y="20" font-size="10" fill="#475569" text-anchor="middle">Card Content</text>
<text x="0" y="90" class="lbl">Edge shimmer on focused/hovered cards</text>
</g>
<!-- 8. ORBITING MOTES (standalone) -->
<text x="24" y="784" class="section-lbl">ORBITING MOTES</text>
<line x1="24" y1="790" x2="670" y2="790" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 840)" filter="url(#shimmer)">
<!-- Mote orbit paths (faint) -->
<circle r="30" fill="none" stroke="#1C2240" stroke-width="0.3" stroke-dasharray="2 4"/>
<circle r="22" fill="none" stroke="#1C2240" stroke-width="0.3" stroke-dasharray="2 4"/>
<!-- Orbiting motes -->
<circle r="2.5" fill="#A78BFA" opacity="0">
<animateMotion dur="5s" repeatCount="indefinite" path="M0,-30 A30,30 0 1 1 -0.1,-30"/>
<animate attributeName="opacity" values="0;0.9;0;0;0" dur="5s" repeatCount="indefinite"/>
</circle>
<circle r="2" fill="#3B82F6" opacity="0">
<animateMotion dur="7s" repeatCount="indefinite" path="M0,-30 A30,30 0 1 0 -0.1,-30"/>
<animate attributeName="opacity" values="0;0;0.8;0;0" dur="7s" repeatCount="indefinite"/>
</circle>
<circle r="1.5" fill="#10B981" opacity="0">
<animateMotion dur="4s" repeatCount="indefinite" path="M0,-22 A22,22 0 1 1 -0.1,-22"/>
<animate attributeName="opacity" values="0;0.7;0;0.5;0" dur="4s" repeatCount="indefinite"/>
</circle>
<circle r="2" fill="#F59E0B" opacity="0">
<animateMotion dur="6s" repeatCount="indefinite" path="M0,-22 A22,22 0 1 0 -0.1,-22"/>
<animate attributeName="opacity" values="0;0;0;0.9;0" dur="6s" repeatCount="indefinite"/>
</circle>
<circle r="1" fill="#EC4899" opacity="0">
<animateMotion dur="9s" repeatCount="indefinite" path="M0,-30 A30,30 0 1 1 -0.1,-30" begin="2s"/>
<animate attributeName="opacity" values="0;0.8;0;0;0.5;0" dur="9s" repeatCount="indefinite"/>
</circle>
<text x="0" y="52" class="lbl">Floating motes around logos, loaders, hero elements</text>
</g>
<text x="350" y="874" font-size="8" fill="#334155" text-anchor="middle">Decorative elements use breathing animations, prismatic color cycling, glow filters from the soft-elegance visual language</text>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,762 @@
/* ============================================
KALEI DESIGN SYSTEM
Extracted from soft-elegance-final.svg
============================================ */
/* --- CSS Custom Properties (Design Tokens) --- */
:root {
/* Backgrounds */
--void: #050508;
--kalei-navy: #0A0E1A;
--deep-glass: #121628;
--twilight: #1C2240;
/* Jewel Tones */
--amethyst: #8B5CF6;
--amethyst-light: #A78BFA;
--amethyst-dark: #6D28D9;
--sapphire: #3B82F6;
--sapphire-light: #60A5FA;
--sapphire-dark: #1D4ED8;
--emerald: #10B981;
--emerald-light: #34D399;
--emerald-dark: #047857;
--ruby: #EF4444;
--amber: #F59E0B;
--amber-light: #FCD34D;
--amber-dark: #B45309;
--rose: #EC4899;
--rose-light: #F9A8D4;
--indigo: #6366F1;
--indigo-light: #818CF8;
/* Text */
--pure-light: #FFFFFF;
--soft-light: #E2E8F0;
--dim-light: #94A3B8;
--faint-light: #475569;
/* Prismatic Gradient */
--prismatic: linear-gradient(135deg, #8B5CF6, #3B82F6, #10B981, #F59E0B, #EC4899);
--prismatic-subtle: linear-gradient(135deg, rgba(139,92,246,0.15), rgba(59,130,246,0.15), rgba(16,185,129,0.15), rgba(245,158,11,0.15), rgba(236,72,153,0.15));
/* Typography */
--font-primary: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-display: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
/* Spacing */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
/* Radii */
--radius-sm: 8px;
--radius-md: 10px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-2xl: 20px;
--radius-full: 9999px;
/* Shadows (glow-based, not drop-shadow) */
--glow-amethyst: 0 0 20px rgba(139, 92, 246, 0.3);
--glow-sapphire: 0 0 20px rgba(59, 130, 246, 0.3);
--glow-emerald: 0 0 20px rgba(16, 185, 129, 0.3);
--glow-amber: 0 0 20px rgba(245, 158, 11, 0.3);
--glow-rose: 0 0 20px rgba(236, 72, 153, 0.3);
--glow-white: 0 0 16px rgba(255, 255, 255, 0.15);
/* Device frame */
--device-width: 390px;
--device-height: 844px;
--status-bar-height: 54px;
--tab-bar-height: 83px;
--nav-header-height: 56px;
}
/* --- Reset & Base --- */
* { margin: 0; padding: 0; box-sizing: border-box; }
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
body {
background: #000;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: var(--font-primary);
color: var(--pure-light);
-webkit-font-smoothing: antialiased;
}
/* --- Device Frame --- */
.device-frame {
width: var(--device-width);
height: var(--device-height);
background: var(--void);
border-radius: 44px;
overflow: hidden;
position: relative;
box-shadow: 0 0 0 2px rgba(255,255,255,0.06), 0 20px 60px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
transform: scale(var(--device-scale, 1.2));
transform-origin: center center;
}
/* --- Status Bar --- */
.status-bar {
height: var(--status-bar-height);
padding: 14px 28px 0;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
background: transparent;
position: relative;
z-index: 10;
}
.status-bar .time {
font-size: 15px;
font-weight: 600;
color: var(--pure-light);
}
.status-bar .icons {
display: flex;
gap: 6px;
align-items: center;
}
.status-bar .icons svg { width: 16px; height: 16px; }
/* --- Navigation Header --- */
.nav-header {
height: var(--nav-header-height);
padding: 0 var(--space-4);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
position: relative;
z-index: 10;
}
.nav-header .nav-title {
font-size: 20px;
font-weight: 600;
color: var(--pure-light);
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.nav-header .nav-back {
display: flex;
align-items: center;
gap: 4px;
color: var(--amethyst-light);
font-size: 16px;
cursor: pointer;
text-decoration: none;
}
.nav-header .nav-back svg {
width: 20px;
height: 20px;
}
.nav-header .nav-action {
color: var(--dim-light);
cursor: pointer;
}
/* --- Screen Content Area --- */
.screen-content {
flex: 1;
overflow-y: auto;
padding: 0 var(--space-4);
position: relative;
}
.screen-content::-webkit-scrollbar { display: none; }
.screen-content.no-pad { padding: 0; }
.screen-content.centered {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
/* --- Tab Bar --- */
.tab-bar {
height: var(--tab-bar-height);
background: var(--kalei-navy);
border-top: 1px solid var(--twilight);
display: flex;
align-items: flex-start;
justify-content: space-around;
padding-top: 8px;
flex-shrink: 0;
position: relative;
z-index: 10;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
cursor: pointer;
text-decoration: none;
min-width: 60px;
}
.tab-item svg {
width: 24px;
height: 24px;
}
.tab-item .tab-label {
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.tab-item.inactive svg { fill: var(--dim-light); stroke: var(--dim-light); }
.tab-item.inactive .tab-label { color: var(--faint-light); }
.tab-item.active .tab-label { color: var(--pure-light); }
/* Active tab colors by type */
.tab-item.active[data-tab="turn"] svg { fill: var(--amethyst); stroke: var(--amethyst); }
.tab-item.active[data-tab="turn"] .tab-label { color: var(--amethyst-light); }
.tab-item.active[data-tab="mirror"] svg { fill: var(--amber); stroke: var(--amber); }
.tab-item.active[data-tab="mirror"] .tab-label { color: var(--amber-light); }
.tab-item.active[data-tab="lens"] svg { fill: var(--emerald); stroke: var(--emerald); }
.tab-item.active[data-tab="lens"] .tab-label { color: var(--emerald-light); }
.tab-item.active[data-tab="gallery"] svg { fill: var(--sapphire); stroke: var(--sapphire); }
.tab-item.active[data-tab="gallery"] .tab-label { color: var(--sapphire-light); }
.tab-item.active[data-tab="you"] svg { fill: var(--soft-light); stroke: var(--soft-light); }
.tab-item.active[data-tab="you"] .tab-label { color: var(--soft-light); }
/* --- Typography --- */
.display-xl { font-family: var(--font-display); font-size: 40px; font-weight: 700; line-height: 1.1; }
.display-lg { font-family: var(--font-display); font-size: 32px; font-weight: 700; line-height: 1.15; }
.display-md { font-size: 24px; font-weight: 600; line-height: 1.2; }
.heading { font-size: 20px; font-weight: 600; line-height: 1.3; }
.subheading { font-size: 16px; font-weight: 600; line-height: 1.4; }
.body-lg { font-size: 17px; font-weight: 400; line-height: 1.5; }
.body { font-size: 15px; font-weight: 400; line-height: 1.5; }
.body-sm { font-size: 13px; font-weight: 400; line-height: 1.5; }
.label { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; }
.text-pure { color: var(--pure-light); }
.text-soft { color: var(--soft-light); }
.text-dim { color: var(--dim-light); }
.text-faint { color: var(--faint-light); }
.text-amethyst { color: var(--amethyst-light); }
.text-sapphire { color: var(--sapphire-light); }
.text-emerald { color: var(--emerald-light); }
.text-amber { color: var(--amber-light); }
.text-rose { color: var(--rose-light); }
.text-prismatic {
background: var(--prismatic);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* --- Buttons --- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
font-family: var(--font-primary);
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.2s ease-out;
text-decoration: none;
}
.btn-primary {
height: 52px;
padding: 0 var(--space-6);
background: var(--amethyst);
color: var(--pure-light);
font-size: 16px;
border-radius: var(--radius-lg);
width: 100%;
box-shadow: var(--glow-amethyst);
}
.btn-primary:hover { background: var(--amethyst-light); }
.btn-primary.btn-emerald { background: var(--emerald); box-shadow: var(--glow-emerald); }
.btn-primary.btn-amber { background: var(--amber); box-shadow: var(--glow-amber); color: var(--void); }
.btn-primary.btn-rose { background: var(--rose); box-shadow: var(--glow-rose); }
.btn-secondary {
height: 44px;
padding: 0 var(--space-5);
background: var(--deep-glass);
color: var(--soft-light);
font-size: 15px;
border-radius: var(--radius-md);
border: 1px solid var(--twilight);
width: 100%;
}
.btn-secondary:hover { background: var(--twilight); }
.btn-ghost {
height: 40px;
padding: 0 var(--space-4);
background: transparent;
color: var(--amethyst-light);
font-size: 15px;
border-radius: var(--radius-md);
}
.btn-ghost:hover { background: rgba(139, 92, 246, 0.1); }
.btn-sm { height: 36px; font-size: 13px; padding: 0 var(--space-3); border-radius: var(--radius-sm); }
.btn-icon { width: 44px; height: 44px; padding: 0; border-radius: var(--radius-md); }
.btn-disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
/* --- Inputs --- */
.input-field {
width: 100%;
height: 48px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-md);
padding: 0 14px;
font-family: var(--font-primary);
font-size: 16px;
color: var(--pure-light);
outline: none;
transition: border-color 0.2s ease-out;
}
.input-field::placeholder { color: var(--faint-light); }
.input-field:focus { border-color: var(--amethyst); }
.textarea-field {
width: 100%;
min-height: 120px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
padding: 14px;
font-family: var(--font-primary);
font-size: 17px;
line-height: 1.5;
color: var(--pure-light);
outline: none;
resize: none;
transition: border-color 0.2s ease-out;
}
.textarea-field::placeholder { color: var(--faint-light); }
.textarea-field:focus { border-color: var(--amethyst); }
.input-label {
font-size: 13px;
font-weight: 500;
color: var(--dim-light);
margin-bottom: var(--space-2);
display: block;
}
/* --- Cards --- */
.card {
background: var(--kalei-navy);
border: 1px solid rgba(28, 34, 64, 0.6);
border-radius: var(--radius-xl);
padding: var(--space-4);
position: relative;
overflow: hidden;
}
.card-elevated {
background: var(--deep-glass);
border-color: var(--twilight);
}
.card-pattern {
padding: 0;
overflow: hidden;
}
.card-pattern .pattern-visual {
height: 160px;
display: flex;
align-items: center;
justify-content: center;
background: var(--void);
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
position: relative;
overflow: hidden;
}
.card-pattern .pattern-content {
padding: var(--space-3) var(--space-4);
}
.card-action {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
cursor: pointer;
transition: background 0.2s ease-out;
}
.card-action:hover { background: var(--deep-glass); }
.card-action .card-action-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.card-action .card-action-text { flex: 1; }
.card-action .card-action-chevron { color: var(--faint-light); }
/* --- Chips / Tags --- */
.chip {
display: inline-flex;
align-items: center;
gap: var(--space-1);
height: 28px;
padding: 0 var(--space-3);
border-radius: var(--radius-full);
font-size: 12px;
font-weight: 500;
}
.chip-amethyst { background: rgba(139,92,246,0.15); color: var(--amethyst-light); }
.chip-sapphire { background: rgba(59,130,246,0.15); color: var(--sapphire-light); }
.chip-emerald { background: rgba(16,185,129,0.15); color: var(--emerald-light); }
.chip-amber { background: rgba(245,158,11,0.15); color: var(--amber-light); }
.chip-rose { background: rgba(236,72,153,0.15); color: var(--rose-light); }
/* --- Fragment Highlight --- */
.fragment-highlight {
position: relative;
display: inline;
}
.fragment-highlight::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 3px;
background: var(--amber);
border-radius: 2px;
opacity: 0.6;
box-shadow: 0 0 8px rgba(245, 158, 11, 0.4);
animation: fragmentGlow 2s ease-in-out infinite;
}
.fragment-icon {
display: inline-flex;
align-items: center;
margin-left: 4px;
color: var(--amber);
animation: fragmentPulse 2s ease-in-out infinite;
}
/* --- Chat Bubbles (Mirror) --- */
.chat-bubble {
max-width: 80%;
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-xl);
margin-bottom: var(--space-3);
font-size: 16px;
line-height: 1.5;
}
.chat-bubble.user {
background: rgba(139, 92, 246, 0.15);
border: 1px solid rgba(139, 92, 246, 0.2);
margin-left: auto;
border-bottom-right-radius: var(--space-1);
}
.chat-bubble.ai {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
margin-right: auto;
border-bottom-left-radius: var(--space-1);
color: var(--soft-light);
}
/* --- Progress Ring --- */
.progress-ring {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.progress-ring svg { transform: rotate(-90deg); }
.progress-ring .progress-text {
position: absolute;
font-size: 20px;
font-weight: 700;
color: var(--pure-light);
}
.progress-ring .progress-label {
position: absolute;
top: 58%;
font-size: 11px;
color: var(--dim-light);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* --- Modals --- */
.modal-overlay {
position: absolute;
inset: 0;
background: rgba(5, 5, 8, 0.7);
backdrop-filter: blur(8px);
display: flex;
align-items: flex-end;
justify-content: center;
z-index: 50;
}
.modal-overlay.centered { align-items: center; }
.modal-sheet {
width: 100%;
max-height: 70%;
background: var(--kalei-navy);
border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
padding: var(--space-6) var(--space-4) var(--space-8);
position: relative;
}
.modal-sheet .modal-handle {
width: 36px;
height: 4px;
background: var(--twilight);
border-radius: var(--radius-full);
margin: 0 auto var(--space-5);
}
.modal-card {
width: 340px;
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-2xl);
padding: var(--space-6);
}
/* --- Dividers --- */
.divider {
height: 1px;
background: var(--twilight);
margin: var(--space-4) 0;
}
/* --- Section Headers --- */
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-3);
}
.section-header .section-title {
font-size: 13px;
font-weight: 600;
color: var(--dim-light);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.section-header .section-action {
font-size: 13px;
color: var(--amethyst-light);
cursor: pointer;
}
/* --- Stat Block --- */
.stat-row {
display: flex;
gap: var(--space-3);
}
.stat-block {
flex: 1;
background: var(--deep-glass);
border-radius: var(--radius-lg);
padding: var(--space-3);
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: var(--pure-light);
}
.stat-label {
font-size: 11px;
color: var(--dim-light);
margin-top: 2px;
}
/* --- Toggle / Radio --- */
.radio-option {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
cursor: pointer;
margin-bottom: var(--space-2);
transition: all 0.2s ease-out;
}
.radio-option.selected {
border-color: var(--amethyst);
background: rgba(139, 92, 246, 0.08);
}
.radio-dot {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid var(--twilight);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.radio-option.selected .radio-dot {
border-color: var(--amethyst);
}
.radio-option.selected .radio-dot::after {
content: '';
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--amethyst);
}
/* --- Step Progress --- */
.step-progress {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-3) 0;
}
.step-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--twilight);
}
.step-dot.active { background: var(--amethyst); box-shadow: 0 0 8px rgba(139, 92, 246, 0.4); }
.step-dot.completed { background: var(--emerald); }
.step-label {
font-size: 12px;
color: var(--dim-light);
margin-left: var(--space-2);
}
/* --- Reframe Blocks --- */
.reframe-block {
padding: var(--space-4);
border-radius: var(--radius-lg);
margin-bottom: var(--space-3);
border-left: 3px solid;
}
.reframe-block.amethyst { border-color: var(--amethyst); background: rgba(139,92,246,0.06); }
.reframe-block.sapphire { border-color: var(--sapphire); background: rgba(59,130,246,0.06); }
.reframe-block.emerald { border-color: var(--emerald); background: rgba(16,185,129,0.06); }
.reframe-block .reframe-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: var(--space-2);
}
.reframe-block.amethyst .reframe-label { color: var(--amethyst-light); }
.reframe-block.sapphire .reframe-label { color: var(--sapphire-light); }
.reframe-block.emerald .reframe-label { color: var(--emerald-light); }
.reframe-block .reframe-text {
font-size: 15px;
line-height: 1.5;
color: var(--soft-light);
}
/* --- Animations (from soft-elegance SVG language) --- */
@keyframes breathing {
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.03); }
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
@keyframes pulse {
0%, 100% { opacity: 0.7; }
50% { opacity: 1; }
}
@keyframes fragmentGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes fragmentPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes hintPulse {
0% { opacity: 0; }
50% { opacity: 0.6; }
100% { opacity: 0; }
}
@keyframes prismaticShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Loading shimmer effect */
.loading-shimmer {
background: linear-gradient(90deg, var(--deep-glass) 25%, rgba(28,34,64,0.6) 50%, var(--deep-glass) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: var(--radius-md);
}
/* Breathing aura (decorative background) */
.aura {
position: absolute;
border-radius: 50%;
filter: blur(60px);
opacity: 0.15;
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
}
/* --- Utilities --- */
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }
.mt-8 { margin-top: var(--space-8); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-6 { margin-bottom: var(--space-6); }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.text-center { text-align: center; }
.w-full { width: 100%; }
.relative { position: relative; }
.overflow-hidden { overflow: hidden; }

View File

@@ -0,0 +1,199 @@
<svg viewBox="0 0 700 700" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/><stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="prismaticH" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8B5CF6"/><stop offset="25%" stop-color="#3B82F6"/><stop offset="50%" stop-color="#10B981"/><stop offset="75%" stop-color="#F59E0B"/><stop offset="100%" stop-color="#EC4899"/>
</linearGradient>
<filter id="glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
.section-lbl { font-size: 11px; fill: #64748B; font-weight: 600; letter-spacing: 0.08em; }
.chrome-text { font-size: 10px; fill: #E2E8F0; font-weight: 600; }
</style>
</defs>
<rect width="700" height="700" fill="#050508"/>
<text x="350" y="28" font-size="13" font-weight="700" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">DEVICE CHROME ELEMENTS</text>
<text x="350" y="44" font-size="9" fill="#475569" text-anchor="middle">Status bar, tab bar, nav header, keyboard, device frame, modal scrim, toast, input accessory</text>
<!-- 1. STATUS BAR (iPhone 14 style) -->
<text x="24" y="74" class="section-lbl">STATUS BAR</text>
<line x1="24" y1="80" x2="670" y2="80" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 110)">
<rect x="-195" y="-15" width="390" height="30" fill="#050508"/>
<!-- Time -->
<text x="-170" y="3" font-size="12" fill="#E2E8F0" font-weight="600">9:41</text>
<!-- Dynamic island -->
<rect x="-40" y="-8" width="80" height="16" rx="8" fill="#000"/>
<!-- Right side indicators -->
<!-- Signal -->
<g transform="translate(130, -4)">
<rect x="0" y="4" width="3" height="4" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4" y="2" width="3" height="6" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="0" width="3" height="8" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="12" y="-2" width="3" height="10" rx="0.5" fill="#E2E8F0"/>
</g>
<!-- WiFi -->
<g transform="translate(152, 0)">
<path d="M 0,-4 C 3,-4 5,-2 6,0" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-linecap="round"/>
<path d="M -2,-1 C 2,-1 4,0 5,2" fill="none" stroke="#E2E8F0" stroke-width="1" stroke-linecap="round" opacity="0.8"/>
<circle cx="2" cy="3" r="1" fill="#E2E8F0"/>
</g>
<!-- Battery -->
<g transform="translate(166, -4)">
<rect x="0" y="0" width="22" height="9" rx="2" fill="none" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="2" width="2" height="5" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="1.5" y="1.5" width="16" height="6" rx="1" fill="#10B981" opacity="0.9"/>
</g>
<text x="0" y="30" class="lbl">iOS Status Bar — 390px wide, #050508 background</text>
</g>
<!-- 2. NAV HEADER -->
<text x="24" y="164" class="section-lbl">NAVIGATION HEADER</text>
<line x1="24" y1="170" x2="670" y2="170" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 208)">
<rect x="-195" y="-22" width="390" height="44" fill="#0A0E1A"/>
<line x1="-195" y1="22" x2="195" y2="22" stroke="#1C2240" stroke-width="0.5"/>
<!-- Back chevron -->
<path d="M -175,-2 L -170,-7 M -175,-2 L -170,3" fill="none" stroke="#8B5CF6" stroke-width="1.5" stroke-linecap="round"/>
<!-- Title -->
<text x="0" y="4" font-size="14" fill="#E2E8F0" font-weight="700" text-anchor="middle" font-family="'Space Grotesk', sans-serif">Mirror</text>
<!-- Right action (settings gear outline) -->
<circle cx="172" cy="0" r="7" fill="none" stroke="#94A3B8" stroke-width="1"/>
<circle cx="172" cy="0" r="2" fill="none" stroke="#94A3B8" stroke-width="0.8"/>
<text x="0" y="44" class="lbl">Nav Header — back chevron, centered title, right action</text>
</g>
<!-- 3. TAB BAR FRAME -->
<text x="24" y="280" class="section-lbl">TAB BAR FRAME</text>
<line x1="24" y1="286" x2="670" y2="286" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 330)">
<rect x="-195" y="-28" width="390" height="56" fill="#0A0E1A"/>
<!-- Top border with subtle prismatic -->
<line x1="-195" y1="-28" x2="195" y2="-28" stroke="url(#prismaticH)" stroke-width="0.5" opacity="0.3"/>
<!-- 5 tab slots -->
<g font-size="8" fill="#475569" text-anchor="middle" font-weight="500">
<!-- Turn (active) -->
<g transform="translate(-156, 0)">
<g filter="url(#glowSm)">
<path d="M 0,-10 L 8,0 L 0,10 L -8,0 Z" fill="url(#grAmethyst)" opacity="0.8"/>
<path d="M 0,-10 L 8,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
</g>
<text x="0" y="20" fill="#A78BFA" font-weight="600">Turn</text>
</g>
<!-- Mirror (inactive) -->
<g transform="translate(-78, 0)">
<path d="M 0,-6 L 4,-3 L 6,3 L 0,7 L -6,3 L -4,-3 Z" fill="none" stroke="#475569" stroke-width="0.8"/>
<text x="0" y="20">Mirror</text>
</g>
<!-- Lens (inactive) -->
<g transform="translate(0, 0)">
<circle r="7" fill="none" stroke="#475569" stroke-width="0.8"/>
<circle r="3" fill="none" stroke="#475569" stroke-width="0.5"/>
<text x="0" y="20">Lens</text>
</g>
<!-- Gallery (inactive) -->
<g transform="translate(78, 0)">
<rect x="-6" y="-6" width="5" height="5" rx="1" fill="none" stroke="#475569" stroke-width="0.7"/>
<rect x="1" y="-6" width="5" height="5" rx="1" fill="none" stroke="#475569" stroke-width="0.7"/>
<rect x="-6" y="1" width="5" height="5" rx="1" fill="none" stroke="#475569" stroke-width="0.7"/>
<rect x="1" y="1" width="5" height="5" rx="1" fill="none" stroke="#475569" stroke-width="0.7"/>
<text x="0" y="20">Gallery</text>
</g>
<!-- You (inactive) -->
<g transform="translate(156, 0)">
<circle r="6" fill="none" stroke="#475569" stroke-width="0.8"/>
<circle r="2.5" fill="none" stroke="#475569" stroke-width="0.6" cy="-1"/>
<text x="0" y="20">You</text>
</g>
</g>
<!-- Home indicator -->
<rect x="-50" y="30" width="100" height="4" rx="2" fill="#475569" opacity="0.3"/>
<text x="0" y="52" class="lbl">Tab Bar — 5 tabs, prismatic top border, home indicator</text>
</g>
<!-- 4. MODAL SCRIM -->
<text x="24" y="410" class="section-lbl">MODAL SCRIM AND TOAST</text>
<line x1="24" y1="416" x2="670" y2="416" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(200, 500)">
<!-- Scrim background -->
<rect x="-100" y="-50" width="200" height="100" rx="4" fill="#050508" opacity="0.7"/>
<rect x="-100" y="-50" width="200" height="100" rx="4" fill="none" stroke="#1C2240" stroke-width="0.5"/>
<!-- Modal card on top -->
<rect x="-70" y="-30" width="140" height="60" rx="12" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.8"/>
<!-- Grab handle -->
<rect x="-16" y="-26" width="32" height="3" rx="1.5" fill="#475569" opacity="0.4"/>
<text x="0" y="0" font-size="10" fill="#E2E8F0" text-anchor="middle" font-weight="600">Modal Content</text>
<text x="0" y="14" font-size="8" fill="#64748B" text-anchor="middle">60% backdrop blur scrim</text>
<text x="0" y="68" class="lbl">Modal Scrim — #050508 at 70% + backdrop blur</text>
</g>
<!-- Toast -->
<g transform="translate(530, 480)">
<g filter="url(#glowSm)">
<rect x="-90" y="-16" width="180" height="32" rx="16" fill="#121628" stroke="#10B981" stroke-width="0.6"/>
<!-- Emerald fragment -->
<g transform="translate(-68, 0)">
<path d="M 0,-5 L 5,0 L 0,5 L -5,0 Z" fill="url(#grEmerald)" opacity="0.8"/>
<path d="M 0,-5 L 5,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
</g>
<text x="4" y="4" font-size="10" fill="#E2E8F0" text-anchor="middle" font-weight="500">Turn saved</text>
</g>
<text x="0" y="34" class="lbl">Success Toast — pill shape, emerald accent</text>
</g>
<!-- Error Toast -->
<g transform="translate(530, 540)">
<g filter="url(#glowSm)">
<rect x="-90" y="-16" width="180" height="32" rx="16" fill="#121628" stroke="#EF4444" stroke-width="0.6"/>
<g transform="translate(-68, 0)">
<path d="M 0,-5 L 5,0 L 0,5 L -5,0 Z" fill="#EF4444" opacity="0.8"/>
</g>
<text x="4" y="4" font-size="10" fill="#E2E8F0" text-anchor="middle" font-weight="500">Connection lost</text>
</g>
<text x="0" y="34" class="lbl">Error Toast — pill shape, ruby accent</text>
</g>
<!-- 5. INPUT ACCESSORY / KEYBOARD TOP -->
<text x="24" y="600" class="section-lbl">INPUT ACCESSORY</text>
<line x1="24" y1="606" x2="670" y2="606" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 648)">
<rect x="-195" y="-20" width="390" height="40" fill="#0A0E1A"/>
<line x1="-195" y1="-20" x2="195" y2="-20" stroke="#1C2240" stroke-width="0.5"/>
<!-- Text input area -->
<rect x="-180" y="-12" width="310" height="24" rx="12" fill="#121628" stroke="#1C2240" stroke-width="0.5"/>
<text x="-168" y="3" font-size="10" fill="#475569">Type your thought...</text>
<!-- Send button -->
<g transform="translate(165, 0)" filter="url(#glowSm)">
<circle r="12" fill="url(#grAmethyst)" opacity="0.8"/>
<path d="M -3,-3 L 4,0 L -3,3" fill="none" stroke="#fff" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<text x="0" y="36" class="lbl">Input Accessory Bar — text field + send button, above keyboard</text>
</g>
<text x="350" y="692" font-size="8" fill="#334155" text-anchor="middle">Device chrome at 390x844px (iPhone 14) — all elements use Kalei dark theme with jewel tone accents</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,250 @@
<svg viewBox="0 0 700 680" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/><stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<linearGradient id="grIndigo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A5B4FC"/><stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<filter id="glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
.section-lbl { font-size: 11px; fill: #64748B; font-weight: 600; letter-spacing: 0.08em; }
</style>
</defs>
<rect width="700" height="680" fill="#050508"/>
<text x="350" y="28" font-size="13" font-weight="700" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">EVIDENCE WALL MOSAIC</text>
<text x="350" y="44" font-size="9" fill="#475569" text-anchor="middle">5 tile shapes, 3 mosaic stages (early/mid/full), connection lines, empty state</text>
<!-- TILE SHAPES -->
<text x="24" y="74" class="section-lbl">TILE SHAPES</text>
<line x1="24" y1="80" x2="670" y2="80" stroke="#1C2240" stroke-width="0.5"/>
<!-- Tile 1: Diamond -->
<g transform="translate(80, 130)">
<g filter="url(#glowMd)">
<path d="M 0,-24 L 24,0 L 0,24 L -24,0 Z" fill="#121628" stroke="#8B5CF6" stroke-width="0.8"/>
<path d="M 0,-24 L 24,0 L 0,0 Z" fill="#8B5CF6" opacity="0.06"/>
<!-- Inner fragment -->
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#grAmethyst)" opacity="0.5" filter="url(#glowSm)"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.1"/>
</g>
<text x="0" y="44" class="lbl">Diamond</text>
</g>
<!-- Tile 2: Tall Hex -->
<g transform="translate(200, 130)">
<g filter="url(#glowMd)">
<path d="M 0,-28 L 20,-14 L 20,14 L 0,28 L -20,14 L -20,-14 Z" fill="#121628" stroke="#3B82F6" stroke-width="0.8"/>
<path d="M 0,-28 L 20,-14 L 0,0 L -20,-14 Z" fill="#3B82F6" opacity="0.05"/>
<path d="M 0,-10 L 10,0 L 0,10 L -10,0 Z" fill="url(#grSapphire)" opacity="0.4" filter="url(#glowSm)"/>
</g>
<text x="0" y="48" class="lbl">Tall Hex</text>
</g>
<!-- Tile 3: Wide Rectangle -->
<g transform="translate(340, 130)">
<g filter="url(#glowMd)">
<rect x="-30" y="-18" width="60" height="36" rx="4" fill="#121628" stroke="#10B981" stroke-width="0.8"/>
<rect x="-30" y="-18" width="60" height="18" rx="4" fill="#10B981" opacity="0.04"/>
<path d="M -8,-6 L 0,-12 L 8,-6 L 0,6 Z" fill="url(#grEmerald)" opacity="0.45" filter="url(#glowSm)"/>
</g>
<text x="0" y="38" class="lbl">Wide Rect</text>
</g>
<!-- Tile 4: Pentagon -->
<g transform="translate(470, 130)">
<g filter="url(#glowMd)">
<path d="M 0,-24 L 23,-7 L 14,20 L -14,20 L -23,-7 Z" fill="#121628" stroke="#F59E0B" stroke-width="0.8"/>
<path d="M 0,-24 L 23,-7 L 0,0 L -23,-7 Z" fill="#F59E0B" opacity="0.05"/>
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#grAmber)" opacity="0.45" filter="url(#glowSm)"/>
</g>
<text x="0" y="42" class="lbl">Pentagon</text>
</g>
<!-- Tile 5: Triangle -->
<g transform="translate(600, 130)">
<g filter="url(#glowMd)">
<path d="M 0,-26 L 24,18 L -24,18 Z" fill="#121628" stroke="#EC4899" stroke-width="0.8"/>
<path d="M 0,-26 L 24,18 L 0,0 Z" fill="#EC4899" opacity="0.05"/>
<path d="M 0,-6 L 6,4 L -6,4 Z" fill="url(#grRose)" opacity="0.45" filter="url(#glowSm)"/>
</g>
<text x="0" y="42" class="lbl">Triangle</text>
</g>
<!-- MOSAIC STAGES -->
<text x="24" y="210" class="section-lbl">MOSAIC STAGES</text>
<line x1="24" y1="216" x2="670" y2="216" stroke="#1C2240" stroke-width="0.5"/>
<!-- Early Stage (3 tiles) -->
<g transform="translate(120, 320)">
<g filter="url(#glowSm)">
<!-- Tile A -->
<path d="M 0,-20 L 18,0 L 0,20 L -18,0 Z" fill="#121628" stroke="#8B5CF6" stroke-width="0.6"/>
<path d="M 0,-6 L 6,0 L 0,6 L -6,0 Z" fill="url(#grAmethyst)" opacity="0.4"/>
<!-- Tile B -->
<g transform="translate(30, 25)">
<rect x="-16" y="-10" width="32" height="20" rx="3" fill="#121628" stroke="#10B981" stroke-width="0.6"/>
<path d="M 0,-4 L 4,0 L 0,4 L -4,0 Z" fill="url(#grEmerald)" opacity="0.35"/>
</g>
<!-- Tile C -->
<g transform="translate(-25, 30)">
<path d="M 0,-14 L 14,8 L -14,8 Z" fill="#121628" stroke="#F59E0B" stroke-width="0.6"/>
<path d="M 0,-4 L 4,2 L -4,2 Z" fill="url(#grAmber)" opacity="0.35"/>
</g>
<!-- Connection lines -->
<line x1="10" y1="10" x2="22" y2="18" stroke="#1C2240" stroke-width="0.5" stroke-dasharray="2 3"/>
<line x1="-10" y1="10" x2="-18" y2="22" stroke="#1C2240" stroke-width="0.5" stroke-dasharray="2 3"/>
</g>
<text x="0" y="66" class="lbl">Early (3 tiles)</text>
</g>
<!-- Mid Stage (8 tiles) -->
<g transform="translate(350, 310)">
<g filter="url(#glowSm)">
<!-- Row 1 -->
<path d="M 0,-30 L 14,-16 L 0,-2 L -14,-16 Z" fill="#121628" stroke="#8B5CF6" stroke-width="0.5"/>
<path d="M 0,-20 L 4,-16 L 0,-12 L -4,-16 Z" fill="url(#grAmethyst)" opacity="0.4"/>
<g transform="translate(28, -22)">
<rect x="-12" y="-8" width="24" height="16" rx="2" fill="#121628" stroke="#3B82F6" stroke-width="0.5"/>
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="url(#grSapphire)" opacity="0.35"/>
</g>
<g transform="translate(-28, -20)">
<path d="M 0,-12 L 10,6 L -10,6 Z" fill="#121628" stroke="#EC4899" stroke-width="0.5"/>
<path d="M 0,-4 L 3,2 L -3,2 Z" fill="url(#grRose)" opacity="0.35"/>
</g>
<!-- Row 2 -->
<g transform="translate(20, 8)">
<path d="M 0,-14 L 12,0 L 0,14 L -12,0 Z" fill="#121628" stroke="#10B981" stroke-width="0.5"/>
<path d="M 0,-4 L 4,0 L 0,4 L -4,0 Z" fill="url(#grEmerald)" opacity="0.35"/>
</g>
<g transform="translate(-20, 10)">
<path d="M 0,-12 L 10,-4 L 10,8 L -10,8 L -10,-4 Z" fill="#121628" stroke="#F59E0B" stroke-width="0.5"/>
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="url(#grAmber)" opacity="0.35"/>
</g>
<!-- Row 3 -->
<g transform="translate(0, 30)">
<rect x="-14" y="-8" width="28" height="16" rx="2" fill="#121628" stroke="#6366F1" stroke-width="0.5"/>
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="url(#grIndigo)" opacity="0.35"/>
</g>
<g transform="translate(30, 28)">
<path d="M 0,-10 L 8,4 L -8,4 Z" fill="#121628" stroke="#8B5CF6" stroke-width="0.5"/>
</g>
<g transform="translate(-32, 28)">
<path d="M 0,-10 L 10,0 L 0,10 L -10,0 Z" fill="#121628" stroke="#3B82F6" stroke-width="0.5"/>
</g>
<!-- Connection lines between tiles -->
<line x1="8" y1="-8" x2="18" y2="-16" stroke="#1C2240" stroke-width="0.4" stroke-dasharray="2 2"/>
<line x1="-8" y1="-10" x2="-20" y2="-14" stroke="#1C2240" stroke-width="0.4" stroke-dasharray="2 2"/>
<line x1="10" y1="6" x2="14" y2="4" stroke="#1C2240" stroke-width="0.4" stroke-dasharray="2 2"/>
<line x1="-10" y1="8" x2="-14" y2="8" stroke="#1C2240" stroke-width="0.4" stroke-dasharray="2 2"/>
</g>
<text x="0" y="66" class="lbl">Mid (8 tiles, connections forming)</text>
</g>
<!-- Full Stage (dense mosaic) -->
<g transform="translate(580, 305)">
<g filter="url(#glowMd)">
<!-- Dense mosaic of tiles -->
<path d="M 0,-38 L 12,-26 L 0,-14 L -12,-26 Z" fill="#121628" stroke="#8B5CF6" stroke-width="0.5"/>
<path d="M 0,-30 L 4,-26 L 0,-22 L -4,-26 Z" fill="url(#grAmethyst)" opacity="0.5"/>
<g transform="translate(20, -30)">
<rect x="-8" y="-6" width="16" height="12" rx="2" fill="#121628" stroke="#3B82F6" stroke-width="0.4"/>
<path d="M 0,-2 L 2,0 L 0,2 L -2,0 Z" fill="url(#grSapphire)" opacity="0.4"/>
</g>
<g transform="translate(-20, -28)">
<path d="M 0,-8 L 8,4 L -8,4 Z" fill="#121628" stroke="#EC4899" stroke-width="0.4"/>
<path d="M 0,-3 L 2,1 L -2,1 Z" fill="url(#grRose)" opacity="0.35"/>
</g>
<g transform="translate(16, -12)">
<path d="M 0,-10 L 8,0 L 0,10 L -8,0 Z" fill="#121628" stroke="#10B981" stroke-width="0.4"/>
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="url(#grEmerald)" opacity="0.4"/>
</g>
<g transform="translate(-18, -10)">
<path d="M 0,-8 L 7,-3 L 7,5 L -7,5 L -7,-3 Z" fill="#121628" stroke="#F59E0B" stroke-width="0.4"/>
<path d="M 0,-2 L 2,0 L 0,2 L -2,0 Z" fill="url(#grAmber)" opacity="0.4"/>
</g>
<g transform="translate(0, 6)">
<rect x="-10" y="-6" width="20" height="12" rx="2" fill="#121628" stroke="#6366F1" stroke-width="0.4"/>
<path d="M 0,-2 L 2,0 L 0,2 L -2,0 Z" fill="url(#grIndigo)" opacity="0.4"/>
</g>
<g transform="translate(-22, 8)">
<path d="M 0,-8 L 6,0 L 0,8 L -6,0 Z" fill="#121628" stroke="#8B5CF6" stroke-width="0.4"/>
</g>
<g transform="translate(22, 8)">
<path d="M 0,-6 L 6,4 L -6,4 Z" fill="#121628" stroke="#3B82F6" stroke-width="0.4"/>
</g>
<g transform="translate(10, 22)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="#121628" stroke="#10B981" stroke-width="0.4"/>
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="url(#grEmerald)" opacity="0.35"/>
</g>
<g transform="translate(-12, 22)">
<rect x="-8" y="-6" width="16" height="12" rx="2" fill="#121628" stroke="#EC4899" stroke-width="0.4"/>
</g>
<g transform="translate(0, 36)">
<path d="M 0,-8 L 10,4 L -10,4 Z" fill="#121628" stroke="#F59E0B" stroke-width="0.4"/>
</g>
<!-- Glowing connection web -->
<g stroke="#1C2240" stroke-width="0.3" opacity="0.5">
<line x1="0" y1="-20" x2="14" y2="-14"/>
<line x1="0" y1="-20" x2="-14" y2="-14"/>
<line x1="12" y1="-6" x2="6" y2="4"/>
<line x1="-12" y1="-4" x2="-6" y2="6"/>
<line x1="6" y1="10" x2="8" y2="18"/>
<line x1="-6" y1="10" x2="-8" y2="18"/>
</g>
</g>
<text x="0" y="66" class="lbl">Full (dense mosaic with connections)</text>
</g>
<!-- EMPTY STATE -->
<text x="24" y="420" class="section-lbl">EMPTY STATE</text>
<line x1="24" y1="426" x2="670" y2="426" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 540)">
<!-- Ghost tiles -->
<g opacity="0.15">
<path d="M 0,-30 L 16,-16 L 0,-2 L -16,-16 Z" fill="none" stroke="#475569" stroke-width="0.8" stroke-dasharray="3 3"/>
<path d="M 24,-20 L 36,-8 L 24,4 L 12,-8 Z" fill="none" stroke="#475569" stroke-width="0.8" stroke-dasharray="3 3"/>
<path d="M -24,-18 L -12,-6 L -24,6 L -36,-6 Z" fill="none" stroke="#475569" stroke-width="0.8" stroke-dasharray="3 3"/>
<rect x="-14" y="6" width="28" height="18" rx="3" fill="none" stroke="#475569" stroke-width="0.8" stroke-dasharray="3 3"/>
</g>
<!-- Central message area -->
<g filter="url(#glowSm)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#grAmethyst)" opacity="0.2">
<animate attributeName="opacity" values="0.1;0.3;0.1" dur="4s" repeatCount="indefinite"/>
</path>
</g>
<text x="0" y="52" font-size="10" fill="#64748B" text-anchor="middle" font-weight="500">Start collecting evidence</text>
<text x="0" y="66" font-size="8" fill="#475569" text-anchor="middle">Each Turn adds a tile to your wall</text>
<text x="0" y="100" class="lbl">Empty state — dashed ghost tiles with CTA</text>
</g>
<text x="350" y="672" font-size="8" fill="#334155" text-anchor="middle">Evidence Wall tiles use mixed shapes, jewel tones per feature, dashed connection lines, and glow on new additions</text>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,322 @@
<svg viewBox="0 0 700 520" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Prismatic gradients for each jewel tone -->
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"><animate attributeName="stop-color" values="#C4B5FD;#DDD6FE;#C4B5FD" dur="4s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"><animate attributeName="stop-color" values="#93C5FD;#BFDBFE;#93C5FD" dur="4.5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"><animate attributeName="stop-color" values="#6EE7B7;#A7F3D0;#6EE7B7" dur="5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"><animate attributeName="stop-color" values="#FDE68A;#FEF3C7;#FDE68A" dur="4.2s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"><animate attributeName="stop-color" values="#FBCFE8;#FCE7F3;#FBCFE8" dur="4.8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<linearGradient id="grRuby" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FCA5A5"/><stop offset="100%" stop-color="#DC2626"/>
</linearGradient>
<linearGradient id="grIndigo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A5B4FC"><animate attributeName="stop-color" values="#A5B4FC;#C7D2FE;#A5B4FC" dur="5.2s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<linearGradient id="grSoftLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F1F5F9"/><stop offset="100%" stop-color="#94A3B8"/>
</linearGradient>
<linearGradient id="grPrismatic" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#8B5CF6"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#3B82F6"><animate attributeName="stop-color" values="#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6;#3B82F6" dur="8s" repeatCount="indefinite"/></stop>
</linearGradient>
<filter id="glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowXl" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="10" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="shimmer" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
.section-lbl { font-size: 11px; fill: #64748B; font-weight: 600; letter-spacing: 0.08em; }
</style>
</defs>
<rect width="700" height="520" fill="#050508"/>
<text x="350" y="28" font-size="13" font-weight="700" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">FRAGMENT ICONS AND HIGHLIGHT EFFECTS</text>
<text x="350" y="44" font-size="9" fill="#475569" text-anchor="middle">The core diamond fragment in 5 sizes, 8 color states, highlight underline, highlight bubble</text>
<!-- SECTION 1: Five Sizes -->
<text x="24" y="74" class="section-lbl">FIVE SIZES</text>
<line x1="24" y1="80" x2="670" y2="80" stroke="#1C2240" stroke-width="0.5"/>
<!-- XS: 12x12 -->
<g transform="translate(80, 120)">
<g filter="url(#glowSm)">
<path d="M 0,-6 L 6,0 L 0,6 L -6,0 Z" fill="url(#grAmethyst)" opacity="0.9"/>
<path d="M 0,-6 L 6,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<path d="M 0,-6 L -6,0 L 0,0 Z" fill="#fff" opacity="0.08"/>
<line x1="0" y1="-6" x2="0" y2="6" stroke="#fff" stroke-width="0.3" opacity="0.2"/>
</g>
<text x="0" y="24" class="lbl">XS (12px)</text>
</g>
<!-- SM: 16x16 -->
<g transform="translate(180, 120)">
<g filter="url(#glowSm)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#grAmethyst)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<path d="M 0,-8 L -8,0 L 0,0 Z" fill="#fff" opacity="0.08"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.4" opacity="0.2"/>
<line x1="-8" y1="0" x2="8" y2="0" stroke="#fff" stroke-width="0.3" opacity="0.1"/>
</g>
<text x="0" y="24" class="lbl">SM (16px)</text>
</g>
<!-- MD: 24x24 -->
<g transform="translate(300, 120)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grAmethyst)" opacity="0.9">
<animate attributeName="fill-opacity" values="0.85;1;0.85" dur="3s" repeatCount="indefinite"/>
</path>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<path d="M 0,-12 L -12,0 L 0,0 Z" fill="#fff" opacity="0.1"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<line x1="-12" y1="0" x2="12" y2="0" stroke="#fff" stroke-width="0.3" opacity="0.12"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">MD (24px)</text>
</g>
<!-- LG: 36x36 -->
<g transform="translate(440, 120)">
<g filter="url(#glowLg)">
<path d="M 0,-18 L 18,0 L 0,18 L -18,0 Z" fill="url(#grAmethyst)" opacity="0.85">
<animate attributeName="fill-opacity" values="0.80;0.95;0.80" dur="4s" repeatCount="indefinite"/>
</path>
<path d="M 0,-18 L 18,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<path d="M 0,-18 L -18,0 L 0,0 Z" fill="#fff" opacity="0.08"/>
<line x1="0" y1="-18" x2="0" y2="18" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<line x1="-18" y1="0" x2="18" y2="0" stroke="#fff" stroke-width="0.4" opacity="0.12"/>
<line x1="-9" y1="-9" x2="9" y2="9" stroke="#fff" stroke-width="0.3" opacity="0.08"/>
<circle cx="-5" cy="-5" r="2" fill="#fff" opacity="0.25"/>
<circle cx="3" cy="-2" r="1" fill="#fff" opacity="0.4"/>
</g>
<text x="0" y="36" class="lbl">LG (36px)</text>
</g>
<!-- XL: 48x48 -->
<g transform="translate(600, 120)">
<g filter="url(#glowXl)">
<path d="M 0,-24 L 24,0 L 0,24 L -24,0 Z" fill="url(#grAmethyst)" opacity="0.85">
<animate attributeName="fill-opacity" values="0.75;0.95;0.75" dur="5s" repeatCount="indefinite"/>
</path>
<path d="M 0,-24 L 24,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<path d="M 0,-24 L -24,0 L 0,0 Z" fill="#fff" opacity="0.08"/>
<path d="M 0,24 L 24,0 L 0,0 Z" fill="#000" opacity="0.08"/>
<line x1="0" y1="-24" x2="0" y2="24" stroke="#fff" stroke-width="0.6" opacity="0.2"/>
<line x1="-24" y1="0" x2="24" y2="0" stroke="#fff" stroke-width="0.5" opacity="0.15"/>
<line x1="-12" y1="-12" x2="12" y2="12" stroke="#fff" stroke-width="0.3" opacity="0.08"/>
<line x1="12" y1="-12" x2="-12" y2="12" stroke="#fff" stroke-width="0.3" opacity="0.08"/>
<circle cx="-7" cy="-7" r="3" fill="#fff" opacity="0.2"/>
<circle cx="4" cy="-3" r="1.5" fill="#fff" opacity="0.4"/>
<line x1="0" y1="-24" x2="24" y2="0" stroke="#C4B5FD" stroke-width="0.8" opacity="0">
<animate attributeName="opacity" values="0;0.4;0" dur="3s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="-24" x2="-24" y2="0" stroke="#E9D5FF" stroke-width="0.8" opacity="0">
<animate attributeName="opacity" values="0;0.3;0" dur="3s" begin="1.5s" repeatCount="indefinite"/>
</line>
</g>
<text x="0" y="44" class="lbl">XL (48px)</text>
</g>
<!-- SECTION 2: Eight Color States -->
<text x="24" y="204" class="section-lbl">EIGHT COLOR STATES</text>
<line x1="24" y1="210" x2="670" y2="210" stroke="#1C2240" stroke-width="0.5"/>
<!-- Amethyst -->
<g transform="translate(56, 252)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grAmethyst)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">Amethyst</text>
</g>
<!-- Sapphire -->
<g transform="translate(138, 252)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grSapphire)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">Sapphire</text>
</g>
<!-- Emerald -->
<g transform="translate(220, 252)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grEmerald)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">Emerald</text>
</g>
<!-- Amber -->
<g transform="translate(302, 252)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grAmber)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">Amber</text>
</g>
<!-- Rose -->
<g transform="translate(384, 252)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grRose)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">Rose</text>
</g>
<!-- Ruby -->
<g transform="translate(466, 252)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grRuby)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">Ruby</text>
</g>
<!-- Indigo -->
<g transform="translate(548, 252)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grIndigo)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">Indigo</text>
</g>
<!-- Soft Light -->
<g transform="translate(630, 252)">
<g filter="url(#glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#grSoftLight)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="28" class="lbl">Soft Light</text>
</g>
<!-- SECTION 3: Special States and Highlights -->
<text x="24" y="330" class="section-lbl">SPECIAL STATES AND HIGHLIGHTS</text>
<line x1="24" y1="336" x2="670" y2="336" stroke="#1C2240" stroke-width="0.5"/>
<!-- Active/Detected glow -->
<g transform="translate(80, 390)">
<g filter="url(#glowLg)">
<path d="M 0,-14 L 14,0 L 0,14 L -14,0 Z" fill="url(#grAmethyst)" opacity="0.95">
<animate attributeName="fill-opacity" values="0.85;1;0.85" dur="2s" repeatCount="indefinite"/>
</path>
<path d="M 0,-14 L 14,0 L 0,0 Z" fill="#fff" opacity="0.2"/>
<line x1="0" y1="-14" x2="0" y2="14" stroke="#fff" stroke-width="0.5" opacity="0.25"/>
<circle cx="-4" cy="-4" r="2" fill="#fff" opacity="0.35"/>
<path d="M 0,-18 L 18,0 L 0,18 L -18,0 Z" fill="none" stroke="#A78BFA" stroke-width="1" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="2s" repeatCount="indefinite"/>
<animate attributeName="stroke-width" values="1;0.3;1" dur="2s" repeatCount="indefinite"/>
</path>
<path d="M 0,-22 L 22,0 L 0,22 L -22,0 Z" fill="none" stroke="#A78BFA" stroke-width="0.5" opacity="0">
<animate attributeName="opacity" values="0;0.3;0" dur="2s" begin="0.3s" repeatCount="indefinite"/>
</path>
</g>
<text x="0" y="44" class="lbl">Active / Detected</text>
</g>
<!-- Prismatic cycling -->
<g transform="translate(220, 390)">
<g filter="url(#glowLg)">
<path d="M 0,-14 L 14,0 L 0,14 L -14,0 Z" fill="url(#grPrismatic)" opacity="0.9">
<animate attributeName="fill-opacity" values="0.8;1;0.8" dur="3s" repeatCount="indefinite"/>
</path>
<path d="M 0,-14 L 14,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<line x1="0" y1="-14" x2="0" y2="14" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-4" cy="-4" r="2" fill="#fff" opacity="0.3"/>
</g>
<text x="0" y="44" class="lbl">Prismatic Cycle</text>
</g>
<!-- Dimmed / Inactive -->
<g transform="translate(360, 390)">
<path d="M 0,-14 L 14,0 L 0,14 L -14,0 Z" fill="none" stroke="#475569" stroke-width="0.8" opacity="0.5"/>
<line x1="0" y1="-14" x2="0" y2="14" stroke="#475569" stroke-width="0.3" opacity="0.3"/>
<text x="0" y="44" class="lbl">Dimmed / Inactive</text>
</g>
<!-- Highlight Underline -->
<g transform="translate(500, 380)">
<text x="0" y="0" font-size="11" fill="#E2E8F0" text-anchor="middle" font-weight="500">highlighted text</text>
<g filter="url(#shimmer)">
<rect x="-48" y="4" width="96" height="3" rx="1.5" fill="url(#grAmethyst)" opacity="0.6">
<animate attributeName="opacity" values="0.4;0.7;0.4" dur="3s" repeatCount="indefinite"/>
</rect>
<rect x="-48" y="4" width="20" height="3" rx="1.5" fill="#fff" opacity="0">
<animate attributeName="opacity" values="0;0.3;0" dur="2s" repeatCount="indefinite"/>
<animate attributeName="x" values="-48;48" dur="2s" repeatCount="indefinite"/>
</rect>
</g>
<g transform="translate(-52, 5)">
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="url(#grAmethyst)" opacity="0.8" filter="url(#glowSm)"/>
</g>
<text x="0" y="30" class="lbl">Highlight Underline</text>
</g>
<!-- Highlight Bubble -->
<g transform="translate(350, 470)">
<g filter="url(#glowMd)">
<rect x="-80" y="-18" width="160" height="36" rx="18" fill="#121628" stroke="#8B5CF6" stroke-width="0.8" opacity="0.9"/>
<rect x="-78" y="-16" width="156" height="32" rx="16" fill="#8B5CF6" opacity="0.06"/>
<g transform="translate(-56, 0)">
<path d="M 0,-6 L 6,0 L 0,6 L -6,0 Z" fill="url(#grAmethyst)" opacity="0.9" filter="url(#glowSm)"/>
<path d="M 0,-6 L 6,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
</g>
<text x="4" y="4" font-size="10" fill="#C4B5FD" text-anchor="middle" font-weight="500">Catastrophizing</text>
<rect x="-82" y="-20" width="164" height="40" rx="20" fill="none" stroke="#8B5CF6" stroke-width="0.5" opacity="0">
<animate attributeName="opacity" values="0;0.3;0" dur="3s" repeatCount="indefinite"/>
</rect>
</g>
<text x="0" y="38" class="lbl">Highlight Bubble (distortion detected in text)</text>
</g>
<text x="350" y="510" font-size="8" fill="#334155" text-anchor="middle">Fragment — Core visual element of Kalei — Crystalline faceted diamond with gradient fills, specular highlights, glow filters</text>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,380 @@
<svg viewBox="0 0 560 560" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="gl" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<linearGradient id="gAme" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/><stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="gSoft" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F1F5F9"/><stop offset="100%" stop-color="#94A3B8"/>
</linearGradient>
<linearGradient id="gEm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#047857"/>
</linearGradient>
<linearGradient id="gSap" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
<linearGradient id="gAmb" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="gRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#BE185D"/>
</linearGradient>
<style>
text { font-family: 'Inter', -apple-system, sans-serif; }
.lbl { font-size: 8px; fill: #64748B; text-anchor: middle; font-weight: 500; }
</style>
</defs>
<rect width="560" height="560" fill="#050508"/>
<text x="280" y="22" font-size="12" font-weight="600" fill="#94A3B8" text-anchor="middle" letter-spacing="0.1em">ACTION &amp; UI ICONS</text>
<!-- === ROW 1: NAVIGATION (Soft Light #E2E8F0 strokes) === -->
<text x="20" y="50" font-size="9" fill="#475569" font-weight="600" letter-spacing="0.08em">NAVIGATION</text>
<!-- Back chevron -->
<g transform="translate(30, 60)">
<path d="M 14,4 L 6,12 L 14,20" fill="none" stroke="#E2E8F0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="12" y="34" class="lbl">Back</text>
</g>
<!-- Close X -->
<g transform="translate(90, 60)">
<path d="M 6,6 L 18,18 M 18,6 L 6,18" fill="none" stroke="#E2E8F0" stroke-width="1.5" stroke-linecap="round"/>
<text x="12" y="34" class="lbl">Close</text>
</g>
<!-- More (3 diamonds) -->
<g transform="translate(150, 60)">
<path d="M 12,4 L 14,6 L 12,8 L 10,6 Z" fill="#E2E8F0" opacity="0.8"/>
<path d="M 12,10 L 14,12 L 12,14 L 10,12 Z" fill="#E2E8F0" opacity="0.8"/>
<path d="M 12,16 L 14,18 L 12,20 L 10,18 Z" fill="#E2E8F0" opacity="0.8"/>
<text x="12" y="34" class="lbl">More</text>
</g>
<!-- Chevron right -->
<g transform="translate(210, 60)">
<path d="M 10,4 L 18,12 L 10,20" fill="none" stroke="#94A3B8" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<text x="12" y="34" class="lbl">Next</text>
</g>
<!-- Up (scroll to top / expand) -->
<g transform="translate(270, 60)">
<path d="M 4,16 L 12,6 L 20,16" fill="none" stroke="#E2E8F0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="12" y="34" class="lbl">Up</text>
</g>
<!-- Down -->
<g transform="translate(330, 60)">
<path d="M 4,8 L 12,18 L 20,8" fill="none" stroke="#E2E8F0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="12" y="34" class="lbl">Down</text>
</g>
<!-- === ROW 2: PRIMARY ACTIONS (Amethyst) === -->
<text x="20" y="120" font-size="9" fill="#475569" font-weight="600" letter-spacing="0.08em">PRIMARY ACTIONS</text>
<!-- Save / Bookmark (gem shape) -->
<g transform="translate(30, 130)" filter="url(#gl)">
<path d="M 12,3 L 20,8 L 20,21 L 12,17 L 4,21 L 4,8 Z" fill="url(#gAme)" opacity="0.85"/>
<path d="M 12,3 L 20,8 L 4,8 Z" fill="#fff" opacity="0.12"/>
<text x="12" y="34" class="lbl">Save</text>
</g>
<!-- Share (angular arrow up-right) -->
<g transform="translate(90, 130)" filter="url(#gl)">
<path d="M 12,4 L 18,4 L 18,10" fill="none" stroke="#A78BFA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 18,4 L 6,16" fill="none" stroke="#A78BFA" stroke-width="1.5" stroke-linecap="round"/>
<path d="M 6,8 L 6,20 L 18,20" fill="none" stroke="#8B5CF6" stroke-width="1" opacity="0.5"/>
<text x="12" y="34" class="lbl">Share</text>
</g>
<!-- Copy -->
<g transform="translate(150, 130)">
<rect x="8" y="6" width="12" height="14" rx="2" fill="none" stroke="#A78BFA" stroke-width="1.2"/>
<rect x="4" y="4" width="12" height="14" rx="2" fill="none" stroke="#8B5CF6" stroke-width="1"/>
<text x="12" y="34" class="lbl">Copy</text>
</g>
<!-- Delete -->
<g transform="translate(210, 130)">
<path d="M 6,8 L 18,8" stroke="#EF4444" stroke-width="1.2" stroke-linecap="round"/>
<path d="M 8,8 L 8,20 L 16,20 L 16,8" fill="none" stroke="#EF4444" stroke-width="1"/>
<path d="M 10,6 L 10,8 M 14,6 L 14,8" stroke="#EF4444" stroke-width="1" stroke-linecap="round"/>
<line x1="10" y1="11" x2="10" y2="17" stroke="#EF4444" stroke-width="0.8" opacity="0.5"/>
<line x1="14" y1="11" x2="14" y2="17" stroke="#EF4444" stroke-width="0.8" opacity="0.5"/>
<text x="12" y="34" class="lbl">Delete</text>
</g>
<!-- Edit -->
<g transform="translate(270, 130)">
<path d="M 4,20 L 4,16 L 16,4 L 20,8 L 8,20 Z" fill="none" stroke="#A78BFA" stroke-width="1.2" stroke-linejoin="round"/>
<line x1="14" y1="6" x2="18" y2="10" stroke="#A78BFA" stroke-width="0.8" opacity="0.5"/>
<text x="12" y="34" class="lbl">Edit</text>
</g>
<!-- Refresh (angular cycle) -->
<g transform="translate(330, 130)">
<path d="M 18,10 A 7,7 0 1 0 16,18" fill="none" stroke="#A78BFA" stroke-width="1.3" stroke-linecap="round"/>
<path d="M 18,6 L 18,11 L 13,11" fill="none" stroke="#A78BFA" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
<text x="12" y="34" class="lbl">Refresh</text>
</g>
<!-- Send (angular arrow) -->
<g transform="translate(390, 130)" filter="url(#gl)">
<path d="M 4,20 L 4,12 L 20,4 L 12,20 L 10,14 Z" fill="url(#gAme)" opacity="0.8"/>
<path d="M 4,12 L 20,4 L 10,14 Z" fill="#fff" opacity="0.1"/>
<text x="12" y="34" class="lbl">Send</text>
</g>
<!-- === ROW 3: STATUS (contextual colors) === -->
<text x="20" y="190" font-size="9" fill="#475569" font-weight="600" letter-spacing="0.08em">STATUS &amp; FEEDBACK</text>
<!-- Checkmark (Emerald, animated subtle pulse) -->
<g transform="translate(30, 200)" filter="url(#gl)">
<circle cx="12" cy="12" r="10" fill="none" stroke="#10B981" stroke-width="1.2" opacity="0.5"/>
<path d="M 7,12 L 11,16 L 18,8" fill="none" stroke="url(#gEm)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<animate attributeName="opacity" values="0.7;1;0.7" dur="3s" repeatCount="indefinite"/>
</path>
<text x="12" y="34" class="lbl">Success</text>
</g>
<!-- Warning (Amber triangle) -->
<g transform="translate(90, 200)" filter="url(#gl)">
<path d="M 12,3 L 22,21 L 2,21 Z" fill="none" stroke="#F59E0B" stroke-width="1.2" stroke-linejoin="round"/>
<path d="M 12,3 L 22,21 L 2,21 Z" fill="#F59E0B" opacity="0.08"/>
<line x1="12" y1="10" x2="12" y2="15" stroke="#FCD34D" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="12" cy="18" r="1" fill="#FCD34D"/>
<text x="12" y="34" class="lbl">Warning</text>
</g>
<!-- Info (hexagonal i) -->
<g transform="translate(150, 200)">
<path d="M 12,2 L 20,7 L 20,17 L 12,22 L 4,17 L 4,7 Z" fill="none" stroke="#3B82F6" stroke-width="1" opacity="0.6"/>
<circle cx="12" cy="8" r="1" fill="#60A5FA"/>
<line x1="12" y1="11" x2="12" y2="17" stroke="#60A5FA" stroke-width="1.5" stroke-linecap="round"/>
<text x="12" y="34" class="lbl">Info</text>
</g>
<!-- Lock -->
<g transform="translate(210, 200)">
<rect x="6" y="12" width="12" height="10" rx="2" fill="none" stroke="#94A3B8" stroke-width="1.2"/>
<path d="M 9,12 L 9,8 A 3,3 0 0 1 15,8 L 15,12" fill="none" stroke="#94A3B8" stroke-width="1.2"/>
<circle cx="12" cy="17" r="1.2" fill="#94A3B8"/>
<text x="12" y="34" class="lbl">Lock</text>
</g>
<!-- Unlock -->
<g transform="translate(270, 200)">
<rect x="6" y="12" width="12" height="10" rx="2" fill="none" stroke="#10B981" stroke-width="1.2"/>
<path d="M 9,12 L 9,8 A 3,3 0 0 1 15,8 L 15,6" fill="none" stroke="#10B981" stroke-width="1.2"/>
<circle cx="12" cy="17" r="1.2" fill="#10B981"/>
<text x="12" y="34" class="lbl">Unlock</text>
</g>
<!-- Error (Ruby X in circle) -->
<g transform="translate(330, 200)">
<circle cx="12" cy="12" r="10" fill="none" stroke="#EF4444" stroke-width="1" opacity="0.5"/>
<path d="M 8,8 L 16,16 M 16,8 L 8,16" stroke="#EF4444" stroke-width="1.5" stroke-linecap="round"/>
<text x="12" y="34" class="lbl">Error</text>
</g>
<!-- === ROW 4: FEATURE-SPECIFIC (mixed jewel tones) === -->
<text x="20" y="260" font-size="9" fill="#475569" font-weight="600" letter-spacing="0.08em">FEATURE ICONS</text>
<!-- Turn/Rotate (animated slowly) -->
<g transform="translate(30, 270)" filter="url(#gl)">
<path d="M 12,4 L 18,12 L 12,20 L 6,12 Z" fill="url(#gAme)" opacity="0.7"/>
<path d="M 12,4 L 18,12 L 6,12 Z" fill="#fff" opacity="0.1"/>
<!-- Rotation arc -->
<path d="M 20,6 A 9,9 0 0 1 20,18" fill="none" stroke="#C4B5FD" stroke-width="0.8" opacity="0.5">
<animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="12s" repeatCount="indefinite"/>
</path>
<text x="12" y="34" class="lbl">Rotate</text>
</g>
<!-- Streak flame (Amber, animated flicker) -->
<g transform="translate(90, 270)" filter="url(#gl)">
<path d="M 12,3 Q 16,8 15,14 Q 18,10 17,6 Q 20,12 16,20 L 8,20 Q 4,12 7,6 Q 6,10 9,14 Q 8,8 12,3 Z" fill="url(#gAmb)" opacity="0.85">
<animate attributeName="opacity" values="0.75;0.95;0.75" dur="1.5s" repeatCount="indefinite"/>
</path>
<path d="M 12,3 Q 14,7 13,10 L 11,10 Q 10,7 12,3 Z" fill="#fff" opacity="0.15"/>
<text x="12" y="34" class="lbl">Streak</text>
</g>
<!-- Bell (notification) -->
<g transform="translate(150, 270)">
<path d="M 12,3 L 12,5 M 8,20 Q 12,23 16,20" fill="none" stroke="#E2E8F0" stroke-width="1" stroke-linecap="round"/>
<path d="M 6,18 L 6,12 Q 6,6 12,5 Q 18,6 18,12 L 18,18 Z" fill="none" stroke="#E2E8F0" stroke-width="1.2"/>
<!-- Notification dot -->
<circle cx="17" cy="6" r="2.5" fill="#EF4444" opacity="0.9">
<animate attributeName="opacity" values="0.7;1;0.7" dur="2s" repeatCount="indefinite"/>
</circle>
<text x="12" y="34" class="lbl">Notify</text>
</g>
<!-- Heart (angular, Rose) -->
<g transform="translate(210, 270)" filter="url(#gl)">
<path d="M 12,19 L 4,11 L 4,7 L 8,3 L 12,7 L 16,3 L 20,7 L 20,11 Z" fill="url(#gRose)" opacity="0.8"/>
<path d="M 12,7 L 8,3 L 4,7 L 4,11 Z" fill="#fff" opacity="0.1"/>
<text x="12" y="34" class="lbl">Heart</text>
</g>
<!-- Star (6-pointed crystalline) -->
<g transform="translate(270, 270)" filter="url(#gl)">
<path d="M 12,2 L 14,9 L 21,9 L 16,14 L 18,21 L 12,17 L 6,21 L 8,14 L 3,9 L 10,9 Z" fill="url(#gAmb)" opacity="0.75"/>
<path d="M 12,2 L 14,9 L 10,9 Z" fill="#fff" opacity="0.12"/>
<text x="12" y="34" class="lbl">Star</text>
</g>
<!-- Settings (hexagonal gear) -->
<g transform="translate(330, 270)">
<path d="M 10,2 L 14,2 L 16,5 L 20,5 L 22,9 L 20,12 L 22,15 L 20,19 L 16,19 L 14,22 L 10,22 L 8,19 L 4,19 L 2,15 L 4,12 L 2,9 L 4,5 L 8,5 Z" fill="none" stroke="#94A3B8" stroke-width="1"/>
<circle cx="12" cy="12" r="4" fill="none" stroke="#94A3B8" stroke-width="1"/>
<text x="12" y="34" class="lbl">Settings</text>
</g>
<!-- Calendar -->
<g transform="translate(390, 270)">
<rect x="4" y="6" width="16" height="16" rx="2" fill="none" stroke="#94A3B8" stroke-width="1"/>
<line x1="4" y1="10" x2="20" y2="10" stroke="#94A3B8" stroke-width="0.8"/>
<line x1="8" y1="4" x2="8" y2="8" stroke="#94A3B8" stroke-width="1" stroke-linecap="round"/>
<line x1="16" y1="4" x2="16" y2="8" stroke="#94A3B8" stroke-width="1" stroke-linecap="round"/>
<!-- Date dots -->
<circle cx="8" cy="14" r="1" fill="#94A3B8" opacity="0.5"/>
<circle cx="12" cy="14" r="1" fill="#8B5CF6" opacity="0.8"/>
<circle cx="16" cy="14" r="1" fill="#94A3B8" opacity="0.5"/>
<circle cx="8" cy="18" r="1" fill="#94A3B8" opacity="0.5"/>
<circle cx="12" cy="18" r="1" fill="#94A3B8" opacity="0.5"/>
<text x="12" y="34" class="lbl">Calendar</text>
</g>
<!-- === ROW 5: MEDIA & UI CONTROLS === -->
<text x="20" y="330" font-size="9" fill="#475569" font-weight="600" letter-spacing="0.08em">MEDIA &amp; CONTROLS</text>
<!-- Play -->
<g transform="translate(30, 340)">
<path d="M 7,4 L 20,12 L 7,20 Z" fill="url(#gAme)" opacity="0.8"/>
<path d="M 7,4 L 20,12 L 7,12 Z" fill="#fff" opacity="0.08"/>
<text x="12" y="34" class="lbl">Play</text>
</g>
<!-- Pause -->
<g transform="translate(90, 340)">
<rect x="6" y="5" width="4" height="14" rx="1" fill="#E2E8F0" opacity="0.8"/>
<rect x="14" y="5" width="4" height="14" rx="1" fill="#E2E8F0" opacity="0.8"/>
<text x="12" y="34" class="lbl">Pause</text>
</g>
<!-- Filter / Funnel -->
<g transform="translate(150, 340)">
<path d="M 3,5 L 21,5 L 14,14 L 14,20 L 10,22 L 10,14 Z" fill="none" stroke="#94A3B8" stroke-width="1.2" stroke-linejoin="round"/>
<text x="12" y="34" class="lbl">Filter</text>
</g>
<!-- Search -->
<g transform="translate(210, 340)">
<circle cx="10" cy="10" r="7" fill="none" stroke="#94A3B8" stroke-width="1.3"/>
<line x1="15" y1="15" x2="21" y2="21" stroke="#94A3B8" stroke-width="1.5" stroke-linecap="round"/>
<text x="12" y="34" class="lbl">Search</text>
</g>
<!-- Eye / View -->
<g transform="translate(270, 340)">
<path d="M 2,12 Q 12,4 22,12 Q 12,20 2,12 Z" fill="none" stroke="#94A3B8" stroke-width="1.1"/>
<!-- Diamond pupil -->
<path d="M 12,8 L 15,12 L 12,16 L 9,12 Z" fill="#94A3B8" opacity="0.6"/>
<text x="12" y="34" class="lbl">View</text>
</g>
<!-- Expand -->
<g transform="translate(330, 340)">
<path d="M 4,4 L 10,4 M 4,4 L 4,10" stroke="#94A3B8" stroke-width="1.3" stroke-linecap="round"/>
<path d="M 20,4 L 14,4 M 20,4 L 20,10" stroke="#94A3B8" stroke-width="1.3" stroke-linecap="round"/>
<path d="M 4,20 L 10,20 M 4,20 L 4,14" stroke="#94A3B8" stroke-width="1.3" stroke-linecap="round"/>
<path d="M 20,20 L 14,20 M 20,20 L 20,14" stroke="#94A3B8" stroke-width="1.3" stroke-linecap="round"/>
<text x="12" y="34" class="lbl">Expand</text>
</g>
<!-- Clock / Timer -->
<g transform="translate(390, 340)">
<path d="M 12,2 L 18,5 L 21,11 L 18,17 L 12,20 L 6,17 L 3,11 L 6,5 Z" fill="none" stroke="#94A3B8" stroke-width="1"/>
<line x1="12" y1="7" x2="12" y2="11" stroke="#E2E8F0" stroke-width="1" stroke-linecap="round"/>
<line x1="12" y1="11" x2="16" y2="14" stroke="#E2E8F0" stroke-width="1" stroke-linecap="round"/>
<text x="12" y="34" class="lbl">Timer</text>
</g>
<!-- === ROW 6: SPECIAL PURPOSE === -->
<text x="20" y="400" font-size="9" fill="#475569" font-weight="600" letter-spacing="0.08em">SPECIAL PURPOSE</text>
<!-- Upgrade / Crown (prismatic animated) -->
<g transform="translate(30, 410)" filter="url(#gl)">
<path d="M 4,18 L 4,10 L 8,14 L 12,6 L 16,14 L 20,10 L 20,18 Z" fill="url(#gAme)" opacity="0.8">
<animate attributeName="opacity" values="0.7;0.95;0.7" dur="3s" repeatCount="indefinite"/>
</path>
<path d="M 4,10 L 8,14 L 12,6 L 16,14 L 20,10 Z" fill="#fff" opacity="0.1"/>
<text x="12" y="34" class="lbl">Premium</text>
</g>
<!-- Science / Beaker -->
<g transform="translate(90, 410)">
<path d="M 9,4 L 9,12 L 4,20 L 20,20 L 15,12 L 15,4" fill="none" stroke="#60A5FA" stroke-width="1.2" stroke-linejoin="round"/>
<line x1="7" y1="4" x2="17" y2="4" stroke="#60A5FA" stroke-width="1"/>
<circle cx="10" cy="16" r="1.5" fill="#60A5FA" opacity="0.4"/>
<circle cx="14" cy="18" r="1" fill="#60A5FA" opacity="0.3"/>
<text x="12" y="34" class="lbl">Science</text>
</g>
<!-- Habit / Loop (animated subtle rotation) -->
<g transform="translate(150, 410)">
<path d="M 12,4 A 8,8 0 1 1 4,12" fill="none" stroke="#10B981" stroke-width="1.3" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="20s" repeatCount="indefinite"/>
</path>
<path d="M 12,4 L 14,7 L 10,7 Z" fill="#10B981"/>
<circle cx="12" cy="12" r="3" fill="none" stroke="#10B981" stroke-width="0.8" opacity="0.4"/>
<text x="12" y="34" class="lbl">Habit</text>
</g>
<!-- Breathe / Meditation -->
<g transform="translate(210, 410)">
<circle cx="12" cy="12" r="8" fill="none" stroke="#6366F1" stroke-width="1" opacity="0.4">
<animate attributeName="r" values="6;10;6" dur="4s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.2;0.5;0.2" dur="4s" repeatCount="indefinite"/>
</circle>
<circle cx="12" cy="12" r="3" fill="#818CF8" opacity="0.6">
<animate attributeName="r" values="2;4;2" dur="4s" repeatCount="indefinite"/>
</circle>
<text x="12" y="34" class="lbl">Breathe</text>
</g>
<!-- Evidence (diamond mosaic) -->
<g transform="translate(270, 410)" filter="url(#gl)">
<path d="M 8,6 L 12,3 L 16,6 L 12,9 Z" fill="#8B5CF6" opacity="0.6"/>
<path d="M 4,12 L 8,9 L 12,12 L 8,15 Z" fill="#3B82F6" opacity="0.5"/>
<path d="M 12,12 L 16,9 L 20,12 L 16,15 Z" fill="#10B981" opacity="0.5"/>
<path d="M 8,18 L 12,15 L 16,18 L 12,21 Z" fill="#F59E0B" opacity="0.5"/>
<text x="12" y="34" class="lbl">Evidence</text>
</g>
<!-- Fragment (the core ◇) -->
<g transform="translate(330, 410)" filter="url(#gl)">
<path d="M 12,2 L 20,12 L 12,22 L 4,12 Z" fill="url(#gAmb)" opacity="0.85"/>
<path d="M 12,2 L 20,12 L 4,12 Z" fill="#fff" opacity="0.12"/>
<line x1="4" y1="12" x2="20" y2="12" stroke="#fff" stroke-width="0.4" opacity="0.3"/>
<animate attributeName="opacity" values="0.8;1;0.8" dur="2.5s" repeatCount="indefinite"/>
<text x="12" y="34" class="lbl">Fragment</text>
</g>
<!-- Spectrum (prismatic bar) -->
<g transform="translate(390, 410)">
<rect x="4" y="8" width="4" height="8" rx="1" fill="#8B5CF6" opacity="0.7"/>
<rect x="9" y="6" width="4" height="12" rx="1" fill="#3B82F6" opacity="0.7"/>
<rect x="14" y="10" width="4" height="6" rx="1" fill="#10B981" opacity="0.7"/>
<rect x="4" y="18" width="14" height="2" rx="1" fill="#1C2240"/>
<text x="12" y="34" class="lbl">Spectrum</text>
</g>
<!-- Totals -->
<text x="280" y="480" font-size="9" fill="#475569" text-anchor="middle">38 icons — 24×24px each — Glow filters on primary actions — Animations on streak, breathe, rotate, notify</text>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,187 @@
<svg viewBox="0 0 560 320" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="gAmber" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="1.8" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<linearGradient id="amberGr" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
</style>
</defs>
<rect width="560" height="320" fill="#050508"/>
<text x="280" y="24" font-size="12" font-weight="600" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">COGNITIVE DISTORTION ICONS</text>
<text x="280" y="38" font-size="9" fill="#475569" text-anchor="middle">10 fragment types — Amber (#F59E0B) default, each with unique crystalline geometry</text>
<!-- Row 1: 5 icons -->
<!-- Each icon is 24x24, rendered in a 100x100 cell with label -->
<!-- 1. CATASTROPHIZING — A downward-cascading shard breaking into pieces -->
<g transform="translate(28, 60)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Main shard falling -->
<path d="M 0,-10 L 5,0 L 0,10 L -5,0 Z" fill="url(#amberGr)" opacity="0.9"/>
<path d="M 0,-10 L 5,0 L -5,0 Z" fill="#fff" opacity="0.15"/>
<!-- Smaller fragment broken off, lower-left -->
<path d="M -8,6 L -5,10 L -9,12 L -11,8 Z" fill="#F59E0B" opacity="0.5"/>
<!-- Smaller fragment, lower-right -->
<path d="M 7,8 L 10,12 L 8,14 L 5,11 Z" fill="#F59E0B" opacity="0.4"/>
<!-- Downward motion lines -->
<line x1="0" y1="11" x2="0" y2="16" stroke="#FCD34D" stroke-width="0.5" opacity="0.4"/>
<line x1="-3" y1="12" x2="-5" y2="17" stroke="#FCD34D" stroke-width="0.4" opacity="0.3"/>
<line x1="3" y1="12" x2="5" y2="17" stroke="#FCD34D" stroke-width="0.4" opacity="0.3"/>
</g>
<text x="12" y="42" class="lbl">Catastrophizing</text>
</g>
<!-- 2. BLACK-AND-WHITE — A diamond split in half: one side filled, one side outline -->
<g transform="translate(140, 60)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Left half: filled -->
<path d="M 0,-10 L -7,0 L 0,10 L 0,0 Z" fill="url(#amberGr)" opacity="0.85"/>
<!-- Right half: outline only -->
<path d="M 0,-10 L 7,0 L 0,10 L 0,0 Z" fill="none" stroke="#F59E0B" stroke-width="1"/>
<!-- Center dividing line -->
<line x1="0" y1="-10" x2="0" y2="10" stroke="#FEF3C7" stroke-width="0.6" opacity="0.5"/>
<!-- Top facet highlight on filled side -->
<path d="M 0,-10 L -7,0 L 0,0 Z" fill="#fff" opacity="0.12"/>
</g>
<text x="12" y="42" class="lbl">Black-and-White</text>
</g>
<!-- 3. MIND READING — A hexagonal head with a small eye/diamond inside -->
<g transform="translate(252, 60)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Hexagonal head outline -->
<path d="M 0,-10 L 8,-5 L 8,5 L 0,10 L -8,5 L -8,-5 Z" fill="none" stroke="#F59E0B" stroke-width="1" opacity="0.7"/>
<!-- Inner diamond (the "read" thought) -->
<path d="M 0,-4 L 4,0 L 0,4 L -4,0 Z" fill="url(#amberGr)" opacity="0.8"/>
<path d="M 0,-4 L 4,0 L -4,0 Z" fill="#fff" opacity="0.15"/>
<!-- Radiating lines from inner diamond (thought waves) -->
<line x1="5" y1="-3" x2="9" y2="-6" stroke="#FCD34D" stroke-width="0.4" opacity="0.35"/>
<line x1="5" y1="3" x2="9" y2="6" stroke="#FCD34D" stroke-width="0.4" opacity="0.35"/>
<line x1="-5" y1="-3" x2="-9" y2="-6" stroke="#FCD34D" stroke-width="0.4" opacity="0.35"/>
</g>
<text x="12" y="42" class="lbl">Mind Reading</text>
</g>
<!-- 4. FORTUNE TELLING — A crystal ball shape with forward-pointing shard -->
<g transform="translate(364, 60)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Crystal ball: faceted circle (octagon) -->
<path d="M 0,-9 L 6,-6 L 9,0 L 6,6 L 0,9 L -6,6 L -9,0 L -6,-6 Z" fill="none" stroke="#F59E0B" stroke-width="0.8" opacity="0.6"/>
<!-- Inner glow -->
<circle cx="0" cy="0" r="4" fill="#F59E0B" opacity="0.15"/>
<!-- Forward arrow shard -->
<path d="M 2,0 L 10,-3 L 14,0 L 10,3 Z" fill="url(#amberGr)" opacity="0.8"/>
<path d="M 2,0 L 10,-3 L 14,0 Z" fill="#fff" opacity="0.12"/>
<!-- Base -->
<path d="M -5,9 L 5,9 L 3,12 L -3,12 Z" fill="#F59E0B" opacity="0.3"/>
</g>
<text x="12" y="42" class="lbl">Fortune Telling</text>
</g>
<!-- 5. PERSONALIZATION — Inward-pointing shard arrows converging on center -->
<g transform="translate(476, 60)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Center dot -->
<circle cx="0" cy="0" r="2.5" fill="url(#amberGr)" opacity="0.8"/>
<!-- Four inward-pointing shards -->
<path d="M 0,-12 L 2,-6 L 0,-4 L -2,-6 Z" fill="#F59E0B" opacity="0.7"/>
<path d="M 12,0 L 6,2 L 4,0 L 6,-2 Z" fill="#F59E0B" opacity="0.6"/>
<path d="M 0,12 L -2,6 L 0,4 L 2,6 Z" fill="#F59E0B" opacity="0.6"/>
<path d="M -12,0 L -6,-2 L -4,0 L -6,2 Z" fill="#F59E0B" opacity="0.7"/>
<!-- Subtle connecting lines -->
<line x1="0" y1="-4" x2="0" y2="-2.5" stroke="#FCD34D" stroke-width="0.4" opacity="0.3"/>
<line x1="4" y1="0" x2="2.5" y2="0" stroke="#FCD34D" stroke-width="0.4" opacity="0.3"/>
</g>
<text x="12" y="42" class="lbl">Personalization</text>
</g>
<!-- Row 2: 5 icons -->
<!-- 6. DISCOUNTING POSITIVES — A star/gem shape with a line through it -->
<g transform="translate(28, 180)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- 6-pointed star -->
<path d="M 0,-10 L 3,-4 L 9,-4 L 5,1 L 7,7 L 0,4 L -7,7 L -5,1 L -9,-4 L -3,-4 Z" fill="url(#amberGr)" opacity="0.5"/>
<path d="M 0,-10 L 3,-4 L -3,-4 Z" fill="#fff" opacity="0.1"/>
<!-- Strike-through line (discounting) -->
<line x1="-10" y1="4" x2="10" y2="-4" stroke="#FEF3C7" stroke-width="1.2" opacity="0.7"/>
<!-- Dimmed effect on the star -->
<path d="M 0,-10 L 3,-4 L 9,-4 L 5,1 L 7,7 L 0,4 L -7,7 L -5,1 L -9,-4 L -3,-4 Z" fill="#050508" opacity="0.3"/>
</g>
<text x="12" y="42" class="lbl">Discounting +</text>
</g>
<!-- 7. EMOTIONAL REASONING — A heart shape made of angular geometry with = sign -->
<g transform="translate(140, 180)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Angular heart: two triangles forming a V-heart -->
<path d="M 0,8 L -8,-1 L -5,-7 L 0,-3 L 5,-7 L 8,-1 Z" fill="url(#amberGr)" opacity="0.7"/>
<path d="M 0,-3 L -5,-7 L -8,-1 Z" fill="#fff" opacity="0.12"/>
<!-- Equals sign next to it -->
<line x1="9" y1="-2" x2="14" y2="-2" stroke="#FCD34D" stroke-width="1" opacity="0.6"/>
<line x1="9" y1="2" x2="14" y2="2" stroke="#FCD34D" stroke-width="1" opacity="0.6"/>
</g>
<text x="12" y="42" class="lbl">Emotional Reasoning</text>
</g>
<!-- 8. SHOULD STATEMENTS — A rigid angular ruler/bracket shape -->
<g transform="translate(252, 180)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Rigid L-bracket -->
<path d="M -6,-10 L -6,10 L 6,10" fill="none" stroke="#F59E0B" stroke-width="1.2" stroke-linejoin="miter"/>
<!-- Tick marks along the ruler -->
<line x1="-6" y1="-6" x2="-3" y2="-6" stroke="#FCD34D" stroke-width="0.6" opacity="0.5"/>
<line x1="-6" y1="-2" x2="-3" y2="-2" stroke="#FCD34D" stroke-width="0.6" opacity="0.5"/>
<line x1="-6" y1="2" x2="-3" y2="2" stroke="#FCD34D" stroke-width="0.6" opacity="0.5"/>
<line x1="-6" y1="6" x2="-3" y2="6" stroke="#FCD34D" stroke-width="0.6" opacity="0.5"/>
<!-- Angular exclamation inside -->
<path d="M 2,-4 L 3,-1 L 1,-1 Z" fill="#FCD34D" opacity="0.6"/>
<circle cx="2" cy="1" r="0.8" fill="#FCD34D" opacity="0.6"/>
</g>
<text x="12" y="42" class="lbl">Should Statements</text>
</g>
<!-- 9. LABELING — An angular tag/label shape -->
<g transform="translate(364, 180)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Tag shape: pentagon pointing right -->
<path d="M -8,-7 L 5,-7 L 10,0 L 5,7 L -8,7 Z" fill="url(#amberGr)" opacity="0.6"/>
<path d="M -8,-7 L 5,-7 L 10,0 Z" fill="#fff" opacity="0.1"/>
<!-- Hole in tag -->
<circle cx="-3" cy="0" r="2" fill="#050508" opacity="0.6" stroke="#FCD34D" stroke-width="0.5"/>
<!-- Inner line -->
<line x1="1" y1="-3" x2="1" y2="3" stroke="#FEF3C7" stroke-width="0.4" opacity="0.3"/>
</g>
<text x="12" y="42" class="lbl">Labeling</text>
</g>
<!-- 10. OVERGENERALIZATION — One dot replicating into many -->
<g transform="translate(476, 180)" filter="url(#gAmber)">
<g transform="translate(12,12)">
<!-- Single source diamond -->
<path d="M -10,0 L -7,-3 L -4,0 L -7,3 Z" fill="url(#amberGr)" opacity="0.9"/>
<path d="M -10,0 L -7,-3 L -4,0 Z" fill="#fff" opacity="0.15"/>
<!-- Arrow -->
<line x1="-3" y1="0" x2="0" y2="0" stroke="#FCD34D" stroke-width="0.5" opacity="0.4"/>
<!-- Multiple replicated diamonds (spreading out) -->
<path d="M 2,-6 L 4,-8 L 6,-6 L 4,-4 Z" fill="#F59E0B" opacity="0.7"/>
<path d="M 4,0 L 6,-2 L 8,0 L 6,2 Z" fill="#F59E0B" opacity="0.6"/>
<path d="M 2,6 L 4,4 L 6,6 L 4,8 Z" fill="#F59E0B" opacity="0.5"/>
<path d="M 8,-4 L 10,-6 L 12,-4 L 10,-2 Z" fill="#F59E0B" opacity="0.4"/>
<path d="M 8,4 L 10,2 L 12,4 L 10,6 Z" fill="#F59E0B" opacity="0.35"/>
</g>
<text x="12" y="42" class="lbl">Overgeneralization</text>
</g>
<!-- Color reference -->
<text x="280" y="278" font-size="9" fill="#475569" text-anchor="middle">Primary: #F59E0B (Amber) — Gradient: #FDE68A to #D97706 — Highlight: #FCD34D</text>
<text x="280" y="292" font-size="9" fill="#475569" text-anchor="middle">All icons 24x24px — Glow filter for active/detected state — Outlined for reference/guide</text>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,258 @@
<svg viewBox="0 0 520 480" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Glow filters matched to soft-elegance: layered gaussian blur + source merge -->
<filter id="gAmethyst" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="gAmber" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="gEmerald" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="gSapphire" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="gSoft" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- Gradients — mimicking the soft-elegance per-blade gradient approach -->
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#047857"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/>
<stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
<linearGradient id="grSoftLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F1F5F9"/>
<stop offset="100%" stop-color="#CBD5E1"/>
</linearGradient>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
</style>
</defs>
<rect width="520" height="480" fill="#050508"/>
<!-- Title -->
<text x="260" y="32" font-size="14" font-weight="600" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">TAB BAR ICONS</text>
<!-- ================================================ -->
<!-- ROW 1: ACTIVE STATES -->
<!-- ================================================ -->
<text x="260" y="68" font-size="11" fill="#475569" text-anchor="middle" letter-spacing="0.08em">ACTIVE</text>
<!-- ◇ TURN — Active (Amethyst) -->
<g transform="translate(52, 100)">
<g filter="url(#gAmethyst)">
<!-- Outer diamond -->
<path d="M 24,2 L 44,24 L 24,46 L 4,24 Z" fill="url(#grAmethyst)" opacity="0.9"/>
<!-- Inner facet: horizontal midline creating top/bottom facets -->
<line x1="4" y1="24" x2="44" y2="24" stroke="#fff" stroke-width="0.5" opacity="0.35"/>
<!-- Inner facet: vertical center line -->
<line x1="24" y1="2" x2="24" y2="46" stroke="#fff" stroke-width="0.4" opacity="0.2"/>
<!-- Top facet highlight -->
<path d="M 24,2 L 44,24 L 4,24 Z" fill="#fff" opacity="0.12"/>
<!-- Specular glint -->
<circle cx="18" cy="18" r="2" fill="#fff" opacity="0.4"/>
<!-- Small rotating inner shard (suggests kaleidoscope turn) -->
<path d="M 24,12 L 30,24 L 24,36 L 18,24 Z" fill="none" stroke="#E9D5FF" stroke-width="0.6" opacity="0.5"/>
</g>
<text x="24" y="64" font-size="10" font-weight="600" fill="#C4B5FD" text-anchor="middle" letter-spacing="0.06em">TURN</text>
</g>
<!-- ✦ MIRROR — Active (Amber) -->
<g transform="translate(152, 100)">
<g filter="url(#gAmber)">
<!-- Hexagonal mirror shape -->
<path d="M 24,3 L 42,14 L 42,35 L 24,46 L 6,35 L 6,14 Z" fill="url(#grAmber)" opacity="0.9"/>
<!-- Horizontal reflection line -->
<line x1="6" y1="24.5" x2="42" y2="24.5" stroke="#fff" stroke-width="0.6" opacity="0.4"/>
<!-- Inner hexagon (mirror reflection) -->
<path d="M 24,10 L 35,17 L 35,32 L 24,39 L 13,32 L 13,17 Z" fill="none" stroke="#FEF3C7" stroke-width="0.5" opacity="0.4"/>
<!-- Top facet light -->
<path d="M 24,3 L 42,14 L 6,14 Z" fill="#fff" opacity="0.1"/>
<!-- Specular -->
<circle cx="19" cy="17" r="1.8" fill="#fff" opacity="0.35"/>
</g>
<text x="24" y="64" font-size="10" font-weight="600" fill="#FCD34D" text-anchor="middle" letter-spacing="0.06em">MIRROR</text>
</g>
<!-- ◎ LENS — Active (Emerald) -->
<g transform="translate(252, 100)">
<g filter="url(#gEmerald)">
<!-- Outer lens circle -->
<circle cx="24" cy="24" r="20" fill="none" stroke="url(#grEmerald)" stroke-width="2" opacity="0.9"/>
<!-- Middle ring -->
<circle cx="24" cy="24" r="13" fill="none" stroke="#6EE7B7" stroke-width="1" opacity="0.5"/>
<!-- Inner core (focus point) -->
<circle cx="24" cy="24" r="5" fill="url(#grEmerald)" opacity="0.8"/>
<!-- Cross-hair lines suggesting focus/precision -->
<line x1="24" y1="6" x2="24" y2="14" stroke="#A7F3D0" stroke-width="0.5" opacity="0.4"/>
<line x1="24" y1="34" x2="24" y2="42" stroke="#A7F3D0" stroke-width="0.5" opacity="0.4"/>
<line x1="6" y1="24" x2="14" y2="24" stroke="#A7F3D0" stroke-width="0.5" opacity="0.4"/>
<line x1="34" y1="24" x2="42" y2="24" stroke="#A7F3D0" stroke-width="0.5" opacity="0.4"/>
<!-- Specular -->
<circle cx="19" cy="19" r="2.5" fill="#fff" opacity="0.2"/>
</g>
<text x="24" y="64" font-size="10" font-weight="600" fill="#6EE7B7" text-anchor="middle" letter-spacing="0.06em">LENS</text>
</g>
<!-- ▦ GALLERY — Active (Sapphire) -->
<g transform="translate(352, 100)">
<g filter="url(#gSapphire)">
<!-- 2x2 grid of small faceted shapes -->
<!-- Top-left diamond -->
<path d="M 12,6 L 20,12 L 12,18 L 4,12 Z" fill="url(#grSapphire)" opacity="0.85"/>
<path d="M 12,6 L 20,12 L 4,12 Z" fill="#fff" opacity="0.1"/>
<!-- Top-right diamond -->
<path d="M 36,6 L 44,12 L 36,18 L 28,12 Z" fill="url(#grSapphire)" opacity="0.75"/>
<path d="M 36,6 L 44,12 L 28,12 Z" fill="#fff" opacity="0.08"/>
<!-- Bottom-left diamond -->
<path d="M 12,28 L 20,34 L 12,40 L 4,34 Z" fill="url(#grSapphire)" opacity="0.7"/>
<path d="M 12,28 L 20,34 L 4,34 Z" fill="#fff" opacity="0.08"/>
<!-- Bottom-right diamond -->
<path d="M 36,28 L 44,34 L 36,40 L 28,34 Z" fill="url(#grSapphire)" opacity="0.85"/>
<path d="M 36,28 L 44,34 L 28,34 Z" fill="#fff" opacity="0.1"/>
<!-- Thin connecting lines -->
<line x1="20" y1="12" x2="28" y2="12" stroke="#93C5FD" stroke-width="0.4" opacity="0.3"/>
<line x1="12" y1="18" x2="12" y2="28" stroke="#93C5FD" stroke-width="0.4" opacity="0.3"/>
<line x1="36" y1="18" x2="36" y2="28" stroke="#93C5FD" stroke-width="0.4" opacity="0.3"/>
<line x1="20" y1="34" x2="28" y2="34" stroke="#93C5FD" stroke-width="0.4" opacity="0.3"/>
</g>
<text x="24" y="64" font-size="10" font-weight="600" fill="#93C5FD" text-anchor="middle" letter-spacing="0.06em">GALLERY</text>
</g>
<!-- ● YOU — Active (Soft Light) -->
<g transform="translate(452, 100)">
<g filter="url(#gSoft)">
<!-- Angular avatar: hexagonal head + angular shoulders -->
<path d="M 24,4 L 32,10 L 32,20 L 24,26 L 16,20 L 16,10 Z" fill="url(#grSoftLight)" opacity="0.85"/>
<path d="M 24,4 L 32,10 L 16,10 Z" fill="#fff" opacity="0.12"/>
<!-- Shoulders: angular trapezoid -->
<path d="M 8,46 L 14,32 L 24,28 L 34,32 L 40,46 Z" fill="url(#grSoftLight)" opacity="0.65"/>
<path d="M 14,32 L 24,28 L 34,32 Z" fill="#fff" opacity="0.08"/>
<!-- Specular on face -->
<circle cx="20" cy="14" r="1.5" fill="#fff" opacity="0.3"/>
</g>
<text x="24" y="64" font-size="10" font-weight="600" fill="#E2E8F0" text-anchor="middle" letter-spacing="0.06em">YOU</text>
</g>
<!-- ================================================ -->
<!-- ROW 2: INACTIVE STATES -->
<!-- ================================================ -->
<text x="260" y="212" font-size="11" fill="#475569" text-anchor="middle" letter-spacing="0.08em">INACTIVE</text>
<!-- ◇ TURN — Inactive -->
<g transform="translate(52, 240)">
<path d="M 24,2 L 44,24 L 24,46 L 4,24 Z" fill="none" stroke="#475569" stroke-width="1.2"/>
<line x1="4" y1="24" x2="44" y2="24" stroke="#475569" stroke-width="0.4" opacity="0.3"/>
<path d="M 24,12 L 30,24 L 24,36 L 18,24 Z" fill="none" stroke="#475569" stroke-width="0.5" opacity="0.3"/>
<text x="24" y="64" font-size="10" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.06em">TURN</text>
</g>
<!-- ✦ MIRROR — Inactive -->
<g transform="translate(152, 240)">
<path d="M 24,3 L 42,14 L 42,35 L 24,46 L 6,35 L 6,14 Z" fill="none" stroke="#475569" stroke-width="1.2"/>
<line x1="6" y1="24.5" x2="42" y2="24.5" stroke="#475569" stroke-width="0.4" opacity="0.3"/>
<path d="M 24,10 L 35,17 L 35,32 L 24,39 L 13,32 L 13,17 Z" fill="none" stroke="#475569" stroke-width="0.4" opacity="0.25"/>
<text x="24" y="64" font-size="10" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.06em">MIRROR</text>
</g>
<!-- ◎ LENS — Inactive -->
<g transform="translate(252, 240)">
<circle cx="24" cy="24" r="20" fill="none" stroke="#475569" stroke-width="1.2"/>
<circle cx="24" cy="24" r="13" fill="none" stroke="#475569" stroke-width="0.5" opacity="0.3"/>
<circle cx="24" cy="24" r="5" fill="none" stroke="#475569" stroke-width="0.8" opacity="0.5"/>
<line x1="24" y1="6" x2="24" y2="14" stroke="#475569" stroke-width="0.4" opacity="0.25"/>
<line x1="24" y1="34" x2="24" y2="42" stroke="#475569" stroke-width="0.4" opacity="0.25"/>
<line x1="6" y1="24" x2="14" y2="24" stroke="#475569" stroke-width="0.4" opacity="0.25"/>
<line x1="34" y1="24" x2="42" y2="24" stroke="#475569" stroke-width="0.4" opacity="0.25"/>
<text x="24" y="64" font-size="10" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.06em">LENS</text>
</g>
<!-- ▦ GALLERY — Inactive -->
<g transform="translate(352, 240)">
<path d="M 12,6 L 20,12 L 12,18 L 4,12 Z" fill="none" stroke="#475569" stroke-width="1"/>
<path d="M 36,6 L 44,12 L 36,18 L 28,12 Z" fill="none" stroke="#475569" stroke-width="1"/>
<path d="M 12,28 L 20,34 L 12,40 L 4,34 Z" fill="none" stroke="#475569" stroke-width="1"/>
<path d="M 36,28 L 44,34 L 36,40 L 28,34 Z" fill="none" stroke="#475569" stroke-width="1"/>
<line x1="20" y1="12" x2="28" y2="12" stroke="#475569" stroke-width="0.3" opacity="0.25"/>
<line x1="12" y1="18" x2="12" y2="28" stroke="#475569" stroke-width="0.3" opacity="0.25"/>
<line x1="36" y1="18" x2="36" y2="28" stroke="#475569" stroke-width="0.3" opacity="0.25"/>
<line x1="20" y1="34" x2="28" y2="34" stroke="#475569" stroke-width="0.3" opacity="0.25"/>
<text x="24" y="64" font-size="10" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.06em">GALLERY</text>
</g>
<!-- ● YOU — Inactive -->
<g transform="translate(452, 240)">
<path d="M 24,4 L 32,10 L 32,20 L 24,26 L 16,20 L 16,10 Z" fill="none" stroke="#475569" stroke-width="1.2"/>
<path d="M 8,46 L 14,32 L 24,28 L 34,32 L 40,46 Z" fill="none" stroke="#475569" stroke-width="1" opacity="0.6"/>
<text x="24" y="64" font-size="10" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.06em">YOU</text>
</g>
<!-- ================================================ -->
<!-- ROW 3: SIMULATED TAB BAR (in-context preview) -->
<!-- ================================================ -->
<text x="260" y="356" font-size="11" fill="#475569" text-anchor="middle" letter-spacing="0.08em">TAB BAR PREVIEW (Turn active)</text>
<!-- Tab bar background -->
<rect x="35" y="370" width="450" height="80" rx="16" fill="#0A0E1A" stroke="#1C2240" stroke-width="1"/>
<line x1="35" y1="370" x2="485" y2="370" stroke="#1C2240" stroke-width="1"/>
<!-- Turn — Active -->
<g transform="translate(62, 380)">
<g filter="url(#gAmethyst)">
<path d="M 12,1 L 22,12 L 12,23 L 2,12 Z" fill="url(#grAmethyst)" opacity="0.9"/>
<path d="M 12,1 L 22,12 L 2,12 Z" fill="#fff" opacity="0.1"/>
<line x1="2" y1="12" x2="22" y2="12" stroke="#fff" stroke-width="0.4" opacity="0.25"/>
</g>
<text x="12" y="36" font-size="9" font-weight="600" fill="#C4B5FD" text-anchor="middle" letter-spacing="0.04em">Turn</text>
</g>
<!-- Mirror — Inactive -->
<g transform="translate(152, 380)">
<path d="M 12,1.5 L 21,7.5 L 21,17.5 L 12,23.5 L 3,17.5 L 3,7.5 Z" fill="none" stroke="#475569" stroke-width="1"/>
<text x="12" y="36" font-size="9" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.04em">Mirror</text>
</g>
<!-- Lens — Inactive -->
<g transform="translate(242, 380)">
<circle cx="12" cy="12" r="10" fill="none" stroke="#475569" stroke-width="1"/>
<circle cx="12" cy="12" r="3" fill="none" stroke="#475569" stroke-width="0.6" opacity="0.5"/>
<text x="12" y="36" font-size="9" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.04em">Lens</text>
</g>
<!-- Gallery — Inactive -->
<g transform="translate(327, 380)">
<path d="M 6,3 L 10,6 L 6,9 L 2,6 Z" fill="none" stroke="#475569" stroke-width="0.8"/>
<path d="M 18,3 L 22,6 L 18,9 L 14,6 Z" fill="none" stroke="#475569" stroke-width="0.8"/>
<path d="M 6,15 L 10,18 L 6,21 L 2,18 Z" fill="none" stroke="#475569" stroke-width="0.8"/>
<path d="M 18,15 L 22,18 L 18,21 L 14,18 Z" fill="none" stroke="#475569" stroke-width="0.8"/>
<text x="12" y="36" font-size="9" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.04em">Gallery</text>
</g>
<!-- You — Inactive -->
<g transform="translate(417, 380)">
<path d="M 12,2 L 16,5 L 16,10 L 12,13 L 8,10 L 8,5 Z" fill="none" stroke="#475569" stroke-width="0.9"/>
<path d="M 4,23 L 7,16 L 12,14 L 17,16 L 20,23 Z" fill="none" stroke="#475569" stroke-width="0.8" opacity="0.6"/>
<text x="12" y="36" font-size="9" font-weight="500" fill="#475569" text-anchor="middle" letter-spacing="0.04em">You</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,329 @@
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" width="400" height="400">
<defs>
<!-- Blade Gradients: Amethyst (0°) -->
<linearGradient id="g0_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#A78BFA"><animate attributeName="stop-color" values="#A78BFA;#C4B5FD;#A78BFA" dur="5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#8B5CF6"><animate attributeName="stop-color" values="#8B5CF6;#A78BFA;#8B5CF6" dur="5s" repeatCount="indefinite"/></stop>
</linearGradient>
<linearGradient id="g0_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#8B5CF6"/><stop offset="100%" stop-color="#5B21B6"/>
</linearGradient>
<!-- Blade Gradients: Sapphire (60°) -->
<linearGradient id="g1_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#93C5FD"><animate attributeName="stop-color" values="#93C5FD;#BFDBFE;#93C5FD" dur="5.5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#3B82F6"/>
</linearGradient>
<linearGradient id="g1_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3B82F6"/><stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
<!-- Blade Gradients: Emerald (120°) -->
<linearGradient id="g2_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#6EE7B7"><animate attributeName="stop-color" values="#6EE7B7;#A7F3D0;#6EE7B7" dur="6s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#10B981"/>
</linearGradient>
<linearGradient id="g2_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#10B981"/><stop offset="100%" stop-color="#047857"/>
</linearGradient>
<!-- Blade Gradients: Amber (180°) -->
<linearGradient id="g3_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#FCD34D"><animate attributeName="stop-color" values="#FCD34D;#FDE68A;#FCD34D" dur="4.8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#F59E0B"/>
</linearGradient>
<linearGradient id="g3_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#F59E0B"/><stop offset="100%" stop-color="#B45309"/>
</linearGradient>
<!-- Blade Gradients: Rose (240°) -->
<linearGradient id="g4_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#F9A8D4"><animate attributeName="stop-color" values="#F9A8D4;#FBCFE8;#F9A8D4" dur="5.2s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#EC4899"/>
</linearGradient>
<linearGradient id="g4_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#EC4899"/><stop offset="100%" stop-color="#BE185D"/>
</linearGradient>
<!-- Blade Gradients: Indigo (300°) -->
<linearGradient id="g5_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#A5B4FC"><animate attributeName="stop-color" values="#A5B4FC;#C7D2FE;#A5B4FC" dur="5.8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#6366F1"/>
</linearGradient>
<linearGradient id="g5_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#6366F1"/><stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<!-- Cycling Prismatic Core Gradient for halo -->
<radialGradient id="prismatic" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#FFFFFF"><animate attributeName="stop-color" values="#FFFFFF;#E0D5FF;#FFFFFF" dur="5s" repeatCount="indefinite"/></stop>
<stop offset="40%" stop-color="#8B5CF6"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="8s" repeatCount="indefinite"/></stop>
</radialGradient>
<!-- Outer aura for transparent bg -->
<radialGradient id="outerAura" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#8B5CF6" stop-opacity="0.08"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="10s" repeatCount="indefinite"/></stop>
<stop offset="70%" stop-color="#8B5CF6" stop-opacity="0.03"/>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"/>
</radialGradient>
<!-- Core transition glow (replaces hard white circle) -->
<radialGradient id="coreHalo" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0"/>
<stop offset="60%" stop-color="#FFFFFF" stop-opacity="0"/>
<stop offset="80%" stop-color="#C4B5FD" stop-opacity="0.08"><animate attributeName="stop-color" values="#C4B5FD;#93C5FD;#6EE7B7;#FDE68A;#FBCFE8;#C4B5FD" dur="8s" repeatCount="indefinite"/><animate attributeName="stop-opacity" values="0.05;0.12;0.05" dur="4s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"/>
</radialGradient>
<!-- Filters -->
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="coreGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="12" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="shimmer" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="wideGlow" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="18" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="bladeTipGlow" x="-200%" y="-200%" width="500%" height="500%">
<feGaussianBlur stdDeviation="5" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- Subtle breathing aura -->
<circle cx="200" cy="200" r="190" fill="url(#outerAura)">
<animate attributeName="r" values="175;195;175" dur="6s" repeatCount="indefinite"/>
</circle>
<!-- The Iris — slow continuous rotation -->
<g transform="translate(200, 200)">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="90s" repeatCount="indefinite" additive="sum"/>
<!-- Amethyst Blade (0°) with shimmer -->
<g transform="rotate(0)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g0_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round">
<animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4s" repeatCount="indefinite"/>
</path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g0_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round">
<animate attributeName="fill-opacity" values="0.90;1;0.90" dur="4s" repeatCount="indefinite"/>
</path>
<!-- Edge shimmer with color tint -->
<line x1="40" y1="0" x2="-30" y2="160" stroke="#C4B5FD" stroke-width="1.2" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="0s" repeatCount="indefinite"/>
</line>
</g>
<!-- Sapphire Blade (60°) -->
<g transform="rotate(60)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g1_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round">
<animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4.5s" repeatCount="indefinite"/>
</path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g1_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#93C5FD" stroke-width="1.2" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="0.83s" repeatCount="indefinite"/>
</line>
</g>
<!-- Emerald Blade (120°) -->
<g transform="rotate(120)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g2_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round">
<animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="5s" repeatCount="indefinite"/>
</path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g2_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#6EE7B7" stroke-width="1.2" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="1.66s" repeatCount="indefinite"/>
</line>
</g>
<!-- Amber Blade (180°) -->
<g transform="rotate(180)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g3_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round">
<animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4.2s" repeatCount="indefinite"/>
</path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g3_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#FDE68A" stroke-width="1.2" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="2.5s" repeatCount="indefinite"/>
</line>
</g>
<!-- Rose Blade (240°) -->
<g transform="rotate(240)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g4_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round">
<animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4.8s" repeatCount="indefinite"/>
</path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g4_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#FBCFE8" stroke-width="1.2" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="3.33s" repeatCount="indefinite"/>
</line>
</g>
<!-- Indigo Blade (300°) -->
<g transform="rotate(300)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g5_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round">
<animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4.3s" repeatCount="indefinite"/>
</path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g5_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#A5B4FC" stroke-width="1.2" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="4.16s" repeatCount="indefinite"/>
</line>
</g>
<!-- Blade tip glow points — soft colored pulses at outer tips of each blade -->
<g filter="url(#bladeTipGlow)">
<circle cx="-30" cy="160" r="4" fill="#A78BFA" opacity="0">
<animate attributeName="opacity" values="0;0.35;0" dur="4s" repeatCount="indefinite"/>
</circle>
<circle cx="-30" cy="160" r="4" fill="#3B82F6" opacity="0" transform="rotate(60)">
<animate attributeName="opacity" values="0;0.35;0" dur="4.5s" begin="0.5s" repeatCount="indefinite"/>
</circle>
<circle cx="-30" cy="160" r="4" fill="#10B981" opacity="0" transform="rotate(120)">
<animate attributeName="opacity" values="0;0.35;0" dur="5s" begin="1s" repeatCount="indefinite"/>
</circle>
<circle cx="-30" cy="160" r="4" fill="#F59E0B" opacity="0" transform="rotate(180)">
<animate attributeName="opacity" values="0;0.35;0" dur="4.2s" begin="1.5s" repeatCount="indefinite"/>
</circle>
<circle cx="-30" cy="160" r="4" fill="#EC4899" opacity="0" transform="rotate(240)">
<animate attributeName="opacity" values="0;0.35;0" dur="4.8s" begin="2s" repeatCount="indefinite"/>
</circle>
<circle cx="-30" cy="160" r="4" fill="#6366F1" opacity="0" transform="rotate(300)">
<animate attributeName="opacity" values="0;0.35;0" dur="4.3s" begin="2.5s" repeatCount="indefinite"/>
</circle>
</g>
<!-- THE KALEIDOSCOPE CORE - SOFT ELEGANCE FINALIST -->
<g filter="url(#coreGlow)">
<!-- Soft radial gradient halo with prismatic color cycling -->
<circle r="45" fill="url(#prismatic)" opacity="0.35">
<animate attributeName="opacity" values="0.25;0.45;0.25" dur="5s" repeatCount="indefinite"/>
</circle>
<!-- Organic core transition glow (replaces hard white circle) -->
<circle r="55" fill="url(#coreHalo)">
<animate attributeName="r" values="50;58;50" dur="4s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.6;1;0.6" dur="4s" repeatCount="indefinite"/>
</circle>
<!-- Secondary set of 6 thicker colored rays (per brand colors) rotating slowly -->
<g opacity="0.35">
<animateTransform attributeName="transform" type="rotate" from="0" to="-360" dur="150s" repeatCount="indefinite"/>
<polygon points="0,0 2,35 -2,35" fill="#A78BFA">
<animate attributeName="opacity" values="0.20;0.40;0.20" dur="4s" repeatCount="indefinite"/>
</polygon>
<polygon points="0,0 2,35 -2,35" fill="#3B82F6" transform="rotate(60)">
<animate attributeName="opacity" values="0.20;0.40;0.20" dur="4.2s" begin="0.4s" repeatCount="indefinite"/>
</polygon>
<polygon points="0,0 2,35 -2,35" fill="#10B981" transform="rotate(120)">
<animate attributeName="opacity" values="0.20;0.40;0.20" dur="4.4s" begin="0.8s" repeatCount="indefinite"/>
</polygon>
<polygon points="0,0 2,35 -2,35" fill="#F59E0B" transform="rotate(180)">
<animate attributeName="opacity" values="0.20;0.40;0.20" dur="4s" begin="1.2s" repeatCount="indefinite"/>
</polygon>
<polygon points="0,0 2,35 -2,35" fill="#EC4899" transform="rotate(240)">
<animate attributeName="opacity" values="0.20;0.40;0.20" dur="4.3s" begin="1.6s" repeatCount="indefinite"/>
</polygon>
<polygon points="0,0 2,35 -2,35" fill="#6366F1" transform="rotate(300)">
<animate attributeName="opacity" values="0.20;0.40;0.20" dur="4.5s" begin="2s" repeatCount="indefinite"/>
</polygon>
</g>
<!-- Primary set of 12 thin white rays radiating outward, alternating lengths -->
<g opacity="0.3">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="75s" repeatCount="indefinite"/>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0">
<animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4s" begin="0s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(30)">
<animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.5s" begin="0.3s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(60)">
<animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4.2s" begin="0.6s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(90)">
<animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.8s" begin="0.9s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(120)">
<animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4s" begin="1.2s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(150)">
<animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.6s" begin="1.5s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(180)">
<animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4.3s" begin="1.8s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(210)">
<animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.7s" begin="2.1s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(240)">
<animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4.1s" begin="2.4s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(270)">
<animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.9s" begin="2.7s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(300)">
<animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4s" begin="3s" repeatCount="indefinite"/>
</line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(330)">
<animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.8s" begin="3.3s" repeatCount="indefinite"/>
</line>
</g>
<!-- Central bright white-to-prismatic circle with heavy glow and pulsing -->
<circle r="12" fill="#ffffff" filter="url(#coreGlow)" opacity="0.95">
<animate attributeName="r" values="10;14;10" dur="3s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.85;1;0.85" dur="3s" repeatCount="indefinite"/>
</circle>
<!-- Inner refraction crescent -->
<circle cx="-4" cy="-4" r="6" fill="#ffffff" opacity="0.2">
<animate attributeName="opacity" values="0.2;0.35;0.2" dur="4s" repeatCount="indefinite"/>
</circle>
<!-- Tiny sharp specular highlight off-center -->
<circle cx="3" cy="-3" r="1.5" fill="#ffffff" opacity="0.9" filter="url(#glow)">
<animate attributeName="opacity" values="0.6;1;0.6" dur="1.5s" repeatCount="indefinite"/>
</circle>
<!-- Additional warm specular (new — adds dimension) -->
<circle cx="-2" cy="5" r="2" fill="#FDE68A" opacity="0">
<animate attributeName="opacity" values="0;0.15;0" dur="5s" repeatCount="indefinite"/>
</circle>
</g>
<!-- Orbiting light motes -->
<g filter="url(#shimmer)">
<circle r="2" fill="#A78BFA" opacity="0">
<animateMotion dur="7s" repeatCount="indefinite" path="M0,-155 A155,155 0 1 1 -0.1,-155"/>
<animate attributeName="opacity" values="0;0.9;0;0;0" dur="7s" repeatCount="indefinite"/>
</circle>
<circle r="1.5" fill="#3B82F6" opacity="0">
<animateMotion dur="9s" repeatCount="indefinite" path="M0,-140 A140,140 0 1 0 -0.1,-140"/>
<animate attributeName="opacity" values="0;0;0.8;0;0" dur="9s" repeatCount="indefinite"/>
</circle>
<circle r="1.5" fill="#10B981" opacity="0">
<animateMotion dur="6s" repeatCount="indefinite" path="M0,-165 A165,165 0 1 1 -0.1,-165"/>
<animate attributeName="opacity" values="0;0.7;0;0.5;0" dur="6s" repeatCount="indefinite"/>
</circle>
<circle r="2" fill="#F59E0B" opacity="0">
<animateMotion dur="11s" repeatCount="indefinite" path="M0,-130 A130,130 0 1 0 -0.1,-130"/>
<animate attributeName="opacity" values="0;0;0;0.9;0" dur="11s" repeatCount="indefinite"/>
</circle>
<circle r="1" fill="#EC4899" opacity="0">
<animateMotion dur="8s" repeatCount="indefinite" path="M0,-150 A150,150 0 1 1 -0.1,-150"/>
<animate attributeName="opacity" values="0;0.8;0;0;0.6;0" dur="8s" repeatCount="indefinite"/>
</circle>
<circle r="1.5" fill="#fff" opacity="0">
<animateMotion dur="5s" repeatCount="indefinite" path="M0,-120 A120,120 0 1 0 -0.1,-120"/>
<animate attributeName="opacity" values="0;1;0;0;0" dur="5s" repeatCount="indefinite"/>
</circle>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,389 @@
<svg viewBox="0 0 700 820" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"><animate attributeName="stop-color" values="#C4B5FD;#DDD6FE;#C4B5FD" dur="4s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<linearGradient id="grIndigo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A5B4FC"/><stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<linearGradient id="shimmerGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1C2240" stop-opacity="0"/>
<stop offset="50%" stop-color="#2A3158" stop-opacity="1"/>
<stop offset="100%" stop-color="#1C2240" stop-opacity="0"/>
</linearGradient>
<radialGradient id="successGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#10B981" stop-opacity="0.4"/>
<stop offset="100%" stop-color="#10B981" stop-opacity="0"/>
</radialGradient>
<filter id="glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="shimmer" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
.section-lbl { font-size: 11px; fill: #64748B; font-weight: 600; letter-spacing: 0.08em; }
</style>
</defs>
<rect width="700" height="820" fill="#050508"/>
<text x="350" y="28" font-size="13" font-weight="700" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">LOADING AND ANIMATION STATES</text>
<text x="350" y="44" font-size="9" fill="#475569" text-anchor="middle">Spinners, skeleton shimmers, Turn animation, AI thinking, success burst, breathing logo</text>
<!-- Row 1: Spinners -->
<text x="24" y="74" class="section-lbl">SPINNERS</text>
<line x1="24" y1="80" x2="670" y2="80" stroke="#1C2240" stroke-width="0.5"/>
<!-- 1. Fragment Spinner — rotating diamond with trail -->
<g transform="translate(100, 140)">
<g filter="url(#glowMd)">
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="1.5s" repeatCount="indefinite"/>
<!-- Trail fragments (fading) -->
<path d="M 0,-20 L 4,-16 L 0,-12 L -4,-16 Z" fill="url(#grAmethyst)" opacity="0.9"/>
<path d="M 0,-20 L 4,-16 L 0,-16 Z" fill="#fff" opacity="0.2"/>
<g transform="rotate(90)">
<path d="M 0,-20 L 4,-16 L 0,-12 L -4,-16 Z" fill="url(#grSapphire)" opacity="0.6"/>
</g>
<g transform="rotate(180)">
<path d="M 0,-20 L 4,-16 L 0,-12 L -4,-16 Z" fill="url(#grEmerald)" opacity="0.3"/>
</g>
<g transform="rotate(270)">
<path d="M 0,-20 L 4,-16 L 0,-12 L -4,-16 Z" fill="url(#grAmber)" opacity="0.15"/>
</g>
</g>
</g>
<text x="0" y="42" class="lbl">Fragment Spinner</text>
</g>
<!-- 2. Iris Spinner — mini kaleidoscope rotation -->
<g transform="translate(260, 140)">
<g filter="url(#glowMd)">
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="3s" repeatCount="indefinite"/>
<g style="mix-blend-mode: screen;">
<path d="M 0,0 L 3,-1 L -2,20 L -3,1 Z" fill="url(#grAmethyst)" opacity="0.6">
<animate attributeName="fill-opacity" values="0.4;0.7;0.4" dur="2s" repeatCount="indefinite"/>
</path>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(60)">
<path d="M 0,0 L 3,-1 L -2,20 L -3,1 Z" fill="url(#grSapphire)" opacity="0.5"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120)">
<path d="M 0,0 L 3,-1 L -2,20 L -3,1 Z" fill="url(#grEmerald)" opacity="0.5"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(180)">
<path d="M 0,0 L 3,-1 L -2,20 L -3,1 Z" fill="url(#grAmber)" opacity="0.6"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240)">
<path d="M 0,0 L 3,-1 L -2,20 L -3,1 Z" fill="url(#grRose)" opacity="0.5"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(300)">
<path d="M 0,0 L 3,-1 L -2,20 L -3,1 Z" fill="url(#grIndigo)" opacity="0.5"/>
</g>
</g>
<circle r="3" fill="#fff" opacity="0.5">
<animate attributeName="opacity" values="0.3;0.7;0.3" dur="1.5s" repeatCount="indefinite"/>
</circle>
</g>
<text x="0" y="42" class="lbl">Iris Spinner (mini logo)</text>
</g>
<!-- 3. Dot Pulse Loader -->
<g transform="translate(420, 140)">
<g filter="url(#glowSm)">
<circle cx="-16" cy="0" r="4" fill="url(#grAmethyst)" opacity="0.3">
<animate attributeName="opacity" values="0.3;0.9;0.3" dur="1.2s" begin="0s" repeatCount="indefinite"/>
<animate attributeName="r" values="3;5;3" dur="1.2s" begin="0s" repeatCount="indefinite"/>
</circle>
<circle cx="0" cy="0" r="4" fill="url(#grSapphire)" opacity="0.3">
<animate attributeName="opacity" values="0.3;0.9;0.3" dur="1.2s" begin="0.2s" repeatCount="indefinite"/>
<animate attributeName="r" values="3;5;3" dur="1.2s" begin="0.2s" repeatCount="indefinite"/>
</circle>
<circle cx="16" cy="0" r="4" fill="url(#grEmerald)" opacity="0.3">
<animate attributeName="opacity" values="0.3;0.9;0.3" dur="1.2s" begin="0.4s" repeatCount="indefinite"/>
<animate attributeName="r" values="3;5;3" dur="1.2s" begin="0.4s" repeatCount="indefinite"/>
</circle>
</g>
<text x="0" y="28" class="lbl">Dot Pulse</text>
</g>
<!-- 4. Fragment Orbit Loader -->
<g transform="translate(580, 140)">
<g filter="url(#shimmer)">
<!-- Orbit path -->
<circle r="18" fill="none" stroke="#1C2240" stroke-width="0.4"/>
<!-- Orbiting fragment -->
<g>
<path d="M 0,-4 L 4,0 L 0,4 L -4,0 Z" fill="url(#grAmethyst)" opacity="0.8">
<animate attributeName="opacity" values="0.5;1;0.5" dur="1.8s" repeatCount="indefinite"/>
</path>
<animateMotion dur="1.8s" repeatCount="indefinite" path="M0,-18 A18,18 0 1 1 -0.1,-18"/>
</g>
<!-- Ghost trail -->
<g>
<path d="M 0,-3 L 3,0 L 0,3 L -3,0 Z" fill="#8B5CF6" opacity="0.3">
<animate attributeName="opacity" values="0.1;0.4;0.1" dur="1.8s" begin="0.3s" repeatCount="indefinite"/>
</path>
<animateMotion dur="1.8s" repeatCount="indefinite" path="M0,-18 A18,18 0 1 1 -0.1,-18" begin="0.3s"/>
</g>
</g>
<text x="0" y="38" class="lbl">Fragment Orbit</text>
</g>
<!-- Row 2: Skeleton Shimmers -->
<text x="24" y="204" class="section-lbl">SKELETON SHIMMERS</text>
<line x1="24" y1="210" x2="670" y2="210" stroke="#1C2240" stroke-width="0.5"/>
<!-- Text skeleton -->
<g transform="translate(175, 250)">
<rect x="-120" y="-8" width="160" height="10" rx="5" fill="#121628"/>
<rect x="-120" y="-8" width="40" height="10" rx="5" fill="url(#shimmerGrad)" opacity="0.5">
<animate attributeName="x" values="-160;120" dur="1.5s" repeatCount="indefinite"/>
</rect>
<rect x="-120" y="8" width="200" height="10" rx="5" fill="#121628"/>
<rect x="-120" y="8" width="40" height="10" rx="5" fill="url(#shimmerGrad)" opacity="0.5">
<animate attributeName="x" values="-160;160" dur="1.5s" begin="0.2s" repeatCount="indefinite"/>
</rect>
<rect x="-120" y="24" width="120" height="10" rx="5" fill="#121628"/>
<rect x="-120" y="24" width="40" height="10" rx="5" fill="url(#shimmerGrad)" opacity="0.5">
<animate attributeName="x" values="-160;80" dur="1.5s" begin="0.4s" repeatCount="indefinite"/>
</rect>
<text x="-20" y="54" class="lbl">Text skeleton shimmer</text>
</g>
<!-- Card skeleton -->
<g transform="translate(500, 250)">
<rect x="-80" y="-30" width="160" height="80" rx="10" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<!-- Image placeholder -->
<rect x="-68" y="-20" width="40" height="40" rx="6" fill="#121628"/>
<rect x="-68" y="-20" width="20" height="40" rx="6" fill="url(#shimmerGrad)" opacity="0.4">
<animate attributeName="x" values="-88;-28" dur="1.5s" repeatCount="indefinite"/>
</rect>
<!-- Text lines -->
<rect x="-16" y="-16" width="80" height="8" rx="4" fill="#121628"/>
<rect x="-16" y="-16" width="20" height="8" rx="4" fill="url(#shimmerGrad)" opacity="0.4">
<animate attributeName="x" values="-36;64" dur="1.5s" begin="0.1s" repeatCount="indefinite"/>
</rect>
<rect x="-16" y="-2" width="60" height="8" rx="4" fill="#121628"/>
<rect x="-16" y="12" width="40" height="6" rx="3" fill="#121628"/>
<text x="0" y="66" class="lbl">Card skeleton shimmer</text>
</g>
<!-- Row 3: Turn Animation, AI Thinking -->
<text x="24" y="344" class="section-lbl">FEATURE ANIMATIONS</text>
<line x1="24" y1="350" x2="670" y2="350" stroke="#1C2240" stroke-width="0.5"/>
<!-- Turn Animation — kaleidoscope shift -->
<g transform="translate(175, 430)">
<g filter="url(#glowLg)">
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="60" dur="2s" repeatCount="indefinite"/>
<g style="mix-blend-mode: screen;">
<path d="M 0,0 L 6,-2 L -4,35 L -6,1 Z" fill="url(#grAmethyst)" opacity="0.5">
<animate attributeName="fill-opacity" values="0.3;0.6;0.3" dur="2s" repeatCount="indefinite"/>
</path>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(60)">
<path d="M 0,0 L 6,-2 L -4,35 L -6,1 Z" fill="url(#grSapphire)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120)">
<path d="M 0,0 L 6,-2 L -4,35 L -6,1 Z" fill="url(#grEmerald)" opacity="0.4"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(180)">
<path d="M 0,0 L 6,-2 L -4,35 L -6,1 Z" fill="url(#grAmber)" opacity="0.5"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240)">
<path d="M 0,0 L 6,-2 L -4,35 L -6,1 Z" fill="url(#grRose)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(300)">
<path d="M 0,0 L 6,-2 L -4,35 L -6,1 Z" fill="url(#grIndigo)" opacity="0.4"/>
</g>
</g>
<!-- Core pulse -->
<circle r="8" fill="#fff" opacity="0.4" filter="url(#glowMd)">
<animate attributeName="r" values="6;10;6" dur="2s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.3;0.6;0.3" dur="2s" repeatCount="indefinite"/>
</circle>
<circle r="3" fill="#fff" opacity="0.7"/>
</g>
<text x="0" y="60" class="lbl">Turn Animation (reframe moment)</text>
<text x="0" y="72" font-size="8" fill="#475569" text-anchor="middle">Rotates 60deg per cycle, pulses core</text>
</g>
<!-- AI Thinking — 3 fragments oscillating -->
<g transform="translate(475, 430)">
<g filter="url(#glowMd)">
<!-- Background bubble -->
<rect x="-50" y="-20" width="100" height="40" rx="20" fill="#121628" stroke="#1C2240" stroke-width="0.6"/>
<!-- Three oscillating fragments -->
<g transform="translate(-20, 0)">
<path d="M 0,-5 L 5,0 L 0,5 L -5,0 Z" fill="url(#grAmethyst)" opacity="0.4">
<animate attributeName="opacity" values="0.2;0.8;0.2" dur="1.5s" begin="0s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="-20,-3;-20,3;-20,-3" dur="1.5s" begin="0s" repeatCount="indefinite"/>
</path>
</g>
<g transform="translate(0, 0)">
<path d="M 0,-5 L 5,0 L 0,5 L -5,0 Z" fill="url(#grSapphire)" opacity="0.4">
<animate attributeName="opacity" values="0.2;0.8;0.2" dur="1.5s" begin="0.3s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,-3;0,3;0,-3" dur="1.5s" begin="0.3s" repeatCount="indefinite"/>
</path>
</g>
<g transform="translate(20, 0)">
<path d="M 0,-5 L 5,0 L 0,5 L -5,0 Z" fill="url(#grEmerald)" opacity="0.4">
<animate attributeName="opacity" values="0.2;0.8;0.2" dur="1.5s" begin="0.6s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="20,-3;20,3;20,-3" dur="1.5s" begin="0.6s" repeatCount="indefinite"/>
</path>
</g>
</g>
<text x="0" y="40" class="lbl">AI Thinking (Claude processing)</text>
</g>
<!-- Row 4: Success Burst, Breathing Logo -->
<text x="24" y="524" class="section-lbl">COMPLETION STATES</text>
<line x1="24" y1="530" x2="670" y2="530" stroke="#1C2240" stroke-width="0.5"/>
<!-- Success Burst -->
<g transform="translate(175, 610)">
<g filter="url(#glowLg)">
<!-- Burst ring -->
<circle r="30" fill="none" stroke="#10B981" stroke-width="2" opacity="0">
<animate attributeName="r" values="10;35;40" dur="1.5s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.6;0.3;0" dur="1.5s" repeatCount="indefinite"/>
</circle>
<circle r="20" fill="none" stroke="#6EE7B7" stroke-width="1" opacity="0">
<animate attributeName="r" values="8;25;30" dur="1.5s" begin="0.2s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.4;0.2;0" dur="1.5s" begin="0.2s" repeatCount="indefinite"/>
</circle>
<!-- Central checkmark fragment -->
<circle r="16" fill="url(#successGlow)" opacity="0.6">
<animate attributeName="opacity" values="0.4;0.8;0.4" dur="2s" repeatCount="indefinite"/>
</circle>
<path d="M 0,-10 L 10,0 L 0,10 L -10,0 Z" fill="url(#grEmerald)" opacity="0.9"/>
<path d="M 0,-10 L 10,0 L 0,0 Z" fill="#fff" opacity="0.2"/>
<path d="M -4,0 L -1,4 L 5,-3" fill="none" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
<!-- Particle shards -->
<g>
<path d="M 0,-22 L 2,-20 L 0,-18 L -2,-20 Z" fill="#6EE7B7" opacity="0">
<animate attributeName="opacity" values="0;0.8;0" dur="1.5s" repeatCount="indefinite"/>
</path>
<g transform="rotate(72)"><path d="M 0,-22 L 2,-20 L 0,-18 L -2,-20 Z" fill="#6EE7B7" opacity="0">
<animate attributeName="opacity" values="0;0.7;0" dur="1.5s" begin="0.1s" repeatCount="indefinite"/>
</path></g>
<g transform="rotate(144)"><path d="M 0,-22 L 2,-20 L 0,-18 L -2,-20 Z" fill="#A7F3D0" opacity="0">
<animate attributeName="opacity" values="0;0.6;0" dur="1.5s" begin="0.2s" repeatCount="indefinite"/>
</path></g>
<g transform="rotate(216)"><path d="M 0,-22 L 2,-20 L 0,-18 L -2,-20 Z" fill="#6EE7B7" opacity="0">
<animate attributeName="opacity" values="0;0.7;0" dur="1.5s" begin="0.3s" repeatCount="indefinite"/>
</path></g>
<g transform="rotate(288)"><path d="M 0,-22 L 2,-20 L 0,-18 L -2,-20 Z" fill="#A7F3D0" opacity="0">
<animate attributeName="opacity" values="0;0.6;0" dur="1.5s" begin="0.4s" repeatCount="indefinite"/>
</path></g>
</g>
</g>
<text x="0" y="56" class="lbl">Success Burst (Turn complete, goal achieved)</text>
</g>
<!-- Breathing Logo -->
<g transform="translate(475, 610)">
<g filter="url(#glowMd)">
<!-- Outer breathing aura -->
<circle r="30" fill="#8B5CF6" opacity="0.04">
<animate attributeName="r" values="25;35;25" dur="5s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.02;0.08;0.02" dur="5s" repeatCount="indefinite"/>
</circle>
<!-- Mini 3-blade iris -->
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="60s" repeatCount="indefinite"/>
<g style="mix-blend-mode: screen;">
<path d="M 0,0 L 3,-1 L -2,22 L -3,1 Z" fill="url(#grAmethyst)" opacity="0.4">
<animate attributeName="fill-opacity" values="0.3;0.5;0.3" dur="5s" repeatCount="indefinite"/>
</path>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120)">
<path d="M 0,0 L 3,-1 L -2,22 L -3,1 Z" fill="url(#grSapphire)" opacity="0.35"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240)">
<path d="M 0,0 L 3,-1 L -2,22 L -3,1 Z" fill="url(#grEmerald)" opacity="0.35"/>
</g>
</g>
<circle r="4" fill="#fff" opacity="0.5" filter="url(#glowSm)">
<animate attributeName="opacity" values="0.3;0.7;0.3" dur="5s" repeatCount="indefinite"/>
<animate attributeName="r" values="3;5;3" dur="5s" repeatCount="indefinite"/>
</circle>
</g>
<text x="0" y="52" class="lbl">Breathing Logo (idle/splash)</text>
</g>
<!-- Row 5: Page Transition -->
<text x="24" y="700" class="section-lbl">TRANSITIONS</text>
<line x1="24" y1="706" x2="670" y2="706" stroke="#1C2240" stroke-width="0.5"/>
<!-- Page fade transition -->
<g transform="translate(175, 760)">
<g filter="url(#glowSm)">
<!-- "From" state -->
<rect x="-55" y="-20" width="40" height="40" rx="6" fill="#121628" stroke="#1C2240" stroke-width="0.5" opacity="0.4"/>
<text x="-35" y="2" font-size="7" fill="#475569" text-anchor="middle">From</text>
<!-- Arrow -->
<path d="M -8,0 L 8,0 M 4,-4 L 8,0 L 4,4" fill="none" stroke="#475569" stroke-width="0.8"/>
<!-- "To" state -->
<rect x="15" y="-20" width="40" height="40" rx="6" fill="#121628" stroke="#8B5CF6" stroke-width="0.5" opacity="0.8"/>
<text x="35" y="2" font-size="7" fill="#C4B5FD" text-anchor="middle">To</text>
</g>
<text x="0" y="40" class="lbl">Fade + slide up transition</text>
</g>
<!-- Fragment scatter transition -->
<g transform="translate(475, 760)">
<g filter="url(#glowSm)">
<!-- Scattered fragments converging -->
<path d="M -30,-15 L -26,-11 L -30,-7 L -34,-11 Z" fill="url(#grAmethyst)" opacity="0.3">
<animate attributeName="opacity" values="0;0.6;0" dur="2s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="-30,-15;0,0;-30,-15" dur="2s" repeatCount="indefinite"/>
</path>
<path d="M 25,-20 L 29,-16 L 25,-12 L 21,-16 Z" fill="url(#grSapphire)" opacity="0.3">
<animate attributeName="opacity" values="0;0.6;0" dur="2s" begin="0.2s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="25,-20;0,0;25,-20" dur="2s" begin="0.2s" repeatCount="indefinite"/>
</path>
<path d="M 20,18 L 24,22 L 20,26 L 16,22 Z" fill="url(#grEmerald)" opacity="0.3">
<animate attributeName="opacity" values="0;0.6;0" dur="2s" begin="0.4s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="20,18;0,0;20,18" dur="2s" begin="0.4s" repeatCount="indefinite"/>
</path>
<path d="M -28,12 L -24,16 L -28,20 L -32,16 Z" fill="url(#grAmber)" opacity="0.3">
<animate attributeName="opacity" values="0;0.6;0" dur="2s" begin="0.6s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="-28,12;0,0;-28,12" dur="2s" begin="0.6s" repeatCount="indefinite"/>
</path>
<!-- Center convergence point -->
<circle r="3" fill="#fff" opacity="0">
<animate attributeName="opacity" values="0;0.8;0" dur="2s" begin="0.8s" repeatCount="indefinite"/>
</circle>
</g>
<text x="0" y="40" class="lbl">Fragment scatter/converge transition</text>
</g>
<text x="350" y="812" font-size="8" fill="#334155" text-anchor="middle">All animations use SMIL for broad SVG compatibility — breathing, rotation, opacity, and transform animations</text>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,301 @@
<svg viewBox="0 0 700 740" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"><animate attributeName="stop-color" values="#C4B5FD;#DDD6FE;#C4B5FD" dur="4s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"><animate attributeName="stop-color" values="#93C5FD;#BFDBFE;#93C5FD" dur="4.5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<linearGradient id="grIndigo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A5B4FC"/><stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<radialGradient id="coreGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fff" stop-opacity="0.3"/>
<stop offset="50%" stop-color="#8B5CF6" stop-opacity="0.1"/>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"/>
</radialGradient>
<filter id="glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- Clip for circular pattern containers -->
<clipPath id="circClip60"><circle r="60"/></clipPath>
<clipPath id="circClip40"><circle r="40"/></clipPath>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
.section-lbl { font-size: 11px; fill: #64748B; font-weight: 600; letter-spacing: 0.08em; }
</style>
</defs>
<rect width="700" height="740" fill="#050508"/>
<text x="350" y="28" font-size="13" font-weight="700" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">KALEIDOSCOPE PATTERNS</text>
<text x="350" y="44" font-size="9" fill="#475569" text-anchor="middle">7 pattern variants for cards, Gallery, hero displays — each uses 6-fold symmetry from the logo</text>
<!-- Row 1: Turn, Mirror, Ritual -->
<text x="24" y="74" class="section-lbl">FEATURE PATTERNS</text>
<line x1="24" y1="80" x2="670" y2="80" stroke="#1C2240" stroke-width="0.5"/>
<!-- 1. TURN PATTERN (Amethyst) — 6 blades rotating slowly -->
<g transform="translate(120, 170)">
<circle r="62" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<g clip-path="url(#circClip60)">
<circle r="60" fill="#0A0E1A"/>
<g filter="url(#glowMd)">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="60s" repeatCount="indefinite"/>
<g style="mix-blend-mode: screen;">
<path d="M 0,0 L 8,-2 L -5,55 L -8,2 Z" fill="url(#grAmethyst)" opacity="0.5">
<animate attributeName="fill-opacity" values="0.4;0.6;0.4" dur="4s" repeatCount="indefinite"/>
</path>
<path d="M 0,0 L 8,-2 L -5,55 Z" fill="#fff" opacity="0.08"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(60)">
<path d="M 0,0 L 8,-2 L -5,55 L -8,2 Z" fill="url(#grAmethyst)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120)">
<path d="M 0,0 L 8,-2 L -5,55 L -8,2 Z" fill="url(#grAmethyst)" opacity="0.4"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(180)">
<path d="M 0,0 L 8,-2 L -5,55 L -8,2 Z" fill="url(#grAmethyst)" opacity="0.5"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240)">
<path d="M 0,0 L 8,-2 L -5,55 L -8,2 Z" fill="url(#grAmethyst)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(300)">
<path d="M 0,0 L 8,-2 L -5,55 L -8,2 Z" fill="url(#grAmethyst)" opacity="0.4"/>
</g>
</g>
<!-- Core glow -->
<circle r="12" fill="url(#coreGlow)" opacity="0.8">
<animate attributeName="opacity" values="0.6;1;0.6" dur="3s" repeatCount="indefinite"/>
</circle>
<circle r="4" fill="#fff" opacity="0.5" filter="url(#glowSm)">
<animate attributeName="r" values="3;5;3" dur="3s" repeatCount="indefinite"/>
</circle>
</g>
<text x="0" y="82" class="lbl">Turn Pattern</text>
<text x="0" y="94" font-size="8" fill="#475569" text-anchor="middle">Amethyst blades</text>
</g>
<!-- 2. MIRROR PATTERN (Amber) — angular, reflective symmetry -->
<g transform="translate(350, 170)">
<circle r="62" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<g clip-path="url(#circClip60)">
<circle r="60" fill="#0A0E1A"/>
<g filter="url(#glowMd)">
<animateTransform attributeName="transform" type="rotate" from="0" to="-360" dur="90s" repeatCount="indefinite"/>
<!-- Mirrored angular shards -->
<g style="mix-blend-mode: screen;">
<path d="M 0,0 L 6,-4 L 10,50 L -4,2 Z" fill="url(#grAmber)" opacity="0.45"/>
<path d="M 0,0 L -6,-4 L -10,50 L 4,2 Z" fill="url(#grAmber)" opacity="0.4"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120)">
<path d="M 0,0 L 6,-4 L 10,50 L -4,2 Z" fill="url(#grAmber)" opacity="0.4"/>
<path d="M 0,0 L -6,-4 L -10,50 L 4,2 Z" fill="url(#grAmber)" opacity="0.35"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240)">
<path d="M 0,0 L 6,-4 L 10,50 L -4,2 Z" fill="url(#grAmber)" opacity="0.45"/>
<path d="M 0,0 L -6,-4 L -10,50 L 4,2 Z" fill="url(#grAmber)" opacity="0.4"/>
</g>
</g>
<circle r="10" fill="url(#coreGlow)" opacity="0.7"/>
<circle r="3" fill="#FDE68A" opacity="0.5" filter="url(#glowSm)"/>
</g>
<text x="0" y="82" class="lbl">Mirror Pattern</text>
<text x="0" y="94" font-size="8" fill="#475569" text-anchor="middle">Amber shards, bilateral</text>
</g>
<!-- 3. RITUAL PATTERN (Emerald) — concentric rings with fragments -->
<g transform="translate(580, 170)">
<circle r="62" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<g clip-path="url(#circClip60)">
<circle r="60" fill="#0A0E1A"/>
<g filter="url(#glowMd)">
<!-- Concentric ring fragments -->
<circle r="45" fill="none" stroke="#10B981" stroke-width="0.8" opacity="0.2" stroke-dasharray="6 8"/>
<circle r="30" fill="none" stroke="#10B981" stroke-width="0.6" opacity="0.25" stroke-dasharray="4 6"/>
<!-- 6 fragments on outer ring -->
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="45s" repeatCount="indefinite"/>
<path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#grEmerald)" opacity="0.6"/>
<path d="M 0,-45 L 3,-42 L 0,-42 Z" fill="#fff" opacity="0.1"/>
<g transform="rotate(60)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#grEmerald)" opacity="0.5"/></g>
<g transform="rotate(120)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#grEmerald)" opacity="0.55"/></g>
<g transform="rotate(180)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#grEmerald)" opacity="0.5"/></g>
<g transform="rotate(240)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#grEmerald)" opacity="0.6"/></g>
<g transform="rotate(300)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#grEmerald)" opacity="0.5"/></g>
</g>
<!-- Inner ring fragments -->
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="-360" dur="30s" repeatCount="indefinite"/>
<path d="M 0,-30 L 2,-28 L 0,-26 L -2,-28 Z" fill="#6EE7B7" opacity="0.5"/>
<g transform="rotate(90)"><path d="M 0,-30 L 2,-28 L 0,-26 L -2,-28 Z" fill="#6EE7B7" opacity="0.4"/></g>
<g transform="rotate(180)"><path d="M 0,-30 L 2,-28 L 0,-26 L -2,-28 Z" fill="#6EE7B7" opacity="0.5"/></g>
<g transform="rotate(270)"><path d="M 0,-30 L 2,-28 L 0,-26 L -2,-28 Z" fill="#6EE7B7" opacity="0.4"/></g>
</g>
</g>
<circle r="8" fill="url(#coreGlow)" opacity="0.6"/>
<circle r="3" fill="#6EE7B7" opacity="0.5" filter="url(#glowSm)"/>
</g>
<text x="0" y="82" class="lbl">Ritual Pattern</text>
<text x="0" y="94" font-size="8" fill="#475569" text-anchor="middle">Emerald concentric rings</text>
</g>
<!-- Row 2: Simple, Complex, Thumbnail, Hero -->
<text x="24" y="310" class="section-lbl">COMPLEXITY VARIANTS</text>
<line x1="24" y1="316" x2="670" y2="316" stroke="#1C2240" stroke-width="0.5"/>
<!-- 4. SIMPLE — minimal 3-blade -->
<g transform="translate(100, 400)">
<circle r="42" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<g clip-path="url(#circClip40)">
<circle r="40" fill="#0A0E1A"/>
<g filter="url(#glowSm)">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="50s" repeatCount="indefinite"/>
<g style="mix-blend-mode: screen;">
<path d="M 0,0 L 5,-2 L -2,36 L -5,1 Z" fill="url(#grAmethyst)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120)">
<path d="M 0,0 L 5,-2 L -2,36 L -5,1 Z" fill="url(#grSapphire)" opacity="0.4"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240)">
<path d="M 0,0 L 5,-2 L -2,36 L -5,1 Z" fill="url(#grEmerald)" opacity="0.4"/>
</g>
</g>
<circle r="3" fill="#fff" opacity="0.4" filter="url(#glowSm)"/>
</g>
<text x="0" y="60" class="lbl">Simple</text>
</g>
<!-- 5. COMPLEX — full 6-blade prismatic -->
<g transform="translate(270, 400)">
<circle r="42" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<g clip-path="url(#circClip40)">
<circle r="40" fill="#0A0E1A"/>
<g filter="url(#glowMd)">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="70s" repeatCount="indefinite"/>
<g style="mix-blend-mode: screen;">
<path d="M 0,0 L 6,-2 L -4,38 L -6,1 Z" fill="url(#grAmethyst)" opacity="0.5"/>
<path d="M 0,0 L 6,-2 L -4,38 Z" fill="#fff" opacity="0.06"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(60)">
<path d="M 0,0 L 6,-2 L -4,38 L -6,1 Z" fill="url(#grSapphire)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120)">
<path d="M 0,0 L 6,-2 L -4,38 L -6,1 Z" fill="url(#grEmerald)" opacity="0.4"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(180)">
<path d="M 0,0 L 6,-2 L -4,38 L -6,1 Z" fill="url(#grAmber)" opacity="0.5"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240)">
<path d="M 0,0 L 6,-2 L -4,38 L -6,1 Z" fill="url(#grRose)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(300)">
<path d="M 0,0 L 6,-2 L -4,38 L -6,1 Z" fill="url(#grIndigo)" opacity="0.4"/>
</g>
</g>
<circle r="8" fill="url(#coreGlow)" opacity="0.7"/>
<circle r="3" fill="#fff" opacity="0.6" filter="url(#glowSm)">
<animate attributeName="r" values="2;4;2" dur="3s" repeatCount="indefinite"/>
</circle>
</g>
<text x="0" y="60" class="lbl">Complex</text>
</g>
<!-- 6. THUMBNAIL — tiny for list items -->
<g transform="translate(420, 400)">
<circle r="20" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="40s" repeatCount="indefinite"/>
<g style="mix-blend-mode: screen;">
<path d="M 420,400 L 423,399 L 418,418 L 417,401 Z" fill="url(#grAmethyst)" opacity="0.4"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120, 420, 400)">
<path d="M 420,400 L 423,399 L 418,418 L 417,401 Z" fill="url(#grSapphire)" opacity="0.35"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240, 420, 400)">
<path d="M 420,400 L 423,399 L 418,418 L 417,401 Z" fill="url(#grEmerald)" opacity="0.35"/>
</g>
</g>
<circle cx="420" cy="400" r="2" fill="#fff" opacity="0.4"/>
<text x="0" y="40" class="lbl">Thumbnail</text>
</g>
<!-- 7. HERO — large, full prismatic with all effects -->
<text x="24" y="490" class="section-lbl">HERO VARIANT</text>
<line x1="24" y1="496" x2="670" y2="496" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 610)">
<circle r="100" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<!-- Outer aura -->
<circle r="95" fill="#8B5CF6" opacity="0.03" filter="url(#glowLg)">
<animate attributeName="r" values="85;100;85" dur="6s" repeatCount="indefinite"/>
</circle>
<g>
<g filter="url(#glowLg)">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="90s" repeatCount="indefinite"/>
<!-- Full 6-blade iris -->
<g style="mix-blend-mode: screen;">
<path d="M 350,610 L 358,608 L 340,690 L 342,612 Z" fill="url(#grAmethyst)" opacity="0.55">
<animate attributeName="fill-opacity" values="0.45;0.65;0.45" dur="4s" repeatCount="indefinite"/>
</path>
<path d="M 350,610 L 358,608 L 340,690 Z" fill="#fff" opacity="0.08"/>
<line x1="350" y1="610" x2="340" y2="690" stroke="#C4B5FD" stroke-width="0.8" opacity="0">
<animate attributeName="opacity" values="0;0.3;0" dur="5s" repeatCount="indefinite"/>
</line>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(60, 350, 610)">
<path d="M 350,610 L 358,608 L 340,690 L 342,612 Z" fill="url(#grSapphire)" opacity="0.5"/>
<line x1="350" y1="610" x2="340" y2="690" stroke="#93C5FD" stroke-width="0.8" opacity="0">
<animate attributeName="opacity" values="0;0.3;0" dur="5s" begin="0.8s" repeatCount="indefinite"/>
</line>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120, 350, 610)">
<path d="M 350,610 L 358,608 L 340,690 L 342,612 Z" fill="url(#grEmerald)" opacity="0.45"/>
<line x1="350" y1="610" x2="340" y2="690" stroke="#6EE7B7" stroke-width="0.8" opacity="0">
<animate attributeName="opacity" values="0;0.3;0" dur="5s" begin="1.6s" repeatCount="indefinite"/>
</line>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(180, 350, 610)">
<path d="M 350,610 L 358,608 L 340,690 L 342,612 Z" fill="url(#grAmber)" opacity="0.55"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240, 350, 610)">
<path d="M 350,610 L 358,608 L 340,690 L 342,612 Z" fill="url(#grRose)" opacity="0.5"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(300, 350, 610)">
<path d="M 350,610 L 358,608 L 340,690 L 342,612 Z" fill="url(#grIndigo)" opacity="0.45"/>
</g>
</g>
</g>
<!-- Core -->
<circle r="20" fill="url(#coreGlow)" opacity="0.8" filter="url(#glowMd)">
<animate attributeName="opacity" values="0.6;1;0.6" dur="4s" repeatCount="indefinite"/>
</circle>
<circle r="6" fill="#fff" opacity="0.7" filter="url(#glowSm)">
<animate attributeName="r" values="5;8;5" dur="3s" repeatCount="indefinite"/>
</circle>
<circle cx="-3" cy="-3" r="2" fill="#fff" opacity="0.3"/>
<text x="0" y="120" class="lbl">Hero Pattern — full prismatic 6-blade iris with edge shimmers, core glow, breathing animation</text>
</g>
<text x="350" y="734" font-size="8" fill="#334155" text-anchor="middle">All patterns use 6-fold rotational symmetry, screen blend mode, and the soft-elegance crystalline visual language</text>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,186 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kalei SVG Asset Library — Preview</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap');
body {
background: #050508;
color: #E2E8F0;
font-family: 'Inter', sans-serif;
padding: 40px;
}
h1 {
font-family: 'Space Grotesk', sans-serif;
font-size: 32px;
font-weight: 700;
margin-bottom: 8px;
background: linear-gradient(135deg, #8B5CF6, #3B82F6, #10B981, #F59E0B, #EC4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle { color: #94A3B8; font-size: 14px; margin-bottom: 40px; }
.section {
margin-bottom: 48px;
border-bottom: 1px solid rgba(28,34,64,0.5);
padding-bottom: 32px;
}
.section h2 {
font-size: 18px;
font-weight: 600;
color: #A78BFA;
margin-bottom: 8px;
letter-spacing: 0.03em;
}
.section .desc {
font-size: 13px;
color: #94A3B8;
margin-bottom: 16px;
}
.section .file-name {
font-size: 11px;
font-weight: 500;
color: #475569;
background: #121628;
display: inline-block;
padding: 3px 10px;
border-radius: 6px;
margin-bottom: 16px;
font-family: monospace;
}
.asset-frame {
background: #0A0E1A;
border: 1px solid #1C2240;
border-radius: 16px;
padding: 24px;
overflow: auto;
max-height: 600px;
}
.asset-frame object, .asset-frame img {
width: 100%;
max-width: 600px;
height: auto;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<h1>KALEI ASSET LIBRARY</h1>
<p class="subtitle">All SVG assets for the Kalei platform, derived from the soft-elegance logo visual language. 12 files, 100+ elements.</p>
<div class="section">
<h2>Tab Bar Icons</h2>
<div class="desc">5 navigation icons (Turn, Mirror, Lens, Gallery, You) in active + inactive states</div>
<div class="file-name">icons-tab-bar.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="icons-tab-bar.svg"></object>
</div>
</div>
<div class="section">
<h2>Cognitive Distortion Icons</h2>
<div class="desc">10 fragment type icons for Mirror feature detection</div>
<div class="file-name">icons-distortions.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="icons-distortions.svg"></object>
</div>
</div>
<div class="section">
<h2>Action & UI Icons</h2>
<div class="desc">30 icons for navigation, actions, status, and feature-specific use</div>
<div class="file-name">icons-actions.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="icons-actions.svg"></object>
</div>
</div>
<div class="section">
<h2>Fragment Icons & Highlights</h2>
<div class="desc">The core ◇ fragment in 5 sizes, 8 color states, plus highlight effects</div>
<div class="file-name">fragment-icons.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="fragment-icons.svg"></object>
</div>
</div>
<div class="section">
<h2>Decorative Shard Elements</h2>
<div class="desc">Background auras, floating shards, dividers, corner accents, edge shimmers</div>
<div class="file-name">decorative-shards.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="decorative-shards.svg"></object>
</div>
</div>
<div class="grid-2">
<div class="section">
<h2>Kaleidoscope Patterns</h2>
<div class="desc">7 pattern variants for cards, Gallery, and hero displays</div>
<div class="file-name">patterns-kaleidoscope.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="patterns-kaleidoscope.svg"></object>
</div>
</div>
<div class="section">
<h2>Progress Indicators</h2>
<div class="desc">Rings, bars, calendars, timers, counters</div>
<div class="file-name">progress-indicators.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="progress-indicators.svg"></object>
</div>
</div>
</div>
<div class="section">
<h2>Evidence Wall</h2>
<div class="desc">Tile shapes, mosaic compositions (early/mid/full), empty state</div>
<div class="file-name">evidence-wall.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="evidence-wall.svg"></object>
</div>
</div>
<div class="section">
<h2>Loading & Animation States</h2>
<div class="desc">Spinners, skeleton shimmers, Turn animation, AI thinking, success burst</div>
<div class="file-name">loading-animations.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="loading-animations.svg"></object>
</div>
</div>
<div class="section">
<h2>Device Chrome</h2>
<div class="desc">Status bar, tab bar, nav header, keyboard, device frame, modal scrim, toast</div>
<div class="file-name">device-chrome.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="device-chrome.svg"></object>
</div>
</div>
<div class="section">
<h2>Spectrum Visualizations</h2>
<div class="desc">The River, Your Glass, Turn Impact, Rhythm Detection, Growth Trajectory</div>
<div class="file-name">spectrum-visualizations.svg</div>
<div class="asset-frame">
<object type="image/svg+xml" data="spectrum-visualizations.svg"></object>
</div>
</div>
<div style="text-align: center; padding: 40px 0; color: #475569; font-size: 12px;">
Kalei Asset Library — 12 SVG files, 100+ elements, 1 CSS design system
</div>
</body>
</html>

View File

@@ -0,0 +1,257 @@
<svg viewBox="0 0 700 700" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/><stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="prismaticH" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8B5CF6"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#3B82F6"><animate attributeName="stop-color" values="#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6;#3B82F6" dur="8s" repeatCount="indefinite"/></stop>
</linearGradient>
<filter id="glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
.section-lbl { font-size: 11px; fill: #64748B; font-weight: 600; letter-spacing: 0.08em; }
.val { font-size: 18px; fill: #E2E8F0; font-weight: 700; text-anchor: middle; }
.val-sm { font-size: 8px; fill: #64748B; text-anchor: middle; font-weight: 500; }
</style>
</defs>
<rect width="700" height="700" fill="#050508"/>
<text x="350" y="28" font-size="13" font-weight="700" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">PROGRESS INDICATORS</text>
<text x="350" y="44" font-size="9" fill="#475569" text-anchor="middle">Rings, bars, calendars, timers, counters — all with prismatic styling</text>
<!-- Row 1: Large Ring, Small Ring -->
<text x="24" y="74" class="section-lbl">PROGRESS RINGS</text>
<line x1="24" y1="80" x2="670" y2="80" stroke="#1C2240" stroke-width="0.5"/>
<!-- Large Ring (72% complete) -->
<g transform="translate(140, 160)">
<!-- Background track -->
<circle r="50" fill="none" stroke="#1C2240" stroke-width="6" opacity="0.5"/>
<!-- Progress arc — 72% = ~259 degrees -->
<g filter="url(#glowMd)">
<circle r="50" fill="none" stroke="url(#grAmethyst)" stroke-width="6" stroke-linecap="round"
stroke-dasharray="226 314" stroke-dashoffset="0" transform="rotate(-90)">
<animate attributeName="stroke-dasharray" values="0 314;226 314" dur="1.5s" fill="freeze"/>
</circle>
</g>
<!-- End cap glow -->
<circle cx="0" cy="0" r="3" fill="#A78BFA" opacity="0.6" filter="url(#glowSm)"
transform="rotate(169) translate(50, 0)"/>
<!-- Center text -->
<text x="0" y="4" class="val">72%</text>
<text x="0" y="16" class="val-sm">COMPLETE</text>
<text x="0" y="74" class="lbl">Large Ring (Lens progress)</text>
</g>
<!-- Small Ring (3/6 steps) -->
<g transform="translate(340, 160)">
<circle r="28" fill="none" stroke="#1C2240" stroke-width="4" opacity="0.5"/>
<g filter="url(#glowSm)">
<circle r="28" fill="none" stroke="url(#grEmerald)" stroke-width="4" stroke-linecap="round"
stroke-dasharray="88 176" stroke-dashoffset="0" transform="rotate(-90)"/>
</g>
<text x="0" y="2" font-size="13" fill="#E2E8F0" font-weight="700" text-anchor="middle">3/6</text>
<text x="0" y="14" class="val-sm">STEPS</text>
<text x="0" y="52" class="lbl">Small Ring (step counter)</text>
</g>
<!-- Prismatic Ring (special/celebration) -->
<g transform="translate(540, 160)">
<circle r="40" fill="none" stroke="#1C2240" stroke-width="5" opacity="0.4"/>
<g filter="url(#glowMd)">
<circle r="40" fill="none" stroke="url(#prismaticH)" stroke-width="5" stroke-linecap="round"
stroke-dasharray="251 251" transform="rotate(-90)"/>
</g>
<!-- Orbiting mote -->
<circle r="2" fill="#fff" opacity="0" filter="url(#glowSm)">
<animateMotion dur="3s" repeatCount="indefinite" path="M0,-40 A40,40 0 1 1 -0.1,-40"/>
<animate attributeName="opacity" values="0;0.8;0;0" dur="3s" repeatCount="indefinite"/>
</circle>
<text x="0" y="4" class="val">100</text>
<text x="0" y="16" class="val-sm">COMPLETE</text>
<text x="0" y="64" class="lbl">Prismatic Ring (100% celebration)</text>
</g>
<!-- Row 2: Linear Bar, Step Dots -->
<text x="24" y="274" class="section-lbl">LINEAR INDICATORS</text>
<line x1="24" y1="280" x2="670" y2="280" stroke="#1C2240" stroke-width="0.5"/>
<!-- Linear Progress Bar -->
<g transform="translate(350, 310)">
<!-- Track -->
<rect x="-250" y="-4" width="500" height="8" rx="4" fill="#1C2240" opacity="0.6"/>
<!-- Fill -->
<g filter="url(#glowSm)">
<rect x="-250" y="-4" width="325" height="8" rx="4" fill="url(#grAmethyst)" opacity="0.8">
<animate attributeName="width" values="0;325" dur="1.2s" fill="freeze"/>
</rect>
</g>
<!-- Shimmer sweep -->
<rect x="-250" y="-4" width="40" height="8" rx="4" fill="#fff" opacity="0">
<animate attributeName="opacity" values="0;0.15;0" dur="2s" repeatCount="indefinite"/>
<animate attributeName="x" values="-250;75" dur="2s" repeatCount="indefinite"/>
</rect>
<!-- End glow -->
<circle cx="75" cy="0" r="4" fill="#A78BFA" opacity="0.4" filter="url(#glowSm)"/>
<text x="-250" y="-14" font-size="9" fill="#94A3B8" font-weight="500">Progress</text>
<text x="250" y="-14" font-size="9" fill="#C4B5FD" font-weight="600" text-anchor="end">65%</text>
<text x="0" y="28" class="lbl">Linear bar with shimmer sweep</text>
</g>
<!-- Step Dots (5 steps, 3 complete) -->
<g transform="translate(350, 380)">
<!-- Connecting line -->
<line x1="-120" y1="0" x2="120" y2="0" stroke="#1C2240" stroke-width="1.5"/>
<!-- Filled progress line -->
<line x1="-120" y1="0" x2="0" y2="0" stroke="url(#grEmerald)" stroke-width="1.5" filter="url(#glowSm)"/>
<!-- Step 1: complete -->
<g transform="translate(-120, 0)">
<circle r="8" fill="url(#grEmerald)" opacity="0.9" filter="url(#glowSm)"/>
<path d="M -3,0 L -1,3 L 4,-2" fill="none" stroke="#fff" stroke-width="1.2" stroke-linecap="round"/>
</g>
<!-- Step 2: complete -->
<g transform="translate(-60, 0)">
<circle r="8" fill="url(#grEmerald)" opacity="0.9" filter="url(#glowSm)"/>
<path d="M -3,0 L -1,3 L 4,-2" fill="none" stroke="#fff" stroke-width="1.2" stroke-linecap="round"/>
</g>
<!-- Step 3: complete -->
<g transform="translate(0, 0)">
<circle r="8" fill="url(#grEmerald)" opacity="0.9" filter="url(#glowSm)"/>
<path d="M -3,0 L -1,3 L 4,-2" fill="none" stroke="#fff" stroke-width="1.2" stroke-linecap="round"/>
</g>
<!-- Step 4: current -->
<g transform="translate(60, 0)">
<circle r="8" fill="#121628" stroke="#10B981" stroke-width="1.5"/>
<circle r="3" fill="#10B981" opacity="0.6">
<animate attributeName="opacity" values="0.4;0.8;0.4" dur="2s" repeatCount="indefinite"/>
</circle>
</g>
<!-- Step 5: future -->
<g transform="translate(120, 0)">
<circle r="8" fill="#121628" stroke="#1C2240" stroke-width="1"/>
</g>
<text x="0" y="28" class="lbl">Step dots (Lens 6-step flow)</text>
</g>
<!-- Row 3: Streak Calendar, Ritual Bar -->
<text x="24" y="434" class="section-lbl">STREAK AND RITUAL</text>
<line x1="24" y1="440" x2="670" y2="440" stroke="#1C2240" stroke-width="0.5"/>
<!-- Streak Calendar (7-day week) -->
<g transform="translate(175, 500)">
<!-- Days of week -->
<g font-size="8" fill="#475569" text-anchor="middle" font-weight="500">
<text x="-90" y="-26">M</text><text x="-60" y="-26">T</text><text x="-30" y="-26">W</text>
<text x="0" y="-26">T</text><text x="30" y="-26">F</text><text x="60" y="-26">S</text><text x="90" y="-26">S</text>
</g>
<!-- Day circles -->
<!-- Mon: complete -->
<circle cx="-90" cy="0" r="12" fill="url(#grAmber)" opacity="0.8" filter="url(#glowSm)"/>
<path d="M -93,0 L -91,3 L -86,-2" fill="none" stroke="#fff" stroke-width="1" stroke-linecap="round" transform="translate(0,0)"/>
<!-- Tue: complete -->
<circle cx="-60" cy="0" r="12" fill="url(#grAmber)" opacity="0.8" filter="url(#glowSm)"/>
<path d="M -63,0 L -61,3 L -56,-2" fill="none" stroke="#fff" stroke-width="1" stroke-linecap="round"/>
<!-- Wed: complete -->
<circle cx="-30" cy="0" r="12" fill="url(#grAmber)" opacity="0.8" filter="url(#glowSm)"/>
<path d="M -33,0 L -31,3 L -26,-2" fill="none" stroke="#fff" stroke-width="1" stroke-linecap="round"/>
<!-- Thu: today, in progress -->
<circle cx="0" cy="0" r="12" fill="#121628" stroke="#F59E0B" stroke-width="1.5"/>
<circle cx="0" cy="0" r="4" fill="#F59E0B" opacity="0.5">
<animate attributeName="opacity" values="0.3;0.7;0.3" dur="2s" repeatCount="indefinite"/>
</circle>
<!-- Fri-Sun: future -->
<circle cx="30" cy="0" r="12" fill="#121628" stroke="#1C2240" stroke-width="0.8"/>
<circle cx="60" cy="0" r="12" fill="#121628" stroke="#1C2240" stroke-width="0.8"/>
<circle cx="90" cy="0" r="12" fill="#121628" stroke="#1C2240" stroke-width="0.8"/>
<!-- Streak count -->
<g filter="url(#glowSm)">
<path d="M 0,22 L 3,28 L 0,26 L -3,28 Z" fill="#F59E0B" opacity="0.7"/>
<text x="0" y="42" font-size="10" fill="#FDE68A" text-anchor="middle" font-weight="600">3 day streak</text>
</g>
<text x="0" y="58" class="lbl">Streak Calendar (weekly view)</text>
</g>
<!-- Ritual Progress Bar (3 of 5 steps in a ritual sequence) -->
<g transform="translate(500, 500)">
<!-- Ritual steps as connected fragments -->
<g filter="url(#glowSm)">
<!-- Step 1: done -->
<path d="M -80,0 L -72,-8 L -64,0 L -72,8 Z" fill="url(#grEmerald)" opacity="0.8"/>
<path d="M -72,-8 L -64,0 L -72,0 Z" fill="#fff" opacity="0.12"/>
<line x1="-64" y1="0" x2="-48" y2="0" stroke="#10B981" stroke-width="1" opacity="0.5"/>
<!-- Step 2: done -->
<path d="M -48,0 L -40,-8 L -32,0 L -40,8 Z" fill="url(#grEmerald)" opacity="0.8"/>
<line x1="-32" y1="0" x2="-16" y2="0" stroke="#10B981" stroke-width="1" opacity="0.5"/>
<!-- Step 3: done -->
<path d="M -16,0 L -8,-8 L 0,0 L -8,8 Z" fill="url(#grEmerald)" opacity="0.8"/>
<line x1="0" y1="0" x2="16" y2="0" stroke="#1C2240" stroke-width="1"/>
<!-- Step 4: current -->
<path d="M 16,0 L 24,-8 L 32,0 L 24,8 Z" fill="#121628" stroke="#10B981" stroke-width="1"/>
<circle cx="24" cy="0" r="2" fill="#10B981" opacity="0.5">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="2s" repeatCount="indefinite"/>
</circle>
<line x1="32" y1="0" x2="48" y2="0" stroke="#1C2240" stroke-width="1"/>
<!-- Step 5: future -->
<path d="M 48,0 L 56,-8 L 64,0 L 56,8 Z" fill="none" stroke="#1C2240" stroke-width="0.8"/>
</g>
<text x="-8" y="28" class="lbl">Ritual Bar (fragment-shaped steps)</text>
</g>
<!-- Row 4: Timer, Evidence Counter -->
<text x="24" y="584" class="section-lbl">TIMER AND COUNTERS</text>
<line x1="24" y1="590" x2="670" y2="590" stroke="#1C2240" stroke-width="0.5"/>
<!-- Timer Ring -->
<g transform="translate(175, 640)">
<circle r="30" fill="none" stroke="#1C2240" stroke-width="3" opacity="0.4"/>
<g filter="url(#glowSm)">
<circle r="30" fill="none" stroke="url(#grAmethyst)" stroke-width="3" stroke-linecap="round"
stroke-dasharray="141 188" transform="rotate(-90)">
<animate attributeName="stroke-dasharray" values="188 188;0 188" dur="60s" repeatCount="indefinite"/>
</circle>
</g>
<text x="0" y="2" font-size="14" fill="#E2E8F0" font-weight="600" text-anchor="middle">2:45</text>
<text x="0" y="52" class="lbl">Timer Ring (Rehearsal countdown)</text>
</g>
<!-- Evidence Counter -->
<g transform="translate(450, 640)">
<g filter="url(#glowMd)">
<rect x="-60" y="-24" width="120" height="48" rx="12" fill="#121628" stroke="#1C2240" stroke-width="0.8"/>
<!-- Inner glow -->
<rect x="-58" y="-22" width="116" height="44" rx="10" fill="#3B82F6" opacity="0.04"/>
<!-- Fragment icon -->
<g transform="translate(-32, 0)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#grSapphire)" opacity="0.8" filter="url(#glowSm)"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
</g>
<!-- Count -->
<text x="14" y="4" font-size="16" fill="#E2E8F0" font-weight="700" text-anchor="middle">24</text>
<text x="14" y="16" font-size="7" fill="#64748B" text-anchor="middle" font-weight="500">EVIDENCE</text>
</g>
<text x="0" y="48" class="lbl">Evidence Counter (Evidence Wall)</text>
</g>
<text x="350" y="692" font-size="8" fill="#334155" text-anchor="middle">All indicators use jewel tone gradients, glow filters, and animated transitions from the Kalei design system</text>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,261 @@
<svg viewBox="0 0 700 900" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"><animate attributeName="stop-color" values="#C4B5FD;#DDD6FE;#C4B5FD" dur="4s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<linearGradient id="grIndigo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A5B4FC"/><stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<!-- River flow gradient -->
<linearGradient id="riverGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8B5CF6" stop-opacity="0.6"/>
<stop offset="30%" stop-color="#3B82F6" stop-opacity="0.5"/>
<stop offset="60%" stop-color="#10B981" stop-opacity="0.4"/>
<stop offset="100%" stop-color="#F59E0B" stop-opacity="0.6"/>
</linearGradient>
<linearGradient id="riverGradFill" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8B5CF6" stop-opacity="0.15"/>
<stop offset="30%" stop-color="#3B82F6" stop-opacity="0.1"/>
<stop offset="60%" stop-color="#10B981" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#F59E0B" stop-opacity="0.12"/>
</linearGradient>
<filter id="glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<style>
text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
.lbl { font-size: 9px; fill: #94A3B8; text-anchor: middle; font-weight: 500; }
.section-lbl { font-size: 11px; fill: #64748B; font-weight: 600; letter-spacing: 0.08em; }
.axis-lbl { font-size: 7px; fill: #475569; font-weight: 500; }
.data-lbl { font-size: 8px; fill: #94A3B8; font-weight: 500; text-anchor: middle; }
</style>
</defs>
<rect width="700" height="900" fill="#050508"/>
<text x="350" y="28" font-size="13" font-weight="700" fill="#94A3B8" text-anchor="middle" letter-spacing="0.12em">SPECTRUM VISUALIZATIONS</text>
<text x="350" y="44" font-size="9" fill="#475569" text-anchor="middle">5 analytics views: The River, Your Glass, Turn Impact, Rhythm Detection, Growth Trajectory</text>
<!-- 1. THE RIVER — emotional flow over time -->
<text x="24" y="74" class="section-lbl">THE RIVER</text>
<line x1="24" y1="80" x2="670" y2="80" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 150)">
<rect x="-280" y="-50" width="560" height="100" rx="8" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<!-- X axis labels -->
<g class="axis-lbl" text-anchor="middle">
<text x="-240" y="60">Mon</text><text x="-160" y="60">Tue</text><text x="-80" y="60">Wed</text>
<text x="0" y="60">Thu</text><text x="80" y="60">Fri</text><text x="160" y="60">Sat</text><text x="240" y="60">Sun</text>
</g>
<!-- River band (area chart) -->
<g filter="url(#glowSm)">
<!-- Upper bound -->
<path d="M -260,-20 C -200,-30 -140,5 -80,-10 C -20,-25 40,10 100,-5 C 160,-20 220,0 260,-15"
fill="none" stroke="url(#riverGrad)" stroke-width="1.5" opacity="0.8"/>
<!-- Lower bound -->
<path d="M -260,15 C -200,25 -140,5 -80,20 C -20,30 40,10 100,18 C 160,25 220,8 260,20"
fill="none" stroke="url(#riverGrad)" stroke-width="1" opacity="0.5"/>
<!-- Fill between -->
<path d="M -260,-20 C -200,-30 -140,5 -80,-10 C -20,-25 40,10 100,-5 C 160,-20 220,0 260,-15
L 260,20 C 220,8 160,25 100,18 C 40,10 -20,30 -80,20 C -140,5 -200,25 -260,15 Z"
fill="url(#riverGradFill)" opacity="0.6"/>
<!-- Data point fragments -->
<g>
<path d="M -240,-18 L -237,-15 L -240,-12 L -243,-15 Z" fill="url(#grAmethyst)" opacity="0.7"/>
<path d="M -160,8 L -157,11 L -160,14 L -163,11 Z" fill="url(#grSapphire)" opacity="0.6"/>
<path d="M -80,-8 L -77,-5 L -80,-2 L -83,-5 Z" fill="url(#grEmerald)" opacity="0.7"/>
<path d="M 0,-22 L 3,-19 L 0,-16 L -3,-19 Z" fill="url(#grAmethyst)" opacity="0.8"/>
<path d="M 80,8 L 83,11 L 80,14 L 77,11 Z" fill="url(#grAmber)" opacity="0.6"/>
<path d="M 160,-18 L 163,-15 L 160,-12 L 157,-15 Z" fill="url(#grRose)" opacity="0.7"/>
<path d="M 240,-12 L 243,-9 L 240,-6 L 237,-9 Z" fill="url(#grIndigo)" opacity="0.7"/>
</g>
</g>
<text x="0" y="76" class="lbl">The River — emotional flow over time, prismatic gradient band</text>
</g>
<!-- 2. YOUR GLASS — distortion distribution -->
<text x="24" y="244" class="section-lbl">YOUR GLASS</text>
<line x1="24" y1="250" x2="670" y2="250" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(200, 360)">
<!-- Radar/spider chart -->
<g opacity="0.15">
<!-- Axis lines -->
<line x1="0" y1="0" x2="0" y2="-70" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="61" y2="-35" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="61" y2="35" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="0" y2="70" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="-61" y2="35" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="-61" y2="-35" stroke="#475569" stroke-width="0.5"/>
<!-- Concentric hex guides -->
<path d="M 0,-35 L 30,-17 L 30,17 L 0,35 L -30,17 L -30,-17 Z" fill="none" stroke="#475569" stroke-width="0.3"/>
<path d="M 0,-70 L 61,-35 L 61,35 L 0,70 L -61,35 L -61,-35 Z" fill="none" stroke="#475569" stroke-width="0.3"/>
</g>
<!-- Data shape (user's distortion profile) -->
<g filter="url(#glowMd)">
<path d="M 0,-55 L 40,-20 L 50,25 L 10,50 L -45,30 L -35,-25 Z"
fill="#F59E0B" fill-opacity="0.08" stroke="#F59E0B" stroke-width="1.2" opacity="0.6"/>
<!-- Vertex fragments -->
<path d="M 0,-55 L 3,-52 L 0,-49 L -3,-52 Z" fill="url(#grAmber)" opacity="0.8" filter="url(#glowSm)"/>
<path d="M 40,-20 L 43,-17 L 40,-14 L 37,-17 Z" fill="url(#grAmber)" opacity="0.7" filter="url(#glowSm)"/>
<path d="M 50,25 L 53,28 L 50,31 L 47,28 Z" fill="url(#grAmber)" opacity="0.7" filter="url(#glowSm)"/>
<path d="M 10,50 L 13,53 L 10,56 L 7,53 Z" fill="url(#grAmber)" opacity="0.6" filter="url(#glowSm)"/>
<path d="M -45,30 L -42,33 L -45,36 L -48,33 Z" fill="url(#grAmber)" opacity="0.7" filter="url(#glowSm)"/>
<path d="M -35,-25 L -32,-22 L -35,-19 L -38,-22 Z" fill="url(#grAmber)" opacity="0.7" filter="url(#glowSm)"/>
</g>
<!-- Labels -->
<g class="data-lbl">
<text x="0" y="-78">Catastrophizing</text>
<text x="72" y="-20">Black-White</text>
<text x="72" y="30">Mind Reading</text>
<text x="0" y="68">Fortune Telling</text>
<text x="-72" y="30">Personalization</text>
<text x="-72" y="-20">Should Stmts</text>
</g>
</g>
<!-- 3. TURN IMPACT — before/after bar chart -->
<g transform="translate(530, 360)">
<text x="0" y="-88" class="section-lbl" text-anchor="middle">TURN IMPACT</text>
<!-- Bar pairs (before/after) -->
<g filter="url(#glowSm)">
<!-- Pair 1: Distress -->
<rect x="-50" y="-40" width="14" height="40" rx="2" fill="#EF4444" opacity="0.4"/>
<rect x="-34" y="-60" width="14" height="60" rx="2" fill="url(#grEmerald)" opacity="0.6"/>
<text x="-36" y="14" class="data-lbl">Distress</text>
<!-- Pair 2: Clarity -->
<rect x="0" y="-25" width="14" height="25" rx="2" fill="#EF4444" opacity="0.4"/>
<rect x="16" y="-55" width="14" height="55" rx="2" fill="url(#grEmerald)" opacity="0.6"/>
<text x="14" y="14" class="data-lbl">Clarity</text>
<!-- Pair 3: Hope -->
<rect x="50" y="-20" width="14" height="20" rx="2" fill="#EF4444" opacity="0.4"/>
<rect x="66" y="-50" width="14" height="50" rx="2" fill="url(#grEmerald)" opacity="0.6"/>
<text x="64" y="14" class="data-lbl">Hope</text>
</g>
<!-- Legend -->
<g transform="translate(0, 30)">
<rect x="-30" y="0" width="8" height="8" rx="1" fill="#EF4444" opacity="0.4"/>
<text x="-18" y="7" font-size="7" fill="#94A3B8">Before</text>
<rect x="20" y="0" width="8" height="8" rx="1" fill="#10B981" opacity="0.6"/>
<text x="32" y="7" font-size="7" fill="#94A3B8">After</text>
</g>
<text x="10" y="56" class="lbl">Turn Impact — before/after comparison</text>
</g>
<!-- 4. RHYTHM DETECTION -->
<text x="24" y="470" class="section-lbl">RHYTHM DETECTION</text>
<line x1="24" y1="476" x2="670" y2="476" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 560)">
<rect x="-280" y="-50" width="560" height="100" rx="8" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<!-- Time axis -->
<g class="axis-lbl" text-anchor="middle">
<text x="-220" y="60">6am</text><text x="-110" y="60">9am</text><text x="0" y="60">12pm</text>
<text x="110" y="60">3pm</text><text x="220" y="60">6pm</text>
</g>
<!-- Activity dots (when user engages) -->
<g filter="url(#glowSm)">
<!-- Morning cluster -->
<circle cx="-200" cy="-10" r="4" fill="url(#grAmethyst)" opacity="0.3"/>
<circle cx="-180" cy="-15" r="5" fill="url(#grAmethyst)" opacity="0.5"/>
<circle cx="-160" cy="-20" r="6" fill="url(#grAmethyst)" opacity="0.7">
<animate attributeName="opacity" values="0.5;0.8;0.5" dur="3s" repeatCount="indefinite"/>
</circle>
<circle cx="-140" cy="-12" r="4" fill="url(#grAmethyst)" opacity="0.4"/>
<!-- Midday lull -->
<circle cx="-40" cy="5" r="3" fill="url(#grSapphire)" opacity="0.2"/>
<circle cx="0" cy="0" r="3" fill="url(#grSapphire)" opacity="0.25"/>
<!-- Afternoon peak -->
<circle cx="100" cy="-25" r="7" fill="url(#grEmerald)" opacity="0.7">
<animate attributeName="opacity" values="0.5;0.8;0.5" dur="3.5s" repeatCount="indefinite"/>
</circle>
<circle cx="120" cy="-18" r="5" fill="url(#grEmerald)" opacity="0.5"/>
<circle cx="140" cy="-10" r="4" fill="url(#grEmerald)" opacity="0.4"/>
<!-- Evening -->
<circle cx="200" cy="10" r="4" fill="url(#grAmber)" opacity="0.3"/>
<circle cx="220" cy="5" r="5" fill="url(#grAmber)" opacity="0.4"/>
</g>
<!-- Pattern line (smoothed) -->
<path d="M -260,10 C -200,-10 -140,-20 -80,0 C -20,15 40,10 100,-25 C 160,-10 220,5 260,10"
fill="none" stroke="#8B5CF6" stroke-width="1" opacity="0.3" stroke-dasharray="4 4"/>
<!-- Peak labels -->
<g filter="url(#glowSm)">
<path d="M -160,-32 L -157,-29 L -160,-26 L -163,-29 Z" fill="url(#grAmethyst)" opacity="0.8"/>
<text x="-160" y="-38" font-size="7" fill="#C4B5FD" text-anchor="middle" font-weight="500">Morning Peak</text>
</g>
<g filter="url(#glowSm)">
<path d="M 100,-37 L 103,-34 L 100,-31 L 97,-34 Z" fill="url(#grEmerald)" opacity="0.8"/>
<text x="100" y="-43" font-size="7" fill="#6EE7B7" text-anchor="middle" font-weight="500">Afternoon Peak</text>
</g>
<text x="0" y="76" class="lbl">Rhythm Detection — engagement patterns over time of day, sized by intensity</text>
</g>
<!-- 5. GROWTH TRAJECTORY -->
<text x="24" y="660" class="section-lbl">GROWTH TRAJECTORY</text>
<line x1="24" y1="666" x2="670" y2="666" stroke="#1C2240" stroke-width="0.5"/>
<g transform="translate(350, 770)">
<rect x="-280" y="-70" width="560" height="140" rx="8" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<!-- Axes -->
<line x1="-260" y1="55" x2="260" y2="55" stroke="#1C2240" stroke-width="0.5"/>
<line x1="-260" y1="55" x2="-260" y2="-55" stroke="#1C2240" stroke-width="0.5"/>
<!-- Month labels -->
<g class="axis-lbl" text-anchor="middle">
<text x="-200" y="68">Jan</text><text x="-110" y="68">Feb</text><text x="-20" y="68">Mar</text>
<text x="70" y="68">Apr</text><text x="160" y="68">May</text><text x="240" y="68">Jun</text>
</g>
<!-- Growth line -->
<g filter="url(#glowMd)">
<path d="M -220,40 C -180,35 -140,20 -80,10 C -20,0 40,-15 100,-25 C 160,-35 200,-40 240,-50"
fill="none" stroke="url(#grAmethyst)" stroke-width="2" stroke-linecap="round" opacity="0.8"/>
<!-- Fill under curve -->
<path d="M -220,40 C -180,35 -140,20 -80,10 C -20,0 40,-15 100,-25 C 160,-35 200,-40 240,-50 L 240,55 L -220,55 Z"
fill="#8B5CF6" opacity="0.04"/>
<!-- Data point fragments -->
<path d="M -220,40 L -217,43 L -220,46 L -223,43 Z" fill="url(#grAmethyst)" opacity="0.6" filter="url(#glowSm)"/>
<path d="M -110,18 L -107,21 L -110,24 L -113,21 Z" fill="url(#grAmethyst)" opacity="0.7" filter="url(#glowSm)"/>
<path d="M -20,4 L -17,7 L -20,10 L -23,7 Z" fill="url(#grAmethyst)" opacity="0.7" filter="url(#glowSm)"/>
<path d="M 70,-18 L 73,-15 L 70,-12 L 67,-15 Z" fill="url(#grAmethyst)" opacity="0.8" filter="url(#glowSm)"/>
<path d="M 160,-36 L 163,-33 L 160,-30 L 157,-33 Z" fill="url(#grAmethyst)" opacity="0.8" filter="url(#glowSm)"/>
<path d="M 240,-50 L 243,-47 L 240,-44 L 237,-47 Z" fill="url(#grAmethyst)" opacity="0.9" filter="url(#glowSm)">
<animate attributeName="opacity" values="0.7;1;0.7" dur="2s" repeatCount="indefinite"/>
</path>
</g>
<!-- Milestone markers -->
<g filter="url(#glowSm)">
<circle cx="-20" cy="4" r="8" fill="none" stroke="#10B981" stroke-width="0.8" opacity="0.4"/>
<text x="-20" y="-10" font-size="7" fill="#6EE7B7" text-anchor="middle">10th Turn</text>
</g>
<g filter="url(#glowSm)">
<circle cx="160" cy="-36" r="8" fill="none" stroke="#F59E0B" stroke-width="0.8" opacity="0.4"/>
<text x="160" y="-48" font-size="7" fill="#FDE68A" text-anchor="middle">30-day streak</text>
</g>
<!-- Y-axis label -->
<text x="-272" y="-10" font-size="7" fill="#475569" transform="rotate(-90, -272, -10)" text-anchor="middle">Resilience Score</text>
</g>
<text x="350" y="860" class="lbl">Growth Trajectory — resilience over months with milestone markers</text>
<text x="350" y="890" font-size="8" fill="#334155" text-anchor="middle">All Spectrum visualizations use fragment-shaped data points, jewel tone gradients, and glow effects</text>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Gallery</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.gallery-header {
padding: var(--space-4) var(--space-4) 0;
}
.gallery-title-row {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 14px;
}
.gallery-heading {
font-size: 26px; font-weight: 700; color: var(--pure-light);
font-family: var(--font-display);
}
.search-icon-btn {
width: 40px; height: 40px; border-radius: var(--radius-md);
background: var(--deep-glass); border: 1px solid var(--twilight);
display: flex; align-items: center; justify-content: center;
color: var(--dim-light); text-decoration: none; cursor: pointer;
transition: border-color 0.2s;
}
.search-icon-btn:hover { border-color: var(--sapphire); color: var(--sapphire-light); }
.toggle-row {
display: flex; gap: 0; background: var(--deep-glass);
border: 1px solid var(--twilight); border-radius: var(--radius-lg);
padding: 3px; margin-bottom: 12px;
}
.toggle-btn {
flex: 1; height: 36px; border-radius: var(--radius-md);
display: flex; align-items: center; justify-content: center;
font-size: 14px; font-weight: 600; cursor: pointer;
text-decoration: none; transition: all 0.2s;
color: var(--dim-light);
}
.toggle-btn.active {
background: var(--sapphire); color: var(--pure-light);
box-shadow: var(--glow-sapphire);
}
.sort-chips {
display: flex; gap: 8px; margin-bottom: 14px;
}
.sort-chip {
height: 30px; padding: 0 14px; border-radius: var(--radius-full);
font-size: 12px; font-weight: 600; cursor: pointer;
display: flex; align-items: center; transition: all 0.15s;
border: 1px solid var(--twilight); color: var(--dim-light);
background: transparent;
}
.sort-chip.active {
background: rgba(var(--sapphire-rgb, 59,130,246), 0.15); border-color: var(--sapphire);
color: var(--sapphire-light);
}
.pattern-card:hover { border-color: var(--amethyst); }
@keyframes patternBreathing {
0%, 100% { opacity: 0.75; transform: scale(1); }
50% { opacity: 1; transform: scale(1.04); }
}
.pattern-visual-area svg { animation: patternBreathing 6s ease-in-out infinite; }
.pattern-grid {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;
padding: 0 var(--space-4) var(--space-6);
}
.pattern-card {
background: var(--kalei-navy); border: 1px solid var(--twilight);
border-radius: var(--radius-xl); overflow: hidden; cursor: pointer;
text-decoration: none; transition: border-color 0.2s;
display: block;
}
.pattern-card:hover { border-color: rgba(139,92,246,0.4); }
.pattern-visual-area {
height: 130px; background: var(--void);
display: flex; align-items: center; justify-content: center;
position: relative; overflow: hidden;
}
.pattern-bg-aura {
position: absolute; inset: 0;
pointer-events: none;
}
.pattern-info { padding: 10px 12px 12px; }
.pattern-thought {
font-size: 12px; color: var(--soft-light); line-height: 1.4;
margin-bottom: 6px; display: -webkit-box; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; overflow: hidden;
}
.pattern-meta {
font-size: 11px; color: var(--dim-light); margin-bottom: 6px;
}
.pattern-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.distortion-mini-chip {
background: rgba(245,158,11,0.1); color: var(--amber-light);
border-radius: 4px; padding: 2px 7px; font-size: 10px; font-weight: 500;
}
.screen-content { padding: 0; overflow-y: auto; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content">
<div class="gallery-header">
<div class="gallery-title-row">
<div class="gallery-heading">Gallery</div>
<a class="search-icon-btn" href="34-gallery-search.html">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<circle cx="8" cy="8" r="5.5" stroke="currentColor" stroke-width="1.5"/>
<path d="M12.5 12.5L16 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<!-- Toggle -->
<div class="toggle-row">
<a class="toggle-btn active" href="31-gallery-all.html">Patterns</a>
<a class="toggle-btn" href="32-gallery-keepsakes.html">Keepsakes</a>
</div>
<!-- Sort chips -->
<div class="sort-chips">
<div class="sort-chip active" onclick="setSort(this)">Recent</div>
<div class="sort-chip" onclick="setSort(this)">Most Reframed</div>
<div class="sort-chip" onclick="setSort(this)">This Week</div>
</div>
</div>
<!-- Pattern grid -->
<div class="pattern-grid">
<!-- Card 1 — Turn pattern (amethyst 6-fold), blade from patterns-kaleidoscope.svg -->
<a class="pattern-card" href="33-gallery-detail.html">
<div class="pattern-visual-area">
<div class="pattern-bg-aura" style="background: radial-gradient(circle at 50% 50%, rgba(139,92,246,0.2), transparent 70%);"></div>
<img src="../../assets/kalei-logo.svg" width="44" height="44" alt="Kalei" style="border-radius: 8px;">
</div>
<div class="pattern-info">
<div class="pattern-thought">I completely bombed my presentation today and everyone saw</div>
<div class="pattern-meta">Feb 22 · 3 perspectives</div>
<div class="pattern-chips">
<span class="distortion-mini-chip">Catastrophizing</span>
<span class="distortion-mini-chip">Overgeneralization</span>
</div>
</div>
</a>
<!-- Card 2 — Black-and-White + Labeling (rose/indigo) -->
<a class="pattern-card" href="33-gallery-detail.html">
<div class="pattern-visual-area">
<div class="pattern-bg-aura" style="background: radial-gradient(circle at 50% 50%, rgba(99,102,241,0.2), transparent 70%);"></div>
<img src="../../assets/kalei-logo.svg" width="44" height="44" alt="Kalei" style="border-radius: 8px;">
</div>
<div class="pattern-info">
<div class="pattern-thought">I never stick to anything I start — I'm not disciplined</div>
<div class="pattern-meta">Feb 17 · 3 perspectives</div>
<div class="pattern-chips">
<span class="distortion-mini-chip">Black-and-White</span>
<span class="distortion-mini-chip">Labeling</span>
</div>
</div>
</a>
<!-- Card 3 — Complex pattern (sapphire 6-fold), blade from patterns-kaleidoscope.svg Complex: M 0,0 L 6,-2 L -4,38 L -6,1 Z -->
<a class="pattern-card" href="33-gallery-detail.html">
<div class="pattern-visual-area">
<div class="pattern-bg-aura" style="background: radial-gradient(circle at 50% 50%, rgba(59,130,246,0.2), transparent 70%);"></div>
<img src="../../assets/kalei-logo.svg" width="44" height="44" alt="Kalei" style="border-radius: 8px;">
</div>
<div class="pattern-info">
<div class="pattern-thought">Nobody at work respects my opinion anymore</div>
<div class="pattern-meta">Feb 20 · 3 perspectives</div>
<div class="pattern-chips">
<span class="distortion-mini-chip">Mind Reading</span>
</div>
</div>
</a>
<!-- Card 4 — Turn pattern (amber, 6-fold), blade from patterns-kaleidoscope.svg -->
<a class="pattern-card" href="33-gallery-detail.html">
<div class="pattern-visual-area">
<div class="pattern-bg-aura" style="background: radial-gradient(circle at 50% 50%, rgba(245,158,11,0.2), transparent 70%);"></div>
<img src="../../assets/kalei-logo.svg" width="44" height="44" alt="Kalei" style="border-radius: 8px;">
</div>
<div class="pattern-info">
<div class="pattern-thought">Everything is going to fall apart if I don't fix this today</div>
<div class="pattern-meta">Feb 19 · 3 perspectives</div>
<div class="pattern-chips">
<span class="distortion-mini-chip">Catastrophizing</span>
<span class="distortion-mini-chip">Fortune Telling</span>
</div>
</div>
</a>
<!-- Card 5 — Complex pattern (rose/pink, 6-fold), Complex blade from patterns-kaleidoscope.svg -->
<a class="pattern-card" href="33-gallery-detail.html">
<div class="pattern-visual-area">
<div class="pattern-bg-aura" style="background: radial-gradient(circle at 50% 50%, rgba(236,72,153,0.2), transparent 70%);"></div>
<img src="../../assets/kalei-logo.svg" width="44" height="44" alt="Kalei" style="border-radius: 8px;">
</div>
<div class="pattern-info">
<div class="pattern-thought">It's all my fault the team missed the deadline</div>
<div class="pattern-meta">Feb 18 · 3 perspectives</div>
<div class="pattern-chips">
<span class="distortion-mini-chip">Personalization</span>
</div>
</div>
</a>
<!-- Card 6 — Turn pattern (indigo 6-fold), blade from patterns-kaleidoscope.svg -->
<a class="pattern-card" href="33-gallery-detail.html">
<div class="pattern-visual-area">
<div class="pattern-bg-aura" style="background: radial-gradient(circle at 50% 50%, rgba(99,102,241,0.2), transparent 70%);"></div>
<img src="../../assets/kalei-logo.svg" width="44" height="44" alt="Kalei" style="border-radius: 8px;">
</div>
<div class="pattern-info">
<div class="pattern-thought">I should be further along in my career by now</div>
<div class="pattern-meta">Feb 17 · 3 perspectives</div>
<div class="pattern-chips">
<span class="distortion-mini-chip">Should Statements</span>
</div>
</div>
</a>
</div>
</div>
<!-- Tab Bar -->
<div class="tab-bar">
<a class="tab-item inactive" data-tab="turn" href="../turn/10-turn-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 2L20 12L12 22L4 12Z" fill="currentColor" opacity="0.9"/><path d="M12 2L20 12L12 12Z" fill="white" opacity="0.15"/></svg>
<span class="tab-label">Turn</span>
</a>
<a class="tab-item inactive" data-tab="mirror" href="../mirror/15-mirror-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 3L16 8L18 14L12 19L6 14L8 8Z" stroke="currentColor" fill="none" stroke-width="1.2"/></svg>
<span class="tab-label">Mirror</span>
</a>
<a class="tab-item inactive" data-tab="lens" href="../lens/20-lens-dashboard.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke="currentColor" fill="none" stroke-width="1.2"/><circle cx="12" cy="12" r="4" stroke="currentColor" fill="none" stroke-width="0.8"/></svg>
<span class="tab-label">Lens</span>
</a>
<a class="tab-item active" data-tab="gallery" href="31-gallery-all.html">
<svg width="24" height="24" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/></svg>
<span class="tab-label">Gallery</span>
</a>
<a class="tab-item inactive" data-tab="you" href="../you/35-you-profile.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4" stroke="currentColor" fill="none" stroke-width="1.2"/><path d="M4 20C4 16 8 14 12 14C16 14 20 16 20 20" stroke="currentColor" fill="none" stroke-width="1.2" stroke-linecap="round"/></svg>
<span class="tab-label">You</span>
</a>
</div>
</div>
<script>
function setSort(el) {
document.querySelectorAll('.sort-chip').forEach(c => c.classList.remove('active'));
el.classList.add('active');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,277 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Keepsakes</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.gallery-header { padding: var(--space-4) var(--space-4) 0; }
.gallery-title-row {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 14px;
}
.gallery-heading {
font-size: 26px; font-weight: 700; color: var(--pure-light);
font-family: var(--font-display);
}
.toggle-row {
display: flex; gap: 0; background: var(--deep-glass);
border: 1px solid var(--twilight); border-radius: var(--radius-lg);
padding: 3px; margin-bottom: 16px;
}
.toggle-btn {
flex: 1; height: 36px; border-radius: var(--radius-md);
display: flex; align-items: center; justify-content: center;
font-size: 14px; font-weight: 600; cursor: pointer;
text-decoration: none; transition: all 0.2s;
color: var(--dim-light);
}
.toggle-btn.active {
background: var(--sapphire); color: var(--pure-light);
box-shadow: var(--glow-sapphire);
}
.keepsake-card {
background: var(--kalei-navy); border: 1px solid var(--twilight);
border-radius: var(--radius-xl); padding: var(--space-4);
margin-bottom: var(--space-3); display: flex; gap: var(--space-3);
cursor: pointer; text-decoration: none; transition: border-color 0.2s;
align-items: flex-start;
}
.keepsake-card:hover { border-color: var(--sapphire); }
.keepsake-pattern svg { animation: breathing 6s ease-in-out infinite; }
.keepsake-pattern {
width: 64px; height: 64px; border-radius: var(--radius-md);
background: var(--void); flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
overflow: hidden; position: relative;
}
.keepsake-content { flex: 1; min-width: 0; }
.keepsake-reframe {
font-size: 14px; color: var(--pure-light); line-height: 1.4;
margin-bottom: 6px; font-weight: 500;
}
.keepsake-source {
font-size: 12px; color: var(--dim-light); margin-bottom: 8px;
display: -webkit-box; -webkit-line-clamp: 1;
-webkit-box-orient: vertical; overflow: hidden;
font-style: italic;
}
.keepsake-meta {
display: flex; align-items: center; gap: 8px;
}
.keepsake-date { font-size: 11px; color: var(--dim-light); }
.keepsake-bookmark {
margin-left: auto; color: var(--sapphire-light); flex-shrink: 0;
}
.section-label {
font-size: 11px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--dim-light);
padding: 4px 0 8px;
}
.screen-content { padding: 0 var(--space-4) var(--space-6); overflow-y: auto; }
.empty-keepsake {
text-align: center; padding: 40px var(--space-4);
}
.keepsake-count-chip {
background: rgba(var(--sapphire-rgb, 59,130,246), 0.1); border: 1px solid rgba(var(--sapphire-rgb, 59,130,246), 0.25);
border-radius: var(--radius-full); padding: 3px 12px; font-size: 12px;
color: var(--sapphire-light); font-weight: 500;
}
.search-icon-btn {
width: 40px; height: 40px; border-radius: var(--radius-md);
background: var(--deep-glass); border: 1px solid var(--twilight);
display: flex; align-items: center; justify-content: center;
color: var(--dim-light); text-decoration: none; cursor: pointer;
transition: border-color 0.2s;
}
.search-icon-btn:hover { border-color: var(--sapphire); color: var(--sapphire-light); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="gallery-header">
<div class="gallery-title-row">
<div class="gallery-heading">Gallery</div>
<a class="search-icon-btn" href="34-gallery-search.html">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<circle cx="8" cy="8" r="5.5" stroke="currentColor" stroke-width="1.5"/>
<path d="M12.5 12.5L16 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="toggle-row">
<a class="toggle-btn" href="31-gallery-all.html">Patterns</a>
<a class="toggle-btn active" href="32-gallery-keepsakes.html">Keepsakes</a>
</div>
</div>
<div class="screen-content">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px;">
<div class="section-label">Saved Reframes</div>
<span class="keepsake-count-chip">4 keepsakes</span>
</div>
<!-- Keepsake 1 -->
<a class="keepsake-card" href="33-gallery-detail.html">
<div class="keepsake-pattern">
<div style="position: absolute; inset: 0; background: radial-gradient(circle at 50% 50%, rgba(139,92,246,0.25), transparent 70%);"></div>
<svg width="50" height="50" viewBox="0 0 50 50" style="mix-blend-mode: screen; position: relative; z-index: 1;">
<defs><linearGradient id="gk1" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#C4B5FD" stop-opacity="0.9"/><stop offset="100%" stop-color="#7C3AED" stop-opacity="0.3"/></linearGradient></defs>
<g transform="translate(25,25)">
<g><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#gk1)"/></g>
<g transform="rotate(60)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#gk1)"/></g>
<g transform="rotate(120)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#gk1)"/></g>
<g transform="rotate(180)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#gk1)"/></g>
<g transform="rotate(240)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#gk1)"/></g>
<g transform="rotate(300)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#gk1)"/></g>
<circle r="5" fill="white" opacity="0.1"/>
</g>
</svg>
</div>
<div class="keepsake-content">
<div class="keepsake-reframe">"What if the people in that room saw something you didn't — someone who showed up, spoke up, and kept going when the pressure was highest?"</div>
<div class="keepsake-source">From: "I completely bombed my presentation today and everyone saw"</div>
<div class="keepsake-meta">
<span class="keepsake-date">Feb 22</span>
<span class="label text-dim">· Perspective Shift</span>
<span class="keepsake-bookmark">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M4 2h8a1 1 0 011 1v11l-5-3-5 3V3a1 1 0 011-1z"/>
</svg>
</span>
</div>
</div>
</a>
<!-- Keepsake 2 -->
<a class="keepsake-card" href="33-gallery-detail.html">
<div class="keepsake-pattern">
<div style="position: absolute; inset: 0; background: radial-gradient(circle at 50% 50%, rgba(16,185,129,0.25), transparent 70%);"></div>
<svg width="50" height="50" viewBox="0 0 50 50" style="mix-blend-mode: screen; position: relative; z-index: 1;">
<defs><linearGradient id="gk2" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#6EE7B7" stop-opacity="0.9"/><stop offset="100%" stop-color="#059669" stop-opacity="0.3"/></linearGradient></defs>
<g transform="translate(25,25)">
<g><path d="M0,0 L4,-18 L0,-23 L-4,-18Z" fill="url(#gk2)"/></g>
<g transform="rotate(72)"><path d="M0,0 L4,-18 L0,-23 L-4,-18Z" fill="url(#gk2)"/></g>
<g transform="rotate(144)"><path d="M0,0 L4,-18 L0,-23 L-4,-18Z" fill="url(#gk2)"/></g>
<g transform="rotate(216)"><path d="M0,0 L4,-18 L0,-23 L-4,-18Z" fill="url(#gk2)"/></g>
<g transform="rotate(288)"><path d="M0,0 L4,-18 L0,-23 L-4,-18Z" fill="url(#gk2)"/></g>
<circle r="4" fill="white" opacity="0.1"/>
</g>
</svg>
</div>
<div class="keepsake-content">
<div class="keepsake-reframe">"You came back to this — today, again. That's not someone who never sticks to things. That's exactly what sticking to something looks like from the inside."</div>
<div class="keepsake-source">From: "I never stick to anything I start — I'm not disciplined"</div>
<div class="keepsake-meta">
<span class="keepsake-date">Feb 17</span>
<span class="label text-dim">· Evidence Check</span>
<span class="keepsake-bookmark">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="color: var(--sapphire-light);">
<path d="M4 2h8a1 1 0 011 1v11l-5-3-5 3V3a1 1 0 011-1z"/>
</svg>
</span>
</div>
</div>
</a>
<!-- Keepsake 3 -->
<a class="keepsake-card" href="33-gallery-detail.html">
<div class="keepsake-pattern">
<div style="position: absolute; inset: 0; background: radial-gradient(circle at 50% 50%, rgba(59,130,246,0.25), transparent 70%);"></div>
<svg width="50" height="50" viewBox="0 0 50 50" style="mix-blend-mode: screen; position: relative; z-index: 1;">
<defs><linearGradient id="gk3" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#93C5FD" stop-opacity="0.9"/><stop offset="100%" stop-color="#2563EB" stop-opacity="0.3"/></linearGradient></defs>
<g transform="translate(25,25)">
<g><path d="M0,0 L5,-20 L0,-26 L-5,-20Z" fill="url(#gk3)"/></g>
<g transform="rotate(120)"><path d="M0,0 L5,-20 L0,-26 L-5,-20Z" fill="url(#gk3)"/></g>
<g transform="rotate(240)"><path d="M0,0 L5,-20 L0,-26 L-5,-20Z" fill="url(#gk3)"/></g>
<circle r="4" fill="white" opacity="0.1"/>
</g>
</svg>
</div>
<div class="keepsake-content">
<div class="keepsake-reframe">"Minds are harder to read than we think. What if the silence wasn't indifference — but the kind of stillness that comes right before something lands?"</div>
<div class="keepsake-source">From: "Nobody at work respects my opinion anymore"</div>
<div class="keepsake-meta">
<span class="keepsake-date">Feb 20</span>
<span class="label text-dim">· Perspective Shift</span>
<span class="keepsake-bookmark">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="color: var(--sapphire-light);">
<path d="M4 2h8a1 1 0 011 1v11l-5-3-5 3V3a1 1 0 011-1z"/>
</svg>
</span>
</div>
</div>
</a>
<!-- Keepsake 4 -->
<a class="keepsake-card" href="33-gallery-detail.html">
<div class="keepsake-pattern">
<div style="position: absolute; inset: 0; background: radial-gradient(circle at 50% 50%, rgba(245,158,11,0.25), transparent 70%);"></div>
<svg width="50" height="50" viewBox="0 0 50 50" style="mix-blend-mode: screen; position: relative; z-index: 1;">
<defs><linearGradient id="gk4" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#FDE68A" stop-opacity="0.9"/><stop offset="100%" stop-color="#D97706" stop-opacity="0.3"/></linearGradient></defs>
<g transform="translate(25,25)">
<g><path d="M0,0 L5,-22 L0,-27 L-5,-22Z" fill="url(#gk4)"/></g>
<g transform="rotate(45)"><path d="M0,0 L5,-22 L0,-27 L-5,-22Z" fill="url(#gk4)"/></g>
<g transform="rotate(90)"><path d="M0,0 L5,-22 L0,-27 L-5,-22Z" fill="url(#gk4)"/></g>
<g transform="rotate(135)"><path d="M0,0 L5,-22 L0,-27 L-5,-22Z" fill="url(#gk4)"/></g>
<g transform="rotate(180)"><path d="M0,0 L5,-22 L0,-27 L-5,-22Z" fill="url(#gk4)"/></g>
<g transform="rotate(225)"><path d="M0,0 L5,-22 L0,-27 L-5,-22Z" fill="url(#gk4)"/></g>
<g transform="rotate(270)"><path d="M0,0 L5,-22 L0,-27 L-5,-22Z" fill="url(#gk4)"/></g>
<g transform="rotate(315)"><path d="M0,0 L5,-22 L0,-27 L-5,-22Z" fill="url(#gk4)"/></g>
<circle r="5" fill="white" opacity="0.08"/>
</g>
</svg>
</div>
<div class="keepsake-content">
<div class="keepsake-reframe">"The story that everything falls apart unless you carry it — that's the exhausting part. What's the smallest thing that would make today count? Start there."</div>
<div class="keepsake-source">From: "Everything is going to fall apart if I don't fix this today"</div>
<div class="keepsake-meta">
<span class="keepsake-date">Feb 19</span>
<span class="label text-dim">· Action Step</span>
<span class="keepsake-bookmark">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="color: var(--sapphire-light);">
<path d="M4 2h8a1 1 0 011 1v11l-5-3-5 3V3a1 1 0 011-1z"/>
</svg>
</span>
</div>
</div>
</a>
</div>
<!-- Tab Bar -->
<div class="tab-bar">
<a class="tab-item inactive" data-tab="turn" href="../turn/10-turn-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 2L20 12L12 22L4 12Z" fill="currentColor" opacity="0.9"/><path d="M12 2L20 12L12 12Z" fill="white" opacity="0.15"/></svg>
<span class="tab-label">Turn</span>
</a>
<a class="tab-item inactive" data-tab="mirror" href="../mirror/15-mirror-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 3L16 8L18 14L12 19L6 14L8 8Z" stroke="currentColor" fill="none" stroke-width="1.2"/></svg>
<span class="tab-label">Mirror</span>
</a>
<a class="tab-item inactive" data-tab="lens" href="../lens/20-lens-dashboard.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke="currentColor" fill="none" stroke-width="1.2"/><circle cx="12" cy="12" r="4" stroke="currentColor" fill="none" stroke-width="0.8"/></svg>
<span class="tab-label">Lens</span>
</a>
<a class="tab-item active" data-tab="gallery" href="31-gallery-all.html">
<svg width="24" height="24" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/></svg>
<span class="tab-label">Gallery</span>
</a>
<a class="tab-item inactive" data-tab="you" href="../you/35-you-profile.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4" stroke="currentColor" fill="none" stroke-width="1.2"/><path d="M4 20C4 16 8 14 12 14C16 14 20 16 20 20" stroke="currentColor" fill="none" stroke-width="1.2" stroke-linecap="round"/></svg>
<span class="tab-label">You</span>
</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,267 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Pattern Detail</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.nav-back { color: var(--sapphire-light) !important; transition: opacity 0.2s; }
.nav-action { color: var(--sapphire-light) !important; transition: opacity 0.2s; }
.nav-back:hover, .nav-action:hover { opacity: 0.75; }
.pattern-hero {
background: var(--void); position: relative;
height: 200px; display: flex; align-items: center; justify-content: center;
overflow: hidden;
}
.pattern-hero-aura {
position: absolute; inset: 0;
background: radial-gradient(ellipse at 50% 50%, rgba(139,92,246,0.2), transparent 70%);
pointer-events: none;
}
.pattern-hero-aura-outer {
position: absolute; inset: 0;
background: radial-gradient(ellipse at 50% 50%, rgba(59,130,246,0.08), transparent 70%);
pointer-events: none;
}
.hero-kaleidoscope { animation: breathing 6s ease-in-out infinite; }
.hero-shard-1 { position: absolute; top: 20px; left: 20px; opacity: 0.15; animation: breathing 6s ease-in-out infinite; }
.hero-shard-2 { position: absolute; bottom: 20px; right: 20px; opacity: 0.12; animation: breathing 6s ease-in-out infinite 1s; }
.original-thought-card {
margin: var(--space-4) var(--space-4) var(--space-3);
background: var(--deep-glass); border: 1px solid var(--twilight);
border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4);
}
.original-label {
font-size: 11px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--dim-light); margin-bottom: 6px;
}
.original-text {
font-size: 15px; color: var(--soft-light); line-height: 1.5; font-style: italic;
}
.reframes-section { padding: 0 var(--space-4); margin-bottom: var(--space-4); }
.section-label {
font-size: 11px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--dim-light); margin-bottom: 10px;
}
.distortions-section { padding: 0 var(--space-4); margin-bottom: var(--space-4); }
.distortion-card {
background: var(--kalei-navy); border: 1px solid rgba(245,158,11,0.2);
border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-2); display: flex; gap: var(--space-3); align-items: flex-start;
}
.distortion-icon-wrap {
width: 36px; height: 36px; border-radius: var(--radius-md);
background: rgba(245,158,11,0.1); display: flex; align-items: center;
justify-content: center; flex-shrink: 0;
}
.distortion-info { flex: 1; }
.distortion-name {
font-size: 14px; font-weight: 600; color: var(--amber-light); margin-bottom: 3px;
}
.distortion-explanation {
font-size: 13px; color: var(--dim-light); line-height: 1.4;
}
.actions-section { padding: 0 var(--space-4); margin-bottom: var(--space-4); }
.meta-row {
padding: var(--space-3) var(--space-4);
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.meta-chip {
background: var(--deep-glass); border: 1px solid var(--twilight);
border-radius: var(--radius-full); padding: 4px 12px;
font-size: 12px; color: var(--dim-light);
}
.btn-turn-again {
display: flex; align-items: center; justify-content: center; gap: 8px;
height: 52px; width: 100%;
background: var(--amethyst); color: var(--pure-light);
font-size: 15px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-amethyst); text-decoration: none;
transition: background 0.2s; margin-bottom: var(--space-2);
}
.btn-turn-again:hover { background: var(--amethyst-light); }
.btn-bookmark {
display: flex; align-items: center; justify-content: center; gap: 8px;
height: 44px; width: 100%;
background: var(--deep-glass); color: var(--sapphire-light);
font-size: 14px; font-weight: 600; border-radius: var(--radius-lg);
border: 1px solid rgba(59,130,246,0.3); text-decoration: none;
transition: all 0.2s; cursor: pointer;
}
.btn-bookmark:hover { background: rgba(59,130,246,0.1); }
.screen-content { padding-bottom: 24px; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="31-gallery-all.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">Pattern</span>
<a class="nav-action" href="../modals/61-pattern-share.html" style="text-decoration: none;">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<circle cx="15" cy="4" r="2" stroke="currentColor" stroke-width="1.4"/>
<circle cx="4" cy="10" r="2" stroke="currentColor" stroke-width="1.4"/>
<circle cx="15" cy="16" r="2" stroke="currentColor" stroke-width="1.4"/>
<path d="M6 9l7-4M6 11l7 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="screen-content">
<!-- Large kaleidoscope pattern hero -->
<div class="pattern-hero">
<div class="pattern-hero-aura"></div>
<div class="pattern-hero-aura-outer"></div>
<svg class="hero-shard-1" width="28" height="28" viewBox="0 0 28 28"><path d="M14 2L20 14L14 26L8 14Z" fill="var(--amethyst)"/></svg>
<svg class="hero-shard-2" width="20" height="20" viewBox="0 0 20 20"><path d="M10 2L14 10L10 18L6 10Z" fill="var(--sapphire)"/></svg>
<!-- Hero kaleidoscope — canonical logo -->
<img src="../../assets/kalei-logo.svg" width="100" height="100" alt="Kalei" class="hero-kaleidoscope" style="filter: drop-shadow(0 0 12px rgba(139,92,246,0.3));">
</div>
<!-- Original thought -->
<div class="original-thought-card">
<div class="original-label">Original Thought — Feb 22, 2026</div>
<div class="original-text">"I completely bombed my presentation today and everyone saw how unprepared I was."</div>
</div>
<!-- 3 Reframe perspectives -->
<div class="reframes-section">
<div class="section-label">3 Perspectives</div>
<div class="reframe-block amethyst">
<div class="reframe-label">Perspective Shift</div>
<div class="reframe-text">What if stumbling through this actually revealed something real about you — not incompetence, but the courage to show up when the stakes felt high? Everyone in that room has had a moment like this.</div>
</div>
<div class="reframe-block sapphire">
<div class="reframe-label">Evidence Check</div>
<div class="reframe-text">Think of a presentation or high-pressure moment where you felt underprepared — and survived. What did you do that got you through? That same resilience is what brought you here today, too.</div>
</div>
<div class="reframe-block emerald">
<div class="reframe-label">Action Step</div>
<div class="reframe-text">Write down one thing that went better than expected today, however small. Then name one specific thing you'd adjust next time. That's not failure — that's preparation.</div>
</div>
</div>
<!-- Detected distortions -->
<div class="distortions-section">
<div class="section-label">Detected Patterns</div>
<div class="distortion-card">
<div class="distortion-icon-wrap">
<!-- Extracted from icons-distortions.svg → Catastrophizing (translate 28,60) -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<defs>
<linearGradient id="d33-amberGr1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<filter id="d33-gAmber1" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(12,12)" filter="url(#d33-gAmber1)">
<!-- Main falling shard -->
<path d="M 0,-10 L 5,0 L 0,10 L -5,0 Z" fill="url(#d33-amberGr1)" opacity="0.9"/>
<path d="M 0,-10 L 5,0 L -5,0 Z" fill="#fff" opacity="0.15"/>
<!-- Broken fragments -->
<path d="M -8,6 L -5,10 L -9,12 L -11,8 Z" fill="#F59E0B" opacity="0.5"/>
<path d="M 7,8 L 10,12 L 8,14 L 5,11 Z" fill="#F59E0B" opacity="0.4"/>
<!-- Motion line -->
<line x1="0" y1="11" x2="0" y2="16" stroke="#FCD34D" stroke-width="0.5" opacity="0.4"/>
</g>
</svg>
</div>
<div class="distortion-info">
<div class="distortion-name">Catastrophizing</div>
<div class="distortion-explanation">Magnifying the negatives of a situation while minimizing any positives. One setback becomes a total disaster in your mind.</div>
</div>
</div>
<div class="distortion-card">
<div class="distortion-icon-wrap">
<!-- Extracted from icons-distortions.svg → Overgeneralization (translate 448,180) -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<defs>
<linearGradient id="d33-amberGr2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<filter id="d33-gAmber2" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(12,12)" filter="url(#d33-gAmber2)">
<!-- Source diamond -->
<path d="M -10,0 L -7,-3 L -4,0 L -7,3 Z" fill="url(#d33-amberGr2)" opacity="0.9"/>
<path d="M -10,0 L -7,-3 L -4,0 Z" fill="#fff" opacity="0.15"/>
<!-- Spreading replicated diamonds -->
<path d="M 2,-6 L 4,-8 L 6,-6 L 4,-4 Z" fill="#F59E0B" opacity="0.7"/>
<path d="M 4,0 L 6,-2 L 8,0 L 6,2 Z" fill="#F59E0B" opacity="0.6"/>
<path d="M 2,4 L 4,2 L 6,4 L 4,6 Z" fill="#F59E0B" opacity="0.5"/>
<path d="M 8,-5 L 10,-7 L 12,-5 L 10,-3 Z" fill="#F59E0B" opacity="0.35"/>
<path d="M 9,3 L 11,1 L 13,3 L 11,5 Z" fill="#F59E0B" opacity="0.3"/>
</g>
</svg>
</div>
<div class="distortion-info">
<div class="distortion-name">Overgeneralization</div>
<div class="distortion-explanation">Drawing a broad, absolute conclusion from a single event. "Everyone saw" and "completely bombed" turn one difficult moment into a sweeping verdict about your ability.</div>
</div>
</div>
</div>
<!-- Meta info -->
<div class="meta-row">
<span class="meta-chip">Feb 22, 2026</span>
<span class="meta-chip">Compassionate style</span>
<span class="meta-chip">4 min</span>
<span class="meta-chip">Turn #47</span>
</div>
<!-- Actions -->
<div class="actions-section">
<a class="btn-turn-again" href="../turn/11-turn-input-active.html">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M2 9C2 5.1 5.1 2 9 2s7 3.1 7 7-3.1 7-7 7" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
<path d="M2 9l2-2M2 9l2 2" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Turn again
</a>
<button class="btn-bookmark" onclick="toggleBookmark(this)">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" id="bmIcon">
<path d="M4 2h8a1 1 0 011 1v11l-5-3-5 3V3a1 1 0 011-1z" stroke="currentColor" stroke-width="1.2" fill="none"/>
</svg>
Save to Keepsakes
</button>
</div>
</div>
</div>
<script>
function toggleBookmark(btn) {
const icon = document.getElementById('bmIcon');
const isSaved = btn.textContent.includes('Saved');
if (isSaved) {
icon.innerHTML = '<path d="M4 2h8a1 1 0 011 1v11l-5-3-5 3V3a1 1 0 011-1z" stroke="currentColor" stroke-width="1.2" fill="none"/>';
btn.innerHTML = btn.innerHTML.replace('Saved', 'Save to Keepsakes');
} else {
icon.innerHTML = '<path d="M4 2h8a1 1 0 011 1v11l-5-3-5 3V3a1 1 0 011-1z" fill="currentColor"/>';
btn.innerHTML = btn.innerHTML.replace('Save to Keepsakes', 'Saved');
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,265 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Gallery Search</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.nav-back { color: var(--sapphire-light) !important; }
.search-container {
padding: 0 var(--space-4) var(--space-3);
}
.search-input-wrap {
position: relative; margin-bottom: var(--space-3);
}
.search-input {
width: 100%; height: 48px;
background: var(--deep-glass); border: 1.5px solid var(--sapphire);
border-radius: var(--radius-lg); padding: 0 44px 0 44px;
font-size: 16px; color: var(--pure-light); outline: none;
font-family: var(--font-primary);
box-shadow: 0 0 12px rgba(59,130,246,0.15);
}
.search-input::placeholder { color: var(--faint-light); }
.search-icon {
position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
color: var(--sapphire-light);
}
.clear-btn {
position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
color: var(--dim-light); cursor: pointer; background: none; border: none;
display: flex; align-items: center; justify-content: center;
}
.filter-section { margin-bottom: var(--space-3); }
.filter-label {
font-size: 11px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--dim-light); margin-bottom: 8px;
}
.filter-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.filter-chip {
height: 30px; padding: 0 12px; border-radius: var(--radius-full);
font-size: 12px; font-weight: 500; cursor: pointer;
display: flex; align-items: center; gap: 5px;
border: 1px solid var(--twilight); color: var(--dim-light);
background: transparent; transition: all 0.15s;
}
.filter-chip.active {
background: rgba(var(--sapphire-rgb, 59,130,246), 0.15); border-color: var(--sapphire);
color: var(--sapphire-light);
}
.filter-chip.amber.active {
background: rgba(245,158,11,0.12); border-color: var(--amber);
color: var(--amber-light);
}
.search-input:focus { box-shadow: var(--glow-sapphire); }
.divider { height: 1px; background: var(--twilight); margin: 4px 0 12px; }
.results-count {
font-size: 13px; color: var(--dim-light); padding: 0 var(--space-4);
margin-bottom: 10px;
}
.result-row {
display: flex; align-items: center; gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid rgba(28,34,64,0.5);
cursor: pointer; text-decoration: none; transition: background 0.15s;
}
.result-row:hover { background: var(--deep-glass); }
.result-thumb {
width: 52px; height: 52px; border-radius: var(--radius-md);
background: var(--void); flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
overflow: hidden; position: relative;
}
.result-info { flex: 1; min-width: 0; }
.result-text {
font-size: 14px; color: var(--soft-light); line-height: 1.4;
margin-bottom: 4px;
display: -webkit-box; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; overflow: hidden;
}
.result-meta-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.result-date { font-size: 12px; color: var(--dim-light); }
.result-distortion {
background: rgba(245,158,11,0.1); color: var(--amber-light);
border-radius: 4px; padding: 2px 7px; font-size: 10px; font-weight: 500;
}
.empty-state {
display: flex; flex-direction: column; align-items: center;
padding: 48px var(--space-4); text-align: center;
}
.screen-content { padding: 0; overflow-y: auto; }
.screen-content::-webkit-scrollbar { display: none; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="31-gallery-all.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">Search</span>
<span class="nav-action"></span>
</div>
<div class="screen-content">
<div class="search-container">
<!-- Search input -->
<div class="search-input-wrap">
<div class="search-icon">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<circle cx="8" cy="8" r="5.5" stroke="currentColor" stroke-width="1.5"/>
<path d="M12.5 12.5L16 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<input type="text" class="search-input" placeholder="Search your thoughts..." id="searchInput" value="presentation" autofocus>
<button class="clear-btn" onclick="clearSearch()">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
</svg>
</button>
</div>
<!-- Distortion type filters -->
<div class="filter-section">
<div class="filter-label">Distortion Type</div>
<div class="filter-chips" id="distortionFilters">
<div class="filter-chip amber" onclick="toggleFilter(this)">All</div>
<div class="filter-chip amber active" onclick="toggleFilter(this)">Catastrophizing</div>
<div class="filter-chip amber" onclick="toggleFilter(this)">Black-and-White</div>
<div class="filter-chip amber" onclick="toggleFilter(this)">Mind Reading</div>
<div class="filter-chip amber" onclick="toggleFilter(this)">Personalization</div>
<div class="filter-chip amber" onclick="toggleFilter(this)">Should Statements</div>
<div class="filter-chip amber" onclick="toggleFilter(this)">Overgeneralization</div>
</div>
</div>
<!-- Date filters -->
<div class="filter-section">
<div class="filter-label">Date Range</div>
<div class="filter-chips">
<div class="filter-chip active" onclick="toggleDateFilter(this)">This Week</div>
<div class="filter-chip" onclick="toggleDateFilter(this)">This Month</div>
<div class="filter-chip" onclick="toggleDateFilter(this)">Last 3 Months</div>
<div class="filter-chip" onclick="toggleDateFilter(this)">All Time</div>
</div>
</div>
<div class="divider"></div>
</div>
<!-- Results -->
<div class="results-count" id="resultsCount">3 results for "presentation"</div>
<a class="result-row" href="33-gallery-detail.html">
<div class="result-thumb">
<div style="position: absolute; inset: 0; background: radial-gradient(circle, rgba(139,92,246,0.3), transparent 70%);"></div>
<svg width="36" height="36" viewBox="0 0 36 36" style="mix-blend-mode: screen; position: relative; z-index: 1;">
<defs><linearGradient id="gs1" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#C4B5FD" stop-opacity="0.9"/><stop offset="100%" stop-color="#7C3AED" stop-opacity="0.4"/></linearGradient></defs>
<g transform="translate(18,18)"><g><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs1)"/></g><g transform="rotate(60)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs1)"/></g><g transform="rotate(120)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs1)"/></g><g transform="rotate(180)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs1)"/></g><g transform="rotate(240)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs1)"/></g><g transform="rotate(300)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs1)"/></g></g>
</svg>
</div>
<div class="result-info">
<div class="result-text">I completely bombed my <mark style="background: rgba(59,130,246,0.2); color: var(--sapphire-light); border-radius: 2px; padding: 0 2px;">presentation</mark> today and everyone saw</div>
<div class="result-meta-row">
<span class="result-date">Feb 22</span>
<span class="result-distortion">Catastrophizing</span>
<span class="result-distortion">Overgeneralization</span>
</div>
</div>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="color: var(--faint-light); flex-shrink: 0;">
<path d="M5 3l4 4-4 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<a class="result-row" href="33-gallery-detail.html">
<div class="result-thumb">
<div style="position: absolute; inset: 0; background: radial-gradient(circle, rgba(245,158,11,0.3), transparent 70%);"></div>
<svg width="36" height="36" viewBox="0 0 36 36" style="mix-blend-mode: screen; position: relative; z-index: 1;">
<defs><linearGradient id="gs2" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#FDE68A" stop-opacity="0.9"/><stop offset="100%" stop-color="#D97706" stop-opacity="0.4"/></linearGradient></defs>
<g transform="translate(18,18)"><g><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs2)"/></g><g transform="rotate(45)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs2)"/></g><g transform="rotate(90)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs2)"/></g><g transform="rotate(135)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs2)"/></g><g transform="rotate(180)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs2)"/></g><g transform="rotate(225)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs2)"/></g><g transform="rotate(270)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs2)"/></g><g transform="rotate(315)"><path d="M0,0 L4,-14 L0,-18 L-4,-14Z" fill="url(#gs2)"/></g></g>
</svg>
</div>
<div class="result-info">
<div class="result-text">Everything is going to fall apart if I don't fix this today — no time for another <mark style="background: rgba(59,130,246,0.2); color: var(--sapphire-light); border-radius: 2px; padding: 0 2px;">presentation</mark></div>
<div class="result-meta-row">
<span class="result-date">Feb 19</span>
<span class="result-distortion">Catastrophizing</span>
<span class="result-distortion">Fortune Telling</span>
</div>
</div>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="color: var(--faint-light); flex-shrink: 0;">
<path d="M5 3l4 4-4 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<a class="result-row" href="33-gallery-detail.html">
<div class="result-thumb">
<div style="position: absolute; inset: 0; background: radial-gradient(circle, rgba(99,102,241,0.3), transparent 70%);"></div>
<svg width="36" height="36" viewBox="0 0 36 36" style="mix-blend-mode: screen; position: relative; z-index: 1;">
<defs><linearGradient id="gs3" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#A5B4FC" stop-opacity="0.9"/><stop offset="100%" stop-color="#4338CA" stop-opacity="0.4"/></linearGradient></defs>
<g transform="translate(18,18)"><g><path d="M0,0 L5,-15 L0,-19 L-5,-15Z" fill="url(#gs3)"/></g><g transform="rotate(60)"><path d="M0,0 L5,-15 L0,-19 L-5,-15Z" fill="url(#gs3)"/></g><g transform="rotate(120)"><path d="M0,0 L5,-15 L0,-19 L-5,-15Z" fill="url(#gs3)"/></g><g transform="rotate(180)"><path d="M0,0 L5,-15 L0,-19 L-5,-15Z" fill="url(#gs3)"/></g><g transform="rotate(240)"><path d="M0,0 L5,-15 L0,-19 L-5,-15Z" fill="url(#gs3)"/></g><g transform="rotate(300)"><path d="M0,0 L5,-15 L0,-19 L-5,-15Z" fill="url(#gs3)"/></g></g>
</svg>
</div>
<div class="result-info">
<div class="result-text">I should be further along by now — one bad <mark style="background: rgba(59,130,246,0.2); color: var(--sapphire-light); border-radius: 2px; padding: 0 2px;">presentation</mark> proves it</div>
<div class="result-meta-row">
<span class="result-date">Feb 17</span>
<span class="result-distortion">Should Statements</span>
</div>
</div>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="color: var(--faint-light); flex-shrink: 0;">
<path d="M5 3l4 4-4 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<!-- Suggestion: No results state when query is empty -->
<div style="padding: 20px var(--space-4); text-align: center; display: none;" id="emptyState">
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="margin: 0 auto 12px; display: block; opacity: 0.3;">
<circle cx="22" cy="22" r="14" stroke="var(--dim-light)" stroke-width="2"/>
<path d="M33 33l9 9" stroke="var(--dim-light)" stroke-width="2" stroke-linecap="round"/>
</svg>
<div class="subheading text-dim">No results found</div>
<div class="body-sm text-faint" style="margin-top: var(--space-1);">Try different keywords or remove filters</div>
</div>
</div>
</div>
<script>
function clearSearch() {
const input = document.getElementById('searchInput');
input.value = '';
document.getElementById('resultsCount').textContent = 'Type to search your gallery';
input.focus();
}
function toggleFilter(el) {
el.classList.toggle('active');
}
function toggleDateFilter(el) {
document.querySelectorAll('.filter-chip:not(.amber)').forEach(c => c.classList.remove('active'));
el.classList.add('active');
}
document.getElementById('searchInput').addEventListener('input', function() {
const q = this.value.trim();
const count = document.getElementById('resultsCount');
if (q) {
count.textContent = `3 results for "${q}"`;
} else {
count.textContent = 'Type to search your gallery';
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,354 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Guide Check-In</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.goal-context-header {
background: var(--kalei-navy);
border-bottom: 1px solid var(--twilight);
padding: var(--space-3) var(--space-4);
display: flex;
align-items: center;
gap: var(--space-3);
flex-shrink: 0;
}
.goal-context-ring {
width: 44px;
height: 44px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.goal-context-ring svg { position: absolute; transform: rotate(-90deg); }
.goal-context-pct {
font-size: 11px;
font-weight: 700;
color: var(--emerald-light);
position: relative;
z-index: 1;
}
.goal-context-info { flex: 1; min-width: 0; }
.goal-context-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--emerald);
margin-bottom: 2px;
}
.goal-context-title {
font-size: 14px;
font-weight: 600;
color: var(--pure-light);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-frame {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
.chat-area {
flex: 1;
overflow-y: auto;
padding: var(--space-4) var(--space-4) var(--space-2);
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.chat-area::-webkit-scrollbar { display: none; }
/* Guide bubble — prismatic border card */
.guide-bubble {
background: var(--kalei-navy);
border-radius: 16px 16px 16px 4px;
padding: var(--space-3) var(--space-4);
position: relative;
max-width: 85%;
align-self: flex-start;
}
.guide-bubble::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px 17px 17px 5px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
z-index: -1;
}
.guide-bubble-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-2);
}
.guide-icon-sm {
font-size: 12px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700;
line-height: 1;
}
.guide-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--amethyst-light);
}
.guide-bubble p {
font-size: 14px;
line-height: 1.55;
color: var(--soft-light);
margin: 0;
}
.guide-bubble .stat-highlight {
color: var(--emerald-light);
font-weight: 600;
}
/* User bubble */
.user-bubble {
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: 16px 16px 4px 16px;
padding: var(--space-3) var(--space-4);
max-width: 80%;
align-self: flex-end;
font-size: 14px;
line-height: 1.55;
color: var(--soft-light);
}
.proof-point {
display: flex;
align-items: center;
gap: var(--space-2);
margin-top: var(--space-2);
padding: var(--space-2) var(--space-3);
background: rgba(16,185,129,0.06);
border-radius: var(--radius-md);
border-left: 2px solid var(--emerald);
}
.proof-point-text {
font-size: 12px;
color: var(--emerald-light);
font-weight: 500;
}
.if-then-block {
background: rgba(139,92,246,0.06);
border-radius: var(--radius-md);
border: 1px solid rgba(139,92,246,0.2);
padding: var(--space-3);
margin-top: var(--space-2);
}
.if-then-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--amethyst-light);
margin-bottom: var(--space-1);
}
.if-then-text {
font-size: 13px;
color: var(--soft-light);
line-height: 1.5;
}
.input-accessory {
height: 64px;
background: var(--kalei-navy);
border-top: 1px solid var(--twilight);
display: flex;
align-items: center;
gap: var(--space-2);
padding: 0 var(--space-3);
flex-shrink: 0;
}
.chat-input {
flex: 1;
height: 40px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-full);
padding: 0 var(--space-4);
font-family: var(--font-primary);
font-size: 15px;
color: var(--pure-light);
outline: none;
transition: border-color 0.2s ease-out;
}
.chat-input::placeholder { color: var(--faint-light); }
.chat-input:focus { border-color: var(--amethyst); }
.send-btn {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, var(--amethyst), var(--emerald));
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
flex-shrink: 0;
box-shadow: 0 0 12px rgba(139,92,246,0.3);
transition: opacity 0.2s ease-out;
}
.send-btn:hover { opacity: 0.85; }
.past-checkins-link {
text-align: center;
padding: var(--space-2) 0;
font-size: 12px;
color: var(--amethyst-light);
text-decoration: none;
flex-shrink: 0;
background: var(--kalei-navy);
border-top: 1px solid var(--twilight);
}
@keyframes breathing {
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.05); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="../lens/27-lens-goal-detail.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Weekly Check-In</span>
<span class="nav-action"></span>
</div>
<!-- Goal context banner -->
<div class="goal-context-header">
<div class="goal-context-ring">
<svg width="44" height="44" viewBox="0 0 44 44">
<defs>
<linearGradient id="ci-grEm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
</defs>
<circle cx="22" cy="22" r="18" fill="none" stroke="var(--twilight)" stroke-width="2.5"/>
<!-- 65% of circumference: 2πr ≈ 113.1, 65% = 73.5, remainder = 39.6 -->
<circle cx="22" cy="22" r="18" fill="none" stroke="url(#ci-grEm)" stroke-width="2.5"
stroke-dasharray="73.5 39.6" stroke-linecap="round"/>
</svg>
<span class="goal-context-pct">65%</span>
</div>
<div class="goal-context-info">
<div class="goal-context-label">Active Goal</div>
<div class="goal-context-title">Present with confidence</div>
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: var(--faint-light); flex-shrink: 0;">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="chat-frame">
<div class="chat-area">
<!-- Guide opens with specific data reference -->
<div class="guide-bubble" style="animation: fadeIn 0.4s ease-out both;">
<div class="guide-bubble-header">
<span class="guide-icon-sm"></span>
<span class="guide-label">The Guide</span>
</div>
<p>I noticed something this week, Alex. You've had <span class="stat-highlight">3 Turns about presentations</span> in the last 7 days — Wednesday's "I completely bombed my presentation" was the most recent. How did the rest of that day actually go?</p>
</div>
<!-- User response -->
<div class="user-bubble" style="animation: fadeIn 0.4s ease-out 0.15s both; opacity: 0;">
Honestly, not as bad as I expected. My manager said the data was solid. I just froze at the start and never recovered mentally.
</div>
<!-- Guide follow-up — names the pattern specifically -->
<div class="guide-bubble" style="animation: fadeIn 0.4s ease-out 0.3s both; opacity: 0;">
<div class="guide-bubble-header">
<span class="guide-icon-sm"></span>
<span class="guide-label">The Guide</span>
</div>
<p>That gap — between how you experience it and how it lands — keeps showing up. Your mind calls it a bomb; your manager calls it solid. That's Catastrophizing doing its thing.</p>
<div class="proof-point">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M6 1L10 6L6 11L2 6Z" fill="var(--emerald)" opacity="0.9"/>
</svg>
<span class="proof-point-text">Catastrophizing: your most common fragment (7 of 47 Turns)</span>
</div>
</div>
<!-- Guide asks for real-world practice check -->
<div class="guide-bubble" style="animation: fadeIn 0.4s ease-out 0.45s both; opacity: 0;">
<div class="guide-bubble-header">
<span class="guide-icon-sm"></span>
<span class="guide-label">The Guide</span>
</div>
<p>Since you started "Present with confidence" on Feb 5, have there been any moments outside the app — even tiny ones — where you held your ground in a room?</p>
</div>
<!-- User response -->
<div class="user-bubble" style="animation: fadeIn 0.4s ease-out 0.6s both; opacity: 0;">
Yeah, actually. I pushed back on a timeline in a standup last week. Normally I'd just say nothing.
</div>
<!-- Guide closes with celebration and if-then -->
<div class="guide-bubble" style="animation: fadeIn 0.4s ease-out 0.75s both; opacity: 0;">
<div class="guide-bubble-header">
<span class="guide-icon-sm"></span>
<span class="guide-label">The Guide</span>
</div>
<p>That's exactly the kind of evidence this goal is built on. Pushing back on a timeline is <span class="stat-highlight">presenting with confidence</span> — just without the slides. Want to save that as evidence?</p>
<div class="if-then-block">
<div class="if-then-label">This week's focus</div>
<div class="if-then-text">If I notice I'm freezing at the start of a presentation, <strong style="color: var(--amethyst-light);">then</strong> I'll say one sentence out loud — anything — to break the silence.</div>
</div>
</div>
</div>
<a class="past-checkins-link" href="66-guide-checkin-summary.html">View check-in summary →</a>
<div class="input-accessory">
<input class="chat-input" type="text" placeholder="Reply to the Guide...">
<button class="send-btn">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M13 8L3 3.5L5 8L3 12.5L13 8Z" fill="white"/>
</svg>
</button>
</div>
</div>
</div>
<script>
// Stagger chat bubbles on load
document.querySelectorAll('.guide-bubble, .user-bubble').forEach((el, i) => {
if (!el.style.animationDelay) return;
el.style.opacity = '0';
});
</script>
</body>
</html>

View File

@@ -0,0 +1,252 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Check-In Summary</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
@keyframes breathing {
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.05); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.summary-card {
background: var(--kalei-navy);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
position: relative;
animation: fadeIn 0.35s ease-out both;
}
.summary-card::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
z-index: -1;
}
.card-section-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--dim-light);
margin-bottom: var(--space-2);
}
.card-section-title {
font-size: 16px;
font-weight: 600;
color: var(--pure-light);
margin-bottom: 4px;
}
.card-section-meta {
font-size: 13px;
color: var(--dim-light);
margin-bottom: var(--space-4);
}
.divider {
height: 1px;
background: var(--twilight);
margin: var(--space-3) 0;
}
/* Plan adjustment */
.plan-adjust-block {
margin-bottom: var(--space-3);
}
.plan-adjust-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--dim-light);
margin-bottom: var(--space-2);
}
.plan-new {
font-size: 13px;
color: var(--soft-light);
padding: var(--space-2) var(--space-3);
background: rgba(139,92,246,0.08);
border: 1px solid rgba(139,92,246,0.2);
border-radius: var(--radius-md);
line-height: 1.5;
}
.plan-new strong {
color: var(--amethyst-light);
}
/* Evidence proof points */
.proof-list {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.proof-item {
display: flex;
align-items: flex-start;
gap: var(--space-3);
}
.proof-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 4px;
}
.proof-dot.emerald { background: var(--emerald); box-shadow: 0 0 6px rgba(16,185,129,0.4); }
.proof-dot.amber { background: var(--amber); box-shadow: 0 0 6px rgba(245,158,11,0.4); }
.proof-dot.sapphire{ background: var(--sapphire);box-shadow: 0 0 6px rgba(59,130,246,0.4); }
.proof-item-text {
font-size: 13px;
color: var(--soft-light);
line-height: 1.5;
}
/* Next check-in */
.next-checkin-card {
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-3);
animation: fadeIn 0.35s ease-out 0.2s both;
opacity: 0;
}
.next-checkin-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-md);
background: rgba(139,92,246,0.1);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.next-checkin-info { flex: 1; }
.next-checkin-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--amethyst);
margin-bottom: 2px;
}
.next-checkin-date {
font-size: 14px;
font-weight: 600;
color: var(--pure-light);
}
.history-footer {
text-align: center;
padding: var(--space-3) 0 var(--space-6);
font-size: 12px;
color: var(--faint-light);
}
.done-btn {
display: flex;
align-items: center;
justify-content: center;
height: 52px;
background: var(--amethyst);
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
text-decoration: none;
box-shadow: 0 0 20px rgba(139,92,246,0.25);
margin-bottom: var(--space-3);
transition: background 0.2s ease-out;
animation: fadeIn 0.35s ease-out 0.3s both;
opacity: 0;
}
.done-btn:hover { background: var(--amethyst-light); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="65-guide-checkin-conversation.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Check-In Summary</span>
<span class="nav-action"></span>
</div>
<div class="screen-content" style="padding-top: var(--space-4); padding-bottom: var(--space-6);">
<!-- Main summary card with prismatic border -->
<div class="summary-card">
<div class="card-section-label">What was reviewed</div>
<div class="card-section-title">Present with confidence</div>
<div class="card-section-meta">Week of Feb 1622, 2026 · Weekly check-in · 65% progress</div>
<div class="divider"></div>
<!-- This week's focus / if-then -->
<div class="plan-adjust-block">
<div class="plan-adjust-label">This week's focus</div>
<div class="plan-new">If I notice I'm freezing at the start of a presentation, <strong>then</strong> I'll say one sentence out loud — anything — to break the silence.</div>
</div>
<div class="divider"></div>
<!-- Evidence proof points -->
<div class="card-section-label" style="margin-bottom: var(--space-3);">Evidence highlighted</div>
<div class="proof-list">
<div class="proof-item">
<div class="proof-dot emerald"></div>
<span class="proof-item-text">Pushed back on a timeline in standup — held ground without a deck</span>
</div>
<div class="proof-item">
<div class="proof-dot amber"></div>
<span class="proof-item-text">Manager called Wednesday's presentation "solid" — the freeze was internal, not visible</span>
</div>
<div class="proof-item">
<div class="proof-dot sapphire"></div>
<span class="proof-item-text">3 Turns this week about presentations — you're naming the pattern, not avoiding it</span>
</div>
</div>
</div>
<!-- Next check-in -->
<div class="next-checkin-card">
<div class="next-checkin-icon">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<rect x="3" y="4" width="14" height="13" rx="2" stroke="var(--amethyst)" stroke-width="1.2"/>
<path d="M7 2v3M13 2v3" stroke="var(--amethyst)" stroke-width="1.2" stroke-linecap="round"/>
<path d="M3 8h14" stroke="var(--amethyst)" stroke-width="1.2"/>
<rect x="7" y="11" width="2" height="2" rx="0.5" fill="var(--amethyst)"/>
</svg>
</div>
<div class="next-checkin-info">
<div class="next-checkin-label">Next check-in</div>
<div class="next-checkin-date">Sunday, Mar 1, 2026</div>
</div>
</div>
<a class="done-btn" href="../lens/27-lens-goal-detail.html">Back to goal</a>
<div class="history-footer">Saved to your coaching history</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,338 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Guide Discovery Bridge</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-aura {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
width: 280px;
height: 280px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.15) 0%, transparent 70%);
filter: blur(50px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
@keyframes breathing {
0%, 100% { opacity: 0.6; transform: scale(1) translate(-50%, -50%); }
50% { opacity: 1; transform: scale(1.05) translate(-48%, -52%); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-16px); }
to { opacity: 1; transform: translateY(0); }
}
/* Bridge card */
.bridge-card {
background: var(--kalei-navy);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-4);
position: relative;
animation: slideDown 0.4s ease-out both;
z-index: 2;
}
.bridge-card::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
z-index: -1;
}
.bridge-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-3);
}
.bridge-card-title-row {
display: flex;
align-items: center;
gap: var(--space-2);
}
.bridge-guide-icon {
width: 28px;
height: 28px;
border-radius: var(--radius-sm);
background: rgba(139,92,246,0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
line-height: 1;
}
.bridge-dismiss {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
color: var(--dim-light);
transition: background 0.2s ease-out;
flex-shrink: 0;
}
.bridge-dismiss:hover { background: var(--twilight); }
.bridge-card-heading {
font-size: 15px;
font-weight: 600;
color: var(--pure-light);
margin-left: 36px;
margin-bottom: var(--space-2);
}
.bridge-card-body {
font-size: 14px;
color: var(--soft-light);
line-height: 1.6;
margin-bottom: var(--space-4);
}
.bridge-card-body strong {
color: var(--amber-light);
font-weight: 600;
}
.bridge-actions {
display: flex;
gap: var(--space-2);
}
.bridge-btn-primary {
flex: 1;
height: 44px;
background: var(--amethyst);
color: var(--pure-light);
font-size: 14px;
font-weight: 600;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 16px rgba(139,92,246,0.25);
transition: background 0.2s ease-out;
}
.bridge-btn-primary:hover { background: var(--amethyst-light); }
.bridge-btn-ghost {
flex: 1;
height: 44px;
background: transparent;
color: var(--dim-light);
font-size: 14px;
font-weight: 500;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: 1px solid var(--twilight);
transition: color 0.2s ease-out, border-color 0.2s ease-out;
}
.bridge-btn-ghost:hover { color: var(--soft-light); border-color: rgba(255,255,255,0.15); }
/* Turn Home content below the bridge (dimmed) */
.home-greeting {
padding: var(--space-4) 0 var(--space-3);
position: relative;
z-index: 1;
}
.textarea-wrapper {
position: relative;
z-index: 1;
margin-bottom: var(--space-4);
}
.turn-card {
display: flex;
align-items: center;
gap: var(--space-3);
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-2);
cursor: pointer;
text-decoration: none;
transition: background 0.2s ease-out, border-color 0.2s ease-out;
opacity: 0.65;
}
.turn-card-thought {
font-size: 14px;
color: var(--soft-light);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 3px;
}
.turn-card-date {
font-size: 11px;
color: var(--faint-light);
}
.mini-kaleido-wrap {
width: 44px;
height: 44px;
border-radius: var(--radius-sm);
overflow: hidden;
flex-shrink: 0;
}
.section-header-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-3);
}
.section-title-label {
font-size: 11px;
font-weight: 600;
color: var(--dim-light);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.turn-btn-wrapper {
position: sticky;
bottom: calc(var(--tab-bar-height) + 12px);
z-index: 5;
padding: var(--space-2) 0 0;
}
.content-pad { padding-bottom: 80px; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content content-pad">
<div class="screen-aura"></div>
<!-- Discovery bridge card at top -->
<div class="bridge-card" style="margin-top: var(--space-4);">
<div class="bridge-card-header">
<div class="bridge-card-title-row">
<div class="bridge-guide-icon"></div>
</div>
<button class="bridge-dismiss" id="dismissBtn" aria-label="Dismiss">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M2 2L10 10M10 2L2 10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="bridge-card-heading">Work stress keeps coming up</div>
<div class="bridge-card-body">
I noticed work appeared in <strong>4 Mirror sessions</strong> and <strong>2 Turns</strong> this week — mostly around pressure to perform and feeling unseen. That energy could go somewhere. Want to shape it into a goal?
</div>
<div class="bridge-actions">
<a class="bridge-btn-primary" href="../lens/20-lens-dashboard.html">Open Lens</a>
<a class="bridge-btn-ghost" href="../turn/10-turn-home.html">Just noticing</a>
</div>
</div>
<!-- Turn Home content (dimmed, in background) -->
<div class="home-greeting">
<div class="heading text-pure">Good morning, Alex</div>
<div class="body mt-1" style="color: var(--dim-light);">What's weighing on you?</div>
</div>
<div class="textarea-wrapper">
<textarea class="textarea-field" style="min-height:100px; font-size:16px;" placeholder="Something happened today..."></textarea>
</div>
<div class="section-header-row">
<span class="section-title-label">Recent Turns</span>
</div>
<!-- Dimmed turn cards — canonical data, correct dates -->
<a class="turn-card" href="../turn/13-turn-results.html">
<div class="mini-kaleido-wrap">
<img src="../../assets/kalei-logo.svg" width="48" height="48" alt="Kalei">
</div>
<div style="flex:1; overflow:hidden;">
<div class="turn-card-thought">"I completely bombed my presentation today and everyone saw"</div>
<div class="turn-card-date">Today · Feb 22</div>
</div>
</a>
<a class="turn-card" href="../turn/13-turn-results.html">
<div class="mini-kaleido-wrap">
<img src="../../assets/kalei-logo.svg" width="48" height="48" alt="Kalei">
</div>
<div style="flex:1; overflow:hidden;">
<div class="turn-card-thought">"Nobody at work actually cares about my ideas"</div>
<div class="turn-card-date">Yesterday · Feb 21</div>
</div>
</a>
<div class="turn-btn-wrapper">
<a href="../turn/11-turn-input-active.html" class="btn btn-primary" style="text-decoration:none;">
<svg width="18" height="18" viewBox="-12 -12 24 24" fill="none">
<defs>
<linearGradient id="db-btnF" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
</defs>
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#db-btnF)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
</svg>
Turn the kaleidoscope
</a>
</div>
</div>
<div class="tab-bar">
<a class="tab-item active" data-tab="turn" href="../turn/10-turn-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 2L20 12L12 22L4 12Z" fill="currentColor" opacity="0.9"/><path d="M12 2L20 12L12 12Z" fill="white" opacity="0.15"/></svg>
<span class="tab-label">Turn</span>
</a>
<a class="tab-item inactive" data-tab="mirror" href="../mirror/15-mirror-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 3L16 8L18 14L12 19L6 14L8 8Z" stroke="currentColor" fill="none" stroke-width="1.2"/></svg>
<span class="tab-label">Mirror</span>
</a>
<a class="tab-item inactive" data-tab="lens" href="../lens/20-lens-dashboard.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke="currentColor" fill="none" stroke-width="1.2"/><circle cx="12" cy="12" r="4" stroke="currentColor" fill="none" stroke-width="0.8"/></svg>
<span class="tab-label">Lens</span>
</a>
<a class="tab-item inactive" data-tab="gallery" href="../gallery/31-gallery-all.html">
<svg width="24" height="24" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/></svg>
<span class="tab-label">Gallery</span>
</a>
<a class="tab-item inactive" data-tab="you" href="../you/35-you-profile.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4" stroke="currentColor" fill="none" stroke-width="1.2"/><path d="M4 20C4 16 8 14 12 14C16 14 20 16 20 20" stroke="currentColor" fill="none" stroke-width="1.2" stroke-linecap="round"/></svg>
<span class="tab-label">You</span>
</a>
</div>
</div>
<script>
document.getElementById('dismissBtn').addEventListener('click', function() {
const card = this.closest('.bridge-card');
card.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out, max-height 0.4s ease-out, margin 0.4s ease-out, padding 0.4s ease-out';
card.style.opacity = '0';
card.style.transform = 'translateY(-8px)';
card.style.maxHeight = '0';
card.style.overflow = 'hidden';
card.style.marginBottom = '0';
card.style.padding = '0';
document.querySelectorAll('.turn-card').forEach(c => c.style.opacity = '1');
});
</script>
</body>
</html>

View File

@@ -0,0 +1,298 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Guide Reinforcement Bridge</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-aura {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
width: 280px;
height: 280px;
border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.12) 0%, transparent 70%);
filter: blur(50px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
@keyframes breathing {
0%, 100% { opacity: 0.6; transform: scale(1) translate(-50%, -50%); }
50% { opacity: 1; transform: scale(1.05) translate(-48%, -52%); }
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-16px); }
to { opacity: 1; transform: translateY(0); }
}
.bridge-card {
background: var(--kalei-navy);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-4);
position: relative;
animation: slideDown 0.4s ease-out both;
z-index: 2;
}
.bridge-card::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
z-index: -1;
}
.bridge-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-3);
}
.bridge-guide-icon {
width: 28px;
height: 28px;
border-radius: var(--radius-sm);
background: rgba(139,92,246,0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.bridge-dismiss {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
color: var(--dim-light);
transition: background 0.2s ease-out;
}
.bridge-dismiss:hover { background: var(--twilight); }
.bridge-card-heading {
font-size: 15px;
font-weight: 600;
color: var(--pure-light);
margin-bottom: var(--space-2);
}
.bridge-card-body {
font-size: 14px;
color: var(--soft-light);
line-height: 1.6;
margin-bottom: var(--space-4);
}
.bridge-card-body strong {
color: var(--amethyst-light);
font-weight: 600;
}
.bridge-card-body em {
color: var(--emerald-light);
font-style: italic;
}
.bridge-actions {
display: flex;
gap: var(--space-2);
}
.bridge-btn-primary {
flex: 1;
height: 44px;
background: var(--emerald);
color: var(--pure-light);
font-size: 14px;
font-weight: 600;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 16px rgba(16,185,129,0.25);
transition: background 0.2s ease-out;
}
.bridge-btn-primary:hover { background: var(--emerald-light); }
.bridge-btn-ghost {
flex: 1;
height: 44px;
background: transparent;
color: var(--dim-light);
font-size: 14px;
font-weight: 500;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: 1px solid var(--twilight);
transition: color 0.2s ease-out, border-color 0.2s ease-out;
}
.bridge-btn-ghost:hover { color: var(--soft-light); border-color: rgba(255,255,255,0.15); }
/* Lens dashboard elements (background, dimmed) */
.lens-heading { color: var(--emerald-light); margin-bottom: 4px; }
.goal-card {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
display: flex;
gap: var(--space-3);
align-items: center;
text-decoration: none;
opacity: 0.6;
}
.goal-ring-wrap {
flex-shrink: 0;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
}
.goal-ring-wrap svg { transform: rotate(-90deg); position: absolute; }
.goal-ring-pct { font-size: 14px; font-weight: 700; color: var(--pure-light); position: relative; z-index: 1; }
.goal-info { flex: 1; min-width: 0; }
.goal-title { font-size: 15px; font-weight: 600; color: var(--pure-light); margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.goal-meta { font-size: 12px; color: var(--dim-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content" style="padding-top: var(--space-4); padding-bottom: 100px;">
<div class="screen-aura"></div>
<div style="position: relative; z-index: 1;">
<!-- Reinforcement bridge card at top -->
<div class="bridge-card">
<div class="bridge-card-header">
<div class="bridge-guide-icon"></div>
<button class="bridge-dismiss" id="dismissBtn" aria-label="Dismiss">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M2 2L10 10M10 2L2 10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="bridge-card-heading">Your Turn connects to something you're building</div>
<div class="bridge-card-body">
Your Tuesday reframe — "I pushed back and held my ground" — is exactly the kind of evidence your goal <em>"Present with confidence"</em> is built on. You're <strong>65% there</strong>, and this week added real proof.
</div>
<div class="bridge-actions">
<a class="bridge-btn-primary" href="../lens/27-lens-goal-detail.html">See the progress</a>
<a class="bridge-btn-ghost" href="../lens/20-lens-dashboard.html">Not now</a>
</div>
</div>
<!-- Lens dashboard content (dimmed background) -->
<div class="heading lens-heading">Your Lens</div>
<div class="body-sm text-dim" style="margin-bottom: var(--space-4);">3 active goals · 47 evidence tiles</div>
<!-- Goal 1: Present with confidence (emerald, 65%) -->
<a class="goal-card" href="../lens/27-lens-goal-detail.html">
<div class="goal-ring-wrap">
<svg width="64" height="64" viewBox="0 0 64 64">
<defs>
<linearGradient id="rb-grEm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
</defs>
<circle cx="32" cy="32" r="27" fill="none" stroke="var(--twilight)" stroke-width="3.5"/>
<!-- 65% of 2π×27 ≈ 169.6: 65% = 110.2, remainder = 59.4 -->
<circle cx="32" cy="32" r="27" fill="none" stroke="url(#rb-grEm)" stroke-width="3.5"
stroke-dasharray="110.2 59.4" stroke-linecap="round"/>
</svg>
<span class="goal-ring-pct">65%</span>
</div>
<div class="goal-info">
<div class="goal-title">Present with confidence</div>
<div class="goal-meta">Started Feb 5 · 2 new tiles this week</div>
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: var(--faint-light); flex-shrink:0;">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<!-- Goal 2: Set boundaries at work (sapphire, 40%) -->
<a class="goal-card" href="../lens/27-lens-goal-detail.html">
<div class="goal-ring-wrap">
<svg width="64" height="64" viewBox="0 0 64 64">
<defs>
<linearGradient id="rb-grSa" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/>
<stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
</defs>
<circle cx="32" cy="32" r="27" fill="none" stroke="var(--twilight)" stroke-width="3.5"/>
<!-- 40% of 169.6 = 67.8, remainder = 101.8 -->
<circle cx="32" cy="32" r="27" fill="none" stroke="url(#rb-grSa)" stroke-width="3.5"
stroke-dasharray="67.8 101.8" stroke-linecap="round"/>
</svg>
<span class="goal-ring-pct">40%</span>
</div>
<div class="goal-info">
<div class="goal-title">Set boundaries at work</div>
<div class="goal-meta">Started Feb 10</div>
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: var(--faint-light); flex-shrink:0;">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
</div>
</div>
<div class="tab-bar">
<a class="tab-item inactive" data-tab="turn" href="../turn/10-turn-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 2L20 12L12 22L4 12Z" fill="currentColor" opacity="0.9"/><path d="M12 2L20 12L12 12Z" fill="white" opacity="0.15"/></svg>
<span class="tab-label">Turn</span>
</a>
<a class="tab-item inactive" data-tab="mirror" href="../mirror/15-mirror-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 3L16 8L18 14L12 19L6 14L8 8Z" stroke="currentColor" fill="none" stroke-width="1.2"/></svg>
<span class="tab-label">Mirror</span>
</a>
<a class="tab-item active" data-tab="lens" href="../lens/20-lens-dashboard.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke="currentColor" fill="none" stroke-width="1.2"/><circle cx="12" cy="12" r="4" stroke="currentColor" fill="none" stroke-width="0.8"/></svg>
<span class="tab-label">Lens</span>
</a>
<a class="tab-item inactive" data-tab="gallery" href="../gallery/31-gallery-all.html">
<svg width="24" height="24" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/></svg>
<span class="tab-label">Gallery</span>
</a>
<a class="tab-item inactive" data-tab="you" href="../you/35-you-profile.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4" stroke="currentColor" fill="none" stroke-width="1.2"/><path d="M4 20C4 16 8 14 12 14C16 14 20 16 20 20" stroke="currentColor" fill="none" stroke-width="1.2" stroke-linecap="round"/></svg>
<span class="tab-label">You</span>
</a>
</div>
</div>
<script>
document.getElementById('dismissBtn').addEventListener('click', function() {
const card = this.closest('.bridge-card');
card.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out, max-height 0.4s ease-out, margin 0.4s ease-out, padding 0.4s ease-out';
card.style.opacity = '0';
card.style.transform = 'translateY(-8px)';
card.style.maxHeight = '0';
card.style.overflow = 'hidden';
card.style.marginBottom = '0';
card.style.padding = '0';
document.querySelectorAll('.goal-card').forEach(c => c.style.opacity = '1');
});
</script>
</body>
</html>

View File

@@ -0,0 +1,293 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Guide Integration Bridge</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.chat-area {
flex: 1;
overflow-y: auto;
padding: var(--space-4) var(--space-4) var(--space-2);
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.chat-area::-webkit-scrollbar { display: none; }
.chat-session-frame {
display: flex;
flex-direction: column;
height: calc(var(--device-height) - var(--status-bar-height) - var(--nav-header-height) - 64px);
}
.input-accessory {
height: 64px;
background: var(--kalei-navy);
border-top: 1px solid var(--twilight);
display: flex;
align-items: center;
gap: var(--space-2);
padding: 0 var(--space-3);
flex-shrink: 0;
}
.chat-input {
flex: 1;
height: 40px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-full);
padding: 0 var(--space-4);
font-family: var(--font-primary);
font-size: 15px;
color: var(--pure-light);
outline: none;
transition: border-color 0.2s ease-out;
}
.chat-input::placeholder { color: var(--faint-light); }
.chat-input:focus { border-color: var(--amethyst); }
.send-btn {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire));
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
flex-shrink: 0;
box-shadow: 0 0 12px rgba(139,92,246,0.3);
}
.nav-close {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
text-decoration: none;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
/* Integration bridge card */
.integration-card {
background: var(--kalei-navy);
border-radius: var(--radius-xl);
padding: var(--space-4);
position: relative;
animation: slideUp 0.45s ease-out 0.5s both;
}
.integration-card::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
z-index: -1;
}
.integration-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-3);
}
.integration-icon {
font-size: 14px;
font-weight: 700;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.integration-title {
font-size: 14px;
font-weight: 600;
color: var(--pure-light);
}
.keepsake-quote {
font-size: 14px;
font-style: italic;
color: var(--sapphire-light);
padding: var(--space-2) var(--space-3);
border-left: 2px solid var(--sapphire);
background: rgba(59,130,246,0.06);
border-radius: 0 var(--radius-md) var(--radius-md) 0;
margin-bottom: var(--space-3);
line-height: 1.6;
}
.keepsake-meta {
font-size: 11px;
color: var(--faint-light);
margin-top: var(--space-1);
}
.current-quote {
font-size: 14px;
font-style: italic;
color: var(--amber-light);
padding: var(--space-2) var(--space-3);
border-left: 2px solid var(--amber);
background: rgba(245,158,11,0.06);
border-radius: 0 var(--radius-md) var(--radius-md) 0;
margin-bottom: var(--space-3);
line-height: 1.6;
}
.current-meta {
font-size: 11px;
color: var(--faint-light);
margin-top: var(--space-1);
}
.integration-body {
font-size: 13px;
color: var(--soft-light);
line-height: 1.55;
margin-bottom: var(--space-4);
}
.integration-actions {
display: flex;
gap: var(--space-2);
}
.int-btn-primary {
flex: 1;
height: 42px;
background: var(--amethyst);
color: var(--pure-light);
font-size: 13px;
font-weight: 600;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 14px rgba(139,92,246,0.25);
transition: background 0.2s ease-out;
}
.int-btn-primary:hover { background: var(--amethyst-light); }
.int-btn-ghost {
flex: 1;
height: 42px;
background: transparent;
color: var(--dim-light);
font-size: 13px;
font-weight: 500;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: 1px solid var(--twilight);
transition: color 0.2s ease-out;
}
.int-btn-ghost:hover { color: var(--soft-light); }
.session-timer {
font-size: 12px;
color: var(--dim-light);
font-variant-numeric: tabular-nums;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="../mirror/15-mirror-home.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<div style="display:flex; flex-direction:column; align-items:center; position:absolute; left:50%; transform:translateX(-50%);">
<span class="nav-title" style="position:static; transform:none;">Mirror Session</span>
<span class="session-timer">12:47</span>
</div>
<a class="nav-close" href="../mirror/19-mirror-reflection.html">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M2 2L12 12M12 2L2 12" stroke="var(--dim-light)" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="chat-session-frame">
<div class="chat-area">
<!-- Earlier messages (faded — mirror session in progress) -->
<div class="chat-bubble ai" style="opacity: 0.5; animation: fadeIn 0.3s ease-out both;">
Welcome, Alex. I'm here to listen. What's been on your mind?
</div>
<div class="chat-bubble user" style="opacity: 0.5; animation: fadeIn 0.3s ease-out 0.1s both;">
I've been catastrophizing again. Every meeting I walk out of feeling like I've failed, even when nothing actually went wrong.
</div>
<div class="chat-bubble ai" style="opacity: 0.5; animation: fadeIn 0.3s ease-out 0.2s both;">
That gap between what happens and how you experience it — that sounds familiar. What does "failing" feel like in those moments?
</div>
<div class="chat-bubble user" style="opacity: 0.5; animation: fadeIn 0.3s ease-out 0.3s both;">
Like everyone noticed and I'm the only one who doesn't belong in the room.
</div>
<!-- Last AI message — picks up the echo -->
<div class="chat-bubble ai" style="animation: fadeIn 0.3s ease-out 0.4s both;">
"Everyone noticed" — that's a pattern I've heard before. You've turned a version of this thought already, and you wrote something that pushed back on it.
</div>
<!-- Integration bridge card after last message -->
<div class="integration-card">
<div class="integration-header">
<span class="integration-icon"></span>
<span class="integration-title">A fragment echoes a pattern from your Turns</span>
</div>
<!-- Past Turn keepsake -->
<div class="keepsake-quote">
"Everyone sees my failures — but nobody actually told me that. I invented their reaction."
<div class="keepsake-meta">Saved from Turn · Feb 18, 2026 · Mind Reading</div>
</div>
<!-- Current Mirror fragment -->
<div class="current-quote">
"Everyone noticed and I'm the only one who doesn't belong."
<div class="current-meta">Today's Mirror session · just now · Catastrophizing</div>
</div>
<div class="integration-body">
Same story, different day. The first time you named it — then reframed it. Which version is closer to what actually happened in those meetings?
</div>
<div class="integration-actions">
<a class="int-btn-primary" href="../you/40-evidence-wall-mid.html">See your Evidence Wall</a>
<a class="int-btn-ghost" href="../mirror/16-mirror-session.html">Keep writing</a>
</div>
</div>
</div>
<div class="input-accessory">
<input class="chat-input" type="text" placeholder="Write freely...">
<button class="send-btn">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M15 9L3 4L5.5 9L3 14L15 9Z" fill="var(--void)" stroke="none"/>
</svg>
</button>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,273 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Guide Attention Prompt</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-aura {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
width: 280px;
height: 280px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.12) 0%, transparent 70%);
filter: blur(50px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
@keyframes breathing {
0%, 100% { opacity: 0.6; transform: scale(1) translate(-50%, -50%); }
50% { opacity: 1; transform: scale(1.05) translate(-48%, -52%); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.lens-heading { color: var(--emerald-light); margin-bottom: 4px; }
.goal-card {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
display: flex;
gap: var(--space-3);
align-items: center;
text-decoration: none;
transition: border-color 0.2s ease-out;
}
.goal-card:hover { border-color: rgba(16,185,129,0.4); }
.goal-ring-wrap {
flex-shrink: 0;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
}
.goal-ring-wrap svg { transform: rotate(-90deg); position: absolute; }
.goal-ring-pct { font-size: 14px; font-weight: 700; color: var(--pure-light); position: relative; z-index: 1; }
.goal-info { flex: 1; min-width: 0; }
.goal-title { font-size: 15px; font-weight: 600; color: var(--pure-light); margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.goal-meta { font-size: 12px; color: var(--dim-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Attention prompt card */
.attention-card {
background: var(--kalei-navy);
border: 1px solid rgba(139,92,246,0.35);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
box-shadow: 0 0 20px rgba(139,92,246,0.08);
position: relative;
overflow: hidden;
animation: fadeIn 0.35s ease-out 0.1s both;
}
.attention-card::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--amethyst), var(--sapphire), var(--emerald), transparent);
opacity: 0.7;
}
.attention-label-row {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-3);
}
.attention-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--amethyst);
box-shadow: 0 0 8px rgba(139,92,246,0.5);
animation: breathingDot 2s ease-in-out infinite;
}
@keyframes breathingDot {
0%, 100% { box-shadow: 0 0 4px rgba(139,92,246,0.3); }
50% { box-shadow: 0 0 12px rgba(139,92,246,0.7); }
}
.attention-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--amethyst-light);
}
.attention-heading {
font-size: 16px;
font-weight: 600;
color: var(--pure-light);
margin-bottom: var(--space-3);
}
.attention-body {
font-size: 14px;
color: var(--soft-light);
line-height: 1.65;
margin-bottom: var(--space-3);
}
.attention-footer {
font-size: 12px;
color: var(--faint-light);
font-style: italic;
padding-top: var(--space-2);
border-top: 1px solid var(--twilight);
margin-bottom: var(--space-4);
}
.attention-footer span {
color: var(--amethyst-light);
font-style: normal;
font-weight: 500;
}
.attention-actions {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.got-it-btn {
height: 48px;
background: var(--amethyst);
color: var(--pure-light);
font-size: 15px;
font-weight: 600;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 18px rgba(139,92,246,0.25);
transition: background 0.2s ease-out;
cursor: pointer;
border: none;
font-family: var(--font-primary);
width: 100%;
}
.got-it-btn:hover { background: var(--amethyst-light); }
.log-btn {
height: 44px;
background: var(--deep-glass);
color: var(--amethyst-light);
font-size: 14px;
font-weight: 500;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: 1px solid rgba(139,92,246,0.25);
transition: border-color 0.2s ease-out;
}
.log-btn:hover { border-color: var(--amethyst); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content" style="padding-top: var(--space-4); padding-bottom: 100px;">
<div class="screen-aura"></div>
<div style="position: relative; z-index: 1;">
<div class="heading lens-heading">Your Lens</div>
<div class="body-sm text-dim" style="margin-bottom: var(--space-4);">3 active goals · 47 evidence tiles</div>
<!-- Goal card — Present with confidence (emerald, 65%) -->
<a class="goal-card" href="../lens/27-lens-goal-detail.html">
<div class="goal-ring-wrap">
<svg width="64" height="64" viewBox="0 0 64 64">
<defs>
<linearGradient id="ap-grEm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
</defs>
<circle cx="32" cy="32" r="27" fill="none" stroke="var(--twilight)" stroke-width="3.5"/>
<!-- 65% of 2π×27 ≈ 169.6: 65% = 110.2, remainder = 59.4 -->
<circle cx="32" cy="32" r="27" fill="none" stroke="url(#ap-grEm)" stroke-width="3.5"
stroke-dasharray="110.2 59.4" stroke-linecap="round"/>
</svg>
<span class="goal-ring-pct">65%</span>
</div>
<div class="goal-info">
<div class="goal-title">Present with confidence</div>
<div class="goal-meta">Started Feb 5 · 2 new tiles this week</div>
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: var(--faint-light); flex-shrink:0;">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<!-- Attention prompt card below goals -->
<div class="attention-card">
<div class="attention-label-row">
<div class="attention-dot"></div>
<span class="attention-label">Today's Focus: Notice</span>
</div>
<div class="attention-heading">Notice when you catch yourself catastrophizing</div>
<div class="attention-body">
Today, see if you can catch the moment your mind jumps from "this went badly" to "everything is ruined." You don't need to stop it — just notice the leap. The awareness itself is the practice.
</div>
<div class="attention-footer">
Linked to your fragment: <span>Catastrophizing (7 of 47 Turns)</span>
</div>
<div class="attention-actions">
<button class="got-it-btn" id="gotItBtn">Got it</button>
<a class="log-btn" href="71-guide-moment-log.html" id="logBtn" style="display: none;">Log a moment</a>
</div>
</div>
</div>
</div>
<div class="tab-bar">
<a class="tab-item inactive" data-tab="turn" href="../turn/10-turn-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 2L20 12L12 22L4 12Z" fill="currentColor" opacity="0.9"/><path d="M12 2L20 12L12 12Z" fill="white" opacity="0.15"/></svg>
<span class="tab-label">Turn</span>
</a>
<a class="tab-item inactive" data-tab="mirror" href="../mirror/15-mirror-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 3L16 8L18 14L12 19L6 14L8 8Z" stroke="currentColor" fill="none" stroke-width="1.2"/></svg>
<span class="tab-label">Mirror</span>
</a>
<a class="tab-item active" data-tab="lens" href="../lens/20-lens-dashboard.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke="currentColor" fill="none" stroke-width="1.2"/><circle cx="12" cy="12" r="4" stroke="currentColor" fill="none" stroke-width="0.8"/></svg>
<span class="tab-label">Lens</span>
</a>
<a class="tab-item inactive" data-tab="gallery" href="../gallery/31-gallery-all.html">
<svg width="24" height="24" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/></svg>
<span class="tab-label">Gallery</span>
</a>
<a class="tab-item inactive" data-tab="you" href="../you/35-you-profile.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4" stroke="currentColor" fill="none" stroke-width="1.2"/><path d="M4 20C4 16 8 14 12 14C16 14 20 16 20 20" stroke="currentColor" fill="none" stroke-width="1.2" stroke-linecap="round"/></svg>
<span class="tab-label">You</span>
</a>
</div>
</div>
<script>
document.getElementById('gotItBtn').addEventListener('click', function() {
this.textContent = 'Noted';
this.style.background = 'rgba(139,92,246,0.4)';
this.style.pointerEvents = 'none';
document.getElementById('logBtn').style.display = 'flex';
});
</script>
</body>
</html>

View File

@@ -0,0 +1,281 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Log a Moment</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
@keyframes breathing {
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.05); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes tileGlow {
0%, 100% { box-shadow: 0 0 8px rgba(139,92,246,0.2); }
50% { box-shadow: 0 0 24px rgba(139,92,246,0.5); }
}
.reminder-card {
background: var(--kalei-navy);
border: 1px solid rgba(139,92,246,0.2);
border-radius: var(--radius-xl);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-5);
display: flex;
align-items: center;
gap: var(--space-3);
}
.reminder-icon {
width: 36px;
height: 36px;
border-radius: var(--radius-md);
background: rgba(139,92,246,0.1);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.reminder-text {
font-size: 13px;
color: var(--dim-light);
line-height: 1.5;
}
.reminder-text strong {
color: var(--soft-light);
font-weight: 500;
}
.input-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--dim-light);
margin-bottom: var(--space-2);
}
.moment-textarea {
width: 100%;
min-height: 100px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
font-family: var(--font-primary);
font-size: 16px;
color: var(--pure-light);
resize: none;
outline: none;
line-height: 1.6;
transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out;
margin-bottom: var(--space-4);
}
.moment-textarea::placeholder { color: var(--faint-light); }
.moment-textarea:focus {
border-color: var(--amethyst);
box-shadow: 0 0 0 3px rgba(139,92,246,0.1);
}
.logged-entry {
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-4);
}
.logged-entry-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--faint-light);
margin-bottom: var(--space-2);
}
.logged-entry-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.6;
}
.logged-entry-meta {
font-size: 12px;
color: var(--faint-light);
margin-top: var(--space-2);
}
/* Confirmation card */
.confirmation-card {
background: var(--kalei-navy);
border: 1px solid rgba(139,92,246,0.3);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-4);
box-shadow: 0 0 16px rgba(139,92,246,0.08);
}
.confirmation-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-3);
}
.confirmation-icon {
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(139,92,246,0.15);
display: flex;
align-items: center;
justify-content: center;
}
.confirmation-title {
font-size: 14px;
font-weight: 600;
color: var(--amethyst-light);
}
.confirmation-body {
font-size: 13px;
color: var(--soft-light);
line-height: 1.6;
margin-bottom: var(--space-3);
}
/* Evidence tile preview */
.evidence-preview {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3);
background: rgba(139,92,246,0.05);
border: 1px solid rgba(139,92,246,0.15);
border-radius: var(--radius-lg);
}
.evidence-tile-mini {
width: 52px;
height: 52px;
border-radius: var(--radius-md);
background: var(--kalei-navy);
border: 1px solid var(--amethyst);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
animation: tileGlow 3s ease-in-out infinite;
}
.evidence-tile-info { flex: 1; }
.evidence-tile-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--amethyst);
margin-bottom: 2px;
}
.evidence-tile-text {
font-size: 13px;
color: var(--soft-light);
}
.save-btn {
height: 52px;
background: var(--amethyst);
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 20px rgba(139,92,246,0.25);
margin-bottom: var(--space-3);
transition: background 0.2s ease-out;
}
.save-btn:hover { background: var(--amethyst-light); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="70-guide-attention-prompt.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Log a Moment</span>
<span class="nav-action"></span>
</div>
<div class="screen-content" style="padding-top: var(--space-4); padding-bottom: var(--space-8);">
<!-- Today's prompt reminder -->
<div class="reminder-card">
<div class="reminder-icon">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<circle cx="9" cy="9" r="7" stroke="var(--amethyst)" stroke-width="1.2"/>
<path d="M9 5v4l3 2" stroke="var(--amethyst)" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="reminder-text">
Today's prompt: <strong>Notice when you catch yourself catastrophizing</strong>
</div>
</div>
<!-- Text input -->
<div class="input-label">What did you notice?</div>
<textarea class="moment-textarea" placeholder="Describe the moment — what happened, when, what the leap looked like..."></textarea>
<!-- Mock logged entry (shown as already submitted) -->
<div class="logged-entry">
<div class="logged-entry-label">Logged today</div>
<div class="logged-entry-text">"After the standup I immediately thought 'everyone thinks I'm incompetent now.' Then I caught it — nobody actually said that. My manager even nodded when I pushed back on the timeline."</div>
<div class="logged-entry-meta">Today, 10:14 AM · Catastrophizing · Present with confidence</div>
</div>
<!-- Confirmation card -->
<div class="confirmation-card">
<div class="confirmation-header">
<div class="confirmation-icon">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M3 8l3.5 3.5L13 4" stroke="var(--amethyst)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span class="confirmation-title">That's the pattern — and you saw it</span>
</div>
<div class="confirmation-body">
Catching the leap between "this went badly" and "everything is ruined" — that's awareness in action. You don't have to stop it every time. Noticing it is enough. Added to your Evidence Wall.
</div>
<!-- Evidence wall tile preview -->
<div class="evidence-preview">
<div class="evidence-tile-mini">
<svg width="24" height="24" viewBox="0 0 24 24">
<defs>
<linearGradient id="ml-grAm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
</defs>
<path d="M12 2L20 12L12 22L4 12Z" fill="url(#ml-grAm)" opacity="0.9"/>
<path d="M12 2L20 12L12 12Z" fill="#fff" opacity="0.15"/>
</svg>
</div>
<div class="evidence-tile-info">
<div class="evidence-tile-label">New tile earned</div>
<div class="evidence-tile-text">Moment logged · Feb 22, 2026</div>
</div>
<a href="../you/40-evidence-wall-mid.html" style="font-size: 12px; color: var(--amethyst-light); text-decoration: none; white-space: nowrap;">View wall →</a>
</div>
</div>
<a class="save-btn" href="../lens/20-lens-dashboard.html">Done</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,272 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Guide Evidence (Mirror)</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
@keyframes breathing {
0%, 100% { opacity: 0.5; transform: scale(1); }
50% { opacity: 0.8; transform: scale(1.03); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Mirror Reflection base styles (dimmed context) */
.reflection-section {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
opacity: 0.7;
}
.reflection-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--amber-light);
margin-bottom: var(--space-2);
}
.reflection-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.65;
}
.theme-chips {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin-top: var(--space-2);
}
.theme-chip {
background: rgba(245,158,11,0.1);
color: var(--amber-light);
border: 1px solid rgba(245,158,11,0.2);
border-radius: var(--radius-full);
padding: 4px 12px;
font-size: 12px;
font-weight: 500;
}
/* Evidence intervention card */
.evidence-card {
background: var(--kalei-navy);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
position: relative;
animation: slideUp 0.4s ease-out both;
}
.evidence-card::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
z-index: -1;
}
.evidence-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-3);
}
.evidence-guide-icon {
font-size: 14px;
font-weight: 700;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.evidence-title {
font-size: 14px;
font-weight: 600;
color: var(--pure-light);
}
.evidence-intro {
font-size: 13px;
color: var(--soft-light);
line-height: 1.6;
margin-bottom: var(--space-3);
}
/* Proof points */
.proof-list {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-bottom: var(--space-4);
}
.proof-item {
display: flex;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-2) var(--space-3);
background: var(--deep-glass);
border-radius: var(--radius-md);
}
.proof-number {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
flex-shrink: 0;
margin-top: 1px;
}
.proof-number.emerald {
background: rgba(16,185,129,0.15);
color: var(--emerald-light);
box-shadow: 0 0 8px rgba(16,185,129,0.2);
}
.proof-number.amber {
background: rgba(245,158,11,0.15);
color: var(--amber-light);
box-shadow: 0 0 8px rgba(245,158,11,0.2);
}
.proof-number.sapphire {
background: rgba(59,130,246,0.15);
color: var(--sapphire-light);
box-shadow: 0 0 8px rgba(59,130,246,0.2);
}
.proof-text {
font-size: 13px;
color: var(--soft-light);
line-height: 1.55;
}
.proof-text strong {
font-weight: 600;
}
.evidence-actions {
display: flex;
gap: var(--space-2);
}
.ev-btn-primary {
flex: 1;
height: 44px;
background: var(--amethyst);
color: var(--pure-light);
font-size: 13px;
font-weight: 600;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 14px rgba(139,92,246,0.25);
transition: background 0.2s ease-out;
}
.ev-btn-primary:hover { background: var(--amethyst-light); }
.ev-btn-ghost {
flex: 1;
height: 44px;
background: transparent;
color: var(--dim-light);
font-size: 13px;
font-weight: 500;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: 1px solid var(--twilight);
cursor: pointer;
transition: color 0.2s ease-out;
}
.ev-btn-ghost:hover { color: var(--soft-light); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="../mirror/16-mirror-session.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Reflection</span>
<span class="nav-action"></span>
</div>
<div class="screen-content" style="padding-top: var(--space-4); padding-bottom: var(--space-8);">
<!-- Mirror reflection context (dimmed) -->
<div class="reflection-section">
<div class="reflection-label">Your session themes</div>
<div class="reflection-text">Workplace pressure and self-worth ran through today's session — particularly around visibility and being taken seriously.</div>
<div class="theme-chips">
<span class="theme-chip">Self-doubt</span>
<span class="theme-chip">Work pressure</span>
<span class="theme-chip">Catastrophizing</span>
</div>
</div>
<div class="reflection-section">
<div class="reflection-label">Fragment detected</div>
<div class="reflection-text">"Nobody takes me seriously at work" — this came up twice today. Mind Reading pattern, with no supporting evidence cited.</div>
</div>
<!-- Evidence intervention card -->
<div class="evidence-card">
<div class="evidence-header">
<span class="evidence-guide-icon"></span>
<span class="evidence-title">Here's what I've seen</span>
</div>
<div class="evidence-intro">
You said nobody takes you seriously. Your own record says something different:
</div>
<div class="proof-list">
<div class="proof-item">
<div class="proof-number emerald">1</div>
<div class="proof-text"><strong>14-day Mirror streak</strong> — you've shown up to reflect every single day this month. That's not someone who doesn't take themselves seriously.</div>
</div>
<div class="proof-item">
<div class="proof-number amber">2</div>
<div class="proof-text"><strong>47 Turns completed</strong> — each one is you choosing to examine a thought instead of just living inside it.</div>
</div>
<div class="proof-item">
<div class="proof-number sapphire">3</div>
<div class="proof-text"><strong>Mind Reading appears 4 times</strong> in your fragments — you've been naming this pattern. Naming it is the first move.</div>
</div>
</div>
<div class="evidence-actions">
<a class="ev-btn-primary" href="../you/40-evidence-wall-mid.html">See your full Evidence Wall</a>
<button class="ev-btn-ghost" id="dismissEvidence">Maybe later</button>
</div>
</div>
</div>
</div>
<script>
document.getElementById('dismissEvidence').addEventListener('click', function() {
const card = this.closest('.evidence-card');
card.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
card.style.opacity = '0';
card.style.transform = 'translateY(-8px)';
setTimeout(() => card.style.display = 'none', 300);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,252 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Guide Evidence (Turn)</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
@keyframes breathing {
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.05); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
/* Turn results base (dimmed context) */
.turn-original-thought {
font-size: 15px;
color: var(--dim-light);
font-style: italic;
text-align: center;
padding: var(--space-4) var(--space-2);
margin-bottom: var(--space-3);
}
.reframe-block {
background: var(--kalei-navy);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
opacity: 0.75;
}
.reframe-block.amethyst { border-left: 3px solid var(--amethyst); }
.reframe-block.sapphire { border-left: 3px solid var(--sapphire); }
.reframe-block.emerald { border-left: 3px solid var(--emerald); }
.reframe-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
margin-bottom: var(--space-2);
}
.amethyst .reframe-label { color: var(--amethyst-light); }
.sapphire .reframe-label { color: var(--sapphire-light); }
.emerald .reframe-label { color: var(--emerald-light); }
.reframe-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.6;
}
/* Evidence intervention card */
.evidence-card {
background: var(--kalei-navy);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
position: relative;
animation: slideUp 0.4s ease-out 0.2s both;
}
.evidence-card::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
z-index: -1;
}
.evidence-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-3);
}
.evidence-guide-icon {
font-size: 14px;
font-weight: 700;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.evidence-title {
font-size: 14px;
font-weight: 600;
color: var(--pure-light);
}
.evidence-intro {
font-size: 13px;
color: var(--soft-light);
line-height: 1.6;
margin-bottom: var(--space-3);
}
.keepsake-quote {
font-size: 14px;
font-style: italic;
color: var(--sapphire-light);
padding: var(--space-3);
border-left: 2px solid var(--sapphire);
background: rgba(59,130,246,0.06);
border-radius: 0 var(--radius-md) var(--radius-md) 0;
margin-bottom: var(--space-3);
line-height: 1.65;
}
.keepsake-meta {
font-size: 11px;
color: var(--faint-light);
margin-top: var(--space-1);
}
.evidence-stat {
font-size: 13px;
color: var(--soft-light);
padding: var(--space-2) var(--space-3);
background: rgba(16,185,129,0.06);
border-radius: var(--radius-md);
display: flex;
align-items: center;
gap: var(--space-2);
}
.evidence-stat strong {
color: var(--emerald-light);
font-weight: 600;
}
/* Action buttons at bottom */
.turn-actions {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-top: var(--space-3);
}
.save-btn {
height: 52px;
background: var(--amethyst);
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 20px rgba(139,92,246,0.25);
transition: background 0.2s ease-out;
}
.save-btn:hover { background: var(--amethyst-light); }
.share-btn {
height: 44px;
background: var(--deep-glass);
color: var(--soft-light);
font-size: 14px;
font-weight: 500;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: 1px solid var(--twilight);
}
.screen-aura {
position: absolute;
top: 15%;
left: 50%;
transform: translate(-50%, -50%);
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.12) 0%, transparent 70%);
filter: blur(40px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="../turn/10-turn-home.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Your Turn</span>
<span class="nav-action"></span>
</div>
<div class="screen-content" style="padding-top: 0; padding-bottom: var(--space-8);">
<div class="screen-aura"></div>
<!-- Canonical Turn from Feb 22 -->
<div class="turn-original-thought">"I completely bombed my presentation today and everyone saw"</div>
<!-- Reframe blocks (dimmed context) -->
<div class="reframe-block amethyst">
<div class="reframe-label">Perspective Shift</div>
<div class="reframe-text">Your manager said the data was solid. What if "bombed" is only the feeling — not what actually landed?</div>
</div>
<div class="reframe-block sapphire">
<div class="reframe-label">Evidence Check</div>
<div class="reframe-text">Who told you everyone saw? What specific reaction are you basing this on — a word, a look, a silence?</div>
</div>
<div class="reframe-block emerald">
<div class="reframe-label">Action Step</div>
<div class="reframe-text">Before your next presentation, write down one thing you want people to take away — not how you want to seem.</div>
</div>
<!-- Evidence intervention card -->
<div class="evidence-card">
<div class="evidence-header">
<span class="evidence-guide-icon"></span>
<span class="evidence-title">Something interesting</span>
</div>
<div class="evidence-intro">
This thought — "I bombed and everyone saw" — is Catastrophizing. You've Turned this exact shape before, and you kept something worth reading again:
</div>
<div class="keepsake-quote">
"The gap between how I felt in that meeting and what anyone actually said out loud was enormous. The catastrophe lived entirely in my head."
<div class="keepsake-meta">Saved keepsake · Feb 14, 2026 · Catastrophizing</div>
</div>
<div class="evidence-stat">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M2 7l3 3L12 3" stroke="var(--emerald)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
You've caught this fragment <strong>7 times</strong> now. Each time you name it, it gets a little smaller.
</div>
</div>
<!-- Standard turn action buttons -->
<div class="turn-actions">
<a class="save-btn" href="../turn/13-turn-results.html">Save this Turn</a>
<a class="share-btn" href="../turn/13-turn-results.html">Share pattern</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,378 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Weekly Pulse (Step 1)</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
@keyframes breathing {
0%, 100% { opacity: 0.5; transform: scale(1) translate(-50%, -50%); }
50% { opacity: 0.9; transform: scale(1.08) translate(-46%, -54%); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes diamondPulse {
0%, 100% { filter: drop-shadow(0 0 4px rgba(245,158,11,0.2)); }
50% { filter: drop-shadow(0 0 14px rgba(245,158,11,0.6)); }
}
.pulse-frame {
display: flex;
flex-direction: column;
flex: 1;
padding: 0 var(--space-4) var(--space-6);
}
.step-indicator {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) 0 var(--space-2);
}
.step-label {
font-size: 12px;
font-weight: 600;
color: var(--dim-light);
}
.step-dots {
display: flex;
gap: 6px;
}
.step-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--twilight);
transition: background 0.2s ease-out;
}
.step-dot.active {
background: linear-gradient(135deg, var(--amethyst), var(--emerald));
box-shadow: 0 0 8px rgba(139,92,246,0.4);
}
.step-dot.complete { background: var(--dim-light); }
.pulse-heading {
font-family: var(--font-display);
font-size: 28px;
font-weight: 700;
color: var(--pure-light);
margin-bottom: var(--space-2);
letter-spacing: -0.01em;
animation: fadeIn 0.4s ease-out 0.1s both;
opacity: 0;
}
.pulse-subheading {
font-size: 15px;
color: var(--dim-light);
margin-bottom: var(--space-6);
animation: fadeIn 0.4s ease-out 0.2s both;
opacity: 0;
}
/* Diamond scale */
.diamond-scale {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: var(--space-2);
margin-bottom: var(--space-5);
animation: fadeIn 0.4s ease-out 0.3s both;
opacity: 0;
}
.scale-item {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
cursor: pointer;
flex: 1;
}
.scale-item svg {
transition: transform 0.2s ease-out, filter 0.2s ease-out;
}
.scale-item:hover svg { transform: scale(1.1); }
.scale-item.selected svg {
animation: diamondPulse 2.5s ease-in-out infinite;
}
.scale-label {
font-size: 10px;
color: var(--faint-light);
text-align: center;
line-height: 1.3;
}
.scale-item.selected .scale-label {
color: var(--amber-light);
font-weight: 600;
}
/* Gradient background auras */
.bg-aura-1 {
position: absolute;
top: 35%;
left: 30%;
width: 200px;
height: 200px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.1) 0%, transparent 70%);
filter: blur(50px);
animation: breathing 7s ease-in-out infinite;
pointer-events: none;
}
.bg-aura-2 {
position: absolute;
top: 25%;
left: 60%;
width: 160px;
height: 160px;
border-radius: 50%;
background: radial-gradient(circle, rgba(245,158,11,0.08) 0%, transparent 70%);
filter: blur(40px);
animation: breathing 9s ease-in-out infinite 2s;
pointer-events: none;
}
/* Write-in field */
.writein-label {
font-size: 12px;
color: var(--dim-light);
margin-bottom: var(--space-2);
animation: fadeIn 0.4s ease-out 0.4s both;
opacity: 0;
}
.writein-input {
width: 100%;
height: 72px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
font-family: var(--font-primary);
font-size: 14px;
color: var(--pure-light);
resize: none;
outline: none;
transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out;
margin-bottom: var(--space-5);
animation: fadeIn 0.4s ease-out 0.45s both;
opacity: 0;
}
.writein-input::placeholder { color: var(--faint-light); }
.writein-input:focus {
border-color: rgba(139,92,246,0.4);
box-shadow: 0 0 0 3px rgba(139,92,246,0.08);
}
.next-btn {
height: 52px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire));
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 24px rgba(139,92,246,0.25);
margin-top: auto;
transition: opacity 0.2s ease-out;
animation: fadeIn 0.4s ease-out 0.5s both;
opacity: 0;
}
.next-btn:hover { opacity: 0.88; }
.close-btn {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: none;
cursor: pointer;
}
/* Week recap mini-stats */
.week-recap {
display: flex;
gap: var(--space-2);
margin-bottom: var(--space-4);
animation: fadeIn 0.4s ease-out 0.25s both;
opacity: 0;
}
.recap-stat {
flex: 1;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-md);
padding: var(--space-2) var(--space-3);
text-align: center;
}
.recap-num {
font-size: 20px;
font-weight: 700;
color: var(--pure-light);
display: block;
margin-bottom: 2px;
}
.recap-lbl {
font-size: 10px;
color: var(--dim-light);
text-transform: uppercase;
letter-spacing: 0.05em;
}
</style>
</head>
<body>
<div class="device-frame">
<!-- Background auras -->
<div class="bg-aura-1"></div>
<div class="bg-aura-2"></div>
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="pulse-frame">
<div class="step-indicator">
<span class="step-label">Step 1 of 3</span>
<div class="step-dots">
<div class="step-dot active"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
</div>
<a class="close-btn" href="../turn/10-turn-home.html">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M2 2L10 10M10 2L2 10" stroke="var(--dim-light)" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="pulse-heading">Your Weekly Pulse</div>
<div class="pulse-subheading">Feb 1622, 2026 · How did this week feel?</div>
<!-- Week at-a-glance stats -->
<div class="week-recap">
<div class="recap-stat">
<span class="recap-num" style="color: var(--amethyst-light);">14</span>
<span class="recap-lbl">Turns</span>
</div>
<div class="recap-stat">
<span class="recap-num" style="color: var(--sapphire-light);">5</span>
<span class="recap-lbl">Mirror</span>
</div>
<div class="recap-stat">
<span class="recap-num" style="color: var(--emerald-light);">2</span>
<span class="recap-lbl">Rituals</span>
</div>
</div>
<!-- 5-diamond scale -->
<div class="diamond-scale" id="diamondScale">
<!-- 1: Rough -->
<div class="scale-item" data-index="0">
<svg width="40" height="40" viewBox="0 0 40 40">
<defs>
<linearGradient id="d1-gr" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#475569"/>
<stop offset="100%" stop-color="#1C2240"/>
</linearGradient>
</defs>
<path d="M20 4L36 20L20 36L4 20Z" fill="url(#d1-gr)" opacity="0.5"/>
<path d="M20 4L36 20L20 20Z" fill="#fff" opacity="0.04"/>
<path d="M14 14l4 3M20 8l-2 6" stroke="rgba(255,255,255,0.1)" stroke-width="0.8"/>
</svg>
<span class="scale-label">Rough</span>
</div>
<!-- 2: Harder than usual -->
<div class="scale-item" data-index="1">
<svg width="44" height="44" viewBox="0 0 44 44">
<defs>
<linearGradient id="d2-gr" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#64748B"/>
<stop offset="100%" stop-color="#334155"/>
</linearGradient>
</defs>
<path d="M22 4L40 22L22 40L4 22Z" fill="url(#d2-gr)" opacity="0.6"/>
<path d="M22 4L40 22L22 22Z" fill="#fff" opacity="0.06"/>
</svg>
<span class="scale-label">Harder than usual</span>
</div>
<!-- 3: Steady — selected with amber glow -->
<div class="scale-item selected" data-index="2" id="selectedDiamond">
<svg width="52" height="52" viewBox="0 0 52 52">
<defs>
<linearGradient id="d3-gr" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FCD34D"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
</defs>
<path d="M26 4L48 26L26 48L4 26Z" fill="url(#d3-gr)" opacity="0.9"/>
<path d="M26 4L48 26L26 26Z" fill="#fff" opacity="0.18"/>
<path d="M26 4L4 26L26 26Z" fill="#fff" opacity="0.06"/>
<line x1="26" y1="4" x2="26" y2="48" stroke="#fff" stroke-width="0.6" opacity="0.15"/>
<line x1="4" y1="26" x2="48" y2="26" stroke="#fff" stroke-width="0.6" opacity="0.1"/>
<circle cx="20" cy="20" r="2.5" fill="#fff" opacity="0.3"/>
</svg>
<span class="scale-label">Steady</span>
</div>
<!-- 4: Good momentum -->
<div class="scale-item" data-index="3">
<svg width="44" height="44" viewBox="0 0 44 44">
<defs>
<linearGradient id="d4-gr" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
</defs>
<path d="M22 3L41 22L22 41L3 22Z" fill="url(#d4-gr)" opacity="0.8"/>
<path d="M22 3L41 22L22 22Z" fill="#fff" opacity="0.14"/>
<circle cx="17" cy="17" r="1.8" fill="#fff" opacity="0.2"/>
</svg>
<span class="scale-label">Good momentum</span>
</div>
<!-- 5: Breakthrough -->
<div class="scale-item" data-index="4">
<svg width="40" height="40" viewBox="0 0 40 40">
<defs>
<linearGradient id="d5-gr" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="50%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#FCD34D"/>
</linearGradient>
<filter id="d5-glow" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="2" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g filter="url(#d5-glow)">
<path d="M20 2L38 20L20 38L2 20Z" fill="url(#d5-gr)" opacity="0.7"/>
<path d="M20 2L38 20L20 20Z" fill="#fff" opacity="0.2"/>
<path d="M20 2L2 20L20 20Z" fill="#fff" opacity="0.1"/>
<circle cx="15" cy="15" r="2.5" fill="#fff" opacity="0.4"/>
</g>
</svg>
<span class="scale-label">Breakthrough week</span>
</div>
</div>
<div class="writein-label">Anything else you want to add? (optional)</div>
<textarea class="writein-input" placeholder="This week felt..."></textarea>
<a class="next-btn" href="75-guide-pulse-read.html">Next</a>
</div>
</div>
<script>
const items = document.querySelectorAll('.scale-item');
items.forEach((item) => {
item.addEventListener('click', () => {
items.forEach(it => it.classList.remove('selected'));
item.classList.add('selected');
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,230 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Weekly Pulse (Step 2)</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
@keyframes breathing {
0%, 100% { opacity: 0.5; transform: scale(1) translate(-50%, -50%); }
50% { opacity: 0.8; transform: scale(1.06) translate(-48%, -52%); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.bg-aura {
position: absolute;
top: 40%;
left: 50%;
width: 240px;
height: 240px;
border-radius: 50%;
background: radial-gradient(circle, rgba(59,130,246,0.08) 0%, transparent 70%);
filter: blur(50px);
animation: breathing 8s ease-in-out infinite;
pointer-events: none;
}
.pulse-frame {
display: flex;
flex-direction: column;
flex: 1;
padding: 0 var(--space-4) var(--space-6);
}
.step-indicator {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) 0 var(--space-2);
}
.step-label {
font-size: 12px;
font-weight: 600;
color: var(--dim-light);
}
.step-dots {
display: flex;
gap: 6px;
}
.step-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--twilight);
}
.step-dot.complete { background: var(--dim-light); }
.step-dot.active {
background: linear-gradient(135deg, var(--amethyst), var(--sapphire));
box-shadow: 0 0 8px rgba(59,130,246,0.4);
}
.close-btn {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: none;
cursor: pointer;
}
.pulse-heading {
font-family: var(--font-display);
font-size: 26px;
font-weight: 700;
color: var(--pure-light);
margin-bottom: var(--space-5);
letter-spacing: -0.01em;
animation: fadeIn 0.4s ease-out 0.1s both;
opacity: 0;
line-height: 1.2;
}
/* Observation list */
.observation-list {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.observation-item {
display: flex;
align-items: flex-start;
gap: var(--space-3);
animation: fadeIn 0.4s ease-out both;
opacity: 0;
}
.obs-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 5px;
}
.obs-dot.amethyst { background: var(--amethyst); box-shadow: 0 0 8px rgba(139,92,246,0.5); }
.obs-dot.amber { background: var(--amber); box-shadow: 0 0 8px rgba(245,158,11,0.5); }
.obs-dot.emerald { background: var(--emerald); box-shadow: 0 0 8px rgba(16,185,129,0.5); }
.obs-dot.sapphire { background: var(--sapphire); box-shadow: 0 0 8px rgba(59,130,246,0.5); }
.obs-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.6;
}
.obs-text strong {
color: var(--pure-light);
font-weight: 600;
}
/* Highlighted callout */
.callout-card {
background: rgba(245,158,11,0.06);
border: 1px solid rgba(245,158,11,0.2);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-5);
animation: fadeIn 0.4s ease-out 0.55s both;
opacity: 0;
}
.callout-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--amber-light);
margin-bottom: var(--space-2);
}
.callout-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.65;
}
.callout-text strong {
color: var(--amber-light);
}
.next-btn {
height: 52px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire));
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 24px rgba(59,130,246,0.2);
margin-top: auto;
transition: opacity 0.2s ease-out;
animation: fadeIn 0.4s ease-out 0.65s both;
opacity: 0;
}
.next-btn:hover { opacity: 0.88; }
</style>
</head>
<body>
<div class="device-frame">
<div class="bg-aura"></div>
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="pulse-frame">
<div class="step-indicator">
<span class="step-label">Step 2 of 3</span>
<div class="step-dots">
<div class="step-dot complete"></div>
<div class="step-dot active"></div>
<div class="step-dot"></div>
</div>
<a class="close-btn" href="../turn/10-turn-home.html">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M2 2L10 10M10 2L2 10" stroke="var(--dim-light)" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="pulse-heading">Here's what I noticed this week</div>
<div class="observation-list">
<!-- 14 Turns — canonical data -->
<div class="observation-item" style="animation-delay: 0.15s;">
<div class="obs-dot amethyst"></div>
<div class="obs-text"><strong>14 Turns</strong> this week — Wednesday was your most active day. That's the day of the presentation. You processed it in real time.</div>
</div>
<!-- Catastrophizing dropped from 5 last week — canonical -->
<div class="observation-item" style="animation-delay: 0.25s;">
<div class="obs-dot amber"></div>
<div class="obs-text">Catastrophizing appeared <strong>3 times</strong> — down from 5 last week. The pattern is still there, but it's losing frequency.</div>
</div>
<!-- 5 mirror sessions — canonical -->
<div class="observation-item" style="animation-delay: 0.35s;">
<div class="obs-dot emerald"></div>
<div class="obs-text"><strong>5 Mirror sessions</strong> and your streak hit <strong>14 days</strong> — the longest you've sustained consistent reflection.</div>
</div>
<!-- Goal evidence tiles — canonical: 2 new for "Present with confidence" -->
<div class="observation-item" style="animation-delay: 0.45s;">
<div class="obs-dot sapphire"></div>
<div class="obs-text"><strong>2 new evidence tiles</strong> added to "Present with confidence" — both from moments this week where you held your ground.</div>
</div>
</div>
<!-- Highlighted callout — feeling vs data gap -->
<div class="callout-card">
<div class="callout-label">Something to notice</div>
<div class="callout-text">
You rated this week as <strong>"Steady"</strong> — but the data shows real movement. Catastrophizing down, streak at an all-time high, goal evidence growing. Sometimes the feeling lags behind the evidence. The evidence is real.
</div>
</div>
<a class="next-btn" href="76-guide-pulse-focus.html">Next</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,419 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Weekly Pulse (Step 3)</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
@keyframes breathing {
0%, 100% { opacity: 0.5; transform: scale(1) translate(-50%, -50%); }
50% { opacity: 0.8; transform: scale(1.06) translate(-48%, -52%); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes prismaticFlash {
0% { opacity: 0; transform: scale(0.9); }
30% { opacity: 1; transform: scale(1.05); }
70% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(1.1); }
}
@keyframes completePulse {
0%, 100% { box-shadow: 0 0 20px rgba(16,185,129,0.2); }
50% { box-shadow: 0 0 40px rgba(16,185,129,0.4); }
}
.bg-aura {
position: absolute;
top: 35%;
left: 50%;
width: 260px;
height: 260px;
border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.1) 0%, transparent 70%);
filter: blur(55px);
animation: breathing 7s ease-in-out infinite;
pointer-events: none;
}
.pulse-frame {
display: flex;
flex-direction: column;
flex: 1;
padding: 0 var(--space-4) var(--space-6);
}
.step-indicator {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) 0 var(--space-2);
}
.step-label {
font-size: 12px;
font-weight: 600;
color: var(--dim-light);
}
.step-dots {
display: flex;
gap: 6px;
}
.step-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--twilight);
}
.step-dot.complete { background: var(--dim-light); }
.step-dot.active {
background: var(--emerald);
box-shadow: 0 0 8px rgba(16,185,129,0.5);
}
.close-btn {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: none;
cursor: pointer;
}
.pulse-heading {
font-family: var(--font-display);
font-size: 28px;
font-weight: 700;
color: var(--pure-light);
margin-bottom: var(--space-5);
letter-spacing: -0.01em;
animation: fadeIn 0.4s ease-out 0.1s both;
opacity: 0;
}
/* Focus suggestion — canonical next-week prompt -->
.focus-card {
background: var(--kalei-navy);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-4);
position: relative;
animation: fadeIn 0.4s ease-out 0.2s both;
opacity: 0;
}
.focus-card::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
z-index: -1;
}
.focus-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--amethyst-light);
margin-bottom: var(--space-2);
display: flex;
align-items: center;
gap: var(--space-2);
}
.focus-icon {
font-size: 12px;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald), var(--amber));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.focus-text {
font-size: 15px;
font-weight: 600;
color: var(--pure-light);
line-height: 1.5;
margin-bottom: var(--space-3);
}
.focus-body {
font-size: 13px;
color: var(--dim-light);
line-height: 1.6;
}
/* Suggestion cards */
.suggestion-card {
background: var(--kalei-navy);
border: 1px solid rgba(16,185,129,0.25);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
display: flex;
align-items: flex-start;
gap: var(--space-3);
animation: fadeIn 0.4s ease-out both;
opacity: 0;
transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out;
}
.suggestion-card:hover {
border-color: rgba(16,185,129,0.5);
box-shadow: 0 0 16px rgba(16,185,129,0.08);
}
.suggestion-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-md);
background: rgba(16,185,129,0.1);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.suggestion-content { flex: 1; }
.suggestion-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--emerald-light);
margin-bottom: 4px;
}
.suggestion-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.55;
}
/* Action buttons */
.pulse-actions {
display: flex;
gap: var(--space-2);
margin-top: auto;
animation: fadeIn 0.4s ease-out 0.6s both;
opacity: 0;
}
.sounds-good-btn {
flex: 1;
height: 52px;
background: var(--emerald);
color: var(--pure-light);
font-size: 15px;
font-weight: 600;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 20px rgba(16,185,129,0.25);
cursor: pointer;
border: none;
font-family: var(--font-primary);
transition: background 0.2s ease-out;
}
.sounds-good-btn:hover { background: var(--emerald-light); }
.adjust-btn {
flex: 1;
height: 52px;
background: transparent;
color: var(--dim-light);
font-size: 15px;
font-weight: 500;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: 1px solid var(--twilight);
transition: color 0.2s ease-out, border-color 0.2s ease-out;
}
.adjust-btn:hover { color: var(--soft-light); border-color: rgba(255,255,255,0.15); }
/* Completion state */
.completion-state {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
text-align: center;
}
.completion-state.visible { display: flex; }
.completion-diamond {
margin-bottom: var(--space-6);
animation: completePulse 3s ease-in-out infinite;
}
.completion-heading {
font-family: var(--font-display);
font-size: 24px;
font-weight: 700;
color: var(--pure-light);
margin-bottom: var(--space-3);
}
.completion-subtext {
font-size: 15px;
color: var(--dim-light);
margin-bottom: var(--space-8);
}
/* Prismatic flash overlay */
.prismatic-flash {
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(139,92,246,0.15), rgba(59,130,246,0.15), rgba(16,185,129,0.15), rgba(245,158,11,0.1));
border-radius: 44px;
pointer-events: none;
display: none;
}
.prismatic-flash.animate {
display: block;
animation: prismaticFlash 0.8s ease-out forwards;
}
.done-btn {
height: 52px;
width: 100%;
background: linear-gradient(135deg, var(--amethyst), var(--sapphire), var(--emerald));
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 0 24px rgba(16,185,129,0.2);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="bg-aura"></div>
<div class="prismatic-flash" id="flash"></div>
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<!-- Step 3 active content -->
<div class="pulse-frame" id="step3Content">
<div class="step-indicator">
<span class="step-label">Step 3 of 3</span>
<div class="step-dots">
<div class="step-dot complete"></div>
<div class="step-dot complete"></div>
<div class="step-dot active"></div>
</div>
<a class="close-btn" href="../turn/10-turn-home.html">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M2 2L10 10M10 2L2 10" stroke="var(--dim-light)" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="pulse-heading">For next week</div>
<!-- Canonical weekly focus suggestion -->
<div class="focus-card">
<div class="focus-label">
<span class="focus-icon"></span>
Your focus
</div>
<div class="focus-text">"Notice the gap between what you fear and what actually happens."</div>
<div class="focus-body">This week your data showed Catastrophizing going down. That gap — between the feared outcome and reality — is where your patterns lose their grip. Try to catch it once a day.</div>
</div>
<!-- Suggestion cards — Alex's actual goals, not fitness -->
<div class="suggestion-card" style="animation-delay: 0.3s;">
<div class="suggestion-icon">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<circle cx="10" cy="10" r="8" stroke="var(--emerald)" stroke-width="1.2"/>
<circle cx="10" cy="10" r="3.5" stroke="var(--emerald)" stroke-width="0.8"/>
<path d="M10 2v2M10 16v2M2 10h2M16 10h2" stroke="var(--emerald)" stroke-width="0.8" stroke-linecap="round"/>
</svg>
</div>
<div class="suggestion-content">
<div class="suggestion-label">Lens · Present with confidence</div>
<div class="suggestion-text">Add one more evidence tile — even a micro-moment of holding your ground counts. You're at 65% and moving.</div>
</div>
</div>
<div class="suggestion-card" style="animation-delay: 0.4s;">
<div class="suggestion-icon">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M10 3L14 7L15 12L10 17L5 12L6 7Z" stroke="var(--amber)" fill="none" stroke-width="1.2"/>
</svg>
</div>
<div class="suggestion-content">
<div class="suggestion-label">Mirror · Streak</div>
<div class="suggestion-text">Your 14-day streak is the longest you've held. Next week it becomes 21. Just keep showing up.</div>
</div>
</div>
<div class="suggestion-card" style="animation-delay: 0.5s;">
<div class="suggestion-icon">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M10 2L18 10L10 18L2 10Z" stroke="var(--amethyst)" fill="none" stroke-width="1.2"/>
<path d="M10 2L18 10L10 10Z" fill="rgba(139,92,246,0.15)"/>
</svg>
</div>
<div class="suggestion-content">
<div class="suggestion-label">Turn · Boundaries goal</div>
<div class="suggestion-text">Your "Set boundaries at work" goal is at 40%. If a work request lands that doesn't feel right, Turn it before you respond.</div>
</div>
</div>
<div class="pulse-actions">
<button class="sounds-good-btn" id="soundsGoodBtn">Sounds good</button>
<a class="adjust-btn" href="74-guide-pulse-report.html">Adjust</a>
</div>
</div>
<!-- Completion state (hidden initially) -->
<div class="pulse-frame completion-state" id="completionState">
<div class="completion-diamond">
<svg width="80" height="80" viewBox="0 0 80 80">
<defs>
<linearGradient id="cp-gr" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="33%" stop-color="#34D399"/>
<stop offset="66%" stop-color="#60A5FA"/>
<stop offset="100%" stop-color="#FCD34D"/>
</linearGradient>
<filter id="cp-glow" x="-25%" y="-25%" width="150%" height="150%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g filter="url(#cp-glow)">
<path d="M40 4L76 40L40 76L4 40Z" fill="url(#cp-gr)" opacity="0.85"/>
<path d="M40 4L76 40L40 40Z" fill="#fff" opacity="0.2"/>
<path d="M40 4L4 40L40 40Z" fill="#fff" opacity="0.1"/>
<line x1="40" y1="4" x2="40" y2="76" stroke="#fff" stroke-width="0.8" opacity="0.15"/>
<line x1="4" y1="40" x2="76" y2="40" stroke="#fff" stroke-width="0.8" opacity="0.1"/>
<circle cx="32" cy="28" r="4" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="completion-heading">Pulse complete.</div>
<div class="completion-subtext">See you next Saturday, Alex.</div>
<a class="done-btn" href="../turn/10-turn-home.html">Done</a>
</div>
</div>
<script>
document.getElementById('soundsGoodBtn').addEventListener('click', function() {
const flash = document.getElementById('flash');
const step3 = document.getElementById('step3Content');
const completion = document.getElementById('completionState');
// Trigger prismatic flash
flash.classList.add('animate');
setTimeout(() => {
step3.style.display = 'none';
completion.classList.add('visible');
flash.classList.remove('animate');
}, 600);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,373 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Lens Dashboard</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-aura {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
width: 280px;
height: 280px;
border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.12) 0%, transparent 70%);
filter: blur(50px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.lens-heading {
color: var(--emerald-light);
margin-bottom: 4px;
}
.goal-card {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
display: flex;
gap: var(--space-3);
align-items: center;
cursor: pointer;
text-decoration: none;
transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out;
}
.goal-card:hover { border-color: rgba(16,185,129,0.4); box-shadow: 0 0 20px rgba(16,185,129,0.08); }
.goal-ring-wrap {
flex-shrink: 0;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
}
.goal-ring-wrap svg { transform: rotate(-90deg); position: absolute; }
.goal-ring-pct {
font-size: 14px;
font-weight: 700;
color: var(--pure-light);
position: relative;
z-index: 1;
}
.goal-info { flex: 1; min-width: 0; }
.goal-title {
font-size: 15px;
font-weight: 600;
color: var(--pure-light);
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.goal-next {
font-size: 12px;
color: var(--dim-light);
margin-bottom: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.days-chip {
display: inline-flex;
align-items: center;
gap: 4px;
background: rgba(16,185,129,0.1);
color: var(--emerald-light);
border-radius: var(--radius-full);
padding: 2px 10px;
font-size: 11px;
font-weight: 500;
}
.affirmation-card {
background: var(--kalei-navy);
border: 1px solid rgba(16,185,129,0.25);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-3);
cursor: pointer;
text-decoration: none;
display: block;
box-shadow: 0 0 16px rgba(16,185,129,0.08);
transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out;
}
.affirmation-card:hover { border-color: rgba(16,185,129,0.5); box-shadow: 0 0 24px rgba(16,185,129,0.15); }
.affirmation-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--emerald-light);
margin-bottom: 6px;
}
.affirmation-text {
font-size: 15px;
line-height: 1.5;
color: var(--soft-light);
font-style: italic;
}
.rehearsal-row {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
margin-bottom: var(--space-2);
cursor: pointer;
text-decoration: none;
transition: border-color 0.2s ease-out, background 0.2s ease-out;
}
.rehearsal-row:hover { border-color: rgba(16,185,129,0.3); background: var(--twilight); }
.rehearsal-icon {
width: 36px;
height: 36px;
border-radius: var(--radius-md);
background: rgba(16,185,129,0.1);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.rehearsal-info { flex: 1; }
.rehearsal-title {
font-size: 14px;
font-weight: 500;
color: var(--soft-light);
}
.rehearsal-meta {
font-size: 12px;
color: var(--dim-light);
}
.add-goal-btn {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
height: 52px;
width: 100%;
background: var(--emerald);
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald);
text-decoration: none;
margin-bottom: var(--space-4);
transition: background 0.2s;
}
.add-goal-btn:hover { background: var(--emerald-light); }
.screen-content { padding-bottom: var(--space-4); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<div class="screen-content" style="padding-top: var(--space-4);">
<div class="screen-aura"></div>
<div style="position: relative; z-index: 1;">
<div class="heading lens-heading">Your Lens</div>
<div class="body-sm text-dim" style="margin-bottom: var(--space-5);">Track your goals, rehearse success</div>
<!-- Goal Cards -->
<!-- Progress rings: r=27, circumference=169.6; rotate(-90deg) on svg via CSS -->
<!-- Goal 1: Present with confidence — emerald — 65% -->
<a class="goal-card" href="27-lens-goal-detail.html">
<div class="goal-ring-wrap">
<svg width="64" height="64" viewBox="0 0 64 64">
<defs>
<linearGradient id="pr20-grEm1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
</defs>
<circle cx="32" cy="32" r="27" fill="none" stroke="var(--twilight)" stroke-width="3.5"/>
<!-- 65% = 169.6 × 0.65 = 110.2 filled, 59.4 gap -->
<circle cx="32" cy="32" r="27" fill="none" stroke="url(#pr20-grEm1)" stroke-width="3.5"
stroke-dasharray="110.2 59.4" stroke-linecap="round"/>
</svg>
<span class="goal-ring-pct">65%</span>
</div>
<div class="goal-info">
<div class="goal-title">Present with confidence</div>
<div class="goal-next">Next: Rehearse quarterly results presentation</div>
<span class="days-chip">
<svg width="10" height="10" viewBox="0 0 10 10"><circle cx="5" cy="5" r="4" stroke="currentColor" fill="none" stroke-width="1"/><path d="M5 3v2l1.5 1" stroke="currentColor" stroke-width="0.8" stroke-linecap="round"/></svg>
Started Feb 5
</span>
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: var(--faint-light); flex-shrink:0;">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<!-- Goal 2: Set boundaries at work — sapphire — 40% -->
<a class="goal-card" href="27-lens-goal-detail.html" style="border-color: rgba(59,130,246,0.2);">
<div class="goal-ring-wrap">
<svg width="64" height="64" viewBox="0 0 64 64">
<defs>
<linearGradient id="pr20-grSa" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/>
<stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
</defs>
<circle cx="32" cy="32" r="27" fill="none" stroke="var(--twilight)" stroke-width="3.5"/>
<!-- 40% = 169.6 × 0.40 = 67.8 filled, 101.8 gap -->
<circle cx="32" cy="32" r="27" fill="none" stroke="url(#pr20-grSa)" stroke-width="3.5"
stroke-dasharray="67.8 101.8" stroke-linecap="round"/>
</svg>
<span class="goal-ring-pct" style="color: var(--sapphire-light);">40%</span>
</div>
<div class="goal-info">
<div class="goal-title">Set boundaries at work</div>
<div class="goal-next">Next: Practice declining the Thursday all-hands</div>
<span class="days-chip" style="background: rgba(59,130,246,0.1); color: var(--sapphire-light);">
<svg width="10" height="10" viewBox="0 0 10 10"><circle cx="5" cy="5" r="4" stroke="currentColor" fill="none" stroke-width="1"/><path d="M5 3v2l1.5 1" stroke="currentColor" stroke-width="0.8" stroke-linecap="round"/></svg>
Started Feb 10
</span>
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: var(--faint-light); flex-shrink:0;">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<!-- Goal 3: Practice self-compassion — amethyst — 25% -->
<a class="goal-card" href="27-lens-goal-detail.html" style="border-color: rgba(139,92,246,0.2);">
<div class="goal-ring-wrap">
<svg width="64" height="64" viewBox="0 0 64 64">
<defs>
<linearGradient id="pr20-grAm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#6D28D9"/>
</linearGradient>
</defs>
<circle cx="32" cy="32" r="27" fill="none" stroke="var(--twilight)" stroke-width="3.5"/>
<!-- 25% = 169.6 × 0.25 = 42.4 filled, 127.2 gap -->
<circle cx="32" cy="32" r="27" fill="none" stroke="url(#pr20-grAm)" stroke-width="3.5"
stroke-dasharray="42.4 127.2" stroke-linecap="round"/>
</svg>
<span class="goal-ring-pct" style="color: var(--amethyst-light);">25%</span>
</div>
<div class="goal-info">
<div class="goal-title">Practice self-compassion</div>
<div class="goal-next">Next: Write a letter to yourself after today</div>
<span class="days-chip" style="background: rgba(139,92,246,0.1); color: var(--amethyst-light);">
<svg width="10" height="10" viewBox="0 0 10 10"><circle cx="5" cy="5" r="4" stroke="currentColor" fill="none" stroke-width="1"/><path d="M5 3v2l1.5 1" stroke="currentColor" stroke-width="0.8" stroke-linecap="round"/></svg>
Started Feb 15
</span>
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: var(--faint-light); flex-shrink:0;">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<a class="add-goal-btn" href="21-lens-create-step1.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M10 4v12M4 10h12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
Add New Goal
</a>
<!-- Daily Affirmation -->
<div class="section-header" style="margin-bottom: var(--space-2);">
<span class="section-title">Daily Affirmation</span>
</div>
<a class="affirmation-card" href="28-lens-affirmation.html">
<div class="affirmation-label">Today's Affirmation</div>
<div class="affirmation-text">"I have the strength and clarity to take one step forward, even when the path isn't fully visible yet."</div>
<div class="body-sm text-emerald" style="margin-top: var(--space-2); font-weight: 500;">Tap to reflect →</div>
</a>
<!-- Rehearsals -->
<div class="section-header" style="margin-top: var(--space-1); margin-bottom: var(--space-2);">
<span class="section-title">Rehearsals</span>
<span class="section-action" style="color: var(--emerald-light);">See all</span>
</div>
<a class="rehearsal-row" href="29-rehearsal-session.html">
<div class="rehearsal-icon">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<circle cx="9" cy="9" r="7" stroke="var(--emerald)" stroke-width="1.2"/>
<circle cx="9" cy="9" r="3" stroke="var(--emerald)" stroke-width="0.8"/>
<path d="M9 2v2M9 14v2M2 9h2M14 9h2" stroke="var(--emerald)" stroke-width="0.8" stroke-linecap="round"/>
</svg>
</div>
<div class="rehearsal-info">
<div class="rehearsal-title">Quarterly Results Presentation</div>
<div class="rehearsal-meta">Scheduled today · 5 min</div>
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: var(--faint-light);">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<a class="rehearsal-row" href="29-rehearsal-session.html">
<div class="rehearsal-icon">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<circle cx="9" cy="9" r="7" stroke="var(--emerald)" stroke-width="1.2"/>
<circle cx="9" cy="9" r="3" stroke="var(--emerald)" stroke-width="0.8"/>
<path d="M9 2v2M9 14v2M2 9h2M14 9h2" stroke="var(--emerald)" stroke-width="0.8" stroke-linecap="round"/>
</svg>
</div>
<div class="rehearsal-info">
<div class="rehearsal-title">Boundary Conversation Practice</div>
<div class="rehearsal-meta">Yesterday · Completed</div>
</div>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="color: var(--emerald); flex-shrink:0;">
<path d="M2 7l3.5 3.5L12 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
</div>
</div>
<!-- Tab Bar -->
<div class="tab-bar">
<a class="tab-item inactive" data-tab="turn" href="../turn/10-turn-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 2L20 12L12 22L4 12Z" fill="currentColor" opacity="0.9"/><path d="M12 2L20 12L12 12Z" fill="white" opacity="0.15"/></svg>
<span class="tab-label">Turn</span>
</a>
<a class="tab-item inactive" data-tab="mirror" href="../mirror/15-mirror-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 3L16 8L18 14L12 19L6 14L8 8Z" stroke="currentColor" fill="none" stroke-width="1.2"/></svg>
<span class="tab-label">Mirror</span>
</a>
<a class="tab-item active" data-tab="lens" href="20-lens-dashboard.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke="currentColor" fill="none" stroke-width="1.2"/><circle cx="12" cy="12" r="4" stroke="currentColor" fill="none" stroke-width="0.8"/></svg>
<span class="tab-label">Lens</span>
</a>
<a class="tab-item inactive" data-tab="gallery" href="../gallery/31-gallery-all.html">
<svg width="24" height="24" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/></svg>
<span class="tab-label">Gallery</span>
</a>
<a class="tab-item inactive" data-tab="you" href="../you/35-you-profile.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4" stroke="currentColor" fill="none" stroke-width="1.2"/><path d="M4 20C4 16 8 14 12 14C16 14 20 16 20 20" stroke="currentColor" fill="none" stroke-width="1.2" stroke-linecap="round"/></svg>
<span class="tab-label">You</span>
</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — New Goal Step 1</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.step-dot.active { background: var(--emerald); box-shadow: 0 0 8px rgba(16,185,129,0.4); }
.step-dot.completed { background: var(--emerald); opacity: 0.5; }
.input-field:focus { border-color: var(--emerald); box-shadow: 0 0 0 3px rgba(16,185,129,0.1); }
.step-question {
font-size: 22px;
font-weight: 600;
color: var(--pure-light);
line-height: 1.3;
margin-bottom: var(--space-3);
}
.step-hint {
font-size: 14px;
color: var(--dim-light);
line-height: 1.5;
margin-bottom: var(--space-6);
}
.nav-back { color: var(--emerald-light) !important; }
.example-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 16px;
}
.example-chip {
background: rgba(16,185,129,0.08);
border: 1px solid rgba(16,185,129,0.2);
border-radius: var(--radius-full);
padding: 6px 14px;
font-size: 13px;
color: var(--emerald-light);
cursor: pointer;
transition: background 0.15s;
}
.example-chip:hover { background: rgba(16,185,129,0.15); }
.bottom-actions {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 70%, transparent);
}
.btn-next-emerald {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
height: 52px;
width: 100%;
background: var(--emerald);
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald);
text-decoration: none;
transition: background 0.2s;
}
.btn-next-emerald:hover { background: var(--emerald-light); }
.screen-content { padding-bottom: 120px; }
.aura-small {
position: absolute;
top: -60px;
right: -60px;
width: 200px;
height: 200px;
border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.1) 0%, transparent 70%);
filter: blur(40px);
pointer-events: none;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="20-lens-dashboard.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">New Goal</span>
<span class="nav-action body-sm text-dim">1 of 6</span>
</div>
<div class="step-progress">
<div class="step-dot active"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
</div>
<div class="screen-content" style="padding-top: var(--space-6); position: relative;">
<div class="aura-small"></div>
<!-- Emerald circle icon -->
<div style="margin-bottom: var(--space-5);">
<svg width="48" height="48" viewBox="0 0 48 48">
<defs>
<linearGradient id="grEm1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
</defs>
<circle cx="24" cy="24" r="22" fill="none" stroke="url(#grEm1)" stroke-width="2" opacity="0.6"/>
<circle cx="24" cy="24" r="10" fill="none" stroke="url(#grEm1)" stroke-width="1.5"/>
<circle cx="24" cy="24" r="4" fill="url(#grEm1)" opacity="0.8"/>
</svg>
</div>
<div class="step-question">What do you want to achieve?</div>
<div class="step-hint">Be specific and ambitious. The clearer your goal, the more effectively you can rehearse achieving it.</div>
<input type="text" class="input-field" placeholder="e.g. Present with confidence" id="goalInput" style="font-size: 17px; height: 56px;">
<div class="example-chips">
<div class="example-chip" onclick="setGoal(this)">Present with confidence</div>
<div class="example-chip" onclick="setGoal(this)">Set boundaries at work</div>
<div class="example-chip" onclick="setGoal(this)">Practice self-compassion</div>
<div class="example-chip" onclick="setGoal(this)">Ask for a promotion</div>
<div class="example-chip" onclick="setGoal(this)">Build a daily habit</div>
</div>
</div>
<div class="bottom-actions">
<a class="btn-next-emerald" href="22-lens-create-step2.html">
Next
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M6 4l5 5-5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
</div>
</div>
<script>
function setGoal(el) {
document.getElementById('goalInput').value = el.textContent;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — New Goal Step 2</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.step-dot.active { background: var(--emerald); box-shadow: 0 0 8px rgba(16,185,129,0.4); }
.step-dot.completed { background: var(--emerald); opacity: 0.5; }
.step-question { font-size: 22px; font-weight: 600; color: var(--pure-light); line-height: 1.3; margin-bottom: var(--space-3); }
.step-hint { font-size: 14px; color: var(--dim-light); line-height: 1.5; margin-bottom: var(--space-5); }
.nav-back { color: var(--emerald-light) !important; }
.textarea-field:focus { border-color: var(--emerald); box-shadow: 0 0 0 3px rgba(16,185,129,0.1); }
.bottom-actions {
position: absolute; bottom: 0; left: 0; right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 70%, transparent);
display: flex; gap: var(--space-3);
}
.btn-back-ghost {
display: flex; align-items: center; justify-content: center;
height: 52px; width: 52px;
background: var(--deep-glass); color: var(--dim-light);
border: 1px solid var(--twilight); border-radius: var(--radius-lg);
text-decoration: none; flex-shrink: 0; transition: background 0.2s;
}
.btn-back-ghost:hover { background: var(--twilight); }
.btn-next-emerald {
display: flex; align-items: center; justify-content: center; gap: var(--space-2);
height: 52px; flex: 1; background: var(--emerald); color: var(--pure-light);
font-size: 16px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald); text-decoration: none; transition: background 0.2s;
}
.btn-next-emerald:hover { background: var(--emerald-light); }
.screen-content { padding-bottom: 120px; }
.why-prompts {
display: flex; flex-direction: column; gap: var(--space-2);
margin-top: var(--space-4);
}
.why-prompt {
background: var(--deep-glass); border: 1px solid var(--twilight);
border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
font-size: 14px; color: var(--dim-light); cursor: pointer;
transition: all 0.15s;
}
.why-prompt:hover { border-color: rgba(16,185,129,0.3); color: var(--soft-light); }
.goal-chip {
background: rgba(16,185,129,0.08); border: 1px solid rgba(16,185,129,0.2);
border-radius: var(--radius-full); padding: 6px 14px; font-size: 13px;
color: var(--emerald-light); display: inline-flex; align-items: center; gap: 6px;
margin-bottom: var(--space-5);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="21-lens-create-step1.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">New Goal</span>
<span class="nav-action body-sm text-dim">2 of 6</span>
</div>
<div class="step-progress">
<div class="step-dot completed"></div>
<div class="step-dot active"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
</div>
<div class="screen-content" style="padding-top: var(--space-6);">
<div class="goal-chip">
<svg width="12" height="12" viewBox="0 0 12 12"><circle cx="6" cy="6" r="5" stroke="var(--emerald)" fill="none" stroke-width="1"/><circle cx="6" cy="6" r="2" fill="var(--emerald)"/></svg>
Present with confidence
</div>
<div class="step-question">Why does this matter to you?</div>
<div class="step-hint">Understanding your deeper motivation keeps you going when things get hard. Be honest with yourself.</div>
<textarea class="textarea-field" placeholder="This matters to me because..." style="min-height: 140px; font-size: 16px;"></textarea>
<div class="why-prompts">
<div class="why-prompt" onclick="appendText(this)">I want to stop letting fear stop me from sharing ideas I believe in</div>
<div class="why-prompt" onclick="appendText(this)">Being visible at work matters for my career growth</div>
<div class="why-prompt" onclick="appendText(this)">I want to feel capable and not undermine myself</div>
</div>
</div>
<div class="bottom-actions">
<a class="btn-back-ghost" href="21-lens-create-step1.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<a class="btn-next-emerald" href="23-lens-create-step3.html">
Next
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M6 4l5 5-5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
</div>
</div>
<script>
function appendText(el) {
const ta = document.querySelector('.textarea-field');
ta.value = el.textContent;
ta.focus();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,179 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — New Goal Step 3</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.step-dot.active { background: var(--emerald); box-shadow: 0 0 8px rgba(16,185,129,0.4); }
.step-dot.completed { background: var(--emerald); opacity: 0.5; }
.step-question { font-size: 22px; font-weight: 600; color: var(--pure-light); line-height: 1.3; margin-bottom: var(--space-3); }
.step-hint { font-size: 14px; color: var(--dim-light); line-height: 1.5; margin-bottom: var(--space-5); }
.nav-back { color: var(--emerald-light) !important; }
.obstacle-input-row {
display: flex; gap: var(--space-2); margin-bottom: var(--space-4);
}
.obstacle-input {
flex: 1; height: 48px; background: var(--deep-glass);
border: 1px solid var(--twilight); border-radius: var(--radius-md);
padding: 0 14px; font-size: 15px; color: var(--pure-light);
outline: none; transition: border-color 0.2s; font-family: var(--font-primary);
}
.obstacle-input::placeholder { color: var(--faint-light); }
.obstacle-input:focus { border-color: var(--emerald); }
.add-btn {
width: 48px; height: 48px; background: var(--emerald);
border: none; border-radius: var(--radius-md); cursor: pointer;
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
transition: background 0.2s;
}
.add-btn:hover { background: var(--emerald-light); }
.obstacle-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.obstacle-chip {
display: inline-flex; align-items: center; gap: 6px;
background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.25);
border-radius: var(--radius-full); padding: 7px 14px;
font-size: 13px; font-weight: 500; color: var(--emerald-light);
}
.obstacle-chip-remove {
width: 16px; height: 16px; border-radius: 50%;
background: rgba(16,185,129,0.2); border: none; cursor: pointer;
display: flex; align-items: center; justify-content: center; color: var(--emerald);
font-size: 10px; font-weight: 700; transition: background 0.15s;
}
.obstacle-chip-remove:hover { background: rgba(16,185,129,0.4); }
.suggestions-label { font-size: 12px; color: var(--dim-light); margin: 16px 0 8px; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 500; }
.suggestion-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.suggestion-chip {
background: var(--deep-glass); border: 1px dashed var(--twilight);
border-radius: var(--radius-full); padding: 6px 12px; font-size: 12px;
color: var(--dim-light); cursor: pointer; transition: all 0.15s;
}
.suggestion-chip:hover { border-color: rgba(16,185,129,0.3); color: var(--emerald-light); }
.bottom-actions {
position: absolute; bottom: 0; left: 0; right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 70%, transparent);
display: flex; gap: var(--space-3);
}
.btn-back-ghost {
display: flex; align-items: center; justify-content: center;
height: 52px; width: 52px; background: var(--deep-glass);
color: var(--dim-light); border: 1px solid var(--twilight);
border-radius: var(--radius-lg); text-decoration: none; flex-shrink: 0;
transition: background 0.2s ease-out;
}
.btn-back-ghost:hover { background: var(--twilight); }
.btn-next-emerald {
display: flex; align-items: center; justify-content: center; gap: var(--space-2);
height: 52px; flex: 1; background: var(--emerald); color: var(--pure-light);
font-size: 16px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald); text-decoration: none; transition: background 0.2s ease-out;
}
.btn-next-emerald:hover { background: var(--emerald-light); }
.screen-content { padding-bottom: 120px; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="22-lens-create-step2.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">New Goal</span>
<span class="nav-action body-sm text-dim">3 of 6</span>
</div>
<div class="step-progress">
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot active"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
</div>
<div class="screen-content" style="padding-top: var(--space-6);">
<div class="step-question">What might get in the way?</div>
<div class="step-hint">Anticipating obstacles makes you 2-3x more likely to follow through. Name the real barriers.</div>
<div class="obstacle-input-row">
<input type="text" class="obstacle-input" placeholder="Add an obstacle..." id="obstacleInput">
<button class="add-btn" onclick="addObstacle()">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 4v12M4 10h12" stroke="white" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
<div class="obstacle-chips" id="obstacleChips">
<span class="obstacle-chip">
Fear of judgment
<button class="obstacle-chip-remove" onclick="removeChip(this)">x</button>
</span>
<span class="obstacle-chip">
Losing my train of thought
<button class="obstacle-chip-remove" onclick="removeChip(this)">x</button>
</span>
<span class="obstacle-chip">
Voice shaking under pressure
<button class="obstacle-chip-remove" onclick="removeChip(this)">x</button>
</span>
</div>
<div class="suggestions-label">Common obstacles</div>
<div class="suggestion-chips">
<span class="suggestion-chip" onclick="addSuggestion(this)">Catastrophizing before the talk</span>
<span class="suggestion-chip" onclick="addSuggestion(this)">Mind going blank</span>
<span class="suggestion-chip" onclick="addSuggestion(this)">Manager in the room</span>
<span class="suggestion-chip" onclick="addSuggestion(this)">Technical difficulties</span>
<span class="suggestion-chip" onclick="addSuggestion(this)">Not enough preparation time</span>
<span class="suggestion-chip" onclick="addSuggestion(this)">Imposter syndrome</span>
</div>
</div>
<div class="bottom-actions">
<a class="btn-back-ghost" href="22-lens-create-step2.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<a class="btn-next-emerald" href="24-lens-create-step4.html">
Next
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M6 4l5 5-5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
</div>
</div>
<script>
function addObstacle() {
const input = document.getElementById('obstacleInput');
if (!input.value.trim()) return;
addChip(input.value.trim());
input.value = '';
}
function addSuggestion(el) {
addChip(el.textContent);
}
function addChip(text) {
const chips = document.getElementById('obstacleChips');
const chip = document.createElement('span');
chip.className = 'obstacle-chip';
chip.innerHTML = `${text}<button class="obstacle-chip-remove" onclick="removeChip(this)">x</button>`;
chips.appendChild(chip);
}
function removeChip(btn) {
btn.parentElement.remove();
}
document.getElementById('obstacleInput').addEventListener('keydown', e => {
if (e.key === 'Enter') addObstacle();
});
</script>
</body>
</html>

View File

@@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — New Goal Step 4</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.step-dot.active { background: var(--emerald); box-shadow: 0 0 8px rgba(16,185,129,0.4); }
.step-dot.completed { background: var(--emerald); opacity: 0.5; }
.step-question { font-size: 22px; font-weight: 600; color: var(--pure-light); line-height: 1.3; margin-bottom: var(--space-2); }
.step-hint { font-size: 14px; color: var(--dim-light); line-height: 1.5; margin-bottom: var(--space-5); }
.nav-back { color: var(--emerald-light) !important; }
.ifthen-card {
background: var(--kalei-navy); border: 1px solid var(--twilight);
border-radius: var(--radius-xl); padding: var(--space-4); margin-bottom: var(--space-3);
}
.obstacle-label {
font-size: 11px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--dim-light); margin-bottom: 8px;
}
.obstacle-name {
font-size: 14px; font-weight: 500; color: var(--emerald-light);
background: rgba(16,185,129,0.08); border-radius: var(--radius-md);
padding: 6px 12px; margin-bottom: var(--space-3); display: inline-block;
}
.ifthen-row {
display: flex; align-items: flex-start; gap: 10px; margin-bottom: var(--space-2);
}
.ifthen-keyword {
font-size: 12px; font-weight: 700; color: var(--emerald); text-transform: uppercase;
letter-spacing: 0.06em; padding-top: 14px; flex-shrink: 0; width: 30px;
}
.ifthen-input {
flex: 1; min-height: 44px; background: var(--deep-glass);
border: 1px solid var(--twilight); border-radius: var(--radius-md);
padding: 10px 12px; font-size: 14px; color: var(--pure-light);
outline: none; resize: none; font-family: var(--font-primary);
transition: border-color 0.2s; line-height: 1.4;
}
.ifthen-input::placeholder { color: var(--faint-light); }
.ifthen-input:focus { border-color: var(--emerald); }
.bottom-actions {
position: absolute; bottom: 0; left: 0; right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 70%, transparent);
display: flex; gap: var(--space-3);
}
.btn-back-ghost {
display: flex; align-items: center; justify-content: center;
height: 52px; width: 52px; background: var(--deep-glass);
color: var(--dim-light); border: 1px solid var(--twilight);
border-radius: var(--radius-lg); text-decoration: none; flex-shrink: 0;
transition: background 0.2s ease-out;
}
.btn-back-ghost:hover { background: var(--twilight); }
.btn-next-emerald {
display: flex; align-items: center; justify-content: center; gap: var(--space-2);
height: 52px; flex: 1; background: var(--emerald); color: var(--pure-light);
font-size: 16px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald); text-decoration: none; transition: background 0.2s ease-out;
}
.btn-next-emerald:hover { background: var(--emerald-light); }
.screen-content { padding-bottom: 120px; }
.info-box {
background: rgba(16,185,129,0.05); border: 1px solid rgba(16,185,129,0.15);
border-radius: var(--radius-md); padding: var(--space-3);
font-size: 13px; color: var(--dim-light); line-height: 1.5;
margin-bottom: var(--space-4);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="23-lens-create-step3.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">New Goal</span>
<span class="nav-action body-sm text-dim">4 of 6</span>
</div>
<div class="step-progress">
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot active"></div>
<div class="step-dot"></div>
<div class="step-dot"></div>
</div>
<div class="screen-content" style="padding-top: var(--space-5);">
<div class="step-question">Plan your responses</div>
<div class="step-hint">If-then plans triple your success rate. Write exactly what you'll do when each obstacle shows up.</div>
<div class="info-box">
Research shows that "If X happens, then I will do Y" plans are 2-3x more effective than general intentions.
</div>
<div class="ifthen-card">
<div class="obstacle-label">Obstacle</div>
<div class="obstacle-name">Voice shaking under pressure</div>
<div class="ifthen-row">
<div class="ifthen-keyword">If</div>
<textarea class="ifthen-input" rows="2">I feel my voice starting to shake while presenting,</textarea>
</div>
<div class="ifthen-row">
<div class="ifthen-keyword">Then</div>
<textarea class="ifthen-input" rows="2" placeholder="I will...">I will pause, take one slow breath, and remind myself: I know this material.</textarea>
</div>
</div>
<div class="ifthen-card">
<div class="obstacle-label">Obstacle</div>
<div class="obstacle-name">Losing my train of thought</div>
<div class="ifthen-row">
<div class="ifthen-keyword">If</div>
<textarea class="ifthen-input" rows="2">I lose my place or blank on a slide,</textarea>
</div>
<div class="ifthen-row">
<div class="ifthen-keyword">Then</div>
<textarea class="ifthen-input" rows="2" placeholder="I will...">I will say "Let me back up a moment" — calmly, with no apology — and continue.</textarea>
</div>
</div>
<div class="ifthen-card">
<div class="obstacle-label">Obstacle</div>
<div class="obstacle-name">Fear of judgment</div>
<div class="ifthen-row">
<div class="ifthen-keyword">If</div>
<textarea class="ifthen-input" rows="2">I start to spiral about what my manager thinks,</textarea>
</div>
<div class="ifthen-row">
<div class="ifthen-keyword">Then</div>
<textarea class="ifthen-input" rows="2" placeholder="I will...">I will focus on one friendly face in the room and speak directly to them.</textarea>
</div>
</div>
</div>
<div class="bottom-actions">
<a class="btn-back-ghost" href="23-lens-create-step3.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<a class="btn-next-emerald" href="25-lens-create-step5.html">
Next
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M6 4l5 5-5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — New Goal Step 5</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.step-dot.active { background: var(--emerald); box-shadow: 0 0 8px rgba(16,185,129,0.4); }
.step-dot.completed { background: var(--emerald); opacity: 0.5; }
.step-question { font-size: 22px; font-weight: 600; color: var(--pure-light); line-height: 1.3; margin-bottom: var(--space-3); }
.step-hint { font-size: 14px; color: var(--dim-light); line-height: 1.5; margin-bottom: var(--space-5); }
.nav-back { color: var(--emerald-light) !important; }
.milestone-item {
background: var(--kalei-navy); border: 1px solid var(--twilight);
border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-3); display: flex; align-items: center; gap: var(--space-3);
}
.milestone-number {
width: 28px; height: 28px; border-radius: 50%;
background: rgba(16,185,129,0.1); border: 1.5px solid rgba(16,185,129,0.3);
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 700; color: var(--emerald); flex-shrink: 0;
}
.milestone-inputs { flex: 1; display: flex; flex-direction: column; gap: 8px; }
.milestone-text-input {
width: 100%; background: var(--deep-glass); border: 1px solid var(--twilight);
border-radius: var(--radius-md); padding: 8px 12px; font-size: 14px;
color: var(--pure-light); outline: none; font-family: var(--font-primary);
transition: border-color 0.2s;
}
.milestone-text-input::placeholder { color: var(--faint-light); }
.milestone-text-input:focus { border-color: var(--emerald); }
.milestone-date-input {
width: 100%; background: var(--deep-glass); border: 1px solid var(--twilight);
border-radius: var(--radius-md); padding: 8px 12px; font-size: 13px;
color: var(--dim-light); outline: none; font-family: var(--font-primary);
transition: border-color 0.2s;
}
.milestone-date-input:focus { border-color: var(--emerald); color: var(--pure-light); }
.add-milestone-btn {
display: flex; align-items: center; justify-content: center; gap: 8px;
height: 44px; width: 100%; background: transparent;
border: 1px dashed rgba(16,185,129,0.3); border-radius: var(--radius-lg);
color: var(--emerald-light); font-size: 14px; font-weight: 500; cursor: pointer;
transition: all 0.15s;
}
.add-milestone-btn:hover { background: rgba(16,185,129,0.05); border-style: solid; }
.bottom-actions {
position: absolute; bottom: 0; left: 0; right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 70%, transparent);
display: flex; gap: var(--space-3);
}
.btn-back-ghost {
display: flex; align-items: center; justify-content: center;
height: 52px; width: 52px; background: var(--deep-glass);
color: var(--dim-light); border: 1px solid var(--twilight);
border-radius: var(--radius-lg); text-decoration: none; flex-shrink: 0;
transition: background 0.2s ease-out;
}
.btn-back-ghost:hover { background: var(--twilight); }
.btn-next-emerald {
display: flex; align-items: center; justify-content: center; gap: var(--space-2);
height: 52px; flex: 1; background: var(--emerald); color: var(--pure-light);
font-size: 16px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald); text-decoration: none; transition: background 0.2s ease-out;
}
.btn-next-emerald:hover { background: var(--emerald-light); }
.screen-content { padding-bottom: 120px; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="24-lens-create-step4.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">New Goal</span>
<span class="nav-action body-sm text-dim">5 of 6</span>
</div>
<div class="step-progress">
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot active"></div>
<div class="step-dot"></div>
</div>
<div class="screen-content" style="padding-top: var(--space-6);">
<div class="step-question">Set your milestones</div>
<div class="step-hint">Break your goal into stepping stones. Each milestone becomes an Evidence Wall tile when completed.</div>
<div class="milestone-item">
<div class="milestone-number">1</div>
<div class="milestone-inputs">
<input type="text" class="milestone-text-input" value="Deliver team standup without notes">
<input type="text" class="milestone-date-input" value="Target: Mar 1, 2026">
</div>
</div>
<div class="milestone-item">
<div class="milestone-number">2</div>
<div class="milestone-inputs">
<input type="text" class="milestone-text-input" value="Present sprint retro to the team">
<input type="text" class="milestone-date-input" value="Target: Mar 15, 2026">
</div>
</div>
<div class="milestone-item">
<div class="milestone-number">3</div>
<div class="milestone-inputs">
<input type="text" class="milestone-text-input" value="Present quarterly results to department">
<input type="text" class="milestone-date-input" value="Target: Apr 1, 2026">
</div>
</div>
<div class="milestone-item" style="border: 1.5px dashed rgba(16,185,129,0.2);">
<div class="milestone-number" style="opacity: 0.4;">4</div>
<div class="milestone-inputs">
<input type="text" class="milestone-text-input" placeholder="Add a milestone..." style="opacity: 0.7;">
<input type="text" class="milestone-date-input" placeholder="Target date...">
</div>
</div>
<button class="add-milestone-btn" onclick="addMilestone()">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
Add milestone
</button>
</div>
<div class="bottom-actions">
<a class="btn-back-ghost" href="24-lens-create-step4.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<a class="btn-next-emerald" href="26-lens-create-step6.html">
Next
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M6 4l5 5-5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
</div>
</div>
<script>
function addMilestone() {
// Would add a new milestone row
}
</script>
</body>
</html>

View File

@@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — New Goal Step 6</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.step-dot.active { background: var(--emerald); box-shadow: 0 0 8px rgba(16,185,129,0.4); }
.step-dot.completed { background: var(--emerald); opacity: 0.5; }
.nav-back { color: var(--emerald-light) !important; }
.viz-aura {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 260px;
height: 260px;
border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.18) 0%, transparent 70%);
filter: blur(50px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
}
.viz-kaleidoscope {
margin: 24px auto 20px;
display: block;
animation: breathing 6s ease-in-out infinite;
}
.prompt-card {
background: var(--kalei-navy); border: 1px solid rgba(16,185,129,0.2);
border-radius: var(--radius-xl); padding: var(--space-5);
margin-bottom: var(--space-4); text-align: center;
box-shadow: 0 0 20px rgba(16,185,129,0.08);
}
.prompt-heading {
font-size: 16px; font-weight: 600; color: var(--emerald-light);
margin-bottom: 12px;
}
.prompt-text {
font-size: 15px; color: var(--soft-light); line-height: 1.6;
font-style: italic;
}
.prompt-step {
display: flex; align-items: flex-start; gap: 12px;
padding: 12px 0; border-bottom: 1px solid rgba(28,34,64,0.6);
}
.prompt-step:last-child { border-bottom: none; }
.prompt-step-num {
width: 24px; height: 24px; border-radius: 50%;
background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.3);
display: flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 700; color: var(--emerald); flex-shrink: 0;
}
.prompt-step-text { font-size: 14px; color: var(--soft-light); line-height: 1.5; }
.bottom-actions {
position: absolute; bottom: 0; left: 0; right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 70%, transparent);
display: flex; flex-direction: column; gap: var(--space-2);
}
.btn-save-emerald {
display: flex; align-items: center; justify-content: center; gap: var(--space-2);
height: 52px; width: 100%; background: var(--emerald); color: var(--pure-light);
font-size: 16px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald); text-decoration: none; transition: background 0.2s;
}
.btn-save-emerald:hover { background: var(--emerald-light); }
.btn-back-row {
display: flex; align-items: center; justify-content: center;
}
.btn-back-text {
color: var(--dim-light); font-size: 14px; text-decoration: none;
display: flex; align-items: center; gap: 4px;
}
.screen-content { padding-bottom: 140px; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="25-lens-create-step5.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">New Goal</span>
<span class="nav-action body-sm text-dim">6 of 6</span>
</div>
<div class="step-progress">
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot completed"></div>
<div class="step-dot active"></div>
</div>
<div class="screen-content" style="padding-top: 16px; position: relative; overflow: hidden;">
<div class="viz-aura"></div>
<!-- Emerald kaleidoscope -->
<img src="../../assets/kalei-logo.svg" width="100" height="100" alt="Kalei" class="viz-kaleidoscope" style="filter: drop-shadow(0 0 12px rgba(139,92,246,0.3));">
<div class="prompt-card">
<div class="prompt-heading">Visualization Prompt</div>
<div class="body-sm text-dim" style="margin-bottom: var(--space-4);">Take a moment to close your eyes after reading each step</div>
<div class="prompt-step">
<div class="prompt-step-num">1</div>
<div class="prompt-step-text">Find a comfortable position and take three slow, deep breaths.</div>
</div>
<div class="prompt-step">
<div class="prompt-step-num">2</div>
<div class="prompt-step-text">Picture yourself standing at the front of the room, composed and ready. Your slides are behind you. The team is watching — and you feel ready.</div>
</div>
<div class="prompt-step">
<div class="prompt-step-num">3</div>
<div class="prompt-step-text">Notice the physical sensations — your feet planted, your voice clear and even, your hands still. You are in control of this moment.</div>
</div>
<div class="prompt-step">
<div class="prompt-step-num">4</div>
<div class="prompt-step-text">Feel the satisfaction of finishing strong. Someone asks a question. You answer it. The room responds with respect. Hold that feeling.</div>
</div>
</div>
<div class="body-sm text-dim text-center" style="line-height: 1.5;">
Regular mental rehearsal activates the same neural pathways as physical practice.
</div>
</div>
<div class="bottom-actions">
<a class="btn-save-emerald" href="27-lens-goal-detail.html">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M3 9l5 5 7-8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Save Goal
</a>
<div class="btn-back-row">
<a class="btn-back-text" href="25-lens-create-step5.html">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M9 3L5 7l4 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Back to milestones
</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Goal Detail</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.nav-back { color: var(--emerald-light) !important; }
.goal-hero {
display: flex; flex-direction: column; align-items: center;
padding: 24px 0 20px;
}
.progress-ring-large {
position: relative; display: inline-flex; align-items: center; justify-content: center;
width: 120px; height: 120px; margin-bottom: 16px;
filter: drop-shadow(0 0 12px rgba(16,185,129,0.2));
animation: breathing 6s ease-in-out infinite;
}
.progress-ring-large svg { transform: rotate(-90deg); position: absolute; }
.progress-ring-large .ring-pct {
font-size: 28px; font-weight: 700; color: var(--pure-light); position: relative; z-index: 1;
}
.progress-ring-large .ring-label {
position: absolute; top: 62%; font-size: 11px; color: var(--dim-light);
text-transform: uppercase; letter-spacing: 0.05em; z-index: 1;
}
.goal-title-hero {
font-size: 20px; font-weight: 700; color: var(--pure-light); text-align: center;
margin-bottom: 6px;
}
.goal-date-chip {
background: rgba(16,185,129,0.08); border: 1px solid rgba(16,185,129,0.2);
border-radius: var(--radius-full); padding: 4px 14px; font-size: 13px;
color: var(--emerald-light); display: inline-flex; align-items: center; gap: 6px;
}
.section-card {
background: var(--kalei-navy); border: 1px solid var(--twilight);
border-radius: var(--radius-xl); padding: var(--space-4); margin-bottom: var(--space-3);
}
.card-section-label {
font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
color: var(--dim-light); margin-bottom: 12px;
}
.milestone-row {
display: flex; align-items: center; gap: var(--space-3);
padding: 8px 0; border-bottom: 1px solid rgba(28,34,64,0.6);
}
.milestone-row:last-child { border-bottom: none; }
.milestone-check {
width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0;
border: 2px solid var(--twilight); display: flex; align-items: center; justify-content: center;
cursor: pointer; transition: all 0.2s;
}
.milestone-check.done {
border-color: var(--emerald); background: rgba(16,185,129,0.15);
}
.milestone-text {
flex: 1; font-size: 14px; color: var(--soft-light); line-height: 1.4;
}
.milestone-text.done { color: var(--dim-light); text-decoration: line-through; }
.milestone-date { font-size: 12px; color: var(--dim-light); }
.ifthen-plan {
background: var(--deep-glass); border-radius: var(--radius-md);
padding: var(--space-3); margin-bottom: var(--space-2);
}
.ifthen-plan:last-child { margin-bottom: 0; }
.ifthen-plan-text { font-size: 13px; color: var(--soft-light); line-height: 1.5; }
.ifthen-if { color: var(--emerald); font-weight: 600; }
.ifthen-then { color: var(--sapphire-light); font-weight: 600; }
.actions-row {
display: flex; gap: var(--space-3); margin-bottom: var(--space-4);
}
.btn-rehearsal {
flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px;
height: 52px; background: var(--emerald); color: var(--pure-light);
font-size: 15px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald); text-decoration: none; transition: background 0.2s;
}
.btn-rehearsal:hover { background: var(--emerald-light); }
.btn-log {
flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px;
height: 52px; background: var(--deep-glass); color: var(--soft-light);
font-size: 15px; font-weight: 600; border-radius: var(--radius-lg);
border: 1px solid var(--twilight); text-decoration: none; transition: all 0.2s ease-out;
}
.btn-log:hover { background: var(--twilight); border-color: rgba(16,185,129,0.3); }
.screen-content { padding-bottom: 24px; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="20-lens-dashboard.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">Goal</span>
<span class="nav-action">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="color: var(--dim-light);">
<circle cx="10" cy="5" r="1.5" fill="currentColor"/>
<circle cx="10" cy="10" r="1.5" fill="currentColor"/>
<circle cx="10" cy="15" r="1.5" fill="currentColor"/>
</svg>
</span>
</div>
<div class="screen-content">
<div class="goal-hero">
<!-- Large progress ring — 65% of circumference (r=52): 2π×52=326.7; 65%=212.4 filled, 114.3 gap -->
<div class="progress-ring-large">
<svg width="120" height="120" viewBox="0 0 120 120">
<defs>
<linearGradient id="pr27-grEm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
</defs>
<circle cx="60" cy="60" r="52" fill="none" stroke="var(--twilight)" stroke-width="6"/>
<circle cx="60" cy="60" r="52" fill="none" stroke="url(#pr27-grEm)" stroke-width="6"
stroke-dasharray="212.4 114.3" stroke-linecap="round"/>
</svg>
<span class="ring-pct">65%</span>
<span class="ring-label">Complete</span>
</div>
<div class="goal-title-hero">Present with confidence</div>
<div class="body-sm text-dim" style="margin-bottom: var(--space-2);">Started Feb 5 · 12 evidence tiles</div>
<span class="goal-date-chip">
<svg width="12" height="12" viewBox="0 0 12 12"><circle cx="6" cy="6" r="5" stroke="currentColor" fill="none" stroke-width="1"/><path d="M6 3v3l2 1.5" stroke="currentColor" stroke-width="0.8" stroke-linecap="round"/></svg>
3 if-then plans
</span>
</div>
<div class="actions-row">
<a class="btn-rehearsal" href="29-rehearsal-session.html">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<circle cx="9" cy="9" r="7" stroke="white" stroke-width="1.2"/>
<circle cx="9" cy="9" r="3" stroke="white" stroke-width="0.8"/>
</svg>
Start Rehearsal
</a>
<a class="btn-log" href="#">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M9 3v6l4 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="9" cy="9" r="7" stroke="currentColor" stroke-width="1.2"/>
</svg>
Log Progress
</a>
</div>
<!-- Milestones -->
<div class="section-card">
<div class="card-section-label">Milestones</div>
<div class="milestone-row" onclick="toggleCheck(this)">
<div class="milestone-check done">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6l3 3 5-5" stroke="var(--emerald)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="flex-col flex" style="flex:1; gap:2px;">
<div class="milestone-text done">Deliver team standup without notes</div>
<div class="milestone-date">Completed Feb 10</div>
</div>
</div>
<div class="milestone-row" onclick="toggleCheck(this)">
<div class="milestone-check done">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6l3 3 5-5" stroke="var(--emerald)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="flex-col flex" style="flex:1; gap:2px;">
<div class="milestone-text done">Present sprint retro to the team</div>
<div class="milestone-date">Completed Feb 18</div>
</div>
</div>
<div class="milestone-row" onclick="toggleCheck(this)">
<div class="milestone-check"></div>
<div class="flex-col flex" style="flex:1; gap:2px;">
<div class="milestone-text">Present quarterly results to department</div>
<div class="milestone-date">Target: Mar 1, 2026</div>
</div>
</div>
<div class="milestone-row" onclick="toggleCheck(this)">
<div class="milestone-check"></div>
<div class="flex-col flex" style="flex:1; gap:2px;">
<div class="milestone-text">Volunteer to present at all-hands</div>
<div class="milestone-date">Target: Mar 28, 2026</div>
</div>
</div>
</div>
<!-- If-Then Plans -->
<div class="section-card">
<div class="card-section-label">If-Then Plans</div>
<div class="ifthen-plan">
<div class="ifthen-plan-text">
<span class="ifthen-if">If</span> I feel my voice shaking while presenting,
<span class="ifthen-then">then</span> I will pause, take a breath, and remember I know this material.
</div>
</div>
<div class="ifthen-plan">
<div class="ifthen-plan-text">
<span class="ifthen-if">If</span> I start to spiral about what my manager thinks,
<span class="ifthen-then">then</span> I will focus on one friendly face in the room and speak to them.
</div>
</div>
<div class="ifthen-plan">
<div class="ifthen-plan-text">
<span class="ifthen-if">If</span> I lose my place or stumble on a slide,
<span class="ifthen-then">then</span> I will say "Let me back up a moment" and continue — no apology needed.
</div>
</div>
</div>
<!-- Why this matters -->
<div class="section-card" style="border-color: rgba(16,185,129,0.15);">
<div class="card-section-label">Why This Matters</div>
<div class="body text-soft" style="line-height: 1.6; font-style: italic;">
"I want to stop letting fear of judgment stop me from sharing ideas I actually believe in. Being visible at work matters for my career — and I deserve to be heard."
</div>
</div>
</div>
</div>
<script>
function toggleCheck(row) {
const check = row.querySelector('.milestone-check');
const text = row.querySelector('.milestone-text');
const isChecked = check.classList.contains('done');
if (isChecked) {
check.classList.remove('done');
check.innerHTML = '';
text.classList.remove('done');
} else {
check.classList.add('done');
check.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6l3 3 5-5" stroke="var(--emerald)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
text.classList.add('done');
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,148 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Daily Affirmation</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.nav-back { color: var(--emerald-light) !important; }
.affirmation-canvas {
position: relative;
display: flex; flex-direction: column; align-items: center; justify-content: center;
flex: 1; padding: 0 var(--space-6); text-align: center;
overflow: hidden;
}
.em-aura-large {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 320px; height: 320px;
border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.18) 0%, rgba(16,185,129,0.05) 50%, transparent 70%);
filter: blur(60px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
}
.em-aura-inner {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 160px; height: 160px;
border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.12) 0%, transparent 70%);
filter: blur(30px);
animation: breathing 6s ease-in-out infinite 0.5s;
pointer-events: none;
}
.mini-kaleidoscope { animation: breathing 6s ease-in-out infinite; margin-bottom: var(--space-6); }
.date-label {
font-size: 11px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.08em; color: var(--emerald);
margin-bottom: 20px;
}
.affirmation-quote {
font-size: 22px; font-weight: 500; color: var(--pure-light);
line-height: 1.4; font-family: var(--font-display);
margin-bottom: 28px; position: relative; z-index: 1;
}
.affirmation-quote::before {
content: '"';
font-size: 48px; color: var(--emerald); opacity: 0.3;
position: absolute; top: -16px; left: -12px;
font-family: Georgia, serif; line-height: 1;
}
.affirmation-subtext {
font-size: 14px; color: var(--dim-light); line-height: 1.5;
margin-bottom: 32px; position: relative; z-index: 1;
}
.reflect-btn {
display: flex; align-items: center; justify-content: center; gap: 10px;
height: 52px; width: 100%; max-width: 300px;
background: var(--emerald); color: var(--pure-light);
font-size: 16px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald); text-decoration: none;
transition: background 0.2s; position: relative; z-index: 1;
}
.reflect-btn:hover { background: var(--emerald-light); }
.shard-deco {
position: absolute; opacity: 0.15;
animation: breathing 6s ease-in-out infinite;
}
.new-affirmation-link {
position: relative; z-index: 1; margin-top: var(--space-4);
font-size: 13px; color: var(--dim-light); text-decoration: none;
display: flex; align-items: center; gap: 6px;
cursor: pointer;
}
.new-affirmation-link:hover { color: var(--emerald-light); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="20-lens-dashboard.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">Affirmation</span>
<span class="nav-action">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="color: var(--dim-light);">
<path d="M4 10h12M4 6h12M4 14h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</span>
</div>
<div class="affirmation-canvas">
<div class="em-aura-large"></div>
<div class="em-aura-inner"></div>
<!-- Decorative shards -->
<svg class="shard-deco" style="top: 10%; left: 5%;" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 2L16 12L12 22L8 12Z" fill="var(--emerald)" opacity="0.6"/>
</svg>
<svg class="shard-deco" style="top: 15%; right: 8%; animation-delay: 2s;" width="16" height="16" viewBox="0 0 16 16">
<path d="M8 1L11 8L8 15L5 8Z" fill="var(--emerald)" opacity="0.5"/>
</svg>
<svg class="shard-deco" style="bottom: 20%; left: 10%; animation-delay: 3s;" width="20" height="20" viewBox="0 0 20 20">
<path d="M10 2L14 10L10 18L6 10Z" fill="var(--emerald-light)" opacity="0.4"/>
</svg>
<svg class="shard-deco" style="bottom: 28%; right: 6%; animation-delay: 1s;" width="12" height="12" viewBox="0 0 12 12">
<path d="M6 1L8 6L6 11L4 6Z" fill="var(--emerald)" opacity="0.5"/>
</svg>
<img src="../../assets/kalei-logo.svg" width="48" height="48" alt="Kalei" class="mini-kaleidoscope">
<div class="date-label">Today — Feb 22, 2026</div>
<div class="affirmation-quote">
I have the strength and clarity to take one step forward, even when the path isn't fully visible yet.
</div>
<div class="affirmation-subtext">
This affirmation was crafted from your goal to present with confidence and your Mirror sessions this week — you've been working through exactly what holds you back.
</div>
<a class="reflect-btn" href="../mirror/15-mirror-home.html">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M9 3L12 7L13 11L9 15L5 11L6 7Z" stroke="white" fill="none" stroke-width="1.2"/>
</svg>
Reflect on this
</a>
<a class="new-affirmation-link" href="#">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7l3.5-3.5M2 7l3.5 3.5M2 7h10" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Generate new affirmation
</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,258 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Rehearsal Session</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.nav-back { color: var(--emerald-light) !important; }
.nav-close-btn { color: var(--dim-light); cursor: pointer; }
.session-bg {
position: absolute; inset: 0;
background: radial-gradient(ellipse at 50% 30%, rgba(16,185,129,0.08) 0%, transparent 65%);
pointer-events: none;
}
.timer-ring-wrap {
display: flex; align-items: center; justify-content: center;
margin: 16px auto 20px; position: relative;
width: 76px; height: 76px;
}
.timer-ring-wrap svg { transform: rotate(-90deg); position: absolute; }
.timer-text {
position: relative; z-index: 1; text-align: center;
}
.timer-time { font-size: 20px; font-weight: 700; color: var(--pure-light); }
.timer-label { font-size: 10px; color: var(--emerald); text-transform: uppercase; letter-spacing: 0.06em; }
.step-indicator {
display: flex; align-items: center; justify-content: center; gap: 6px;
margin-bottom: var(--space-5);
}
.step-indicator-dot {
width: 6px; height: 6px; border-radius: 50%; background: var(--twilight);
transition: all 0.3s;
}
.step-indicator-dot.active { background: var(--emerald); width: 18px; border-radius: 3px; }
.step-indicator-dot.done { background: var(--emerald); opacity: 0.5; }
.prompt-area {
padding: var(--space-4) var(--space-5);
text-align: center;
animation: fadeIn 0.5s ease;
}
.prompt-step-tag {
font-size: 11px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--emerald); margin-bottom: 12px;
}
.prompt-main {
font-size: 20px; font-weight: 600; color: var(--pure-light);
line-height: 1.4; margin-bottom: 12px; font-family: var(--font-display);
}
.prompt-sub {
font-size: 15px; color: var(--dim-light); line-height: 1.6;
}
.breathing-guide {
display: flex; align-items: center; justify-content: center;
margin: 24px auto 20px;
}
.breathing-circle {
width: 80px; height: 80px; border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.25), rgba(16,185,129,0.05));
border: 1.5px solid rgba(16,185,129,0.3);
animation: breathCycle 8s ease-in-out infinite;
display: flex; align-items: center; justify-content: center;
position: relative;
}
.breathing-circle::after {
content: '';
position: absolute; inset: 8px; border-radius: 50%;
background: rgba(16,185,129,0.1);
animation: breathCycleInner 8s ease-in-out infinite;
}
.breath-label {
font-size: 12px; color: var(--emerald); font-weight: 500;
letter-spacing: 0.05em; position: relative; z-index: 1;
animation: breathLabel 8s ease-in-out infinite;
}
@keyframes breathCycle {
0%, 100% { transform: scale(1); opacity: 0.8; }
25% { transform: scale(1.5); opacity: 1; }
50% { transform: scale(1.5); opacity: 1; }
75% { transform: scale(1); opacity: 0.8; }
}
@keyframes breathCycleInner {
0%, 100% { transform: scale(1); }
25% { transform: scale(1.3); }
50% { transform: scale(1.3); }
75% { transform: scale(1); }
}
@keyframes breathLabel {
0%, 100% { opacity: 0; }
10% { opacity: 1; }
24% { opacity: 1; }
35% { opacity: 0; }
50% { opacity: 1; }
60% { opacity: 1; }
72% { opacity: 0; }
}
.bottom-area {
padding: var(--space-4) var(--space-4) var(--space-6);
display: flex; flex-direction: column; gap: var(--space-3);
}
.btn-next-step {
display: flex; align-items: center; justify-content: center; gap: 10px;
height: 56px; width: 100%; background: var(--emerald); color: var(--pure-light);
font-size: 16px; font-weight: 600; border-radius: var(--radius-lg);
box-shadow: var(--glow-emerald); border: none; cursor: pointer;
transition: background 0.2s;
}
.btn-next-step:hover { background: var(--emerald-light); }
.skip-link {
text-align: center; font-size: 13px; color: var(--dim-light);
cursor: pointer; padding: 4px;
}
.skip-link:hover { color: var(--soft-light); }
.screen-content { padding: 0; overflow-y: hidden; display: flex; flex-direction: column; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="27-lens-goal-detail.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<div style="display:flex; flex-direction:column; align-items:center; position:absolute; left:50%; transform:translateX(-50%);">
<span class="nav-title" style="position:static; transform:none;">Rehearsal</span>
<span style="font-size:11px; color:var(--emerald); font-weight:500;">Present with confidence</span>
</div>
<a class="nav-close-btn" href="20-lens-dashboard.html">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none">
<path d="M6 6l10 10M16 6L6 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="screen-content">
<div class="session-bg"></div>
<!-- Timer ring — extracted from progress-indicators.svg Timer Ring: r=30, stroke-width=3, 76px viewport -->
<!-- Full circumference = 2π×30 = 188.5; stroke-dasharray="141 188" = 75% remaining -->
<div class="timer-ring-wrap">
<svg width="76" height="76" viewBox="0 0 76 76">
<defs>
<linearGradient id="pr29-grEm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
</defs>
<circle cx="38" cy="38" r="30" fill="none" stroke="var(--twilight)" stroke-width="3"/>
<circle cx="38" cy="38" r="30" fill="none" stroke="url(#pr29-grEm)" stroke-width="3"
stroke-dasharray="141 188" stroke-dashoffset="0" stroke-linecap="round" id="timerArc"/>
</svg>
<div class="timer-text">
<div class="timer-time" id="timerDisplay">3:20</div>
<div class="timer-label">Remaining</div>
</div>
</div>
<!-- Step indicators -->
<div class="step-indicator">
<div class="step-indicator-dot done"></div>
<div class="step-indicator-dot active"></div>
<div class="step-indicator-dot"></div>
<div class="step-indicator-dot"></div>
<div class="step-indicator-dot"></div>
</div>
<!-- Breathing guide -->
<div class="breathing-guide">
<div class="breathing-circle">
<span class="breath-label">Breathe</span>
</div>
</div>
<!-- Prompt -->
<div class="prompt-area">
<div class="prompt-step-tag">Step 2 of 5 — Visualize</div>
<div class="prompt-main">Picture yourself beginning the presentation</div>
<div class="prompt-sub">Your team lead has just introduced you. You walk to the front. You look out at the room — and you feel ready. Your voice is steady. You know this material.</div>
</div>
</div>
<div class="bottom-area">
<button class="btn-next-step" onclick="nextStep()">
I can see it
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M6 4l5 5-5 5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<div class="skip-link">Skip this step</div>
</div>
</div>
<script>
const steps = [
{ tag: 'Step 1 of 5 — Settle', main: 'Find a comfortable position', sub: 'Sit upright and close your eyes when you\'re ready. Take three slow, deep breaths. Let your shoulders drop. You\'re safe here.' },
{ tag: 'Step 2 of 5 — Visualize', main: 'Picture yourself beginning the presentation', sub: 'Your team lead has just introduced you. You walk to the front. You look out at the room — and you feel ready. Your voice is steady. You know this material.' },
{ tag: 'Step 3 of 5 — Feel', main: 'Notice what confidence feels like in your body', sub: 'Your feet are planted. Your breathing is even. When you speak, the words come clearly. Someone nods. You make eye contact and hold it.' },
{ tag: 'Step 4 of 5 — If-Then', main: 'Your voice starts to shake — what do you do?', sub: 'You pause. You take one slow breath. You say to yourself: "I know this material." And you continue — because you do. This is the plan working.' },
{ tag: 'Step 5 of 5 — Return', main: 'Gently return to the present', sub: 'Take a deep breath and slowly open your eyes. That feeling of calm authority is real. It\'s yours. You can bring it into any room.' }
];
let currentStep = 1;
let timeLeft = 200;
function nextStep() {
if (currentStep < steps.length - 1) {
currentStep++;
updateUI();
} else {
window.location.href = '30-rehearsal-complete.html';
}
}
function updateUI() {
const step = steps[currentStep];
document.querySelector('.prompt-step-tag').textContent = step.tag;
document.querySelector('.prompt-main').textContent = step.main;
document.querySelector('.prompt-sub').textContent = step.sub;
document.querySelectorAll('.step-indicator-dot').forEach((dot, i) => {
dot.className = 'step-indicator-dot';
if (i < currentStep) dot.classList.add('done');
else if (i === currentStep) dot.classList.add('active');
});
if (currentStep === steps.length - 1) {
document.querySelector('.btn-next-step').innerHTML = 'Complete Session <svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M3 9l4 4 8-8" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
}
}
// Timer — circumference = 2π×30 = 188.5 (matches progress-indicators.svg Timer Ring r=30)
const interval = setInterval(() => {
if (timeLeft > 0) {
timeLeft--;
const m = Math.floor(timeLeft / 60);
const s = timeLeft % 60;
document.getElementById('timerDisplay').textContent = `${m}:${s.toString().padStart(2,'0')}`;
const circumference = 188.5;
const totalTime = 300;
const filled = circumference * (timeLeft / totalTime);
document.getElementById('timerArc').setAttribute('stroke-dasharray', `${filled} ${circumference}`);
} else {
clearInterval(interval);
}
}, 1000);
</script>
</body>
</html>

View File

@@ -0,0 +1,267 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Rehearsal Complete</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.nav-back { color: var(--emerald-light) !important; }
.complete-hero {
display: flex; flex-direction: column; align-items: center;
padding: 20px var(--space-4) 0; text-align: center;
}
.success-ring {
position: relative; width: 90px; height: 90px;
display: flex; align-items: center; justify-content: center;
margin-bottom: 16px;
}
.success-ring svg { position: absolute; }
.success-icon { position: relative; z-index: 1; }
.complete-title {
font-size: 26px; font-weight: 700; color: var(--pure-light);
font-family: var(--font-display); margin-bottom: 6px;
}
.complete-sub {
font-size: 15px; color: var(--dim-light); margin-bottom: var(--space-6);
}
.rating-section {
width: 100%; padding: 0 var(--space-4);
margin-bottom: var(--space-4);
}
.rating-label {
font-size: 15px; font-weight: 600; color: var(--soft-light);
margin-bottom: 14px; text-align: center;
}
.vividness-stars {
display: flex; gap: 12px; justify-content: center; margin-bottom: 6px;
}
.fragment-star {
cursor: pointer; transition: transform 0.15s;
opacity: 0.3;
}
.fragment-star:hover, .fragment-star.active { opacity: 1; transform: scale(1.1); }
.rating-hint {
font-size: 12px; color: var(--dim-light); text-align: center;
margin-top: 8px;
}
.slider-section {
width: 100%; padding: 0 var(--space-4);
margin-bottom: var(--space-5);
}
.slider-label {
font-size: 15px; font-weight: 600; color: var(--soft-light);
margin-bottom: 12px; text-align: center;
}
.slider-track {
position: relative; height: 6px; background: var(--twilight);
border-radius: var(--radius-full); margin-bottom: 8px;
}
.slider-fill {
position: absolute; left: 0; top: 0; bottom: 0;
width: 70%; background: var(--emerald); border-radius: var(--radius-full);
box-shadow: 0 0 8px rgba(16,185,129,0.4);
}
.slider-thumb {
position: absolute; top: 50%; right: 30%; transform: translate(50%, -50%);
width: 20px; height: 20px; border-radius: 50%;
background: var(--emerald); box-shadow: 0 0 12px rgba(16,185,129,0.5);
cursor: pointer;
}
.slider-labels {
display: flex; justify-content: space-between;
font-size: 12px; color: var(--dim-light);
}
input[type=range] {
-webkit-appearance: none; width: 100%; height: 6px;
background: var(--twilight); border-radius: 3px; outline: none;
cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%;
background: var(--emerald); cursor: pointer;
box-shadow: 0 0 10px rgba(16,185,129,0.4);
}
@keyframes drawRing {
from { stroke-dashoffset: 251.3; }
to { stroke-dashoffset: 0; }
}
.evidence-notification {
margin: 0 var(--space-4) var(--space-4);
background: linear-gradient(135deg, rgba(16,185,129,0.1), rgba(59,130,246,0.1));
border: 1px solid rgba(16,185,129,0.3);
border-radius: var(--radius-xl); padding: var(--space-4);
display: flex; align-items: center; gap: var(--space-3);
}
.evidence-tile-preview {
width: 52px; height: 52px; border-radius: var(--radius-md);
background: rgba(16,185,129,0.1); border: 1.5px solid var(--emerald);
box-shadow: 0 0 12px rgba(16,185,129,0.3);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; animation: pulse 2s ease-in-out infinite;
}
.evidence-info { flex: 1; }
.evidence-title { font-size: 14px; font-weight: 600; color: var(--pure-light); margin-bottom: 3px; }
.evidence-sub { font-size: 12px; color: var(--dim-light); }
.done-btn {
height: 52px; margin: 0 var(--space-4) var(--space-6);
background: var(--emerald); color: var(--pure-light); font-size: 16px; font-weight: 600;
border-radius: var(--radius-lg); box-shadow: var(--glow-emerald);
text-decoration: none; display: flex; align-items: center; justify-content: center;
transition: background 0.2s ease-out;
}
.done-btn:hover { background: var(--emerald-light); }
.screen-content { padding: 0; }
.em-aura {
position: absolute; top: 10%; left: 50%; transform: translateX(-50%);
width: 240px; height: 240px; border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.12) 0%, transparent 70%);
filter: blur(50px); animation: breathing 6s ease-in-out infinite;
pointer-events: none; z-index: 0;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="29-rehearsal-session.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<span class="nav-title">Rehearsal Complete</span>
<span class="nav-action"></span>
</div>
<div class="screen-content" style="overflow-y: auto; position: relative;">
<div class="em-aura"></div>
<div class="complete-hero" style="position: relative; z-index: 1;">
<div class="success-ring">
<svg width="90" height="90" viewBox="0 0 90 90">
<circle cx="45" cy="45" r="40" fill="none" stroke="var(--twilight)" stroke-width="3"/>
<circle cx="45" cy="45" r="40" fill="none" stroke="var(--emerald)" stroke-width="3"
stroke-dasharray="251.3" stroke-dashoffset="251.3" stroke-linecap="round"
style="animation: drawRing 1s ease 0.3s forwards;"/>
</svg>
<div class="success-icon">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M6 16l7 7 13-14" stroke="var(--emerald)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
<div class="complete-title">Rehearsal Complete</div>
<div class="complete-sub">Present with confidence · Quarterly Results Scenario · 5 min</div>
</div>
<!-- Vividness Rating -->
<div class="rating-section">
<div class="rating-label">How vivid was your visualization?</div>
<div class="vividness-stars" id="stars">
<!-- 5 emerald MD fragment diamonds — extracted from fragment-icons.svg Emerald MD -->
<!-- MD diamond: M 0,-12 L 12,0 L 0,12 L -12,0 Z, specular M 0,-12 L 12,0 L 0,0 Z opacity=0.18, glow filter glowMd stdDev=3 -->
<!-- Shared defs in first SVG only — siblings share defs from same document -->
<svg class="fragment-star" onclick="setRating(1)" width="36" height="36" viewBox="0 0 36 36">
<defs>
<linearGradient id="fr30-grEm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
<filter id="fr30-glowMd" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="3" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(18,18)" filter="url(#fr30-glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#fr30-grEm)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
</svg>
<svg class="fragment-star" onclick="setRating(2)" width="36" height="36" viewBox="0 0 36 36">
<g transform="translate(18,18)" filter="url(#fr30-glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#fr30-grEm)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
</svg>
<svg class="fragment-star active" onclick="setRating(3)" width="36" height="36" viewBox="0 0 36 36">
<g transform="translate(18,18)" filter="url(#fr30-glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#fr30-grEm)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
</svg>
<svg class="fragment-star active" onclick="setRating(4)" width="36" height="36" viewBox="0 0 36 36">
<g transform="translate(18,18)" filter="url(#fr30-glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#fr30-grEm)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
</svg>
<svg class="fragment-star active" onclick="setRating(5)" width="36" height="36" viewBox="0 0 36 36">
<g transform="translate(18,18)" filter="url(#fr30-glowMd)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#fr30-grEm)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="rating-hint" id="ratingHint">Very vivid — you could almost feel it</div>
</div>
<!-- How real slider -->
<div class="slider-section">
<div class="slider-label">How real did it feel?</div>
<input type="range" min="0" max="100" value="70" id="realSlider">
<div class="slider-labels">
<span>Distant</span>
<span>Very real</span>
</div>
</div>
<!-- Evidence Wall notification -->
<div class="evidence-notification">
<div class="evidence-tile-preview">
<svg width="24" height="24" viewBox="0 0 24 24">
<defs><linearGradient id="gsEv" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/></linearGradient></defs>
<circle cx="12" cy="12" r="9" fill="url(#gsEv)" opacity="0.8"/>
<circle cx="12" cy="12" r="5" fill="none" stroke="white" stroke-width="1.2" opacity="0.6"/>
</svg>
</div>
<div class="evidence-info">
<div class="evidence-title">Evidence Wall tile earned!</div>
<div class="evidence-sub">Lens Rehearsal · Circle tile added to your mosaic</div>
</div>
</div>
<a class="done-btn" href="20-lens-dashboard.html">Done</a>
</div>
</div>
<script>
let currentRating = 3;
const hints = ['', 'Hard to focus', 'Somewhat clear', 'Pretty vivid', 'Very vivid — you could almost feel it', 'Crystal clear — fully immersive'];
function setRating(n) {
currentRating = n;
document.querySelectorAll('.fragment-star').forEach((s, i) => {
s.classList.toggle('active', i < n);
});
document.getElementById('ratingHint').textContent = hints[n];
}
</script>
</body>
</html>

View File

@@ -0,0 +1,332 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Mirror</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.mirror-aura {
position: absolute;
top: 15%;
left: 50%;
transform: translate(-50%, -50%);
width: 260px;
height: 260px;
border-radius: 50%;
background: radial-gradient(circle, rgba(245,158,11,0.14) 0%, transparent 70%);
filter: blur(50px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.mirror-greeting {
padding: var(--space-5) 0 var(--space-4);
position: relative;
z-index: 1;
}
.streak-badge {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(245,158,11,0.12);
border: 1px solid rgba(245,158,11,0.25);
border-radius: var(--radius-full);
padding: 4px 12px;
font-size: 13px;
font-weight: 600;
color: var(--amber-light);
}
.start-session-card {
background: linear-gradient(135deg, rgba(245,158,11,0.12), rgba(245,158,11,0.04));
border: 1px solid rgba(245,158,11,0.25);
border-radius: var(--radius-xl);
padding: var(--space-5);
display: flex;
align-items: center;
gap: var(--space-4);
cursor: pointer;
text-decoration: none;
transition: all 0.2s;
position: relative;
z-index: 1;
margin-bottom: var(--space-5);
box-shadow: 0 0 24px rgba(245,158,11,0.08);
}
.start-session-card:hover { box-shadow: var(--glow-amber); }
.mirror-icon-wrap {
width: 56px;
height: 56px;
border-radius: var(--radius-lg);
background: rgba(245,158,11,0.15);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.session-card-content { flex: 1; }
.session-card-title {
font-size: 18px;
font-weight: 600;
color: var(--pure-light);
margin-bottom: 3px;
}
.session-card-sub {
font-size: 13px;
color: var(--dim-light);
}
.recent-session-item {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) 0;
border-bottom: 1px solid rgba(28,34,64,0.5);
cursor: pointer;
text-decoration: none;
transition: opacity 0.2s ease-out;
}
.recent-session-item:hover { opacity: 0.72; }
.recent-session-item:last-child { border-bottom: none; }
.session-dot {
width: 36px;
height: 36px;
border-radius: var(--radius-md);
background: rgba(245,158,11,0.1);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.session-info { flex: 1; }
.session-date { font-size: 13px; color: var(--soft-light); font-weight: 500; }
.session-stats { font-size: 11px; color: var(--dim-light); margin-top: 2px; }
.fragments-grid {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin-top: var(--space-3);
position: relative;
z-index: 1;
}
.fragment-count-chip {
display: inline-flex;
align-items: center;
gap: 5px;
background: rgba(245,158,11,0.1);
border: 1px solid rgba(245,158,11,0.2);
border-radius: var(--radius-full);
padding: 4px 10px;
font-size: 12px;
font-weight: 500;
color: var(--amber-light);
}
.fragment-count-num {
background: rgba(245,158,11,0.2);
border-radius: var(--radius-full);
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
color: var(--amber);
}
.content-z { position: relative; z-index: 1; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<div class="screen-content" style="padding-bottom: 16px;">
<div class="mirror-aura"></div>
<div class="mirror-greeting">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: var(--space-2);">
<div class="heading text-pure">Good morning, Alex</div>
<div class="streak-badge">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M7 1L9 5L13 5.5L10 8.5L10.5 13L7 11L3.5 13L4 8.5L1 5.5L5 5Z" fill="var(--amber)" opacity="0.9"/>
</svg>
14 days
</div>
</div>
<div class="body" style="color: var(--dim-light);">What's on your mind today?</div>
</div>
<!-- Start session card -->
<a class="start-session-card" href="16-mirror-session.html">
<div class="mirror-icon-wrap">
<!-- Mirror hexagon icon -->
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
<defs>
<linearGradient id="mirrorGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
</defs>
<path d="M14 3L20 7.5L20 16.5L14 21L8 16.5L8 7.5Z" stroke="url(#mirrorGrad)" fill="none" stroke-width="1.5"/>
<path d="M14 7L18 10.2V16.8L14 20L10 16.8V10.2Z" fill="url(#mirrorGrad)" opacity="0.3"/>
<circle cx="14" cy="12" r="2" fill="url(#mirrorGrad)" opacity="0.6"/>
</svg>
</div>
<div class="session-card-content">
<div class="session-card-title">Start a session</div>
<div class="session-card-sub">Journal freely — fragments detected automatically</div>
</div>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M8 4L14 10L8 16" stroke="var(--amber-light)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<!-- Recent sessions -->
<div class="content-z">
<div class="section-header">
<span class="section-title">Recent Sessions</span>
<span class="section-action">See all</span>
</div>
<div class="card" style="margin-bottom: var(--space-4);">
<a class="recent-session-item" href="19-mirror-reflection.html">
<div class="session-dot">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M9 2L12.5 6.5L13.5 11L9 14.5L4.5 11L5.5 6.5Z" stroke="var(--amber)" fill="none" stroke-width="1.2"/>
</svg>
</div>
<div class="session-info">
<div class="session-date">Feb 21 — Evening</div>
<div class="session-stats">12 min &nbsp;·&nbsp; 847 words &nbsp;·&nbsp; 3 fragments</div>
</div>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M5 3L9 7L5 11" stroke="var(--faint-light)" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<a class="recent-session-item" href="19-mirror-reflection.html">
<div class="session-dot">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M9 2L12.5 6.5L13.5 11L9 14.5L4.5 11L5.5 6.5Z" stroke="var(--amber)" fill="none" stroke-width="1.2"/>
</svg>
</div>
<div class="session-info">
<div class="session-date">Feb 20 — Morning</div>
<div class="session-stats">8 min &nbsp;·&nbsp; 524 words &nbsp;·&nbsp; 1 fragment</div>
</div>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M5 3L9 7L5 11" stroke="var(--faint-light)" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<a class="recent-session-item" href="19-mirror-reflection.html">
<div class="session-dot">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M9 2L12.5 6.5L13.5 11L9 14.5L4.5 11L5.5 6.5Z" stroke="var(--amber)" fill="none" stroke-width="1.2"/>
</svg>
</div>
<div class="session-info">
<div class="session-date">Feb 18 — Night</div>
<div class="session-stats">15 min &nbsp;·&nbsp; 1,102 words &nbsp;·&nbsp; 4 fragments</div>
</div>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M5 3L9 7L5 11" stroke="var(--faint-light)" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
</div>
</div>
<!-- Your Fragments section -->
<div class="content-z">
<div class="section-header">
<span class="section-title">Your Fragments</span>
<span class="section-action">Details</span>
</div>
<div class="fragments-grid">
<div class="fragment-count-chip">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M5 0.5L9 5L5 9.5L1 5Z" fill="var(--amber)" opacity="0.8"/>
</svg>
Catastrophizing
<div class="fragment-count-num">7</div>
</div>
<div class="fragment-count-chip">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M5 0.5L9 5L5 9.5L1 5Z" fill="var(--amber)" opacity="0.8"/>
</svg>
Mind Reading
<div class="fragment-count-num">4</div>
</div>
<div class="fragment-count-chip">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M5 0.5L9 5L5 9.5L1 5Z" fill="var(--amber)" opacity="0.8"/>
</svg>
Should Statements
<div class="fragment-count-num">3</div>
</div>
<div class="fragment-count-chip">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M5 0.5L9 5L5 9.5L1 5Z" fill="var(--amber)" opacity="0.8"/>
</svg>
Overgeneralization
<div class="fragment-count-num">2</div>
</div>
<div class="fragment-count-chip">
<svg width="10" height="10" viewBox="0 0 10 10">
<path d="M5 0.5L9 5L5 9.5L1 5Z" fill="var(--amber)" opacity="0.8"/>
</svg>
Personalization
<div class="fragment-count-num">2</div>
</div>
</div>
</div>
</div>
<div class="tab-bar">
<a class="tab-item inactive" data-tab="turn" href="../turn/10-turn-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 2L20 12L12 22L4 12Z" fill="currentColor" opacity="0.9"/><path d="M12 2L20 12L12 12Z" fill="white" opacity="0.15"/></svg>
<span class="tab-label">Turn</span>
</a>
<a class="tab-item active" data-tab="mirror" href="15-mirror-home.html">
<svg width="24" height="24" viewBox="0 0 24 24"><path d="M12 3L16 8L18 14L12 19L6 14L8 8Z" stroke="currentColor" fill="none" stroke-width="1.2"/></svg>
<span class="tab-label">Mirror</span>
</a>
<a class="tab-item inactive" data-tab="lens" href="../lens/20-lens-dashboard.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke="currentColor" fill="none" stroke-width="1.2"/><circle cx="12" cy="12" r="4" stroke="currentColor" fill="none" stroke-width="0.8"/></svg>
<span class="tab-label">Lens</span>
</a>
<a class="tab-item inactive" data-tab="gallery" href="../gallery/31-gallery-all.html">
<svg width="24" height="24" viewBox="0 0 24 24"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" fill="none" stroke-width="1"/></svg>
<span class="tab-label">Gallery</span>
</a>
<a class="tab-item inactive" data-tab="you" href="../you/35-you-profile.html">
<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4" stroke="currentColor" fill="none" stroke-width="1.2"/><path d="M4 20C4 16 8 14 12 14C16 14 20 16 20 20" stroke="currentColor" fill="none" stroke-width="1.2" stroke-linecap="round"/></svg>
<span class="tab-label">You</span>
</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,219 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Mirror Session</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.chat-area {
flex: 1;
overflow-y: auto;
padding: var(--space-4) var(--space-4) var(--space-2);
display: flex;
flex-direction: column;
}
.chat-area::-webkit-scrollbar { display: none; }
.chat-session-frame {
display: flex;
flex-direction: column;
height: calc(var(--device-height) - var(--status-bar-height) - var(--nav-header-height) - 64px);
}
.input-accessory {
height: 64px;
background: var(--kalei-navy);
border-top: 1px solid var(--twilight);
display: flex;
align-items: center;
gap: var(--space-2);
padding: 0 var(--space-3);
flex-shrink: 0;
}
.chat-input {
flex: 1;
height: 40px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-full);
padding: 0 var(--space-4);
font-family: var(--font-primary);
font-size: 15px;
color: var(--pure-light);
outline: none;
}
.chat-input::placeholder { color: var(--faint-light); }
.send-btn {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--amber);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
flex-shrink: 0;
box-shadow: var(--glow-amber);
transition: all 0.2s ease-out;
text-decoration: none;
}
.send-btn:hover { background: var(--amber-light); box-shadow: 0 0 24px rgba(245,158,11,0.5); }
.ai-thinking {
display: flex;
align-items: center;
gap: 4px;
padding: var(--space-2) var(--space-3);
margin-bottom: var(--space-3);
width: 56px;
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
border-bottom-left-radius: var(--space-1);
}
.thinking-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--amber);
animation: thinkingPulse 1.2s ease-in-out infinite;
}
.thinking-dot:nth-child(2) { animation-delay: 0.2s; }
.thinking-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes thinkingPulse {
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
40% { opacity: 1; transform: scale(1); }
}
.session-timer {
font-size: 12px;
color: var(--dim-light);
font-variant-numeric: tabular-nums;
}
.nav-close {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
text-decoration: none;
transition: background 0.2s ease-out;
}
.nav-close:hover { background: var(--twilight); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="15-mirror-home.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<div style="display:flex; flex-direction:column; align-items:center; position:absolute; left:50%; transform:translateX(-50%);">
<span class="nav-title" style="position:static; transform:none;">Mirror Session</span>
<span class="session-timer" id="timer">0:00</span>
</div>
<a class="nav-close" href="19-mirror-reflection.html">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M2 2L12 12M12 2L2 12" stroke="var(--dim-light)" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="chat-session-frame">
<div class="chat-area">
<!-- AI opening -->
<div class="chat-bubble ai" style="animation: fadeIn 0.4s ease-out;">
Welcome back. I'm here to listen and reflect with you. What's on your mind today? There's no right way to start — just begin wherever feels natural.
</div>
<!-- User message 1 -->
<div class="chat-bubble user" style="animation: fadeIn 0.4s ease-out 0.1s both; opacity:0;">
I gave this big presentation today to the whole department — the quarterly results. I completely bombed it. I lost my place twice, my voice was shaking, and I could see people checking their phones. I just fell apart in front of everyone.
</div>
<!-- AI response 1 -->
<div class="chat-bubble ai" style="animation: fadeIn 0.4s ease-out 0.2s both; opacity:0;">
That sounds like a really unsettling experience — the kind where you're in the middle of it and every small thing feels amplified. When you say you "completely bombed it," what actually happened from start to finish?
</div>
<!-- User message 2 -->
<div class="chat-bubble user" style="animation: fadeIn 0.4s ease-out 0.3s both; opacity:0;">
I mean I got through it, technically. But I stumbled on the revenue slide, and my manager was in the front row. She had this expression — I could tell she was embarrassed for me. She probably thinks I'm incompetent now. I always freeze under pressure. I'm just not someone who can handle this kind of visibility.
</div>
<!-- AI response 2 -->
<div class="chat-bubble ai" style="animation: fadeIn 0.4s ease-out 0.4s both; opacity:0;">
I want to stay with something you said — that you got through it. Then you moved to your manager's expression, then to what she must be thinking, then to a conclusion about who you are as a person. That's a lot of ground covered in a few seconds. What do you actually know about her reaction, versus what you're filling in?
</div>
<!-- User message 3 -->
<div class="chat-bubble user" style="animation: fadeIn 0.4s ease-out 0.5s both; opacity:0;">
Honestly, I don't know what she was actually thinking. She asked a question at the end about the margin numbers — that could mean anything. I just immediately jumped to the worst interpretation. I always do that.
</div>
<!-- AI response 3 -->
<div class="chat-bubble ai" style="animation: fadeIn 0.4s ease-out 0.6s both; opacity:0;">
Notice that shift you just made — from "she probably thinks I'm incompetent" to "I don't actually know what she was thinking." That move, right there, is what we're working on. The first version is a story. The second version is honest. Which one do you want to act from?
</div>
<!-- AI thinking indicator -->
<div class="ai-thinking" style="animation: fadeIn 0.4s ease-out 0.7s both; opacity:0;">
<div class="thinking-dot"></div>
<div class="thinking-dot"></div>
<div class="thinking-dot"></div>
</div>
</div>
<!-- Input accessory bar -->
<div class="input-accessory">
<input class="chat-input" type="text" placeholder="Write freely...">
<a class="send-btn" href="17-mirror-fragment-highlight.html">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M15 9L3 4L5.5 9L3 14L15 9Z" fill="var(--void)" stroke="none"/>
</svg>
</a>
</div>
</div>
</div>
<script>
let seconds = 0;
const timerEl = document.getElementById('timer');
setInterval(() => {
seconds++;
const m = Math.floor(seconds / 60);
const s = seconds % 60;
timerEl.textContent = m + ':' + String(s).padStart(2, '0');
}, 1000);
</script>
</body>
</html>

View File

@@ -0,0 +1,262 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Fragment Detected</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.chat-area {
flex: 1;
overflow-y: auto;
padding: var(--space-4) var(--space-4) var(--space-2);
display: flex;
flex-direction: column;
}
.chat-area::-webkit-scrollbar { display: none; }
.chat-session-frame {
display: flex;
flex-direction: column;
height: calc(var(--device-height) - var(--status-bar-height) - var(--nav-header-height) - 64px);
}
.input-accessory {
height: 64px;
background: var(--kalei-navy);
border-top: 1px solid var(--twilight);
display: flex;
align-items: center;
gap: var(--space-2);
padding: 0 var(--space-3);
flex-shrink: 0;
}
.chat-input {
flex: 1;
height: 40px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-full);
padding: 0 var(--space-4);
font-family: var(--font-primary);
font-size: 15px;
color: var(--pure-light);
outline: none;
}
.chat-input::placeholder { color: var(--faint-light); }
.send-btn {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--amber);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
flex-shrink: 0;
box-shadow: var(--glow-amber);
transition: all 0.2s ease-out;
}
.send-btn:hover { background: var(--amber-light); box-shadow: 0 0 24px rgba(245,158,11,0.5); }
.nav-close {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
text-decoration: none;
transition: background 0.2s ease-out;
}
.nav-close:hover { background: var(--twilight); }
.session-timer {
font-size: 12px;
color: var(--dim-light);
font-variant-numeric: tabular-nums;
}
/* Fragment highlight styles */
.fragment-highlight {
position: relative;
display: inline;
cursor: pointer;
text-decoration: none;
color: inherit;
transition: opacity 0.2s ease-out;
}
.fragment-highlight:hover { opacity: 0.8; }
.fragment-highlight::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 3px;
background: var(--amber);
border-radius: 2px;
opacity: 0.7;
box-shadow: 0 0 10px rgba(245,158,11,0.5);
animation: fragmentGlow 2s ease-in-out infinite;
}
.fragment-icon {
display: inline-flex;
align-items: center;
margin-left: 4px;
animation: fragmentPulse 2s ease-in-out infinite;
}
/* Fragment detected notice */
.fragment-notice {
display: flex;
align-items: center;
gap: var(--space-2);
margin-left: 0;
margin-top: 6px;
margin-bottom: var(--space-3);
padding: 6px 12px;
background: rgba(245,158,11,0.08);
border: 1px solid rgba(245,158,11,0.2);
border-radius: var(--radius-full);
width: fit-content;
animation: fadeIn 0.4s ease-out;
}
.fragment-notice-text {
font-size: 11px;
font-weight: 600;
color: var(--amber-light);
text-transform: uppercase;
letter-spacing: 0.06em;
}
/* Amber glow behind highlighted bubble */
.chat-bubble.user.highlighted {
box-shadow: 0 0 20px rgba(245,158,11,0.15);
border-color: rgba(245,158,11,0.25);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="16-mirror-session.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<div style="display:flex; flex-direction:column; align-items:center; position:absolute; left:50%; transform:translateX(-50%);">
<span class="nav-title" style="position:static; transform:none;">Mirror Session</span>
<span class="session-timer">4:23</span>
</div>
<a class="nav-close" href="19-mirror-reflection.html">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M2 2L12 12M12 2L2 12" stroke="var(--dim-light)" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</a>
</div>
<div class="chat-session-frame">
<div class="chat-area">
<!-- Prior context -->
<div class="chat-bubble ai">
Welcome back. I'm here to listen and reflect with you. What's on your mind today?
</div>
<div class="chat-bubble user">
I gave this big presentation today to the whole department. I completely bombed it — lost my place, my voice was shaking. I just fell apart in front of everyone.
</div>
<div class="chat-bubble ai">
That sounds really unsettling. When you say you "completely bombed it," what actually happened from start to finish?
</div>
<!-- Highlighted message with fragment -->
<div class="chat-bubble user highlighted">
I mean I got through it technically, but I stumbled on the revenue slide.
<a class="fragment-highlight" href="18-mirror-fragment-detail.html">She probably thinks I'm incompetent now</a><span class="fragment-icon">
<!-- Fragment XS amber — from fragment-icons.svg XS (12px) with specular highlights -->
<svg width="12" height="12" viewBox="-6 -6 12 12" fill="none">
<defs>
<linearGradient id="s17-fragAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<filter id="s17-fragGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g filter="url(#s17-fragGlow)">
<path d="M 0,-6 L 6,0 L 0,6 L -6,0 Z" fill="url(#s17-fragAmber)" opacity="0.9"/>
<path d="M 0,-6 L 6,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<path d="M 0,-6 L -6,0 L 0,0 Z" fill="#fff" opacity="0.08"/>
<line x1="0" y1="-6" x2="0" y2="6" stroke="#fff" stroke-width="0.3" opacity="0.2"/>
</g>
</svg>
</span>. I always do this when I feel criticized — I just freeze.
</div>
<!-- Fragment detected notice -->
<div class="fragment-notice">
<!-- Fragment XS amber — extracted from fragment-icons.svg XS size (12px), amber color state -->
<svg width="12" height="12" viewBox="-6 -6 12 12" fill="none">
<defs>
<linearGradient id="s17-grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<filter id="s17-glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g filter="url(#s17-glowSm)">
<path d="M 0,-6 L 6,0 L 0,6 L -6,0 Z" fill="url(#s17-grAmber)" opacity="0.9"/>
<path d="M 0,-6 L 6,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<path d="M 0,-6 L -6,0 L 0,0 Z" fill="#fff" opacity="0.08"/>
<line x1="0" y1="-6" x2="0" y2="6" stroke="#fff" stroke-width="0.3" opacity="0.2"/>
</g>
</svg>
<span class="fragment-notice-text">Fragment detected — tap to explore</span>
</div>
<!-- AI response -->
<div class="chat-bubble ai">
I noticed something in what you just wrote. You moved from describing the presentation to predicting your manager's private judgment about your competence. What do you actually know about her reaction, versus what you're filling in?
</div>
</div>
<!-- Input accessory bar -->
<div class="input-accessory">
<input class="chat-input" type="text" placeholder="Write freely...">
<button class="send-btn">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M15 9L3 4L5.5 9L3 14L15 9Z" fill="var(--void)" stroke="none"/>
</svg>
</button>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,235 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Fragment Detail</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.dimmed-chat {
position: absolute;
inset: 0;
background: rgba(5,5,8,0.75);
backdrop-filter: blur(6px);
z-index: 0;
}
.chat-preview {
padding: calc(var(--status-bar-height) + var(--nav-header-height) + 16px) var(--space-4) var(--space-4);
opacity: 0.4;
}
.modal-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: flex-end;
z-index: 50;
}
.modal-sheet {
width: 100%;
background: var(--kalei-navy);
border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
padding: var(--space-3) var(--space-4) var(--space-8);
border-top: 1px solid rgba(245,158,11,0.2);
box-shadow: 0 -8px 40px rgba(245,158,11,0.1);
animation: slideUp 0.35s cubic-bezier(0.32, 0.72, 0, 1);
}
.modal-handle {
width: 36px;
height: 4px;
background: var(--twilight);
border-radius: var(--radius-full);
margin: 0 auto var(--space-5);
}
.distortion-header {
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-4);
}
.distortion-icon-wrap {
width: 48px;
height: 48px;
border-radius: var(--radius-lg);
background: rgba(245,158,11,0.12);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 0 16px rgba(245,158,11,0.15);
}
.distortion-name {
font-family: var(--font-display);
font-size: 22px;
font-weight: 700;
color: var(--amber-light);
}
.distortion-type {
font-size: 11px;
color: var(--dim-light);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-top: 2px;
}
.quote-card {
background: var(--deep-glass);
border: 1px solid rgba(245,158,11,0.2);
border-left: 3px solid var(--amber);
border-radius: var(--radius-md);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-4);
}
.quote-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--amber-light);
margin-bottom: 6px;
}
.quote-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.5;
font-style: italic;
}
.explanation {
font-size: 14px;
color: var(--dim-light);
line-height: 1.6;
margin-bottom: var(--space-5);
}
.modal-actions {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.btn-turn-thought {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
height: 52px;
background: var(--amethyst);
color: var(--pure-light);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
text-decoration: none;
box-shadow: var(--glow-amethyst);
transition: background 0.2s;
}
.btn-turn-thought:hover { background: var(--amethyst-light); }
.btn-dismiss {
display: flex;
align-items: center;
justify-content: center;
height: 44px;
background: transparent;
color: var(--dim-light);
font-size: 15px;
font-weight: 500;
border-radius: var(--radius-md);
text-decoration: none;
border: 1px solid var(--twilight);
transition: background 0.15s;
}
.btn-dismiss:hover { background: var(--deep-glass); }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar" style="position:absolute; top:0; left:0; right:0; z-index:2; opacity:0.4;">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="dimmed-chat">
<div class="chat-preview">
<div class="chat-bubble ai" style="font-size:14px;">Welcome back. What's on your mind today?</div>
<div class="chat-bubble user" style="font-size:14px;">I got through it technically, but I stumbled on the revenue slide. <span style="text-decoration:underline; text-decoration-color: rgba(245,158,11,0.7);">She probably thinks I'm incompetent now.</span> I always freeze under pressure.</div>
<div class="chat-bubble ai" style="font-size:14px;">You moved from describing the presentation to predicting your manager's private judgment. What do you actually know about her reaction?</div>
</div>
</div>
<div class="modal-overlay">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="distortion-header">
<div class="distortion-icon-wrap">
<!-- Mind Reading — exact paths extracted from icons-distortions.svg (lines 5771) -->
<!-- Hexagonal head outline + inner diamond + radiating thought waves -->
<svg width="28" height="28" viewBox="-14 -14 28 28" fill="none">
<defs>
<filter id="s18-gAmber" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="1.8" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<linearGradient id="s18-amberGr" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
</defs>
<g filter="url(#s18-gAmber)">
<path d="M 0,-10 L 8,-5 L 8,5 L 0,10 L -8,5 L -8,-5 Z" fill="none" stroke="#F59E0B" stroke-width="1" opacity="0.7"/>
<path d="M 0,-4 L 4,0 L 0,4 L -4,0 Z" fill="url(#s18-amberGr)" opacity="0.8"/>
<path d="M 0,-4 L 4,0 L -4,0 Z" fill="#fff" opacity="0.15"/>
<line x1="5" y1="-3" x2="9" y2="-6" stroke="#FCD34D" stroke-width="0.4" opacity="0.35"/>
<line x1="5" y1="3" x2="9" y2="6" stroke="#FCD34D" stroke-width="0.4" opacity="0.35"/>
<line x1="-5" y1="-3" x2="-9" y2="-6" stroke="#FCD34D" stroke-width="0.4" opacity="0.35"/>
</g>
</svg>
</div>
<div>
<div class="distortion-name">Mind Reading</div>
<div class="distortion-type">Cognitive distortion</div>
</div>
</div>
<div class="quote-card">
<div class="quote-label">Detected phrase</div>
<div class="quote-text">"She probably thinks I'm incompetent now"</div>
</div>
<p class="explanation">
Mind Reading happens when we assume we know what others are thinking — usually something critical or negative about us — without any direct evidence. You saw an expression and immediately filled in a whole verdict about your worth. In reality, your manager may have been processing the numbers, thinking about a question to ask, or dealing with something entirely unrelated to you.
</p>
<p class="explanation" style="margin-top: -12px;">
This pattern is especially common after high-stakes moments. The mind wants certainty, so it invents a story. The cost is that you start responding to a story — not reality.
</p>
<div class="modal-actions">
<a href="../turn/11-turn-input-active.html" class="btn-turn-thought">
<!-- Fragment SM amethyst — from fragment-icons.svg SM size section -->
<svg width="16" height="16" viewBox="-8 -8 16 16" fill="none">
<defs>
<linearGradient id="s18-grAm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<filter id="s18-glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g filter="url(#s18-glowSm)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#s18-grAm)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<path d="M 0,-8 L -8,0 L 0,0 Z" fill="#fff" opacity="0.08"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.4" opacity="0.2"/>
</g>
</svg>
Turn this thought
</a>
<a href="17-mirror-fragment-highlight.html" class="btn-dismiss">Dismiss</a>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Session Reflection</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.summary-card {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-4);
}
.summary-stats {
display: flex;
gap: var(--space-3);
margin-bottom: var(--space-4);
}
.summary-stat {
flex: 1;
background: var(--deep-glass);
border-radius: var(--radius-lg);
padding: var(--space-3);
text-align: center;
}
.summary-stat-value {
font-family: var(--font-display);
font-size: 22px;
font-weight: 700;
color: var(--pure-light);
}
.summary-stat-label {
font-size: 10px;
color: var(--dim-light);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 2px;
}
.fragments-found {
display: flex;
align-items: center;
gap: var(--space-2);
flex-wrap: wrap;
}
.fragments-found-label {
font-size: 12px;
color: var(--dim-light);
margin-right: var(--space-1);
}
.frag-chip {
display: inline-flex;
align-items: center;
gap: 4px;
height: 24px;
padding: 0 9px;
border-radius: var(--radius-full);
font-size: 11px;
font-weight: 500;
background: rgba(245,158,11,0.12);
color: var(--amber-light);
border: 1px solid rgba(245,158,11,0.2);
}
/* Mood check */
.mood-section {
margin-bottom: var(--space-4);
}
.mood-label {
font-size: 14px;
font-weight: 500;
color: var(--soft-light);
margin-bottom: var(--space-3);
}
.mood-options {
display: flex;
justify-content: space-between;
gap: var(--space-2);
}
.mood-option {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: var(--space-2) 0;
border-radius: var(--radius-md);
cursor: pointer;
border: 1px solid transparent;
transition: all 0.15s;
}
.mood-option.selected {
background: rgba(245,158,11,0.08);
border-color: rgba(245,158,11,0.3);
}
.mood-option-label {
font-size: 10px;
color: var(--dim-light);
}
/* Themes */
.themes-row {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin-top: var(--space-2);
}
.theme-chip {
display: inline-flex;
align-items: center;
height: 28px;
padding: 0 12px;
border-radius: var(--radius-full);
font-size: 12px;
font-weight: 500;
background: var(--deep-glass);
color: var(--soft-light);
border: 1px solid var(--twilight);
}
.btn-save-session {
display: flex;
align-items: center;
justify-content: center;
height: 52px;
background: var(--amber);
color: var(--void);
font-size: 16px;
font-weight: 600;
border-radius: var(--radius-lg);
text-decoration: none;
box-shadow: var(--glow-amber);
margin-bottom: var(--space-3);
transition: all 0.2s ease-out;
}
.btn-save-session:hover { background: var(--amber-light); box-shadow: 0 0 28px rgba(245,158,11,0.4); }
.amber-aura {
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border-radius: 50%;
background: radial-gradient(circle, rgba(245,158,11,0.1) 0%, transparent 70%);
filter: blur(40px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="15-mirror-home.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<div style="display:flex; flex-direction:column; align-items:center; position:absolute; left:50%; transform:translateX(-50%);">
<span class="nav-title" style="position:static; transform:none;">Session Reflection</span>
<span style="font-size:11px; color:var(--dim-light);">Feb 21 — Evening</span>
</div>
<span class="nav-action"></span>
</div>
<div class="screen-content" style="padding-bottom: 24px; position: relative;">
<div class="amber-aura"></div>
<div class="mt-3" style="position:relative;z-index:1;">
<!-- Summary stats -->
<div class="summary-card">
<div class="summary-stats">
<div class="summary-stat">
<div class="summary-stat-value">12</div>
<div class="summary-stat-label">Minutes</div>
</div>
<div class="summary-stat">
<div class="summary-stat-value">847</div>
<div class="summary-stat-label">Words</div>
</div>
<div class="summary-stat">
<div class="summary-stat-value" style="color: var(--amber-light);">3</div>
<div class="summary-stat-label">Fragments</div>
</div>
</div>
<div class="label text-dim mb-2">Fragments Detected</div>
<div class="fragments-found">
<!-- Mind Reading — hexagonal head with inner diamond (icons-distortions.svg) -->
<span class="frag-chip">
<svg width="10" height="10" viewBox="-12 -12 24 24" fill="none">
<defs>
<linearGradient id="s19-amGr1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
</defs>
<path d="M 0,-10 L 8,-5 L 8,5 L 0,10 L -8,5 L -8,-5 Z" fill="none" stroke="#F59E0B" stroke-width="1" opacity="0.7"/>
<path d="M 0,-4 L 4,0 L 0,4 L -4,0 Z" fill="url(#s19-amGr1)" opacity="0.8"/>
<path d="M 0,-4 L 4,0 L -4,0 Z" fill="#fff" opacity="0.15"/>
</svg>
Mind Reading
</span>
<!-- Catastrophizing — cascading shard (icons-distortions.svg) -->
<span class="frag-chip">
<svg width="10" height="10" viewBox="-12 -12 24 24" fill="none">
<defs>
<linearGradient id="s19-amGr2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
</defs>
<path d="M 0,-10 L 5,0 L 0,10 L -5,0 Z" fill="url(#s19-amGr2)" opacity="0.9"/>
<path d="M 0,-10 L 5,0 L -5,0 Z" fill="#fff" opacity="0.15"/>
<path d="M -8,6 L -5,10 L -9,12 L -11,8 Z" fill="#F59E0B" opacity="0.5"/>
<path d="M 7,8 L 10,12 L 8,14 L 5,11 Z" fill="#F59E0B" opacity="0.4"/>
</svg>
Catastrophizing
</span>
<!-- Should Statements — rigid ruler/bracket (icons-distortions.svg) -->
<span class="frag-chip">
<svg width="10" height="10" viewBox="-12 -12 24 24" fill="none">
<path d="M -6,-10 L -6,10 L 6,10" fill="none" stroke="#F59E0B" stroke-width="1.2" stroke-linejoin="miter"/>
<line x1="-6" y1="-6" x2="-3" y2="-6" stroke="#FCD34D" stroke-width="0.6" opacity="0.5"/>
<line x1="-6" y1="-2" x2="-3" y2="-2" stroke="#FCD34D" stroke-width="0.6" opacity="0.5"/>
<line x1="-6" y1="2" x2="-3" y2="2" stroke="#FCD34D" stroke-width="0.6" opacity="0.5"/>
<line x1="-6" y1="6" x2="-3" y2="6" stroke="#FCD34D" stroke-width="0.6" opacity="0.5"/>
<path d="M 2,-4 L 3,-1 L 1,-1 Z" fill="#FCD34D" opacity="0.6"/>
<circle cx="2" cy="1" r="0.8" fill="#FCD34D" opacity="0.6"/>
</svg>
Should Statements
</span>
</div>
</div>
<!-- Mood check -->
<div class="mood-section">
<div class="subheading text-pure mb-3">How are you feeling now?</div>
<div class="mood-options">
<!-- Mood 1: Settled (small nested diamond) -->
<div class="mood-option" onclick="selectMood(this)">
<svg width="32" height="32" viewBox="0 0 32 32">
<path d="M16 4L26 16L16 28L6 16Z" fill="var(--twilight)" stroke="var(--faint-light)" stroke-width="1" fill-opacity="0.5"/>
<path d="M16 10L22 16L16 22L10 16Z" fill="var(--emerald)" opacity="0.5"/>
</svg>
<span class="mood-option-label">Settled</span>
</div>
<!-- Mood 2: Lighter (rising diamond) -->
<div class="mood-option" onclick="selectMood(this)">
<svg width="32" height="32" viewBox="0 0 32 32">
<path d="M16 8L24 16L16 24L8 16Z" fill="var(--sapphire)" opacity="0.3" stroke="var(--sapphire-light)" stroke-width="1"/>
<path d="M16 4L20 8" stroke="var(--sapphire-light)" stroke-width="1.5" stroke-linecap="round"/>
<path d="M12 4L16 8" stroke="var(--sapphire-light)" stroke-width="1" stroke-linecap="round" opacity="0.5"/>
<path d="M20 4L24 8" stroke="var(--sapphire-light)" stroke-width="1" stroke-linecap="round" opacity="0.5"/>
</svg>
<span class="mood-option-label">Lighter</span>
</div>
<!-- Mood 3: Neutral (balanced diamond) -->
<div class="mood-option selected" onclick="selectMood(this)">
<svg width="32" height="32" viewBox="0 0 32 32">
<path d="M16 6L26 16L16 26L6 16Z" fill="var(--amber)" opacity="0.2" stroke="var(--amber-light)" stroke-width="1.2"/>
<line x1="10" y1="16" x2="22" y2="16" stroke="var(--amber-light)" stroke-width="1.5" stroke-linecap="round"/>
</svg>
<span class="mood-option-label" style="color:var(--amber-light);">Neutral</span>
</div>
<!-- Mood 4: Heavy (dense diamond) -->
<div class="mood-option" onclick="selectMood(this)">
<svg width="32" height="32" viewBox="0 0 32 32">
<path d="M16 6L26 16L16 26L6 16Z" fill="var(--amethyst)" opacity="0.25" stroke="var(--amethyst-light)" stroke-width="1"/>
<path d="M16 10L22 16L16 22L10 16Z" fill="var(--amethyst)" opacity="0.3"/>
<path d="M16 13L19 16L16 19L13 16Z" fill="var(--amethyst)" opacity="0.6"/>
</svg>
<span class="mood-option-label">Heavy</span>
</div>
<!-- Mood 5: Turbulent (spiky diamond) -->
<div class="mood-option" onclick="selectMood(this)">
<svg width="32" height="32" viewBox="0 0 32 32">
<path d="M16 4L20 12L28 12L22 18L24 26L16 22L8 26L10 18L4 12L12 12Z" fill="var(--ruby)" opacity="0.2" stroke="var(--ruby)" stroke-width="0.8"/>
</svg>
<span class="mood-option-label">Turbulent</span>
</div>
</div>
</div>
<!-- Key themes -->
<div class="mb-4">
<div class="section-header">
<span class="section-title">Key Themes</span>
</div>
<div class="themes-row">
<span class="theme-chip">Public speaking anxiety</span>
<span class="theme-chip">Manager judgment</span>
<span class="theme-chip">Self-criticism</span>
<span class="theme-chip">Freeze response</span>
</div>
</div>
<!-- The Guide noticed section -->
<div style="position: relative; border-radius: var(--radius-lg); margin-bottom: var(--space-4);">
<!-- Prismatic gradient border -->
<div style="position: absolute; inset: -1px; border-radius: var(--radius-lg); background: linear-gradient(135deg, #8B5CF6, #3B82F6, #10B981, #F59E0B, #EC4899, #8B5CF6); z-index: 0; background-size: 300% 300%; animation: prismaticShift 6s ease-in-out infinite;"></div>
<div style="position: relative; z-index: 1; background: var(--kalei-navy); border-radius: calc(var(--radius-lg) - 1px); padding: var(--space-4);">
<div style="display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3);">
<!-- 6-blade kaleidoscope icon -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<defs>
<linearGradient id="s19-guideGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#8B5CF6"/>
<stop offset="50%" stop-color="#3B82F6"/>
<stop offset="100%" stop-color="#10B981"/>
</linearGradient>
</defs>
<polygon points="8,1 15,8 8,15 1,8" fill="none" stroke="url(#s19-guideGrad)" stroke-width="1.5" stroke-linejoin="round"/>
<circle cx="8" cy="8" r="2" fill="url(#s19-guideGrad)" opacity="0.7"/>
</svg>
<span style="font-family: var(--font-display); font-size: 13px; font-weight: 600; color: var(--soft-light); text-transform: uppercase; letter-spacing: 0.06em;">The Guide noticed...</span>
</div>
<p style="font-size: 13px; color: var(--dim-light); line-height: 1.6; margin-bottom: var(--space-2);">Today's session connects to your Lens goal <span style="color: var(--emerald-light); font-weight: 500;">"Present with confidence"</span>. The Mind Reading pattern you named — assuming your manager's verdict — is the exact thought that derails you before presentations begin.</p>
<p style="font-size: 13px; color: var(--dim-light); line-height: 1.6; margin-bottom: var(--space-3);">Your if-then plan for this goal is ready to rehearse. <span style="color: var(--emerald-light); font-weight: 500;">14-day streak</span> — you're building something real.</p>
<a href="../lens/20-lens-dashboard.html" style="font-size: 13px; font-weight: 500; color: var(--emerald-light); text-decoration: none; display: inline-flex; align-items: center; gap: 4px; transition: opacity 0.2s ease-out;">Check your Lens goals
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M4 2l4 4-4 4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
</div>
</div>
<!-- Actions -->
<a href="#" class="btn-save-session" onclick="this.textContent='Saved'; return false;">
Save session
</a>
<a href="../turn/10-turn-home.html" class="btn btn-secondary" style="text-decoration:none;">
Start a Turn
</a>
</div>
</div>
</div>
<script>
function selectMood(el) {
document.querySelectorAll('.mood-option').forEach(o => {
o.classList.remove('selected');
o.querySelector('.mood-option-label').style.color = '';
});
el.classList.add('selected');
el.querySelector('.mood-option-label').style.color = 'var(--amber-light)';
}
</script>
</body>
</html>

View File

@@ -0,0 +1,334 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Upgrade to Prism</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.bg-screen {
position: absolute;
inset: 0;
background: var(--void);
}
/* Blurred background context (Turn Home) */
.bg-content {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
padding: 54px 16px 16px;
filter: blur(3px);
opacity: 0.4;
}
.bg-greeting {
font-size: 22px;
font-weight: 600;
color: var(--pure-light);
margin-top: 16px;
}
.bg-card {
height: 80px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
margin-top: 20px;
}
.modal-overlay {
position: absolute;
inset: 0;
background: rgba(5,5,8,0.75);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
z-index: 50;
}
.modal-card {
width: 340px;
background: var(--kalei-navy);
border-radius: var(--radius-2xl);
padding: var(--space-6);
position: relative;
overflow: hidden;
animation: modalSlideIn 0.35s ease-out forwards;
}
@keyframes modalSlideIn {
from { opacity: 0; transform: translateY(12px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* Prismatic border */
.modal-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: var(--radius-2xl);
padding: 1.5px;
background: linear-gradient(135deg, #8B5CF6, #3B82F6, #10B981, #F59E0B, #EC4899);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
.modal-header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.modal-logo {
margin-bottom: 14px;
}
.modal-title {
font-size: 22px;
font-weight: 700;
font-family: var(--font-display);
text-align: center;
background: linear-gradient(135deg, #C4B5FD, #93C5FD, #6EE7B7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.modal-subtitle {
font-size: 14px;
color: var(--dim-light);
text-align: center;
margin-top: 6px;
}
.benefits-list {
margin-bottom: 20px;
}
.benefit-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 8px 0;
}
.benefit-icon {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 1px;
}
.benefit-text {
flex: 1;
}
.benefit-title {
font-size: 14px;
font-weight: 600;
color: var(--pure-light);
}
.benefit-desc {
font-size: 12px;
color: var(--dim-light);
margin-top: 1px;
}
.price-row {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 18px;
padding: 12px;
background: rgba(139,92,246,0.06);
border-radius: var(--radius-lg);
border: 1px solid rgba(139,92,246,0.15);
}
.price-val {
font-size: 28px;
font-weight: 700;
font-family: var(--font-display);
background: linear-gradient(135deg, #C4B5FD, #93C5FD, #6EE7B7, #FDE68A, #F9A8D4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.price-period {
font-size: 13px;
color: var(--dim-light);
}
.btn-prismatic {
width: 100%;
height: 52px;
border: none;
border-radius: var(--radius-lg);
background: linear-gradient(135deg, #8B5CF6, #3B82F6, #10B981);
color: white;
font-size: 16px;
font-weight: 600;
font-family: var(--font-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
margin-bottom: 10px;
box-shadow: 0 0 24px rgba(139,92,246,0.3);
transition: opacity 0.2s;
}
.btn-prismatic:hover { opacity: 0.9; }
.maybe-later {
width: 100%;
height: 40px;
background: transparent;
border: none;
color: var(--dim-light);
font-size: 14px;
font-family: var(--font-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
transition: color 0.2s;
}
.maybe-later:hover { color: var(--soft-light); }
.trial-note {
font-size: 11px;
color: var(--faint-light);
text-align: center;
margin-top: 6px;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<!-- Blurred background -->
<div class="bg-content" aria-hidden="true">
<div class="bg-greeting">Good morning, Alex</div>
<div class="bg-card" style="margin-top:16px;"></div>
<div class="bg-card" style="margin-top:10px; height:60px;"></div>
</div>
<!-- Modal overlay -->
<div class="modal-overlay">
<div class="modal-card">
<!-- Kaleidoscope logo -->
<div class="modal-header">
<div class="modal-logo">
<svg width="56" height="56" viewBox="0 0 56 56" style="mix-blend-mode: normal;">
<defs>
<linearGradient id="upBlade1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD" stop-opacity="0.9"/>
<stop offset="100%" stop-color="#7C3AED" stop-opacity="0.4"/>
</linearGradient>
<linearGradient id="upBlade2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD" stop-opacity="0.8"/>
<stop offset="100%" stop-color="#2563EB" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="upBlade3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7" stop-opacity="0.8"/>
<stop offset="100%" stop-color="#059669" stop-opacity="0.3"/>
</linearGradient>
</defs>
<g transform="translate(28,28)" style="animation: breathing 6s ease-in-out infinite;">
<g><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#upBlade1)"/></g>
<g transform="rotate(60)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#upBlade2)"/></g>
<g transform="rotate(120)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#upBlade3)"/></g>
<g transform="rotate(180)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#upBlade1)"/></g>
<g transform="rotate(240)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#upBlade2)"/></g>
<g transform="rotate(300)"><path d="M0,0 L4,-20 L0,-24 L-4,-20Z" fill="url(#upBlade3)"/></g>
<circle r="5" fill="white" opacity="0.15"/>
</g>
</svg>
</div>
<div class="modal-title">Unlock Kalei Plus</div>
<div class="modal-subtitle">More turns. More insight. Same intention.</div>
</div>
<!-- Benefits — grounded, not pushy -->
<div class="benefits-list">
<div class="benefit-item">
<div class="benefit-icon" style="background: rgba(139,92,246,0.15);">
<svg width="14" height="14" viewBox="0 0 14 14">
<path d="M7 1L13 7L7 13L1 7Z" fill="#C4B5FD" opacity="0.9"/>
</svg>
</div>
<div class="benefit-text">
<div class="benefit-title">10 Turns per day</div>
<div class="benefit-desc">From 3 free → 10 per day. Enough for real-time reframing</div>
</div>
</div>
<div class="benefit-item">
<div class="benefit-icon" style="background: rgba(59,130,246,0.12);">
<svg width="14" height="14" viewBox="0 0 14 14">
<path d="M7 1L13 7L7 13L1 7Z" fill="#93C5FD" opacity="0.9"/>
</svg>
</div>
<div class="benefit-text">
<div class="benefit-title">Full Spectrum Access</div>
<div class="benefit-desc">The River, Your Glass, Rhythm, Growth — all views unlocked</div>
</div>
</div>
<div class="benefit-item">
<div class="benefit-icon" style="background: rgba(16,185,129,0.12);">
<svg width="14" height="14" viewBox="0 0 14 14">
<path d="M7 1L13 7L7 13L1 7Z" fill="#6EE7B7" opacity="0.9"/>
</svg>
</div>
<div class="benefit-text">
<div class="benefit-title">Weekly &amp; Monthly Reports</div>
<div class="benefit-desc">AI-written summaries with specific pattern trends and dates</div>
</div>
</div>
</div>
<!-- Tier price row -->
<div class="price-row">
<div class="price-val">$9.99</div>
<div class="price-period">/ month</div>
</div>
<!-- Tier note -->
<div style="display:flex; justify-content:center; gap:18px; margin-bottom:14px;">
<div style="text-align:center;">
<div style="font-size:10px; color:var(--faint-light); text-transform:uppercase; letter-spacing:0.06em; margin-bottom:3px;">Free</div>
<div style="font-size:12px; color:var(--dim-light);">3 turns/day</div>
</div>
<div style="text-align:center; position:relative;">
<div style="font-size:10px; color:var(--amethyst-light); text-transform:uppercase; letter-spacing:0.06em; margin-bottom:3px; font-weight:600;">Plus</div>
<div style="font-size:12px; color:var(--soft-light); font-weight:600;">10 turns/day</div>
</div>
<div style="text-align:center;">
<div style="font-size:10px; color:var(--faint-light); text-transform:uppercase; letter-spacing:0.06em; margin-bottom:3px;">Pro</div>
<div style="font-size:12px; color:var(--dim-light);">Unlimited</div>
</div>
</div>
<!-- CTA -->
<a class="btn-prismatic" href="../you/38-you-subscription.html">Try Plus free for 7 days</a>
<div class="trial-note">7 days free, then $9.99/month. Cancel anytime. Pro available at $19.99/month.</div>
<a class="maybe-later" href="../turn/10-turn-home.html">Maybe later</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,312 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Rate Limit</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.bg-content {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
filter: blur(2px);
opacity: 0.35;
}
.bg-status {
height: 54px;
padding: 14px 28px 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.bg-time { font-size: 15px; font-weight: 600; color: var(--pure-light); }
.bg-header {
padding: 16px;
font-size: 22px;
font-weight: 600;
color: var(--pure-light);
}
.bg-input {
margin: 0 16px;
height: 120px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
}
.bg-cards {
margin: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.bg-mini-card {
height: 60px;
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
}
.limit-overlay {
position: absolute;
inset: 0;
background: rgba(5,5,8,0.65);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
z-index: 50;
}
.limit-card {
width: 100%;
background: var(--kalei-navy);
border: 1px solid rgba(139,92,246,0.2);
border-radius: var(--radius-2xl);
padding: 24px;
box-shadow: 0 0 40px rgba(0,0,0,0.5), 0 0 60px rgba(139,92,246,0.08);
animation: modalSlideIn 0.3s ease-out forwards;
}
@keyframes modalSlideIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.limit-icon {
width: 52px;
height: 52px;
border-radius: 50%;
background: rgba(139,92,246,0.12);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 14px;
}
.limit-title {
font-size: 18px;
font-weight: 700;
color: var(--pure-light);
text-align: center;
margin-bottom: 6px;
}
.limit-subtitle {
font-size: 13px;
color: var(--dim-light);
text-align: center;
margin-bottom: 18px;
}
.progress-section {
margin-bottom: 18px;
}
.progress-label {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.progress-label-text {
font-size: 12px;
color: var(--dim-light);
}
.progress-label-count {
font-size: 12px;
font-weight: 600;
color: var(--pure-light);
}
.progress-bar-bg {
height: 8px;
background: var(--twilight);
border-radius: var(--radius-full);
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
border-radius: var(--radius-full);
background: linear-gradient(90deg, var(--amethyst), var(--ruby));
width: 100%;
}
.turn-dots {
display: flex;
gap: 8px;
justify-content: center;
margin-top: 10px;
}
.turn-dot {
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(139,92,246,0.3);
border: 2px solid var(--amethyst);
display: flex;
align-items: center;
justify-content: center;
}
.turn-dot.used {
background: var(--amethyst);
opacity: 0.7;
}
.upgrade-prompt {
background: rgba(139,92,246,0.06);
border: 1px solid rgba(139,92,246,0.2);
border-radius: var(--radius-lg);
padding: 12px;
margin-bottom: 16px;
text-align: center;
}
.upgrade-prompt-text {
font-size: 13px;
color: var(--soft-light);
}
.upgrade-prompt-link {
font-size: 13px;
font-weight: 600;
color: var(--amethyst-light);
}
.timer-row {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
margin-bottom: 16px;
font-size: 13px;
color: var(--dim-light);
}
.timer-val {
font-size: 14px;
font-weight: 600;
color: var(--soft-light);
font-variant-numeric: tabular-nums;
}
.btn-prismatic {
width: 100%;
height: 48px;
border: none;
border-radius: var(--radius-lg);
background: linear-gradient(135deg, #8B5CF6, #3B82F6);
color: white;
font-size: 15px;
font-weight: 600;
font-family: var(--font-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
margin-bottom: 10px;
box-shadow: 0 0 20px rgba(139,92,246,0.25);
}
.dismiss-btn {
width: 100%;
height: 40px;
background: transparent;
border: none;
color: var(--dim-light);
font-size: 14px;
font-family: var(--font-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
}
</style>
</head>
<body>
<div class="device-frame">
<!-- Blurred Turn Home context -->
<div class="bg-content" aria-hidden="true">
<div class="bg-status">
<span class="bg-time">9:41</span>
</div>
<div class="bg-header">Good morning, Alex</div>
<div class="bg-input"></div>
<div class="bg-cards">
<div class="bg-mini-card"></div>
<div class="bg-mini-card"></div>
</div>
</div>
<!-- Rate limit overlay -->
<div class="limit-overlay">
<div class="limit-card">
<div class="limit-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 2L22 12L12 22L2 12Z" fill="#8B5CF6" opacity="0.6"/>
<path d="M12 8V12M12 16H12.01" stroke="#C4B5FD" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<div class="limit-title">That's your 3 for today</div>
<div class="limit-subtitle">Good work showing up. Your next turns reset at midnight.</div>
<!-- Progress -->
<div class="progress-section">
<div class="progress-label">
<span class="progress-label-text">Daily usage</span>
<span class="progress-label-count">3 / 3</span>
</div>
<div class="progress-bar-bg">
<div class="progress-bar-fill"></div>
</div>
<div class="turn-dots">
<div class="turn-dot used">
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M6 1L11 6L6 11L1 6Z" fill="#fff" opacity="0.8"/>
</svg>
</div>
<div class="turn-dot used">
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M6 1L11 6L6 11L1 6Z" fill="#fff" opacity="0.8"/>
</svg>
</div>
<div class="turn-dot used">
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M6 1L11 6L6 11L1 6Z" fill="#fff" opacity="0.8"/>
</svg>
</div>
</div>
</div>
<!-- Upgrade prompt — calm, not pushy -->
<div class="upgrade-prompt">
<span class="upgrade-prompt-text">Plus gives you </span>
<span class="upgrade-prompt-text" style="font-weight:600; color: var(--pure-light);">10 turns per day</span>
<span class="upgrade-prompt-text"> — enough for real-time reframing</span>
</div>
<!-- Timer -->
<div class="timer-row">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<circle cx="7" cy="8" r="5" stroke="#94A3B8" stroke-width="1.2"/>
<path d="M7 5V8L9 9" stroke="#94A3B8" stroke-width="1.2" stroke-linecap="round"/>
<path d="M5 1H9" stroke="#94A3B8" stroke-width="1.2" stroke-linecap="round"/>
</svg>
Resets in <span class="timer-val" id="timer">18:23:47</span>
</div>
<!-- Upgrade CTA -->
<a class="btn-prismatic" href="58-upgrade-modal.html">
See Plus — from $9.99/mo
</a>
<a class="dismiss-btn" href="../turn/10-turn-home.html">Got it, see you tomorrow</a>
</div>
</div>
</div>
<script>
// Countdown timer simulation
let seconds = 18 * 3600 + 23 * 60 + 47;
const timerEl = document.getElementById('timer');
function updateTimer() {
if (seconds <= 0) return;
seconds--;
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
timerEl.textContent = `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
}
setInterval(updateTimer, 1000);
</script>
</body>
</html>

View File

@@ -0,0 +1,274 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Crisis Response</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.device-frame {
background: var(--void);
}
.screen-content {
display: flex;
flex-direction: column;
padding: 0 20px 24px;
}
.hero-section {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 24px;
padding-bottom: 20px;
}
.ruby-aura {
position: absolute;
top: 80px;
left: 50%;
transform: translateX(-50%);
width: 240px;
height: 240px;
border-radius: 50%;
background: radial-gradient(circle, rgba(239,68,68,0.12) 0%, transparent 70%);
filter: blur(40px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.hero-icon {
width: 72px;
height: 72px;
border-radius: 50%;
background: rgba(239,68,68,0.12);
border: 2px solid rgba(239,68,68,0.3);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
position: relative;
z-index: 1;
animation: breathing 6s ease-in-out infinite;
}
.hero-title {
font-size: 26px;
font-weight: 700;
font-family: var(--font-display);
color: var(--ruby);
text-align: center;
margin-bottom: 8px;
position: relative;
z-index: 1;
}
.hero-subtitle {
font-size: 15px;
color: var(--dim-light);
text-align: center;
line-height: 1.5;
position: relative;
z-index: 1;
}
.resources-section {
margin-bottom: 16px;
}
.resource-card {
background: var(--kalei-navy);
border: 1px solid rgba(239,68,68,0.2);
border-radius: var(--radius-xl);
padding: 16px;
margin-bottom: 10px;
}
.resource-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 4px;
}
.resource-icon {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(239,68,68,0.12);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.resource-name {
font-size: 15px;
font-weight: 600;
color: var(--pure-light);
}
.resource-detail {
font-size: 13px;
color: var(--dim-light);
margin-top: 2px;
margin-left: 48px;
}
.divider { height: 1px; background: rgba(239,68,68,0.1); margin: 8px 0; }
.btn-ruby {
width: 100%;
height: 52px;
border: none;
border-radius: var(--radius-lg);
background: var(--ruby);
color: white;
font-size: 16px;
font-weight: 600;
font-family: var(--font-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
text-decoration: none;
margin-bottom: 10px;
box-shadow: 0 0 20px rgba(239,68,68,0.3);
transition: background 0.2s;
}
.btn-ruby:hover { background: var(--ruby); filter: brightness(0.85); }
.btn-okay {
width: 100%;
height: 44px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
color: var(--soft-light);
font-size: 15px;
font-weight: 500;
font-family: var(--font-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
margin-bottom: 10px;
transition: background 0.2s;
}
.btn-okay:hover { background: var(--twilight); }
.breathing-link {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
font-size: 13px;
color: var(--dim-light);
text-decoration: none;
padding: 8px;
transition: color 0.2s;
}
.breathing-link:hover { color: var(--soft-light); }
.safe-note {
font-size: 11px;
color: var(--faint-light);
text-align: center;
line-height: 1.5;
margin-top: 4px;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<div class="ruby-aura"></div>
<div class="screen-content">
<!-- Hero -->
<div class="hero-section">
<div class="hero-icon">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M16 4L28 16L16 28L4 16Z" fill="#EF4444" opacity="0.3"/>
<path d="M16 8L24 16L16 24L8 16Z" fill="#EF4444" opacity="0.5"/>
<!-- Heart shape -->
<path d="M11 13.5C11 12.1 12.1 11 13.5 11C14.3 11 15 11.4 15.5 12C16 11.4 16.7 11 17.5 11C18.9 11 20 12.1 20 13.5C20 16 15.5 20 15.5 20C15.5 20 11 16 11 13.5Z" fill="#FCA5A5" opacity="0.9"/>
</svg>
</div>
<div class="hero-title">You're not alone in this</div>
<div class="hero-subtitle">Whatever you're carrying right now — it's real, and it matters. You don't have to face it alone. Trained people are ready to listen, right now, any hour.</div>
</div>
<!-- Crisis resources -->
<div class="resources-section">
<!-- 988 Lifeline -->
<div class="resource-card">
<div class="resource-header">
<div class="resource-icon">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<rect x="3" y="3" width="12" height="12" rx="2" stroke="#EF4444" stroke-width="1.2"/>
<path d="M6 7C6 7 7.5 6 9 7C10.5 8 12 7 12 7" stroke="#EF4444" stroke-width="1" stroke-linecap="round"/>
<path d="M6 10.5C7.5 12 10.5 12 12 10.5" stroke="#EF4444" stroke-width="1" stroke-linecap="round"/>
<circle cx="7" cy="9" r="0.8" fill="#EF4444"/>
<circle cx="11" cy="9" r="0.8" fill="#EF4444"/>
</svg>
</div>
<div class="resource-name">988 Suicide &amp; Crisis Lifeline</div>
</div>
<div class="resource-detail">Call or text 988 — free, confidential, 24/7</div>
</div>
<!-- Crisis Text Line -->
<div class="resource-card">
<div class="resource-header">
<div class="resource-icon">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<rect x="2" y="4" width="14" height="10" rx="2" stroke="#EF4444" stroke-width="1.2"/>
<path d="M6 14L4 16" stroke="#EF4444" stroke-width="1.2" stroke-linecap="round"/>
<path d="M5 8H13M5 11H10" stroke="#EF4444" stroke-width="1" stroke-linecap="round"/>
</svg>
</div>
<div class="resource-name">Crisis Text Line</div>
</div>
<div class="resource-detail">Text HOME to 741741 — text-based support, 24/7</div>
</div>
</div>
<!-- Primary CTA: Call or text 988 -->
<a class="btn-ruby" href="tel:988">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M6.5 3.5C6.5 3.5 5 3 3.5 5S3 9 5.5 11.5S10 15 12.5 14.5S15.5 13.5 15.5 12L13 10.5C13 10.5 12 10 11 11C10 12 9.5 11.5 7.5 9.5S7 7 8 6L6.5 3.5Z" fill="white" opacity="0.9"/>
</svg>
Call or text 988 — free, 24/7
</a>
<!-- Secondary: I'm okay -->
<a class="btn-okay" href="../turn/10-turn-home.html">
I'm okay right now
</a>
<!-- Breathing exercise link -->
<a class="breathing-link" href="../ritual/47-ritual-quick.html">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<circle cx="7" cy="7" r="5" stroke="#94A3B8" stroke-width="1.2" stroke-dasharray="2 2"/>
<circle cx="7" cy="7" r="2" fill="#94A3B8" opacity="0.5"/>
</svg>
Try a grounding exercise instead
</a>
<div class="safe-note">
If you or someone else is in immediate danger, call 911.<br>
Kalei is a support tool, not a substitute for emergency care.
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,339 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Share Pattern</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
/* Blurred gallery background */
.bg-content {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
filter: blur(3px);
opacity: 0.35;
}
.bg-status {
height: 54px;
padding: 14px 28px 0;
display: flex;
justify-content: space-between;
}
.bg-header {
height: 56px;
background: rgba(28,34,64,0.3);
}
.bg-pattern-card {
margin: 16px;
height: 260px;
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-2xl);
}
.modal-overlay {
position: absolute;
inset: 0;
background: rgba(5,5,8,0.7);
backdrop-filter: blur(8px);
display: flex;
align-items: flex-end;
z-index: 50;
}
.share-sheet {
width: 100%;
background: var(--kalei-navy);
border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
padding: 0 0 32px;
border-top: 1px solid var(--twilight);
}
.sheet-handle {
width: 36px;
height: 4px;
background: var(--twilight);
border-radius: var(--radius-full);
margin: 14px auto 18px;
}
.sheet-title {
font-size: 16px;
font-weight: 600;
color: var(--pure-light);
text-align: center;
margin-bottom: 18px;
}
/* Pattern card preview */
.pattern-preview {
margin: 0 16px 20px;
background: var(--void);
border-radius: var(--radius-xl);
border: 1px solid var(--twilight);
overflow: hidden;
}
.pattern-visual-area {
height: 120px;
display: flex;
align-items: center;
justify-content: center;
background: var(--void);
position: relative;
}
.pattern-aura {
position: absolute;
width: 120px;
height: 120px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.15) 0%, transparent 70%);
filter: blur(30px);
animation: breathing 6s ease-in-out infinite;
}
.pattern-content-area {
padding: 12px 14px;
border-top: 1px solid var(--twilight);
}
.pattern-thought {
font-size: 13px;
color: var(--soft-light);
line-height: 1.4;
margin-bottom: 6px;
font-style: italic;
}
.pattern-reframe {
font-size: 12px;
color: var(--amethyst-light);
margin-bottom: 8px;
}
.pattern-branding {
display: flex;
align-items: center;
gap: 6px;
}
.branding-logo {
width: 16px;
height: 16px;
}
.branding-text {
font-size: 10px;
font-weight: 600;
color: var(--faint-light);
letter-spacing: 0.04em;
}
/* Share targets */
.share-targets {
display: flex;
justify-content: space-around;
padding: 0 20px;
margin-bottom: 16px;
}
.share-target {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
cursor: pointer;
background: transparent;
border: none;
padding: 8px;
border-radius: var(--radius-lg);
transition: background 0.15s;
text-decoration: none;
}
.share-target:hover { background: rgba(28,34,64,0.5); }
.share-target-icon {
width: 52px;
height: 52px;
border-radius: 14px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
display: flex;
align-items: center;
justify-content: center;
}
.share-target-label {
font-size: 11px;
color: var(--dim-light);
font-family: var(--font-primary);
}
.close-row {
padding: 0 16px;
}
.close-btn {
width: 100%;
height: 48px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-lg);
color: var(--soft-light);
font-size: 15px;
font-weight: 500;
font-family: var(--font-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
transition: background 0.15s;
}
.close-btn:hover { background: var(--twilight); }
.copied-toast {
position: absolute;
bottom: 320px;
left: 50%;
transform: translateX(-50%);
background: var(--deep-glass);
border: 1px solid var(--emerald);
border-radius: var(--radius-full);
padding: 8px 20px;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 0 16px rgba(16,185,129,0.2);
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
white-space: nowrap;
z-index: 60;
}
.copied-toast.visible { opacity: 1; }
</style>
</head>
<body>
<div class="device-frame">
<!-- Blurred gallery background -->
<div class="bg-content" aria-hidden="true">
<div class="bg-status">
<span style="font-size:15px; font-weight:600; color:var(--pure-light);">9:41</span>
</div>
<div class="bg-header"></div>
<div class="bg-pattern-card"></div>
</div>
<!-- Copied toast -->
<div class="copied-toast" id="copiedToast">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M2 7L5.5 10.5L12 3" stroke="#10B981" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span style="font-size:13px; color:var(--soft-light); font-weight:500;">Link copied</span>
</div>
<!-- Share sheet modal -->
<div class="modal-overlay">
<div class="share-sheet">
<div class="sheet-handle"></div>
<div class="sheet-title">Share Pattern</div>
<!-- Pattern card preview -->
<div class="pattern-preview">
<div class="pattern-visual-area">
<div class="pattern-aura"></div>
<svg width="100" height="100" viewBox="0 0 100 100" style="mix-blend-mode: screen; position:relative; z-index:1;">
<defs>
<linearGradient id="sb1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD" stop-opacity="0.8"/>
<stop offset="100%" stop-color="#7C3AED" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="sb2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7" stop-opacity="0.7"/>
<stop offset="100%" stop-color="#059669" stop-opacity="0.2"/>
</linearGradient>
<linearGradient id="sb3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A" stop-opacity="0.6"/>
<stop offset="100%" stop-color="#D97706" stop-opacity="0.2"/>
</linearGradient>
</defs>
<g transform="translate(50,50)">
<g><path d="M0,0 L6,-32 L0,-38 L-6,-32Z" fill="url(#sb1)"/></g>
<g transform="rotate(60)"><path d="M0,0 L6,-32 L0,-38 L-6,-32Z" fill="url(#sb2)"/></g>
<g transform="rotate(120)"><path d="M0,0 L6,-32 L0,-38 L-6,-32Z" fill="url(#sb3)"/></g>
<g transform="rotate(180)"><path d="M0,0 L6,-32 L0,-38 L-6,-32Z" fill="url(#sb1)"/></g>
<g transform="rotate(240)"><path d="M0,0 L6,-32 L0,-38 L-6,-32Z" fill="url(#sb2)"/></g>
<g transform="rotate(300)"><path d="M0,0 L6,-32 L0,-38 L-6,-32Z" fill="url(#sb3)"/></g>
<circle r="8" fill="white" opacity="0.12"/>
</g>
</svg>
</div>
<div class="pattern-content-area">
<div class="pattern-thought">"I completely bombed my presentation today..."</div>
<div class="pattern-reframe">One hard moment doesn't define the arc. What's one thing you learned that you couldn't have without this?</div>
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:6px;">
<span style="font-size:10px; color:var(--faint-light);">Feb 22, 2026 · Catastrophizing</span>
<span style="font-size:10px; color:var(--emerald-light); font-weight:600;">+52% lift</span>
</div>
<div class="pattern-branding">
<svg class="branding-logo" width="16" height="16" viewBox="0 0 16 16">
<g transform="translate(8,8)">
<path d="M0,0 L2,-6 L0,-8 L-2,-6Z" fill="#8B5CF6" opacity="0.7"/>
<path d="M0,0 L2,-6 L0,-8 L-2,-6Z" fill="#3B82F6" opacity="0.5" transform="rotate(120)"/>
<path d="M0,0 L2,-6 L0,-8 L-2,-6Z" fill="#10B981" opacity="0.5" transform="rotate(240)"/>
</g>
</svg>
<span class="branding-text">Made with Kalei</span>
</div>
</div>
</div>
<!-- Share targets -->
<div class="share-targets">
<button class="share-target" onclick="copyLink()">
<div class="share-target-icon">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none">
<path d="M9 13L13 9M8 10L6 12C5 13 5 15 6 16C7 17 9 17 10 16L12 14" stroke="#E2E8F0" stroke-width="1.4" stroke-linecap="round"/>
<path d="M14 12L16 10C17 9 17 7 16 6C15 5 13 5 12 6L10 8" stroke="#E2E8F0" stroke-width="1.4" stroke-linecap="round"/>
</svg>
</div>
<span class="share-target-label">Copy Link</span>
</button>
<button class="share-target" onclick="shareMessages()">
<div class="share-target-icon">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none">
<rect x="3" y="5" width="16" height="12" rx="3" stroke="#E2E8F0" stroke-width="1.4"/>
<path d="M3 17L7 14" stroke="#E2E8F0" stroke-width="1.4" stroke-linecap="round"/>
<path d="M7 10H15M7 13H12" stroke="#E2E8F0" stroke-width="1" stroke-linecap="round"/>
</svg>
</div>
<span class="share-target-label">Messages</span>
</button>
<button class="share-target" onclick="shareMore()">
<div class="share-target-icon">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none">
<circle cx="7" cy="11" r="1.5" fill="#E2E8F0"/>
<circle cx="11" cy="11" r="1.5" fill="#E2E8F0"/>
<circle cx="15" cy="11" r="1.5" fill="#E2E8F0"/>
</svg>
</div>
<span class="share-target-label">More...</span>
</button>
</div>
<!-- Close -->
<div class="close-row">
<a class="close-btn" href="../gallery/33-gallery-detail.html">Close</a>
</div>
</div>
</div>
</div>
<script>
function copyLink() {
const toast = document.getElementById('copiedToast');
toast.classList.add('visible');
setTimeout(() => toast.classList.remove('visible'), 2000);
}
function shareMessages() {
// Simulated share action
alert('Opening Messages...');
}
function shareMore() {
// Simulated share sheet
alert('Opening share sheet...');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,250 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Splash</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.splash-container {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-5);
background: var(--void);
cursor: pointer;
user-select: none;
text-decoration: none;
}
.logo-wrap {
position: relative;
width: 140px;
height: 140px;
display: flex;
align-items: center;
justify-content: center;
animation: logoFadeIn 0.8s ease-out forwards;
opacity: 0;
}
.kalei-wordmark {
font-family: var(--font-display);
font-size: 32px;
font-weight: 700;
color: var(--soft-light);
letter-spacing: 0.05em;
animation: logoFadeIn 0.8s ease-out 0.4s forwards;
opacity: 0;
}
.tap-hint {
position: absolute;
bottom: 60px;
color: var(--faint-light);
letter-spacing: 0.08em;
animation: hintPulse 2s ease-in-out infinite;
animation-delay: 2s;
opacity: 0;
}
@keyframes logoFadeIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
@keyframes hintPulse {
0% { opacity: 0; }
50% { opacity: 0.5; }
100% { opacity: 0; }
}
</style>
</head>
<body>
<div class="device-frame">
<a class="splash-container" href="02-welcome.html">
<div class="logo-wrap">
<!-- soft-elegance-final.svg — the actual Kalei logo, scaled to 160x160 -->
<svg viewBox="0 0 400 400" width="160" height="160">
<defs>
<linearGradient id="g0_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#A78BFA"><animate attributeName="stop-color" values="#A78BFA;#C4B5FD;#A78BFA" dur="5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#8B5CF6"><animate attributeName="stop-color" values="#8B5CF6;#A78BFA;#8B5CF6" dur="5s" repeatCount="indefinite"/></stop>
</linearGradient>
<linearGradient id="g0_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#8B5CF6"/><stop offset="100%" stop-color="#5B21B6"/>
</linearGradient>
<linearGradient id="g1_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#93C5FD"><animate attributeName="stop-color" values="#93C5FD;#BFDBFE;#93C5FD" dur="5.5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#3B82F6"/>
</linearGradient>
<linearGradient id="g1_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3B82F6"/><stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
<linearGradient id="g2_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#6EE7B7"><animate attributeName="stop-color" values="#6EE7B7;#A7F3D0;#6EE7B7" dur="6s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#10B981"/>
</linearGradient>
<linearGradient id="g2_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#10B981"/><stop offset="100%" stop-color="#047857"/>
</linearGradient>
<linearGradient id="g3_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#FCD34D"><animate attributeName="stop-color" values="#FCD34D;#FDE68A;#FCD34D" dur="4.8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#F59E0B"/>
</linearGradient>
<linearGradient id="g3_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#F59E0B"/><stop offset="100%" stop-color="#B45309"/>
</linearGradient>
<linearGradient id="g4_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#F9A8D4"><animate attributeName="stop-color" values="#F9A8D4;#FBCFE8;#F9A8D4" dur="5.2s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#EC4899"/>
</linearGradient>
<linearGradient id="g4_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#EC4899"/><stop offset="100%" stop-color="#BE185D"/>
</linearGradient>
<linearGradient id="g5_F1" x1="20" y1="17" x2="-30" y2="160" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#A5B4FC"><animate attributeName="stop-color" values="#A5B4FC;#C7D2FE;#A5B4FC" dur="5.8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#6366F1"/>
</linearGradient>
<linearGradient id="g5_F2" x1="40" y1="0" x2="140" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#6366F1"/><stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<radialGradient id="prismatic" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#FFFFFF"><animate attributeName="stop-color" values="#FFFFFF;#E0D5FF;#FFFFFF" dur="5s" repeatCount="indefinite"/></stop>
<stop offset="40%" stop-color="#8B5CF6"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="8s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="8s" repeatCount="indefinite"/></stop>
</radialGradient>
<radialGradient id="outerAura" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#8B5CF6" stop-opacity="0.08"><animate attributeName="stop-color" values="#8B5CF6;#3B82F6;#10B981;#F59E0B;#EC4899;#8B5CF6" dur="10s" repeatCount="indefinite"/></stop>
<stop offset="70%" stop-color="#8B5CF6" stop-opacity="0.03"/>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="coreHalo" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0"/>
<stop offset="60%" stop-color="#FFFFFF" stop-opacity="0"/>
<stop offset="80%" stop-color="#C4B5FD" stop-opacity="0.08"><animate attributeName="stop-color" values="#C4B5FD;#93C5FD;#6EE7B7;#FDE68A;#FBCFE8;#C4B5FD" dur="8s" repeatCount="indefinite"/><animate attributeName="stop-opacity" values="0.05;0.12;0.05" dur="4s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"/>
</radialGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="coreGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="12" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="shimmer" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="wideGlow" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="18" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="bladeTipGlow" x="-200%" y="-200%" width="500%" height="500%">
<feGaussianBlur stdDeviation="5" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<circle cx="200" cy="200" r="190" fill="url(#outerAura)">
<animate attributeName="r" values="175;195;175" dur="6s" repeatCount="indefinite"/>
</circle>
<g transform="translate(200, 200)">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="90s" repeatCount="indefinite" additive="sum"/>
<g transform="rotate(0)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g0_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round"><animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4s" repeatCount="indefinite"/></path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g0_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"><animate attributeName="fill-opacity" values="0.90;1;0.90" dur="4s" repeatCount="indefinite"/></path>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#C4B5FD" stroke-width="1.2" opacity="0"><animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="0s" repeatCount="indefinite"/></line>
</g>
<g transform="rotate(60)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g1_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round"><animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4.5s" repeatCount="indefinite"/></path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g1_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#93C5FD" stroke-width="1.2" opacity="0"><animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="0.83s" repeatCount="indefinite"/></line>
</g>
<g transform="rotate(120)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g2_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round"><animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="5s" repeatCount="indefinite"/></path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g2_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#6EE7B7" stroke-width="1.2" opacity="0"><animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="1.66s" repeatCount="indefinite"/></line>
</g>
<g transform="rotate(180)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g3_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round"><animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4.2s" repeatCount="indefinite"/></path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g3_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#FDE68A" stroke-width="1.2" opacity="0"><animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="2.5s" repeatCount="indefinite"/></line>
</g>
<g transform="rotate(240)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g4_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round"><animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4.8s" repeatCount="indefinite"/></path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g4_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#FBCFE8" stroke-width="1.2" opacity="0"><animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="3.33s" repeatCount="indefinite"/></line>
</g>
<g transform="rotate(300)" style="mix-blend-mode: screen;">
<path d="M 40,0 L -30,160 L 140,60 Z" fill="url(#g5_F2)" fill-opacity="0.80" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.4" stroke-linejoin="round"><animate attributeName="fill-opacity" values="0.75;0.90;0.75" dur="4.3s" repeatCount="indefinite"/></path>
<path d="M 40,0 L 20,34.641 L -30,160 Z" fill="url(#g5_F1)" fill-opacity="0.95" stroke="#ffffff" stroke-width="0.5" stroke-opacity="0.6" stroke-linejoin="round"/>
<line x1="40" y1="0" x2="-30" y2="160" stroke="#A5B4FC" stroke-width="1.2" opacity="0"><animate attributeName="opacity" values="0;0.5;0" dur="5s" begin="4.16s" repeatCount="indefinite"/></line>
</g>
<g filter="url(#bladeTipGlow)">
<circle cx="-30" cy="160" r="4" fill="#A78BFA" opacity="0"><animate attributeName="opacity" values="0;0.35;0" dur="4s" repeatCount="indefinite"/></circle>
<circle cx="-30" cy="160" r="4" fill="#3B82F6" opacity="0" transform="rotate(60)"><animate attributeName="opacity" values="0;0.35;0" dur="4.5s" begin="0.5s" repeatCount="indefinite"/></circle>
<circle cx="-30" cy="160" r="4" fill="#10B981" opacity="0" transform="rotate(120)"><animate attributeName="opacity" values="0;0.35;0" dur="5s" begin="1s" repeatCount="indefinite"/></circle>
<circle cx="-30" cy="160" r="4" fill="#F59E0B" opacity="0" transform="rotate(180)"><animate attributeName="opacity" values="0;0.35;0" dur="4.2s" begin="1.5s" repeatCount="indefinite"/></circle>
<circle cx="-30" cy="160" r="4" fill="#EC4899" opacity="0" transform="rotate(240)"><animate attributeName="opacity" values="0;0.35;0" dur="4.8s" begin="2s" repeatCount="indefinite"/></circle>
<circle cx="-30" cy="160" r="4" fill="#6366F1" opacity="0" transform="rotate(300)"><animate attributeName="opacity" values="0;0.35;0" dur="4.3s" begin="2.5s" repeatCount="indefinite"/></circle>
</g>
<g filter="url(#coreGlow)">
<circle r="45" fill="url(#prismatic)" opacity="0.35"><animate attributeName="opacity" values="0.25;0.45;0.25" dur="5s" repeatCount="indefinite"/></circle>
<circle r="55" fill="url(#coreHalo)"><animate attributeName="r" values="50;58;50" dur="4s" repeatCount="indefinite"/><animate attributeName="opacity" values="0.6;1;0.6" dur="4s" repeatCount="indefinite"/></circle>
<g opacity="0.35">
<animateTransform attributeName="transform" type="rotate" from="0" to="-360" dur="150s" repeatCount="indefinite"/>
<polygon points="0,0 2,35 -2,35" fill="#A78BFA"><animate attributeName="opacity" values="0.20;0.40;0.20" dur="4s" repeatCount="indefinite"/></polygon>
<polygon points="0,0 2,35 -2,35" fill="#3B82F6" transform="rotate(60)"><animate attributeName="opacity" values="0.20;0.40;0.20" dur="4.2s" begin="0.4s" repeatCount="indefinite"/></polygon>
<polygon points="0,0 2,35 -2,35" fill="#10B981" transform="rotate(120)"><animate attributeName="opacity" values="0.20;0.40;0.20" dur="4.4s" begin="0.8s" repeatCount="indefinite"/></polygon>
<polygon points="0,0 2,35 -2,35" fill="#F59E0B" transform="rotate(180)"><animate attributeName="opacity" values="0.20;0.40;0.20" dur="4s" begin="1.2s" repeatCount="indefinite"/></polygon>
<polygon points="0,0 2,35 -2,35" fill="#EC4899" transform="rotate(240)"><animate attributeName="opacity" values="0.20;0.40;0.20" dur="4.3s" begin="1.6s" repeatCount="indefinite"/></polygon>
<polygon points="0,0 2,35 -2,35" fill="#6366F1" transform="rotate(300)"><animate attributeName="opacity" values="0.20;0.40;0.20" dur="4.5s" begin="2s" repeatCount="indefinite"/></polygon>
</g>
<g opacity="0.3">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="75s" repeatCount="indefinite"/>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0"><animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4s" begin="0s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(30)"><animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.5s" begin="0.3s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(60)"><animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4.2s" begin="0.6s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(90)"><animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.8s" begin="0.9s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(120)"><animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4s" begin="1.2s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(150)"><animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.6s" begin="1.5s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(180)"><animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4.3s" begin="1.8s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(210)"><animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.7s" begin="2.1s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(240)"><animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4.1s" begin="2.4s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(270)"><animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.9s" begin="2.7s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="0" y2="50" stroke="#ffffff" stroke-width="0.6" stroke-linecap="round" opacity="0" transform="rotate(300)"><animate attributeName="opacity" values="0;0.30;0;0.15;0" dur="4s" begin="3s" repeatCount="indefinite"/></line>
<line x1="0" y1="0" x2="25" y2="43.301" stroke="#ffffff" stroke-width="0.4" stroke-linecap="round" opacity="0" transform="rotate(330)"><animate attributeName="opacity" values="0;0.20;0;0.10;0" dur="3.8s" begin="3.3s" repeatCount="indefinite"/></line>
</g>
<circle r="12" fill="#ffffff" filter="url(#coreGlow)" opacity="0.95"><animate attributeName="r" values="10;14;10" dur="3s" repeatCount="indefinite"/><animate attributeName="opacity" values="0.85;1;0.85" dur="3s" repeatCount="indefinite"/></circle>
<circle cx="-4" cy="-4" r="6" fill="#ffffff" opacity="0.2"><animate attributeName="opacity" values="0.2;0.35;0.2" dur="4s" repeatCount="indefinite"/></circle>
<circle cx="3" cy="-3" r="1.5" fill="#ffffff" opacity="0.9" filter="url(#glow)"><animate attributeName="opacity" values="0.6;1;0.6" dur="1.5s" repeatCount="indefinite"/></circle>
<circle cx="-2" cy="5" r="2" fill="#FDE68A" opacity="0"><animate attributeName="opacity" values="0;0.15;0" dur="5s" repeatCount="indefinite"/></circle>
</g>
<g filter="url(#shimmer)">
<circle r="2" fill="#A78BFA" opacity="0"><animateMotion dur="7s" repeatCount="indefinite" path="M0,-155 A155,155 0 1 1 -0.1,-155"/><animate attributeName="opacity" values="0;0.9;0;0;0" dur="7s" repeatCount="indefinite"/></circle>
<circle r="1.5" fill="#3B82F6" opacity="0"><animateMotion dur="9s" repeatCount="indefinite" path="M0,-140 A140,140 0 1 0 -0.1,-140"/><animate attributeName="opacity" values="0;0;0.8;0;0" dur="9s" repeatCount="indefinite"/></circle>
<circle r="1.5" fill="#10B981" opacity="0"><animateMotion dur="6s" repeatCount="indefinite" path="M0,-165 A165,165 0 1 1 -0.1,-165"/><animate attributeName="opacity" values="0;0.7;0;0.5;0" dur="6s" repeatCount="indefinite"/></circle>
<circle r="2" fill="#F59E0B" opacity="0"><animateMotion dur="11s" repeatCount="indefinite" path="M0,-130 A130,130 0 1 0 -0.1,-130"/><animate attributeName="opacity" values="0;0;0;0.9;0" dur="11s" repeatCount="indefinite"/></circle>
<circle r="1" fill="#EC4899" opacity="0"><animateMotion dur="8s" repeatCount="indefinite" path="M0,-150 A150,150 0 1 1 -0.1,-150"/><animate attributeName="opacity" values="0;0.8;0;0;0.6;0" dur="8s" repeatCount="indefinite"/></circle>
<circle r="1.5" fill="#fff" opacity="0"><animateMotion dur="5s" repeatCount="indefinite" path="M0,-120 A120,120 0 1 0 -0.1,-120"/><animate attributeName="opacity" values="0;1;0;0;0" dur="5s" repeatCount="indefinite"/></circle>
</g>
</g>
</svg>
</div>
<span class="kalei-wordmark">Kalei</span>
<span class="body-sm tap-hint">tap to continue</span>
</a>
</div>
<script>
setTimeout(function() {
window.location.href = '02-welcome.html';
}, 3500);
</script>
</body>
</html>

View File

@@ -0,0 +1,273 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Welcome</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-content.centered {
padding: var(--space-6);
position: relative;
}
.hero-area {
position: relative;
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-10);
z-index: 1;
}
.hero-aura {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 300px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.18) 0%, rgba(59,130,246,0.06) 50%, transparent 70%);
filter: blur(55px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.tagline-line1 {
display: block;
color: var(--pure-light);
}
.tagline-line2 {
display: block;
color: var(--amethyst-light);
}
.cta-area {
width: 100%;
margin-top: var(--space-8);
z-index: 1;
}
/* Floating shards background (decorative-shards.svg) */
.bg-shards {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content centered">
<!--
Floating shards background extracted from decorative-shards.svg → Floating Shards section
Shard paths preserved exactly, repositioned across the 390x730 screen
-->
<svg class="bg-shards" viewBox="0 0 390 730" fill="none">
<defs>
<linearGradient id="s02-grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="s02-grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/>
<stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="s02-grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="s02-grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="s02-grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/>
<stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<filter id="s02-glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="s02-glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="s02-shimmer" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- Shard 1 — large amethyst, top-left drift (from floating shards, shard 1 shape) -->
<g transform="translate(40, 140)" filter="url(#s02-glowMd)">
<path d="M 0,-18 L 10,-4 L 2,16 L -8,6 Z" fill="url(#s02-grAmethyst)" opacity="0.18">
<animate attributeName="opacity" values="0.1;0.25;0.1" dur="7s" repeatCount="indefinite"/>
</path>
<path d="M 0,-18 L 10,-4 L -8,6 Z" fill="#fff" opacity="0.06"/>
<animateTransform attributeName="transform" type="translate" values="40,140; 45,132; 40,140" dur="12s" repeatCount="indefinite"/>
</g>
<!-- Shard 2 — sapphire, top-right -->
<g transform="translate(340, 100)" filter="url(#s02-glowSm)">
<path d="M 0,-10 L 7,2 L -2,12 L -6,0 Z" fill="url(#s02-grSapphire)" opacity="0.15">
<animate attributeName="opacity" values="0.08;0.22;0.08" dur="5s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="340,100; 345,92; 340,100" dur="9s" repeatCount="indefinite"/>
</g>
<!-- Shard 3 — emerald, left mid -->
<g transform="translate(28, 460)" filter="url(#s02-glowSm)">
<path d="M 0,-6 L 5,0 L 0,8 L -4,2 Z" fill="url(#s02-grEmerald)" opacity="0.15">
<animate attributeName="opacity" values="0.08;0.2;0.08" dur="4s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="28,460; 33,452; 28,460" dur="7s" repeatCount="indefinite"/>
</g>
<!-- Shard 4 — rose, tiny mote top-right -->
<g transform="translate(360, 200)" filter="url(#s02-shimmer)">
<path d="M 0,-4 L 3,0 L 0,4 L -3,0 Z" fill="url(#s02-grRose)" opacity="0.22">
<animate attributeName="opacity" values="0.12;0.35;0.12" dur="3s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="360,200; 363,195; 360,200" dur="5s" repeatCount="indefinite"/>
</g>
<!-- Shard 5 — amber, lower-left -->
<g transform="translate(50, 580)" filter="url(#s02-shimmer)">
<path d="M 0,-5 L 4,0 L 0,5 L -4,0 Z" fill="url(#s02-grAmber)" opacity="0.15">
<animate attributeName="opacity" values="0.08;0.22;0.08" dur="6s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="50,580; 52,572; 50,580" dur="8s" repeatCount="indefinite"/>
</g>
<!-- Shard 6 — amethyst, lower-right -->
<g transform="translate(330, 560)" filter="url(#s02-glowSm)">
<path d="M 0,-8 L 6,0 L 0,8 L -6,0 Z" fill="url(#s02-grAmethyst)" opacity="0.12">
<animate attributeName="opacity" values="0.06;0.18;0.06" dur="5.5s" repeatCount="indefinite"/>
</path>
<animateTransform attributeName="transform" type="translate" values="330,560; 335,553; 330,560" dur="10s" repeatCount="indefinite"/>
</g>
</svg>
<div class="hero-area">
<div class="hero-aura"></div>
<!--
Complex 6-blade prismatic kaleidoscope extracted from patterns-kaleidoscope.svg → Complex variant
Adapted: centred at 100,100, 200x200 viewport with circular clip
-->
<svg width="200" height="200" viewBox="0 0 200 200" style="position:relative; z-index:1; animation: breathing 6s ease-in-out infinite;">
<defs>
<linearGradient id="s02-kAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD">
<animate attributeName="stop-color" values="#C4B5FD;#DDD6FE;#C4B5FD" dur="4s" repeatCount="indefinite"/>
</stop>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="s02-kSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD">
<animate attributeName="stop-color" values="#93C5FD;#BFDBFE;#93C5FD" dur="4.5s" repeatCount="indefinite"/>
</stop>
<stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="s02-kEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="s02-kAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="s02-kRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/>
<stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<linearGradient id="s02-kIndigo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A5B4FC"/>
<stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<radialGradient id="s02-coreGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fff" stop-opacity="0.3"/>
<stop offset="50%" stop-color="#8B5CF6" stop-opacity="0.1"/>
<stop offset="100%" stop-color="#8B5CF6" stop-opacity="0"/>
</radialGradient>
<filter id="s02-glowMdK" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="s02-glowSmK" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<clipPath id="s02-circClip">
<circle cx="100" cy="100" r="92"/>
</clipPath>
</defs>
<!-- Navy bg -->
<circle cx="100" cy="100" r="92" fill="#0A0E1A" stroke="#1C2240" stroke-width="0.5"/>
<!-- Complex 6-blade pattern (from patterns-kaleidoscope.svg → Complex variant, blade reach ~80px) -->
<g clip-path="url(#s02-circClip)">
<g filter="url(#s02-glowMdK)" transform="translate(100,100)">
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="70s" repeatCount="indefinite"/>
<g style="mix-blend-mode: screen;">
<path d="M 0,0 L 6,-2 L -4,80 L -6,1 Z" fill="url(#s02-kAmethyst)" opacity="0.5">
<animate attributeName="fill-opacity" values="0.4;0.6;0.4" dur="4s" repeatCount="indefinite"/>
</path>
<path d="M 0,0 L 6,-2 L -4,80 Z" fill="#fff" opacity="0.06"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(60)">
<path d="M 0,0 L 6,-2 L -4,80 L -6,1 Z" fill="url(#s02-kSapphire)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(120)">
<path d="M 0,0 L 6,-2 L -4,80 L -6,1 Z" fill="url(#s02-kEmerald)" opacity="0.4"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(180)">
<path d="M 0,0 L 6,-2 L -4,80 L -6,1 Z" fill="url(#s02-kAmber)" opacity="0.5"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(240)">
<path d="M 0,0 L 6,-2 L -4,80 L -6,1 Z" fill="url(#s02-kRose)" opacity="0.45"/>
</g>
<g style="mix-blend-mode: screen;" transform="rotate(300)">
<path d="M 0,0 L 6,-2 L -4,80 L -6,1 Z" fill="url(#s02-kIndigo)" opacity="0.4"/>
</g>
</g>
</g>
<!-- Core glow -->
<circle cx="100" cy="100" r="16" fill="url(#s02-coreGlow)" opacity="0.8">
<animate attributeName="opacity" values="0.6;1;0.6" dur="3s" repeatCount="indefinite"/>
</circle>
<circle cx="100" cy="100" r="6" fill="#fff" opacity="0.65" filter="url(#s02-glowSmK)">
<animate attributeName="r" values="5;8;5" dur="3s" repeatCount="indefinite"/>
</circle>
</g>
</svg>
</div>
<div style="z-index:1;">
<span class="display-lg tagline-line1">Same pieces.</span>
<span class="display-lg tagline-line2">New angle.</span>
</div>
<p class="body text-dim" style="margin-top: var(--space-4); z-index:1;">
Your thoughts haven't changed. Only the angle has. Kalei helps you turn the same fragments until something new comes into focus.
</p>
<div class="cta-area">
<a href="03-fragment-intro.html" class="btn btn-primary" style="text-decoration:none;">See how it works</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,315 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Fragment Intro</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-content.centered {
padding: var(--space-8) var(--space-6);
position: relative;
overflow: hidden;
}
/* Floating shards background */
.shards-bg {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 0;
}
/* Fragment hero area */
.fragment-area {
position: relative;
width: 140px;
height: 140px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-10);
cursor: pointer;
z-index: 1;
}
.fragment-aura {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(245,158,11,0.18) 0%, transparent 70%);
filter: blur(28px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.fragment-svg-wrap {
position: relative;
z-index: 1;
animation: fragmentPulse 2s ease-in-out infinite;
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.fragment-svg-wrap.tapped {
animation: none;
transform: scale(1.35);
}
@keyframes fragmentPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.07); }
}
/* Glow ring around the fragment (box-shadow approach, no filter:drop-shadow) */
.fragment-glow-ring {
position: absolute;
top: 50%;
left: 50%;
width: 80px;
height: 80px;
margin-top: -40px;
margin-left: -40px;
border-radius: var(--radius-sm);
box-shadow: 0 0 30px rgba(245,158,11,0.35), 0 0 60px rgba(245,158,11,0.15);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
transform: rotate(45deg);
}
/* Copy sections */
.copy-block {
position: relative;
z-index: 1;
text-align: center;
}
.copy-initial {
transition: opacity 0.4s ease-out;
}
.copy-initial.hidden {
opacity: 0;
pointer-events: none;
}
.copy-continued {
opacity: 0;
transform: translateY(10px);
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
display: none;
}
.copy-continued.visible {
opacity: 1;
transform: translateY(0);
display: block;
}
.tap-hint-text {
color: var(--faint-light);
margin-top: var(--space-3);
animation: hintPulse 2s ease-in-out infinite;
}
.text-amber {
color: var(--amber);
}
@keyframes hintPulse {
0% { opacity: 0; }
50% { opacity: 0.6; }
100% { opacity: 0; }
}
/* CTA pinned to bottom */
.cta-area {
position: absolute;
bottom: var(--space-10);
left: var(--space-6);
right: var(--space-6);
z-index: 2;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content centered">
<!--
Floating Shards background — extracted from decorative-shards.svg → Floating Shards section
5 shaped shard paths repositioned across 390×730 screen area, amber/gold tones
-->
<svg class="shards-bg" width="390" height="730" viewBox="0 0 390 730" fill="none">
<defs>
<filter id="s03-glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="s03-glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- Shard 1 — upper left — amber shaped (from decorative-shards.svg path 1) -->
<g transform="translate(48,160)" filter="url(#s03-glowSm)" opacity="0.55">
<path d="M 0,-18 L 10,-4 L 2,16 L -8,6 Z" fill="#FDE68A" transform="rotate(20)">
<animate attributeName="opacity" values="0.4;0.7;0.4" dur="5s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;2,-3;0,0" dur="5s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Shard 2 — upper right — amber shaped (from decorative-shards.svg path 2) -->
<g transform="translate(340,100)" filter="url(#s03-glowSm)" opacity="0.45">
<path d="M 0,-14 L 8,0 L 0,14 L -8,0 Z" fill="#F59E0B" transform="rotate(-30)">
<animate attributeName="opacity" values="0.3;0.6;0.3" dur="7s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;-2,2;0,0" dur="7s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Shard 3 — mid left — elongated amber (from decorative-shards.svg path 3) -->
<g transform="translate(30,400)" filter="url(#s03-glowSm)" opacity="0.4">
<path d="M 0,-22 L 6,-5 L 0,22 L -6,-5 Z" fill="#FDE68A" transform="rotate(15)">
<animate attributeName="opacity" values="0.25;0.55;0.25" dur="9s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;3,4;0,0" dur="9s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Shard 4 — mid right — small amber (from decorative-shards.svg path 4) -->
<g transform="translate(355,350)" filter="url(#s03-glowSm)" opacity="0.5">
<path d="M 0,-12 L 8,4 L -2,12 L -8,-2 Z" fill="#D97706" transform="rotate(-20)">
<animate attributeName="opacity" values="0.35;0.65;0.35" dur="6s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;-3,-2;0,0" dur="6s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Shard 5 — lower left — medium amber (from decorative-shards.svg path 5) -->
<g transform="translate(65,560)" filter="url(#s03-glowMd)" opacity="0.35">
<path d="M 0,-16 L 10,0 L 2,16 L -10,4 Z" fill="#FDE68A" transform="rotate(35)">
<animate attributeName="opacity" values="0.2;0.45;0.2" dur="8s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;2,5;0,0" dur="8s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Shard 6 — lower right — small accent -->
<g transform="translate(325,520)" filter="url(#s03-glowSm)" opacity="0.4">
<path d="M 0,-10 L 6,0 L 0,10 L -6,0 Z" fill="#F59E0B" transform="rotate(-10)">
<animate attributeName="opacity" values="0.3;0.5;0.3" dur="4s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;-1,-3;0,0" dur="4s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
</svg>
<!--
Fragment hero — extracted from fragment-icons.svg → Amber LG fragment
Centred at 0,0 in 140×140 viewport (translate 70,70)
Paths: M 0,-18 L 18,0 L 0,18 L -18,0 Z (LG = 36px diameter, radius 18)
Filters and gradients prefixed s03-
-->
<div class="fragment-area" id="fragmentArea" onclick="handleTap()">
<div class="fragment-aura"></div>
<div class="fragment-glow-ring"></div>
<svg class="fragment-svg-wrap" id="fragmentSvg" width="140" height="140" viewBox="0 0 140 140">
<defs>
<linearGradient id="s03-grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<filter id="s03-glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="6" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="s03-glowPulse" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(70,70)">
<!-- Outer active pulse ring — extracted from fragment-icons.svg active/detected state -->
<path d="M 0,-26 L 26,0 L 0,26 L -26,0 Z" fill="none" stroke="#FDE68A" stroke-width="1" opacity="0" filter="url(#s03-glowPulse)">
<animate attributeName="opacity" values="0;0.5;0" dur="2s" repeatCount="indefinite"/>
<animate attributeName="stroke-width" values="1;2;1" dur="2s" repeatCount="indefinite"/>
</path>
<!-- LG Amber fragment — extracted from fragment-icons.svg → Amber LG (radius 18) -->
<g filter="url(#s03-glowLg)">
<path d="M 0,-18 L 18,0 L 0,18 L -18,0 Z" fill="url(#s03-grAmber)" opacity="0.9"/>
<!-- Primary specular highlight (top-right facet) -->
<path d="M 0,-18 L 18,0 L 0,0 Z" fill="#fff" opacity="0.15"/>
<!-- Secondary shadow facet (bottom-left) -->
<path d="M 0,-18 L -18,0 L 0,0 Z" fill="#fff" opacity="0.08"/>
<!-- Vertical centre line -->
<line x1="0" y1="-18" x2="0" y2="18" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<!-- Horizontal centre line -->
<line x1="-18" y1="0" x2="18" y2="0" stroke="#fff" stroke-width="0.4" opacity="0.12"/>
<!-- Highlight dot (upper-left) -->
<circle cx="-4" cy="-4" r="2" fill="#fff" opacity="0.3"/>
</g>
</g>
</svg>
</div>
<!-- Initial copy — before tap -->
<div class="copy-block copy-initial" id="copyInitial">
<h2 class="heading" style="color: var(--pure-light); margin-bottom: var(--space-3);">Every thought is a fragment.</h2>
<p class="body text-dim">Shaped by experience, colored by emotion. The same piece of glass can look like a wound or a window — depending on how the light hits it.</p>
<p class="body-sm tap-hint-text">Tap the fragment to continue</p>
</div>
<!-- Continued copy — after tap -->
<div class="copy-block copy-continued" id="copyContinued">
<h2 class="heading" style="color: var(--pure-light); margin-bottom: var(--space-3);">The pieces don't change. The angle does.</h2>
<p class="body text-dim" style="margin-bottom: var(--space-4);">Kalei doesn't tell you your feelings are wrong. It shows you there are other ways to arrange the same fragments — until a new pattern emerges.</p>
<p class="body text-amber">That's the Turn.</p>
</div>
<!-- CTA -->
<div class="cta-area">
<a href="04-turn-demo.html" class="btn btn-primary" id="nextBtn" style="display:none; text-decoration:none;">Next</a>
</div>
</div>
</div>
<script>
var tapped = false;
function handleTap() {
if (tapped) return;
tapped = true;
var svg = document.getElementById('fragmentSvg');
var initial = document.getElementById('copyInitial');
var continued = document.getElementById('copyContinued');
var nextBtn = document.getElementById('nextBtn');
svg.classList.add('tapped');
setTimeout(function() {
initial.classList.add('hidden');
continued.classList.add('visible');
nextBtn.style.display = 'flex';
}, 300);
setTimeout(function() {
svg.classList.remove('tapped');
}, 600);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Turn Demo</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-content {
padding: var(--space-5) var(--space-4) 100px;
}
.thought-card {
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4) var(--space-5);
margin-bottom: var(--space-5);
transition: opacity 0.3s ease-out, transform 0.3s ease-out;
}
.thought-card.collapsing {
animation: collapseCard 0.5s ease forwards;
}
@keyframes collapseCard {
0% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(0.85) translateY(-10px); }
}
.animation-stage {
height: 110px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: var(--space-5);
opacity: 0;
transition: opacity 0.3s ease;
}
.animation-stage.visible { opacity: 1; }
.turn-fragment {
position: absolute;
opacity: 0;
}
.turn-fragment.f1 { animation: fragmentF1 1.2s ease-out 0.1s forwards; }
.turn-fragment.f2 { animation: fragmentF2 1.2s ease-out 0.2s forwards; }
.turn-fragment.f3 { animation: fragmentF3 1.2s ease-out 0.3s forwards; }
@keyframes fragmentF1 {
0% { opacity: 0; transform: scale(0.2); }
40% { opacity: 1; transform: scale(1.3) translateX(-50px) translateY(-20px); }
100% { opacity: 1; transform: scale(1) translateX(-55px) translateY(-10px); }
}
@keyframes fragmentF2 {
0% { opacity: 0; transform: scale(0.2); }
40% { opacity: 1; transform: scale(1.3) translateY(-40px); }
100% { opacity: 1; transform: scale(1) translateY(-15px); }
}
@keyframes fragmentF3 {
0% { opacity: 0; transform: scale(0.2); }
40% { opacity: 1; transform: scale(1.3) translateX(50px) translateY(-20px); }
100% { opacity: 1; transform: scale(1) translateX(55px) translateY(-10px); }
}
.reframes-area {
opacity: 0;
transform: translateY(12px);
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}
.reframes-area.visible {
opacity: 1;
transform: translateY(0);
}
.distortion-row {
display: flex;
gap: var(--space-2);
margin-bottom: var(--space-3);
flex-wrap: wrap;
}
.cta-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 60%, transparent);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content">
<p class="label text-dim" style="margin-bottom: var(--space-3);">Watch the Turn in action</p>
<div class="thought-card" id="thoughtCard">
<p class="body-lg text-soft" style="font-style: italic;">"I completely bombed my presentation today and everyone saw how bad it was."</p>
</div>
<!--
Turn animation fragments — extracted from fragment-icons.svg
MD size (radius 12), centred at 0,0 in 28×28 viewport (translate 14,14)
Three jewel colors: Amethyst (f1), Sapphire (f2), Emerald (f3)
Gradient IDs prefixed s04- to avoid conflicts
-->
<div class="animation-stage" id="animStage">
<!-- Fragment 1 — Amethyst MD (from fragment-icons.svg Amethyst MD) -->
<div class="turn-fragment f1">
<svg width="28" height="28" viewBox="0 0 28 28">
<defs>
<linearGradient id="s04-grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<filter id="s04-glowMdA" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(14,14)" filter="url(#s04-glowMdA)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#s04-grAmethyst)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<!-- Fragment 2 — Sapphire MD (from fragment-icons.svg Sapphire MD) -->
<div class="turn-fragment f2">
<svg width="28" height="28" viewBox="0 0 28 28">
<defs>
<linearGradient id="s04-grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/>
<stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
<filter id="s04-glowMdB" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(14,14)" filter="url(#s04-glowMdB)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#s04-grSapphire)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<!-- Fragment 3 — Emerald MD (from fragment-icons.svg Emerald MD) -->
<div class="turn-fragment f3">
<svg width="28" height="28" viewBox="0 0 28 28">
<defs>
<linearGradient id="s04-grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#047857"/>
</linearGradient>
<filter id="s04-glowMdC" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(14,14)" filter="url(#s04-glowMdC)">
<path d="M 0,-12 L 12,0 L 0,12 L -12,0 Z" fill="url(#s04-grEmerald)" opacity="0.9"/>
<path d="M 0,-12 L 12,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-3" cy="-3" r="1.5" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
</div>
<div class="reframes-area" id="reframesArea">
<div class="distortion-row">
<span class="chip chip-amber">Catastrophizing</span>
<span class="chip chip-amber">Overgeneralization</span>
</div>
<p class="label text-dim" style="margin-bottom: var(--space-3);">Three new angles</p>
<div class="reframe-block amethyst">
<div class="reframe-label">Perspective Shift</div>
<div class="reframe-text">One rough presentation doesn't define your skills. Every speaker — even the most experienced — has moments that don't land. This is one data point, not your whole story.</div>
</div>
<div class="reframe-block sapphire">
<div class="reframe-label">Evidence Check</div>
<div class="reframe-text">Think about the last three times you communicated something clearly and well. Those moments exist too. Your brain is amplifying this one and filtering the others out.</div>
</div>
<div class="reframe-block emerald">
<div class="reframe-label">Action Step</div>
<div class="reframe-text">Write down one specific thing that actually went okay in that presentation — even something small. Then name one thing to prepare differently next time. That's how you close the loop.</div>
</div>
</div>
</div>
<div class="cta-bar">
<a href="05-style-selection.html" class="btn btn-primary" id="ctaBtn" style="opacity:0; transition:opacity 0.5s ease; text-decoration:none;">
That's the Turn
</a>
</div>
</div>
<script>
setTimeout(function() {
var thoughtCard = document.getElementById('thoughtCard');
var animStage = document.getElementById('animStage');
var reframesArea = document.getElementById('reframesArea');
var ctaBtn = document.getElementById('ctaBtn');
thoughtCard.classList.add('collapsing');
setTimeout(function() {
thoughtCard.style.display = 'none';
animStage.classList.add('visible');
}, 500);
setTimeout(function() {
animStage.style.display = 'none';
reframesArea.classList.add('visible');
ctaBtn.style.opacity = '1';
}, 2000);
}, 1000);
</script>
</body>
</html>

View File

@@ -0,0 +1,262 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Style Selection</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-content {
padding: var(--space-6) var(--space-4) 120px;
}
.style-option {
display: flex;
align-items: center;
gap: var(--space-4);
padding: var(--space-4);
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
cursor: pointer;
margin-bottom: var(--space-3);
transition: all 0.2s ease-out;
}
.style-option:hover:not(.selected) {
border-color: rgba(139,92,246,0.3);
background: rgba(139,92,246,0.05);
}
.style-option.selected {
border-color: var(--amethyst);
background: rgba(139,92,246,0.08);
box-shadow: 0 0 20px rgba(139,92,246,0.15);
}
.style-icon-wrap {
width: 44px;
height: 44px;
border-radius: var(--radius-md);
background: rgba(139,92,246,0.12);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background 0.2s ease;
}
.style-option.selected .style-icon-wrap {
background: rgba(139,92,246,0.2);
}
.style-text { flex: 1; }
.style-name {
color: var(--pure-light);
margin-bottom: var(--space-1);
}
.style-desc {
color: var(--dim-light);
}
.radio-indicator {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid var(--twilight);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.2s ease-out;
}
.style-option.selected .radio-indicator {
border-color: var(--amethyst);
}
.radio-dot-inner {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--amethyst);
opacity: 0;
transform: scale(0);
transition: all 0.2s ease-out;
}
.style-option.selected .radio-dot-inner {
opacity: 1;
transform: scale(1);
}
.cta-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 60%, transparent);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content">
<h1 class="display-md text-pure" style="margin-bottom: var(--space-2);">Choose your coaching style</h1>
<p class="body text-dim" style="margin-bottom: var(--space-6);">How do you prefer to be guided when working through difficult thoughts?</p>
<!--
Style icons — extracted from fragment-icons.svg → SM size (radius 8), centred at 0,0
Each SVG is 22×22, translate(11,11), paths: M 0,-8 L 8,0 L 0,8 L -8,0 Z
Gradient IDs prefixed s05- to avoid conflicts
Each style gets a unique jewel color
-->
<!-- Option 1: Stoic Clarity — Amethyst SM fragment -->
<div class="style-option" data-style="stoic" onclick="selectStyle(this)">
<div class="style-icon-wrap">
<svg width="22" height="22" viewBox="0 0 22 22">
<defs>
<linearGradient id="s05-grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<filter id="s05-glowSmA" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(11,11)" filter="url(#s05-glowSmA)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#s05-grAmethyst)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="style-text">
<div class="subheading style-name">Stoic Clarity</div>
<div class="body-sm style-desc">Direct truths, no softening. Reality-grounded reframes that cut to what's actually true.</div>
</div>
<div class="radio-indicator"><div class="radio-dot-inner"></div></div>
</div>
<!-- Option 2: Compassionate — Rose SM fragment -->
<div class="style-option" data-style="compassionate" onclick="selectStyle(this)">
<div class="style-icon-wrap">
<svg width="22" height="22" viewBox="0 0 22 22">
<defs>
<linearGradient id="s05-grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/>
<stop offset="100%" stop-color="#BE185D"/>
</linearGradient>
<filter id="s05-glowSmB" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(11,11)" filter="url(#s05-glowSmB)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#s05-grRose)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="style-text">
<div class="subheading style-name">Compassionate</div>
<div class="body-sm style-desc">Warmth before challenge. You're always met exactly where you are, without judgment.</div>
</div>
<div class="radio-indicator"><div class="radio-dot-inner"></div></div>
</div>
<!-- Option 3: Pragmatic — Emerald SM fragment -->
<div class="style-option" data-style="pragmatic" onclick="selectStyle(this)">
<div class="style-icon-wrap">
<svg width="22" height="22" viewBox="0 0 22 22">
<defs>
<linearGradient id="s05-grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#047857"/>
</linearGradient>
<filter id="s05-glowSmC" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(11,11)" filter="url(#s05-glowSmC)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#s05-grEmerald)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="style-text">
<div class="subheading style-name">Pragmatic</div>
<div class="body-sm style-desc">Skip the philosophy, get to the action. Concrete steps you can take today, every time.</div>
</div>
<div class="radio-indicator"><div class="radio-dot-inner"></div></div>
</div>
<!-- Option 4: Growth-Oriented — Sapphire SM fragment -->
<div class="style-option" data-style="growth" onclick="selectStyle(this)">
<div class="style-icon-wrap">
<svg width="22" height="22" viewBox="0 0 22 22">
<defs>
<linearGradient id="s05-grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/>
<stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
<filter id="s05-glowSmD" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g transform="translate(11,11)" filter="url(#s05-glowSmD)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#s05-grSapphire)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="style-text">
<div class="subheading style-name">Growth-Oriented</div>
<div class="body-sm style-desc">Challenge me to stretch beyond comfort. Reframes that ask more of you, not less.</div>
</div>
<div class="radio-indicator"><div class="radio-dot-inner"></div></div>
</div>
</div>
<div class="cta-bar">
<a href="06-notifications.html" class="btn btn-primary btn-disabled" id="continueBtn" style="text-decoration:none;">
Continue
</a>
</div>
</div>
<script>
function selectStyle(el) {
document.querySelectorAll('.style-option').forEach(function(opt) {
opt.classList.remove('selected');
});
el.classList.add('selected');
var btn = document.getElementById('continueBtn');
btn.classList.remove('btn-disabled');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Notifications</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-content.centered {
padding: var(--space-8) var(--space-6);
}
.notif-icon-wrap {
position: relative;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-10);
}
.notif-aura {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.18) 0%, rgba(59,130,246,0.06) 50%, transparent 70%);
filter: blur(40px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.reminder-preview {
width: 100%;
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-10);
text-align: left;
}
.reminder-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-2);
}
.reminder-time {
color: var(--faint-light);
margin-left: auto;
}
.cta-area {
width: 100%;
display: flex;
flex-direction: column;
gap: var(--space-3);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content centered">
<div class="notif-icon-wrap">
<div class="notif-aura"></div>
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" style="position:relative; z-index:1;">
<defs>
<linearGradient id="bellGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A78BFA"/>
<stop offset="100%" stop-color="#6D28D9"/>
</linearGradient>
<linearGradient id="fragAccent" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
</defs>
<!-- Bell body -->
<path d="M36 14C28 14 22 20 22 28L22 44L16 50L56 50L50 44L50 28C50 20 44 14 36 14Z" fill="url(#bellGrad)" opacity="0.85"/>
<path d="M32 50C32 52.2 33.8 54 36 54C38.2 54 40 52.2 40 50" fill="none" stroke="#A78BFA" stroke-width="1.5" opacity="0.7"/>
<circle cx="36" cy="13" r="2.5" fill="url(#bellGrad)"/>
<!--
Fragment accent — extracted from fragment-icons.svg → Amber SM (radius 8)
Centred at 57,24 (upper-right of bell) in 72×72 viewport
-->
<g transform="translate(57,24)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#fragAccent)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.5" opacity="0.2"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<h2 class="heading text-pure" style="margin-bottom: var(--space-3);">A nudge, not a nag</h2>
<p class="body text-dim" style="margin-bottom: var(--space-6);">Kalei sends one quiet reminder a day — not to create pressure, but to offer a pause. The kind that actually helps.</p>
<div class="reminder-preview">
<div class="reminder-header">
<!--
Reminder icon — extracted from fragment-icons.svg → Amethyst XS (radius 6)
Centred at 0,0 in 14×14 viewport (translate 7,7)
-->
<svg width="14" height="14" viewBox="0 0 14 14">
<defs>
<linearGradient id="s06-rfg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
</defs>
<g transform="translate(7,7)">
<path d="M 0,-6 L 6,0 L 0,6 L -6,0 Z" fill="url(#s06-rfg)" opacity="0.85"/>
<path d="M 0,-6 L 6,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<circle cx="-1.5" cy="-1.5" r="0.8" fill="#fff" opacity="0.3"/>
</g>
</svg>
<span class="label text-amethyst">Kalei</span>
<span class="body-sm reminder-time">now</span>
</div>
<p class="subheading text-pure" style="margin-bottom: var(--space-1);">Something on your mind?</p>
<p class="body-sm text-dim">Thoughts that go unexamined have a way of growing. A quick Turn takes a minute and often changes the rest of the day.</p>
</div>
<div class="cta-area">
<a href="07-account-creation.html" class="btn btn-primary" style="text-decoration:none;">
Allow Notifications
</a>
<a href="07-account-creation.html" class="btn btn-ghost" style="text-decoration:none; color: var(--dim-light);">
Not now
</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,193 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Create Account</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-content {
padding: var(--space-6) var(--space-4) var(--space-10);
}
.btn-apple {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-3);
height: 52px;
background: var(--pure-light);
color: var(--void);
border-radius: var(--radius-lg);
text-decoration: none;
transition: opacity 0.2s ease-out;
font-family: var(--font-primary);
font-size: 16px;
font-weight: 600;
width: 100%;
}
.btn-apple:hover { opacity: 0.9; }
.btn-google {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-3);
height: 52px;
background: transparent;
color: var(--soft-light);
border-radius: var(--radius-lg);
border: 1px solid var(--twilight);
text-decoration: none;
transition: all 0.2s ease-out;
font-family: var(--font-primary);
font-size: 16px;
font-weight: 600;
width: 100%;
}
.btn-google:hover { border-color: var(--faint-light); background: rgba(255,255,255,0.03); }
.social-btns {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.divider-row {
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.divider-line {
flex: 1;
height: 1px;
background: var(--twilight);
}
.form-fields {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.field-group {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.btn-create {
display: flex;
align-items: center;
justify-content: center;
height: 52px;
width: 100%;
background: var(--amethyst);
color: var(--pure-light);
border-radius: var(--radius-lg);
text-decoration: none;
box-shadow: var(--glow-amethyst);
margin-bottom: var(--space-4);
font-family: var(--font-primary);
font-size: 16px;
font-weight: 600;
transition: all 0.2s ease-out;
}
.btn-create:hover { background: var(--amethyst-light); }
.signin-link {
text-align: center;
}
.signin-link a {
color: var(--amethyst-light);
text-decoration: none;
font-weight: 500;
}
.privacy-note {
text-align: center;
color: var(--faint-light);
margin-top: var(--space-4);
line-height: 1.5;
}
.privacy-note a {
color: var(--dim-light);
text-decoration: underline;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content">
<h1 class="display-md text-pure" style="margin-bottom: var(--space-2);">Create your account</h1>
<p class="body text-dim" style="margin-bottom: var(--space-6);">Your thoughts stay private. Always.</p>
<div class="social-btns">
<a href="08-first-turn.html" class="btn-apple">
<!-- Apple logo SVG (monochrome) -->
<svg width="18" height="22" viewBox="0 0 18 22" fill="none">
<path d="M14.98 11.47c-.02-2.53 2.07-3.75 2.16-3.81-1.18-1.72-3.01-1.96-3.66-1.98-1.56-.16-3.04.92-3.83.92-.79 0-2.01-.9-3.31-.87C4.59 5.76 3 6.64 2.1 8.06.27 10.93 1.64 15.24 3.42 17.61c.9 1.29 1.96 2.74 3.36 2.69 1.35-.05 1.86-.87 3.49-.87 1.63 0 2.09.87 3.52.84 1.46-.02 2.38-1.32 3.27-2.62.73-1.07 1.02-2.12 1.04-2.17-.02-.01-2.1-.8-2.12-3.01z" fill="#000"/>
<path d="M12.44 3.83C13.17 2.94 13.67 1.7 13.53.44c-1.06.04-2.34.71-3.1 1.59-.67.77-1.27 2.01-1.11 3.19 1.18.09 2.39-.59 3.12-1.39z" fill="#000"/>
</svg>
Sign in with Apple
</a>
<a href="08-first-turn.html" class="btn-google">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M19.6 10.2c0-.7-.1-1.4-.2-2H10v3.8h5.4c-.2 1.3-1 2.4-2.1 3.1v2.6h3.4c2-1.8 3.1-4.5 3.1-7.5z" fill="#4285F4"/>
<path d="M10 20c2.7 0 5-.9 6.6-2.4l-3.4-2.6c-.9.6-2 1-3.2 1-2.5 0-4.6-1.7-5.4-4H1.1v2.7C2.7 17.7 6.1 20 10 20z" fill="#34A853"/>
<path d="M4.6 12c-.2-.6-.3-1.3-.3-2s.1-1.4.3-2V5.3H1.1C.4 6.7 0 8.3 0 10s.4 3.3 1.1 4.7l3.5-2.7z" fill="#FBBC05"/>
<path d="M10 4c1.4 0 2.6.5 3.6 1.4L16.7 2.4C15 .8 12.7 0 10 0 6.1 0 2.7 2.3 1.1 5.7l3.5 2.7C5.4 5.7 7.5 4 10 4z" fill="#EA4335"/>
</svg>
Sign in with Google
</a>
</div>
<div class="divider-row">
<div class="divider-line"></div>
<span class="label text-faint">or</span>
<div class="divider-line"></div>
</div>
<div class="form-fields">
<div class="field-group">
<label class="input-label">Email</label>
<input type="email" class="input-field" placeholder="you@example.com">
</div>
<div class="field-group">
<label class="input-label">Password</label>
<input type="password" class="input-field" placeholder="Create a strong password">
</div>
</div>
<a href="08-first-turn.html" class="btn-create">Create Account</a>
<div class="signin-link body-sm text-dim">
Already have an account? <a href="#">Sign in</a>
</div>
<div class="privacy-note body-sm">
By continuing, you agree to our <a href="#">Terms</a> and <a href="#">Privacy Policy</a>.<br>
We never sell your data.
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Your First Turn</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-content {
padding: var(--space-6) var(--space-4) 120px;
position: relative;
}
.textarea-wrap {
position: relative;
margin-bottom: var(--space-4);
}
.textarea-aura {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 340px;
height: 240px;
border-radius: 50%;
background: radial-gradient(ellipse, rgba(139,92,246,0.12) 0%, transparent 70%);
filter: blur(40px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.thought-textarea {
position: relative;
z-index: 1;
width: 100%;
height: 200px;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-5);
font-family: var(--font-primary);
font-size: 17px;
line-height: 1.6;
color: var(--pure-light);
outline: none;
resize: none;
transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out;
}
.thought-textarea::placeholder { color: var(--faint-light); }
.thought-textarea:focus {
border-color: var(--amethyst);
box-shadow: 0 0 0 1px rgba(139,92,246,0.2), 0 0 20px rgba(139,92,246,0.3);
}
.fragment-hint {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-6);
}
.hint-icon {
flex-shrink: 0;
animation: hintIconPulse 2s ease-in-out infinite;
}
@keyframes hintIconPulse {
0%, 100% { opacity: 0.75; transform: scale(1); }
50% { opacity: 1; transform: scale(1.12); }
}
.prompts-label {
color: var(--faint-light);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: var(--space-3);
}
.prompt-chips {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.prompt-chip {
padding: var(--space-1) var(--space-3);
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-full);
color: var(--dim-light);
cursor: pointer;
transition: all 0.2s ease-out;
}
.prompt-chip:hover {
border-color: var(--amethyst);
background: rgba(139,92,246,0.08);
color: var(--amethyst-light);
}
.cta-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-4) var(--space-4) var(--space-6);
background: linear-gradient(to top, var(--void) 70%, transparent);
}
.btn-turn {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-3);
height: 56px;
width: 100%;
background: var(--amethyst);
color: var(--pure-light);
font-family: var(--font-primary);
font-size: 17px;
font-weight: 600;
border-radius: var(--radius-xl);
text-decoration: none;
box-shadow: 0 0 24px rgba(139,92,246,0.35);
transition: all 0.2s ease-out;
}
.btn-turn:hover {
background: var(--amethyst-light);
box-shadow: 0 0 32px rgba(139,92,246,0.5);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content">
<h1 class="display-md text-pure" style="margin-bottom: var(--space-2);">What's the thought?</h1>
<p class="body text-dim" style="margin-bottom: var(--space-5);">Write it exactly as it sounds in your head — raw, unfiltered, even a little ugly. Kalei works better with the real version.</p>
<div class="textarea-wrap">
<div class="textarea-aura"></div>
<textarea class="thought-textarea" placeholder="The thought that keeps running on repeat..."></textarea>
</div>
<div class="fragment-hint">
<!--
Hint icon — extracted from fragment-icons.svg → Amethyst XS (radius 6)
Centred at 0,0 in 14×14 viewport (translate 7,7)
-->
<svg class="hint-icon" width="14" height="14" viewBox="0 0 14 14">
<defs>
<linearGradient id="s08-hintGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
</defs>
<g transform="translate(7,7)">
<path d="M 0,-6 L 6,0 L 0,6 L -6,0 Z" fill="url(#s08-hintGrad)" opacity="0.75"/>
<path d="M 0,-6 L 6,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<circle cx="-1.5" cy="-1.5" r="0.8" fill="#fff" opacity="0.3"/>
</g>
</svg>
<span class="body-sm text-faint" style="font-style: italic;">The Turn will show you new angles on this thought</span>
</div>
<div>
<p class="body-sm prompts-label">Try a prompt</p>
<div class="prompt-chips">
<div class="prompt-chip body-sm" onclick="usePrompt('work')">Something at work</div>
<div class="prompt-chip body-sm" onclick="usePrompt('relationship')">A relationship</div>
<div class="prompt-chip body-sm" onclick="usePrompt('self')">Something about myself</div>
<div class="prompt-chip body-sm" onclick="usePrompt('mistake')">A recent mistake</div>
</div>
</div>
</div>
<div class="cta-bar">
<a href="09-welcome-complete.html" class="btn-turn">
<!--
Button icon — extracted from fragment-icons.svg → Amethyst SM (radius 8)
Centred at 0,0 in 20×20 viewport (translate 10,10), white fill (on coloured button bg)
-->
<svg width="20" height="20" viewBox="0 0 20 20">
<g transform="translate(10,10)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="white" opacity="0.35"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="white" opacity="0.2"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.5" opacity="0.3"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.5"/>
</g>
</svg>
Turn the kaleidoscope
</a>
</div>
</div>
<script>
var prompts = {
work: "I feel like I'm constantly behind and my team is starting to notice how much I'm struggling to keep up.",
relationship: "I keep pulling away from people I care about — I don't know why I do it, but I always end up alone.",
self: "I don't deserve the things I've been given. It's only a matter of time before people figure out I'm not actually that capable.",
mistake: "I said something really hurtful and now I can't stop replaying the look on their face. I ruined everything."
};
function usePrompt(key) {
var textarea = document.querySelector('.thought-textarea');
textarea.value = prompts[key];
textarea.focus();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,313 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — You're Ready</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.screen-content.centered {
padding: var(--space-8) var(--space-6);
position: relative;
overflow: hidden;
}
.bg-shards {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.kalei-pattern {
position: relative;
width: 180px;
height: 180px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-8);
z-index: 1;
}
.pattern-aura {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 300px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.18) 0%, rgba(59,130,246,0.07) 40%, rgba(16,185,129,0.04) 60%, transparent 70%);
filter: blur(55px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.pattern-svg {
position: relative;
z-index: 1;
}
/* Burst rings */
.burst-container {
position: absolute;
top: 42%;
left: 50%;
pointer-events: none;
z-index: 0;
}
.burst-ring {
position: absolute;
border-radius: 50%;
border: 1px solid;
transform: translate(-50%, -50%);
animation: burstExpand 2.4s ease-out forwards;
}
.burst-ring:nth-child(1) { border-color: rgba(139,92,246,0.4); animation-delay: 0s; }
.burst-ring:nth-child(2) { border-color: rgba(59,130,246,0.3); animation-delay: 0.35s; }
.burst-ring:nth-child(3) { border-color: rgba(16,185,129,0.2); animation-delay: 0.7s; }
@keyframes burstExpand {
0% { width: 40px; height: 40px; opacity: 1; }
100% { width: 280px; height: 280px; opacity: 0; }
}
.setup-stats {
display: flex;
gap: var(--space-3);
margin-bottom: var(--space-10);
z-index: 1;
width: 100%;
}
.setup-stat {
flex: 1;
background: rgba(139,92,246,0.08);
border: 1px solid rgba(139,92,246,0.2);
border-radius: var(--radius-lg);
padding: var(--space-3);
text-align: center;
}
.setup-stat-icon {
display: flex;
justify-content: center;
margin-bottom: var(--space-1);
}
.cta-area {
width: 100%;
z-index: 1;
}
.btn-enter {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-3);
height: 56px;
width: 100%;
background: var(--amethyst);
color: var(--pure-light);
font-family: var(--font-primary);
font-size: 17px;
font-weight: 700;
border-radius: var(--radius-xl);
text-decoration: none;
box-shadow: 0 0 24px rgba(139,92,246,0.4);
transition: all 0.2s ease-out;
animation: breathing 6s ease-in-out infinite;
}
.btn-enter:hover {
background: var(--amethyst-light);
box-shadow: 0 0 40px rgba(139,92,246,0.6);
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="screen-content centered">
<!--
Background floating shards — extracted from decorative-shards.svg → Floating Shards section
6 shaped shard paths repositioned across 390×730 screen, prismatic all-jewel colors
-->
<svg class="bg-shards" viewBox="0 0 390 730" fill="none" preserveAspectRatio="xMidYMid slice">
<defs>
<filter id="s09-glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- Amethyst shard upper-left -->
<g transform="translate(40,85)" filter="url(#s09-glowSm)" opacity="0.55">
<path d="M 0,-18 L 10,-4 L 2,16 L -8,6 Z" fill="#C4B5FD" transform="rotate(15)">
<animate attributeName="opacity" values="0.4;0.65;0.4" dur="5s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;2,-3;0,0" dur="5s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Sapphire shard upper-right -->
<g transform="translate(348,65)" filter="url(#s09-glowSm)" opacity="0.5">
<path d="M 0,-14 L 8,0 L 0,14 L -8,0 Z" fill="#93C5FD" transform="rotate(-25)">
<animate attributeName="opacity" values="0.35;0.6;0.35" dur="7s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;-2,2;0,0" dur="7s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Emerald shard mid-left -->
<g transform="translate(25,380)" filter="url(#s09-glowSm)" opacity="0.45">
<path d="M 0,-22 L 6,-5 L 0,22 L -6,-5 Z" fill="#6EE7B7" transform="rotate(12)">
<animate attributeName="opacity" values="0.3;0.55;0.3" dur="9s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;3,5;0,0" dur="9s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Amber shard mid-right -->
<g transform="translate(360,330)" filter="url(#s09-glowSm)" opacity="0.45">
<path d="M 0,-12 L 8,4 L -2,12 L -8,-2 Z" fill="#FDE68A" transform="rotate(-18)">
<animate attributeName="opacity" values="0.3;0.55;0.3" dur="6s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;-3,-2;0,0" dur="6s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Rose shard lower-left -->
<g transform="translate(55,570)" filter="url(#s09-glowSm)" opacity="0.4">
<path d="M 0,-16 L 10,0 L 2,16 L -10,4 Z" fill="#F9A8D4" transform="rotate(30)">
<animate attributeName="opacity" values="0.25;0.5;0.25" dur="8s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;2,5;0,0" dur="8s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
<!-- Indigo shard lower-right -->
<g transform="translate(335,510)" filter="url(#s09-glowSm)" opacity="0.4">
<path d="M 0,-10 L 6,0 L 0,10 L -6,0 Z" fill="#818CF8" transform="rotate(-8)">
<animate attributeName="opacity" values="0.25;0.5;0.25" dur="4.5s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;-1,-3;0,0" dur="4.5s" repeatCount="indefinite" additive="sum"/>
</path>
</g>
</svg>
<!-- Burst rings -->
<div class="burst-container">
<div class="burst-ring"></div>
<div class="burst-ring"></div>
<div class="burst-ring"></div>
</div>
<!--
Hero Kaleidoscope — extracted from patterns-kaleidoscope.svg → Complex 6-blade Prismatic variant
Blades: M 0,0 L 6,-2 L -4,75 L -6,1 Z × 6 at 60° intervals, 6 jewel colors
animateTransform rotate from 0 to 360, dur 90s (Hero pace)
Centred at 80,80 in 160×160 viewport, circClip radius 70
All IDs prefixed s09-k
-->
<div class="kalei-pattern">
<div class="pattern-aura"></div>
<img src="../../assets/kalei-logo.svg" width="100" height="100" alt="Kalei" class="pattern-svg" style="filter: drop-shadow(0 0 12px rgba(139,92,246,0.3));">
</div>
<h1 class="display-lg text-pure" style="margin-bottom: var(--space-4); z-index:1;">Welcome, Alex</h1>
<p class="body text-dim" style="margin-bottom: var(--space-8); z-index:1; max-width: 280px;">Your kaleidoscope is calibrated. The same thoughts you've always had — now seen from angles you haven't tried yet.</p>
<!--
Setup stat icons — extracted from fragment-icons.svg → SM size (radius 8)
Centred at 0,0 in 18×18 viewport (translate 9,9), canonical diamond path
Gradient IDs prefixed s09-
-->
<div class="setup-stats">
<div class="setup-stat">
<div class="setup-stat-icon">
<svg width="18" height="18" viewBox="0 0 18 18">
<defs>
<linearGradient id="s09-statAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
</defs>
<g transform="translate(9,9)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#s09-statAmethyst)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="body-sm text-dim">Style chosen</div>
</div>
<div class="setup-stat">
<div class="setup-stat-icon">
<svg width="18" height="18" viewBox="0 0 18 18">
<defs>
<linearGradient id="s09-statEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/>
<stop offset="100%" stop-color="#047857"/>
</linearGradient>
</defs>
<g transform="translate(9,9)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#s09-statEmerald)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="body-sm text-dim">Account ready</div>
</div>
<div class="setup-stat">
<div class="setup-stat-icon">
<svg width="18" height="18" viewBox="0 0 18 18">
<defs>
<linearGradient id="s09-statAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/>
<stop offset="100%" stop-color="#D97706"/>
</linearGradient>
</defs>
<g transform="translate(9,9)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="url(#s09-statAmber)" opacity="0.9"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="#fff" opacity="0.18"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.3"/>
</g>
</svg>
</div>
<div class="body-sm text-dim">First Turn logged</div>
</div>
</div>
<div class="cta-area">
<a href="../turn/10-turn-home.html" class="btn-enter">
<!--
Button icon — extracted from fragment-icons.svg → Amethyst SM (radius 8)
White fill on coloured button background
-->
<svg width="20" height="20" viewBox="0 0 20 20">
<g transform="translate(10,10)">
<path d="M 0,-8 L 8,0 L 0,8 L -8,0 Z" fill="white" opacity="0.35"/>
<path d="M 0,-8 L 8,0 L 0,0 Z" fill="white" opacity="0.2"/>
<line x1="0" y1="-8" x2="0" y2="8" stroke="#fff" stroke-width="0.5" opacity="0.3"/>
<circle cx="-2" cy="-2" r="1" fill="#fff" opacity="0.5"/>
</g>
</svg>
Enter Kalei
</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,283 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Choose Your Ritual</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.ritual-card {
border-radius: var(--radius-2xl);
overflow: hidden;
margin-bottom: var(--space-4);
text-decoration: none;
display: block;
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
border: 1px solid;
}
.ritual-card-header {
padding: var(--space-5) var(--space-4) var(--space-4);
position: relative;
overflow: hidden;
}
.ritual-card-body {
padding: var(--space-3) var(--space-4) var(--space-4);
border-top: 1px solid;
}
.ritual-name {
font-size: 20px;
font-weight: 700;
margin-bottom: 4px;
}
.ritual-tagline {
font-size: 12px;
color: var(--dim-light);
margin-top: 2px;
line-height: 1.4;
}
.ritual-duration {
font-size: 13px;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: var(--radius-full);
margin-top: 8px;
}
.ritual-steps-preview {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.ritual-step-chip {
display: inline-flex;
align-items: center;
gap: 4px;
height: 26px;
padding: 0 10px;
border-radius: var(--radius-full);
font-size: 11px;
font-weight: 500;
background: rgba(255,255,255,0.08);
}
.ritual-arrow {
font-size: 10px;
opacity: 0.35;
color: var(--dim-light);
}
/* Morning — Amethyst */
.ritual-morning {
background: rgba(139,92,246,0.08);
border-color: rgba(139,92,246,0.3);
}
.ritual-morning .ritual-card-header {
background: linear-gradient(135deg, rgba(139,92,246,0.2), rgba(99,102,241,0.08));
}
.ritual-morning .ritual-card-body { border-color: rgba(139,92,246,0.18); }
.ritual-morning:hover { transform: translateY(-2px); box-shadow: 0 0 28px rgba(139,92,246,0.22); }
/* Evening — Sapphire */
.ritual-evening {
background: rgba(59,130,246,0.08);
border-color: rgba(59,130,246,0.3);
}
.ritual-evening .ritual-card-header {
background: linear-gradient(135deg, rgba(59,130,246,0.2), rgba(30,58,138,0.08));
}
.ritual-evening .ritual-card-body { border-color: rgba(59,130,246,0.18); }
.ritual-evening:hover { transform: translateY(-2px); box-shadow: 0 0 28px rgba(59,130,246,0.22); }
/* Quick — Emerald */
.ritual-quick {
background: rgba(16,185,129,0.08);
border-color: rgba(16,185,129,0.3);
}
.ritual-quick .ritual-card-header {
background: linear-gradient(135deg, rgba(16,185,129,0.2), rgba(4,120,87,0.08));
}
.ritual-quick .ritual-card-body { border-color: rgba(16,185,129,0.18); }
.ritual-quick:hover { transform: translateY(-2px); box-shadow: 0 0 28px rgba(16,185,129,0.22); }
.ritual-bg-deco {
position: absolute;
top: -20px;
right: -20px;
width: 110px;
height: 110px;
border-radius: 50%;
opacity: 0.07;
filter: blur(24px);
pointer-events: none;
}
.intro-text {
padding: var(--space-2) 0 var(--space-4);
font-size: 14px;
color: var(--dim-light);
line-height: 1.6;
}
.streak-nudge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--amber-light);
font-weight: 600;
margin-top: 6px;
}
.streak-nudge-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--amber);
box-shadow: 0 0 6px rgba(245,158,11,0.5);
animation: pulse 2s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="device-frame">
<!-- STATUS BAR -->
<div class="status-bar">
<span class="time">8:47</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<!-- NAV HEADER -->
<div class="nav-header">
<a class="nav-back" href="../you/35-you-profile.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Your Rituals</span>
<span class="nav-action">
<a href="49-ritual-streak.html" style="text-decoration:none; color: var(--amber-light); font-size:13px; font-weight:600;">14 🔥</a>
</span>
</div>
<!-- SCREEN CONTENT -->
<div class="screen-content" style="padding-top: var(--space-2);">
<div class="intro-text">
Choose the ritual that fits this moment. Each one is a different shape of care — and Alex has been showing up for 14 days straight.
<div class="streak-nudge">
<span class="streak-nudge-dot"></span>
Longest streak yet. Keep the thread going.
</div>
</div>
<!-- Morning Clarity -->
<a class="ritual-card ritual-morning" href="45-ritual-morning.html">
<div class="ritual-card-header">
<div class="ritual-bg-deco" style="background: var(--amethyst);"></div>
<div style="display:flex; align-items:center; gap: var(--space-3); margin-bottom: var(--space-2);">
<svg width="36" height="36" viewBox="0 0 36 36">
<defs><linearGradient id="mGr" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#C4B5FD"/><stop offset="100%" stop-color="#7C3AED"/></linearGradient></defs>
<g transform="translate(18,18)">
<path d="M0,0 L3,-13 L0,-15 L-3,-13Z" fill="url(#mGr)" opacity="0.9"/>
<g transform="rotate(60)"><path d="M0,0 L3,-13 L0,-15 L-3,-13Z" fill="url(#mGr)" opacity="0.7"/></g>
<g transform="rotate(120)"><path d="M0,0 L3,-13 L0,-15 L-3,-13Z" fill="url(#mGr)" opacity="0.8"/></g>
<g transform="rotate(180)"><path d="M0,0 L3,-13 L0,-15 L-3,-13Z" fill="url(#mGr)" opacity="0.7"/></g>
<g transform="rotate(240)"><path d="M0,0 L3,-13 L0,-15 L-3,-13Z" fill="url(#mGr)" opacity="0.9"/></g>
<g transform="rotate(300)"><path d="M0,0 L3,-13 L0,-15 L-3,-13Z" fill="url(#mGr)" opacity="0.8"/></g>
<circle r="3" fill="white" opacity="0.2"/>
</g>
</svg>
<div>
<div class="ritual-name" style="color: var(--amethyst-light);">Morning Clarity</div>
<div class="ritual-tagline">Set your intention before the day sets it for you</div>
<div class="ritual-duration" style="background: rgba(139,92,246,0.15); color: var(--amethyst-light);">
<svg width="12" height="12" viewBox="0 0 12 12"><circle cx="6" cy="6" r="5" fill="none" stroke="currentColor" stroke-width="1"/><path d="M6 3v3l2 2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
15 min · 4 steps
</div>
</div>
</div>
</div>
<div class="ritual-card-body">
<div class="label text-dim" style="margin-bottom: var(--space-2);">Steps</div>
<div class="ritual-steps-preview">
<div class="ritual-step-chip" style="color: var(--amber-light);">Intention</div>
<div class="ritual-arrow"></div>
<div class="ritual-step-chip" style="color: var(--amethyst-light);">Fragments</div>
<div class="ritual-arrow"></div>
<div class="ritual-step-chip" style="color: var(--sapphire-light);">Rehearsal</div>
<div class="ritual-arrow"></div>
<div class="ritual-step-chip" style="color: var(--emerald-light);">Commit</div>
</div>
</div>
</a>
<!-- Evening Wind-Down -->
<a class="ritual-card ritual-evening" href="46-ritual-evening.html">
<div class="ritual-card-header">
<div class="ritual-bg-deco" style="background: var(--sapphire);"></div>
<div style="display:flex; align-items:center; gap: var(--space-3); margin-bottom: var(--space-2);">
<svg width="36" height="36" viewBox="0 0 36 36">
<defs><linearGradient id="eGr" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/></linearGradient></defs>
<circle cx="18" cy="18" r="13" fill="none" stroke="url(#eGr)" stroke-width="1.5" opacity="0.5"/>
<circle cx="18" cy="18" r="8" fill="none" stroke="url(#eGr)" stroke-width="1.5" opacity="0.7"/>
<circle cx="18" cy="18" r="3" fill="url(#eGr)" opacity="0.8"/>
</svg>
<div>
<div class="ritual-name" style="color: var(--sapphire-light);">Evening Wind-Down</div>
<div class="ritual-tagline">Let the day settle before you do</div>
<div class="ritual-duration" style="background: rgba(59,130,246,0.15); color: var(--sapphire-light);">
<svg width="12" height="12" viewBox="0 0 12 12"><circle cx="6" cy="6" r="5" fill="none" stroke="currentColor" stroke-width="1"/><path d="M6 3v3l2 2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
20 min · 4 steps
</div>
</div>
</div>
</div>
<div class="ritual-card-body">
<div class="label text-dim" style="margin-bottom: var(--space-2);">Steps</div>
<div class="ritual-steps-preview">
<div class="ritual-step-chip" style="color: var(--sapphire-light);">Day Reflection</div>
<div class="ritual-arrow"></div>
<div class="ritual-step-chip" style="color: var(--amethyst-light);">Pattern Check</div>
<div class="ritual-arrow"></div>
<div class="ritual-step-chip" style="color: var(--emerald-light);">Gratitude</div>
<div class="ritual-arrow"></div>
<div class="ritual-step-chip" style="color: var(--indigo-light);">Tomorrow</div>
</div>
</div>
</a>
<!-- Quick Grounding -->
<a class="ritual-card ritual-quick" href="47-ritual-quick.html" style="margin-bottom: var(--space-6);">
<div class="ritual-card-header">
<div class="ritual-bg-deco" style="background: var(--emerald);"></div>
<div style="display:flex; align-items:center; gap: var(--space-3); margin-bottom: var(--space-2);">
<svg width="36" height="36" viewBox="0 0 36 36">
<defs><linearGradient id="qGr" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/></linearGradient></defs>
<path d="M18 4L28 10V22L18 28L8 22V10Z" fill="none" stroke="url(#qGr)" stroke-width="1.5" opacity="0.8"/>
<path d="M18 10L24 14V20L18 24L12 20V14Z" fill="url(#qGr)" opacity="0.3"/>
</svg>
<div>
<div class="ritual-name" style="color: var(--emerald-light);">Quick Grounding</div>
<div class="ritual-tagline">Two minutes to come back to yourself when stressed</div>
<div class="ritual-duration" style="background: rgba(16,185,129,0.15); color: var(--emerald-light);">
<svg width="12" height="12" viewBox="0 0 12 12"><circle cx="6" cy="6" r="5" fill="none" stroke="currentColor" stroke-width="1"/><path d="M6 3v3l2 2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
2 min · 2 steps
</div>
</div>
</div>
</div>
<div class="ritual-card-body">
<div class="label text-dim" style="margin-bottom: var(--space-2);">Steps</div>
<div class="ritual-steps-preview">
<div class="ritual-step-chip" style="color: var(--amethyst-light);">Name it</div>
<div class="ritual-arrow"></div>
<div class="ritual-step-chip" style="color: var(--emerald-light);">Ground it</div>
</div>
</div>
</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Morning Clarity Ritual</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.ritual-progress-bar {
display: flex;
gap: 4px;
padding: var(--space-3) var(--space-4) 0;
}
.rp-seg {
flex: 1;
height: 4px;
border-radius: 2px;
background: var(--twilight);
transition: background 0.3s ease-out;
}
.rp-seg.done { background: var(--amethyst); box-shadow: 0 0 6px rgba(139,92,246,0.4); }
.rp-seg.active { background: linear-gradient(to right, var(--amethyst), var(--sapphire)); box-shadow: 0 0 8px rgba(139,92,246,0.3); animation: pulse-bar 2s ease-in-out infinite; }
@keyframes pulse-bar { 0%, 100% { opacity: 1; } 50% { opacity: 0.65; } }
.ritual-timer {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: var(--dim-light);
}
.step-card {
background: var(--deep-glass);
border: 1px solid rgba(245,158,11,0.25);
border-radius: var(--radius-2xl);
padding: var(--space-5);
margin-bottom: var(--space-4);
box-shadow: 0 0 20px rgba(245,158,11,0.05);
}
.step-badge {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: 4px 12px;
border-radius: var(--radius-full);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: var(--space-3);
}
.step-badge.intention { background: rgba(245,158,11,0.15); color: var(--amber-light); }
.step-prompt {
font-size: 18px;
font-weight: 600;
color: var(--pure-light);
line-height: 1.45;
margin-bottom: 6px;
}
.step-subprompt {
font-size: 13px;
color: var(--dim-light);
line-height: 1.5;
margin-bottom: var(--space-4);
}
.journal-area {
width: 100%;
min-height: 120px;
background: var(--kalei-navy);
border: 1px solid var(--amber);
border-radius: var(--radius-lg);
padding: 14px;
font-size: 16px;
line-height: 1.5;
color: var(--soft-light);
outline: none;
resize: none;
box-shadow: 0 0 12px rgba(245,158,11,0.08);
font-family: var(--font-primary);
}
.journal-area::placeholder { color: var(--faint-light); }
.char-count { text-align: right; font-size: 11px; color: var(--faint-light); margin-top: var(--space-2); }
.step-nav-area {
padding: var(--space-4) 0 var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.btn-next {
width: 100%;
height: 52px;
background: var(--amethyst);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 600;
color: var(--pure-light);
text-decoration: none;
box-shadow: var(--glow-amethyst);
border: none;
cursor: pointer;
gap: var(--space-2);
transition: background 0.2s ease-out;
}
.btn-next:hover { background: var(--amethyst-light); }
.upcoming-steps {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-3) var(--space-4);
}
.upcoming-step-row {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-2) 0;
}
.upcoming-step-row:not(:last-child) { border-bottom: 1px solid var(--twilight); }
.upcoming-step-num {
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--twilight);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: var(--dim-light);
flex-shrink: 0;
}
.upcoming-step-num.current { background: var(--amethyst); color: var(--pure-light); box-shadow: 0 0 8px rgba(139,92,246,0.4); }
.aura-deco {
position: absolute;
top: 120px;
left: 50%;
transform: translateX(-50%);
width: 200px;
height: 200px;
border-radius: 50%;
background: radial-gradient(circle, rgba(245,158,11,0.07), transparent 70%);
filter: blur(40px);
pointer-events: none;
animation: breathing 6s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="device-frame">
<!-- STATUS BAR -->
<div class="status-bar">
<span class="time">8:47</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<!-- NAV HEADER -->
<div class="nav-header">
<a class="nav-back" href="44-ritual-templates.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Morning Clarity</span>
<span class="nav-action">
<div class="ritual-timer">
<svg width="12" height="12" viewBox="0 0 12 12"><circle cx="6" cy="6" r="5" fill="none" stroke="currentColor" stroke-width="1"/><path d="M6 3v3l2 2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
3:24
</div>
</span>
</div>
<!-- STEP PROGRESS -->
<div class="ritual-progress-bar">
<div class="rp-seg active"></div>
<div class="rp-seg"></div>
<div class="rp-seg"></div>
<div class="rp-seg"></div>
</div>
<!-- Ambient decoration -->
<div class="aura-deco"></div>
<!-- SCREEN CONTENT -->
<div class="screen-content" style="padding-top: var(--space-5);">
<!-- Step Card -->
<div class="step-card">
<div class="step-badge intention">
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M6 1L9 4L10 6L9 8L6 10L3 8L2 6L3 4Z" fill="none" stroke="currentColor" stroke-width="1"/></svg>
Step 1 — Intention
</div>
<div class="step-prompt">What's one thing you're carrying into today?</div>
<div class="step-subprompt">Not a task list — just what's already in your mind before the day begins.</div>
<textarea class="journal-area" placeholder="Let it land here before the world demands anything of you..."></textarea>
<div class="char-count">0 / 500</div>
</div>
<!-- Upcoming Steps -->
<div class="label text-dim" style="margin-bottom: var(--space-2);">Today's ritual</div>
<div class="upcoming-steps">
<div class="upcoming-step-row">
<div class="upcoming-step-num current">1</div>
<div style="flex:1; font-size:14px; color: var(--soft-light);">Intention setting</div>
<span style="font-size: 12px; color: var(--dim-light);">~4 min</span>
</div>
<div class="upcoming-step-row">
<div class="upcoming-step-num">2</div>
<div style="flex:1; font-size:14px; color: var(--faint-light);">Fragment awareness</div>
<span style="font-size: 12px; color: var(--faint-light);">~4 min</span>
</div>
<div class="upcoming-step-row">
<div class="upcoming-step-num">3</div>
<div style="flex:1; font-size:14px; color: var(--faint-light);">Rehearsal moment</div>
<span style="font-size: 12px; color: var(--faint-light);">~4 min</span>
</div>
<div class="upcoming-step-row">
<div class="upcoming-step-num">4</div>
<div style="flex:1; font-size:14px; color: var(--faint-light);">Daily commitment</div>
<span style="font-size: 12px; color: var(--faint-light);">~3 min</span>
</div>
</div>
<!-- Navigation -->
<div class="step-nav-area">
<a href="48-ritual-complete.html" class="btn-next">
Continue
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<a href="44-ritual-templates.html" style="text-align: center; font-size: 14px; color: var(--faint-light); text-decoration: none; padding: var(--space-2);">Save &amp; Exit</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,246 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Evening Wind-Down Ritual</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.ritual-progress-bar {
display: flex;
gap: 4px;
padding: var(--space-3) var(--space-4) 0;
}
.rp-seg {
flex: 1;
height: 4px;
border-radius: 2px;
background: var(--twilight);
transition: background 0.3s ease-out;
}
.rp-seg.done { background: var(--sapphire); box-shadow: 0 0 6px rgba(59,130,246,0.4); }
.rp-seg.active { background: linear-gradient(to right, var(--sapphire), var(--amethyst)); box-shadow: 0 0 8px rgba(59,130,246,0.3); animation: pulse-bar 2s ease-in-out infinite; }
@keyframes pulse-bar { 0%, 100% { opacity: 1; } 50% { opacity: 0.65; } }
.ritual-timer {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: var(--dim-light);
}
.step-card {
background: var(--deep-glass);
border: 1px solid rgba(59,130,246,0.25);
border-radius: var(--radius-2xl);
padding: var(--space-5);
margin-bottom: var(--space-4);
box-shadow: 0 0 20px rgba(59,130,246,0.05);
}
.step-badge {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: 4px 12px;
border-radius: var(--radius-full);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: var(--space-3);
background: rgba(59,130,246,0.15);
color: var(--sapphire-light);
}
.step-prompt {
font-size: 18px;
font-weight: 600;
color: var(--pure-light);
line-height: 1.45;
margin-bottom: 6px;
}
.step-subprompt {
font-size: 13px;
color: var(--dim-light);
line-height: 1.5;
margin-bottom: var(--space-4);
}
.journal-area {
width: 100%;
min-height: 120px;
background: var(--kalei-navy);
border: 1px solid var(--sapphire);
border-radius: var(--radius-lg);
padding: 14px;
font-size: 16px;
line-height: 1.5;
color: var(--soft-light);
outline: none;
resize: none;
box-shadow: 0 0 12px rgba(59,130,246,0.08);
font-family: var(--font-primary);
}
.journal-area::placeholder { color: var(--faint-light); }
.char-count { text-align: right; font-size: 11px; color: var(--faint-light); margin-top: var(--space-2); }
.upcoming-steps {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-4);
}
.upcoming-step-row {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-2) 0;
}
.upcoming-step-row:not(:last-child) { border-bottom: 1px solid var(--twilight); }
.upcoming-step-num {
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--twilight);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: var(--dim-light);
flex-shrink: 0;
}
.upcoming-step-num.current { background: var(--sapphire); color: var(--pure-light); box-shadow: 0 0 8px rgba(59,130,246,0.4); }
.upcoming-step-num.done-check {
background: rgba(59,130,246,0.15);
}
.aura-deco {
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
width: 200px;
height: 200px;
border-radius: 50%;
background: radial-gradient(circle, rgba(59,130,246,0.07), transparent 70%);
filter: blur(40px);
pointer-events: none;
animation: breathing 6s ease-in-out infinite;
}
.btn-next {
width: 100%;
height: 52px;
background: var(--sapphire);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 600;
color: var(--pure-light);
text-decoration: none;
box-shadow: var(--glow-sapphire);
border: none;
cursor: pointer;
gap: var(--space-2);
margin-bottom: var(--space-3);
transition: background 0.2s ease-out;
}
.btn-next:hover { background: var(--sapphire-light); }
/* Prefill looks like real content was typed */
.journal-prefilled {
color: var(--soft-light);
}
</style>
</head>
<body>
<div class="device-frame">
<!-- STATUS BAR -->
<div class="status-bar">
<span class="time">9:18</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<!-- NAV HEADER -->
<div class="nav-header">
<a class="nav-back" href="44-ritual-templates.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Evening Wind-Down</span>
<span class="nav-action">
<div class="ritual-timer">
<svg width="12" height="12" viewBox="0 0 12 12"><circle cx="6" cy="6" r="5" fill="none" stroke="currentColor" stroke-width="1"/><path d="M6 3v3l2 2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
7:41
</div>
</span>
</div>
<!-- STEP PROGRESS — on step 2 of 4 -->
<div class="ritual-progress-bar">
<div class="rp-seg done"></div>
<div class="rp-seg active"></div>
<div class="rp-seg"></div>
<div class="rp-seg"></div>
</div>
<!-- Ambient decoration -->
<div class="aura-deco"></div>
<!-- SCREEN CONTENT -->
<div class="screen-content" style="padding-top: var(--space-5);">
<!-- Step Card -->
<div class="step-card">
<div class="step-badge">
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M6 1L10 6L6 11L2 6Z" fill="currentColor" opacity="0.8"/></svg>
Step 2 — Pattern Check
</div>
<div class="step-prompt">What thought kept pulling at you today?</div>
<div class="step-subprompt">Not the event — just the story you told yourself about it.</div>
<textarea class="journal-area journal-prefilled" placeholder="What kept running through your head...">I feel like I didn't accomplish enough today and I'm falling behind on everything.</textarea>
<div class="char-count">67 / 500</div>
</div>
<!-- Upcoming Steps -->
<div class="label text-dim" style="margin-bottom: var(--space-2);">Tonight's ritual</div>
<div class="upcoming-steps">
<div class="upcoming-step-row">
<div class="upcoming-step-num done-check">
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M2 6l3 3 5-5" stroke="var(--sapphire-light)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div style="flex:1; font-size:14px; color: var(--dim-light);">Day reflection</div>
<span style="font-size: 12px; color: var(--sapphire); font-weight: 600;">Done</span>
</div>
<div class="upcoming-step-row">
<div class="upcoming-step-num current">2</div>
<div style="flex:1; font-size:14px; color: var(--soft-light);">Pattern check</div>
<span style="font-size: 12px; color: var(--dim-light);">~5 min</span>
</div>
<div class="upcoming-step-row">
<div class="upcoming-step-num">3</div>
<div style="flex:1; font-size:14px; color: var(--faint-light);">Gratitude</div>
<span style="font-size: 12px; color: var(--faint-light);">~5 min</span>
</div>
<div class="upcoming-step-row">
<div class="upcoming-step-num">4</div>
<div style="flex:1; font-size:14px; color: var(--faint-light);">Tomorrow's intention</div>
<span style="font-size: 12px; color: var(--faint-light);">~5 min</span>
</div>
</div>
<!-- Navigation -->
<a href="48-ritual-complete.html" class="btn-next">
Continue
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<a href="44-ritual-templates.html" style="text-align: center; display: block; font-size: 14px; color: var(--faint-light); text-decoration: none; padding: var(--space-2); margin-bottom: var(--space-4);">Save &amp; Exit</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,264 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Quick Grounding Ritual</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.ritual-progress-bar {
display: flex;
gap: 4px;
padding: var(--space-3) var(--space-4) 0;
}
.rp-seg {
flex: 1;
height: 4px;
border-radius: 2px;
background: var(--twilight);
transition: background 0.3s ease-out;
}
.rp-seg.done { background: var(--emerald); box-shadow: 0 0 6px rgba(16,185,129,0.4); }
.rp-seg.active { background: linear-gradient(to right, var(--emerald), var(--sapphire)); box-shadow: 0 0 8px rgba(16,185,129,0.3); animation: pulse-bar 2s ease-in-out infinite; }
@keyframes pulse-bar { 0%, 100% { opacity: 1; } 50% { opacity: 0.65; } }
.ritual-timer {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: var(--dim-light);
}
.step-card {
background: var(--deep-glass);
border: 1px solid rgba(16,185,129,0.25);
border-radius: var(--radius-2xl);
padding: var(--space-5);
margin-bottom: var(--space-4);
box-shadow: 0 0 20px rgba(16,185,129,0.05);
}
.step-badge {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: 4px 12px;
border-radius: var(--radius-full);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: var(--space-3);
background: rgba(16,185,129,0.15);
color: var(--emerald-light);
}
.step-prompt {
font-size: 18px;
font-weight: 600;
color: var(--pure-light);
line-height: 1.45;
margin-bottom: 6px;
}
.step-subprompt {
font-size: 13px;
color: var(--dim-light);
line-height: 1.5;
margin-bottom: var(--space-4);
}
.thought-input {
width: 100%;
min-height: 90px;
background: var(--kalei-navy);
border: 1px solid var(--emerald);
border-radius: var(--radius-lg);
padding: 14px;
font-size: 16px;
line-height: 1.5;
color: var(--soft-light);
outline: none;
resize: none;
box-shadow: 0 0 12px rgba(16,185,129,0.08);
font-family: var(--font-primary);
}
.thought-input::placeholder { color: var(--faint-light); }
.char-count { text-align: right; font-size: 11px; color: var(--faint-light); margin-top: var(--space-2); }
.upcoming-steps {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-4);
}
.upcoming-step-row {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-2) 0;
}
.upcoming-step-row:not(:last-child) { border-bottom: 1px solid var(--twilight); }
.upcoming-step-num {
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--twilight);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: var(--dim-light);
flex-shrink: 0;
}
.upcoming-step-num.current { background: var(--emerald); color: var(--void); box-shadow: 0 0 8px rgba(16,185,129,0.4); }
.aura-deco {
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
width: 180px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(16,185,129,0.09), transparent 70%);
filter: blur(40px);
pointer-events: none;
animation: breathing 6s ease-in-out infinite;
}
.btn-next {
width: 100%;
height: 52px;
background: var(--emerald);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 600;
color: var(--void);
text-decoration: none;
box-shadow: var(--glow-emerald);
border: none;
cursor: pointer;
gap: var(--space-2);
margin-bottom: var(--space-3);
transition: background 0.2s ease-out;
}
.btn-next:hover { background: var(--emerald-light); }
.quick-tip {
background: rgba(16,185,129,0.07);
border: 1px solid rgba(16,185,129,0.2);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-4);
font-size: 13px;
color: var(--emerald-light);
display: flex;
gap: var(--space-2);
align-items: flex-start;
line-height: 1.5;
}
.breathe-ring {
width: 52px;
height: 52px;
border-radius: 50%;
background: rgba(16,185,129,0.08);
border: 1.5px solid rgba(16,185,129,0.3);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--space-3);
animation: breathing 6s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="device-frame">
<!-- STATUS BAR -->
<div class="status-bar">
<span class="time">2:14</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<!-- NAV HEADER -->
<div class="nav-header">
<a class="nav-back" href="44-ritual-templates.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Quick Grounding</span>
<span class="nav-action">
<div class="ritual-timer">
<svg width="12" height="12" viewBox="0 0 12 12"><circle cx="6" cy="6" r="5" fill="none" stroke="currentColor" stroke-width="1"/><path d="M6 3v3l2 2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
1:08
</div>
</span>
</div>
<!-- STEP PROGRESS -->
<div class="ritual-progress-bar">
<div class="rp-seg active"></div>
<div class="rp-seg"></div>
</div>
<!-- Ambient decoration -->
<div class="aura-deco"></div>
<!-- SCREEN CONTENT -->
<div class="screen-content" style="padding-top: var(--space-5);">
<!-- Breathing ring -->
<div class="breathe-ring">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none">
<circle cx="11" cy="11" r="8" fill="none" stroke="#6EE7B7" stroke-width="1.2" stroke-dasharray="3 3"/>
<circle cx="11" cy="11" r="4" fill="#10B981" opacity="0.4"/>
</svg>
</div>
<!-- Step Card -->
<div class="step-card">
<div class="step-badge">
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M6 1L10 6L6 11L2 6Z" fill="currentColor" opacity="0.8"/></svg>
Step 1 — Name It
</div>
<div class="step-prompt">What thought is pulling you out of the present right now?</div>
<div class="step-subprompt">One sentence is enough. You don't need to solve it — just name it.</div>
<textarea class="thought-input" placeholder="I keep thinking about..."></textarea>
<div class="char-count">0 / 280</div>
</div>
<!-- Quick tip -->
<div class="quick-tip">
<svg width="14" height="14" viewBox="0 0 14 14" style="flex-shrink:0; margin-top: 1px;"><circle cx="7" cy="7" r="5.5" fill="none" stroke="currentColor" stroke-width="1"/><path d="M7 4v3.5M7 9.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
Keep it brief. One sentence captures the essence. You'll reframe it in the next step.
</div>
<!-- Steps overview -->
<div class="label text-dim" style="margin-bottom: var(--space-2);">2 Steps · ~2 min total</div>
<div class="upcoming-steps">
<div class="upcoming-step-row">
<div class="upcoming-step-num current">1</div>
<div style="flex:1; font-size:14px; color: var(--soft-light);">Name the thought</div>
<span style="font-size: 12px; color: var(--dim-light);">~1 min</span>
</div>
<div class="upcoming-step-row">
<div class="upcoming-step-num">2</div>
<div style="flex:1; font-size:14px; color: var(--faint-light);">Ground it with a reframe</div>
<span style="font-size: 12px; color: var(--faint-light);">~1 min</span>
</div>
</div>
<!-- Navigation -->
<a href="48-ritual-complete.html" class="btn-next">
Continue
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<a href="44-ritual-templates.html" style="text-align: center; display: block; font-size: 14px; color: var(--faint-light); text-decoration: none; padding: var(--space-2); margin-bottom: var(--space-4);">Exit Ritual</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,310 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Ritual Complete</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.celebration-burst {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 300px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.11) 0%, rgba(59,130,246,0.07) 30%, rgba(16,185,129,0.05) 60%, transparent 80%);
filter: blur(30px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
}
@keyframes successBurst {
0% { transform: scale(0) rotate(-30deg); opacity: 0; }
60% { transform: scale(1.15) rotate(5deg); opacity: 1; }
80% { transform: scale(0.95) rotate(-2deg); opacity: 1; }
100% { transform: scale(1) rotate(0deg); opacity: 1; }
}
.complete-hero {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--space-8) var(--space-4) var(--space-5);
text-align: center;
position: relative;
}
.burst-icon {
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-5);
animation: successBurst 0.8s ease forwards;
}
.complete-heading {
font-family: var(--font-display);
font-size: 30px;
font-weight: 700;
background: var(--prismatic);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: var(--space-2);
}
.complete-sub {
font-size: 14px;
color: var(--dim-light);
margin-bottom: var(--space-5);
line-height: 1.6;
max-width: 280px;
}
.stats-row {
display: flex;
gap: var(--space-3);
width: 100%;
margin-bottom: var(--space-5);
}
.stat-pill {
flex: 1;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-3);
text-align: center;
}
.stat-pill .val {
font-size: 22px;
font-weight: 700;
color: var(--pure-light);
}
.stat-pill .lbl {
font-size: 10px;
color: var(--dim-light);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 2px;
}
.tile-earned-card {
background: var(--deep-glass);
border: 1px solid rgba(139,92,246,0.35);
border-radius: var(--radius-xl);
padding: var(--space-4);
width: 100%;
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-5);
box-shadow: 0 0 20px rgba(139,92,246,0.12);
}
.tile-preview-icon {
width: 52px;
height: 52px;
border-radius: 12px;
background: rgba(59,130,246,0.12);
border: 1px solid var(--sapphire);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 14px rgba(59,130,246,0.2);
flex-shrink: 0;
}
.btn-done {
width: 100%;
height: 52px;
background: var(--amethyst);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 600;
color: var(--pure-light);
text-decoration: none;
box-shadow: var(--glow-amethyst);
margin-bottom: var(--space-3);
transition: background 0.2s ease-out;
}
.btn-done:hover { background: var(--amethyst-light); }
.streak-indicator {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
margin-bottom: var(--space-5);
}
.streak-flame-dots {
display: flex;
gap: 3px;
}
.flame-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--amber);
box-shadow: 0 0 5px rgba(245,158,11,0.4);
}
.flame-dot.active { animation: pulse 2s ease-in-out infinite; }
.flame-dot.new {
background: var(--amber-light);
box-shadow: 0 0 8px rgba(245,158,11,0.7);
animation: pulse 1.5s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="device-frame">
<!-- STATUS BAR -->
<div class="status-bar" style="position: relative; z-index: 2;">
<span class="time">8:52</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<!-- Celebration background -->
<div class="celebration-burst"></div>
<!-- SCREEN CONTENT -->
<div class="screen-content centered" style="position: relative; z-index: 2;">
<div class="burst-icon">
<svg width="120" height="120" viewBox="-60 -60 120 120">
<defs>
<linearGradient id="rc48-grEm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"><animate attributeName="stop-color" values="#6EE7B7;#A7F3D0;#6EE7B7" dur="5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
<radialGradient id="rc48-successGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#10B981" stop-opacity="0.4"/>
<stop offset="100%" stop-color="#10B981" stop-opacity="0"/>
</radialGradient>
<filter id="rc48-glowLg" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="rc48-glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="rc48-glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<clipPath id="rc48-clip"><circle r="56"/></clipPath>
</defs>
<g clip-path="url(#rc48-clip)" filter="url(#rc48-glowMd)">
<circle r="45" fill="none" stroke="#10B981" stroke-width="0.8" opacity="0.2" stroke-dasharray="6 8"/>
<circle r="30" fill="none" stroke="#10B981" stroke-width="0.6" opacity="0.25" stroke-dasharray="4 6"/>
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="45s" repeatCount="indefinite"/>
<path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#rc48-grEm)" opacity="0.6"/>
<g transform="rotate(60)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#rc48-grEm)" opacity="0.5"/></g>
<g transform="rotate(120)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#rc48-grEm)" opacity="0.55"/></g>
<g transform="rotate(180)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#rc48-grEm)" opacity="0.5"/></g>
<g transform="rotate(240)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#rc48-grEm)" opacity="0.6"/></g>
<g transform="rotate(300)"><path d="M 0,-45 L 3,-42 L 0,-39 L -3,-42 Z" fill="url(#rc48-grEm)" opacity="0.5"/></g>
</g>
</g>
<g filter="url(#rc48-glowLg)">
<circle r="30" fill="none" stroke="#10B981" stroke-width="2" opacity="0">
<animate attributeName="r" values="10;35;40" dur="1.5s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.6;0.3;0" dur="1.5s" repeatCount="indefinite"/>
</circle>
<circle r="20" fill="none" stroke="#6EE7B7" stroke-width="1" opacity="0">
<animate attributeName="r" values="8;25;30" dur="1.5s" begin="0.2s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.4;0.2;0" dur="1.5s" begin="0.2s" repeatCount="indefinite"/>
</circle>
<circle r="16" fill="url(#rc48-successGlow)" opacity="0.6">
<animate attributeName="opacity" values="0.4;0.8;0.4" dur="2s" repeatCount="indefinite"/>
</circle>
<path d="M 0,-10 L 10,0 L 0,10 L -10,0 Z" fill="url(#rc48-grEm)" opacity="0.9"/>
<path d="M 0,-10 L 10,0 L 0,0 Z" fill="#fff" opacity="0.2"/>
<path d="M -4,0 L -1,4 L 5,-3" fill="none" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
</g>
<circle r="4" fill="#6EE7B7" opacity="0.5" filter="url(#rc48-glowSm)">
<animate attributeName="r" values="3;5;3" dur="2s" repeatCount="indefinite"/>
</circle>
</svg>
</div>
<div class="complete-heading">Day 14. Done.</div>
<div class="complete-sub">You showed up again. That's the whole thing — fourteen mornings in a row, building something that lasts.</div>
<!-- Streak — 14 dots, last one highlighted as new -->
<div class="streak-indicator">
<div class="streak-flame-dots">
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot"></div>
<div class="flame-dot new"></div>
</div>
</div>
<span class="body-sm" style="color: var(--amber-light); font-weight: 600; margin-bottom: var(--space-5); display:block;">14-day streak — your longest yet</span>
<!-- Stats -->
<div class="stats-row">
<div class="stat-pill">
<div class="val">15</div>
<div class="lbl">Minutes</div>
</div>
<div class="stat-pill">
<div class="val">4</div>
<div class="lbl">Steps</div>
</div>
<div class="stat-pill">
<div class="val" style="color: var(--amber-light);">14</div>
<div class="lbl">Day Streak</div>
</div>
</div>
<!-- New tile earned -->
<div class="tile-earned-card">
<div class="tile-preview-icon">
<svg width="34" height="26" viewBox="-18 -12 36 24">
<defs>
<linearGradient id="rc48-grSap" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"><animate attributeName="stop-color" values="#93C5FD;#BFDBFE;#93C5FD" dur="4.5s" repeatCount="indefinite"/></stop>
<stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<filter id="rc48-glowSap" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<rect x="-15" y="-9" width="30" height="18" rx="3" fill="#121628" stroke="#3B82F6" stroke-width="0.8"/>
<g filter="url(#rc48-glowSap)">
<path d="M 0,-5 L 5,0 L 0,5 L -5,0 Z" fill="url(#rc48-grSap)" opacity="0.5"/>
</g>
</svg>
</div>
<div style="flex:1;">
<div class="subheading" style="color: var(--sapphire-light); margin-bottom: 2px;">Evidence tile added</div>
<div class="body-sm text-dim">Ritual Completion · Feb 22 · Day 14</div>
</div>
<a href="../you/42-evidence-wall-full.html" style="text-decoration:none;">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M8 4L14 10L8 16" stroke="var(--sapphire-light)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
</div>
<!-- CTA -->
<a href="../turn/10-turn-home.html" class="btn-done">Back to home</a>
<a href="49-ritual-streak.html" style="text-decoration: none; font-size: 14px; color: var(--dim-light);">View full streak history</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,422 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Ritual Streak</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.streak-hero {
text-align: center;
padding: var(--space-6) 0 var(--space-5);
position: relative;
}
.streak-number {
font-family: var(--font-display);
font-size: 80px;
font-weight: 700;
color: var(--amber-light);
line-height: 1;
text-shadow: 0 0 48px rgba(245,158,11,0.35);
}
.streak-label {
font-size: 16px;
color: var(--dim-light);
margin-top: var(--space-2);
}
.streak-started {
font-size: 12px;
color: var(--faint-light);
margin-top: 4px;
}
.streak-aura {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(245,158,11,0.12), transparent 70%);
filter: blur(32px);
animation: breathing 6s ease-in-out infinite;
pointer-events: none;
}
.week-calendar {
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-4);
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-3);
}
.calendar-month {
font-size: 14px;
font-weight: 600;
color: var(--soft-light);
}
.day-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 6px;
}
.day-label {
text-align: center;
font-size: 10px;
color: var(--faint-light);
text-transform: uppercase;
letter-spacing: 0.04em;
padding-bottom: var(--space-2);
}
.day-cell {
aspect-ratio: 1;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 500;
position: relative;
}
.day-cell.empty { background: transparent; color: transparent; }
.day-cell.no-ritual { background: var(--void); color: var(--faint-light); border: 1px solid var(--twilight); }
.day-cell.missed { background: rgba(239,68,68,0.06); color: var(--faint-light); border: 1px solid rgba(239,68,68,0.12); }
.day-cell.done {
background: rgba(245,158,11,0.18);
color: var(--amber-light);
font-weight: 700;
border: 1px solid rgba(245,158,11,0.3);
box-shadow: 0 0 6px rgba(245,158,11,0.15);
}
.day-cell.today {
background: var(--amber);
color: var(--void);
font-weight: 700;
box-shadow: 0 0 14px rgba(245,158,11,0.5);
}
.day-cell.today::after {
content: '';
position: absolute;
inset: -2px;
border-radius: 10px;
border: 2px solid var(--amber-light);
}
.day-cell.future { background: transparent; color: var(--faint-light); border: 1px dashed rgba(28,34,64,0.8); }
.streak-stats {
display: flex;
gap: var(--space-3);
margin-bottom: var(--space-4);
}
.streak-stat {
flex: 1;
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
text-align: center;
}
.streak-stat .sval {
font-size: 28px;
font-weight: 700;
color: var(--amber-light);
line-height: 1.2;
}
.streak-stat .slbl {
font-size: 11px;
color: var(--dim-light);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 2px;
}
.heat-map {
background: var(--deep-glass);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: var(--space-4);
margin-bottom: var(--space-4);
}
.heat-label { font-size: 11px; color: var(--dim-light); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--space-3); }
.heat-grid {
display: grid;
grid-template-columns: 40px repeat(7, 1fr);
gap: 4px;
align-items: center;
}
.heat-time { font-size: 10px; color: var(--faint-light); text-align: right; padding-right: 6px; }
.heat-cell {
aspect-ratio: 1;
border-radius: 4px;
}
.heat-cell.h0 { background: var(--void); border: 1px solid var(--twilight); }
.heat-cell.h1 { background: rgba(245,158,11,0.2); }
.heat-cell.h2 { background: rgba(245,158,11,0.4); }
.heat-cell.h3 { background: rgba(245,158,11,0.6); }
.heat-cell.h4 { background: var(--amber); box-shadow: 0 0 6px rgba(245,158,11,0.4); }
.heat-day-label { font-size: 9px; color: var(--faint-light); text-align: center; }
.insight-note {
font-size: 12px;
color: var(--dim-light);
line-height: 1.5;
margin-top: var(--space-2);
}
.btn-start-ritual {
width: 100%;
height: 52px;
background: var(--amber);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 600;
color: var(--void);
text-decoration: none;
box-shadow: var(--glow-amber);
margin-bottom: var(--space-6);
transition: background 0.2s ease-out;
}
.btn-start-ritual:hover { background: var(--amber-light); }
.completion-rate {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-2);
}
.rate-bar-bg {
flex: 1;
height: 6px;
background: var(--twilight);
border-radius: var(--radius-full);
overflow: hidden;
margin: 0 var(--space-3);
}
.rate-bar-fill {
height: 100%;
border-radius: var(--radius-full);
background: var(--amber);
box-shadow: 0 0 8px rgba(245,158,11,0.4);
}
</style>
</head>
<body>
<div class="device-frame">
<!-- STATUS BAR -->
<div class="status-bar">
<span class="time">8:52</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<!-- NAV HEADER -->
<div class="nav-header">
<a class="nav-back" href="44-ritual-templates.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Ritual Streak</span>
<span class="nav-action"></span>
</div>
<!-- SCREEN CONTENT -->
<div class="screen-content" style="padding-top: 0;">
<!-- Streak Hero -->
<div class="streak-hero">
<div class="streak-aura"></div>
<div class="streak-number">14</div>
<div class="streak-label">14-day streak</div>
<div class="streak-started">Started Feb 1, 2026 · Still going</div>
</div>
<!-- Streak Stats -->
<div class="streak-stats">
<div class="streak-stat">
<div class="sval">14</div>
<div class="slbl">Current</div>
</div>
<div class="streak-stat">
<div class="sval">14</div>
<div class="slbl">Longest</div>
</div>
<div class="streak-stat">
<div class="sval">89%</div>
<div class="slbl">Completion</div>
</div>
</div>
<!-- Completion rate bar -->
<div class="completion-rate" style="margin-bottom: var(--space-4);">
<span style="font-size:12px; color:var(--dim-light);">89% completion rate</span>
<div class="rate-bar-bg" style="max-width: 140px;">
<div class="rate-bar-fill" style="width:89%;"></div>
</div>
<span style="font-size:12px; color:var(--amber-light); font-weight:600;">12 of 14 days</span>
</div>
<!-- February Calendar -->
<div class="week-calendar">
<div class="calendar-header">
<span class="calendar-month">February 2026</span>
<div style="display: flex; gap: var(--space-2);">
<a href="#" style="color: var(--dim-light); text-decoration: none; font-size: 18px; line-height: 1;"></a>
<a href="#" style="color: var(--dim-light); text-decoration: none; font-size: 18px; line-height: 1;"></a>
</div>
</div>
<!-- Day labels -->
<div class="day-grid">
<div class="day-label">Mo</div>
<div class="day-label">Tu</div>
<div class="day-label">We</div>
<div class="day-label">Th</div>
<div class="day-label">Fr</div>
<div class="day-label">Sa</div>
<div class="day-label">Su</div>
</div>
<!-- Week 1: Feb starts on Sunday. Feb 1 = Sun col 7 -->
<div class="day-grid" style="margin-top: 4px;">
<div class="day-cell empty"></div>
<div class="day-cell empty"></div>
<div class="day-cell empty"></div>
<div class="day-cell empty"></div>
<div class="day-cell empty"></div>
<div class="day-cell empty"></div>
<div class="day-cell done">1</div>
</div>
<!-- Week 2: Feb 28 — missed Feb 4 & 6 in first week per ritual context -->
<div class="day-grid" style="margin-top: 4px;">
<div class="day-cell done">2</div>
<div class="day-cell done">3</div>
<div class="day-cell missed">4</div>
<div class="day-cell done">5</div>
<div class="day-cell missed">6</div>
<div class="day-cell done">7</div>
<div class="day-cell done">8</div>
</div>
<!-- Week 3: Feb 915 — all done, streak building -->
<div class="day-grid" style="margin-top: 4px;">
<div class="day-cell done">9</div>
<div class="day-cell done">10</div>
<div class="day-cell done">11</div>
<div class="day-cell done">12</div>
<div class="day-cell done">13</div>
<div class="day-cell done">14</div>
<div class="day-cell done">15</div>
</div>
<!-- Week 4: Feb 1622 — current streak, today = 22 -->
<div class="day-grid" style="margin-top: 4px;">
<div class="day-cell done">16</div>
<div class="day-cell done">17</div>
<div class="day-cell done">18</div>
<div class="day-cell done">19</div>
<div class="day-cell done">20</div>
<div class="day-cell done">21</div>
<div class="day-cell today">22</div>
</div>
<!-- Week 5: Feb 2328 — future -->
<div class="day-grid" style="margin-top: 4px;">
<div class="day-cell future">23</div>
<div class="day-cell future">24</div>
<div class="day-cell future">25</div>
<div class="day-cell future">26</div>
<div class="day-cell future">27</div>
<div class="day-cell future">28</div>
<div class="day-cell empty"></div>
</div>
</div>
<!-- Completion Time Heat Map -->
<div class="heat-map">
<div class="heat-label">When you practice — last 14 days</div>
<!-- Day labels row -->
<div class="heat-grid">
<div></div>
<div class="heat-day-label">Mo</div>
<div class="heat-day-label">Tu</div>
<div class="heat-day-label">We</div>
<div class="heat-day-label">Th</div>
<div class="heat-day-label">Fr</div>
<div class="heat-day-label">Sa</div>
<div class="heat-day-label">Su</div>
</div>
<!-- 6am row — light activity -->
<div class="heat-grid">
<div class="heat-time">6am</div>
<div class="heat-cell h0"></div>
<div class="heat-cell h1"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h1"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h0"></div>
</div>
<!-- 89am row — peak (spec: most turns 89am) -->
<div class="heat-grid">
<div class="heat-time">8am</div>
<div class="heat-cell h4"></div>
<div class="heat-cell h4"></div>
<div class="heat-cell h3"></div>
<div class="heat-cell h3"></div>
<div class="heat-cell h4"></div>
<div class="heat-cell h2"></div>
<div class="heat-cell h3"></div>
</div>
<!-- 12pm row -->
<div class="heat-grid">
<div class="heat-time">12pm</div>
<div class="heat-cell h1"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h1"></div>
<div class="heat-cell h2"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h1"></div>
<div class="heat-cell h1"></div>
</div>
<!-- 6pm row — secondary peak (spec: 68pm) -->
<div class="heat-grid">
<div class="heat-time">6pm</div>
<div class="heat-cell h2"></div>
<div class="heat-cell h2"></div>
<div class="heat-cell h1"></div>
<div class="heat-cell h1"></div>
<div class="heat-cell h3"></div>
<div class="heat-cell h2"></div>
<div class="heat-cell h1"></div>
</div>
<!-- 9pm -->
<div class="heat-grid">
<div class="heat-time">9pm</div>
<div class="heat-cell h0"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h1"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h0"></div>
<div class="heat-cell h0"></div>
</div>
<div class="insight-note">Your strongest window is 89am on weekdays — that's when your rituals land deepest.</div>
</div>
<!-- CTA -->
<a href="44-ritual-templates.html" class="btn-start-ritual">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" style="margin-right:6px;">
<path d="M9 2L16 9L9 16L2 9Z" fill="var(--void)" opacity="0.5"/>
<path d="M9 5L13 9L9 13L5 9Z" fill="var(--void)" opacity="0.8"/>
</svg>
Start Today's Ritual
</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,391 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Spectrum Dashboard</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.prism-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 10px;
border-radius: var(--radius-full);
background: linear-gradient(135deg, rgba(139,92,246,0.25), rgba(59,130,246,0.25), rgba(16,185,129,0.25));
border: 1px solid rgba(139,92,246,0.4);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.prism-badge-text {
background: linear-gradient(135deg, #C4B5FD, #93C5FD, #6EE7B7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.nav-header-right {
display: flex;
align-items: center;
}
.time-selector {
display: flex;
gap: 4px;
padding: 12px 0 8px;
}
.time-chip {
flex: 1;
height: 32px;
border-radius: var(--radius-full);
border: 1px solid var(--twilight);
background: transparent;
color: var(--dim-light);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
font-family: var(--font-primary);
}
.time-chip.active {
background: rgba(139,92,246,0.15);
border-color: var(--amethyst);
color: var(--amethyst-light);
}
.viz-card {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: 14px;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 14px;
cursor: pointer;
text-decoration: none;
transition: border-color 0.2s;
}
.viz-card:hover { border-color: rgba(139,92,246,0.4); }
.viz-thumb {
width: 64px;
height: 48px;
border-radius: var(--radius-md);
background: var(--void);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
overflow: hidden;
}
.viz-info { flex: 1; }
.viz-title {
font-size: 15px;
font-weight: 600;
color: var(--pure-light);
margin-bottom: 2px;
}
.viz-desc {
font-size: 12px;
color: var(--dim-light);
}
.viz-chevron {
color: var(--faint-light);
}
.insight-card {
background: linear-gradient(135deg, rgba(139,92,246,0.08), rgba(59,130,246,0.05));
border: 1px solid rgba(139,92,246,0.2);
border-radius: var(--radius-xl);
padding: 14px;
margin-top: 4px;
}
.insight-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--amethyst-light);
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.insight-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.5;
}
.summary-links {
display: flex;
gap: 10px;
margin-top: 10px;
}
.summary-link {
flex: 1;
height: 44px;
border-radius: var(--radius-lg);
border: 1px solid var(--twilight);
background: var(--deep-glass);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
font-size: 13px;
font-weight: 500;
color: var(--soft-light);
text-decoration: none;
transition: border-color 0.2s;
}
.summary-link:hover { border-color: rgba(139,92,246,0.4); }
.screen-content { padding-bottom: 20px; }
.spectrum-aura {
position: absolute;
top: 80px;
right: -60px;
width: 180px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139,92,246,0.07) 0%, rgba(59,130,246,0.04) 40%, transparent 70%);
filter: blur(40px);
pointer-events: none;
animation: breathing 6s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="../you/35-you-profile.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Spectrum</span>
<span class="nav-action">
<div class="prism-badge">
<svg width="10" height="10" viewBox="0 0 10 10">
<defs>
<linearGradient id="prismIcon" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/>
<stop offset="50%" stop-color="#93C5FD"/>
<stop offset="100%" stop-color="#6EE7B7"/>
</linearGradient>
</defs>
<path d="M5 1L9 5L5 9L1 5Z" fill="url(#prismIcon)" opacity="0.9"/>
</svg>
<span class="prism-badge-text">Prism</span>
</div>
</span>
</div>
<div class="spectrum-aura"></div>
<div class="screen-content">
<!-- Time range selector -->
<div class="time-selector">
<button class="time-chip active" onclick="selectTime(this)">Week</button>
<button class="time-chip" onclick="selectTime(this)">Month</button>
<button class="time-chip" onclick="selectTime(this)">All Time</button>
</div>
<!-- Visualization cards -->
<a class="viz-card" href="51-spectrum-river.html">
<div class="viz-thumb">
<!-- River thumbnail -->
<svg width="64" height="48" viewBox="0 0 64 48">
<defs>
<linearGradient id="riverGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8B5CF6" stop-opacity="0.8"/>
<stop offset="33%" stop-color="#3B82F6" stop-opacity="0.7"/>
<stop offset="66%" stop-color="#10B981" stop-opacity="0.7"/>
<stop offset="100%" stop-color="#F59E0B" stop-opacity="0.8"/>
</linearGradient>
</defs>
<path d="M0 20 C10 14 20 28 30 22 C40 16 50 26 64 20" stroke="url(#riverGrad)" stroke-width="6" fill="none" stroke-linecap="round" opacity="0.9"/>
<path d="M0 26 C10 20 20 34 30 28 C40 22 50 32 64 26" stroke="url(#riverGrad)" stroke-width="3" fill="none" stroke-linecap="round" opacity="0.5"/>
<circle cx="12" cy="17" r="2" fill="#C4B5FD"/>
<circle cx="32" cy="22" r="2" fill="#6EE7B7"/>
<circle cx="52" cy="21" r="2" fill="#FDE68A"/>
</svg>
</div>
<div class="viz-info">
<div class="viz-title">The River</div>
<div class="viz-desc">Emotional patterns over time</div>
</div>
<div class="viz-chevron">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</a>
<a class="viz-card" href="52-spectrum-glass.html">
<div class="viz-thumb">
<!-- Faceted gem thumbnail — prismatic faces -->
<svg width="64" height="48" viewBox="0 0 64 48">
<defs>
<linearGradient id="glassAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A" stop-opacity="0.8"/>
<stop offset="100%" stop-color="#D97706" stop-opacity="0.3"/>
</linearGradient>
</defs>
<!-- Outer hex guide -->
<polygon points="32,4 54,16 54,32 32,44 10,32 10,16" stroke="#1C2240" stroke-width="0.5" fill="none" opacity="0.4"/>
<!-- Inner guide -->
<polygon points="32,14 44,21 44,28 32,35 20,28 20,21" stroke="#1C2240" stroke-width="0.5" fill="none" opacity="0.3"/>
<!-- Gem faces with distinct jewel tones -->
<polygon points="32,7 42,18 32,24" fill="#8B5CF6" opacity="0.15"/>
<polygon points="42,18 50,30 32,24" fill="#3B82F6" opacity="0.15"/>
<polygon points="50,30 42,41 32,24" fill="#10B981" opacity="0.15"/>
<polygon points="42,41 22,41 32,24" fill="#F59E0B" opacity="0.15"/>
<polygon points="22,41 14,30 32,24" fill="#EC4899" opacity="0.12"/>
<polygon points="14,30 22,18 32,24" fill="#6366F1" opacity="0.12"/>
<!-- Main faceted outline -->
<polygon points="32,7 50,18 50,30 32,43 14,30 14,18" fill="url(#glassAmber)" fill-opacity="0.06" stroke="#FDE68A" stroke-width="0.8" opacity="0.7"/>
<!-- Center diamond -->
<path d="M32 20L35 24L32 28L29 24Z" fill="#F59E0B" opacity="0.4"/>
</svg>
</div>
<div class="viz-info">
<div class="viz-title">Your Glass</div>
<div class="viz-desc">Feature balance radar</div>
</div>
<div class="viz-chevron">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</a>
<a class="viz-card" href="53-spectrum-impact.html">
<div class="viz-thumb">
<!-- Bar chart thumbnail -->
<svg width="64" height="48" viewBox="0 0 64 48">
<rect x="6" y="28" width="7" height="14" rx="2" fill="#EF4444" opacity="0.7"/>
<rect x="15" y="18" width="7" height="24" rx="2" fill="#10B981" opacity="0.7"/>
<rect x="26" y="22" width="7" height="20" rx="2" fill="#EF4444" opacity="0.7"/>
<rect x="35" y="12" width="7" height="30" rx="2" fill="#10B981" opacity="0.7"/>
<rect x="46" y="26" width="7" height="16" rx="2" fill="#EF4444" opacity="0.7"/>
<rect x="55" y="14" width="7" height="28" rx="2" fill="#10B981" opacity="0.7"/>
</svg>
</div>
<div class="viz-info">
<div class="viz-title">Turn Impact</div>
<div class="viz-desc">Before &amp; after mood shifts</div>
</div>
<div class="viz-chevron">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</a>
<a class="viz-card" href="54-spectrum-rhythm.html">
<div class="viz-thumb">
<!-- Bubble chart thumbnail -->
<svg width="64" height="48" viewBox="0 0 64 48">
<circle cx="14" cy="36" r="5" fill="#8B5CF6" opacity="0.6"/>
<circle cx="28" cy="20" r="8" fill="#8B5CF6" opacity="0.8"/>
<circle cx="42" cy="30" r="4" fill="#3B82F6" opacity="0.6"/>
<circle cx="54" cy="16" r="6" fill="#10B981" opacity="0.7"/>
<circle cx="20" cy="10" r="3" fill="#F59E0B" opacity="0.6"/>
</svg>
</div>
<div class="viz-info">
<div class="viz-title">Rhythm Detection</div>
<div class="viz-desc">Your activity patterns</div>
</div>
<div class="viz-chevron">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</a>
<a class="viz-card" href="55-spectrum-growth.html">
<div class="viz-thumb">
<!-- Line chart thumbnail -->
<svg width="64" height="48" viewBox="0 0 64 48">
<defs>
<linearGradient id="growthArea" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#10B981" stop-opacity="0.3"/>
<stop offset="100%" stop-color="#10B981" stop-opacity="0"/>
</linearGradient>
</defs>
<path d="M4 38 L18 30 L30 24 L42 16 L56 8" stroke="#10B981" stroke-width="2" fill="none" stroke-linecap="round"/>
<path d="M4 38 L18 30 L30 24 L42 16 L56 8 L56 44 L4 44Z" fill="url(#growthArea)"/>
<circle cx="30" cy="24" r="3" fill="#10B981"/>
<circle cx="56" cy="8" r="3" fill="#10B981"/>
</svg>
</div>
<div class="viz-info">
<div class="viz-title">Growth Trajectory</div>
<div class="viz-desc">Wellness score over time</div>
</div>
<div class="viz-chevron">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</a>
<!-- AI Insight Card -->
<div class="insight-card">
<div class="insight-label">
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M6 1L11 6L6 11L1 6Z" fill="#A78BFA"/>
</svg>
This Week's Pattern
</div>
<div class="insight-text">Catastrophizing is down this week — your 14-day ritual streak is doing real work. Morning turns (89am) show your highest reframe quality. Mind Reading remains steady; worth watching.</div>
</div>
<!-- Weekly / Monthly summary links -->
<div class="summary-links">
<a class="summary-link" href="56-spectrum-weekly.html">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect x="1" y="3" width="12" height="10" rx="2" stroke="#94A3B8" stroke-width="1.2"/>
<path d="M4 1V4M10 1V4" stroke="#94A3B8" stroke-width="1.2" stroke-linecap="round"/>
</svg>
Weekly Summary
</a>
<a class="summary-link" href="57-spectrum-monthly.html">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect x="1" y="3" width="12" height="10" rx="2" stroke="#94A3B8" stroke-width="1.2"/>
<path d="M4 1V4M10 1V4" stroke="#94A3B8" stroke-width="1.2" stroke-linecap="round"/>
<path d="M3 8H11M3 10.5H8" stroke="#94A3B8" stroke-width="1" stroke-linecap="round"/>
</svg>
Monthly Report
</a>
</div>
</div>
</div>
<script>
function selectTime(el) {
document.querySelectorAll('.time-chip').forEach(c => c.classList.remove('active'));
el.classList.add('active');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,314 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — The River</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.river-container {
padding: 16px;
position: relative;
}
.date-labels {
display: flex;
justify-content: space-between;
padding: 0 8px;
margin-bottom: 6px;
}
.date-label {
font-size: 10px;
color: var(--faint-light);
font-weight: 500;
}
.river-svg-wrap {
border-radius: var(--radius-xl);
overflow: hidden;
background: var(--void);
border: 1px solid var(--twilight);
}
.data-points-section {
margin-top: 16px;
}
.data-point-row {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid rgba(28,34,64,0.5);
}
.data-point-row:last-child { border-bottom: none; }
.dp-date {
font-size: 11px;
color: var(--faint-light);
width: 52px;
flex-shrink: 0;
}
.dp-diamond { flex-shrink: 0; }
.dp-text {
flex: 1;
font-size: 13px;
color: var(--soft-light);
line-height: 1.4;
}
.dp-tag {
font-size: 10px;
font-weight: 600;
color: var(--faint-light);
background: rgba(28,34,64,0.8);
padding: 2px 7px;
border-radius: var(--radius-full);
flex-shrink: 0;
}
.dp-mood {
font-size: 11px;
font-weight: 600;
flex-shrink: 0;
min-width: 36px;
text-align: right;
}
.insight-panel {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: 14px;
margin-top: 16px;
}
.insight-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--amethyst-light);
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.insight-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.55;
}
.screen-content { padding-bottom: 20px; }
.mood-dip-note {
font-size: 11px;
color: var(--rose-light);
display: flex;
align-items: center;
gap: 4px;
margin-top: 6px;
}
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">8:52</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/><rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/><rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/><rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/><path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/><circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/></svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/><rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/><rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/></svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="50-spectrum-dashboard.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">The River</span>
<span class="nav-action" style="font-size:11px; color:var(--faint-light);">Feb 122</span>
</div>
<div class="screen-content no-pad">
<div class="river-container">
<!-- Date labels — 3 weeks -->
<div class="date-labels">
<span class="date-label">Feb 1</span>
<span class="date-label">Feb 8</span>
<span class="date-label">Feb 15</span>
<span class="date-label">Feb 22</span>
</div>
<!-- River SVG — mood trend over 3 weeks with dip around Feb 15 -->
<div class="river-svg-wrap">
<svg width="358" height="160" viewBox="0 0 358 160" style="display:block;">
<defs>
<linearGradient id="s51-riverGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8B5CF6" stop-opacity="0.7"/>
<stop offset="35%" stop-color="#3B82F6" stop-opacity="0.65"/>
<stop offset="55%" stop-color="#EC4899" stop-opacity="0.6"/>
<stop offset="70%" stop-color="#EF4444" stop-opacity="0.55"/>
<stop offset="85%" stop-color="#3B82F6" stop-opacity="0.65"/>
<stop offset="100%" stop-color="#10B981" stop-opacity="0.7"/>
</linearGradient>
<linearGradient id="s51-riverGradFill" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8B5CF6" stop-opacity="0.15"/>
<stop offset="35%" stop-color="#3B82F6" stop-opacity="0.10"/>
<stop offset="55%" stop-color="#EC4899" stop-opacity="0.08"/>
<stop offset="70%" stop-color="#EF4444" stop-opacity="0.10"/>
<stop offset="100%" stop-color="#10B981" stop-opacity="0.14"/>
</linearGradient>
<linearGradient id="s51-grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/><stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="s51-grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="s51-grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="s51-grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="s51-grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<filter id="s51-glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<rect width="358" height="160" fill="#0A0E1A"/>
<!-- Grid lines -->
<line x1="24" y1="30" x2="358" y2="30" stroke="#1C2240" stroke-width="0.5" stroke-dasharray="3,6"/>
<line x1="24" y1="75" x2="358" y2="75" stroke="#1C2240" stroke-width="0.5" stroke-dasharray="3,6"/>
<line x1="24" y1="120" x2="358" y2="120" stroke="#1C2240" stroke-width="0.5" stroke-dasharray="3,6"/>
<!-- Y-axis labels -->
<text x="20" y="33" text-anchor="end" fill="#475569" font-size="8" font-family="Inter">High</text>
<text x="20" y="78" text-anchor="end" fill="#475569" font-size="8" font-family="Inter">Mid</text>
<text x="20" y="123" text-anchor="end" fill="#475569" font-size="8" font-family="Inter">Low</text>
<!-- Vertical week dividers -->
<line x1="109" y1="20" x2="109" y2="135" stroke="#1C2240" stroke-width="0.5" stroke-dasharray="2,6"/>
<line x1="218" y1="20" x2="218" y2="135" stroke="#1C2240" stroke-width="0.5" stroke-dasharray="2,6"/>
<!-- River path: gradual rise W1, slight dip around Feb 15, recovery to peak Feb 22 -->
<g transform="translate(189, 75)" filter="url(#s51-glowSm)">
<!-- Upper bound — rising then dip then surge -->
<path d="M -165,-5 C -140,-12 -110,-18 -80,-22 C -50,-26 -30,-20 0,5 C 20,22 40,18 80,0 C 110,-12 140,-22 165,-28"
fill="none" stroke="url(#s51-riverGrad)" stroke-width="2" opacity="0.85"/>
<!-- Lower bound -->
<path d="M -165,15 C -140,10 -110,5 -80,2 C -50,0 -30,8 0,28 C 20,40 40,36 80,20 C 110,8 140,-2 165,-10"
fill="none" stroke="url(#s51-riverGrad)" stroke-width="1" opacity="0.45"/>
<!-- Fill between bands -->
<path d="M -165,-5 C -140,-12 -110,-18 -80,-22 C -50,-26 -30,-20 0,5 C 20,22 40,18 80,0 C 110,-12 140,-22 165,-28
L 165,-10 C 140,-2 110,8 80,20 C 40,36 20,40 0,28 C -30,8 -50,0 -80,2 C -110,5 -140,10 -165,15 Z"
fill="url(#s51-riverGradFill)" opacity="0.7"/>
<!-- Data point fragments — key moments in Alex's story -->
<!-- Feb 1: start, amethyst, mid-mood -->
<path d="M -157,3 L -154,6 L -157,9 L -160,6 Z" fill="url(#s51-grAmethyst)" opacity="0.7"/>
<path d="M -157,3 L -154,6 L -157,6 Z" fill="#fff" opacity="0.15"/>
<!-- Feb 5: gains momentum, sapphire -->
<path d="M -109,-15 L -106,-12 L -109,-9 L -112,-12 Z" fill="url(#s51-grSapphire)" opacity="0.7"/>
<path d="M -109,-15 L -106,-12 L -109,-12 Z" fill="#fff" opacity="0.15"/>
<!-- Feb 10: strong week, emerald -->
<path d="M -52,-20 L -49,-17 L -52,-14 L -55,-17 Z" fill="url(#s51-grEmerald)" opacity="0.75"/>
<path d="M -52,-20 L -49,-17 L -52,-17 Z" fill="#fff" opacity="0.15"/>
<!-- Feb 15: work stress dip, rose -->
<path d="M 0,14 L 3,17 L 0,20 L -3,17 Z" fill="url(#s51-grRose)" opacity="0.8"/>
<path d="M 0,14 L 3,17 L 0,17 Z" fill="#fff" opacity="0.15"/>
<!-- Feb 18: recovery, amber -->
<path d="M 52,8 L 55,11 L 52,14 L 49,11 Z" fill="url(#s51-grAmber)" opacity="0.7"/>
<path d="M 52,8 L 55,11 L 52,11 Z" fill="#fff" opacity="0.15"/>
<!-- Feb 22: today, peak, emerald pulse -->
<path d="M 157,-26 L 160,-23 L 157,-20 L 154,-23 Z" fill="url(#s51-grEmerald)" opacity="0.9">
<animate attributeName="opacity" values="0.7;1;0.7" dur="2s" repeatCount="indefinite"/>
</path>
<path d="M 157,-26 L 160,-23 L 157,-23 Z" fill="#fff" opacity="0.2"/>
</g>
<!-- Dip annotation -->
<text x="178" y="18" text-anchor="middle" fill="#F9A8D4" font-size="7" font-family="Inter" opacity="0.7">Work stress</text>
<line x1="178" y1="20" x2="178" y2="105" stroke="#DB2777" stroke-width="0.5" stroke-dasharray="2,4" opacity="0.3"/>
<!-- X axis labels -->
<text x="44" y="148" text-anchor="middle" fill="#475569" font-size="8" font-family="Inter">Feb 1</text>
<text x="109" y="148" text-anchor="middle" fill="#475569" font-size="8" font-family="Inter">Feb 8</text>
<text x="178" y="148" text-anchor="middle" fill="#475569" font-size="8" font-family="Inter">Feb 15</text>
<text x="240" y="148" text-anchor="middle" fill="#475569" font-size="8" font-family="Inter">Feb 18</text>
<text x="320" y="148" text-anchor="middle" fill="#475569" font-size="8" font-family="Inter">Feb 22</text>
</svg>
</div>
<!-- Key moments trail -->
<div class="data-points-section">
<div class="section-header">
<span class="section-title">Key Moments</span>
</div>
<div class="data-point-row">
<span class="dp-date">Feb 5</span>
<svg class="dp-diamond" width="14" height="14" viewBox="0 0 14 14">
<defs><linearGradient id="dpS" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/></linearGradient></defs>
<path d="M7 1L13 7L7 13L1 7Z" fill="url(#dpS)"/>
</svg>
<span class="dp-text">Week 1 momentum builds — first 5-day run</span>
<span class="dp-tag">Catastrophizing</span>
<span class="dp-mood" style="color: var(--sapphire-light);">+19%</span>
</div>
<div class="data-point-row">
<span class="dp-date">Feb 10</span>
<svg class="dp-diamond" width="14" height="14" viewBox="0 0 14 14">
<defs><linearGradient id="dpE" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/></linearGradient></defs>
<path d="M7 1L13 7L7 13L1 7Z" fill="url(#dpE)"/>
</svg>
<span class="dp-text">Best reframe of the month on a work fear</span>
<span class="dp-tag">Should Stmts</span>
<span class="dp-mood" style="color: var(--emerald-light);">+34%</span>
</div>
<div class="data-point-row">
<span class="dp-date">Feb 15</span>
<svg class="dp-diamond" width="14" height="14" viewBox="0 0 14 14">
<defs><linearGradient id="dpR" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#DB2777"/></linearGradient></defs>
<path d="M7 1L13 7L7 13L1 7Z" fill="url(#dpR)"/>
</svg>
<span class="dp-text">Stressful work week — but ritual held steady</span>
<span class="dp-tag">Mind Reading</span>
<span class="dp-mood" style="color: var(--rose-light);">8%</span>
</div>
<div class="data-point-row">
<span class="dp-date">Feb 22</span>
<svg class="dp-diamond" width="14" height="14" viewBox="0 0 14 14">
<defs><linearGradient id="dpA" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/></linearGradient></defs>
<path d="M7 1L13 7L7 13L1 7Z" fill="url(#dpA)"/>
</svg>
<span class="dp-text">Day 14 — highest wellbeing score yet</span>
<span class="dp-tag">Ritual</span>
<span class="dp-mood" style="color: var(--emerald-light);">+41%</span>
</div>
</div>
<!-- AI Insight -->
<div class="insight-panel">
<div class="insight-label">
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M6 1L11 6L6 11L1 6Z" fill="#A78BFA"/>
</svg>
River Reading
</div>
<div class="insight-text">Your emotional river tells a clear story: gradual rise in weeks 12, a dip around Feb 15 (the work-stress week), then a strong recovery. The dip didn't break your streak — you kept practicing through it. That's the point.</div>
<div class="mood-dip-note">
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M5 1L9 5L5 9L1 5Z" fill="#F9A8D4" opacity="0.8"/></svg>
Feb 15 dip is normal — stress week. You bounced back in 2 days.
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,300 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=390, initial-scale=1">
<title>Kalei — Your Glass</title>
<link rel="stylesheet" href="../../assets/design-system.css">
<style>
.glass-container {
padding: 16px;
}
.radar-wrap {
display: flex;
justify-content: center;
background: var(--void);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: 20px 0;
}
.legend {
margin-top: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
}
.legend-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.legend-label {
font-size: 13px;
color: var(--soft-light);
flex: 1;
}
.legend-value {
font-size: 13px;
font-weight: 600;
color: var(--pure-light);
}
.legend-bar-wrap {
flex: 1;
height: 4px;
background: var(--twilight);
border-radius: var(--radius-full);
overflow: hidden;
}
.legend-bar {
height: 100%;
border-radius: var(--radius-full);
}
.balance-card {
background: var(--kalei-navy);
border: 1px solid var(--twilight);
border-radius: var(--radius-xl);
padding: 14px;
margin-top: 12px;
}
.balance-header {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--dim-light);
margin-bottom: 10px;
}
.insight-panel {
background: linear-gradient(135deg, rgba(139,92,246,0.06), rgba(59,130,246,0.04));
border: 1px solid rgba(139,92,246,0.2);
border-radius: var(--radius-xl);
padding: 14px;
margin-top: 12px;
}
.insight-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--amethyst-light);
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.insight-text {
font-size: 14px;
color: var(--soft-light);
line-height: 1.55;
}
.screen-content { padding-bottom: 20px; }
</style>
</head>
<body>
<div class="device-frame">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1" y="7" width="2.5" height="5" rx="0.5" fill="#E2E8F0" opacity="0.4"/>
<rect x="4.5" y="5" width="2.5" height="7" rx="0.5" fill="#E2E8F0" opacity="0.6"/>
<rect x="8" y="3" width="2.5" height="9" rx="0.5" fill="#E2E8F0" opacity="0.8"/>
<rect x="11.5" y="1" width="2.5" height="11" rx="0.5" fill="#E2E8F0"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M 2,8 C 4,4 12,4 14,8" stroke="#E2E8F0" stroke-width="1.2" fill="none" stroke-linecap="round"/>
<path d="M 4,10 C 6,7 10,7 12,10" stroke="#E2E8F0" stroke-width="1" fill="none" opacity="0.8" stroke-linecap="round"/>
<circle cx="8" cy="12" r="1.2" fill="#E2E8F0"/>
</svg>
<svg width="24" height="12" viewBox="0 0 24 12" fill="none">
<rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="#E2E8F0" stroke-width="1" opacity="0.5"/>
<rect x="22" y="3" width="2" height="6" rx="1" fill="#E2E8F0" opacity="0.3"/>
<rect x="2" y="2" width="16" height="8" rx="1.5" fill="#10B981" opacity="0.9"/>
</svg>
</div>
</div>
<div class="nav-header">
<a class="nav-back" href="50-spectrum-dashboard.html">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<span class="nav-title">Your Glass</span>
<span class="nav-action"></span>
</div>
<div class="screen-content no-pad">
<div class="glass-container">
<!-- Faceted Gem / Radar Chart — Your Glass -->
<div class="radar-wrap">
<svg width="280" height="240" viewBox="0 0 280 240">
<defs>
<!-- Faceted gem face gradients — prismatic per face -->
<linearGradient id="s52-grAmber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FDE68A"/><stop offset="100%" stop-color="#D97706"/>
</linearGradient>
<linearGradient id="s52-grAmethyst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C4B5FD"/><stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
<linearGradient id="s52-grSapphire" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#93C5FD"/><stop offset="100%" stop-color="#2563EB"/>
</linearGradient>
<linearGradient id="s52-grEmerald" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6EE7B7"/><stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="s52-grRose" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FBCFE8"/><stop offset="100%" stop-color="#DB2777"/>
</linearGradient>
<linearGradient id="s52-grIndigo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A5B4FC"/><stop offset="100%" stop-color="#4338CA"/>
</linearGradient>
<filter id="s52-glowMd" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="s52-glowSm" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="s52-glow" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="8" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- Central glow aura -->
<ellipse cx="140" cy="115" rx="60" ry="55" fill="#F59E0B" opacity="0.04" filter="url(#s52-glow)"/>
<!-- Radar chart -->
<g transform="translate(140, 115)">
<!-- Axis guides -->
<g opacity="0.15">
<line x1="0" y1="0" x2="0" y2="-70" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="61" y2="-35" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="61" y2="35" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="0" y2="70" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="-61" y2="35" stroke="#475569" stroke-width="0.5"/>
<line x1="0" y1="0" x2="-61" y2="-35" stroke="#475569" stroke-width="0.5"/>
<path d="M 0,-35 L 30,-17 L 30,17 L 0,35 L -30,17 L -30,-17 Z" fill="none" stroke="#475569" stroke-width="0.3"/>
<path d="M 0,-70 L 61,-35 L 61,35 L 0,70 L -61,35 L -61,-35 Z" fill="none" stroke="#475569" stroke-width="0.3"/>
</g>
<!-- Faceted gem faces — each face a different jewel tone -->
<g filter="url(#s52-glowMd)">
<!-- Top face: amber (Catastrophizing) -->
<path d="M 0,0 L -3,-52 L 3,-52 Z" fill="#FDE68A" fill-opacity="0.06"/>
<path d="M 0,-55 L 40,-20 L 0,0 Z" fill="#F59E0B" fill-opacity="0.07"/>
<!-- Upper-right face: amethyst (Black-White) -->
<path d="M 0,0 L 40,-20 L 50,25 Z" fill="#8B5CF6" fill-opacity="0.06"/>
<!-- Lower-right face: sapphire (Mind Reading) -->
<path d="M 0,0 L 50,25 L 10,50 Z" fill="#3B82F6" fill-opacity="0.06"/>
<!-- Bottom face: emerald (Fortune Telling) -->
<path d="M 0,0 L 10,50 L -45,30 Z" fill="#10B981" fill-opacity="0.06"/>
<!-- Left face: rose (Personalization) -->
<path d="M 0,0 L -45,30 L -35,-25 Z" fill="#EC4899" fill-opacity="0.06"/>
<!-- Upper-left face: indigo (Should Stmts) -->
<path d="M 0,0 L -35,-25 L 0,-55 Z" fill="#6366F1" fill-opacity="0.06"/>
<!-- Main outline -->
<path d="M 0,-55 L 40,-20 L 50,25 L 10,50 L -45,30 L -35,-25 Z"
fill="none" stroke="#F59E0B" stroke-width="1.2" opacity="0.5"/>
<!-- Vertex diamonds — each vertex gets its facet color -->
<path d="M 0,-55 L 3,-52 L 0,-49 L -3,-52 Z" fill="url(#s52-grAmber)" opacity="0.9" filter="url(#s52-glowSm)"/>
<path d="M 0,-55 L 3,-52 L 0,-52 Z" fill="#fff" opacity="0.2"/>
<path d="M 40,-20 L 43,-17 L 40,-14 L 37,-17 Z" fill="url(#s52-grAmethyst)" opacity="0.8" filter="url(#s52-glowSm)"/>
<path d="M 40,-20 L 43,-17 L 40,-17 Z" fill="#fff" opacity="0.2"/>
<path d="M 50,25 L 53,28 L 50,31 L 47,28 Z" fill="url(#s52-grSapphire)" opacity="0.8" filter="url(#s52-glowSm)"/>
<path d="M 50,25 L 53,28 L 50,28 Z" fill="#fff" opacity="0.2"/>
<path d="M 10,50 L 13,53 L 10,56 L 7,53 Z" fill="url(#s52-grEmerald)" opacity="0.7" filter="url(#s52-glowSm)"/>
<path d="M 10,50 L 13,53 L 10,53 Z" fill="#fff" opacity="0.2"/>
<path d="M -45,30 L -42,33 L -45,36 L -48,33 Z" fill="url(#s52-grRose)" opacity="0.8" filter="url(#s52-glowSm)"/>
<path d="M -45,30 L -42,33 L -45,33 Z" fill="#fff" opacity="0.2"/>
<path d="M -35,-25 L -32,-22 L -35,-19 L -38,-22 Z" fill="url(#s52-grIndigo)" opacity="0.8" filter="url(#s52-glowSm)"/>
<path d="M -35,-25 L -32,-22 L -35,-22 Z" fill="#fff" opacity="0.2"/>
<!-- Central core diamond -->
<path d="M 0,-6 L 5,0 L 0,6 L -5,0 Z" fill="#F59E0B" opacity="0.3"/>
</g>
<!-- Axis labels — colored per facet -->
<text x="0" y="-78" text-anchor="middle" font-size="9" fill="#FDE68A" font-family="Inter" font-weight="500">Catastrophizing</text>
<text x="76" y="-20" text-anchor="start" font-size="9" fill="#C4B5FD" font-family="Inter" font-weight="500">Black-White</text>
<text x="76" y="30" text-anchor="start" font-size="9" fill="#93C5FD" font-family="Inter" font-weight="500">Mind Reading</text>
<text x="0" y="76" text-anchor="middle" font-size="9" fill="#6EE7B7" font-family="Inter" font-weight="500">Fortune Telling</text>
<text x="-76" y="30" text-anchor="end" font-size="9" fill="#F9A8D4" font-family="Inter" font-weight="500">Personalization</text>
<text x="-76" y="-20" text-anchor="end" font-size="9" fill="#A5B4FC" font-family="Inter" font-weight="500">Should Stmts</text>
</g>
</svg>
</div>
<!-- Fragment pattern balance — maps to Alex's canonical data -->
<div class="balance-card">
<div class="balance-header">Fragment Patterns — 47 Turns</div>
<div class="legend">
<div class="legend-item">
<div class="legend-dot" style="background: var(--amber);"></div>
<div class="legend-label">Catastrophizing</div>
<div class="legend-bar-wrap">
<div class="legend-bar" style="width:100%; background: linear-gradient(90deg, var(--amber), var(--amber-light));"></div>
</div>
<div class="legend-value" style="color: var(--amber-light);">7x</div>
</div>
<div class="legend-item">
<div class="legend-dot" style="background: var(--sapphire);"></div>
<div class="legend-label">Mind Reading</div>
<div class="legend-bar-wrap">
<div class="legend-bar" style="width:57%; background: var(--sapphire);"></div>
</div>
<div class="legend-value" style="color: var(--sapphire-light);">4x</div>
</div>
<div class="legend-item">
<div class="legend-dot" style="background: var(--amethyst);"></div>
<div class="legend-label">Should Statements</div>
<div class="legend-bar-wrap">
<div class="legend-bar" style="width:43%; background: var(--amethyst);"></div>
</div>
<div class="legend-value" style="color: var(--amethyst-light);">3x</div>
</div>
<div class="legend-item">
<div class="legend-dot" style="background: var(--emerald);"></div>
<div class="legend-label">Overgeneralization</div>
<div class="legend-bar-wrap">
<div class="legend-bar" style="width:28%; background: var(--emerald);"></div>
</div>
<div class="legend-value" style="color: var(--emerald-light);">2x</div>
</div>
<div class="legend-item">
<div class="legend-dot" style="background: var(--rose);"></div>
<div class="legend-label">Personalization</div>
<div class="legend-bar-wrap">
<div class="legend-bar" style="width:28%; background: var(--rose);"></div>
</div>
<div class="legend-value" style="color: var(--rose-light);">2x</div>
</div>
</div>
</div>
<!-- AI Insight -->
<div class="insight-panel">
<div class="insight-label">
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M6 1L11 6L6 11L1 6Z" fill="#A78BFA"/>
</svg>
Glass Reading
</div>
<div class="insight-text">Catastrophizing is your dominant facet — 7 of your 47 turns. The good news: it's been declining each week. Mind Reading holds steady at 4. Your glass is gradually becoming more balanced as the ritual work lands.</div>
</div>
</div>
</div>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More