feat: add design system handoff to feat/design-system branch
Adds colors_and_type.css token system, fonts (Lexend, Instrument Serif Italic, JetBrains Mono, Atkinson Hyperlegible Next, OpenDyslexic), SVG assets (wordmark, waveform mark, grain), HTML preview spec cards, UI kit, and SKILL.md reference under src/design-system/. Foundation for applying the new Kon visual identity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
4
src/assets/grain.svg
Normal file
4
src/assets/grain.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200" preserveAspectRatio="none">
|
||||||
|
<filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" stitchTiles="stitch"></feTurbulence><feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"></feColorMatrix></filter>
|
||||||
|
<rect width="100%" height="100%" filter="url(#n)"></rect>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 398 B |
8
src/assets/waveform-mark.svg
Normal file
8
src/assets/waveform-mark.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||||
|
<rect width="64" height="64" rx="14" fill="#1b1a17"></rect>
|
||||||
|
<g transform="translate(32 32)">
|
||||||
|
<rect x="-14" y="-10" width="5" height="20" rx="2.5" fill="#e8a87c" opacity="0.75"></rect>
|
||||||
|
<rect x="-4" y="-17" width="5" height="34" rx="2.5" fill="#e8a87c"></rect>
|
||||||
|
<rect x="6" y="-6" width="5" height="12" rx="2.5" fill="#e8a87c" opacity="0.5"></rect>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 459 B |
5
src/assets/wordmark.svg
Normal file
5
src/assets/wordmark.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 80" width="240" height="80">
|
||||||
|
<text x="12" y="54" font-family="'Instrument Serif', Georgia, serif" font-style="italic" font-weight="400" font-size="48" fill="#e8a87c" letter-spacing="-0.01em">Kon</text>
|
||||||
|
<text x="92" y="46" font-family="'Instrument Serif', Georgia, serif" font-size="28" fill="#e8a87c">ක</text>
|
||||||
|
<text x="12" y="70" font-family="'Lexend', sans-serif" font-size="9" fill="#716b60" letter-spacing="1.4" text-transform="uppercase" style="text-transform:uppercase">THINK OUT LOUD</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 592 B |
188
src/design-system/README.md
Normal file
188
src/design-system/README.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# Kon — Design System
|
||||||
|
|
||||||
|
> **Think out loud.** A voice-first dictation app built for neurodivergent minds, by CORBEL.
|
||||||
|
|
||||||
|
## What is Kon?
|
||||||
|
|
||||||
|
Kon is a local-first dictation + thought-capture app. You press a hotkey, speak, and Kon transcribes, cleans, and — optionally — extracts tasks from what you said. Everything runs on your machine (Whisper / Parakeet models); nothing is sent anywhere.
|
||||||
|
|
||||||
|
**Company:** CORBEL — an AI consultancy and technology integrator that removes friction from day-to-day life and work.
|
||||||
|
|
||||||
|
**Product essence:** *Clarity without friction.* Every design decision is measured against it. If something adds cognitive load, it doesn't ship.
|
||||||
|
|
||||||
|
**Archetypes:** Sage (primary) + Magician (secondary). Kon listens more than it speaks.
|
||||||
|
|
||||||
|
**Visual reference points:** The Barbican, Muji, Amsterdam urban design, Nujabes album art — warm brutalism, honest materials, structural clarity. **Anti-references:** Notion-style feature density, generic blue/white SaaS, FAANG aesthetics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- **Repo:** `github.com/jakejars/kon` — Tauri + Svelte 5 + Tailwind v4 desktop app. Imported into `kon-source/` (lib components + routes).
|
||||||
|
- **Design brief:** Pasted in the initial message — complete token system, type scale, motion guidelines, ideology.
|
||||||
|
- **Key reads in `kon-source/`:**
|
||||||
|
- `app.css` — @theme tokens, global base, zone + motion system
|
||||||
|
- `lib/Sidebar.svelte` — navigation shell
|
||||||
|
- `lib/pages/DictationPage.svelte` — record / transcribe (the hero screen)
|
||||||
|
- `lib/pages/HistoryPage.svelte` — saved transcripts
|
||||||
|
- `lib/pages/TasksPage.svelte` — inbox / today / soon / later buckets
|
||||||
|
- `lib/pages/FilesPage.svelte` — audio file import
|
||||||
|
- `lib/components/Card.svelte`, `Titlebar.svelte`, `EmptyState.svelte`, `ZonePicker.svelte`, `Toggle.svelte`, `ToastViewport.svelte`
|
||||||
|
- `docs/icon-mapping.md` — Lucide migration audit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Index — what's in this folder
|
||||||
|
|
||||||
|
| Path | What it is |
|
||||||
|
|---|---|
|
||||||
|
| `README.md` | This file. Start here. |
|
||||||
|
| `colors_and_type.css` | **The** stylesheet. CSS variables, semantic type classes, grain, zones. `@import` it. |
|
||||||
|
| `SKILL.md` | Agent-skill manifest (cross-compatible with Claude Code Agent Skills). |
|
||||||
|
| `assets/` | Logos, the sinhala wordmark glyph, grain texture, icon references |
|
||||||
|
| `preview/` | HTML spec cards shown in the Design System tab (swatches, specimens, components) |
|
||||||
|
| `ui_kits/kon-app/` | UI kit — pixel-level recreation of the Kon desktop app with interactive click-thru |
|
||||||
|
| `kon-source/` | Imported Svelte source — the ground truth for component structure |
|
||||||
|
|
||||||
|
No slide template was attached, so `slides/` is not created.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content Fundamentals
|
||||||
|
|
||||||
|
> **Voice:** A calm friend who has already done the anxious part for you.
|
||||||
|
|
||||||
|
**Person.** Addresses the user as *you*; never refers to itself as *I*. Brand name "Kon" appears sparingly — the product speaks, it doesn't narrate itself. *(Settings labels: "Save recordings", "British English" — never "I'll save your recordings".)*
|
||||||
|
|
||||||
|
**Sentence shape.** Short. Direct. Rarely more than ~12 words per line of UI copy. Never exclaims. Never hypes. Full stops inside UI labels are dropped; in prose (empty states, toasts) they're used naturally.
|
||||||
|
|
||||||
|
**Tone.**
|
||||||
|
- **Ambient, not demanding.** Empty states are *"Your words will appear here…"* — not *"Record your first transcript!"* Blank screens trigger freeze; Kon offers presence instead of prompting.
|
||||||
|
- **Calm on failure.** Failed transcription copy leads with *"Your audio is saved"* — the anxiety reducer, not the apology. Never red all-caps.
|
||||||
|
- **No guilt, no streaks.** No streak-shaming, no *"keep it up!"*, no urgency.
|
||||||
|
- **British English.** "Minimise", "finalising", "customise". `britishEnglish` is a first-class setting.
|
||||||
|
|
||||||
|
**Casing.**
|
||||||
|
- Sentence case for all headings and buttons. *"Start recording"*, *"Clear all"*, *"Sensory zone"* — never *Start Recording* or *START RECORDING*.
|
||||||
|
- Eyebrow labels (10px, uppercase, 0.12em tracking) are the **only** place all-caps appears — e.g. *THINK OUT LOUD* under the sidebar wordmark. Never apply uppercase to sentences.
|
||||||
|
- Brand name is always `Kon` (capital K, one lowercase letter).
|
||||||
|
- Product tagline — *"Think out loud"* — always sentence case.
|
||||||
|
|
||||||
|
**Buttons.** Verbs. *Record, Stop, Save, Copy, Clear, Export, Extract Tasks, Open viewer.* The one exception is *Tasks* (noun) as a sidebar toggle — consistent with its icon label.
|
||||||
|
|
||||||
|
**Status language.** State-driven, non-anthropomorphic. `Ready`, `Recording…`, `Loading model…`, `Finalising…`, `Error`. Not *"I'm listening"*.
|
||||||
|
|
||||||
|
**Microcopy examples (lifted from the codebase):**
|
||||||
|
- Empty: *"Press record or Ctrl+Shift+R"* · *"Your transcriptions will be saved here"* · *"No matching transcripts"*
|
||||||
|
- Saved feedback: *"Saved · 3 tasks extracted"*
|
||||||
|
- Error toast: *"Could not start recording"* + *"device disconnected"* body
|
||||||
|
- Settings helper: *"Auto mode skips monitor sources and validates by RMS energy."*
|
||||||
|
|
||||||
|
**Emoji.** **Never.** Nothing in the codebase uses them. The one decorative character is the Sinhala letter ක (U+0D9A) next to the wordmark — a brand mark, not emoji.
|
||||||
|
|
||||||
|
**Numbers + units.** Tabular figures for timers, durations, word counts. Units always lowercase, no abbreviations in prose: *"3 tasks extracted"*, *"12 words"*, *"Dropped 2s of older audio"*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Visual Foundations
|
||||||
|
|
||||||
|
### Palette — warm, not beige
|
||||||
|
Three-layer surface system: `--bg` (60%) → `--bg-elevated` (30%) → `--bg-card` — all neutrals carry a warm amber undertone for cohesion. Never `#000` on `#fff` (halation). Accent `#e8a87c` (amber/copper) is **always meaningful** — interactivity, recording state, or brand identity. Never decorative. Three semantic colors only: `--success` moss green, `--danger` muted red, `--warning` honey amber. 60-30-10 split: 60% surface, 30% elevated, 10% amber.
|
||||||
|
|
||||||
|
### Sensory zones
|
||||||
|
Three environment modes — **Cave** (cool teal tint, focus), **Energy** (warm neutral, collaboration), **Reset** (muted sage, relaxation). Zones tint surfaces only; the amber accent never changes. Environment is the variable, brand is the constant. Zone transitions cross-fade 300–500ms (instant when reduce-motion is on).
|
||||||
|
|
||||||
|
### Type — Lexend everywhere, italic serif only for identity
|
||||||
|
- **UI + body:** Lexend (variable 300–700). Designed for reading difficulties; high x-height, generous spacing.
|
||||||
|
- **Display / wordmark:** Instrument Serif Italic 400. **Only** the wordmark and page titles. Never body, never UI.
|
||||||
|
- **Mono:** JetBrains Mono — file paths, durations, kbd.
|
||||||
|
- **User-selectable alternatives:** Atkinson Hyperlegible, OpenDyslexic.
|
||||||
|
- 16px floor for all body. 1.5× line-height minimum. Left-aligned only. Max 75ch. Sentence case. Never italic for extended reading. Never more than 3 weights per screen.
|
||||||
|
|
||||||
|
### Backgrounds
|
||||||
|
Flat warm surfaces, layered. **Grain texture** at 2.5% opacity dark / 1.5% light — a 200×200 noise tile, `pointer-events:none`, z-index 50, overlaid on the whole viewport. No gradients. No full-bleed photography. No hand-drawn illustrations. No repeating geometric patterns. The grain is the one and only texture.
|
||||||
|
|
||||||
|
### Imagery — when present
|
||||||
|
Warm grain wash: shift toward amber, noise overlay 2–3%, slight vignette (10–15%). Subjects are textured surfaces (wood grain, concrete) and **hands only** (writing, holding things — never faces or screens). Late-afternoon light, low-to-medium contrast. No stock photography of smiling people.
|
||||||
|
|
||||||
|
### Illustration
|
||||||
|
Abstract waveform / sound-ripple motifs in amber. 2px stroke. Flat colour with opacity variations — no gradients, no characters, no mascots.
|
||||||
|
|
||||||
|
### Motion — elderflower, not espresso
|
||||||
|
- **Easing:** `cubic-bezier(0.2, 0.8, 0.2, 1)` (ease-out).
|
||||||
|
- **UI:** 150–200ms. **Decorative:** 300–500ms. **Zone transitions:** 300–500ms cross-fade. **Waveform:** 2s ease-in-out amplitude pulse, clamped.
|
||||||
|
- **Never:** bounce, screen shake, slide-from-offscreen, auto-play, attention-grabbing.
|
||||||
|
- **Reduce motion is the default** — opt IN to animation.
|
||||||
|
|
||||||
|
### Hover + press states
|
||||||
|
- **Hover:** background shifts to `--hover` (a single step warmer than the underlying surface). Buttons: text colour step up (tertiary→secondary, secondary→text). Accent buttons: `--accent` → `--accent-hover` (slightly darker copper).
|
||||||
|
- **Press:** `active:scale-[0.93]` on circular actions (record button), `active:opacity-80` on standard buttons. No ripple. No pulse.
|
||||||
|
- **Active nav:** `--nav-active` background, text upgraded to primary, icon stroked 2.0 instead of 1.5, icon colour → amber.
|
||||||
|
|
||||||
|
### Borders
|
||||||
|
Two weights only: `--border` (visible) and `--border-subtle` (barely-there divider). Always 1px. Accent border appears only for focus rings, active selection, or recording indicators.
|
||||||
|
|
||||||
|
### Shadows
|
||||||
|
Low and warm. `shadow-sm` `0 1px 3px rgba(0,0,0,0.2)` for cards. `shadow-md` `0 4px 12px rgba(0,0,0,0.25)` for popovers. The **one signature shadow** is the accent glow on the record button at rest: `0 4px 20px rgba(232,168,124,0.3)`, plus the `pulse-warm` animation while recording.
|
||||||
|
|
||||||
|
### Corners
|
||||||
|
`--radius-sm` 6px (kbd, pills) · `--radius-md` 8px (buttons, inputs) · `--radius-lg` 12px (cards internal) · `--radius-xl` 16px (primary containers) · `--radius-2xl` 20px (the big transcript card). Record button is fully circular. Toasts, chips, badges are fully rounded (`rounded-full`).
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
Card = `--bg-card` background + `--border-subtle` 1px + `rounded-2xl` + `shadow-sm`. No heavy drop shadows. No coloured left-border accents (the overused SaaS trope is explicitly avoided). Content sits directly on the card — no inner boxes stacked on boxes.
|
||||||
|
|
||||||
|
### Layout — fixed spine
|
||||||
|
Titlebar 32px · sidebar 48px collapsed / 210px expanded · optional right task rail 280px. Pages handle their own scroll; the shell does not. Left-aligned text everywhere. No centred hero sections.
|
||||||
|
|
||||||
|
### Transparency + blur
|
||||||
|
Used sparingly, only for: tooltips on collapsed sidebar items, the backdrop of the float window, the accent-subtle tint (`--accent 6%`) on selected/pressed accent buttons. No glassmorphism. No frosted nav bars.
|
||||||
|
|
||||||
|
### Iconography
|
||||||
|
Lucide icons, 2px stroke, rounded terminals. 16px in nav, 20px in features, 24px primary actions. Every icon paired with a visible text label — no standalone icons (exception: OS titlebar controls). Collapsed sidebar uses icon + aria-label + tooltip. Lucide is loaded from CDN.
|
||||||
|
|
||||||
|
### Focus rings
|
||||||
|
2px solid `--accent`, 3px offset, rendered **above** grain (z-index > 50). Tested on every theme × zone combination.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Iconography
|
||||||
|
|
||||||
|
**Library:** [Lucide Icons](https://lucide.dev) (MIT). Every Lucide glyph Kon uses is listed in `kon-source/docs/icon-mapping.md`.
|
||||||
|
|
||||||
|
**Stroke weight:** 2px default · 2.5 on active sidebar items · 1 for empty-state illustrations (48px, opacity 40%).
|
||||||
|
|
||||||
|
**Sizes:**
|
||||||
|
- 14px — chevrons, inline status dots
|
||||||
|
- 16px — sidebar nav, toolbar icons, source tags
|
||||||
|
- 20px — feature-area icons (Dictation toolbar, Settings panels)
|
||||||
|
- 24px — primary actions
|
||||||
|
- 48px — empty-state illustrations
|
||||||
|
|
||||||
|
**Colour:**
|
||||||
|
- `--text-tertiary` at rest
|
||||||
|
- `--text-secondary` on hover (group hover)
|
||||||
|
- `--accent` when active, selected, or brand-significant (recording dot, active nav, play button)
|
||||||
|
- `--danger` for destructive and the recording state indicator
|
||||||
|
- `--success` for completion checks
|
||||||
|
|
||||||
|
**Core Kon set (from the audit):** `Mic` (Dictation), `FileText` (Files), `SquareCheck` (Tasks), `Clock` (History), `Settings`, `Circle`/`Square` (record/stop), `Play`/`Pause`, `Search`, `Upload`, `Download`, `Copy`, `ChevronDown`, `ChevronLeft`, `ChevronRight`, `X`, `Check`, `Plus`, `Pin`, `User`, `AlignLeft`, `AlertTriangle`, `Loader2`.
|
||||||
|
|
||||||
|
**Brand glyph:** The Sinhala letter **ක** (U+0D9A) sits next to the wordmark in `--accent`. Decorative only. During recording it spins slowly (2s linear infinite, `sinhala-spin`). A Unicode codepoint, not an SVG — scales perfectly, no asset needed.
|
||||||
|
|
||||||
|
**Emoji:** **Never used.**
|
||||||
|
|
||||||
|
**Bitmap icons, icon fonts, sprites:** **None.** Lucide covers everything.
|
||||||
|
|
||||||
|
**Loading indicator:** `Loader2` with `animate-spin` is the standard. The codebase also has `UnicodeSpinner.svelte` (◉ ◎ ○) as an ambient/decorative alternative when Lucide feels too mechanical.
|
||||||
|
|
||||||
|
**Delivery:** In HTML artefacts, load Lucide from CDN: `<script src="https://unpkg.com/lucide@latest"></script>` then `lucide.createIcons()`. Or use inline SVGs copied from [lucide.dev](https://lucide.dev). See `assets/icons/` for the handful pre-copied for design previews.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Font substitution note
|
||||||
|
|
||||||
|
The repo ships bundled woff2 files (`/fonts/lexend-variable.woff2`, `/fonts/instrument-serif-italic.woff2`, `/fonts/jetbrains-mono.woff2`, `/fonts/atkinson-hyperlegible-next.woff2`, `/fonts/opendyslexic.woff2`) which were **not included** in the imported subset. `colors_and_type.css` loads the Google Fonts CDN equivalents of Lexend, Instrument Serif Italic, JetBrains Mono, and Atkinson Hyperlegible — visually identical.
|
||||||
|
|
||||||
|
**Missing:** OpenDyslexic (not on Google Fonts; needs to be self-hosted). Atkinson Hyperlegible **Next** is also not yet on Google Fonts — we substituted the original **Atkinson Hyperlegible** (very close; same designer, same metrics).
|
||||||
|
|
||||||
|
**Please drop the original woff2 files into `fonts/` when you have a chance.** The CSS paths under `@font-face` would then be `fonts/lexend-variable.woff2` etc., matching the app bundle exactly.
|
||||||
26
src/design-system/SKILL.md
Normal file
26
src/design-system/SKILL.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: kon-design
|
||||||
|
description: Use this skill to generate well-branded interfaces and assets for Kon (by CORBEL), either for production or throwaway prototypes/mocks/etc. Contains essential design guidelines, colors, type, fonts, assets, and UI kit components for prototyping.
|
||||||
|
user-invocable: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Read the README.md file within this skill, and explore the other available files.
|
||||||
|
|
||||||
|
If creating visual artifacts (slides, mocks, throwaway prototypes, etc), copy assets out and create static HTML files for the user to view. If working on production code, you can copy assets and read the rules here to become an expert in designing with this brand.
|
||||||
|
|
||||||
|
If the user invokes this skill without any other guidance, ask them what they want to build or design, ask some questions, and act as an expert designer who outputs HTML artifacts _or_ production code, depending on the need.
|
||||||
|
|
||||||
|
## Essence — non-negotiable
|
||||||
|
- **Clarity without friction.** Every decision measured against it.
|
||||||
|
- **Neurodivergent-first.** No streak-shaming, no guilt, no red urgency, no auto-play.
|
||||||
|
- Warm brutalism — Barbican / Muji / Nujabes. Not San Francisco SaaS.
|
||||||
|
- Amber is always meaningful. Never decorative.
|
||||||
|
- Sentence case. No emoji. Never italic for body.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `colors_and_type.css` — import this for all tokens + semantic type.
|
||||||
|
- `README.md` — full brand + content + visual foundations.
|
||||||
|
- `assets/` — logos, glyph, grain, icon samples.
|
||||||
|
- `ui_kits/kon-app/` — component recreations of the desktop app.
|
||||||
|
- `preview/` — spec cards (swatches, specimens, components).
|
||||||
|
- `kon-source/` — ground-truth Svelte source from the Kon repo.
|
||||||
248
src/design-system/colors_and_type.css
Normal file
248
src/design-system/colors_and_type.css
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
/* ============================================================
|
||||||
|
Kon — Colors & Type
|
||||||
|
Single source of truth for CSS variables + semantic type.
|
||||||
|
Import this into any Kon design artefact.
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* --- Brand Webfonts (local woff2, shipped with Kon) --- */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lexend';
|
||||||
|
src: url('fonts/lexend-variable.woff2') format('woff2-variations'),
|
||||||
|
url('fonts/lexend-variable.woff2') format('woff2');
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Instrument Serif';
|
||||||
|
src: url('fonts/instrument-serif-italic.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
src: url('fonts/jetbrains-mono.woff2') format('woff2-variations'),
|
||||||
|
url('fonts/jetbrains-mono.woff2') format('woff2');
|
||||||
|
font-weight: 100 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Atkinson Hyperlegible Next';
|
||||||
|
src: url('fonts/atkinson-hyperlegible-next.woff2') format('woff2-variations'),
|
||||||
|
url('fonts/atkinson-hyperlegible-next.woff2') format('woff2');
|
||||||
|
font-weight: 200 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'OpenDyslexic';
|
||||||
|
src: url('fonts/opendyslexic.woff2') format('woff2');
|
||||||
|
font-weight: 400 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Design Tokens — Dark Theme (default)
|
||||||
|
============================================================ */
|
||||||
|
:root,
|
||||||
|
:root[data-theme="dark"] {
|
||||||
|
/* — Surfaces (warm layered depth) — */
|
||||||
|
--bg: #0f0e0c;
|
||||||
|
--bg-elevated: #171614;
|
||||||
|
--bg-card: #1b1a17;
|
||||||
|
--bg-input: #151412;
|
||||||
|
--sidebar: #13120f;
|
||||||
|
--nav-active: #201e1a;
|
||||||
|
--hover: #1e1c18;
|
||||||
|
|
||||||
|
/* — Borders — */
|
||||||
|
--border: #2c2923;
|
||||||
|
--border-subtle: #221f1b;
|
||||||
|
--border-focus: #e8a87c;
|
||||||
|
|
||||||
|
/* — Text (warm hierarchy) — */
|
||||||
|
--text: #f0ece4;
|
||||||
|
--text-secondary: #9a9486;
|
||||||
|
--text-tertiary: #716b60;
|
||||||
|
|
||||||
|
/* — Accent (amber/copper — always meaningful) — */
|
||||||
|
--accent: #e8a87c;
|
||||||
|
--accent-hover: #d4976a;
|
||||||
|
--accent-subtle: #e8a87c10;
|
||||||
|
--accent-glow: #e8a87c25;
|
||||||
|
|
||||||
|
/* — Semantic — */
|
||||||
|
--success: #7ec89a;
|
||||||
|
--danger: #e87171;
|
||||||
|
--warning: #e8c86e;
|
||||||
|
|
||||||
|
/* — Radii — */
|
||||||
|
--radius-sm: 6px;
|
||||||
|
--radius-md: 8px;
|
||||||
|
--radius-lg: 12px;
|
||||||
|
--radius-xl: 16px;
|
||||||
|
--radius-2xl: 20px;
|
||||||
|
|
||||||
|
/* — Spacing (4px base) — */
|
||||||
|
--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;
|
||||||
|
|
||||||
|
/* — Shadow (warm, low-contrast) — */
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.2);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.25);
|
||||||
|
--shadow-lg: 0 12px 28px rgba(0,0,0,0.35);
|
||||||
|
--shadow-accent: 0 4px 20px rgba(232,168,124,0.3);
|
||||||
|
|
||||||
|
/* — Typography — */
|
||||||
|
--font-body: 'Lexend', system-ui, -apple-system, sans-serif;
|
||||||
|
--font-display: 'Instrument Serif', Georgia, serif;
|
||||||
|
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||||
|
--font-accessible: 'Atkinson Hyperlegible Next', 'Lexend', sans-serif;
|
||||||
|
--font-dyslexic: 'OpenDyslexic', 'Lexend', sans-serif;
|
||||||
|
|
||||||
|
/* — Type scale (Major Third 1.250, base 16) — */
|
||||||
|
--text-caption: 12px;
|
||||||
|
--text-small: 13px;
|
||||||
|
--text-body: 16px;
|
||||||
|
--text-body-lg: 18px;
|
||||||
|
--text-h4: 18px;
|
||||||
|
--text-h3: 21px;
|
||||||
|
--text-h2: 26px;
|
||||||
|
--text-h1: 32px;
|
||||||
|
--text-wordmark: 26px;
|
||||||
|
|
||||||
|
--leading-tight: 1.15;
|
||||||
|
--leading-snug: 1.3;
|
||||||
|
--leading-normal: 1.5;
|
||||||
|
--leading-relaxed: 1.6;
|
||||||
|
--leading-transcript: 1.85;
|
||||||
|
|
||||||
|
/* — Motion — */
|
||||||
|
--ease: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
--duration-ui: 150ms;
|
||||||
|
--duration-decorative: 300ms;
|
||||||
|
--duration-zone: 400ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Light Theme
|
||||||
|
============================================================ */
|
||||||
|
:root[data-theme="light"] {
|
||||||
|
--bg: #faf8f5;
|
||||||
|
--bg-elevated: #f3f0eb;
|
||||||
|
--bg-card: #ffffff;
|
||||||
|
--bg-input: #f0ede8;
|
||||||
|
--sidebar: #f5f2ed;
|
||||||
|
--nav-active: #eae6e0;
|
||||||
|
--hover: #ede9e3;
|
||||||
|
|
||||||
|
--border: #ddd8d0;
|
||||||
|
--border-subtle: #e6e2da;
|
||||||
|
|
||||||
|
--text: #1a1816;
|
||||||
|
--text-secondary: #5c574d;
|
||||||
|
--text-tertiary: #8a8578;
|
||||||
|
|
||||||
|
--accent: #b87a4a;
|
||||||
|
--accent-hover: #a06b3e;
|
||||||
|
--accent-subtle: #b87a4a10;
|
||||||
|
--accent-glow: #b87a4a20;
|
||||||
|
|
||||||
|
--success: #3d8a5a;
|
||||||
|
--danger: #c44d4d;
|
||||||
|
--warning: #b89a3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Sensory Zones — tint surfaces only, accent stays amber.
|
||||||
|
============================================================ */
|
||||||
|
:root[data-zone="cave"] { --bg:#0f1214; --bg-elevated:#141a1e; --bg-card:#171f24; --sidebar:#0d1012; }
|
||||||
|
:root[data-zone="energy"] { --bg:#100e0b; --bg-elevated:#1c1815; --bg-card:#211d18; --sidebar:#141110; }
|
||||||
|
:root[data-zone="reset"] { --bg:#0e100e; --bg-elevated:#161a16; --bg-card:#1a1f1a; --sidebar:#0c0e0c; }
|
||||||
|
|
||||||
|
:root[data-theme="light"][data-zone="cave"] { --bg:#f0f5f7; --bg-elevated:#e8f0f2; --bg-card:#f5fafc; --sidebar:#eaf2f4; }
|
||||||
|
:root[data-theme="light"][data-zone="energy"] { --bg:#faf7f3; --bg-elevated:#f5f0e8; --bg-card:#fffcf7; --sidebar:#f5f0ea; }
|
||||||
|
:root[data-theme="light"][data-zone="reset"] { --bg:#f3f7f2; --bg-elevated:#edf2ea; --bg-card:#f8fcf6; --sidebar:#eff4ec; }
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Semantic elements — h1-h4, p, code, kbd, wordmark
|
||||||
|
============================================================ */
|
||||||
|
html, body {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body);
|
||||||
|
line-height: var(--leading-normal);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-wordmark {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: var(--text-wordmark);
|
||||||
|
color: var(--accent);
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, .k-h1 { font-family: var(--font-body); font-weight: 700; font-size: var(--text-h1); line-height: var(--leading-tight); letter-spacing: -0.01em; color: var(--text); }
|
||||||
|
h2, .k-h2 { font-family: var(--font-body); font-weight: 600; font-size: var(--text-h2); line-height: var(--leading-snug); color: var(--text); }
|
||||||
|
h2.k-display, .k-page-title { font-family: var(--font-display); font-style: italic; font-weight: 400; font-size: var(--text-h2); line-height: var(--leading-snug); color: var(--text); }
|
||||||
|
h3, .k-h3 { font-family: var(--font-body); font-weight: 600; font-size: var(--text-h3); line-height: var(--leading-snug); color: var(--text); }
|
||||||
|
h4, .k-h4 { font-family: var(--font-body); font-weight: 600; font-size: var(--text-h4); line-height: var(--leading-snug); color: var(--text); }
|
||||||
|
|
||||||
|
p, .k-body { font-family: var(--font-body); font-size: var(--text-body); line-height: var(--leading-normal); color: var(--text); max-width: 75ch; }
|
||||||
|
.k-body-lg { font-size: var(--text-body-lg); line-height: var(--leading-relaxed); }
|
||||||
|
.k-small { font-size: var(--text-small); line-height: var(--leading-normal); color: var(--text-secondary); }
|
||||||
|
.k-caption { font-size: var(--text-caption); line-height: var(--leading-normal); color: var(--text-tertiary); letter-spacing: 0.02em; }
|
||||||
|
.k-eyebrow { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-tertiary); font-weight: 500; }
|
||||||
|
|
||||||
|
.k-transcript { font-family: var(--font-body); font-size: var(--text-body); line-height: var(--leading-transcript); letter-spacing: 0.01em; font-feature-settings: "liga" 1, "kern" 1; }
|
||||||
|
|
||||||
|
code, .k-code, kbd {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
kbd { padding: 2px 6px; border-radius: var(--radius-sm); background: var(--bg-elevated); border: 1px solid var(--border-subtle); color: var(--text-tertiary); font-size: 11px; }
|
||||||
|
|
||||||
|
a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
|
||||||
|
a:hover { color: var(--accent-hover); }
|
||||||
|
|
||||||
|
::selection { background: var(--accent-glow); color: var(--text); }
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 3px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grain texture (2.5% dark / 1.5% light) — apply with .grain on a container */
|
||||||
|
.grain { position: relative; }
|
||||||
|
.grain::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute; inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
opacity: 0.025;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/></svg>");
|
||||||
|
}
|
||||||
|
:root[data-theme="light"] .grain::after { opacity: 0.015; }
|
||||||
|
|
||||||
|
/* Kon sinhala glyph ක (U+0D9A) — wordmark companion */
|
||||||
|
.k-glyph::after { content: '\0D9A'; color: var(--accent); margin-left: 0.25em; display: inline-block; }
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,*::before,*::after { animation-duration:0.01ms!important; transition-duration:0.01ms!important; }
|
||||||
|
}
|
||||||
26
src/design-system/preview/brand-icons.html
Normal file
26
src/design-system/preview/brand-icons.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;color:var(--text-tertiary)}
|
||||||
|
.row svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||||
|
.nm{font:400 10px var(--font-mono);color:var(--text-tertiary);margin-top:4px}
|
||||||
|
.u{display:flex;flex-direction:column;align-items:center;gap:4px;min-width:62px}
|
||||||
|
.accent{color:var(--accent)}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Iconography · Lucide · 2px stroke · rounded</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="u accent"><svg viewBox="0 0 24 24"><rect x="9" y="2" width="6" height="11" rx="3"/><path d="M5 10v2a7 7 0 0 0 14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/></svg><div class="nm">Mic</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><div class="nm">FileText</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="m9 12 2 2 4-4"/></svg><div class="nm">SquareCheck</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg><div class="nm">Clock</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9"/></svg><div class="nm">Settings</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg><div class="nm">Search</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg><div class="nm">Upload</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg><div class="nm">Play</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg><div class="nm">Pause</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><div class="nm">Copy</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg><div class="nm">X</div></div>
|
||||||
|
<div class="u"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg><div class="nm">Check</div></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
25
src/design-system/preview/brand-wordmark.html
Normal file
25
src/design-system/preview/brand-wordmark.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:28px;font-family:var(--font-body)}
|
||||||
|
.wrap{display:flex;gap:48px;align-items:flex-start}
|
||||||
|
.k{font-family:var(--font-display);font-style:italic;font-weight:400;font-size:84px;color:var(--accent);line-height:1;letter-spacing:-.015em;margin:0}
|
||||||
|
.tag{margin-top:14px;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--text-tertiary)}
|
||||||
|
.notes{margin-top:4px;max-width:320px}
|
||||||
|
.meta{font:400 10px/1.7 var(--font-mono);color:var(--text-tertiary)}
|
||||||
|
.meta b{color:var(--text-secondary);font-weight:500}
|
||||||
|
.swatch{display:inline-block;width:9px;height:9px;border-radius:999px;background:var(--accent);margin-right:6px;vertical-align:middle}
|
||||||
|
.desc{margin-top:10px;font:400 11px/1.55 var(--font-body);color:var(--text-secondary)}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="wrap">
|
||||||
|
<div>
|
||||||
|
<div class="k">Kon</div>
|
||||||
|
<div class="tag">Think out loud</div>
|
||||||
|
</div>
|
||||||
|
<div class="notes">
|
||||||
|
<div class="meta"><b>Wordmark</b> Instrument Serif Italic 400</div>
|
||||||
|
<div class="meta"><b>Colour</b> <span class="swatch"></span>#e8a87c accent, always</div>
|
||||||
|
<div class="meta"><b>Minimum size</b> 18px</div>
|
||||||
|
<p class="desc">Always italic, always amber. The wordmark stands alone — no accompanying glyph or icon in the lockup.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
21
src/design-system/preview/colors-accent.html
Normal file
21
src/design-system/preview/colors-accent.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body)}
|
||||||
|
.row{display:flex;gap:12px;flex-wrap:wrap}
|
||||||
|
.sw{display:flex;flex-direction:column;gap:6px;min-width:130px}
|
||||||
|
.chip{height:64px;border-radius:var(--radius-md);border:1px solid var(--border-subtle);position:relative;overflow:hidden}
|
||||||
|
.chip.glow{box-shadow:0 4px 20px rgba(232,168,124,.3)}
|
||||||
|
.n{font:500 11px/1.3 var(--font-body);color:var(--text)}
|
||||||
|
.h{font:400 10px/1.3 var(--font-mono);color:var(--text-tertiary)}
|
||||||
|
.eb{font:500 10px/1.3 var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.note{color:var(--text-secondary);font-size:11px;margin-top:10px;max-width:540px;line-height:1.5}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Accent · amber always means something</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="sw"><div class="chip glow" style="background:#e8a87c"></div><div class="n">accent</div><div class="h">#e8a87c</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#d4976a"></div><div class="n">accent-hover</div><div class="h">#d4976a</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#171614;position:relative"><div style="position:absolute;inset:0;background:#e8a87c10"></div></div><div class="n">accent-subtle</div><div class="h">#e8a87c · 6%</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#171614;position:relative"><div style="position:absolute;inset:0;background:#e8a87c25"></div></div><div class="n">accent-glow</div><div class="h">#e8a87c · 15%</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="note">Amber is reserved for interactivity, recording state, or brand identity. Never decorative.</div>
|
||||||
|
</body></html>
|
||||||
17
src/design-system/preview/colors-semantic.html
Normal file
17
src/design-system/preview/colors-semantic.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body)}
|
||||||
|
.row{display:flex;gap:12px;flex-wrap:wrap}
|
||||||
|
.sw{display:flex;flex-direction:column;gap:6px;min-width:150px}
|
||||||
|
.chip{height:64px;border-radius:var(--radius-md);border:1px solid var(--border-subtle);display:flex;align-items:center;justify-content:center;font:500 13px var(--font-body);color:#0f0e0c}
|
||||||
|
.n{font:500 11px/1.3 var(--font-body);color:var(--text)}
|
||||||
|
.h{font:400 10px/1.3 var(--font-mono);color:var(--text-tertiary)}
|
||||||
|
.eb{font:500 10px/1.3 var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Semantic · three states only</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="sw"><div class="chip" style="background:#7ec89a">Success</div><div class="n">success · moss</div><div class="h">#7ec89a</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#e87171;color:#1a0e0e">Danger</div><div class="n">danger · muted red</div><div class="h">#e87171</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#e8c86e">Warning</div><div class="n">warning · honey</div><div class="h">#e8c86e</div></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
24
src/design-system/preview/colors-surfaces.html
Normal file
24
src/design-system/preview/colors-surfaces.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:#0a0907;padding:24px;font-family:var(--font-body)}
|
||||||
|
.row{display:flex;gap:10px;flex-wrap:wrap}
|
||||||
|
.sw{display:flex;flex-direction:column;gap:6px;min-width:110px}
|
||||||
|
.chip{height:56px;border-radius:var(--radius-md);border:1px solid #2c2923}
|
||||||
|
.n{font:500 11px/1.3 var(--font-body);color:#f0ece4}
|
||||||
|
.h{font:400 10px/1.3 var(--font-mono);color:#8a8578;letter-spacing:.02em}
|
||||||
|
.eb{font:500 10px/1.3 var(--font-body);color:#8a8578;letter-spacing:.12em;text-transform:uppercase;margin-bottom:12px}
|
||||||
|
.note{margin-top:14px;font:400 11px/1.55 var(--font-body);color:#9a9486;max-width:560px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Surfaces · warm, with clearer elevation steps</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="sw"><div class="chip" style="background:#0a0907"></div><div class="n">bg</div><div class="h">#0a0907</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#141310"></div><div class="n">bg-elevated</div><div class="h">#141310</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#1d1b17"></div><div class="n">bg-card</div><div class="h">#1d1b17</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#26231e"></div><div class="n">bg-raised</div><div class="h">#26231e</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#0f0e0b"></div><div class="n">bg-input</div><div class="h">#0f0e0b</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#0d0c09"></div><div class="n">sidebar</div><div class="h">#0d0c09</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#252219"></div><div class="n">nav-active</div><div class="h">#252219</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="background:#221f1a"></div><div class="n">hover</div><div class="h">#221f1a</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="note">Steps widened from ~3% to ~6% lightness between layers. Base dropped half a stop; cards + raised panels lifted a stop. Still warm amber-biased neutrals — no pure black, no halation.</div>
|
||||||
|
</body></html>
|
||||||
19
src/design-system/preview/colors-text.html
Normal file
19
src/design-system/preview/colors-text.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body)}
|
||||||
|
.row{display:flex;gap:12px;flex-wrap:wrap}
|
||||||
|
.sw{display:flex;flex-direction:column;gap:6px;min-width:130px}
|
||||||
|
.chip{height:60px;border-radius:var(--radius-md);border:1px solid var(--border-subtle);display:flex;align-items:center;padding:0 14px;font-size:13px}
|
||||||
|
.n{font:500 11px/1.3 var(--font-body);color:var(--text)}
|
||||||
|
.h{font:400 10px/1.3 var(--font-mono);color:var(--text-tertiary)}
|
||||||
|
.eb{font:500 10px/1.3 var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.contrast{margin-top:16px;font-size:11px;color:var(--text-secondary);line-height:1.55;max-width:560px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Text hierarchy · warm greys with amber undertone</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="sw"><div class="chip" style="color:#f0ece4">The quick brown</div><div class="n">text</div><div class="h">#f0ece4 · AAA</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="color:#9a9486">fox jumps over</div><div class="n">text-secondary</div><div class="h">#9a9486 · AA</div></div>
|
||||||
|
<div class="sw"><div class="chip" style="color:#716b60">the lazy dog</div><div class="n">text-tertiary</div><div class="h">#716b60 · large only</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="contrast">Never #000 on #fff — causes halation for neurodivergent users. Tertiary only at 18px bold / 24px regular minimum.</div>
|
||||||
|
</body></html>
|
||||||
27
src/design-system/preview/colors-zones.html
Normal file
27
src/design-system/preview/colors-zones.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body)}
|
||||||
|
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
|
||||||
|
.zone{border:1px solid var(--border-subtle);border-radius:var(--radius-lg);overflow:hidden}
|
||||||
|
.hero{height:72px;position:relative;display:flex;align-items:flex-end;padding:10px 14px}
|
||||||
|
.dot{position:absolute;top:12px;right:12px;width:10px;height:10px;border-radius:50%;background:#e8a87c}
|
||||||
|
.nm{font:500 13px var(--font-body);color:#f0ece4}
|
||||||
|
.meta{padding:10px 14px;background:rgba(255,255,255,.02);display:flex;justify-content:space-between;font:400 10px var(--font-mono);color:#716b60}
|
||||||
|
.eb{font:500 10px/1.3 var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Sensory zones · tint surfaces only — accent stays amber</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="zone">
|
||||||
|
<div class="hero" style="background:#141a1e"><span class="dot"></span><span class="nm">Cave</span></div>
|
||||||
|
<div class="meta"><span>focus · cool teal</span><span>#0f1214</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="zone">
|
||||||
|
<div class="hero" style="background:#1c1815"><span class="dot"></span><span class="nm">Energy</span></div>
|
||||||
|
<div class="meta"><span>active · warm neutral</span><span>#100e0b</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="zone">
|
||||||
|
<div class="hero" style="background:#161a16"><span class="dot"></span><span class="nm">Reset</span></div>
|
||||||
|
<div class="meta"><span>calm · muted sage</span><span>#0e100e</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
29
src/design-system/preview/components-buttons.html
Normal file
29
src/design-system/preview/components-buttons.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:16px}
|
||||||
|
.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:14px}
|
||||||
|
.b{font:500 13px var(--font-body);padding:8px 14px;border-radius:8px;border:1px solid transparent;cursor:pointer;display:inline-flex;align-items:center;gap:8px}
|
||||||
|
.primary{background:var(--accent);color:#0f0e0c;box-shadow:0 4px 20px rgba(232,168,124,.3)}
|
||||||
|
.primary:hover{background:var(--accent-hover)}
|
||||||
|
.secondary{background:transparent;color:var(--text-secondary);border-color:var(--border-subtle)}
|
||||||
|
.secondary:hover{background:var(--hover);color:var(--text)}
|
||||||
|
.ghost{background:transparent;color:var(--text-secondary)}
|
||||||
|
.ghost:hover{background:var(--hover);color:var(--text)}
|
||||||
|
.danger{background:transparent;color:var(--danger)}
|
||||||
|
.danger:hover{background:rgba(232,113,113,.1)}
|
||||||
|
.sm{font-size:11px;padding:4px 10px}
|
||||||
|
.lg{font-size:13px;padding:10px 18px}
|
||||||
|
.rec{width:40px;height:40px;border-radius:999px;background:var(--accent);box-shadow:0 4px 20px rgba(232,168,124,.3);display:flex;align-items:center;justify-content:center;border:none;cursor:pointer}
|
||||||
|
.rec>span{width:14px;height:14px;border-radius:999px;background:#fff}
|
||||||
|
.stop{background:var(--danger)}
|
||||||
|
.stop>span{border-radius:3px;background:#fff}
|
||||||
|
.l{font:400 10px var(--font-mono);color:var(--text-tertiary);min-width:80px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Buttons</div>
|
||||||
|
<div class="row"><div class="l">primary</div><button class="b primary">Record</button><button class="b primary lg">Start recording</button><button class="b primary sm">Save</button></div>
|
||||||
|
<div class="row"><div class="l">secondary</div><button class="b secondary">Export</button><button class="b secondary">Rename</button><button class="b secondary sm">Clear</button></div>
|
||||||
|
<div class="row"><div class="l">ghost</div><button class="b ghost">Copy</button><button class="b ghost">Template</button><button class="b ghost">Cancel</button></div>
|
||||||
|
<div class="row"><div class="l">danger</div><button class="b danger">Delete</button><button class="b danger sm">Clear all</button></div>
|
||||||
|
<div class="row"><div class="l">record</div><button class="rec"><span></span></button><button class="rec stop"><span></span></button><span style="font-size:11px;color:var(--text-tertiary)">40×40 · circular · warm-pulse when recording</span></div>
|
||||||
|
</body></html>
|
||||||
28
src/design-system/preview/components-cards.html
Normal file
28
src/design-system/preview/components-cards.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.card{background:var(--bg-card);border:1px solid var(--border-subtle);border-radius:20px;padding:18px 20px;box-shadow:0 1px 3px rgba(0,0,0,.2);max-width:520px;margin-bottom:14px}
|
||||||
|
.title{font:600 15px var(--font-body);margin-bottom:6px}
|
||||||
|
.body{font-size:13px;color:var(--text-secondary);line-height:1.55}
|
||||||
|
.meta{margin-top:10px;display:flex;gap:10px;font-size:11px;color:var(--text-tertiary)}
|
||||||
|
.pill{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;background:rgba(232,168,124,.1);color:var(--accent);font-size:11px;font-weight:500}
|
||||||
|
.pill.dot::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor}
|
||||||
|
.pill.muted{background:var(--bg-elevated);color:var(--text-tertiary)}
|
||||||
|
.pill.danger{background:rgba(232,113,113,.12);color:var(--danger)}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Cards · bg-card · 1px subtle border · radius 20 · shadow-sm</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="title">Reviewed the onboarding copy</div>
|
||||||
|
<div class="body">Okay so the real issue is the first-run screen assumes you know what dictation means. We should lead with "press and hold, then speak" — that's the whole thing.</div>
|
||||||
|
<div class="meta"><span>01:42</span><span>·</span><span>14 Apr · live</span><span>·</span><span>142 words</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="eb" style="margin-top:8px">Badges & pills</div>
|
||||||
|
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
||||||
|
<span class="pill dot">Recording</span>
|
||||||
|
<span class="pill">Ready</span>
|
||||||
|
<span class="pill muted">Auto</span>
|
||||||
|
<span class="pill muted">12 words</span>
|
||||||
|
<span class="pill danger dot">Error</span>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
16
src/design-system/preview/components-empty-states.html
Normal file
16
src/design-system/preview/components-empty-states.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.state{display:flex;align-items:center;justify-content:center;flex-direction:column;gap:14px;padding:22px;border:1px solid var(--border-subtle);border-radius:16px;background:var(--bg-card)}
|
||||||
|
.g{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
|
||||||
|
svg{width:40px;height:40px;fill:none;stroke:currentColor;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;opacity:.4;color:var(--text-secondary)}
|
||||||
|
.msg{font-size:13px;color:var(--text-secondary);text-align:center;max-width:22ch;line-height:1.55}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Empty states · ambient presence, no demanding CTA</div>
|
||||||
|
<div class="g">
|
||||||
|
<div class="state"><svg viewBox="0 0 24 24"><rect x="9" y="2" width="6" height="11" rx="3"/><path d="M5 10v2a7 7 0 0 0 14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/></svg><div class="msg">Press record or Ctrl+Shift+R</div></div>
|
||||||
|
<div class="state"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg><div class="msg">Your transcriptions will be saved here</div></div>
|
||||||
|
<div class="state"><svg viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg><div class="msg">Something went wrong with that transcription. Your audio is saved — try again when you're ready.</div></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
32
src/design-system/preview/components-inputs.html
Normal file
32
src/design-system/preview/components-inputs.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.row{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:14px;align-items:center}
|
||||||
|
.field{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--bg-input);border:1px solid var(--border-subtle);border-radius:8px;min-width:240px;font-size:13px;color:var(--text-tertiary)}
|
||||||
|
.field.focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(232,168,124,.15)}
|
||||||
|
.field input{background:transparent;border:none;color:var(--text);font:inherit;outline:none;flex:1}
|
||||||
|
.toggle{width:38px;height:22px;background:var(--bg-elevated);border:1px solid var(--border-subtle);border-radius:999px;position:relative}
|
||||||
|
.toggle.on{background:var(--accent);border-color:transparent}
|
||||||
|
.knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:999px;background:var(--text)}
|
||||||
|
.toggle.on .knob{left:18px;background:#0f0e0c}
|
||||||
|
.seg{display:inline-flex;background:var(--bg-input);border:1px solid var(--border-subtle);border-radius:8px;padding:2px}
|
||||||
|
.seg span{padding:5px 12px;font-size:12px;border-radius:6px;color:var(--text-secondary)}
|
||||||
|
.seg span.sel{background:var(--bg-card);color:var(--text)}
|
||||||
|
svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Form inputs</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="field"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg><input placeholder="Search all transcripts..." value="onboarding"></div>
|
||||||
|
<div class="field focus"><input value="device-1-usb-yeti" style="color:var(--text)"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div style="display:flex;align-items:center;gap:10px"><div class="toggle on"><div class="knob"></div></div><span style="font-size:13px">Save recordings</span></div>
|
||||||
|
<div style="display:flex;align-items:center;gap:10px"><div class="toggle"><div class="knob"></div></div><span style="font-size:13px;color:var(--text-secondary)">British English</span></div>
|
||||||
|
<div style="display:flex;align-items:center;gap:10px"><div class="toggle on"><div class="knob"></div></div><span style="font-size:13px">Reduce motion</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="seg"><span>Auto</span><span class="sel">Clean</span><span>Raw</span></div>
|
||||||
|
<div class="seg"><span class="sel">Dark</span><span>Light</span><span>System</span></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
25
src/design-system/preview/components-nav.html
Normal file
25
src/design-system/preview/components-nav.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.nav{background:var(--sidebar);border:1px solid var(--border-subtle);border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:2px;width:220px}
|
||||||
|
.item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:8px;font-size:13px;color:var(--text-secondary);cursor:pointer}
|
||||||
|
.item:hover{background:var(--hover);color:var(--text)}
|
||||||
|
.item.active{background:var(--nav-active);color:var(--text);font-weight:500}
|
||||||
|
.item.active svg{color:var(--accent);stroke-width:2}
|
||||||
|
svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;color:var(--text-tertiary);flex-shrink:0}
|
||||||
|
.badge{margin-left:auto;font:500 10px var(--font-body);padding:1px 7px;border-radius:999px;background:rgba(232,168,124,.15);color:var(--accent)}
|
||||||
|
.wrap{display:flex;gap:24px;align-items:flex-start}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Navigation · sidebar items</div>
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="nav">
|
||||||
|
<div class="item active"><svg viewBox="0 0 24 24"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/></svg>Dictation</div>
|
||||||
|
<div class="item"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>Files</div>
|
||||||
|
<div class="item"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="m9 12 2 2 4-4"/></svg>Tasks<span class="badge">4</span></div>
|
||||||
|
<div class="item"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>History</div>
|
||||||
|
<div class="item"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>Settings</div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:12px;color:var(--text-secondary);max-width:240px;line-height:1.55">Active item gets <code style="font:var(--font-mono);color:var(--accent)">--nav-active</code> background, amber icon, stroke 2. Hover lifts text to primary and fills background with <code style="font:var(--font-mono);color:var(--accent)">--hover</code>.</div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
19
src/design-system/preview/components-toasts.html
Normal file
19
src/design-system/preview/components-toasts.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.toast{display:flex;gap:12px;padding:14px 16px;border-radius:12px;background:var(--bg-elevated);border:1px solid var(--border-subtle);box-shadow:0 12px 28px rgba(0,0,0,.35);max-width:380px;margin-bottom:10px;align-items:flex-start}
|
||||||
|
.toast.err{border-color:rgba(232,113,113,.35);background:rgba(232,113,113,.08)}
|
||||||
|
.toast.ok{border-color:rgba(126,200,154,.3)}
|
||||||
|
.dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;margin-top:6px}
|
||||||
|
.dot.err{background:var(--danger)}
|
||||||
|
.dot.ok{background:var(--success)}
|
||||||
|
.dot.warn{background:var(--warning)}
|
||||||
|
.title{font:500 13px var(--font-body);margin-bottom:3px}
|
||||||
|
.body{font:400 12px var(--font-body);color:var(--text-secondary);line-height:1.5}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Toasts · bottom-right · sticky on error</div>
|
||||||
|
<div class="toast err"><div class="dot err"></div><div><div class="title">Could not start recording</div><div class="body">Audio device disconnected. Plug it back in and try again.</div></div></div>
|
||||||
|
<div class="toast"><div class="dot warn"></div><div><div class="title">Dropped 2s of older audio</div><div class="body">Kon is keeping up in real time.</div></div></div>
|
||||||
|
<div class="toast ok"><div class="dot ok"></div><div><div class="title">Saved · 3 tasks extracted</div><div class="body">Your audio is saved. Open Tasks to review.</div></div></div>
|
||||||
|
</body></html>
|
||||||
26
src/design-system/preview/spacing-motion.html
Normal file
26
src/design-system/preview/spacing-motion.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.row{display:flex;gap:16px;flex-wrap:wrap}
|
||||||
|
.spec{display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px dashed var(--border-subtle);border-radius:8px;font-size:12px;color:var(--text-secondary)}
|
||||||
|
.curve{height:60px;background:var(--bg-elevated);border-radius:8px;padding:10px 14px;display:flex;align-items:center;gap:14px}
|
||||||
|
svg{width:120px;height:30px}
|
||||||
|
code{font:400 11px var(--font-mono);color:var(--accent)}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Motion · elderflower, not espresso</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="spec"><span>UI</span><code>150–200ms</code></div>
|
||||||
|
<div class="spec"><span>Decorative</span><code>300–500ms</code></div>
|
||||||
|
<div class="spec"><span>Zone cross-fade</span><code>300–500ms</code></div>
|
||||||
|
<div class="spec"><span>Waveform pulse</span><code>2s</code></div>
|
||||||
|
</div>
|
||||||
|
<div class="eb" style="margin-top:20px">Easing</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="curve">
|
||||||
|
<svg viewBox="0 0 120 30" preserveAspectRatio="none"><path d="M2 28 C 26 28, 50 4, 118 2" stroke="#e8a87c" stroke-width="1.5" fill="none"/></svg>
|
||||||
|
<div><div style="font:500 12px var(--font-body)">ease-out</div><code>cubic-bezier(.2, .8, .2, 1)</code></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:16px;font-size:11px;color:var(--text-tertiary);max-width:540px;line-height:1.55">Never: bounce, shake, slide-from-offscreen, auto-play. Reduce motion is the default — opt IN.</div>
|
||||||
|
</body></html>
|
||||||
48
src/design-system/preview/spacing-radii.html
Normal file
48
src/design-system/preview/spacing-radii.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:18px}
|
||||||
|
.row{display:flex;gap:18px;flex-wrap:wrap;align-items:flex-end}
|
||||||
|
.u{display:flex;flex-direction:column;gap:10px;align-items:flex-start}
|
||||||
|
.tile{
|
||||||
|
position:relative;
|
||||||
|
width:88px;height:72px;
|
||||||
|
background:var(--bg-elevated);
|
||||||
|
border:1px solid var(--border);
|
||||||
|
box-shadow:var(--shadow-sm);
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.tile::before{
|
||||||
|
content:'';
|
||||||
|
position:absolute;top:-1px;left:-1px;
|
||||||
|
width:48px;height:48px;
|
||||||
|
border-top:2px solid var(--accent);
|
||||||
|
border-left:2px solid var(--accent);
|
||||||
|
border-top-left-radius:inherit;
|
||||||
|
opacity:.9;
|
||||||
|
}
|
||||||
|
.px{position:absolute;bottom:6px;right:8px;font:500 10px var(--font-mono);color:var(--text-tertiary);letter-spacing:.02em}
|
||||||
|
.n{font:600 13px var(--font-body);color:var(--text)}
|
||||||
|
.h{font:400 10px/1.4 var(--font-body);color:var(--text-tertiary)}
|
||||||
|
.pill{
|
||||||
|
position:relative;width:88px;height:72px;
|
||||||
|
border-radius:999px;
|
||||||
|
background:var(--bg-elevated);
|
||||||
|
border:1px solid var(--border);
|
||||||
|
box-shadow:var(--shadow-sm);
|
||||||
|
}
|
||||||
|
.pill::before{
|
||||||
|
content:'';position:absolute;inset:0;border-radius:999px;
|
||||||
|
border:2px dashed rgba(232,168,124,.6);
|
||||||
|
}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Radii · amber arc = curve magnitude</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="u"><div class="tile" style="border-radius:6px"><span class="px">6</span></div><div><div class="n">sm</div><div class="h">keyboard, pills</div></div></div>
|
||||||
|
<div class="u"><div class="tile" style="border-radius:8px"><span class="px">8</span></div><div><div class="n">md</div><div class="h">buttons, fields</div></div></div>
|
||||||
|
<div class="u"><div class="tile" style="border-radius:12px"><span class="px">12</span></div><div><div class="n">lg</div><div class="h">inner cards, nav</div></div></div>
|
||||||
|
<div class="u"><div class="tile" style="border-radius:16px"><span class="px">16</span></div><div><div class="n">xl</div><div class="h">containers</div></div></div>
|
||||||
|
<div class="u"><div class="tile" style="border-radius:20px"><span class="px">20</span></div><div><div class="n">2xl</div><div class="h">transcript card</div></div></div>
|
||||||
|
<div class="u"><div class="pill"><span class="px">∞</span></div><div><div class="n">full</div><div class="h">record button, dots</div></div></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
23
src/design-system/preview/spacing-scale.html
Normal file
23
src/design-system/preview/spacing-scale.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:24px;font-family:var(--font-body)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.row{display:flex;gap:14px;align-items:flex-end}
|
||||||
|
.u{display:flex;flex-direction:column;gap:6px;align-items:center}
|
||||||
|
.sq{background:var(--accent);border-radius:var(--radius-md);opacity:.9}
|
||||||
|
.n{font:500 10px var(--font-mono);color:var(--text-tertiary)}
|
||||||
|
.v{font:500 11px var(--font-body);color:var(--text)}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Spacing · 4px base</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="u"><div class="sq" style="width:4px;height:4px"></div><div class="v">1</div><div class="n">4px</div></div>
|
||||||
|
<div class="u"><div class="sq" style="width:8px;height:8px"></div><div class="v">2</div><div class="n">8px</div></div>
|
||||||
|
<div class="u"><div class="sq" style="width:12px;height:12px"></div><div class="v">3</div><div class="n">12px</div></div>
|
||||||
|
<div class="u"><div class="sq" style="width:16px;height:16px"></div><div class="v">4</div><div class="n">16px</div></div>
|
||||||
|
<div class="u"><div class="sq" style="width:20px;height:20px"></div><div class="v">5</div><div class="n">20px</div></div>
|
||||||
|
<div class="u"><div class="sq" style="width:24px;height:24px"></div><div class="v">6</div><div class="n">24px</div></div>
|
||||||
|
<div class="u"><div class="sq" style="width:32px;height:32px"></div><div class="v">8</div><div class="n">32px</div></div>
|
||||||
|
<div class="u"><div class="sq" style="width:40px;height:40px"></div><div class="v">10</div><div class="n">40px</div></div>
|
||||||
|
<div class="u"><div class="sq" style="width:48px;height:48px"></div><div class="v">12</div><div class="n">48px</div></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
18
src/design-system/preview/spacing-shadows.html
Normal file
18
src/design-system/preview/spacing-shadows.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:28px;font-family:var(--font-body)}
|
||||||
|
.eb{font:500 10px var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:18px}
|
||||||
|
.row{display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start}
|
||||||
|
.u{display:flex;flex-direction:column;gap:10px;align-items:center}
|
||||||
|
.c{width:110px;height:60px;background:var(--bg-card);border:1px solid var(--border-subtle);border-radius:12px}
|
||||||
|
.n{font:500 11px var(--font-body);color:var(--text)}
|
||||||
|
.h{font:400 10px var(--font-mono);color:var(--text-tertiary);text-align:center;max-width:120px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Shadow system · low, warm</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="u"><div class="c" style="box-shadow:0 1px 3px rgba(0,0,0,.2)"></div><div class="n">sm</div><div class="h">0 1px 3px / .2 · cards</div></div>
|
||||||
|
<div class="u"><div class="c" style="box-shadow:0 4px 12px rgba(0,0,0,.25)"></div><div class="n">md</div><div class="h">0 4px 12px / .25 · popovers</div></div>
|
||||||
|
<div class="u"><div class="c" style="box-shadow:0 12px 28px rgba(0,0,0,.35)"></div><div class="n">lg</div><div class="h">0 12px 28px / .35 · modals</div></div>
|
||||||
|
<div class="u"><div class="c" style="background:#e8a87c;box-shadow:0 4px 20px rgba(232,168,124,.3);border-color:transparent"></div><div class="n">accent glow</div><div class="h">record button only</div></div>
|
||||||
|
</div>
|
||||||
|
</body></html>
|
||||||
12
src/design-system/preview/type-body.html
Normal file
12
src/design-system/preview/type-body.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:28px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.row{display:flex;align-items:flex-start;gap:18px;margin-bottom:14px}
|
||||||
|
.l{font:400 11px var(--font-mono);color:var(--text-tertiary);min-width:110px;padding-top:4px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="row"><div class="l">Body · 16/1.5</div><div style="max-width:56ch">Kon listens more than it speaks. Press the hotkey, speak your thought, and your words appear — saved, searchable, left exactly where you put them.</div></div>
|
||||||
|
<div class="row"><div class="l">Body large · 18/1.6</div><div style="font-size:18px;line-height:1.6;max-width:52ch;color:var(--text-secondary)">A calm friend who has already done the anxious part for you.</div></div>
|
||||||
|
<div class="row"><div class="l">Small · 13</div><div style="font-size:13px;color:var(--text-secondary)">Press record or Ctrl+Shift+R</div></div>
|
||||||
|
<div class="row"><div class="l">Caption · 12</div><div style="font-size:12px;color:var(--text-tertiary)">12 words · 00:47 · live</div></div>
|
||||||
|
<div class="row"><div class="l">Eyebrow · 10</div><div style="font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-tertiary)">Think out loud</div></div>
|
||||||
|
</body></html>
|
||||||
12
src/design-system/preview/type-headings.html
Normal file
12
src/design-system/preview/type-headings.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:28px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.row{display:flex;align-items:flex-end;gap:18px;margin-bottom:18px}
|
||||||
|
.l{font:400 11px var(--font-mono);color:var(--text-tertiary);min-width:110px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="row"><div class="l">H1 · 32/1.15/700 Lexend</div><div style="font:700 32px/1.15 var(--font-body);letter-spacing:-.01em">Clarity without friction</div></div>
|
||||||
|
<div class="row"><div class="l">H2 · 26/1.2/600 Lexend</div><div style="font:600 26px/1.2 var(--font-body)">Think out loud</div></div>
|
||||||
|
<div class="row"><div class="l">Display · 26 Instrument Serif</div><div style="font:400 italic 26px/1.1 var(--font-display);color:var(--text)">History</div></div>
|
||||||
|
<div class="row"><div class="l">H3 · 21/1.3</div><div style="font:600 21px/1.3 var(--font-body)">Sensory zones</div></div>
|
||||||
|
<div class="row"><div class="l">H4 · 18/1.3</div><div style="font:600 18px/1.3 var(--font-body)">Press record or ⌃⇧R</div></div>
|
||||||
|
</body></html>
|
||||||
13
src/design-system/preview/type-transcript-mono.html
Normal file
13
src/design-system/preview/type-transcript-mono.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html,body{margin:0;background:var(--bg);padding:28px;font-family:var(--font-body);color:var(--text)}
|
||||||
|
.eb{font:500 10px/1 var(--font-body);color:var(--text-tertiary);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px}
|
||||||
|
.tr{font-family:var(--font-body);font-size:17px;line-height:1.85;letter-spacing:.01em;max-width:58ch;color:var(--text)}
|
||||||
|
.mono{margin-top:18px;font:500 13px var(--font-mono);color:var(--text-secondary)}
|
||||||
|
kbd{font:400 11px var(--font-mono);padding:3px 7px;border-radius:6px;background:#171614;border:1px solid #221f1b;color:var(--text-tertiary);margin-right:6px}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="eb">Transcript · 16–24px · 1.85 · +0.01em · liga + kern</div>
|
||||||
|
<div class="tr">So the thing I wanted to capture is — if we move the onboarding to the second screen instead of the first, the friction around first launch basically disappears. That feels right.</div>
|
||||||
|
<div class="mono">~/Library/Application Support/Kon/transcripts.db</div>
|
||||||
|
<div style="margin-top:14px"><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>R</kbd><span style="font-size:12px;color:var(--text-tertiary)">Global record toggle</span></div>
|
||||||
|
</body></html>
|
||||||
116
src/design-system/ui_kits/DictationPage.jsx
Normal file
116
src/design-system/ui_kits/DictationPage.jsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// DictationPage.jsx
|
||||||
|
const { useState: useStateD, useRef } = React;
|
||||||
|
|
||||||
|
function RecordButton({ recording, onToggle }) {
|
||||||
|
const style = {
|
||||||
|
width:40, height:40, borderRadius:999, border:'none', cursor:'pointer',
|
||||||
|
display:'flex', alignItems:'center', justifyContent:'center',
|
||||||
|
transition:'all 150ms cubic-bezier(.2,.8,.2,1)',
|
||||||
|
background: recording ? 'var(--danger)' : 'var(--accent)',
|
||||||
|
boxShadow: recording ? '0 0 0 0 rgba(232,113,113,.4)' : '0 4px 20px rgba(232,168,124,.3)',
|
||||||
|
animation: recording ? 'konPulse 2s ease-in-out infinite' : 'none',
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<button onClick={onToggle} style={style} aria-label={recording ? 'Stop recording' : 'Start recording'}>
|
||||||
|
{recording
|
||||||
|
? <span style={{width:14, height:14, borderRadius:3, background:'#fff'}}/>
|
||||||
|
: <span style={{width:14, height:14, borderRadius:999, background:'rgba(255,255,255,.92)'}}/>}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Waveform({ recording }) {
|
||||||
|
const bars = [0.4, 0.8, 0.5, 1, 0.6, 0.9, 0.4, 0.7, 0.5, 0.8, 0.3, 0.6];
|
||||||
|
return (
|
||||||
|
<div style={{flex:1, minHeight:32, borderRadius:8, border:'1px solid var(--border-subtle)', background:'rgba(23,22,20,.4)', display:'flex', alignItems:'center', padding:'0 14px', gap:10}}>
|
||||||
|
{recording ? (
|
||||||
|
<span style={{display:'inline-flex', alignItems:'flex-end', gap:3, height:18}}>
|
||||||
|
{bars.map((h, i) => (
|
||||||
|
<span key={i} style={{
|
||||||
|
width:2, borderRadius:999, background:'rgba(232,113,113,.7)',
|
||||||
|
height:`${h*100}%`,
|
||||||
|
animation:`konBar 1.2s ease-in-out ${i*60}ms infinite`,
|
||||||
|
}}/>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span style={{font:'400 11px var(--font-body)', color:'var(--text-tertiary)'}}>
|
||||||
|
Press record or <kbd style={{font:'400 10px var(--font-mono)', padding:'2px 6px', borderRadius:4, background:'var(--bg-elevated)', border:'1px solid var(--border-subtle)', color:'var(--text-tertiary)'}}>Ctrl+Shift+R</kbd>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToolbarBtn({ children, onClick, accent, disabled }) {
|
||||||
|
const [hov, setHov] = useStateD(false);
|
||||||
|
return (
|
||||||
|
<button onClick={onClick} disabled={disabled}
|
||||||
|
onMouseEnter={()=>setHov(true)} onMouseLeave={()=>setHov(false)}
|
||||||
|
style={{
|
||||||
|
font:'500 12px var(--font-body)', padding:'6px 12px', borderRadius:8, border:'none', cursor: disabled?'default':'pointer',
|
||||||
|
background: hov && !disabled ? (accent ? 'rgba(232,168,124,.1)':'var(--hover)') : 'transparent',
|
||||||
|
color: accent ? 'var(--accent)' : (hov ? 'var(--text)' : 'var(--text-secondary)'),
|
||||||
|
opacity: disabled ? 0.4 : 1, whiteSpace:'nowrap', transition:'all 150ms',
|
||||||
|
}}>{children}</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DictationPage({ recording, onToggle, timer, transcript, onTranscript, status, statusColor, onExtractTasks, onClear }) {
|
||||||
|
const wordCount = transcript.trim() ? transcript.trim().split(/\s+/).length : 0;
|
||||||
|
const ta = useRef(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{display:'flex', flexDirection:'column', height:'100%'}}>
|
||||||
|
{/* Control strip */}
|
||||||
|
<div style={{display:'flex', alignItems:'center', gap:12, padding:'0 20px', height:56, borderBottom:'1px solid var(--border-subtle)', flexShrink:0}}>
|
||||||
|
<RecordButton recording={recording} onToggle={onToggle}/>
|
||||||
|
<span style={{font:'500 11px var(--font-body)', color: recording ? 'var(--danger)' : 'var(--text-secondary)'}}>{recording?'Stop':'Record'}</span>
|
||||||
|
<Waveform recording={recording}/>
|
||||||
|
<span style={{font:'700 20px var(--font-body)', color:'var(--text)', fontVariantNumeric:'tabular-nums', letterSpacing:'-.02em'}}>{timer}</span>
|
||||||
|
<span style={{font:'500 11px var(--font-body)', color: recording ? 'var(--danger)' : 'var(--success)', minWidth:48, textAlign:'right', letterSpacing:'.04em'}}>
|
||||||
|
{recording
|
||||||
|
? <span style={{display:'inline-flex', alignItems:'center', gap:6}}><span style={{width:6, height:6, borderRadius:999, background:'var(--danger)'}}/>REC</span>
|
||||||
|
: 'Ready'}
|
||||||
|
</span>
|
||||||
|
<span style={{width:1, height:16, background:'var(--border-subtle)'}}/>
|
||||||
|
<button style={{display:'flex', alignItems:'center', gap:6, padding:'6px 10px', background:'transparent', color:'var(--text-tertiary)', border:'none', borderRadius:8, cursor:'pointer', fontSize:11}}>
|
||||||
|
<Icon name="check" size={16}/> Tasks
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Toolbar */}
|
||||||
|
<div style={{display:'flex', alignItems:'center', justifyContent:'flex-end', gap:4, padding:'8px 20px', borderBottom:'1px solid var(--border-subtle)', flexShrink:0}}>
|
||||||
|
<ToolbarBtn>Template</ToolbarBtn>
|
||||||
|
<ToolbarBtn accent onClick={onExtractTasks} disabled={!transcript.trim()}>Extract Tasks</ToolbarBtn>
|
||||||
|
<span style={{width:1, height:16, background:'var(--border-subtle)', margin:'0 4px'}}/>
|
||||||
|
<ToolbarBtn>Save</ToolbarBtn>
|
||||||
|
<ToolbarBtn>Copy</ToolbarBtn>
|
||||||
|
<ToolbarBtn onClick={onClear}>Clear</ToolbarBtn>
|
||||||
|
<ToolbarBtn>Export</ToolbarBtn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Transcript */}
|
||||||
|
<div style={{flex:1, padding:'12px 20px', minHeight:0, display:'flex'}}>
|
||||||
|
<div style={{background:'var(--bg-card)', border:'1px solid var(--border-subtle)', borderRadius:20, boxShadow:'0 1px 3px rgba(0,0,0,.2)', flex:1, display:'flex', flexDirection:'column', minHeight:0}}>
|
||||||
|
{transcript.trim() ? (
|
||||||
|
<textarea ref={ta} value={transcript} onChange={e=>onTranscript(e.target.value)}
|
||||||
|
style={{flex:1, width:'100%', background:'transparent', color:'var(--text)', padding:24, border:'none', resize:'none', outline:'none',
|
||||||
|
font:'400 17px/1.85 var(--font-body)', letterSpacing:'.01em'}}/>
|
||||||
|
) : (
|
||||||
|
<div style={{flex:1, display:'flex', alignItems:'center', justifyContent:'center', flexDirection:'column', gap:16, color:'var(--text-tertiary)'}}>
|
||||||
|
<Icon name="mic" size={40} stroke={1} className="" />
|
||||||
|
<span style={{font:'400 13px var(--font-body)', color:'var(--text-secondary)'}}>Press record or Ctrl+Shift+R</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div style={{display:'flex', justifyContent:'space-between', padding:'4px 24px 14px', flexShrink:0}}>
|
||||||
|
<span style={{font:'400 11px var(--font-body)', color:'var(--text-tertiary)'}}>{wordCount} {wordCount===1?'word':'words'}</span>
|
||||||
|
<span style={{font:'400 11px var(--font-body)', color:'var(--text-tertiary)'}}>Clean · No profile</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(window, { DictationPage });
|
||||||
134
src/design-system/ui_kits/OtherPages.jsx
Normal file
134
src/design-system/ui_kits/OtherPages.jsx
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// OtherPages.jsx — Files, Tasks, History, Settings
|
||||||
|
|
||||||
|
function PageHeader({ title, subtitle, right }) {
|
||||||
|
return (
|
||||||
|
<div style={{display:'flex', alignItems:'flex-end', justifyContent:'space-between', padding:'22px 24px 14px', borderBottom:'1px solid var(--border-subtle)'}}>
|
||||||
|
<div>
|
||||||
|
<h2 style={{font:'600 22px/1.2 var(--font-body)', color:'var(--text)', margin:0}}>{title}</h2>
|
||||||
|
{subtitle && <p style={{font:'400 13px var(--font-body)', color:'var(--text-secondary)', margin:'4px 0 0'}}>{subtitle}</p>}
|
||||||
|
</div>
|
||||||
|
{right}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleHistory = [
|
||||||
|
{ date: '14 Apr · 16:42', preview: 'Okay so the real issue is the first-run screen assumes you know what dictation means. We should lead with press and hold, then speak…', words: 142, dur: '01:42' },
|
||||||
|
{ date: '14 Apr · 14:10', preview: 'Running notes for the Barbican trip — things to pack, times for trains, the pub meet at 7.', words: 58, dur: '00:38' },
|
||||||
|
{ date: '13 Apr · 22:08', preview: 'Reviewed the onboarding copy. Three problems — first-run assumes context, the mic permission flow is buried, and the empty state is cold.', words: 204, dur: '02:15' },
|
||||||
|
{ date: '13 Apr · 09:51', preview: 'Grocery list: elderflower cordial, oat milk, the good bread, the mushrooms from the place on Columbia Road.', words: 26, dur: '00:22' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function HistoryPage() {
|
||||||
|
return (
|
||||||
|
<div style={{display:'flex', flexDirection:'column', height:'100%'}}>
|
||||||
|
<PageHeader title="History" subtitle="Everything you've said, kept nearby."
|
||||||
|
right={<div style={{display:'flex', alignItems:'center', gap:8, padding:'8px 12px', background:'var(--bg-input)', border:'1px solid var(--border-subtle)', borderRadius:8, minWidth:260, color:'var(--text-tertiary)'}}>
|
||||||
|
<Icon name="search" size={14}/>
|
||||||
|
<input placeholder="Search transcripts..." style={{background:'transparent', border:'none', color:'var(--text)', outline:'none', flex:1, font:'400 13px var(--font-body)'}}/>
|
||||||
|
</div>}/>
|
||||||
|
<div style={{flex:1, overflow:'auto', padding:'16px 24px'}}>
|
||||||
|
{sampleHistory.map((h, i) => (
|
||||||
|
<div key={i} style={{padding:'14px 18px', background:'var(--bg-card)', border:'1px solid var(--border-subtle)', borderRadius:16, marginBottom:10, cursor:'pointer'}}>
|
||||||
|
<div style={{display:'flex', justifyContent:'space-between', marginBottom:6}}>
|
||||||
|
<span style={{font:'500 12px var(--font-body)', color:'var(--text-tertiary)'}}>{h.date}</span>
|
||||||
|
<span style={{font:'400 11px var(--font-mono)', color:'var(--text-tertiary)'}}>{h.dur} · {h.words} words</span>
|
||||||
|
</div>
|
||||||
|
<p style={{font:'400 13px/1.55 var(--font-body)', color:'var(--text-secondary)', margin:0, maxWidth:'72ch'}}>{h.preview}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TasksPage({ tasks, onToggle }) {
|
||||||
|
return (
|
||||||
|
<div style={{display:'flex', flexDirection:'column', height:'100%'}}>
|
||||||
|
<PageHeader title="Tasks" subtitle="Extracted from what you said. Check them off when they're done."/>
|
||||||
|
<div style={{flex:1, padding:'16px 24px', overflow:'auto'}}>
|
||||||
|
<div style={{font:'500 10px var(--font-body)', color:'var(--text-tertiary)', letterSpacing:'.12em', textTransform:'uppercase', marginBottom:12}}>Inbox · {tasks.filter(t=>!t.done).length} open</div>
|
||||||
|
{tasks.map(t => (
|
||||||
|
<div key={t.id} onClick={()=>onToggle(t.id)}
|
||||||
|
style={{display:'flex', alignItems:'center', gap:12, padding:'12px 16px', background:'var(--bg-card)', border:'1px solid var(--border-subtle)', borderRadius:12, marginBottom:8, cursor:'pointer'}}>
|
||||||
|
<div style={{width:18, height:18, borderRadius:6, border: t.done ? 'none' : '1.5px solid var(--border)', background: t.done ? 'var(--accent)' : 'transparent', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0}}>
|
||||||
|
{t.done && <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#0f0e0c" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>}
|
||||||
|
</div>
|
||||||
|
<span style={{font:'400 14px var(--font-body)', color: t.done ? 'var(--text-tertiary)' : 'var(--text)', textDecoration: t.done ? 'line-through' : 'none', flex:1}}>{t.text}</span>
|
||||||
|
<span style={{font:'400 11px var(--font-mono)', color:'var(--text-tertiary)'}}>{t.from}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleFiles = [
|
||||||
|
{ name: 'onboarding-review.wav', size: '4.2 MB', dur: '01:42', date: '14 Apr' },
|
||||||
|
{ name: 'barbican-trip.wav', size: '1.8 MB', dur: '00:38', date: '14 Apr' },
|
||||||
|
{ name: 'copy-review.wav', size: '5.6 MB', dur: '02:15', date: '13 Apr' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function FilesPage() {
|
||||||
|
return (
|
||||||
|
<div style={{display:'flex', flexDirection:'column', height:'100%'}}>
|
||||||
|
<PageHeader title="Files" subtitle="Drop in audio to transcribe. Everything stays on this machine."
|
||||||
|
right={<button style={{display:'flex', alignItems:'center', gap:6, padding:'8px 14px', background:'var(--accent)', color:'#0f0e0c', border:'none', borderRadius:8, font:'500 13px var(--font-body)', cursor:'pointer', boxShadow:'0 4px 20px rgba(232,168,124,.3)'}}><Icon name="plus" size={14} stroke={2.5}/> New upload</button>}/>
|
||||||
|
<div style={{flex:1, padding:'18px 24px', overflow:'auto'}}>
|
||||||
|
<div style={{border:'1.5px dashed var(--border)', borderRadius:16, padding:'34px 22px', textAlign:'center', marginBottom:18, background:'rgba(27,26,23,.3)'}}>
|
||||||
|
<div style={{display:'flex', justifyContent:'center', color:'var(--text-tertiary)', marginBottom:10}}><Icon name="download" size={26} stroke={1.25}/></div>
|
||||||
|
<div style={{font:'500 14px var(--font-body)', color:'var(--text)', marginBottom:4}}>Drop audio files here</div>
|
||||||
|
<div style={{font:'400 12px var(--font-body)', color:'var(--text-tertiary)'}}>wav · mp3 · m4a · flac · ogg</div>
|
||||||
|
</div>
|
||||||
|
{sampleFiles.map((f, i) => (
|
||||||
|
<div key={i} style={{display:'flex', alignItems:'center', gap:14, padding:'12px 16px', background:'var(--bg-card)', border:'1px solid var(--border-subtle)', borderRadius:12, marginBottom:8}}>
|
||||||
|
<div style={{width:36, height:36, borderRadius:8, background:'var(--bg-elevated)', display:'flex', alignItems:'center', justifyContent:'center', color:'var(--accent)'}}><Icon name="play" size={16} stroke={2}/></div>
|
||||||
|
<div style={{flex:1}}>
|
||||||
|
<div style={{font:'500 13px var(--font-body)', color:'var(--text)'}}>{f.name}</div>
|
||||||
|
<div style={{font:'400 11px var(--font-mono)', color:'var(--text-tertiary)', marginTop:2}}>{f.dur} · {f.size} · {f.date}</div>
|
||||||
|
</div>
|
||||||
|
<button style={{padding:'6px 12px', background:'transparent', color:'var(--text-secondary)', border:'1px solid var(--border-subtle)', borderRadius:8, font:'500 12px var(--font-body)', cursor:'pointer'}}>Transcribe</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Toggle({ on, onChange }) {
|
||||||
|
return (
|
||||||
|
<button onClick={() => onChange(!on)}
|
||||||
|
style={{width:38, height:22, background: on ? 'var(--accent)' : 'var(--bg-elevated)', border:`1px solid ${on?'transparent':'var(--border-subtle)'}`, borderRadius:999, position:'relative', cursor:'pointer', transition:'all 150ms', padding:0}}>
|
||||||
|
<span style={{position:'absolute', top:2, left: on?18:2, width:16, height:16, borderRadius:999, background: on ? '#0f0e0c' : 'var(--text)', transition:'all 150ms'}}/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SettingsPage() {
|
||||||
|
const [s, setS] = useStateD({ saveAudio: true, british: false, reduceMotion: true, fillers: true });
|
||||||
|
const Row = ({ label, desc, k }) => (
|
||||||
|
<div style={{display:'flex', alignItems:'center', justifyContent:'space-between', padding:'14px 0', borderBottom:'1px solid var(--border-subtle)'}}>
|
||||||
|
<div><div style={{font:'500 14px var(--font-body)', color:'var(--text)'}}>{label}</div><div style={{font:'400 12px var(--font-body)', color:'var(--text-secondary)', marginTop:3, maxWidth:'54ch', lineHeight:1.5}}>{desc}</div></div>
|
||||||
|
<Toggle on={s[k]} onChange={v => setS({...s, [k]:v})}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div style={{display:'flex', flexDirection:'column', height:'100%'}}>
|
||||||
|
<PageHeader title="Settings"/>
|
||||||
|
<div style={{flex:1, overflow:'auto', padding:'20px 32px', maxWidth:680}}>
|
||||||
|
<div style={{font:'500 10px var(--font-body)', color:'var(--text-tertiary)', letterSpacing:'.12em', textTransform:'uppercase', marginBottom:6}}>Recording</div>
|
||||||
|
<Row label="Save recordings" desc="Keep the audio alongside each transcript. Useful if a transcription goes wrong — you can try again." k="saveAudio"/>
|
||||||
|
<Row label="Remove fillers" desc='Strip "um", "uh", "you know" automatically when finalising the transcript.' k="fillers"/>
|
||||||
|
<Row label="British English" desc="Favour British spellings: colour, organise, behaviour." k="british"/>
|
||||||
|
<div style={{font:'500 10px var(--font-body)', color:'var(--text-tertiary)', letterSpacing:'.12em', textTransform:'uppercase', margin:'28px 0 6px'}}>Accessibility</div>
|
||||||
|
<Row label="Reduce motion" desc="Cross-fades become instant. The waveform stops pulsing. Opt in to animation, not out." k="reduceMotion"/>
|
||||||
|
<div style={{marginTop:24, padding:'14px 18px', borderRadius:12, background:'rgba(232,168,124,.06)', border:'1px solid rgba(232,168,124,.2)'}}>
|
||||||
|
<div style={{font:'500 12px var(--font-body)', color:'var(--accent)', marginBottom:4}}>Everything stays on this machine.</div>
|
||||||
|
<div style={{font:'400 12px/1.55 var(--font-body)', color:'var(--text-secondary)'}}>Transcription runs locally. Audio never leaves your device unless you export it yourself.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(window, { HistoryPage, TasksPage, FilesPage, SettingsPage });
|
||||||
15
src/design-system/ui_kits/README.md
Normal file
15
src/design-system/ui_kits/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Kon app · UI kit
|
||||||
|
|
||||||
|
Desktop transcription app — local-first, neurodivergent-first, amber on warm black.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `index.html` — interactive demo: 5 navigable screens, live-ish record toggle, task checkoff.
|
||||||
|
- `Sidebar.jsx` — left nav + wordmark + status dot.
|
||||||
|
- `DictationPage.jsx` — main page: record button, waveform, timer, toolbar, transcript card.
|
||||||
|
- `OtherPages.jsx` — History, Tasks, Files, Settings.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
Open `index.html`. Click nav items to switch. Click the amber circle to toggle "recording" — the button pulses and the waveform bars animate. Check tasks off on the Tasks page.
|
||||||
|
|
||||||
|
## Notes on fidelity
|
||||||
|
Recreated from the Svelte source in `kon-source/`. The visual language, component shapes, copy tone and icon stroke weights match the app. Local transcription, model loading, Tauri IPC, and the real audio pipeline are stubbed — this kit is for visual + interaction reference.
|
||||||
77
src/design-system/ui_kits/Sidebar.jsx
Normal file
77
src/design-system/ui_kits/Sidebar.jsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Sidebar.jsx — nav + wordmark
|
||||||
|
const { useState } = React;
|
||||||
|
|
||||||
|
const Icon = ({ name, size = 16, stroke = 1.5, className = '' }) => {
|
||||||
|
const paths = {
|
||||||
|
mic: <><rect x="9" y="2" width="6" height="11" rx="3"/><path d="M5 10v2a7 7 0 0 0 14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/></>,
|
||||||
|
file: <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></>,
|
||||||
|
check: <><rect x="3" y="3" width="18" height="18" rx="2"/><path d="m9 12 2 2 4-4"/></>,
|
||||||
|
clock: <><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></>,
|
||||||
|
settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></>,
|
||||||
|
search: <><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></>,
|
||||||
|
copy: <><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></>,
|
||||||
|
x: <><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></>,
|
||||||
|
plus: <><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></>,
|
||||||
|
play: <polygon points="5 3 19 12 5 21 5 3"/>,
|
||||||
|
download: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></>,
|
||||||
|
chevLeft: <polyline points="15 18 9 12 15 6"/>,
|
||||||
|
alert: <><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></>,
|
||||||
|
};
|
||||||
|
return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" className={className}>{paths[name]}</svg>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NAV = [
|
||||||
|
{ id: 'dictation', label: 'Dictation', icon: 'mic' },
|
||||||
|
{ id: 'files', label: 'Files', icon: 'file' },
|
||||||
|
{ id: 'tasks', label: 'Tasks', icon: 'check' },
|
||||||
|
{ id: 'history', label: 'History', icon: 'clock' },
|
||||||
|
{ id: 'settings', label: 'Settings', icon: 'settings' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function Sidebar({ current, onNav, taskCount = 0, status = 'Ready', statusColor = '#7ec89a' }) {
|
||||||
|
return (
|
||||||
|
<aside style={{width:210, minWidth:210, background:'var(--sidebar)', borderRight:'1px solid var(--border-subtle)', display:'flex', flexDirection:'column', height:'100%'}}>
|
||||||
|
<div style={{display:'flex', alignItems:'baseline', gap:8, padding:'16px 20px 0'}}>
|
||||||
|
<h1 style={{font:'italic 400 26px/1 var(--font-display)', color:'var(--text)', margin:0, letterSpacing:'-.01em'}}>Kon</h1>
|
||||||
|
<span style={{fontSize:18, color:'var(--accent)'}}>෧</span>
|
||||||
|
</div>
|
||||||
|
<p style={{font:'500 10px/1.3 var(--font-body)', color:'var(--text-tertiary)', margin:'6px 20px 0', letterSpacing:'.12em', textTransform:'uppercase'}}>Think out loud</p>
|
||||||
|
<div style={{margin:'16px 20px', height:1, background:'var(--border-subtle)'}} />
|
||||||
|
|
||||||
|
<nav style={{display:'flex', flexDirection:'column', gap:2, padding:'0 12px'}}>
|
||||||
|
{NAV.map(item => {
|
||||||
|
const active = current === item.id;
|
||||||
|
return (
|
||||||
|
<button key={item.id} onClick={() => onNav(item.id)}
|
||||||
|
style={{display:'flex', alignItems:'center', gap:10, padding:'8px 12px', borderRadius:8, border:'none', cursor:'pointer', textAlign:'left',
|
||||||
|
background: active ? 'var(--nav-active)' : 'transparent',
|
||||||
|
color: active ? 'var(--text)' : 'var(--text-secondary)',
|
||||||
|
fontWeight: active ? 500 : 400, fontSize:13, fontFamily:'var(--font-body)'}}
|
||||||
|
onMouseEnter={e => !active && (e.currentTarget.style.background='var(--hover)')}
|
||||||
|
onMouseLeave={e => !active && (e.currentTarget.style.background='transparent')}>
|
||||||
|
<span style={{color: active ? 'var(--accent)' : 'var(--text-tertiary)', display:'flex'}}>
|
||||||
|
<Icon name={item.icon} stroke={active ? 2 : 1.5}/>
|
||||||
|
</span>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
{item.id === 'tasks' && taskCount > 0 && (
|
||||||
|
<span style={{marginLeft:'auto', font:'500 10px var(--font-body)', padding:'1px 7px', borderRadius:999, background:'rgba(232,168,124,.15)', color:'var(--accent)'}}>{taskCount}</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div style={{flex:1}} />
|
||||||
|
<div style={{margin:'0 20px 12px', height:1, background:'var(--border-subtle)'}} />
|
||||||
|
<div style={{padding:'0 20px 20px'}}>
|
||||||
|
<div style={{display:'flex', alignItems:'center', gap:8}}>
|
||||||
|
<span style={{width:7, height:7, borderRadius:999, background:statusColor, flexShrink:0}}/>
|
||||||
|
<span style={{font:'400 11px var(--font-body)', color:'var(--text-secondary)'}}>{status}</span>
|
||||||
|
</div>
|
||||||
|
<p style={{font:'400 10px var(--font-body)', color:'var(--text-tertiary)', margin:'6px 0 0'}}>Clean · v0.1</p>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(window, { Sidebar, Icon });
|
||||||
148
src/design-system/ui_kits/index.html
Normal file
148
src/design-system/ui_kits/index.html
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Kon · UI kit</title>
|
||||||
|
<link rel="stylesheet" href="../../colors_and_type.css">
|
||||||
|
<style>
|
||||||
|
html, body { margin:0; height:100%; background:#0b0a08; font-family:var(--font-body); color:var(--text); }
|
||||||
|
body { display:flex; align-items:center; justify-content:center; padding:28px; }
|
||||||
|
.frame {
|
||||||
|
width: 1080px; height: 720px; max-width:100%; max-height:calc(100vh - 56px);
|
||||||
|
background: var(--bg);
|
||||||
|
border-radius: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 24px 80px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,.04);
|
||||||
|
display:flex; flex-direction:column;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
.titlebar {
|
||||||
|
height: 36px; flex-shrink:0;
|
||||||
|
background: #0c0b09;
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
display:flex; align-items:center; padding: 0 14px; gap: 8px;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
.lights { display:flex; gap:8px; }
|
||||||
|
.light { width:12px; height:12px; border-radius:999px; }
|
||||||
|
.lc { background:#ff5f57; } .la { background:#ffbd2e; } .lg { background:#28c840; }
|
||||||
|
.title {
|
||||||
|
flex:1; text-align:center;
|
||||||
|
font: 500 12px var(--font-body);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
letter-spacing: .02em;
|
||||||
|
}
|
||||||
|
.shell { flex:1; display:flex; min-height:0; }
|
||||||
|
.main { flex:1; display:flex; flex-direction:column; min-width:0; background: var(--bg); position: relative; }
|
||||||
|
|
||||||
|
/* Grain */
|
||||||
|
.frame::after {
|
||||||
|
content:''; position:absolute; inset:0; pointer-events:none; z-index:50;
|
||||||
|
background-image: url('../../assets/grain.svg');
|
||||||
|
background-size: 200px 200px; opacity: .025; mix-blend-mode: overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes konPulse {
|
||||||
|
0%,100% { box-shadow: 0 0 0 0 rgba(232,113,113,.4); }
|
||||||
|
50% { box-shadow: 0 0 0 8px rgba(232,113,113,0); }
|
||||||
|
}
|
||||||
|
@keyframes konBar {
|
||||||
|
0%,100% { transform: scaleY(.5); opacity:.6; }
|
||||||
|
50% { transform: scaleY(1); opacity:1; }
|
||||||
|
}
|
||||||
|
@keyframes konFade { from { opacity: 0 } to { opacity: 1 } }
|
||||||
|
[data-fade] { animation: konFade 300ms cubic-bezier(.2,.8,.2,1); }
|
||||||
|
|
||||||
|
button:focus-visible, input:focus-visible, textarea:focus-visible {
|
||||||
|
outline: none; box-shadow: 0 0 0 3px var(--accent-glow);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<script type="text/babel" src="./Sidebar.jsx"></script>
|
||||||
|
<script type="text/babel" src="./DictationPage.jsx"></script>
|
||||||
|
<script type="text/babel" src="./OtherPages.jsx"></script>
|
||||||
|
|
||||||
|
<script type="text/babel">
|
||||||
|
const { useState, useEffect, useRef } = React;
|
||||||
|
|
||||||
|
function pad(n){ return String(n).padStart(2,'0'); }
|
||||||
|
|
||||||
|
function KonApp() {
|
||||||
|
const [current, setCurrent] = useState('dictation');
|
||||||
|
const [recording, setRecording] = useState(false);
|
||||||
|
const [seconds, setSeconds] = useState(0);
|
||||||
|
const [transcript, setTranscript] = useState(
|
||||||
|
`Okay so the real issue is the first-run screen assumes you know what dictation means. We should lead with press and hold, then speak — that's the whole thing. Once someone has said one sentence, everything else falls into place.`
|
||||||
|
);
|
||||||
|
const [tasks, setTasks] = useState([
|
||||||
|
{ id:1, text:'Rewrite first-run screen copy to lead with "press and hold, then speak"', from:'14 Apr', done:false },
|
||||||
|
{ id:2, text:'Move mic permission flow before the model download step', from:'14 Apr', done:false },
|
||||||
|
{ id:3, text:'Design the warm empty state for History', from:'13 Apr', done:true },
|
||||||
|
{ id:4, text:'Pick elderflower cordial up from Columbia Road', from:'13 Apr', done:false },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const intRef = useRef(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (recording) {
|
||||||
|
intRef.current = setInterval(() => setSeconds(s => s + 1), 1000);
|
||||||
|
} else {
|
||||||
|
clearInterval(intRef.current);
|
||||||
|
}
|
||||||
|
return () => clearInterval(intRef.current);
|
||||||
|
}, [recording]);
|
||||||
|
|
||||||
|
const timer = `${pad(Math.floor(seconds/60))}:${pad(seconds%60)}`;
|
||||||
|
const taskCount = tasks.filter(t => !t.done).length;
|
||||||
|
const status = recording ? 'Recording' : 'Ready';
|
||||||
|
const statusColor = recording ? 'var(--danger)' : 'var(--success)';
|
||||||
|
|
||||||
|
const onExtractTasks = () => {
|
||||||
|
const t = { id: Date.now(), text: 'Review the onboarding flow end-to-end', from: 'just now', done: false };
|
||||||
|
setTasks([t, ...tasks]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClear = () => { setTranscript(''); setSeconds(0); };
|
||||||
|
|
||||||
|
let page;
|
||||||
|
if (current === 'dictation') {
|
||||||
|
page = <DictationPage recording={recording} onToggle={()=>setRecording(r=>!r)}
|
||||||
|
timer={timer} transcript={transcript} onTranscript={setTranscript}
|
||||||
|
status={status} statusColor={statusColor}
|
||||||
|
onExtractTasks={onExtractTasks} onClear={onClear}/>;
|
||||||
|
} else if (current === 'history') {
|
||||||
|
page = <HistoryPage/>;
|
||||||
|
} else if (current === 'tasks') {
|
||||||
|
page = <TasksPage tasks={tasks} onToggle={id => setTasks(tasks.map(t => t.id===id ? {...t, done:!t.done} : t))}/>;
|
||||||
|
} else if (current === 'files') {
|
||||||
|
page = <FilesPage/>;
|
||||||
|
} else {
|
||||||
|
page = <SettingsPage/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="frame">
|
||||||
|
<div className="titlebar">
|
||||||
|
<div className="lights"><div className="light lc"/><div className="light la"/><div className="light lg"/></div>
|
||||||
|
<div className="title">Kon</div>
|
||||||
|
<div style={{width:54}}/>
|
||||||
|
</div>
|
||||||
|
<div className="shell">
|
||||||
|
<Sidebar current={current} onNav={setCurrent} taskCount={taskCount} status={status} statusColor={statusColor}/>
|
||||||
|
<div className="main" data-fade key={current}>{page}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(<KonApp/>);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
src/fonts/atkinson-hyperlegible-next.woff2
Normal file
BIN
src/fonts/atkinson-hyperlegible-next.woff2
Normal file
Binary file not shown.
BIN
src/fonts/instrument-serif-italic.woff2
Normal file
BIN
src/fonts/instrument-serif-italic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/jetbrains-mono.woff2
Normal file
BIN
src/fonts/jetbrains-mono.woff2
Normal file
Binary file not shown.
BIN
src/fonts/lexend-variable.woff2
Normal file
BIN
src/fonts/lexend-variable.woff2
Normal file
Binary file not shown.
BIN
src/fonts/opendyslexic.woff2
Normal file
BIN
src/fonts/opendyslexic.woff2
Normal file
Binary file not shown.
Reference in New Issue
Block a user