Spaces:
Running
Running
Commit ·
9b1f90c
1
Parent(s): 20f0740
v1 cleanup: drop fork artifacts, add MethdAI logo, fix install URLs
Browse files- Delete plan.md (internal POC doc, outdated)
- Delete index.html + style.css at repo root (marketing page from
upstream fork by Toon Beerten — not MethdAI's work)
- Add src/reachy_mini_receptionist/static/logo.png (MethdAI brand)
- Update dashboard.html to load /static/logo.png (was 404'ing on a
filename that never existed in the repo)
- README: fix install URL to spaces/MethdAI/receptionist-app, drop
GEMINI_LIVE_MODEL from optional table (no longer dashboard-exposed),
remove stale 'requires confirmation' note on register_guest
- README.md +4 -8
- index.html +0 -141
- plan.md +0 -89
- src/reachy_mini_receptionist/static/dashboard.html +1 -1
- src/reachy_mini_receptionist/static/logo.png +3 -0
- style.css +0 -437
README.md
CHANGED
|
@@ -40,7 +40,7 @@ A real **[Reachy Mini](https://www.pollen-robotics.com/reachy-mini/)** robot fro
|
|
| 40 |
### Option 1 — Install via the Reachy Mini Control app (recommended)
|
| 41 |
|
| 42 |
1. Open Reachy Mini Control on your computer
|
| 43 |
-
2. **Install from Hugging Face** →
|
| 44 |
3. Toggle the app **On**
|
| 45 |
4. Open the dashboard at `http://<your-robot-hostname>.local:7860/dashboard`
|
| 46 |
5. Follow the welcome banner — it tells you exactly which API keys to add and where to find them
|
|
@@ -49,11 +49,8 @@ A real **[Reachy Mini](https://www.pollen-robotics.com/reachy-mini/)** robot fro
|
|
| 49 |
|
| 50 |
```bash
|
| 51 |
# SSH into the robot
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
# Install the package in editable mode
|
| 56 |
-
/venvs/apps_venv/bin/pip install -e .
|
| 57 |
|
| 58 |
# Make sure the Reachy Mini daemon is running
|
| 59 |
sudo systemctl status reachy-mini-daemon
|
|
@@ -86,7 +83,6 @@ The welcome banner on first launch tells you which keys are missing. Click each
|
|
| 86 |
|---|---|
|
| 87 |
| `VOICE_BACKEND` | Switch between `gemini` (default) and `openai` |
|
| 88 |
| `GEMINI_LIVE_VOICE` | Pick a voice — Puck, Charon, Kore, Aoede, etc. |
|
| 89 |
-
| `GEMINI_LIVE_MODEL` | Override the default Gemini Live model |
|
| 90 |
| `OPENAI_API_KEY` | Required only if `VOICE_BACKEND=openai` |
|
| 91 |
| `RESEND_FROM` | Sender address — defaults to Resend's sandbox sender (delivers only to your Resend account); set to `reception@yourdomain.com` after verifying your domain at Resend |
|
| 92 |
| `FACE_TTL_DAYS` | How long a registered face is remembered (default 90 days) |
|
|
@@ -102,7 +98,7 @@ The welcome banner on first launch tells you which keys are missing. Click each
|
|
| 102 |
|
| 103 |
**Voice + tools** — Either `gemini_live.py` or `openai_realtime.py` (chosen by `VOICE_BACKEND`) handles bi-directional audio with the LLM. Tools available to the LLM:
|
| 104 |
- `get_today_calendar` — fetch today's appointments
|
| 105 |
-
- `register_guest` — save a new visitor's face under a name
|
| 106 |
- `lookup_employee` — find a host in the directory
|
| 107 |
- `send_email` — notify the host
|
| 108 |
|
|
|
|
| 40 |
### Option 1 — Install via the Reachy Mini Control app (recommended)
|
| 41 |
|
| 42 |
1. Open Reachy Mini Control on your computer
|
| 43 |
+
2. **Install from Hugging Face** → paste `MethdAI/receptionist-app` → Install
|
| 44 |
3. Toggle the app **On**
|
| 45 |
4. Open the dashboard at `http://<your-robot-hostname>.local:7860/dashboard`
|
| 46 |
5. Follow the welcome banner — it tells you exactly which API keys to add and where to find them
|
|
|
|
| 49 |
|
| 50 |
```bash
|
| 51 |
# SSH into the robot
|
| 52 |
+
/venvs/apps_venv/bin/pip install --upgrade \
|
| 53 |
+
git+https://huggingface.co/spaces/MethdAI/receptionist-app
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
# Make sure the Reachy Mini daemon is running
|
| 56 |
sudo systemctl status reachy-mini-daemon
|
|
|
|
| 83 |
|---|---|
|
| 84 |
| `VOICE_BACKEND` | Switch between `gemini` (default) and `openai` |
|
| 85 |
| `GEMINI_LIVE_VOICE` | Pick a voice — Puck, Charon, Kore, Aoede, etc. |
|
|
|
|
| 86 |
| `OPENAI_API_KEY` | Required only if `VOICE_BACKEND=openai` |
|
| 87 |
| `RESEND_FROM` | Sender address — defaults to Resend's sandbox sender (delivers only to your Resend account); set to `reception@yourdomain.com` after verifying your domain at Resend |
|
| 88 |
| `FACE_TTL_DAYS` | How long a registered face is remembered (default 90 days) |
|
|
|
|
| 98 |
|
| 99 |
**Voice + tools** — Either `gemini_live.py` or `openai_realtime.py` (chosen by `VOICE_BACKEND`) handles bi-directional audio with the LLM. Tools available to the LLM:
|
| 100 |
- `get_today_calendar` — fetch today's appointments
|
| 101 |
+
- `register_guest` — save a new visitor's face under a name
|
| 102 |
- `lookup_employee` — find a host in the directory
|
| 103 |
- `send_email` — notify the host
|
| 104 |
|
index.html
DELETED
|
@@ -1,141 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
<head>
|
| 5 |
-
<meta charset="utf-8" />
|
| 6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 7 |
-
<title>Reachy Mini AI Receptionist</title>
|
| 8 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
-
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Manrope:wght@400;500;600&display=swap" rel="stylesheet">
|
| 11 |
-
<link rel="stylesheet" href="style.css" />
|
| 12 |
-
</head>
|
| 13 |
-
|
| 14 |
-
<body>
|
| 15 |
-
<header class="hero">
|
| 16 |
-
<div class="topline">
|
| 17 |
-
<div class="brand">
|
| 18 |
-
<span class="logo">🤖</span>
|
| 19 |
-
<span class="brand-name">Reachy Mini</span>
|
| 20 |
-
</div>
|
| 21 |
-
<div class="pill">Realtime voice · Vision aware · Expressive motion</div>
|
| 22 |
-
</div>
|
| 23 |
-
<div class="hero-grid">
|
| 24 |
-
<div class="hero-copy">
|
| 25 |
-
<p class="eyebrow">AI Receptionist</p>
|
| 26 |
-
<h1>
|
| 27 |
-
Face-aware receptionist with tool-calling automation.
|
| 28 |
-
<a class="live-demo-badge" href="#live-demo">Live demo</a>
|
| 29 |
-
</h1>
|
| 30 |
-
<p class="lede">
|
| 31 |
-
A camera-aware front-desk assistant for Reachy Mini. Greet visitors naturally, register guests, check appointments, and log handoff actions from a single dashboard.
|
| 32 |
-
</p>
|
| 33 |
-
<p class="lede">
|
| 34 |
-
Built for reception workflows: realtime face detection to know when someone is present, face recognition to personalize interactions, and structured tool calls so the AI can register guests, query appointments, and trigger handoff actions reliably.
|
| 35 |
-
</p>
|
| 36 |
-
<div class="hero-actions">
|
| 37 |
-
<a class="btn primary" href="#highlights">Explore features</a>
|
| 38 |
-
<a class="btn ghost" href="#story">See how it feels</a>
|
| 39 |
-
</div>
|
| 40 |
-
<div class="hero-badges">
|
| 41 |
-
<span>Realtime face detection</span>
|
| 42 |
-
<span>Visitor face recognition</span>
|
| 43 |
-
<span>Model tool-calling workflows</span>
|
| 44 |
-
<span>Low-latency voice + dashboard ops</span>
|
| 45 |
-
</div>
|
| 46 |
-
</div>
|
| 47 |
-
<div class="hero-visual">
|
| 48 |
-
<div class="glass-card">
|
| 49 |
-
<img src="screenshot.png" alt="Reachy Mini AI Receptionist screenshot" class="hero-gif">
|
| 50 |
-
<p class="caption">Reachy Mini can greet, identify, and assist visitors with receptionist-specific context.</p>
|
| 51 |
-
<div class="video-embed-wrapper" id="live-demo">
|
| 52 |
-
<iframe
|
| 53 |
-
src="https://www.youtube.com/embed/4U9uj5b9p2Y"
|
| 54 |
-
title="Reachy Mini AI Receptionist live demo"
|
| 55 |
-
loading="lazy"
|
| 56 |
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
| 57 |
-
allowfullscreen>
|
| 58 |
-
</iframe>
|
| 59 |
-
</div>
|
| 60 |
-
<p class="video-link"><a href="https://youtu.be/4U9uj5b9p2Y" target="_blank" rel="noopener">Watch on YouTube</a></p>
|
| 61 |
-
</div>
|
| 62 |
-
</div>
|
| 63 |
-
</div>
|
| 64 |
-
</header>
|
| 65 |
-
|
| 66 |
-
<section id="highlights" class="section features">
|
| 67 |
-
<div class="section-header">
|
| 68 |
-
<p class="eyebrow">What’s inside</p>
|
| 69 |
-
<h2>All-in-one receptionist layer for your robot</h2>
|
| 70 |
-
<p class="intro">
|
| 71 |
-
The app blends realtime speech, vision, and workflow tools so Reachy Mini can run a front desk flow.
|
| 72 |
-
</p>
|
| 73 |
-
</div>
|
| 74 |
-
<div class="feature-grid">
|
| 75 |
-
<div class="feature-card">
|
| 76 |
-
<span class="icon">🎤</span>
|
| 77 |
-
<h3>Natural voice chat</h3>
|
| 78 |
-
<p>Talk freely and get fast, high-quality replies powered by realtime models.</p>
|
| 79 |
-
</div>
|
| 80 |
-
<div class="feature-card">
|
| 81 |
-
<span class="icon">🎥</span>
|
| 82 |
-
<h3>Face-aware onboarding</h3>
|
| 83 |
-
<p>Recognize known visitors, register new guests, and keep the latest face context synced with the conversation.</p>
|
| 84 |
-
</div>
|
| 85 |
-
<div class="feature-card">
|
| 86 |
-
<span class="icon">💃</span>
|
| 87 |
-
<h3>Expressive motion</h3>
|
| 88 |
-
<p>Use subtle head and antenna gestures during welcome and registration interactions.</p>
|
| 89 |
-
</div>
|
| 90 |
-
<div class="feature-card">
|
| 91 |
-
<span class="icon">🧠</span>
|
| 92 |
-
<h3>Calendar-aware assistance</h3>
|
| 93 |
-
<p>Use appointment context to welcome guests on time and guide follow-up actions.</p>
|
| 94 |
-
</div>
|
| 95 |
-
<div class="feature-card">
|
| 96 |
-
<span class="icon">🌐</span>
|
| 97 |
-
<h3>Ready for your setup</h3>
|
| 98 |
-
<p>Works in console mode or web mode with dashboard APIs for quick operator visibility.</p>
|
| 99 |
-
</div>
|
| 100 |
-
</div>
|
| 101 |
-
</section>
|
| 102 |
-
|
| 103 |
-
<section id="story" class="section story">
|
| 104 |
-
<div class="story-grid">
|
| 105 |
-
<div class="story-card">
|
| 106 |
-
<p class="eyebrow">How it feels</p>
|
| 107 |
-
<h3>From greeting to handoff in seconds</h3>
|
| 108 |
-
<ul class="story-list">
|
| 109 |
-
<li><span>👋</span> Greet visitors naturally with low-latency voice conversation.</li>
|
| 110 |
-
<li><span>👀</span> Use camera context to identify known guests or register new ones.</li>
|
| 111 |
-
<li><span>📅</span> Check appointment context and respond with relevant timing cues.</li>
|
| 112 |
-
<li><span>📨</span> Log handoff actions in the outbox for host follow-up.</li>
|
| 113 |
-
</ul>
|
| 114 |
-
</div>
|
| 115 |
-
<div class="story-card secondary">
|
| 116 |
-
<p class="eyebrow">Where it shines</p>
|
| 117 |
-
<h3>Great for offices, demos, and guided reception flows</h3>
|
| 118 |
-
<p class="story-text">
|
| 119 |
-
Show how Reachy Mini can handle repeatable visitor workflows while staying expressive and conversational. It is ideal for front-desk demos, events, and product showcases.
|
| 120 |
-
</p>
|
| 121 |
-
<div class="chips">
|
| 122 |
-
<span class="chip">Guest recognition</span>
|
| 123 |
-
<span class="chip">Calendar check</span>
|
| 124 |
-
<span class="chip">Outbox logging</span>
|
| 125 |
-
<span class="chip">Dashboard APIs</span>
|
| 126 |
-
<span class="chip">Realtime conversation</span>
|
| 127 |
-
</div>
|
| 128 |
-
</div>
|
| 129 |
-
</div>
|
| 130 |
-
</section>
|
| 131 |
-
|
| 132 |
-
<footer class="footer">
|
| 133 |
-
<p>
|
| 134 |
-
Reachy Mini AI Receptionist by Toon Beerten (<a href="mailto:toon@neontreebot.be">toon@neontreebot.be</a>), based on the Reachy Mini conversation app by <a href="https://github.com/pollen-robotics" target="_blank" rel="noopener">Pollen Robotics</a>.
|
| 135 |
-
Explore more apps on <a href="https://huggingface.co/spaces/pollen-robotics/Reachy_Mini_Apps" target="_blank" rel="noopener">Hugging Face Spaces</a>.
|
| 136 |
-
</p>
|
| 137 |
-
</footer>
|
| 138 |
-
|
| 139 |
-
</body>
|
| 140 |
-
|
| 141 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plan.md
DELETED
|
@@ -1,89 +0,0 @@
|
|
| 1 |
-
# Reachy Mini AI Receptionist — Plan
|
| 2 |
-
|
| 3 |
-
## What This App Does
|
| 4 |
-
|
| 5 |
-
An interactive AI receptionist that:
|
| 6 |
-
|
| 7 |
-
1. Talks to visitors via OpenAI Realtime API (low-latency speech in/out).
|
| 8 |
-
2. Monitors camera frames for face detection/recognition in a background worker.
|
| 9 |
-
3. Stores guest face crops as PNG files in a persistent `guests/` directory.
|
| 10 |
-
4. Uses a hardcoded POC calendar for appointment context.
|
| 11 |
-
5. Exposes a dashboard with live video, guests, calendar, outbox, and debug logs.
|
| 12 |
-
|
| 13 |
-
Start command:
|
| 14 |
-
|
| 15 |
-
```bash
|
| 16 |
-
python -m reachy_mini_receptionist.main
|
| 17 |
-
```
|
| 18 |
-
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
-
## Architecture
|
| 22 |
-
|
| 23 |
-
The receptionist extends the conversation base stack and adds receptionist-specific modules:
|
| 24 |
-
|
| 25 |
-
```text
|
| 26 |
-
src/reachy_mini_receptionist/
|
| 27 |
-
├── face_db.py # File-based face store (PNG per guest in guests/)
|
| 28 |
-
├── face_recognition_worker.py # Background detection/recognition + event emission
|
| 29 |
-
├── calendar_data.py # Hardcoded appointment data
|
| 30 |
-
├── main.py # App entrypoint + dashboard API mounting
|
| 31 |
-
└── tools/
|
| 32 |
-
├── get_today_calendar.py
|
| 33 |
-
├── register_guest.py
|
| 34 |
-
├── send_email.py
|
| 35 |
-
└── check_current_face.py # Legacy compatibility path
|
| 36 |
-
```
|
| 37 |
-
|
| 38 |
-
---
|
| 39 |
-
|
| 40 |
-
## Key Design Decisions
|
| 41 |
-
|
| 42 |
-
### 1) Face database is file-based
|
| 43 |
-
|
| 44 |
-
- Guests are stored as grayscale PNG crops in `guests/`.
|
| 45 |
-
- `FaceDatabase` enforces capacity with FIFO-style eviction when full.
|
| 46 |
-
- No SQL database is required.
|
| 47 |
-
|
| 48 |
-
### 2) Face recognition runs in a background worker
|
| 49 |
-
|
| 50 |
-
- `FaceRecognitionWorker` runs independently from the realtime audio loop.
|
| 51 |
-
- Worker state is consumed by tools and dashboard endpoints.
|
| 52 |
-
- Stable face transitions emit context events to the model.
|
| 53 |
-
|
| 54 |
-
### 3) Calendar is static for POC
|
| 55 |
-
|
| 56 |
-
- `calendar_data.py` returns hardcoded appointments.
|
| 57 |
-
- Easy to swap later for Google/Microsoft calendar integrations.
|
| 58 |
-
|
| 59 |
-
### 4) Dashboard API is mounted in-app
|
| 60 |
-
|
| 61 |
-
- `GET /dashboard` serves the receptionist dashboard.
|
| 62 |
-
- `GET /video_feed` streams annotated MJPEG.
|
| 63 |
-
- `GET /api/guests`, `/api/calendar`, `/api/outbox`, `/api/face_status`, `/api/logs` expose app state.
|
| 64 |
-
|
| 65 |
-
### 5) Profile is intentionally locked
|
| 66 |
-
|
| 67 |
-
- `LOCKED_PROFILE` is set to `_reachy_mini_receptionist_locked_profile` in `config.py`.
|
| 68 |
-
- Current locked tool allow-list is:
|
| 69 |
-
- `get_today_calendar`
|
| 70 |
-
- `register_guest`
|
| 71 |
-
- `send_email`
|
| 72 |
-
|
| 73 |
-
---
|
| 74 |
-
|
| 75 |
-
## Face Context Event Behavior
|
| 76 |
-
|
| 77 |
-
The receptionist flow is push-based:
|
| 78 |
-
|
| 79 |
-
- Stable face transitions are emitted by `FaceRecognitionWorker`.
|
| 80 |
-
- `OpenaiRealtimeHandler` injects these as context-only user items.
|
| 81 |
-
- No automatic `response.create` is triggered by these face context updates.
|
| 82 |
-
|
| 83 |
-
---
|
| 84 |
-
|
| 85 |
-
## Known Notes
|
| 86 |
-
|
| 87 |
-
- With `--no-camera`, recognition and registration tools cannot operate.
|
| 88 |
-
- Output language behavior is controlled by profile instructions.
|
| 89 |
-
- If profile/tool loading fails, the app can fall back to default model behavior; monitor startup logs.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/reachy_mini_receptionist/static/dashboard.html
CHANGED
|
@@ -943,7 +943,7 @@
|
|
| 943 |
<body>
|
| 944 |
|
| 945 |
<header>
|
| 946 |
-
<img src="/static/
|
| 947 |
class="brand-logo"
|
| 948 |
onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';" />
|
| 949 |
<span class="brand-logo-fallback" style="font-size:1.4rem; display:none;">🤖</span>
|
|
|
|
| 943 |
<body>
|
| 944 |
|
| 945 |
<header>
|
| 946 |
+
<img src="/static/logo.png" alt="MethdAI"
|
| 947 |
class="brand-logo"
|
| 948 |
onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';" />
|
| 949 |
<span class="brand-logo-fallback" style="font-size:1.4rem; display:none;">🤖</span>
|
src/reachy_mini_receptionist/static/logo.png
ADDED
|
Git LFS Details
|
style.css
DELETED
|
@@ -1,437 +0,0 @@
|
|
| 1 |
-
:root {
|
| 2 |
-
--bg: #060c1d;
|
| 3 |
-
--panel: #0c172b;
|
| 4 |
-
--glass: rgba(17, 27, 48, 0.7);
|
| 5 |
-
--card: rgba(255, 255, 255, 0.04);
|
| 6 |
-
--accent: #7af5c4;
|
| 7 |
-
--accent-2: #f6c452;
|
| 8 |
-
--text: #e8edf7;
|
| 9 |
-
--muted: #9fb3ce;
|
| 10 |
-
--border: rgba(255, 255, 255, 0.08);
|
| 11 |
-
--shadow: 0 25px 70px rgba(0, 0, 0, 0.45);
|
| 12 |
-
font-family: "Space Grotesk", "Manrope", system-ui, -apple-system, sans-serif;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
* {
|
| 16 |
-
margin: 0;
|
| 17 |
-
padding: 0;
|
| 18 |
-
box-sizing: border-box;
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
body {
|
| 22 |
-
background: radial-gradient(circle at 20% 20%, rgba(122, 245, 196, 0.12), transparent 30%),
|
| 23 |
-
radial-gradient(circle at 80% 0%, rgba(246, 196, 82, 0.14), transparent 32%),
|
| 24 |
-
radial-gradient(circle at 50% 70%, rgba(124, 142, 255, 0.1), transparent 30%),
|
| 25 |
-
var(--bg);
|
| 26 |
-
color: var(--text);
|
| 27 |
-
min-height: 100vh;
|
| 28 |
-
line-height: 1.6;
|
| 29 |
-
padding-bottom: 3rem;
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
a {
|
| 33 |
-
color: inherit;
|
| 34 |
-
text-decoration: none;
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
.hero {
|
| 38 |
-
padding: 3.5rem clamp(1.5rem, 3vw, 3rem) 2.5rem;
|
| 39 |
-
position: relative;
|
| 40 |
-
overflow: hidden;
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
.hero::after {
|
| 44 |
-
content: "";
|
| 45 |
-
position: absolute;
|
| 46 |
-
inset: 0;
|
| 47 |
-
background: linear-gradient(120deg, rgba(122, 245, 196, 0.12), rgba(246, 196, 82, 0.08), transparent);
|
| 48 |
-
pointer-events: none;
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
.topline {
|
| 52 |
-
display: flex;
|
| 53 |
-
align-items: center;
|
| 54 |
-
justify-content: space-between;
|
| 55 |
-
max-width: 1200px;
|
| 56 |
-
margin: 0 auto 2rem;
|
| 57 |
-
position: relative;
|
| 58 |
-
z-index: 2;
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
.brand {
|
| 62 |
-
display: flex;
|
| 63 |
-
align-items: center;
|
| 64 |
-
gap: 0.5rem;
|
| 65 |
-
font-weight: 700;
|
| 66 |
-
letter-spacing: 0.5px;
|
| 67 |
-
color: var(--text);
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
.logo {
|
| 71 |
-
display: inline-flex;
|
| 72 |
-
align-items: center;
|
| 73 |
-
justify-content: center;
|
| 74 |
-
width: 2.2rem;
|
| 75 |
-
height: 2.2rem;
|
| 76 |
-
border-radius: 10px;
|
| 77 |
-
background: linear-gradient(145deg, rgba(122, 245, 196, 0.15), rgba(124, 142, 255, 0.15));
|
| 78 |
-
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
.brand-name {
|
| 82 |
-
font-size: 1.1rem;
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
.pill {
|
| 86 |
-
background: rgba(255, 255, 255, 0.06);
|
| 87 |
-
border: 1px solid var(--border);
|
| 88 |
-
padding: 0.6rem 1rem;
|
| 89 |
-
border-radius: 999px;
|
| 90 |
-
color: var(--muted);
|
| 91 |
-
font-size: 0.9rem;
|
| 92 |
-
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
.hero-grid {
|
| 96 |
-
display: grid;
|
| 97 |
-
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
| 98 |
-
gap: clamp(1.5rem, 2.5vw, 2.5rem);
|
| 99 |
-
max-width: 1200px;
|
| 100 |
-
margin: 0 auto;
|
| 101 |
-
position: relative;
|
| 102 |
-
z-index: 2;
|
| 103 |
-
align-items: center;
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
.hero-copy h1 {
|
| 107 |
-
font-size: clamp(2.6rem, 4vw, 3.6rem);
|
| 108 |
-
margin-bottom: 1rem;
|
| 109 |
-
line-height: 1.1;
|
| 110 |
-
letter-spacing: -0.5px;
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
.live-demo-badge {
|
| 114 |
-
display: inline-flex;
|
| 115 |
-
align-items: center;
|
| 116 |
-
margin-left: 0.65rem;
|
| 117 |
-
padding: 0.2rem 0.55rem;
|
| 118 |
-
font-size: 0.34em;
|
| 119 |
-
line-height: 1;
|
| 120 |
-
letter-spacing: 0.02em;
|
| 121 |
-
font-weight: 600;
|
| 122 |
-
border-radius: 999px;
|
| 123 |
-
border: 1px solid var(--border);
|
| 124 |
-
color: var(--muted);
|
| 125 |
-
background: rgba(255, 255, 255, 0.04);
|
| 126 |
-
vertical-align: middle;
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
.live-demo-badge:hover {
|
| 130 |
-
color: var(--text);
|
| 131 |
-
border-color: rgba(255, 255, 255, 0.22);
|
| 132 |
-
background: rgba(255, 255, 255, 0.07);
|
| 133 |
-
}
|
| 134 |
-
|
| 135 |
-
.eyebrow {
|
| 136 |
-
display: inline-flex;
|
| 137 |
-
align-items: center;
|
| 138 |
-
gap: 0.5rem;
|
| 139 |
-
text-transform: uppercase;
|
| 140 |
-
letter-spacing: 1px;
|
| 141 |
-
font-size: 0.8rem;
|
| 142 |
-
color: var(--muted);
|
| 143 |
-
margin-bottom: 0.75rem;
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
.eyebrow::before {
|
| 147 |
-
content: "";
|
| 148 |
-
display: inline-block;
|
| 149 |
-
width: 24px;
|
| 150 |
-
height: 2px;
|
| 151 |
-
background: linear-gradient(90deg, var(--accent), var(--accent-2));
|
| 152 |
-
border-radius: 999px;
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
.lede {
|
| 156 |
-
font-size: 1.1rem;
|
| 157 |
-
color: var(--muted);
|
| 158 |
-
max-width: 620px;
|
| 159 |
-
}
|
| 160 |
-
|
| 161 |
-
.hero-actions {
|
| 162 |
-
display: flex;
|
| 163 |
-
gap: 1rem;
|
| 164 |
-
align-items: center;
|
| 165 |
-
margin: 1.6rem 0 1.2rem;
|
| 166 |
-
flex-wrap: wrap;
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
.btn {
|
| 170 |
-
display: inline-flex;
|
| 171 |
-
align-items: center;
|
| 172 |
-
justify-content: center;
|
| 173 |
-
gap: 0.6rem;
|
| 174 |
-
padding: 0.85rem 1.4rem;
|
| 175 |
-
border-radius: 12px;
|
| 176 |
-
font-weight: 700;
|
| 177 |
-
border: 1px solid transparent;
|
| 178 |
-
cursor: pointer;
|
| 179 |
-
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease;
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
.btn.primary {
|
| 183 |
-
background: linear-gradient(135deg, #7af5c4, #7c8eff);
|
| 184 |
-
color: #0a0f1f;
|
| 185 |
-
box-shadow: 0 15px 30px rgba(122, 245, 196, 0.25);
|
| 186 |
-
}
|
| 187 |
-
|
| 188 |
-
.btn.primary:hover {
|
| 189 |
-
transform: translateY(-2px);
|
| 190 |
-
box-shadow: 0 25px 45px rgba(122, 245, 196, 0.35);
|
| 191 |
-
}
|
| 192 |
-
|
| 193 |
-
.btn.ghost {
|
| 194 |
-
background: rgba(255, 255, 255, 0.05);
|
| 195 |
-
border-color: var(--border);
|
| 196 |
-
color: var(--text);
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
.btn.ghost:hover {
|
| 200 |
-
border-color: rgba(255, 255, 255, 0.3);
|
| 201 |
-
transform: translateY(-2px);
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
.btn.wide {
|
| 205 |
-
width: 100%;
|
| 206 |
-
justify-content: center;
|
| 207 |
-
}
|
| 208 |
-
|
| 209 |
-
.hero-badges {
|
| 210 |
-
display: flex;
|
| 211 |
-
flex-wrap: wrap;
|
| 212 |
-
gap: 0.6rem;
|
| 213 |
-
color: var(--muted);
|
| 214 |
-
font-size: 0.9rem;
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
.hero-badges span {
|
| 218 |
-
padding: 0.5rem 0.8rem;
|
| 219 |
-
border-radius: 10px;
|
| 220 |
-
border: 1px solid var(--border);
|
| 221 |
-
background: rgba(255, 255, 255, 0.04);
|
| 222 |
-
}
|
| 223 |
-
|
| 224 |
-
.hero-visual .glass-card {
|
| 225 |
-
background: rgba(255, 255, 255, 0.03);
|
| 226 |
-
border: 1px solid var(--border);
|
| 227 |
-
border-radius: 18px;
|
| 228 |
-
padding: 1.2rem;
|
| 229 |
-
box-shadow: var(--shadow);
|
| 230 |
-
backdrop-filter: blur(10px);
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
.hero-gif {
|
| 234 |
-
width: 100%;
|
| 235 |
-
display: block;
|
| 236 |
-
border-radius: 14px;
|
| 237 |
-
border: 1px solid var(--border);
|
| 238 |
-
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.35);
|
| 239 |
-
}
|
| 240 |
-
|
| 241 |
-
.caption {
|
| 242 |
-
margin-top: 0.75rem;
|
| 243 |
-
color: var(--muted);
|
| 244 |
-
font-size: 0.95rem;
|
| 245 |
-
}
|
| 246 |
-
|
| 247 |
-
.video-embed-wrapper {
|
| 248 |
-
margin-top: 0.9rem;
|
| 249 |
-
position: relative;
|
| 250 |
-
width: 100%;
|
| 251 |
-
aspect-ratio: 16 / 9;
|
| 252 |
-
border-radius: 12px;
|
| 253 |
-
overflow: hidden;
|
| 254 |
-
border: 1px solid var(--border);
|
| 255 |
-
background: rgba(0, 0, 0, 0.35);
|
| 256 |
-
}
|
| 257 |
-
|
| 258 |
-
.video-embed-wrapper iframe {
|
| 259 |
-
width: 100%;
|
| 260 |
-
height: 100%;
|
| 261 |
-
display: block;
|
| 262 |
-
border: 0;
|
| 263 |
-
}
|
| 264 |
-
|
| 265 |
-
.video-link {
|
| 266 |
-
margin-top: 0.55rem;
|
| 267 |
-
font-size: 0.85rem;
|
| 268 |
-
color: var(--muted);
|
| 269 |
-
}
|
| 270 |
-
|
| 271 |
-
.video-link a {
|
| 272 |
-
text-decoration: underline;
|
| 273 |
-
text-underline-offset: 2px;
|
| 274 |
-
}
|
| 275 |
-
|
| 276 |
-
.section {
|
| 277 |
-
max-width: 1200px;
|
| 278 |
-
margin: 0 auto;
|
| 279 |
-
padding: clamp(2rem, 4vw, 3.5rem) clamp(1.5rem, 3vw, 3rem);
|
| 280 |
-
}
|
| 281 |
-
|
| 282 |
-
.section-header {
|
| 283 |
-
text-align: center;
|
| 284 |
-
max-width: 780px;
|
| 285 |
-
margin: 0 auto 2rem;
|
| 286 |
-
}
|
| 287 |
-
|
| 288 |
-
.section-header h2 {
|
| 289 |
-
font-size: clamp(2rem, 3vw, 2.6rem);
|
| 290 |
-
margin-bottom: 0.5rem;
|
| 291 |
-
}
|
| 292 |
-
|
| 293 |
-
.intro {
|
| 294 |
-
color: var(--muted);
|
| 295 |
-
font-size: 1.05rem;
|
| 296 |
-
}
|
| 297 |
-
|
| 298 |
-
.feature-grid {
|
| 299 |
-
display: grid;
|
| 300 |
-
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
| 301 |
-
gap: 1rem;
|
| 302 |
-
}
|
| 303 |
-
|
| 304 |
-
.feature-card {
|
| 305 |
-
background: rgba(255, 255, 255, 0.03);
|
| 306 |
-
border: 1px solid var(--border);
|
| 307 |
-
border-radius: 16px;
|
| 308 |
-
padding: 1.25rem;
|
| 309 |
-
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
| 310 |
-
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
| 311 |
-
}
|
| 312 |
-
|
| 313 |
-
.feature-card:hover {
|
| 314 |
-
transform: translateY(-4px);
|
| 315 |
-
border-color: rgba(122, 245, 196, 0.3);
|
| 316 |
-
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.3);
|
| 317 |
-
}
|
| 318 |
-
|
| 319 |
-
.feature-card .icon {
|
| 320 |
-
width: 48px;
|
| 321 |
-
height: 48px;
|
| 322 |
-
border-radius: 12px;
|
| 323 |
-
display: grid;
|
| 324 |
-
place-items: center;
|
| 325 |
-
background: rgba(122, 245, 196, 0.14);
|
| 326 |
-
margin-bottom: 0.8rem;
|
| 327 |
-
font-size: 1.4rem;
|
| 328 |
-
}
|
| 329 |
-
|
| 330 |
-
.feature-card h3 {
|
| 331 |
-
margin-bottom: 0.35rem;
|
| 332 |
-
}
|
| 333 |
-
|
| 334 |
-
.feature-card p {
|
| 335 |
-
color: var(--muted);
|
| 336 |
-
}
|
| 337 |
-
|
| 338 |
-
.story {
|
| 339 |
-
padding-top: 1rem;
|
| 340 |
-
}
|
| 341 |
-
|
| 342 |
-
.story-grid {
|
| 343 |
-
display: grid;
|
| 344 |
-
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
| 345 |
-
gap: 1rem;
|
| 346 |
-
}
|
| 347 |
-
|
| 348 |
-
.story-card {
|
| 349 |
-
background: rgba(255, 255, 255, 0.03);
|
| 350 |
-
border: 1px solid var(--border);
|
| 351 |
-
border-radius: 18px;
|
| 352 |
-
padding: 1.5rem;
|
| 353 |
-
box-shadow: var(--shadow);
|
| 354 |
-
}
|
| 355 |
-
|
| 356 |
-
.story-card.secondary {
|
| 357 |
-
background: linear-gradient(145deg, rgba(124, 142, 255, 0.08), rgba(122, 245, 196, 0.06));
|
| 358 |
-
}
|
| 359 |
-
|
| 360 |
-
.story-card h3 {
|
| 361 |
-
margin-bottom: 0.8rem;
|
| 362 |
-
}
|
| 363 |
-
|
| 364 |
-
.story-list {
|
| 365 |
-
list-style: none;
|
| 366 |
-
display: grid;
|
| 367 |
-
gap: 0.7rem;
|
| 368 |
-
color: var(--muted);
|
| 369 |
-
font-size: 0.98rem;
|
| 370 |
-
}
|
| 371 |
-
|
| 372 |
-
.story-list li {
|
| 373 |
-
display: flex;
|
| 374 |
-
gap: 0.7rem;
|
| 375 |
-
align-items: flex-start;
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
.story-text {
|
| 379 |
-
color: var(--muted);
|
| 380 |
-
line-height: 1.7;
|
| 381 |
-
margin-bottom: 1rem;
|
| 382 |
-
}
|
| 383 |
-
|
| 384 |
-
.chips {
|
| 385 |
-
display: flex;
|
| 386 |
-
flex-wrap: wrap;
|
| 387 |
-
gap: 0.5rem;
|
| 388 |
-
}
|
| 389 |
-
|
| 390 |
-
.chip {
|
| 391 |
-
padding: 0.45rem 0.8rem;
|
| 392 |
-
border-radius: 12px;
|
| 393 |
-
background: rgba(0, 0, 0, 0.2);
|
| 394 |
-
border: 1px solid var(--border);
|
| 395 |
-
color: var(--text);
|
| 396 |
-
font-size: 0.9rem;
|
| 397 |
-
}
|
| 398 |
-
|
| 399 |
-
.footer {
|
| 400 |
-
text-align: center;
|
| 401 |
-
color: var(--muted);
|
| 402 |
-
padding: 2rem 1.5rem 0;
|
| 403 |
-
}
|
| 404 |
-
|
| 405 |
-
.footer a {
|
| 406 |
-
color: var(--text);
|
| 407 |
-
border-bottom: 1px solid transparent;
|
| 408 |
-
}
|
| 409 |
-
|
| 410 |
-
.footer a:hover {
|
| 411 |
-
border-color: rgba(255, 255, 255, 0.5);
|
| 412 |
-
}
|
| 413 |
-
|
| 414 |
-
@media (max-width: 768px) {
|
| 415 |
-
.hero {
|
| 416 |
-
padding-top: 2.5rem;
|
| 417 |
-
}
|
| 418 |
-
|
| 419 |
-
.topline {
|
| 420 |
-
flex-direction: column;
|
| 421 |
-
gap: 0.8rem;
|
| 422 |
-
align-items: flex-start;
|
| 423 |
-
}
|
| 424 |
-
|
| 425 |
-
.hero-actions {
|
| 426 |
-
width: 100%;
|
| 427 |
-
}
|
| 428 |
-
|
| 429 |
-
.btn {
|
| 430 |
-
width: 100%;
|
| 431 |
-
justify-content: center;
|
| 432 |
-
}
|
| 433 |
-
|
| 434 |
-
.hero-badges {
|
| 435 |
-
gap: 0.4rem;
|
| 436 |
-
}
|
| 437 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|