fix(app): iOS HIG Phase 1 — input font sizes and text minimums

- Replace all text-[10px] (308 instances) with text-xs (12px)
- Replace all text-[9px] and text-[8px] (133 instances) with text-xs
- Replace all text-[11px] (76 instances) with text-xs
- Bump all input/textarea font sizes to text-base (16px) to prevent iOS auto-zoom
- No visual design changes — only sizing minimums enforced

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 12:53:25 +00:00
co-authored by Claude Opus 4.6
parent dde3859a2e
commit b2fcc23623
97 changed files with 5561 additions and 564 deletions
+61
View File
@@ -0,0 +1,61 @@
# AIUI Project Memory
## Session Startup
1. Run `preview_start` with name `"app"` immediately — runs both Vite (:5173) + Claude proxy (:3141) via `packages/app/scripts/dev.sh`
2. Always commit work before ending a session
3. Work on `development` branch, merge to `main` only when production ready
## User Preferences
- NO worktrees, NO temporary branches — just `development` and `main`
- Always use combined dev script (proxy + frontend), never bare `vite`
- Commit frequently to avoid losing work
## Current State (2026-03-04)
- Branch: `overnight/2026-03-03`, all committed and pushed to remote (git.tx1138.com)
- Typecheck passes clean
## What's Been Built
- Chat: AI streaming with stop generation, web search, article integration, paste & extract
- Content panel tabs: Films, Music, Magazine, News, Books, TV Series, Images, Places, Code, Design System, Nostr, **Apps**
- Detail views for each content type (side-by-side desktop, overlay mobile)
- **Apps tab**: curated DB of ~30 Nostr/Bitcoin apps, AppsGrid + AppDetail with search/category filtering/how-to
- Design system viewer (grid + detail) for tokens, colors, typography, components
- Nostr feed scaffold with note/article/zap filtering
- Content extraction: contentExtraction.ts + contentFiltering.ts (overhauled classifiers)
- **Bare domain extraction** from AI text (e.g. "check out damus.io")
- Banner fallback composable (primary → API → gradient)
- Image fallbacks: Wikipedia + Google Books sources
- Loading skeletons per content type variant
- Project grid with breadcrumb nav and inline creation
- Filesystem Vite plugin for local project browsing
- PWA with star icon, TMDB proxy, Jamendo for music
- **Slash command palette**: /code, /nostr, /design, /search show in palette with auto-send
- **Chat action buttons**: wrapped in glass container (backdrop blur, border, shadow)
- **Settings modal**: Memory + Advanced Settings via gear icon
- **Chat history**: dedicated clock icon button
- **Web search**: Brave API primary, SearXNG rotation fallback, DuckDuckGo fallback
- iOS HIG mobile UX rules in `.cursor/rules/15-mobile-ux.mdc`
## Key Files
- Dev script: `packages/app/scripts/dev.sh`
- Launch config: `.claude/launch.json` (name: "app")
- Main page: `packages/app/src/pages/ChatPage.vue`
- Content panel: `packages/app/src/components/content/ContentPanel.vue`
- Content grids: `packages/app/src/components/content/*Grid.vue`
- Detail views: `packages/app/src/components/content/*Detail.vue`
- **Apps**: `packages/app/src/data/apps.ts` (curated DB), `AppsGrid.vue`, `AppDetail.vue`
- AI composable: `packages/app/src/composables/useAI.ts`
- Content extraction: `packages/app/src/composables/contentExtraction.ts`
- Content filtering: `packages/app/src/composables/contentFiltering.ts`
- Content panel logic: `packages/app/src/composables/useContentPanel.ts`
- Image fallbacks: `packages/app/src/composables/useImageFallback.ts`
- Banner fallback: `packages/app/src/composables/useBannerFallback.ts`
- Chat input: `packages/app/src/components/chat/ChatInput.vue`
- Prompt palette: `packages/app/src/components/chat/PromptPalette.vue`
- Chat message: `packages/app/src/components/chat/ChatMessage.vue`
- Settings modal: `packages/app/src/components/chat/SettingsModal.vue`
- Web search plugin: `packages/app/vite-web-search.ts`
- Prompt templates store: `packages/app/src/stores/promptTemplates.ts`
## Recent Session Work (2026-03-04)
See `session-2026-03-04.md` for details.
+18
View File
@@ -0,0 +1,18 @@
# Code Mode UI — Future Work
## After content surfacing is complete, implement:
### 1. Code Mode Visual Treatment
- Colour the message container in orange (`#F7931A`) styling when in code mode
- Change header text from "Message AIUI" to "Code"
- Visual signal so user knows they're in coding context
### 2. Design System Context Selection
- All design system items should be selectable with a cursor/pointer icon on hover
- Selecting a design system item provides that UI context to the code generation
- Think of it as "code with this component/token in mind"
### 3. File Browser / Open File Context
- File browser or open file in the content panel
- Selected files provide context for coding
- Pairs with the design system selection — user picks UI + files as coding context
+66
View File
@@ -0,0 +1,66 @@
# Session 2026-03-04
## Completed This Session
### 1. Chat UX Changes
- **History button**: Changed from title-click dropdown to dedicated clock icon in ChatHeader
- **Settings modal**: Created `SettingsModal.vue` — Memory + Advanced Settings behind gear icon, glass-card with backdrop blur
- **PromptIndex fix**: Reverted to original behavior (current conversation only), fixed broken v-if/v-else chain where StreamingDots broke the template chain
- **Chat action buttons**: Wrapped hover icons in proper glass container (`bg-black/60 backdrop-blur-md border border-white/10`) with divider between actions and thumbs
### 2. iOS HIG Integration
- Created `.cursor/rules/15-mobile-ux.mdc` with comprehensive iOS HIG values
- Updated CLAUDE.md Mobile UX section
### 3. Web Search Fix
- All SearXNG instances were returning 429, DuckDuckGo rate-limiting
- Added Brave Search API as primary backend (`BRAVE_SEARCH_API_KEY` env var)
- Expanded SearXNG pool to 8 instances with rotation
- Added HTML response guard for captcha pages
### 4. Content Detection Overhaul (MAJOR)
- **Expanded all classifiers** in `contentFiltering.ts`: isNewsQuery, isMusicQuery, isBookQuery, isTVQuery, isPlaceQuery, isWebsitesQuery + response variants
- **Added Nostr detection**: `isNostrQuery()`, `isNostrLikeResponse()`
- **Added App detection**: `isAppQuery()`, `isAppLikeResponse()`
- **Updated `filterTabsByContext()`**: new `hasNostr` + `hasApps` params, nostr/app query priority
- **Updated `preferredFirstTab()`**: nostr + app checks
### 5. Bare Domain Extraction
- `extractBareDomainLinks(text)` in contentExtraction.ts
- Detects plain domains like "damus.io" not inside markdown/bold/URL patterns
- Known TLDs whitelist, file extension blacklist
### 6. Apps Tab (NEW FEATURE)
- **Database**: `packages/app/src/data/apps.ts` — AppEntry interface, ~30 curated apps
- Nostr clients: Damus, Primal, Snort, Amethyst, Coracle, Iris, noStrudel
- Lightning wallets: Phoenix, Breez, Zeus, Alby, Mutiny, WoS
- Bitcoin wallets: Sparrow, BlueWallet, Nunchuk, Coldcard
- Privacy: SimpleX Chat, Signal, Mullvad VPN
- Node software: Start9, Umbrel, RaspiBlitz, myNode
- Dev tools: NDK, nostr-tools, Nak
- **Extraction**: `extractApps(text, userQuery)` — keyword matching against DB, surfaces with 1+ match for app/nostr/known-app queries, 2+ for general
- **UI**: `AppsGrid.vue` (list with search/category filter), `AppDetail.vue` (gradient header, how-to steps, related apps, external link)
- **Wired in**: useContentPanel.ts (panelApps ref, selectedApp, open/close), ContentPanel.vue (registered), PromptIndex badges
### 7. Slash Command Palette
- `/code`, `/nostr`, `/design`, `/search` appear as commands in PromptPalette
- Commands section above Templates section with `/slash` prefix styling
- Auto-send on select (except `/search` which sets text for query input)
- 8px side margins (`left-2 right-2`), no max-height scroll limit
- `ChatInput.vue`: simplified `isPaletteMode` — no longer excludes command names
### 8. App Detection Fix
- Queries mentioning known app names (e.g. "start9") now match via `queryMatchesApp` check
- Previously required explicit app/nostr query patterns like "what app" or "best wallet"
## Known Issues / TODO for Next Session
- User reported "start9" search shows Brief but Apps tab was empty — FIXED in last commit
- The `/design` command was added to palette and ChatWindow handleSend
- Consider adding more apps to the curated database over time
- The plan file is at `.claude/plans/content-detection-overhaul.md` (all steps complete)
## Git State
- Branch: `overnight/2026-03-03`
- Latest commit: `f346992` — feat(chat): slash command palette, action button containers, app detection fix
- Previous commit: `84ccdc7` — feat(app): content detection overhaul, apps tab, chat UX, web search
- All pushed to origin
+160
View File
@@ -0,0 +1,160 @@
# Plan: Overhaul Content Detection + Add Apps Tab
## Context
The content surfacing system misses many common AI response patterns. Example: AI responds about Nostr (mentioning damus.io, primal.net, snort.social) but the Nostr tab never surfaces. Query/response classifiers use narrow regexes that miss natural language variations. There's no "topic detection" layer, no app detection, and bare domains in AI text aren't extracted as websites.
**Goals:**
1. Fix content detection to handle how AIs actually respond
2. Add Nostr tab surfacing (currently only via `/nostr` command)
3. Add Apps tab with curated Nostr + Bitcoin ecosystem apps (local DB + AI extraction fallback)
4. Extract bare domains from AI text (e.g. "check out damus.io")
---
## Part 1: Expand Query & Response Classifiers
**File:** `packages/app/src/composables/contentFiltering.ts`
### 1A. Add Nostr classifiers (new functions)
- `isNostrQuery(q)` — matches: nostr, npub, nip-\d, damus, primal, snort, amethyst, coracle, zap, relay, note1, nevent, nprofile, fiatjaf, nostrich, "decentralized social"
- `isNostrLikeResponse(text)` — requires literal "nostr" OR 2+ Nostr-specific signals (npub, nip-, client names, relay+wss, zap+lightning)
### 1B. Add App classifiers (new functions)
- `isAppQuery(q)` — matches: app, client, wallet, tool, software, download, install, "what app", "best app for", "recommend.*app"
- `isAppLikeResponse(text)` — matches: "you can use", "popular clients include", "I'd recommend", "available on", "download from"
### 1C. Expand existing classifiers with broader patterns
| Classifier | Add these patterns |
|---|---|
| `isNewsQuery` | "what happened today", "any updates on", "trending", "catch me up", "brief me", "current events" |
| `isMusicQuery` | "genre", "spotify", "bandcamp", "grammys", "billboard", "mixtape", "discography", "banger", "favorite jam" |
| `isBookQuery` | "what should I read", "favorite reads", "reading list", "book club", "memoir", "audiobook", "goodreads", "worth reading" |
| `isTVQuery` | "what's good on netflix", "anything to binge", "hbo", "disney+", "apple tv", "amazon prime", "docuseries", "limited series" |
| `isPlaceQuery` | "hungry", "food near me", "best brunch spot", "happy hour", "speakeasy", "rooftop bar", "food truck" |
| `isWebsitesQuery` | "point me to", "link me", "any good sites", "tools for", "platforms for" |
| `isWebsitesLikeResponse` | "here are some resources", "I'd recommend checking", "you can visit", "useful resources" |
| `isNewsLikeResponse` | "I can't access the web but", "having trouble reaching", "unable to browse but" |
### 1D. Update `preferredFirstTab()` — add nostr + app checks
### 1E. Update `filterTabsByContext()` — add `hasNostr` and `hasApps` params, integrate into tab ordering
---
## Part 2: Bare Domain Extraction
**File:** `packages/app/src/composables/contentExtraction.ts`
Add `extractBareDomainLinks(text)`:
- Detect plain-text domains like "damus.io", "primal.net" not inside markdown links or bold patterns
- Skip positions covered by existing extractors (markdown links, bold-domain, full URLs)
- Require known TLDs (.com, .org, .io, .net, .social, .app, etc.)
- Block file extensions (.js, .ts, .vue, .json, .css)
- Use existing `normUrl()` for dedup
---
## Part 3: Apps Tab — Curated Database + AI Extraction
### 3A. Create app database
**New file:** `packages/app/src/data/apps.ts`
```ts
interface AppEntry {
id: string
name: string
description: string // One-liner
longDescription: string // Why use this, how it works
category: 'nostr-client' | 'lightning-wallet' | 'bitcoin-wallet' | 'privacy' | 'node' | 'dev-tool' | 'relay'
platforms: ('ios' | 'android' | 'web' | 'desktop' | 'cli' | 'nodeos')[]
url: string
icon?: string
keywords: string[] // For matching AI responses
howTo?: string[] // Getting started steps
relatedApps?: string[] // IDs of related apps
}
```
**Initial curated apps (~25-30):**
- Nostr clients: Damus, Primal, Snort, Amethyst, Coracle, Iris, Nostrudel, nos.social
- Lightning wallets: Phoenix, Mutiny, Breez, Zeus, Alby, Wallet of Satoshi
- Bitcoin wallets: Sparrow, Blue Wallet, Nunchuk, Coldcard, Green
- Privacy tools: Tor, SimpleX Chat, Signal, Mullvad VPN
- Node software: Start9, Umbrel, RaspiBlitz, myNode
- Dev tools: NDK, nostr-tools, Nak
### 3B. Add app extraction
**File:** `packages/app/src/composables/contentExtraction.ts`
Add `extractApps(text, userQuery)`:
1. Match AI text against known app names/keywords from database
2. If app query detected OR 2+ known apps mentioned → return matched apps
3. For unknown apps, create basic entries from context (name + URL if bare domain found)
### 3C. Create UI components
**New files:**
- `packages/app/src/components/content/AppsGrid.vue` — Grid of app cards (icon, name, category badge, one-liner)
- `packages/app/src/components/content/AppDetail.vue` — Detail: icon, name, platforms, long description, how-to steps, link, related apps
Follow existing grid/detail patterns (e.g. `BookGrid.vue`/`BookDetail.vue`).
### 3D. Register in ContentPanel.vue
Add rendering for `activeTab === 'app'`, add `'app'` to `ContentTab` type.
---
## Part 4: Wire Everything Together
**File:** `packages/app/src/composables/useContentPanel.ts`
In `updatePanelFromText()`:
- Call `extractBareDomainLinks(text)`, merge with website sources
- Call `extractApps(text, userQuery)`
- Compute `hasNostr = isNostrQuery(userQuery) || isNostrLikeResponse(text)`
- Compute `hasApps = apps.length > 0`
- Pass `hasNostr` and `hasApps` to `filterTabsByContext()`
- Add `panelApps` ref, title logic for apps/nostr tabs
Same changes in `getContextualInlineContent()`.
Broaden magazine detection: add tech/protocol keywords, surface magazine for 3+ sections with no other structured content.
---
## Part 5: PromptIndex badges
**File:** `packages/app/src/components/chat/PromptIndex.vue`
Add 'Nostr' and 'Apps' badge detection.
---
## Implementation Order
1. `contentFiltering.ts` — classifiers + filterTabsByContext signature
2. `contentExtraction.ts``extractBareDomainLinks()` + `extractApps()`
3. `data/apps.ts` — curated app database
4. `useContentPanel.ts` — wire everything
5. `AppsGrid.vue` + `AppDetail.vue` — UI components
6. `ContentPanel.vue` — register tab + components
7. `PromptIndex.vue` — badges
8. Typecheck + manual test
## Verification
1. `pnpm typecheck` passes
2. "tell me about Nostr" → Nostr + magazine tabs surface
3. "best Nostr clients?" → Apps tab with Damus, Primal, Snort
4. "recommend a bitcoin wallet" → Apps tab with Phoenix, Sparrow
5. "what happened with BIP 110?" → Magazine tab (regression)
6. "best movies of 2024" → Films tab (regression)
7. Bare domains in AI text extracted as websites
8. PromptIndex badges show Nostr/Apps
+74
View File
@@ -0,0 +1,74 @@
# Plan: Code Mode UI — Orange Input, Design System Context, File Browser Context
## Context
The user wants three connected features that enhance the coding experience in AIUI:
1. Visual indication when in code mode (orange input container, "Code" label)
2. Ability to select design system items as coding context
3. Ability to select files from file browser as coding context
After this, the user wants to circle back and create a flawless version of content extraction/tab surfacing.
## Changes
### 1. Orange Code Mode Input Container
**Files**: `ChatWindow.vue`, `ChatInput.vue`
**ChatWindow.vue** (line 106-115):
- Pass `activeTab` to ChatInput as a prop: `:active-tab="activeTab"`
- Change placeholder logic: `activeTab === 'code' ? 'Code...' : isStreaming ? 'Waiting for response...' : 'Message AIUI...'`
**ChatInput.vue**:
- Add `activeTab` prop (optional string, default `''`)
- Conditionally style the container div (line 79-81):
- When `activeTab === 'code'`: use `bg-accent/15 border border-accent/25 backdrop-blur-xl` instead of `path-glass-bubble`
- Keep the `rounded-2xl px-4 py-3 flex items-end gap-2 transition-all duration-300` classes
- Conditionally style the send button orange when in code mode
### 2. Design System Item Selection for Coding Context
**Files**: `useCodeContext.ts`, `DesignSystemGrid.vue`
**useCodeContext.ts**:
- Add `selectedDesignTokens: ref<string[]>([])` to module state (stores item IDs)
- Add `toggleDesignToken(id)` — adds/removes from selection array
- Add `clearDesignTokens()` — clears selection
- Add `isDesignTokenSelected(id)` — checks if item is in selection
- Clear on `exitCodeMode()`
- Export all new state/actions
**DesignSystemGrid.vue**:
- Import `useCodeContext`
- When `codeMode` is true, show a selection indicator (accent ring + checkmark) on items
- `selectItem` should call `toggleDesignToken(item.id)` when in code mode (instead of `openDesignSystemItem`)
- When NOT in code mode, keep existing behavior (open detail view)
- Selected items get `ring-2 ring-accent/50 bg-accent/10` styling
### 3. File Browser Selection for Coding Context
**Files**: `useCodeContext.ts`, `ProjectGrid.vue`
**useCodeContext.ts**:
- Add `selectedFiles: ref<string[]>([])` — paths of files selected for context
- Add `toggleFileSelection(path)` — adds/removes from selection
- Add `clearFileSelection()` — clears all
- Add `isFileSelected(path)` — checks if file in selection
- Clear on `exitCodeMode()`
- Export new state/actions
**ProjectGrid.vue**:
- Import `useCodeContext`
- When `codeMode` is true, file clicks toggle selection instead of (or in addition to) opening
- Show visual selection state (accent highlight/checkmark) on selected files in FileTreeNode
## Files to Modify
1. `packages/app/src/components/chat/ChatWindow.vue` — pass activeTab prop
2. `packages/app/src/components/chat/ChatInput.vue` — conditional orange styling + "Code" placeholder
3. `packages/app/src/composables/useCodeContext.ts` — add design token + file selection state
4. `packages/app/src/components/content/DesignSystemGrid.vue` — toggle selection in code mode
5. `packages/app/src/components/content/ProjectGrid.vue` — toggle file selection in code mode
## Verification
1. `pnpm typecheck` — no type errors
2. `pnpm lint` — no new lint errors
3. Manual: `/code` command → input turns orange with "Code..." placeholder
4. Manual: In code mode, design system tab → clicking items toggles selection (accent ring)
5. Manual: In code mode, file browser → clicking files toggles selection
6. Manual: Exiting code mode clears all selections
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -82,7 +82,7 @@ define(['./workbox-f97094b3'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.0t39d8ip1tk"
"revision": "0.b6ot7ui3o7c"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
<template>
<div class="px-3 md:px-4 pb-1">
<button
class="flex items-center gap-1.5 text-[11px] text-white/40 hover:text-white/60 transition-colors"
class="flex items-center gap-1.5 text-xs text-white/40 hover:text-white/60 transition-colors"
@click="isExpanded = !isExpanded"
>
<svg
@@ -19,8 +19,8 @@
<!-- Temperature -->
<div class="space-y-1">
<div class="flex items-center justify-between">
<label class="text-[11px] text-white/40">Temperature</label>
<span class="text-[11px] text-white/50 tabular-nums">{{ temperature.toFixed(2) }}</span>
<label class="text-xs text-white/40">Temperature</label>
<span class="text-xs text-white/50 tabular-nums">{{ temperature.toFixed(2) }}</span>
</div>
<input
v-model.number="temperature"
@@ -36,8 +36,8 @@
<!-- Max Tokens -->
<div class="space-y-1">
<div class="flex items-center justify-between">
<label class="text-[11px] text-white/40">Max Tokens</label>
<span class="text-[11px] text-white/50 tabular-nums">{{ maxTokens }}</span>
<label class="text-xs text-white/40">Max Tokens</label>
<span class="text-xs text-white/50 tabular-nums">{{ maxTokens }}</span>
</div>
<input
v-model.number="maxTokens"
@@ -53,8 +53,8 @@
<!-- Top P -->
<div class="space-y-1">
<div class="flex items-center justify-between">
<label class="text-[11px] text-white/40">Top P</label>
<span class="text-[11px] text-white/50 tabular-nums">{{ topP.toFixed(2) }}</span>
<label class="text-xs text-white/40">Top P</label>
<span class="text-xs text-white/50 tabular-nums">{{ topP.toFixed(2) }}</span>
</div>
<input
v-model.number="topP"
@@ -69,12 +69,12 @@
<!-- Stop Sequences (M9.10) -->
<div class="space-y-1">
<label class="text-[11px] text-white/40">Stop Sequences</label>
<label class="text-xs text-white/40">Stop Sequences</label>
<div v-if="stopSequences.length > 0" class="flex gap-1 flex-wrap mb-1">
<span
v-for="(seq, i) in stopSequences"
:key="i"
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-white/5 border border-white/10 text-[10px] text-white/50"
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-white/5 border border-white/10 text-xs text-white/50"
>
{{ seq }}
<button class="text-white/30 hover:text-white/60" @click="removeStopSequence(i)">×</button>
@@ -83,7 +83,7 @@
<input
v-model="newStopSeq"
type="text"
class="w-full bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
class="w-full bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-base text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
placeholder="Add stop sequence (Enter to add)"
@keydown.enter="addStopSequence"
/>
@@ -91,7 +91,7 @@
<!-- Reset -->
<button
class="text-[11px] text-white/30 hover:text-white/50 transition-colors"
class="text-xs text-white/30 hover:text-white/50 transition-colors"
@click="resetDefaults"
>
Reset to defaults
@@ -3,7 +3,7 @@
v-if="branches.length > 1"
class="flex items-center justify-center px-4 py-1.5 animate-fade-up-fast"
>
<div class="glass-button-sm !h-7 !min-h-0 flex items-center gap-1.5 px-2 text-[11px]">
<div class="glass-button-sm !h-7 !min-h-0 flex items-center gap-1.5 px-2 text-xs">
<button
class="w-5 h-5 flex items-center justify-center rounded-md hover:bg-white/10 transition-colors text-white/60 hover:text-white/90 disabled:opacity-30 disabled:cursor-default"
:disabled="currentIndex <= 0"
@@ -27,7 +27,7 @@
<!-- Mint info -->
<div
class="text-[10px] font-mono"
class="text-xs font-mono"
:class="isDark ? 'text-white/30' : 'text-gray-400'"
>
Mint: {{ displayMint }}
@@ -45,7 +45,7 @@
<!-- Actions -->
<div class="flex gap-2">
<button
class="flex-1 px-3 py-1.5 rounded-lg text-[10px] font-medium transition-colors"
class="flex-1 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
:class="isDark
? 'bg-white/5 text-white/60 hover:bg-white/10'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
@@ -54,7 +54,7 @@
{{ copied ? 'Copied!' : 'Copy Token' }}
</button>
<button
class="flex-1 px-3 py-1.5 rounded-lg text-[10px] font-medium transition-colors bg-[#F7931A]/10 text-[#F7931A] hover:bg-[#F7931A]/20"
class="flex-1 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors bg-[#F7931A]/10 text-[#F7931A] hover:bg-[#F7931A]/20"
@click="openInWallet"
>
Open in Wallet
@@ -122,9 +122,9 @@
<div class="w-full text-left pt-3 pb-1 min-w-0">
<h2 class="text-sm font-semibold truncate text-white/96">{{ title }}</h2>
<div class="flex items-center gap-1.5 mt-0.5">
<p class="text-[10px] truncate font-mono text-white/40">{{ conversationId }}</p>
<span class="text-[10px] text-white/20">·</span>
<span class="text-[10px] truncate text-white/50">{{ modelDisplayName }}</span>
<p class="text-xs truncate font-mono text-white/40">{{ conversationId }}</p>
<span class="text-xs text-white/20">·</span>
<span class="text-xs truncate text-white/50">{{ modelDisplayName }}</span>
</div>
</div>
@@ -138,7 +138,7 @@
@click.stop
>
<div v-for="provider in availableProviders" :key="provider.id">
<p class="text-[10px] font-semibold uppercase tracking-wider mb-1.5 px-1 text-white/40">
<p class="text-xs font-semibold uppercase tracking-wider mb-1.5 px-1 text-white/40">
{{ provider.name }}
</p>
<div class="space-y-0.5">
@@ -155,17 +155,17 @@
<span class="flex gap-0.5 shrink-0">
<span
v-if="getModelCaps(model.id).vision"
class="text-[10px] opacity-60"
class="text-xs opacity-60"
title="Supports vision input"
>👁</span>
<span
v-if="getModelCaps(model.id).tools"
class="text-[10px] opacity-60"
class="text-xs opacity-60"
title="Supports tool use"
>🔧</span>
<span
v-if="getModelCaps(model.id).longContext"
class="text-[10px] opacity-60"
class="text-xs opacity-60"
title="Long context window"
>📄</span>
</span>
@@ -198,7 +198,7 @@
:style="menuDropdownStyle"
@click.stop
>
<p class="text-[10px] font-semibold uppercase tracking-wider mb-1.5 px-2 text-white/40">Export</p>
<p class="text-xs font-semibold uppercase tracking-wider mb-1.5 px-2 text-white/40">Export</p>
<button
class="w-full text-left px-3 py-2 rounded-lg text-xs text-white/60 hover:text-white hover:bg-white/10 transition-all"
@click="handleExport('markdown')"
@@ -25,11 +25,11 @@
{{ conv.title || 'Untitled' }}
</p>
<div class="flex items-center gap-1.5 mt-1">
<span class="text-[10px] text-white/30">
<span class="text-xs text-white/30">
{{ formatTime(conv.updatedAt) }}
</span>
<span class="text-[10px] text-white/20">&middot;</span>
<span class="text-[10px] text-white/30">
<span class="text-xs text-white/20">&middot;</span>
<span class="text-xs text-white/30">
{{ conv.messages.length }} msg{{ conv.messages.length !== 1 ? 's' : '' }}
</span>
</div>
@@ -18,7 +18,7 @@
class="mb-2 flex items-start gap-2 rounded-xl bg-white/5 border border-white/10 px-3 py-2 animate-fade-up-fast"
>
<div class="flex-1 min-w-0">
<p class="text-[10px] text-accent/70 font-medium mb-0.5">Replying to</p>
<p class="text-xs text-accent/70 font-medium mb-0.5">Replying to</p>
<p class="text-xs text-white/50 truncate">{{ replyTo.excerpt }}</p>
</div>
<button
@@ -54,7 +54,7 @@
</svg>
</button>
</div>
<span v-if="images.length >= MAX_IMAGES" class="self-center text-[10px] text-white/30">
<span v-if="images.length >= MAX_IMAGES" class="self-center text-xs text-white/30">
Max {{ MAX_IMAGES }} images
</span>
</div>
@@ -102,7 +102,7 @@
v-model="text"
rows="1"
:placeholder="placeholder"
class="flex-1 resize-none bg-transparent text-sm outline-none min-h-[24px] max-h-[120px] text-white/90 placeholder:text-white/25"
class="flex-1 resize-none bg-transparent text-base outline-none min-h-[24px] max-h-[120px] text-white/90 placeholder:text-white/25"
@keydown="handleKeydown"
@input="autoResize"
@paste="onPaste"
@@ -276,90 +276,90 @@
</div>
<div v-if="!isEditing" class="flex items-center gap-2 mt-1.5">
<span class="text-[10px] select-none text-white/30">{{ formattedTime }}</span>
<span v-if="message.editedAt" class="text-[10px] text-white/25 select-none">(edited)</span>
<span v-if="showTokenCount" class="text-[10px] text-white/20 select-none" :title="`~${estimatedTokens} tokens`">{{ tokenLabel }}</span>
<span v-if="message.feedback" class="text-[10px] select-none">{{ message.feedback === 'up' ? '👍' : '👎' }}</span>
<span class="text-xs select-none text-white/30">{{ formattedTime }}</span>
<span v-if="message.editedAt" class="text-xs text-white/25 select-none">(edited)</span>
<span v-if="showTokenCount" class="text-xs text-white/20 select-none" :title="`~${estimatedTokens} tokens`">{{ tokenLabel }}</span>
<span v-if="message.feedback" class="text-xs select-none">{{ message.feedback === 'up' ? '👍' : '👎' }}</span>
<button
v-if="inlineFilms.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlineFilms.length }} films
</button>
<button
v-else-if="inlineBooks.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlineBooks.length }} books
</button>
<button
v-else-if="inlineTVSeries.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlineTVSeries.length }} series
</button>
<button
v-else-if="inlineImages.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlineImages.length }} images
</button>
<button
v-else-if="inlinePlaces.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlinePlaces.length }} places
</button>
<button
v-if="inlinePlaces.length > 0 && inlinePlaces.some(p => p.lat != null)"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openMapView(inlinePlaces)"
>
View on map
</button>
<button
v-else-if="inlineSongs.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlineSongs.length }} songs
</button>
<button
v-else-if="inlinePodcasts.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlinePodcasts.length }} podcasts
</button>
<button
v-else-if="inlineNewsLinks.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlineNewsLinks.length }} articles
</button>
<button
v-else-if="inlineWebsitesLinks.length > 1"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View all {{ inlineWebsitesLinks.length }} websites
</button>
<button
v-else-if="inlineMagazineSections.length > 0"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openPanel"
>
View brief
</button>
<button
v-if="isLongForm"
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
class="text-xs text-accent/70 hover:text-accent transition-colors"
@click.stop="openLongFormArticle(message.content)"
>
Read as article
@@ -11,14 +11,14 @@
v-model="query"
type="text"
placeholder="Search messages..."
class="flex-1 bg-transparent text-sm text-white/90 placeholder:text-white/25 outline-none min-w-0"
class="flex-1 bg-transparent text-base text-white/90 placeholder:text-white/25 outline-none min-w-0"
@keydown.enter.exact="nextMatch"
@keydown.shift.enter="prevMatch"
@keydown.escape="close"
@keydown.up.prevent="prevMatch"
@keydown.down.prevent="nextMatch"
/>
<span v-if="query" class="text-[10px] text-white/40 whitespace-nowrap select-none">
<span v-if="query" class="text-xs text-white/40 whitespace-nowrap select-none">
{{ matchCount > 0 ? `${currentMatchIndex + 1}/${matchCount}` : 'No results' }}
</span>
<button
@@ -108,7 +108,7 @@
v-if="codeContext.isCodeMode.value && codeContext.activeProject.value"
class="px-3 pb-1 flex items-center gap-1.5"
>
<span class="text-[10px] px-2 py-0.5 rounded-md bg-accent/15 text-accent font-medium truncate max-w-[200px]">
<span class="text-xs px-2 py-0.5 rounded-md bg-accent/15 text-accent font-medium truncate max-w-[200px]">
{{ codeContext.activeProject.value.name }}
</span>
<button
@@ -23,8 +23,8 @@
:class="{ 'hidden md:flex': activeTab !== 0 }"
>
<div class="px-3 py-2 flex items-center justify-between border-b border-white/5">
<span class="text-[10px] text-accent font-medium truncate">{{ model1Label }}</span>
<span v-if="isStreaming1" class="text-[10px] text-white/30 animate-pulse">streaming...</span>
<span class="text-xs text-accent font-medium truncate">{{ model1Label }}</span>
<span v-if="isStreaming1" class="text-xs text-white/30 animate-pulse">streaming...</span>
</div>
<div class="flex-1 overflow-y-auto p-3">
<div
@@ -43,8 +43,8 @@
:class="{ 'hidden md:flex': activeTab !== 1 }"
>
<div class="px-3 py-2 flex items-center justify-between border-b border-white/5">
<span class="text-[10px] text-accent font-medium truncate">{{ model2Label }}</span>
<span v-if="isStreaming2" class="text-[10px] text-white/30 animate-pulse">streaming...</span>
<span class="text-xs text-accent font-medium truncate">{{ model2Label }}</span>
<span v-if="isStreaming2" class="text-xs text-white/30 animate-pulse">streaming...</span>
</div>
<div class="flex-1 overflow-y-auto p-3">
<div
@@ -11,7 +11,7 @@
/>
<!-- Tooltip on hover -->
<div
class="absolute -top-8 left-1/2 -translate-x-1/2 hidden group-hover:flex items-center px-2 py-1 rounded-md bg-black/80 text-[10px] text-white/80 whitespace-nowrap pointer-events-none z-10"
class="absolute -top-8 left-1/2 -translate-x-1/2 hidden group-hover:flex items-center px-2 py-1 rounded-md bg-black/80 text-xs text-white/80 whitespace-nowrap pointer-events-none z-10"
>
{{ tooltipText }}
</div>
@@ -2,7 +2,7 @@
<div class="px-3 md:px-4 pb-1">
<!-- Collapsed toggle -->
<button
class="flex items-center gap-1.5 text-[11px] text-white/40 hover:text-white/60 transition-colors"
class="flex items-center gap-1.5 text-xs text-white/40 hover:text-white/60 transition-colors"
@click="isExpanded = !isExpanded"
>
<svg
@@ -27,12 +27,12 @@
<input
v-model="editText"
type="text"
class="flex-1 bg-transparent text-xs text-white/80 outline-none"
class="flex-1 bg-transparent text-base text-white/80 outline-none"
@keydown.enter="saveEdit(item.id)"
@keydown.escape="cancelEdit"
/>
<button
class="text-[10px] text-accent/70 hover:text-accent"
class="text-xs text-accent/70 hover:text-accent"
@click="saveEdit(item.id)"
>
Save
@@ -66,19 +66,19 @@
<input
v-model="newText"
type="text"
class="flex-1 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
class="flex-1 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-base text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
placeholder="Add a memory..."
@keydown.enter="addMemory"
/>
<button
class="px-2.5 py-1.5 rounded-lg text-[11px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-all"
class="px-2.5 py-1.5 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-all"
:disabled="!newText.trim()"
@click="addMemory"
>
Add
</button>
</div>
<p v-else class="text-[10px] text-white/25">
<p v-else class="text-xs text-white/25">
Maximum 20 memories reached
</p>
</div>
+10 -10
View File
@@ -25,11 +25,11 @@
<!-- Error -->
<div v-else-if="error" class="flex items-center gap-2">
<span class="text-[10px]" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ isProfile ? 'Profile' : 'Note' }} not found
</span>
<span
class="text-[9px] font-mono truncate"
class="text-xs font-mono truncate"
:class="isDark ? 'text-purple-400/40' : 'text-purple-400'"
>
{{ truncatedId }}
@@ -40,33 +40,33 @@
<div v-else-if="note">
<div class="flex items-center gap-2 mb-1.5">
<div
class="w-6 h-6 rounded-full shrink-0 flex items-center justify-center text-[9px] font-bold"
class="w-6 h-6 rounded-full shrink-0 flex items-center justify-center text-xs font-bold"
:class="isDark ? 'bg-purple-500/20 text-purple-400' : 'bg-purple-100 text-purple-600'"
>
{{ note.authorName?.charAt(0)?.toUpperCase() ?? '?' }}
</div>
<span
class="text-[11px] font-semibold truncate"
class="text-xs font-semibold truncate"
:class="isDark ? 'text-white/70' : 'text-gray-700'"
>
{{ note.authorName ?? 'anon' }}
</span>
<span
class="text-[9px] ml-auto shrink-0"
class="text-xs ml-auto shrink-0"
:class="isDark ? 'text-white/20' : 'text-gray-300'"
>
{{ formatTime(note.created_at) }}
</span>
</div>
<p
class="text-[11px] leading-relaxed line-clamp-4"
class="text-xs leading-relaxed line-clamp-4"
:class="isDark ? 'text-white/60' : 'text-gray-600'"
>
{{ note.content }}
</p>
<div class="flex items-center gap-2 mt-1.5">
<span
class="text-[9px] font-mono"
class="text-xs font-mono"
:class="isDark ? 'text-purple-400/40' : 'text-purple-400/60'"
>
nostr
@@ -78,20 +78,20 @@
<div v-else-if="isProfile">
<div class="flex items-center gap-2">
<div
class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center text-[10px] font-bold"
class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center text-xs font-bold"
:class="isDark ? 'bg-purple-500/20 text-purple-400' : 'bg-purple-100 text-purple-600'"
>
{{ truncatedId.charAt(0).toUpperCase() }}
</div>
<div>
<span
class="text-[11px] font-mono block"
class="text-xs font-mono block"
:class="isDark ? 'text-white/60' : 'text-gray-600'"
>
{{ truncatedId }}
</span>
<span
class="text-[9px]"
class="text-xs"
:class="isDark ? 'text-purple-400/40' : 'text-purple-400/60'"
>
nostr profile
@@ -4,7 +4,7 @@
<button
v-for="p in personaStore.sortedPersonas"
:key="p.id"
class="px-2.5 py-1 rounded-full text-[11px] font-medium transition-all duration-200 border"
class="px-2.5 py-1 rounded-full text-xs font-medium transition-all duration-200 border"
:class="isActive(p.id)
? 'bg-accent/20 text-accent border-accent/30'
: 'bg-white/5 text-white/50 border-white/10 hover:text-white/70 hover:bg-white/10'"
@@ -18,14 +18,14 @@
{{ p.name }}
</button>
<button
class="px-2 py-1 rounded-full text-[11px] text-white/30 hover:text-white/60 border border-transparent hover:border-white/10 transition-all duration-200"
class="px-2 py-1 rounded-full text-xs text-white/30 hover:text-white/60 border border-transparent hover:border-white/10 transition-all duration-200"
@click="showEditor = true"
>
+ New
</button>
<button
v-if="activePersonaId"
class="px-2 py-1 rounded-full text-[11px] text-white/30 hover:text-white/60 transition-all"
class="px-2 py-1 rounded-full text-xs text-white/30 hover:text-white/60 transition-all"
title="Clear persona"
@click="clearPersona"
>
@@ -58,20 +58,20 @@
<div class="space-y-3">
<div>
<label class="block text-[11px] text-white/40 mb-1">Name</label>
<label class="block text-xs text-white/40 mb-1">Name</label>
<input
v-model="formName"
type="text"
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white/90 focus:outline-none focus:border-accent/50"
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-base text-white/90 focus:outline-none focus:border-accent/50"
placeholder="e.g. Film Critic"
/>
</div>
<div>
<label class="block text-[11px] text-white/40 mb-1">System Prompt</label>
<label class="block text-xs text-white/40 mb-1">System Prompt</label>
<textarea
v-model="formPrompt"
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white/90 resize-none focus:outline-none focus:border-accent/50"
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-base text-white/90 resize-none focus:outline-none focus:border-accent/50"
rows="5"
placeholder="You are a film critic who..."
/>
@@ -79,10 +79,10 @@
<div class="flex gap-3">
<div class="flex-1">
<label class="block text-[11px] text-white/40 mb-1">Model Preference</label>
<label class="block text-xs text-white/40 mb-1">Model Preference</label>
<select
v-model="formModel"
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white/90 focus:outline-none focus:border-accent/50"
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-base text-white/90 focus:outline-none focus:border-accent/50"
>
<option value="">Default</option>
<option value="claude-haiku-4.5">Claude 4.5 Haiku</option>
@@ -91,7 +91,7 @@
</select>
</div>
<div class="w-20">
<label class="block text-[11px] text-white/40 mb-1">Colour</label>
<label class="block text-xs text-white/40 mb-1">Colour</label>
<input
v-model="formColor"
type="color"
@@ -21,13 +21,13 @@
{{ pair.userMsg.content }}
</p>
<div class="flex items-center gap-1.5 mt-1 flex-wrap">
<span class="text-[10px] text-white/30">
<span class="text-xs text-white/30">
{{ formatTime(pair.userMsg.timestamp) }}
</span>
<span
v-for="badge in pair.badges"
:key="badge"
class="text-[9px] px-1.5 py-0.5 rounded-md bg-white/8 text-white/40"
class="text-xs px-1.5 py-0.5 rounded-md bg-white/8 text-white/40"
>
{{ badge }}
</span>
@@ -8,18 +8,18 @@
<div class="flex items-center justify-between">
<h4 class="text-xs font-semibold text-white/80">{{ selectedTemplate.title }}</h4>
<button
class="text-[10px] text-white/40 hover:text-white/60 transition-colors"
class="text-xs text-white/40 hover:text-white/60 transition-colors"
@click="cancelTemplate"
>
Cancel
</button>
</div>
<div v-for="v in variables" :key="v" class="space-y-1">
<label class="text-[11px] text-white/40">{{ v }}</label>
<label class="text-xs text-white/40">{{ v }}</label>
<input
v-model="variableValues[v]"
type="text"
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-sm text-white/90 focus:outline-none focus:border-accent/50"
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-base text-white/90 focus:outline-none focus:border-accent/50"
:placeholder="v"
@keydown.enter="applyTemplate"
/>
@@ -40,7 +40,7 @@
<!-- Commands section -->
<div v-if="filteredCommands.length > 0">
<div class="p-2 border-b border-white/5">
<p class="text-[10px] text-white/30 px-2">Commands</p>
<p class="text-xs text-white/30 px-2">Commands</p>
</div>
<div
v-for="(cmd, i) in filteredCommands"
@@ -51,17 +51,17 @@
@mouseenter="highlightIndex = i"
>
<div class="flex items-center gap-2">
<span class="text-[11px] font-mono text-accent/70">{{ cmd.slash }}</span>
<span class="text-xs font-mono text-accent/70">{{ cmd.slash }}</span>
<p class="text-sm text-white/80">{{ cmd.title }}</p>
</div>
<p v-if="cmd.preview" class="text-[11px] text-white/40 mt-0.5 truncate">{{ cmd.preview }}</p>
<p v-if="cmd.preview" class="text-xs text-white/40 mt-0.5 truncate">{{ cmd.preview }}</p>
</div>
</div>
<!-- Templates section -->
<div v-if="filteredUserTemplates.length > 0">
<div class="p-2 border-b border-white/5">
<p class="text-[10px] text-white/30 px-2">Templates</p>
<p class="text-xs text-white/30 px-2">Templates</p>
</div>
<div
v-for="(t, i) in filteredUserTemplates"
@@ -72,7 +72,7 @@
@mouseenter="highlightIndex = filteredCommands.length + i"
>
<p class="text-sm text-white/80">{{ t.title }}</p>
<p v-if="t.preview" class="text-[11px] text-white/40 mt-0.5 truncate">{{ t.preview }}</p>
<p v-if="t.preview" class="text-xs text-white/40 mt-0.5 truncate">{{ t.preview }}</p>
</div>
</div>
@@ -27,7 +27,7 @@
<!-- Memory Section -->
<div class="space-y-2">
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-white/40">
<h3 class="text-xs font-semibold uppercase tracking-wider text-white/40">
Memory ({{ memoryStore.items.length }}/20)
</h3>
@@ -41,12 +41,12 @@
<input
v-model="editText"
type="text"
class="flex-1 bg-transparent text-xs text-white/80 outline-none"
class="flex-1 bg-transparent text-base text-white/80 outline-none"
@keydown.enter="saveEdit(item.id)"
@keydown.escape="cancelEdit"
/>
<button
class="text-[10px] text-accent/70 hover:text-accent"
class="text-xs text-accent/70 hover:text-accent"
@click="saveEdit(item.id)"
>
Save
@@ -79,19 +79,19 @@
<input
v-model="newMemoryText"
type="text"
class="flex-1 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
class="flex-1 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-base text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
placeholder="Add a memory..."
@keydown.enter="addMemory"
/>
<button
class="px-2.5 py-1.5 rounded-lg text-[11px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-all"
class="px-2.5 py-1.5 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-all"
:disabled="!newMemoryText.trim()"
@click="addMemory"
>
Add
</button>
</div>
<p v-else class="text-[10px] text-white/25">
<p v-else class="text-xs text-white/25">
Maximum 20 memories reached
</p>
</div>
@@ -102,15 +102,15 @@
<!-- Advanced Section -->
<div class="space-y-3">
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-white/40">
<h3 class="text-xs font-semibold uppercase tracking-wider text-white/40">
Advanced
</h3>
<template v-if="conv">
<div class="space-y-1">
<div class="flex items-center justify-between">
<label class="text-[11px] text-white/40">Temperature</label>
<span class="text-[11px] text-white/50 tabular-nums">{{ temperature.toFixed(2) }}</span>
<label class="text-xs text-white/40">Temperature</label>
<span class="text-xs text-white/50 tabular-nums">{{ temperature.toFixed(2) }}</span>
</div>
<input
v-model.number="temperature"
@@ -125,8 +125,8 @@
<div class="space-y-1">
<div class="flex items-center justify-between">
<label class="text-[11px] text-white/40">Max Tokens</label>
<span class="text-[11px] text-white/50 tabular-nums">{{ maxTokens }}</span>
<label class="text-xs text-white/40">Max Tokens</label>
<span class="text-xs text-white/50 tabular-nums">{{ maxTokens }}</span>
</div>
<input
v-model.number="maxTokens"
@@ -141,8 +141,8 @@
<div class="space-y-1">
<div class="flex items-center justify-between">
<label class="text-[11px] text-white/40">Top P</label>
<span class="text-[11px] text-white/50 tabular-nums">{{ topP.toFixed(2) }}</span>
<label class="text-xs text-white/40">Top P</label>
<span class="text-xs text-white/50 tabular-nums">{{ topP.toFixed(2) }}</span>
</div>
<input
v-model.number="topP"
@@ -156,12 +156,12 @@
</div>
<div class="space-y-1">
<label class="text-[11px] text-white/40">Stop Sequences</label>
<label class="text-xs text-white/40">Stop Sequences</label>
<div v-if="stopSequences.length > 0" class="flex gap-1 flex-wrap mb-1">
<span
v-for="(seq, i) in stopSequences"
:key="i"
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-white/5 border border-white/10 text-[10px] text-white/50"
class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-white/5 border border-white/10 text-xs text-white/50"
>
{{ seq }}
<button class="text-white/30 hover:text-white/60" @click="removeStopSequence(i)">&times;</button>
@@ -170,14 +170,14 @@
<input
v-model="newStopSeq"
type="text"
class="w-full bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
class="w-full bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-base text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
placeholder="Add stop sequence (Enter to add)"
@keydown.enter="addStopSequence"
/>
</div>
<button
class="text-[11px] text-white/30 hover:text-white/50 transition-colors"
class="text-xs text-white/30 hover:text-white/50 transition-colors"
@click="resetDefaults"
>
Reset to defaults
@@ -22,12 +22,12 @@
<h2 class="text-lg font-bold text-white">{{ app.name }}</h2>
<div class="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-1 text-xs text-white/60">
<span
class="px-1.5 py-0.5 rounded text-[10px] font-medium bg-white/15"
class="px-1.5 py-0.5 rounded text-xs font-medium bg-white/15"
>{{ categoryLabel }}</span>
<span
v-for="p in app.platforms"
:key="p"
class="text-[10px]"
class="text-xs"
>{{ platformLabel(p) }}</span>
</div>
</div>
@@ -49,7 +49,7 @@
class="flex gap-2.5 text-xs"
>
<span
class="w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5"
class="w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold shrink-0 mt-0.5"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'"
>{{ i + 1 }}</span>
<span :class="isDark ? 'text-white/70' : 'text-gray-600'">{{ step }}</span>
@@ -79,7 +79,7 @@
<div>
<p class="text-xs font-medium"
:class="isDark ? 'text-white/80' : 'text-gray-800'">{{ app.url.replace(/^https?:\/\//, '') }}</p>
<p class="text-[10px]"
<p class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">Official website</p>
</div>
</div>
@@ -112,7 +112,7 @@
<div class="min-w-0">
<p class="text-xs font-medium truncate"
:class="isDark ? 'text-white/80' : 'text-gray-800'">{{ related.name }}</p>
<p class="text-[10px] truncate"
<p class="text-xs truncate"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ related.description }}</p>
</div>
</button>
@@ -8,7 +8,7 @@
<h3 class="text-sm font-bold" :class="isDark ? 'text-white/90' : 'text-gray-900'">
{{ title }}
</h3>
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredApps.length }} apps
</span>
</div>
@@ -17,7 +17,7 @@
v-model="search"
type="text"
placeholder="Search apps..."
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10'
: 'bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5'"
@@ -27,7 +27,7 @@
<button
v-for="cat in categories"
:key="cat.value"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeCategory === cat.value
? 'nav-tab-active'
: isDark
@@ -65,13 +65,13 @@
{{ app.name }}
</p>
<span
class="text-[9px] px-1.5 py-0.5 rounded font-medium shrink-0"
class="text-xs px-1.5 py-0.5 rounded font-medium shrink-0"
:class="isDark ? 'bg-white/10 text-white/50' : 'bg-black/5 text-gray-500'"
>
{{ categoryLabel(app.category) }}
</span>
</div>
<p class="text-[11px] mt-0.5 line-clamp-2"
<p class="text-xs mt-0.5 line-clamp-2"
:class="isDark ? 'text-white/50' : 'text-gray-500'">
{{ app.description }}
</p>
@@ -79,7 +79,7 @@
<span
v-for="p in app.platforms"
:key="p"
class="text-[8px] px-1 py-0.5 rounded"
class="text-xs px-1 py-0.5 rounded"
:class="isDark ? 'bg-white/5 text-white/30' : 'bg-black/3 text-gray-400'"
>
{{ platformLabel(p) }}
@@ -5,7 +5,7 @@
<h3 class="text-sm font-bold text-white/90">
Node Apps
</h3>
<span class="text-[10px] font-mono text-white/30">
<span class="text-xs font-mono text-white/30">
{{ filteredApps.length }} apps
</span>
</div>
@@ -14,15 +14,14 @@
v-model="search"
type="text"
placeholder="Search node apps..."
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10"
style="font-size: 16px"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10"
/>
<div class="flex flex-wrap gap-1.5">
<button
v-for="cat in categories"
:key="cat.value"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeCategory === cat.value
? 'nav-tab-active'
: 'text-white/40 hover:text-white/70 hover:bg-white/5'"
@@ -46,7 +45,7 @@
<span class="text-lg leading-none">{{ app.icon }}</span>
<span class="text-xs font-semibold text-white/90 truncate">{{ app.name }}</span>
</div>
<p class="text-[10px] text-white/50 line-clamp-2 leading-relaxed">
<p class="text-xs text-white/50 line-clamp-2 leading-relaxed">
{{ app.description }}
</p>
<div class="mt-2 flex items-center gap-1.5">
@@ -54,7 +53,7 @@
class="w-1.5 h-1.5 rounded-full"
:class="statusDotClass(app.liveStatus)"
/>
<span class="text-[10px]" :class="statusTextClass(app.liveStatus)">
<span class="text-xs" :class="statusTextClass(app.liveStatus)">
{{ statusLabel(app.liveStatus) }}
</span>
</div>
@@ -32,7 +32,7 @@
:class="isDark ? 'text-white/50' : 'text-gray-500'">
{{ book.author }}<span v-if="book.year"> · {{ book.year }}</span>
</p>
<p v-if="book.description" class="text-[11px] mt-1 line-clamp-2 leading-relaxed"
<p v-if="book.description" class="text-xs mt-1 line-clamp-2 leading-relaxed"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
{{ book.description }}
</p>
@@ -47,7 +47,7 @@
<span
v-for="genre in book.genres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md font-medium"
class="text-xs px-2 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'"
>
{{ genre }}
@@ -95,7 +95,7 @@
<div>
<p class="text-xs font-medium"
:class="isDark ? 'text-white/80' : 'text-gray-800'">{{ link.name }}</p>
<p v-if="link.desc" class="text-[10px]"
<p v-if="link.desc" class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ link.desc }}</p>
</div>
</div>
@@ -9,7 +9,7 @@
{{ title }}
</h3>
<div class="flex items-center gap-2 shrink-0">
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredBooks.length }} books
</span>
<slot name="header-actions" />
@@ -20,7 +20,7 @@
v-model="search"
type="text"
placeholder="Search books..."
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10'
: 'bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5'"
@@ -30,7 +30,7 @@
<button
v-for="genre in topGenres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeGenre === genre
? 'nav-tab-active'
: isDark
@@ -69,20 +69,20 @@
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
<div class="absolute bottom-0 left-0 right-0 p-2">
<p class="text-[11px] font-semibold text-white/90 leading-tight truncate">
<p class="text-xs font-semibold text-white/90 leading-tight truncate">
{{ book.title }}
</p>
<p class="text-[9px] text-white/40 truncate mt-0.5">{{ book.author }}</p>
<p class="text-xs text-white/40 truncate mt-0.5">{{ book.author }}</p>
</div>
<div v-if="book.rating" class="absolute top-1.5 left-1.5">
<span class="text-[9px] px-1.5 py-0.5 rounded bg-black/60 text-amber-400 backdrop-blur-sm font-medium">
<span class="text-xs px-1.5 py-0.5 rounded bg-black/60 text-amber-400 backdrop-blur-sm font-medium">
{{ book.rating.toFixed(1) }}
</span>
</div>
<div v-if="book.year" class="absolute top-1.5 right-1.5">
<span class="text-[8px] px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm">
<span class="text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm">
{{ book.year }}
</span>
</div>
@@ -19,7 +19,7 @@
<div class="flex-1 min-w-0 pl-8">
<div class="flex items-center gap-2">
<!-- Language badge -->
<span class="shrink-0 text-[9px] px-1.5 py-0.5 rounded font-mono"
<span class="shrink-0 text-xs px-1.5 py-0.5 rounded font-mono"
:class="isDark ? 'bg-white/10 text-white/50' : 'bg-black/5 text-gray-500'">
{{ language }}
</span>
@@ -28,7 +28,7 @@
{{ fileName }}
</p>
</div>
<p v-if="projectName" class="text-[10px] font-mono mt-0.5 truncate"
<p v-if="projectName" class="text-xs font-mono mt-0.5 truncate"
:class="isDark ? 'text-white/25' : 'text-gray-400'">
{{ projectName }} / {{ filePath }}
</p>
@@ -39,7 +39,7 @@
<button
v-for="tab in displayTabs"
:key="tab"
class="text-[10px] px-2.5 py-1.5 rounded-lg font-medium whitespace-nowrap transition-all duration-150"
class="text-xs px-2.5 py-1.5 rounded-lg font-medium whitespace-nowrap transition-all duration-150"
:class="activeTab === tab
? 'nav-tab-active'
: isDark
@@ -15,7 +15,7 @@
{{ contextLabel }}
</p>
<div class="flex items-center gap-2 shrink-0">
<p class="text-[10px] font-mono uppercase tracking-[0.2em]"
<p class="text-xs font-mono uppercase tracking-[0.2em]"
:class="isDark ? 'text-white/25' : 'text-gray-400'">
Surfacing
</p>
@@ -20,13 +20,13 @@
:class="isDark ? 'text-white/90' : 'text-gray-900'">
{{ item.name }}
</h2>
<p class="text-[10px]"
<p class="text-xs"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
{{ categoryLabel }}
</p>
</div>
<button
class="text-[10px] px-2 py-1 rounded-md transition-colors"
class="text-xs px-2 py-1 rounded-md transition-colors"
:class="copied
? 'bg-emerald-500/20 text-emerald-400'
: isDark
@@ -47,7 +47,7 @@
<!-- Live preview -->
<div>
<h4 class="text-[10px] uppercase tracking-[0.2em] font-semibold mb-2"
<h4 class="text-xs uppercase tracking-[0.2em] font-semibold mb-2"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
Preview
</h4>
@@ -58,7 +58,7 @@
<div class="h-12 rounded-lg border"
:class="isDark ? 'border-white/10' : 'border-black/10'"
:style="{ background: extractColorValue(item.code) }" />
<p class="text-[10px] font-mono text-center"
<p class="text-xs font-mono text-center"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
{{ extractColorValue(item.code) }}
</p>
@@ -119,11 +119,11 @@
</button>
</div>
<div v-else-if="item.id === 'atom-badge'" class="flex flex-wrap gap-1.5">
<span class="text-[10px] px-2 py-1 rounded-md font-medium"
<span class="text-xs px-2 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'">Science Fiction</span>
<span class="text-[10px] px-2 py-1 rounded-md font-medium"
<span class="text-xs px-2 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'">Drama</span>
<span class="text-[10px] px-2 py-1 rounded-md font-medium"
<span class="text-xs px-2 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'">Thriller</span>
</div>
<div v-else-if="item.id === 'mol-glass-card'">
@@ -135,17 +135,17 @@
<!-- Nav tab preview -->
<div v-else-if="item.id === 'atom-nav-tab'" class="flex gap-1.5">
<button class="text-[10px] px-2.5 py-1 rounded-md font-medium bg-accent/20 text-accent">Films</button>
<button class="text-[10px] px-2.5 py-1 rounded-md font-medium"
<button class="text-xs px-2.5 py-1 rounded-md font-medium bg-accent/20 text-accent">Films</button>
<button class="text-xs px-2.5 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/5 text-white/50' : 'bg-black/5 text-gray-500'">Songs</button>
<button class="text-[10px] px-2.5 py-1 rounded-md font-medium"
<button class="text-xs px-2.5 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/5 text-white/50' : 'bg-black/5 text-gray-500'">Podcasts</button>
</div>
<!-- Text input preview -->
<div v-else-if="item.id === 'atom-input'">
<input
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10'
: 'bg-black/5 text-gray-800 placeholder:text-gray-400 focus:bg-black/10'"
@@ -159,12 +159,12 @@
<div class="h-16 overflow-y-auto rounded-lg px-3 py-2"
:class="isDark ? 'bg-white/5' : 'bg-black/5'"
style="scrollbar-width: thin;">
<p v-for="n in 8" :key="n" class="text-[10px] py-0.5"
<p v-for="n in 8" :key="n" class="text-xs py-0.5"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
Scrollable content line {{ n }}
</p>
</div>
<p class="text-[10px] text-center" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<p class="text-xs text-center" :class="isDark ? 'text-white/30' : 'text-gray-400'">
4px wide, translucent thumb
</p>
</div>
@@ -185,7 +185,7 @@
<span class="text-sm">🎬</span>
<div>
<p class="text-xs font-medium" :class="isDark ? 'text-white/80' : 'text-gray-800'">Netflix</p>
<p class="text-[10px]" :class="isDark ? 'text-white/30' : 'text-gray-400'">Stream now</p>
<p class="text-xs" :class="isDark ? 'text-white/30' : 'text-gray-400'">Stream now</p>
</div>
</div>
<svg class="w-3.5 h-3.5" :class="isDark ? 'text-white/30' : 'text-gray-400'"
@@ -203,7 +203,7 @@
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent" />
<div class="absolute bottom-0 left-0 p-3">
<h3 class="text-sm font-bold text-white/90">Banner Title</h3>
<p class="text-[10px] text-white/50">Subtitle text</p>
<p class="text-xs text-white/50">Subtitle text</p>
</div>
</div>
</div>
@@ -216,7 +216,7 @@
:style="{ background: `linear-gradient(${120 * n}deg, ${['#2d1b69','#1b3a4b','#3b1b2b'][n-1]}, ${['#1a0a3e','#0a2030','#200a1a'][n-1]})` }">
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div class="absolute bottom-0 left-0 right-0 p-1.5">
<p class="text-[9px] text-white/80 font-medium truncate">{{ ['Film', 'Album', 'Series'][n-1] }}</p>
<p class="text-xs text-white/80 font-medium truncate">{{ ['Film', 'Album', 'Series'][n-1] }}</p>
</div>
</div>
</div>
@@ -225,13 +225,13 @@
<!-- Chat bubble preview -->
<div v-else-if="item.id === 'org-chat-bubble'" class="space-y-2">
<div class="flex justify-end">
<div class="max-w-[80%] px-3 py-2 rounded-2xl text-[11px]"
<div class="max-w-[80%] px-3 py-2 rounded-2xl text-xs"
:class="isDark ? 'bg-white/10 text-white/90' : 'bg-black/10 text-gray-800'">
What films should I watch?
</div>
</div>
<div class="flex justify-start">
<div class="max-w-[80%] px-3 py-2 text-[11px]"
<div class="max-w-[80%] px-3 py-2 text-xs"
:class="isDark ? 'text-white/70' : 'text-gray-600'">
Here are some great picks from your library...
</div>
@@ -242,10 +242,10 @@
<div v-else-if="item.id === 'org-content-panel'" class="space-y-2">
<div class="flex gap-1 pb-1.5"
:style="isDark ? 'border-bottom: 1px solid rgba(255,255,255,0.08)' : 'border-bottom: 1px solid rgba(0,0,0,0.06)'">
<span class="text-[9px] px-2 py-0.5 rounded font-medium bg-accent/20 text-accent">Films</span>
<span class="text-[9px] px-2 py-0.5 rounded font-medium"
<span class="text-xs px-2 py-0.5 rounded font-medium bg-accent/20 text-accent">Films</span>
<span class="text-xs px-2 py-0.5 rounded font-medium"
:class="isDark ? 'text-white/40' : 'text-gray-400'">Songs</span>
<span class="text-[9px] px-2 py-0.5 rounded font-medium"
<span class="text-xs px-2 py-0.5 rounded font-medium"
:class="isDark ? 'text-white/40' : 'text-gray-400'">Books</span>
</div>
<div class="grid grid-cols-3 gap-1">
@@ -267,8 +267,8 @@
</svg>
</div>
<div class="absolute bottom-1 left-2">
<p class="text-[10px] font-bold text-white/90">Title</p>
<p class="text-[8px] text-white/50">Meta</p>
<p class="text-xs font-bold text-white/90">Title</p>
<p class="text-xs text-white/50">Meta</p>
</div>
</div>
<div class="space-y-1 px-1">
@@ -285,15 +285,15 @@
:class="isDark ? 'bg-[#0a0a0a]' : 'bg-white'">
<p class="text-[7px] uppercase tracking-[0.3em] mb-0.5"
:class="isDark ? 'text-white/30' : 'text-gray-400'">Editorial</p>
<p class="text-[11px] font-serif font-bold"
<p class="text-xs font-serif font-bold"
:class="isDark ? 'text-white/90' : 'text-gray-900'">Hero Headline</p>
</div>
<div class="px-2 py-2" :class="isDark ? 'bg-[#0a0a0a]' : 'bg-white'">
<p class="text-[9px] font-serif font-bold"
<p class="text-xs font-serif font-bold"
:class="isDark ? 'text-white/80' : 'text-gray-800'">Half Tile</p>
</div>
<div class="px-2 py-2" :class="isDark ? 'bg-[#0a0a0a]' : 'bg-white'">
<p class="text-[9px] font-serif font-bold"
<p class="text-xs font-serif font-bold"
:class="isDark ? 'text-white/80' : 'text-gray-800'">Half Tile</p>
</div>
</div>
@@ -304,22 +304,22 @@
<div class="p-3 rounded-xl"
:class="isDark ? 'bg-white/[0.03] border border-white/5' : 'bg-black/[0.02] border border-black/5'">
<div class="flex items-start gap-2.5">
<div class="w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-bold shrink-0"
<div class="w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold shrink-0"
style="background: rgba(168, 85, 247, 0.2); color: rgba(168, 85, 247, 0.8);">
F
</div>
<div class="min-w-0 flex-1">
<div class="flex items-center gap-1.5">
<span class="text-[11px] font-semibold" :class="isDark ? 'text-white/80' : 'text-gray-800'">fiatjaf</span>
<span class="text-[9px]" :class="isDark ? 'text-white/25' : 'text-gray-300'">2h</span>
<span class="text-xs font-semibold" :class="isDark ? 'text-white/80' : 'text-gray-800'">fiatjaf</span>
<span class="text-xs" :class="isDark ? 'text-white/25' : 'text-gray-300'">2h</span>
</div>
<p class="text-[10px] mt-0.5 leading-relaxed"
<p class="text-xs mt-0.5 leading-relaxed"
:class="isDark ? 'text-white/50' : 'text-gray-500'">
Nostr is the simplest open protocol...
</p>
<div class="flex gap-3 mt-1.5">
<span class="text-[9px]" :class="isDark ? 'text-white/25' : 'text-gray-300'">3 replies</span>
<span class="text-[9px] text-amber-500/70">21000 sats</span>
<span class="text-xs" :class="isDark ? 'text-white/25' : 'text-gray-300'">3 replies</span>
<span class="text-xs text-amber-500/70">21000 sats</span>
</div>
</div>
</div>
@@ -332,7 +332,7 @@
:class="isDark ? 'bg-white/10 text-white/70' : 'bg-black/10 text-gray-600'">
Fade Up (900ms)
</div>
<button class="text-[10px] px-2 py-0.5 rounded transition-colors"
<button class="text-xs px-2 py-0.5 rounded transition-colors"
:class="isDark ? 'text-white/40 hover:text-white/60' : 'text-gray-400 hover:text-gray-600'"
@click="fadeUpKey++">
Replay
@@ -345,7 +345,7 @@
:class="isDark ? 'bg-white/10 text-white/70' : 'bg-black/10 text-gray-600'">
Scale In (250ms)
</div>
<button class="text-[10px] px-2 py-0.5 rounded transition-colors"
<button class="text-xs px-2 py-0.5 rounded transition-colors"
:class="isDark ? 'text-white/40 hover:text-white/60' : 'text-gray-400 hover:text-gray-600'"
@click="scaleInKey++">
Replay
@@ -364,7 +364,7 @@
<!-- Used In -->
<div v-if="item.usedIn">
<h4 class="text-[10px] uppercase tracking-[0.2em] font-semibold mb-2"
<h4 class="text-xs uppercase tracking-[0.2em] font-semibold mb-2"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
Used In
</h4>
@@ -379,7 +379,7 @@
<!-- Code block -->
<div>
<h4 class="text-[10px] uppercase tracking-[0.2em] font-semibold mb-2"
<h4 class="text-xs uppercase tracking-[0.2em] font-semibold mb-2"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
Code
</h4>
@@ -8,7 +8,7 @@
:class="isDark ? 'text-white/90' : 'text-gray-900'">
Design System
</span>
<p class="text-[10px]"
<p class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredItems.length }} items
</p>
@@ -19,7 +19,7 @@
<button
v-for="cat in categories"
:key="cat.id"
class="text-[10px] px-2.5 py-1 rounded-md font-medium whitespace-nowrap transition-colors"
class="text-xs px-2.5 py-1 rounded-md font-medium whitespace-nowrap transition-colors"
:class="activeCategory === cat.id
? 'bg-accent/20 text-accent'
: isDark
@@ -95,7 +95,7 @@
:class="isDark ? 'text-white/80' : 'text-gray-800'">
{{ item.name }}
</h3>
<p class="text-[10px] leading-snug line-clamp-2"
<p class="text-xs leading-snug line-clamp-2"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
{{ item.description }}
</p>
@@ -139,7 +139,7 @@ const items: DesignSystemItem[] = [
{ id: 'type-body', name: 'Body Font', category: 'typography', description: 'Inter / system-ui for all body text', code: 'font-family: Inter, system-ui, -apple-system, sans-serif;\n/* Applied globally */', usedIn: 'Global default — ChatMessage, grids, detail views' },
{ id: 'type-mono', name: 'Monospace Font', category: 'typography', description: 'Menlo / Monaco for code and IDs', code: 'font-family: Menlo, Monaco, "Courier New", monospace;\n/* Tailwind: font-mono */', usedIn: 'CodeDetail, conversation IDs, relay URLs, metadata' },
{ id: 'type-serif', name: 'Serif Font', category: 'typography', description: 'Georgia for magazine/editorial layouts', code: 'font-family: Georgia, "Times New Roman", Times, serif;\n/* Used in MagazineGrid, AI Brief */', usedIn: 'MagazineGrid, MagazineSectionDetail, AI Brief' },
{ id: 'type-sizes', name: 'Text Sizes', category: 'typography', description: 'Compact scale: 10px labels to 2xl headings', code: '/* Key sizes used */\ntext-[10px] /* labels, metadata */\ntext-xs /* 12px - secondary text */\ntext-sm /* 14px - body text */\ntext-base /* 16px - primary text */\ntext-lg /* 18px - section headings */\ntext-xl /* 20px - page headings */\ntext-2xl /* 24px - hero text */', usedIn: 'Globally — see specific usage in each size bracket' },
{ id: 'type-sizes', name: 'Text Sizes', category: 'typography', description: 'Compact scale: 10px labels to 2xl headings', code: '/* Key sizes used */\ntext-xs /* labels, metadata */\ntext-xs /* 12px - secondary text */\ntext-sm /* 14px - body text */\ntext-base /* 16px - primary text */\ntext-lg /* 18px - section headings */\ntext-xl /* 20px - page headings */\ntext-2xl /* 24px - hero text */', usedIn: 'Globally — see specific usage in each size bracket' },
// Spacing
{ id: 'space-grid', name: '4px Grid', category: 'spacing', preview: 'inline', description: 'All spacing follows a 4px base grid', code: '/* 4px grid system */\n1 = 4px /* micro gap */\n2 = 8px /* tight gap */\n3 = 12px /* small padding */\n4 = 16px /* standard padding */\n5 = 20px /* section padding */\n6 = 24px /* large gap */\n8 = 32px /* section spacing */\n12 = 48px /* large sections */', usedIn: 'Every layout — padding, margins, gaps between elements' },
@@ -149,7 +149,7 @@ const items: DesignSystemItem[] = [
{ id: 'atom-glass-btn', name: 'Glass Button', category: 'atoms', description: '48px height, glass morphism background', code: '<button class="glass-button">\n Action\n</button>\n\n/* glass-button:\n height: 48px\n background: rgba(0,0,0,0.6)\n backdrop-filter: blur(18px)\n border-radius: 12px\n border: 1px solid rgba(255,255,255,0.12)\n*/', usedIn: 'ChatInput send, modal actions, primary controls' },
{ id: 'atom-glass-btn-sm', name: 'Glass Button Small', category: 'atoms', description: 'Compact glass button variant', code: '<button class="glass-button-sm">\n Small\n</button>\n\n/* Compact variant of glass-button */', usedIn: 'ChatInput send/stop buttons, inline actions' },
{ id: 'atom-icon-btn', name: 'Icon Button', category: 'atoms', description: 'Path glass icon, 32-36px square', code: '<button class="w-9 h-9 rounded-xl path-glass-icon\n flex items-center justify-center">\n <svg class="w-4 h-4" ...>\n</button>\n\n/* path-glass-icon:\n background: transparent\n transition: colors\n hover: bg-white/10\n*/', usedIn: 'ChatHeader toolbar, detail back buttons, close buttons' },
{ id: 'atom-badge', name: 'Genre Badge', category: 'atoms', description: 'Tiny pill badge for tags/genres', code: '<span class="text-[10px] px-2 py-1 rounded-md\n font-medium bg-white/10 text-white/60">\n Science Fiction\n</span>', usedIn: 'FilmGrid, SongGrid, BookGrid, TVSeriesGrid genre filters' },
{ id: 'atom-badge', name: 'Genre Badge', category: 'atoms', description: 'Tiny pill badge for tags/genres', code: '<span class="text-xs px-2 py-1 rounded-md\n font-medium bg-white/10 text-white/60">\n Science Fiction\n</span>', usedIn: 'FilmGrid, SongGrid, BookGrid, TVSeriesGrid genre filters' },
{ id: 'atom-nav-tab', name: 'Nav Tab', category: 'atoms', description: 'Content panel tab with active state', code: '<button class="nav-tab-active">\n Films\n</button>\n\n/* Active: accent underline\n Inactive: text-white/50 hover:text-white\n Transition: 200ms */', usedIn: 'ContentPanel tab bar, mobile content tab filters' },
{ id: 'atom-input', name: 'Text Input', category: 'atoms', description: 'Search/filter input field', code: '<input\n class="w-full px-3 py-2 rounded-lg text-xs\n outline-none transition-colors\n bg-white/5 text-white/80\n placeholder:text-white/25\n focus:bg-white/10"\n placeholder="Search..."\n/>', usedIn: 'All grid search bars, ProjectGrid new project' },
{ id: 'atom-scrollbar', name: 'Custom Scrollbar', category: 'atoms', description: 'Thin translucent scrollbar for scroll areas', code: '.custom-scrollbar::-webkit-scrollbar {\n width: 4px;\n}\n.custom-scrollbar::-webkit-scrollbar-thumb {\n background: rgba(255,255,255, 0.1);\n border-radius: 2px;\n}\n/* Also: .scrollbar-hide hides completely */', usedIn: 'Content grids, chat message list, file trees' },
@@ -157,16 +157,16 @@ const items: DesignSystemItem[] = [
// Molecules
{ id: 'mol-glass-card', name: 'Glass Card', category: 'molecules', description: 'Frosted glass card with border', code: '<div class="glass-card">\n <h3>Title</h3>\n <p>Content</p>\n</div>\n\n/* glass-card:\n background: rgba(0,0,0,0.65)\n backdrop-filter: blur(18px)\n border: 1px solid rgba(255,255,255,0.12)\n border-radius: 16px\n padding: 16px\n*/', usedIn: 'ChatWindow container, content panel wrapper' },
{ id: 'mol-gradient-card', name: 'Gradient Card', category: 'molecules', description: 'Card with gradient background', code: '<div class="gradient-card">\n <h3>Featured</h3>\n <p>Content</p>\n</div>\n\n/* gradient-card:\n background: linear-gradient(135deg, ...)\n border-radius: 16px\n*/', usedIn: 'Featured content highlights, promotional sections' },
{ id: 'mol-source-link', name: 'Source Link Row', category: 'molecules', description: 'Icon + label + external link arrow', code: '<a class="flex items-center justify-between\n p-3 rounded-xl bg-white/5\n hover:bg-white/10 transition-colors">\n <div class="flex items-center gap-2.5">\n <span class="text-sm">icon</span>\n <div>\n <p class="text-xs font-medium\n text-white/80">Name</p>\n <p class="text-[10px]\n text-white/30">Description</p>\n </div>\n </div>\n <svg><!-- external link icon --></svg>\n</a>', usedIn: 'FilmDetail, SongDetail, PodcastDetail sources' },
{ id: 'mol-source-link', name: 'Source Link Row', category: 'molecules', description: 'Icon + label + external link arrow', code: '<a class="flex items-center justify-between\n p-3 rounded-xl bg-white/5\n hover:bg-white/10 transition-colors">\n <div class="flex items-center gap-2.5">\n <span class="text-sm">icon</span>\n <div>\n <p class="text-xs font-medium\n text-white/80">Name</p>\n <p class="text-xs\n text-white/30">Description</p>\n </div>\n </div>\n <svg><!-- external link icon --></svg>\n</a>', usedIn: 'FilmDetail, SongDetail, PodcastDetail sources' },
{ id: 'mol-banner-hero', name: 'Banner Hero', category: 'molecules', description: 'Aspect 16/7 image with gradient overlay', code: '<div class="relative w-full aspect-[16/7]\n overflow-hidden">\n <img :src="url" class="absolute inset-0\n w-full h-full object-cover" />\n <div class="absolute inset-0\n bg-gradient-to-t from-black/80\n via-black/30 to-transparent" />\n <div class="absolute bottom-0 p-4">\n <h2 class="text-lg font-bold\n text-white">Title</h2>\n </div>\n</div>', usedIn: 'FilmDetail, TVSeriesDetail, BookDetail banners' },
{ id: 'mol-cover-card', name: 'Cover Card', category: 'molecules', description: 'Poster/cover image card with overlay text', code: '<button class="group rounded-2xl overflow-hidden">\n <div class="aspect-[2/3] relative">\n <img class="w-full h-full object-cover\n group-hover:scale-110\n transition-transform duration-300" />\n <div class="absolute inset-0\n bg-gradient-to-t from-black/60\n to-transparent" />\n <div class="absolute bottom-0 p-2">\n <p class="text-[11px] text-white/90">\n Title</p>\n </div>\n </div>\n</button>', usedIn: 'FilmGrid, TVSeriesGrid, SongGrid, BookGrid cards' },
{ id: 'mol-cover-card', name: 'Cover Card', category: 'molecules', description: 'Poster/cover image card with overlay text', code: '<button class="group rounded-2xl overflow-hidden">\n <div class="aspect-[2/3] relative">\n <img class="w-full h-full object-cover\n group-hover:scale-110\n transition-transform duration-300" />\n <div class="absolute inset-0\n bg-gradient-to-t from-black/60\n to-transparent" />\n <div class="absolute bottom-0 p-2">\n <p class="text-xs text-white/90">\n Title</p>\n </div>\n </div>\n</button>', usedIn: 'FilmGrid, TVSeriesGrid, SongGrid, BookGrid cards' },
// Organisms
{ id: 'org-chat-bubble', name: 'Chat Bubble', category: 'organisms', description: 'AI/User message bubble with streaming', code: '<!-- User bubble -->\n<div class="flex justify-end">\n <div class="glass-card max-w-[85%]\n px-4 py-3 text-sm text-white/90">\n Message text\n </div>\n</div>\n\n<!-- AI bubble -->\n<div class="flex justify-start">\n <div class="max-w-[85%] px-4 py-3\n text-sm text-white/80">\n Response with markdown\n </div>\n</div>', usedIn: 'ChatMessage.vue — the primary chat interface' },
{ id: 'org-content-panel', name: 'Content Panel', category: 'organisms', description: 'Tabs + grid + detail navigation', code: '<!-- Structure -->\n<div class="flex flex-col h-full">\n <!-- Tab bar -->\n <div class="flex gap-1 px-3 py-2">\n <button class="nav-tab">Tab</button>\n </div>\n <!-- Grid view -->\n <ContentGridView />\n <!-- or Detail view -->\n <DetailView />\n</div>', usedIn: 'ChatPage middle column, mobile Content tab' },
{ id: 'org-detail-view', name: 'Detail View', category: 'organisms', description: 'Full detail with banner, back button, metadata', code: '<!-- Pattern: Banner → Meta → Content -->\n<div class="h-full overflow-y-auto">\n <!-- Banner with back button -->\n <div class="relative aspect-[16/7]">\n <img class="object-cover" />\n <div class="gradient-overlay" />\n <button class="absolute top-3 left-3\n path-glass-icon">Back</button>\n <div class="absolute bottom-0 p-4">\n <h2>Title</h2>\n <div>Metadata</div>\n </div>\n </div>\n <!-- Body -->\n <div class="p-4 space-y-4">\n <p>Description</p>\n <div>Genre badges</div>\n <div>Source links</div>\n </div>\n</div>', usedIn: 'FilmDetail, BookDetail, TVSeriesDetail, SongDetail, PodcastDetail' },
{ id: 'org-magazine', name: 'Magazine Grid', category: 'organisms', description: 'Editorial tile layout with hero, wide, and half tiles', code: '<!-- Magazine structure -->\n<div class="grid grid-cols-2 gap-px\n bg-white/12">\n <!-- Wide tile (col-span-2) -->\n <button class="col-span-2 px-5 py-5\n bg-[#0a0a0a]">\n <p class="text-[9px] uppercase\n tracking-[0.3em]">Label</p>\n <h2 class="font-serif text-lg\n font-bold">Title</h2>\n <p class="font-serif text-sm">Text</p>\n </button>\n <!-- Half tiles -->\n <button class="px-4 py-4 bg-[#0a0a0a]">\n <h3 class="font-serif text-sm\n font-bold">Title</h3>\n <p class="font-serif text-xs">Text</p>\n </button>\n</div>', usedIn: 'MagazineGrid.vue — AI Brief editorial view' },
{ id: 'org-nostr-note', name: 'Nostr Note', category: 'organisms', description: 'Note card with avatar, author, content, zaps', code: '<div class="p-3 rounded-xl bg-white/[0.03]\n border border-white/5">\n <div class="flex items-start gap-2.5">\n <div class="w-8 h-8 rounded-full\n bg-purple-500/20 text-purple-400">\n F\n </div>\n <div class="flex-1">\n <span class="text-xs font-semibold">\n author</span>\n <p class="text-[11px] text-white/60">\n Note content...</p>\n <span class="text-[9px]\n text-amber-500/70">21000 sats</span>\n </div>\n </div>\n</div>', usedIn: 'NostrGrid.vue — Nostr feed tab' },
{ id: 'org-magazine', name: 'Magazine Grid', category: 'organisms', description: 'Editorial tile layout with hero, wide, and half tiles', code: '<!-- Magazine structure -->\n<div class="grid grid-cols-2 gap-px\n bg-white/12">\n <!-- Wide tile (col-span-2) -->\n <button class="col-span-2 px-5 py-5\n bg-[#0a0a0a]">\n <p class="text-xs uppercase\n tracking-[0.3em]">Label</p>\n <h2 class="font-serif text-lg\n font-bold">Title</h2>\n <p class="font-serif text-sm">Text</p>\n </button>\n <!-- Half tiles -->\n <button class="px-4 py-4 bg-[#0a0a0a]">\n <h3 class="font-serif text-sm\n font-bold">Title</h3>\n <p class="font-serif text-xs">Text</p>\n </button>\n</div>', usedIn: 'MagazineGrid.vue — AI Brief editorial view' },
{ id: 'org-nostr-note', name: 'Nostr Note', category: 'organisms', description: 'Note card with avatar, author, content, zaps', code: '<div class="p-3 rounded-xl bg-white/[0.03]\n border border-white/5">\n <div class="flex items-start gap-2.5">\n <div class="w-8 h-8 rounded-full\n bg-purple-500/20 text-purple-400">\n F\n </div>\n <div class="flex-1">\n <span class="text-xs font-semibold">\n author</span>\n <p class="text-xs text-white/60">\n Note content...</p>\n <span class="text-xs\n text-amber-500/70">21000 sats</span>\n </div>\n </div>\n</div>', usedIn: 'NostrGrid.vue — Nostr feed tab' },
// Animations
{ id: 'anim-fade-up', name: 'Fade Up', category: 'atoms', description: 'Entry animation: translate + opacity', code: '.animate-fade-up {\n animation: fadeUp 900ms ease-out;\n}\n@keyframes fadeUp {\n from {\n opacity: 0;\n transform: translateY(16px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n/* Also: animate-fade-up-fast (400ms) */', usedIn: 'Empty states, initial load elements, ChatWindow' },
@@ -8,7 +8,7 @@
<button
v-for="tab in subTabs"
:key="tab.id"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeSubTab === tab.id
? 'nav-tab-active'
: 'text-white/40 hover:text-white/70 hover:bg-white/5'"
@@ -34,24 +34,24 @@
:key="item.id"
class="flex items-center gap-3 p-3 rounded-xl bg-white/[0.03] hover:bg-white/[0.07] border border-white/5 transition-all duration-150 cursor-pointer"
>
<span class="text-[10px] w-6 h-6 rounded flex items-center justify-center shrink-0" :class="typeStyle(item.type)">
<span class="text-xs w-6 h-6 rounded flex items-center justify-center shrink-0" :class="typeStyle(item.type)">
{{ typeIcon(item.type) }}
</span>
<div class="flex-1 min-w-0">
<div class="text-xs font-semibold truncate text-white/80">{{ item.title }}</div>
<div v-if="item.subtitle" class="text-[10px] truncate text-white/40">{{ item.subtitle }}</div>
<div v-if="item.subtitle" class="text-xs truncate text-white/40">{{ item.subtitle }}</div>
</div>
<span class="text-[8px] text-white/20 shrink-0">{{ item.type }}</span>
<span class="text-xs text-white/20 shrink-0">{{ item.type }}</span>
</div>
</template>
<!-- Recent -->
<template v-else-if="activeSubTab === 'recent'">
<div class="flex items-center justify-between mb-2">
<span class="text-[10px] text-white/30">{{ viewHistory.length }} items</span>
<span class="text-xs text-white/30">{{ viewHistory.length }} items</span>
<button
v-if="viewHistory.length > 0"
class="text-[9px] text-red-400/50 hover:text-red-400/80 transition-colors"
class="text-xs text-red-400/50 hover:text-red-400/80 transition-colors"
@click="clearHistory"
>
Clear
@@ -70,14 +70,14 @@
:key="entry.id + entry.viewedAt"
class="flex items-center gap-3 p-3 rounded-xl bg-white/[0.03] hover:bg-white/[0.07] border border-white/5 transition-all duration-150 cursor-pointer"
>
<span class="text-[10px] w-6 h-6 rounded flex items-center justify-center shrink-0" :class="typeStyle(entry.type)">
<span class="text-xs w-6 h-6 rounded flex items-center justify-center shrink-0" :class="typeStyle(entry.type)">
{{ typeIcon(entry.type) }}
</span>
<div class="flex-1 min-w-0">
<div class="text-xs font-semibold truncate text-white/80">{{ entry.title }}</div>
<div v-if="entry.subtitle" class="text-[10px] truncate text-white/40">{{ entry.subtitle }}</div>
<div v-if="entry.subtitle" class="text-xs truncate text-white/40">{{ entry.subtitle }}</div>
</div>
<span class="text-[8px] text-white/20 shrink-0">{{ timeAgo(entry.viewedAt) }}</span>
<span class="text-xs text-white/20 shrink-0">{{ timeAgo(entry.viewedAt) }}</span>
</div>
</template>
@@ -95,13 +95,13 @@
:key="item.id"
class="flex items-center gap-3 p-3 rounded-xl bg-white/[0.03] hover:bg-white/[0.07] border border-white/5 transition-all duration-150"
>
<span class="text-[10px] w-6 h-6 rounded flex items-center justify-center shrink-0" :class="typeStyle(item.type)">
<span class="text-xs w-6 h-6 rounded flex items-center justify-center shrink-0" :class="typeStyle(item.type)">
{{ typeIcon(item.type) }}
</span>
<div class="flex-1 min-w-0">
<div class="text-xs font-semibold truncate text-white/80">{{ item.title }}</div>
</div>
<span class="text-[9px] px-1.5 py-0.5 rounded bg-accent/15 text-accent/80 shrink-0">
<span class="text-xs px-1.5 py-0.5 rounded bg-accent/15 text-accent/80 shrink-0">
{{ item.count }}x
</span>
</div>
@@ -115,11 +115,11 @@
v-model="newCollectionName"
type="text"
placeholder="New collection name..."
class="flex-1 px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
class="flex-1 px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
@keydown.enter="createNewCollection"
/>
<button
class="px-2.5 py-2 rounded-lg text-[10px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
class="px-2.5 py-2 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
:disabled="!newCollectionName.trim()"
@click="createNewCollection"
>
@@ -142,12 +142,12 @@
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<p class="text-xs font-semibold text-white/80 truncate">{{ col.name }}</p>
<p v-if="col.description" class="text-[9px] text-white/30 truncate">{{ col.description }}</p>
<p v-if="col.description" class="text-xs text-white/30 truncate">{{ col.description }}</p>
</div>
<div class="flex items-center gap-1 shrink-0">
<span class="text-[9px] text-white/25">{{ col.items.length }} items</span>
<span class="text-xs text-white/25">{{ col.items.length }} items</span>
<button
class="text-[9px] px-1.5 py-0.5 rounded text-red-400/50 hover:text-red-400/80 hover:bg-red-400/10 transition-colors"
class="text-xs px-1.5 py-0.5 rounded text-red-400/50 hover:text-red-400/80 hover:bg-red-400/10 transition-colors"
@click="deleteCollection(col.id)"
>
Delete
@@ -162,16 +162,16 @@
:key="item.id"
class="aspect-square rounded bg-white/5 flex items-center justify-center"
>
<span class="text-[8px] font-bold" :class="typeStyle(item.type)">{{ typeIcon(item.type) }}</span>
<span class="text-xs font-bold" :class="typeStyle(item.type)">{{ typeIcon(item.type) }}</span>
</div>
</div>
<!-- Items list -->
<div v-for="item in col.items" :key="item.id" class="flex items-center gap-2 text-[10px]">
<div v-for="item in col.items" :key="item.id" class="flex items-center gap-2 text-xs">
<span :class="typeStyle(item.type)" class="w-4 h-4 rounded flex items-center justify-center text-[7px] shrink-0">{{ typeIcon(item.type) }}</span>
<span class="text-white/60 truncate flex-1">{{ item.title }}</span>
<button
class="text-red-400/40 hover:text-red-400/70 transition-colors text-[8px] shrink-0"
class="text-red-400/40 hover:text-red-400/70 transition-colors text-xs shrink-0"
@click="removeFromCollection(col.id, item.id)"
>
x
@@ -187,7 +187,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
<p class="text-xs text-white/30">No tags yet</p>
<p class="text-[10px] text-white/20">Tag items from content cards to organize them</p>
<p class="text-xs text-white/20">Tag items from content cards to organize them</p>
</div>
<!-- Tag cloud -->
@@ -195,7 +195,7 @@
<button
v-for="tc in tagCloud"
:key="tc.tag"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeTagFilter === tc.tag
? 'nav-tab-active'
: 'text-white/40 hover:text-white/70 bg-white/5 hover:bg-white/10'"
@@ -207,15 +207,15 @@
<!-- Filtered items by tag -->
<div v-if="activeTagFilter" class="space-y-2">
<p class="text-[10px] text-white/30">Items tagged "{{ activeTagFilter }}"</p>
<p class="text-xs text-white/30">Items tagged "{{ activeTagFilter }}"</p>
<div
v-for="itemId in getItemsByTag(activeTagFilter)"
:key="itemId"
class="flex items-center gap-2 p-2.5 rounded-xl bg-white/[0.03] border border-white/5"
>
<span class="text-[10px] text-white/60 font-mono truncate">{{ itemId }}</span>
<span class="text-xs text-white/60 font-mono truncate">{{ itemId }}</span>
<button
class="text-[8px] text-red-400/50 hover:text-red-400/80 transition-colors shrink-0"
class="text-xs text-red-400/50 hover:text-red-400/80 transition-colors shrink-0"
@click="removeTag(itemId, activeTagFilter!)"
>
untag
@@ -236,45 +236,45 @@
<template v-else>
<!-- Recently played songs -->
<div v-if="recentSongs.length > 0" class="mb-4">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold mb-2">Recently Played</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold mb-2">Recently Played</p>
<div
v-for="entry in recentSongs.slice(0, 10)"
:key="entry.id"
class="flex items-center gap-2 p-2 rounded-lg bg-white/[0.03] hover:bg-white/[0.07] border border-white/5 mb-1 transition-colors"
>
<span class="text-[10px] w-5 h-5 rounded flex items-center justify-center shrink-0 bg-green-500/20 text-green-400">S</span>
<span class="text-xs w-5 h-5 rounded flex items-center justify-center shrink-0 bg-green-500/20 text-green-400">S</span>
<div class="flex-1 min-w-0">
<p class="text-[11px] text-white/70 truncate">{{ entry.title }}</p>
<p v-if="entry.subtitle" class="text-[9px] text-white/30 truncate">{{ entry.subtitle }}</p>
<p class="text-xs text-white/70 truncate">{{ entry.title }}</p>
<p v-if="entry.subtitle" class="text-xs text-white/30 truncate">{{ entry.subtitle }}</p>
</div>
<span class="text-[8px] text-white/20 shrink-0">{{ timeAgo(entry.viewedAt) }}</span>
<span class="text-xs text-white/20 shrink-0">{{ timeAgo(entry.viewedAt) }}</span>
</div>
</div>
<!-- Most played songs -->
<div v-if="mostPlayedSongs.length > 0" class="mb-4">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold mb-2">Most Played</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold mb-2">Most Played</p>
<div
v-for="item in mostPlayedSongs.slice(0, 10)"
:key="item.id"
class="flex items-center gap-2 p-2 rounded-lg bg-white/[0.03] hover:bg-white/[0.07] border border-white/5 mb-1 transition-colors"
>
<span class="text-[10px] w-5 h-5 rounded flex items-center justify-center shrink-0 bg-green-500/20 text-green-400">S</span>
<span class="text-xs w-5 h-5 rounded flex items-center justify-center shrink-0 bg-green-500/20 text-green-400">S</span>
<div class="flex-1 min-w-0">
<p class="text-[11px] text-white/70 truncate">{{ item.title }}</p>
<p class="text-xs text-white/70 truncate">{{ item.title }}</p>
</div>
<span class="text-[9px] px-1.5 py-0.5 rounded bg-green-400/15 text-green-400/80 shrink-0">{{ item.count }}x</span>
<span class="text-xs px-1.5 py-0.5 rounded bg-green-400/15 text-green-400/80 shrink-0">{{ item.count }}x</span>
</div>
</div>
<!-- By genre -->
<div v-if="songsByGenre.length > 0" class="mb-4">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold mb-2">By Genre</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold mb-2">By Genre</p>
<div class="flex flex-wrap gap-1.5">
<button
v-for="genre in songsByGenre"
:key="genre.genre"
class="text-[10px] px-2 py-1 rounded-md bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
class="text-xs px-2 py-1 rounded-md bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
>
{{ genre.genre }} <span class="text-white/20">{{ genre.count }}</span>
</button>
@@ -283,12 +283,12 @@
<!-- By decade -->
<div v-if="songsByDecade.length > 0">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold mb-2">By Decade</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold mb-2">By Decade</p>
<div class="flex flex-wrap gap-1.5">
<button
v-for="dec in songsByDecade"
:key="dec.decade"
class="text-[10px] px-2 py-1 rounded-md bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
class="text-xs px-2 py-1 rounded-md bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
>
{{ dec.decade }}s <span class="text-white/20">{{ dec.count }}</span>
</button>
@@ -8,7 +8,7 @@
<h3 class="text-sm font-bold" :class="isDark ? 'text-white/90' : 'text-gray-900'">
Favorites
</h3>
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredItems.length }} saved
</span>
</div>
@@ -17,7 +17,7 @@
<button
v-for="filter in typeFilters"
:key="filter.id"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeType === filter.id
? 'nav-tab-active'
: isDark
@@ -63,7 +63,7 @@
: 'bg-black/[0.02] hover:bg-black/[0.05] border border-black/5'"
>
<span
class="text-[10px] w-6 h-6 rounded flex items-center justify-center shrink-0"
class="text-xs w-6 h-6 rounded flex items-center justify-center shrink-0"
:class="typeStyle(item.type)"
>
{{ typeIcon(item.type) }}
@@ -77,7 +77,7 @@
</div>
<div
v-if="item.subtitle"
class="text-[10px] truncate"
class="text-xs truncate"
:class="isDark ? 'text-white/40' : 'text-gray-500'"
>
{{ item.subtitle }}
@@ -25,30 +25,30 @@
<div class="min-w-0 flex-1 py-0.5">
<p class="text-sm font-semibold truncate"
:class="isDark ? 'text-white/90' : 'text-gray-900'">{{ film.title }}</p>
<p class="text-[11px] mt-0.5"
<p class="text-xs mt-0.5"
:class="isDark ? 'text-white/40' : 'text-gray-500'">
{{ film.year }}<template v-if="film.director"> · {{ film.director }}</template>
</p>
<p v-if="isExternal && film.synopsis"
class="text-[10px] mt-0.5 line-clamp-2"
class="text-xs mt-0.5 line-clamp-2"
:class="isDark ? 'text-white/35' : 'text-gray-400'">
{{ film.synopsis }}
</p>
<div class="flex items-center gap-1.5 mt-1.5">
<span v-if="film.rating > 0"
class="text-[10px] font-semibold px-1.5 py-0.5 rounded"
class="text-xs font-semibold px-1.5 py-0.5 rounded"
:class="ratingClass">
{{ film.rating }}
</span>
<span v-if="isExternal"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-info/15 text-info/70' : 'bg-info/10 text-blue-600'">
not in library
</span>
<span
v-for="src in film.sources.slice(0, 3)"
:key="src.type"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-white/8 text-white/50' : 'bg-black/5 text-gray-500'"
>
{{ src.type }}
@@ -40,7 +40,7 @@
<span
v-for="genre in film.genres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md font-medium"
class="text-xs px-2 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'"
>
{{ genre }}
@@ -49,7 +49,7 @@
<div v-if="film.synopsis">
<h4 v-if="isExternal"
class="text-[10px] uppercase tracking-[0.2em] font-semibold mb-1.5"
class="text-xs uppercase tracking-[0.2em] font-semibold mb-1.5"
:class="isDark ? 'text-white/50' : 'text-gray-500'">
Why watch
</h4>
@@ -87,7 +87,7 @@
<div>
<p class="text-xs font-medium"
:class="isDark ? 'text-white/80' : 'text-gray-800'">{{ src.name }}</p>
<p class="text-[10px]"
<p class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ src.quality }}</p>
</div>
</div>
@@ -9,7 +9,7 @@
{{ title }}
</h3>
<div class="flex items-center gap-2 shrink-0">
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredFilms.length }} films
</span>
<slot name="header-actions" />
@@ -20,7 +20,7 @@
v-model="search"
type="text"
placeholder="Search films..."
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10'
: 'bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5'"
@@ -30,7 +30,7 @@
<button
v-for="genre in topGenres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeGenre === genre
? 'nav-tab-active'
: isDark
@@ -69,12 +69,12 @@
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
<div class="absolute bottom-0 left-0 right-0 p-2">
<p class="text-[11px] font-semibold text-white/90 leading-tight truncate">
<p class="text-xs font-semibold text-white/90 leading-tight truncate">
{{ film.title }}
</p>
<div class="flex items-center gap-1 mt-0.5">
<span class="text-[9px] text-accent font-bold"> {{ film.rating }}</span>
<span class="text-[9px] text-white/40">{{ film.year }}</span>
<span class="text-xs text-accent font-bold"> {{ film.rating }}</span>
<span class="text-xs text-white/40">{{ film.year }}</span>
</div>
</div>
@@ -82,7 +82,7 @@
<span
v-for="src in film.sources.slice(0, 2)"
:key="src.type"
class="text-[8px] px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"
class="text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"
>
{{ src.type }}
</span>
@@ -36,7 +36,7 @@
:class="isDark ? 'text-white/50' : 'text-gray-500'">
{{ image.source }}
</p>
<p v-if="image.description" class="text-[11px] mt-1 line-clamp-2 leading-relaxed"
<p v-if="image.description" class="text-xs mt-1 line-clamp-2 leading-relaxed"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
{{ image.description }}
</p>
@@ -49,7 +49,7 @@
Source: {{ image.source }}
</div>
<div v-if="image.width && image.height" class="text-[10px]"
<div v-if="image.width && image.height" class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ image.width }} &times; {{ image.height }}
</div>
@@ -9,7 +9,7 @@
{{ title }}
</h3>
<div class="flex items-center gap-2 shrink-0">
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ images.length }} images
</span>
<slot name="header-actions" />
@@ -45,8 +45,8 @@
</div>
<div v-if="img.title || img.source"
class="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/70 via-black/30 to-transparent">
<p v-if="img.title" class="text-[10px] font-medium text-white/90 truncate">{{ img.title }}</p>
<p v-if="img.source" class="text-[8px] text-white/50 truncate">{{ img.source }}</p>
<p v-if="img.title" class="text-xs font-medium text-white/90 truncate">{{ img.title }}</p>
<p v-if="img.source" class="text-xs text-white/50 truncate">{{ img.source }}</p>
</div>
</button>
</div>
@@ -37,7 +37,7 @@
<!-- Content -->
<div class="relative z-10 flex flex-col justify-end h-full px-5 pb-5 pt-12"
style="min-height: 180px;">
<p class="text-[9px] uppercase tracking-[0.3em] font-medium mb-2"
<p class="text-xs uppercase tracking-[0.3em] font-medium mb-2"
:class="isDark ? 'text-white/40' : 'text-black/40'">
In response to
</p>
@@ -68,7 +68,7 @@
<path v-else stroke-linecap="round" stroke-linejoin="round"
d="M4 6h16M4 12h16M4 18h7" />
</svg>
<p class="text-[9px] uppercase tracking-[0.3em] font-semibold text-center"
<p class="text-xs uppercase tracking-[0.3em] font-semibold text-center"
:class="isDark ? 'text-white/30' : 'text-black/30'">
{{ tile.label }}
</p>
@@ -82,7 +82,7 @@
: 'bg-[#faf9f6] hover:bg-black/[0.02]'"
@click="tile.section && openTile(tile.section)">
<p v-if="tile.label"
class="text-[9px] uppercase tracking-[0.3em] font-semibold mb-2"
class="text-xs uppercase tracking-[0.3em] font-semibold mb-2"
:class="isDark ? 'text-white/30' : 'text-black/35'">
{{ tile.label }}
</p>
@@ -91,7 +91,7 @@
{{ tile.title }}
</h2>
<p v-if="tile.author"
class="text-[10px] mb-2"
class="text-xs mb-2"
:class="isDark ? 'text-white/40' : 'text-black/40'">
By {{ tile.author }}
</p>
@@ -114,7 +114,7 @@
]"
@click="tile.section && openTile(tile.section)">
<p v-if="tile.label"
class="text-[8px] uppercase tracking-[0.25em] font-semibold mb-1.5"
class="text-xs uppercase tracking-[0.25em] font-semibold mb-1.5"
:class="isDark ? 'text-white/25' : 'text-black/30'">
{{ tile.label }}
</p>
@@ -17,12 +17,12 @@
</svg>
</button>
<div class="flex-1 text-center pl-8">
<span class="text-[9px] uppercase tracking-[0.3em] font-semibold"
<span class="text-xs uppercase tracking-[0.3em] font-semibold"
:class="isDark ? 'text-white/30' : 'text-black/30'">
AI Brief
</span>
</div>
<span class="text-[10px] font-mono tabular-nums shrink-0"
<span class="text-xs font-mono tabular-nums shrink-0"
:class="isDark ? 'text-white/25' : 'text-black/25'">
{{ currentIndex + 1 }}/{{ totalSections }}
</span>
@@ -33,7 +33,7 @@
<div class="px-6 py-8 md:px-8 md:py-10 max-w-lg mx-auto my-auto">
<!-- Group label -->
<p v-if="section.group"
class="text-[9px] uppercase tracking-[0.3em] font-semibold mb-4"
class="text-xs uppercase tracking-[0.3em] font-semibold mb-4"
:class="isDark ? 'text-white/25' : 'text-black/30'">
{{ section.group }}
</p>
@@ -30,11 +30,11 @@
<p class="text-sm font-semibold truncate"
:class="isDark ? 'text-white/90' : 'text-gray-900'">{{ article.title }}</p>
<p v-if="article.content"
class="text-[11px] mt-0.5 line-clamp-2"
class="text-xs mt-0.5 line-clamp-2"
:class="isDark ? 'text-white/40' : 'text-gray-500'">
{{ article.content }}
</p>
<p class="text-[10px] mt-1 truncate"
<p class="text-xs mt-1 truncate"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ formatDomain(article.url) }}
</p>
@@ -9,7 +9,7 @@
{{ title }}
</h3>
<div class="flex items-center gap-2 shrink-0">
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredArticles.length }} {{ variant === 'websites' ? 'websites' : 'articles' }}
</span>
<slot name="header-actions" />
@@ -20,7 +20,7 @@
v-model="search"
type="text"
:placeholder="variant === 'websites' ? 'Search websites...' : 'Search articles...'"
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10'
: 'bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5'"
@@ -89,16 +89,16 @@
isDark ? 'bg-black shadow-[inset_0_1px_0_rgba(255,255,255,0.12)]' : 'bg-white shadow-[inset_0_1px_0_rgba(0,0,0,0.06)]'
]"
>
<p class="text-[11px] font-semibold leading-tight line-clamp-2"
<p class="text-xs font-semibold leading-tight line-clamp-2"
:class="isDark ? 'text-white/95' : 'text-gray-900'">
{{ article.title }}
</p>
<p v-if="article.content"
class="text-[9px] line-clamp-1 mt-0.5"
class="text-xs line-clamp-1 mt-0.5"
:class="isDark ? 'text-white/70' : 'text-gray-600'">
{{ article.content }}
</p>
<p class="text-[8px] truncate mt-0.5"
<p class="text-xs truncate mt-0.5"
:class="isDark ? 'text-white/50' : 'text-gray-500'">
{{ formatDomain(article.url) }}
</p>
@@ -25,7 +25,7 @@
<template v-else>
<div class="p-4 border-b border-white/[0.08]">
<h3 class="text-sm font-bold text-white/90 mb-2">Long-Form Articles</h3>
<p class="text-[10px] text-white/30">NIP-23 kind:30023 articles from your network</p>
<p class="text-xs text-white/30">NIP-23 kind:30023 articles from your network</p>
</div>
<div class="flex-1 overflow-y-auto custom-scrollbar px-4 pt-3 pb-16 space-y-2">
@@ -43,13 +43,13 @@
<h4 class="text-xs font-semibold text-white/80 line-clamp-2">
{{ getArticleTitle(article) }}
</h4>
<p class="text-[10px] text-white/40 line-clamp-2">
<p class="text-xs text-white/40 line-clamp-2">
{{ getArticleSummary(article) }}
</p>
<div class="flex items-center gap-2">
<span class="text-[9px] text-white/25 font-mono">{{ truncate(article.pubkey) }}</span>
<span class="text-[9px] text-white/20">{{ formatDate(article.created_at) }}</span>
<span v-if="getArticleImage(article)" class="text-[9px] text-accent/40 ml-auto">has image</span>
<span class="text-xs text-white/25 font-mono">{{ truncate(article.pubkey) }}</span>
<span class="text-xs text-white/20">{{ formatDate(article.created_at) }}</span>
<span v-if="getArticleImage(article)" class="text-xs text-accent/40 ml-auto">has image</span>
</div>
</div>
</button>
@@ -13,7 +13,7 @@
</button>
<div class="flex-1 min-w-0">
<p class="text-xs font-semibold text-white/80 truncate">{{ activeThread.contactName }}</p>
<p class="text-[9px] text-white/30 font-mono truncate">{{ activeThread.contactPubkey }}</p>
<p class="text-xs text-white/30 font-mono truncate">{{ activeThread.contactPubkey }}</p>
</div>
</div>
@@ -31,7 +31,7 @@
: 'bg-white/5 text-white/70'"
>
<p class="text-xs leading-relaxed break-words">{{ msg.content }}</p>
<p class="text-[9px] mt-1 text-white/25 tabular-nums">{{ formatTime(msg.created_at) }}</p>
<p class="text-xs mt-1 text-white/25 tabular-nums">{{ formatTime(msg.created_at) }}</p>
</div>
</div>
</div>
@@ -43,7 +43,7 @@
v-model="messageInput"
type="text"
placeholder="Type a message..."
class="flex-1 px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
class="flex-1 px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
@keydown.enter="sendMessage"
/>
<button
@@ -63,7 +63,7 @@
<div class="flex items-center justify-between gap-2 mb-3">
<h3 class="text-sm font-bold text-white/90">Messages</h3>
<button
class="text-[10px] px-2.5 py-1 rounded bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="text-xs px-2.5 py-1 rounded bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
@click="showNewDM = !showNewDM"
>
{{ showNewDM ? 'Cancel' : 'New' }}
@@ -76,10 +76,10 @@
v-model="newContactPubkey"
type="text"
placeholder="Recipient hex pubkey or npub..."
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
/>
<button
class="text-[10px] px-2.5 py-1 rounded bg-white/5 text-white/60 hover:bg-white/10 transition-colors disabled:opacity-30"
class="text-xs px-2.5 py-1 rounded bg-white/5 text-white/60 hover:bg-white/10 transition-colors disabled:opacity-30"
:disabled="!newContactPubkey.trim()"
@click="startNewDM"
>
@@ -117,7 +117,7 @@
@click="selectContact(thread.contactPubkey)"
>
<div class="flex items-start gap-2.5">
<div class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center text-[10px] font-bold bg-accent/20 text-accent">
<div class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center text-xs font-bold bg-accent/20 text-accent">
{{ thread.contactName.charAt(0).toUpperCase() }}
</div>
<div class="flex-1 min-w-0">
@@ -125,11 +125,11 @@
<span class="text-xs font-semibold truncate text-white/80">
{{ thread.contactName }}
</span>
<span v-if="thread.lastMessage" class="text-[9px] ml-auto shrink-0 text-white/20">
<span v-if="thread.lastMessage" class="text-xs ml-auto shrink-0 text-white/20">
{{ formatTime(thread.lastMessage.created_at) }}
</span>
</div>
<p v-if="thread.lastMessage" class="text-[11px] mt-1 text-white/40 truncate">
<p v-if="thread.lastMessage" class="text-xs mt-1 text-white/40 truncate">
{{ thread.lastMessage.content }}
</p>
</div>
@@ -5,7 +5,7 @@
<button
v-for="tab in subTabs"
:key="tab.id"
class="text-[10px] px-2.5 py-1 rounded-md transition-all duration-150"
class="text-xs px-2.5 py-1 rounded-md transition-all duration-150"
:class="activeSubTab === tab.id
? 'nav-tab-active'
: 'text-white/40 hover:text-white/70 hover:bg-white/5'"
@@ -45,7 +45,7 @@
Nostr Feed
</h3>
<div class="flex items-center gap-2 shrink-0">
<span class="text-[10px] font-mono text-white/30">
<span class="text-xs font-mono text-white/30">
{{ filteredNotes.length }} notes
</span>
<slot name="header-actions" />
@@ -66,20 +66,20 @@
<textarea
ref="composeRef"
v-model="composeText"
class="w-full bg-transparent text-xs text-white/80 placeholder:text-white/25 outline-none resize-none min-h-[80px]"
class="w-full bg-transparent text-base text-white/80 placeholder:text-white/25 outline-none resize-none min-h-[80px]"
placeholder="What's on your mind?"
@keydown.meta.enter="publishNote"
@keydown.ctrl.enter="publishNote"
/>
<div class="flex items-center justify-between gap-2">
<span
class="text-[10px] tabular-nums"
class="text-xs tabular-nums"
:class="composeText.length > 280 ? 'text-accent/80' : 'text-white/25'"
>
{{ composeText.length }}
</span>
<button
class="text-[10px] px-3 py-1.5 rounded-lg bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
class="text-xs px-3 py-1.5 rounded-lg bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
:disabled="!composeText.trim() || isPublishing"
@click="publishNote"
>
@@ -92,7 +92,7 @@
<div
v-for="result in publishResults"
:key="result.url"
class="flex items-center gap-2 text-[10px] px-2 py-1 rounded bg-white/[0.02]"
class="flex items-center gap-2 text-xs px-2 py-1 rounded bg-white/[0.02]"
>
<span
class="w-1.5 h-1.5 rounded-full shrink-0"
@@ -111,12 +111,12 @@
v-model="search"
type="text"
placeholder="Search notes, npubs..."
class="flex-1 px-3 py-2 rounded-lg text-xs outline-none transition-colors bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10"
class="flex-1 px-3 py-2 rounded-lg text-base outline-none transition-colors bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10"
@keydown.enter="triggerNostrSearch"
/>
<button
v-if="search.trim()"
class="px-2.5 py-2 rounded-lg text-[10px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30 shrink-0"
class="px-2.5 py-2 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30 shrink-0"
:disabled="isSearching"
@click="triggerNostrSearch"
>
@@ -128,7 +128,7 @@
<button
v-for="kind in noteKinds"
:key="kind.id"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeKind === kind.id
? 'nav-tab-active'
: 'text-white/40 hover:text-white/70 hover:bg-white/5'"
@@ -159,7 +159,7 @@
@click="selectedNoteId = note.id"
>
<div class="flex items-start gap-2.5">
<div class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center text-[10px] font-bold bg-purple-500/20 text-purple-400">
<div class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center text-xs font-bold bg-purple-500/20 text-purple-400">
{{ note.authorName?.charAt(0)?.toUpperCase() ?? '?' }}
</div>
<div class="flex-1 min-w-0">
@@ -167,7 +167,7 @@
<span class="text-xs font-semibold truncate text-white/80">
{{ note.authorName ?? 'anon' }}
</span>
<span v-if="note.nip05" class="text-[9px] truncate text-purple-400/60 flex items-center gap-0.5">
<span v-if="note.nip05" class="text-xs truncate text-purple-400/60 flex items-center gap-0.5">
<svg
v-if="nip05Status[note.id] === true"
class="w-2.5 h-2.5 text-emerald-400 shrink-0"
@@ -179,23 +179,23 @@
</svg>
{{ note.nip05 }}
</span>
<span class="text-[9px] ml-auto shrink-0 text-white/20">
<span class="text-xs ml-auto shrink-0 text-white/20">
{{ formatTime(note.created_at) }}
</span>
</div>
<p class="text-[11px] mt-1 leading-relaxed line-clamp-3 text-white/60">
<p class="text-xs mt-1 leading-relaxed line-clamp-3 text-white/60">
{{ note.content }}
</p>
<div class="flex items-center gap-3 mt-2">
<button
class="text-[9px] px-1.5 py-0.5 rounded bg-white/5 text-accent/60 hover:text-accent hover:bg-accent/10 transition-colors"
class="text-xs px-1.5 py-0.5 rounded bg-white/5 text-accent/60 hover:text-accent hover:bg-accent/10 transition-colors"
@click.stop="openZap(note)"
>
Zap
</button>
<span
v-if="note.kind !== 1"
class="text-[9px] px-1.5 py-0.5 rounded bg-white/5 text-white/30"
class="text-xs px-1.5 py-0.5 rounded bg-white/5 text-white/30"
>
kind:{{ note.kind }}
</span>
@@ -206,12 +206,12 @@
<!-- Relay status -->
<div class="mt-4 pt-4 border-t border-white/5">
<p class="text-[10px] font-medium mb-2 text-white/30">Relays</p>
<p class="text-xs font-medium mb-2 text-white/30">Relays</p>
<div class="space-y-1">
<div
v-for="relay in relayStates"
:key="relay.url"
class="flex items-center gap-2 text-[10px] px-2 py-1 rounded-lg bg-white/[0.02]"
class="flex items-center gap-2 text-xs px-2 py-1 rounded-lg bg-white/[0.02]"
>
<span
class="w-1.5 h-1.5 rounded-full shrink-0"
@@ -8,7 +8,7 @@
<button
v-for="lt in listTypes"
:key="lt.kind"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeListKind === lt.kind
? 'nav-tab-active'
: 'text-white/40 hover:text-white/70 hover:bg-white/5'"
@@ -35,11 +35,11 @@
v-model="newItemValue"
type="text"
:placeholder="activeListKind === 3 ? 'Add npub or hex pubkey...' : 'Add item (hex id or npub)...'"
class="flex-1 px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="flex-1 px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
@keydown.enter="addItem"
/>
<button
class="px-2.5 py-2 rounded-lg text-[10px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
class="px-2.5 py-2 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
:disabled="!newItemValue.trim()"
@click="addItem"
>
@@ -53,15 +53,15 @@
:key="item.value"
class="flex items-center gap-2 p-2.5 rounded-xl bg-white/[0.03] border border-white/5"
>
<div class="w-6 h-6 rounded-full shrink-0 flex items-center justify-center text-[8px] font-bold bg-purple-500/20 text-purple-400">
<div class="w-6 h-6 rounded-full shrink-0 flex items-center justify-center text-xs font-bold bg-purple-500/20 text-purple-400">
{{ item.tag === 'p' ? 'P' : item.tag === 'e' ? 'E' : item.tag === 't' ? '#' : '?' }}
</div>
<div class="flex-1 min-w-0">
<p class="text-[10px] text-white/60 font-mono truncate">{{ item.displayValue }}</p>
<p v-if="item.petname" class="text-[9px] text-white/30">{{ item.petname }}</p>
<p class="text-xs text-white/60 font-mono truncate">{{ item.displayValue }}</p>
<p v-if="item.petname" class="text-xs text-white/30">{{ item.petname }}</p>
</div>
<button
class="text-[9px] px-2 py-1 rounded bg-white/5 text-red-400/50 hover:text-red-400/80 hover:bg-red-400/10 transition-colors shrink-0"
class="text-xs px-2 py-1 rounded bg-white/5 text-red-400/50 hover:text-red-400/80 hover:bg-red-400/10 transition-colors shrink-0"
@click="removeItem(item)"
>
Remove
@@ -82,7 +82,7 @@
{{ isPublishingList ? 'Publishing...' : 'Publish updated list' }}
</button>
<p v-if="publishStatus" class="text-[10px] text-center" :class="publishOk ? 'text-emerald-400/60' : 'text-red-400/60'">
<p v-if="publishStatus" class="text-xs text-center" :class="publishOk ? 'text-emerald-400/60' : 'text-red-400/60'">
{{ publishStatus }}
</p>
</div>
@@ -27,7 +27,7 @@
</span>
</div>
<p class="text-sm font-bold text-white/90 mt-2">{{ profile.display_name || profile.name || 'Anonymous' }}</p>
<p v-if="profile.nip05" class="text-[10px] text-purple-400/60">{{ profile.nip05 }}</p>
<p v-if="profile.nip05" class="text-xs text-purple-400/60">{{ profile.nip05 }}</p>
<p v-if="profile.about" class="text-xs text-white/50 mt-1 line-clamp-2">{{ profile.about }}</p>
</div>
</div>
@@ -35,80 +35,80 @@
<!-- Edit form -->
<div class="space-y-3">
<div>
<label class="text-[10px] text-white/30 block mb-1">Display Name</label>
<label class="text-xs text-white/30 block mb-1">Display Name</label>
<input
v-model="profile.display_name"
type="text"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
placeholder="Your display name"
/>
</div>
<div>
<label class="text-[10px] text-white/30 block mb-1">Username</label>
<label class="text-xs text-white/30 block mb-1">Username</label>
<input
v-model="profile.name"
type="text"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
placeholder="username"
/>
</div>
<div>
<label class="text-[10px] text-white/30 block mb-1">Bio</label>
<label class="text-xs text-white/30 block mb-1">Bio</label>
<textarea
v-model="profile.about"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors resize-none min-h-[60px]"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors resize-none min-h-[60px]"
placeholder="Tell the world about yourself"
/>
</div>
<div>
<label class="text-[10px] text-white/30 block mb-1">Avatar URL</label>
<label class="text-xs text-white/30 block mb-1">Avatar URL</label>
<input
v-model="profile.picture"
type="url"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
placeholder="https://example.com/avatar.jpg"
/>
</div>
<div>
<label class="text-[10px] text-white/30 block mb-1">Banner URL</label>
<label class="text-xs text-white/30 block mb-1">Banner URL</label>
<input
v-model="profile.banner"
type="url"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
placeholder="https://example.com/banner.jpg"
/>
</div>
<div>
<label class="text-[10px] text-white/30 block mb-1">Website</label>
<label class="text-xs text-white/30 block mb-1">Website</label>
<input
v-model="profile.website"
type="url"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
placeholder="https://example.com"
/>
</div>
<div>
<label class="text-[10px] text-white/30 block mb-1">NIP-05 Address</label>
<label class="text-xs text-white/30 block mb-1">NIP-05 Address</label>
<input
v-model="profile.nip05"
type="text"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
placeholder="you@example.com"
/>
</div>
<div>
<label class="text-[10px] text-white/30 block mb-1">Lightning Address</label>
<label class="text-xs text-white/30 block mb-1">Lightning Address</label>
<input
v-model="profile.lud16"
type="text"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
placeholder="you@getalby.com"
/>
</div>
@@ -123,7 +123,7 @@
</button>
<!-- Status -->
<div v-if="publishStatus" class="text-[10px] text-center" :class="publishSuccess ? 'text-emerald-400/60' : 'text-red-400/60'">
<div v-if="publishStatus" class="text-xs text-center" :class="publishSuccess ? 'text-emerald-400/60' : 'text-red-400/60'">
{{ publishStatus }}
</div>
</div>
@@ -9,11 +9,11 @@
v-model="newRelayUrl"
type="text"
placeholder="wss://relay.example.com"
class="flex-1 px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="flex-1 px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
@keydown.enter="addNewRelay"
/>
<button
class="px-3 py-2 rounded-lg text-[10px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
class="px-3 py-2 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
:disabled="!newRelayUrl.trim()"
@click="addNewRelay"
>
@@ -37,13 +37,13 @@
<span class="text-xs font-mono text-white/70 truncate flex-1">{{ relay.url }}</span>
<span
v-if="relay.latencyMs !== null"
class="text-[9px] tabular-nums shrink-0"
class="text-xs tabular-nums shrink-0"
:class="relay.latencyMs < 200 ? 'text-emerald-400/60' : relay.latencyMs < 500 ? 'text-yellow-400/60' : 'text-red-400/60'"
>
{{ relay.latencyMs }}ms
</span>
<span
class="text-[9px] shrink-0"
class="text-xs shrink-0"
:class="relay.connected ? 'text-emerald-400/60' : 'text-red-400/60'"
>
{{ relay.connected ? 'Connected' : 'Disconnected' }}
@@ -53,7 +53,7 @@
<!-- Controls -->
<div class="flex items-center gap-2">
<button
class="text-[9px] px-2 py-1 rounded transition-colors"
class="text-xs px-2 py-1 rounded transition-colors"
:class="relay.read
? 'bg-accent/15 text-accent/80'
: 'bg-white/5 text-white/30 hover:text-white/50'"
@@ -62,7 +62,7 @@
Read
</button>
<button
class="text-[9px] px-2 py-1 rounded transition-colors"
class="text-xs px-2 py-1 rounded transition-colors"
:class="relay.write
? 'bg-accent/15 text-accent/80'
: 'bg-white/5 text-white/30 hover:text-white/50'"
@@ -72,14 +72,14 @@
</button>
<div class="flex-1" />
<button
class="text-[9px] px-2 py-1 rounded bg-white/5 text-white/30 hover:text-white/50 transition-colors"
class="text-xs px-2 py-1 rounded bg-white/5 text-white/30 hover:text-white/50 transition-colors"
:disabled="testingRelay === relay.url"
@click="testConnection(relay.url)"
>
{{ testingRelay === relay.url ? 'Testing...' : 'Test' }}
</button>
<button
class="text-[9px] px-2 py-1 rounded bg-white/5 text-red-400/50 hover:text-red-400/80 hover:bg-red-400/10 transition-colors"
class="text-xs px-2 py-1 rounded bg-white/5 text-red-400/50 hover:text-red-400/80 hover:bg-red-400/10 transition-colors"
@click="removeRelay(relay.url)"
>
Remove
@@ -89,7 +89,7 @@
<!-- Test result -->
<p
v-if="testResults[relay.url] !== undefined"
class="text-[9px]"
class="text-xs"
:class="testResults[relay.url] !== null ? 'text-emerald-400/60' : 'text-red-400/60'"
>
{{ testResults[relay.url] !== null ? `Reachable (${testResults[relay.url]}ms)` : 'Unreachable' }}
@@ -99,13 +99,13 @@
<!-- Import NIP-65 -->
<div v-if="isLoggedIn" class="mt-4 pt-4 border-t border-white/5">
<button
class="w-full text-left px-3 py-2 rounded-lg text-[10px] bg-white/5 text-white/40 hover:text-white/60 hover:bg-white/10 transition-colors"
class="w-full text-left px-3 py-2 rounded-lg text-xs bg-white/5 text-white/40 hover:text-white/60 hover:bg-white/10 transition-colors"
:disabled="isImporting"
@click="importFromNIP65"
>
{{ isImporting ? 'Importing...' : 'Import relays from NIP-65 (kind:10002)' }}
</button>
<p v-if="importMessage" class="text-[9px] mt-1 text-white/30">{{ importMessage }}</p>
<p v-if="importMessage" class="text-xs mt-1 text-white/30">{{ importMessage }}</p>
</div>
</div>
</div>
@@ -21,18 +21,18 @@
<!-- Root note -->
<div v-if="rootNote" class="rounded-xl bg-white/[0.05] border border-white/10 p-3">
<div class="flex items-center gap-1.5 mb-1">
<div class="w-6 h-6 rounded-full shrink-0 flex items-center justify-center text-[9px] font-bold bg-purple-500/20 text-purple-400">
<div class="w-6 h-6 rounded-full shrink-0 flex items-center justify-center text-xs font-bold bg-purple-500/20 text-purple-400">
{{ rootNote.authorName?.charAt(0)?.toUpperCase() ?? '?' }}
</div>
<span class="text-xs font-semibold text-white/80">{{ rootNote.authorName ?? 'anon' }}</span>
<span class="text-[9px] ml-auto text-white/20">{{ formatTime(rootNote.created_at) }}</span>
<span class="text-xs ml-auto text-white/20">{{ formatTime(rootNote.created_at) }}</span>
</div>
<p class="text-xs text-white/70 leading-relaxed whitespace-pre-wrap">{{ rootNote.content }}</p>
</div>
<!-- Replies -->
<div v-if="threadTree.length > 0" class="space-y-1">
<p class="text-[10px] text-white/30 font-medium mt-3 mb-1">{{ threadTree.length }} replies</p>
<p class="text-xs text-white/30 font-medium mt-3 mb-1">{{ threadTree.length }} replies</p>
<ThreadNode
v-for="node in threadTree"
:key="node.note.id"
@@ -49,7 +49,7 @@
<!-- Reply input -->
<div v-if="rootNote && isLoggedIn" class="px-4 py-3 border-t border-white/[0.08]">
<p v-if="replyTo" class="text-[9px] text-white/30 mb-1">
<p v-if="replyTo" class="text-xs text-white/30 mb-1">
Replying to {{ replyTo.authorName ?? 'anon' }}
<button class="text-accent/60 ml-1" @click="replyTo = null">cancel</button>
</p>
@@ -58,7 +58,7 @@
v-model="replyText"
type="text"
placeholder="Reply..."
class="flex-1 px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
class="flex-1 px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
@keydown.enter="sendReply"
/>
<button
@@ -24,18 +24,18 @@
<div class="min-w-0 flex-1 py-0.5">
<p class="text-sm font-semibold truncate"
:class="isDark ? 'text-white/90' : 'text-gray-900'">{{ place.name }}</p>
<p class="text-[11px] mt-0.5 truncate"
<p class="text-xs mt-0.5 truncate"
:class="isDark ? 'text-white/40' : 'text-gray-500'">
{{ place.cuisine || place.category }}<template v-if="place.city"> · {{ place.city }}</template>
</p>
<div class="flex items-center gap-1.5 mt-1.5">
<span v-if="place.rating && place.rating > 0"
class="text-[10px] font-semibold px-1.5 py-0.5 rounded"
class="text-xs font-semibold px-1.5 py-0.5 rounded"
:class="ratingClass">
{{ place.rating.toFixed(1) }}
</span>
<span v-if="place.priceLevel"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-white/8 text-white/50' : 'bg-black/5 text-gray-500'">
{{ '$'.repeat(place.priceLevel) }}
</span>
@@ -122,7 +122,7 @@
<div>
<p class="text-xs font-medium"
:class="isDark ? 'text-white/80' : 'text-gray-800'">{{ link.name }}</p>
<p v-if="link.desc" class="text-[10px]"
<p v-if="link.desc" class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ link.desc }}</p>
</div>
</div>
@@ -6,7 +6,7 @@
{{ title || 'Places' }}
</h3>
<div class="shrink-0 flex items-center gap-2">
<span class="text-[10px]" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredPlaces.length }} places
</span>
<slot name="header-actions" />
@@ -16,7 +16,7 @@
v-model="search"
type="text"
:placeholder="`Search places...`"
class="w-full text-xs px-3 py-2 rounded-lg outline-none transition-colors"
class="w-full text-base px-3 py-2 rounded-lg outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder-white/25 focus:bg-white/8'
: 'bg-black/5 text-gray-800 placeholder-gray-400 focus:bg-black/8'"
@@ -25,7 +25,7 @@
<button
v-for="cat in topCategories"
:key="cat"
class="text-[9px] px-2 py-1 rounded-md font-medium transition-all duration-150"
class="text-xs px-2 py-1 rounded-md font-medium transition-all duration-150"
:class="activeCategory === cat
? 'nav-tab-active'
: isDark
@@ -60,16 +60,16 @@
/>
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
<div class="absolute bottom-0 left-0 right-0 p-2">
<p class="text-[11px] font-semibold text-white/90 truncate">{{ place.name }}</p>
<p class="text-[9px] text-white/40 truncate mt-0.5">{{ place.cuisine || place.category }}</p>
<p class="text-xs font-semibold text-white/90 truncate">{{ place.name }}</p>
<p class="text-xs text-white/40 truncate mt-0.5">{{ place.cuisine || place.category }}</p>
</div>
<div v-if="place.rating" class="absolute top-1.5 right-1.5">
<span class="text-[8px] px-1.5 py-0.5 rounded bg-black/60 text-amber-400 backdrop-blur-sm font-semibold">
<span class="text-xs px-1.5 py-0.5 rounded bg-black/60 text-amber-400 backdrop-blur-sm font-semibold">
{{ place.rating.toFixed(1) }}
</span>
</div>
<div v-if="place.priceLevel" class="absolute top-1.5 left-1.5">
<span class="text-[8px] px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm">
<span class="text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm">
{{ '$'.repeat(place.priceLevel) }}
</span>
</div>
@@ -25,20 +25,20 @@
<div class="min-w-0 flex-1 py-0.5">
<p class="text-sm font-semibold truncate"
:class="isDark ? 'text-white/90' : 'text-gray-900'">{{ podcast.title }}</p>
<p class="text-[11px] mt-0.5"
<p class="text-xs mt-0.5"
:class="isDark ? 'text-white/40' : 'text-gray-500'">
{{ podcast.host || 'Podcast' }}<template v-if="podcast.year"> · {{ podcast.year }}</template>
</p>
<div class="flex items-center gap-1.5 mt-1.5">
<span v-if="isExternal"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-info/15 text-info/70' : 'bg-info/10 text-blue-600'">
not in library
</span>
<span
v-for="src in podcast.sources.slice(0, 3)"
:key="src.type"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-white/8 text-white/50' : 'bg-black/5 text-gray-500'"
>
{{ src.type }}
@@ -46,7 +46,7 @@
<span
v-for="genre in podcast.genres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md font-medium"
class="text-xs px-2 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'"
>
{{ genre }}
@@ -95,7 +95,7 @@
<div>
<p class="text-xs font-medium"
:class="isDark ? 'text-white/80' : 'text-gray-800'">{{ link.name }}</p>
<p v-if="link.desc" class="text-[10px]"
<p v-if="link.desc" class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ link.desc }}</p>
</div>
</div>
@@ -9,7 +9,7 @@
{{ title }}
</h3>
<div class="flex items-center gap-2 shrink-0">
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredPodcasts.length }} podcasts
</span>
<slot name="header-actions" />
@@ -20,7 +20,7 @@
v-model="search"
type="text"
placeholder="Search podcasts..."
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10'
: 'bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5'"
@@ -30,7 +30,7 @@
<button
v-for="genre in topGenres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeGenre === genre
? 'nav-tab-active'
: isDark
@@ -69,17 +69,17 @@
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
<div class="absolute bottom-0 left-0 right-0 p-2">
<p class="text-[11px] font-semibold text-white/90 leading-tight truncate">
<p class="text-xs font-semibold text-white/90 leading-tight truncate">
{{ podcast.title }}
</p>
<p class="text-[9px] text-white/40 truncate mt-0.5">{{ podcast.host || 'Podcast' }}</p>
<p class="text-xs text-white/40 truncate mt-0.5">{{ podcast.host || 'Podcast' }}</p>
</div>
<div class="absolute top-1.5 right-1.5 flex gap-0.5 flex-wrap justify-end max-w-[60%]">
<span
v-for="src in podcast.sources.slice(0, 2)"
:key="src.type"
class="text-[8px] px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"
class="text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"
>
{{ src.type }}
</span>
@@ -9,7 +9,7 @@
<!-- Breadcrumb: "Projects" root (clickable when deeper) -->
<button
v-if="viewState !== 'projects'"
class="text-[10px] shrink-0 transition-colors"
class="text-xs shrink-0 transition-colors"
:class="isDark
? 'text-white/40 hover:text-white/70 hover:underline'
: 'text-gray-400 hover:text-gray-700 hover:underline'"
@@ -17,7 +17,7 @@
>
Projects
</button>
<span v-if="viewState !== 'projects'" class="text-[10px] shrink-0"
<span v-if="viewState !== 'projects'" class="text-xs shrink-0"
:class="isDark ? 'text-white/20' : 'text-gray-300'">/</span>
<!-- Current location (not clickable) -->
@@ -29,11 +29,11 @@
<div class="flex items-center gap-2 shrink-0">
<!-- Subtitle info -->
<p v-if="viewState === 'projects'" class="text-[10px]"
<p v-if="viewState === 'projects'" class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ projectList.length }} repos
</p>
<p v-else-if="viewState === 'filetree'" class="text-[10px]"
<p v-else-if="viewState === 'filetree'" class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ activeProject?.language }}
</p>
@@ -49,7 +49,7 @@
v-model="search"
type="text"
placeholder="Search projects..."
class="flex-1 min-w-0 px-3 py-2 rounded-lg text-xs bg-transparent outline-none"
class="flex-1 min-w-0 px-3 py-2 rounded-lg text-base bg-transparent outline-none"
:class="isDark
? 'text-white/80 placeholder:text-white/20 border border-white/10 focus:border-white/25'
: 'text-gray-800 placeholder:text-gray-400 border border-black/10 focus:border-black/20'"
@@ -80,7 +80,7 @@
v-model="newProjectName"
type="text"
placeholder="Project name..."
class="w-full px-3 py-2 rounded-lg text-xs bg-transparent outline-none mb-2"
class="w-full px-3 py-2 rounded-lg text-base bg-transparent outline-none mb-2"
:class="isDark
? 'text-white/80 placeholder:text-white/20 border border-white/10 focus:border-white/25'
: 'text-gray-800 placeholder:text-gray-400 border border-black/10 focus:border-black/20'"
@@ -89,14 +89,14 @@
/>
<div class="flex justify-end gap-2">
<button
class="text-[10px] px-2.5 py-1 rounded-lg transition-colors"
class="text-xs px-2.5 py-1 rounded-lg transition-colors"
:class="isDark ? 'text-white/40 hover:text-white/70' : 'text-gray-500 hover:text-gray-800'"
@click="cancelCreateProject"
>
Cancel
</button>
<button
class="text-[10px] px-2.5 py-1 rounded-lg font-medium transition-colors"
class="text-xs px-2.5 py-1 rounded-lg font-medium transition-colors"
:class="isDark ? 'bg-accent/20 text-accent hover:bg-accent/30' : 'bg-accent/10 text-accent hover:bg-accent/20'"
:disabled="!newProjectName.trim()"
@click="confirmCreateProject"
@@ -127,7 +127,7 @@
:class="isDark ? 'text-white/80' : 'text-gray-800'">
{{ project.name }}
</p>
<p class="text-[10px] mt-0.5 truncate"
<p class="text-xs mt-0.5 truncate"
:class="isDark ? 'text-white/25' : 'text-gray-400'">
{{ project.language }}
</p>
@@ -15,13 +15,13 @@
<textarea
v-model="noteContent"
class="w-full h-32 px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors resize-none"
class="w-full h-32 px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors resize-none"
placeholder="Add a note about this content..."
/>
<div class="rounded-lg bg-white/[0.03] border border-white/5 p-3">
<p class="text-[10px] text-white/25 mb-1">Preview</p>
<p class="text-[11px] text-white/60 whitespace-pre-wrap">{{ previewText }}</p>
<p class="text-xs text-white/25 mb-1">Preview</p>
<p class="text-xs text-white/60 whitespace-pre-wrap">{{ previewText }}</p>
</div>
<div class="flex gap-2">
@@ -40,11 +40,11 @@
</button>
</div>
<p v-if="!isLoggedIn" class="text-[10px] text-yellow-400/60 text-center">
<p v-if="!isLoggedIn" class="text-xs text-yellow-400/60 text-center">
Sign in with Nostr to share
</p>
<p v-if="publishResult" class="text-[10px] text-center" :class="publishOk ? 'text-emerald-400/60' : 'text-red-400/60'">
<p v-if="publishResult" class="text-xs text-center" :class="publishOk ? 'text-emerald-400/60' : 'text-red-400/60'">
{{ publishResult }}
</p>
</div>
@@ -1,9 +1,9 @@
<template>
<div v-if="isLoading || suggestions.length > 0" class="mt-4 pt-4 border-t border-white/[0.08]">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold mb-2">More Like This</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold mb-2">More Like This</p>
<div v-if="isLoading" class="py-3">
<p class="text-[10px] text-white/30">Finding similar content...</p>
<p class="text-xs text-white/30">Finding similar content...</p>
</div>
<div v-else class="space-y-1.5">
@@ -12,8 +12,8 @@
:key="i"
class="p-2.5 rounded-lg bg-white/[0.03] border border-white/5"
>
<p class="text-[11px] font-semibold text-white/80">{{ item.title }}</p>
<p class="text-[9px] text-white/40 mt-0.5">{{ item.reason }}</p>
<p class="text-xs font-semibold text-white/80">{{ item.title }}</p>
<p class="text-xs text-white/40 mt-0.5">{{ item.reason }}</p>
</div>
</div>
</div>
@@ -25,20 +25,20 @@
<div class="min-w-0 flex-1 py-0.5">
<p class="text-sm font-semibold truncate"
:class="isDark ? 'text-white/90' : 'text-gray-900'">{{ song.title }}</p>
<p class="text-[11px] mt-0.5"
<p class="text-xs mt-0.5"
:class="isDark ? 'text-white/40' : 'text-gray-500'">
{{ song.artist }}<template v-if="song.year"> · {{ song.year }}</template>
</p>
<div class="flex items-center gap-1.5 mt-1.5">
<span v-if="isExternal"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-info/15 text-info/70' : 'bg-info/10 text-blue-600'">
not in library
</span>
<span
v-for="src in (song.sources ?? []).slice(0, 3)"
:key="src.type"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-white/8 text-white/50' : 'bg-black/5 text-gray-500'"
>
{{ src.type }}
@@ -52,7 +52,7 @@
<span
v-for="genre in song.genres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md font-medium"
class="text-xs px-2 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'"
>
{{ genre }}
@@ -91,7 +91,7 @@
<div>
<p class="text-xs font-medium"
:class="isDark ? 'text-white/80' : 'text-gray-800'">{{ link.name }}</p>
<p v-if="link.desc" class="text-[10px]"
<p v-if="link.desc" class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ link.desc }}</p>
</div>
</div>
@@ -9,7 +9,7 @@
{{ title }}
</h3>
<div class="flex items-center gap-2 shrink-0">
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredSongs.length }} songs
</span>
<slot name="header-actions" />
@@ -20,7 +20,7 @@
v-model="search"
type="text"
placeholder="Search songs..."
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10'
: 'bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5'"
@@ -30,7 +30,7 @@
<button
v-for="genre in topGenres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeGenre === genre
? 'nav-tab-active'
: isDark
@@ -80,17 +80,17 @@
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
<div class="absolute bottom-0 left-0 right-0 p-2">
<p class="text-[11px] font-semibold text-white/90 leading-tight truncate">
<p class="text-xs font-semibold text-white/90 leading-tight truncate">
{{ song.title }}
</p>
<p class="text-[9px] text-white/40 truncate mt-0.5">{{ song.artist }}</p>
<p class="text-xs text-white/40 truncate mt-0.5">{{ song.artist }}</p>
</div>
<div class="absolute top-1.5 right-1.5 flex gap-0.5 flex-wrap justify-end max-w-[60%]">
<span
v-for="src in (song.sources ?? []).slice(0, 2)"
:key="src.type"
class="text-[8px] px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"
class="text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"
>
{{ src.type }}
</span>
@@ -25,33 +25,33 @@
<div class="min-w-0 flex-1 py-0.5">
<p class="text-sm font-semibold truncate"
:class="isDark ? 'text-white/90' : 'text-gray-900'">{{ series.title }}</p>
<p class="text-[11px] mt-0.5"
<p class="text-xs mt-0.5"
:class="isDark ? 'text-white/40' : 'text-gray-500'">
{{ yearDisplay }}<template v-if="series.network"> · {{ series.network }}</template>
</p>
<p v-if="series.synopsis"
class="text-[10px] mt-0.5 line-clamp-2"
class="text-xs mt-0.5 line-clamp-2"
:class="isDark ? 'text-white/35' : 'text-gray-400'">
{{ series.synopsis }}
</p>
<div class="flex items-center gap-1.5 mt-1.5">
<span v-if="series.rating && series.rating > 0"
class="text-[10px] font-semibold px-1.5 py-0.5 rounded"
class="text-xs font-semibold px-1.5 py-0.5 rounded"
:class="ratingClass">
{{ series.rating.toFixed(1) }}
</span>
<span v-if="series.seasons"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-white/8 text-white/50' : 'bg-black/5 text-gray-500'">
{{ series.seasons }}S
</span>
<span v-if="series.status === 'ongoing'"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-success/15 text-success/70' : 'bg-green-50 text-green-600'">
ongoing
</span>
<span v-else-if="series.status === 'ended'"
class="text-[9px] px-1.5 py-0.5 rounded font-medium"
class="text-xs px-1.5 py-0.5 rounded font-medium"
:class="isDark ? 'bg-white/8 text-white/40' : 'bg-black/5 text-gray-400'">
ended
</span>
@@ -60,7 +60,7 @@
<span
v-for="genre in series.genres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md font-medium"
class="text-xs px-2 py-1 rounded-md font-medium"
:class="isDark ? 'bg-white/10 text-white/60' : 'bg-black/5 text-gray-600'"
>
{{ genre }}
@@ -108,7 +108,7 @@
<div>
<p class="text-xs font-medium"
:class="isDark ? 'text-white/80' : 'text-gray-800'">{{ link.name }}</p>
<p v-if="link.desc" class="text-[10px]"
<p v-if="link.desc" class="text-xs"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ link.desc }}</p>
</div>
</div>
@@ -9,7 +9,7 @@
{{ title }}
</h3>
<div class="flex items-center gap-2 shrink-0">
<span class="text-[10px] font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
<span class="text-xs font-mono" :class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ filteredSeries.length }} series
</span>
<slot name="header-actions" />
@@ -20,7 +20,7 @@
v-model="search"
type="text"
placeholder="Search TV series..."
class="w-full px-3 py-2 rounded-lg text-xs outline-none transition-colors"
class="w-full px-3 py-2 rounded-lg text-base outline-none transition-colors"
:class="isDark
? 'bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10'
: 'bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5'"
@@ -30,7 +30,7 @@
<button
v-for="genre in topGenres"
:key="genre"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeGenre === genre
? 'nav-tab-active'
: isDark
@@ -69,22 +69,22 @@
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
<div class="absolute bottom-0 left-0 right-0 p-2">
<p class="text-[11px] font-semibold text-white/90 leading-tight truncate">
<p class="text-xs font-semibold text-white/90 leading-tight truncate">
{{ s.title }}
</p>
<p class="text-[9px] text-white/40 truncate mt-0.5">
<p class="text-xs text-white/40 truncate mt-0.5">
{{ yearDisplay(s) }}<template v-if="s.seasons"> · {{ s.seasons }}S</template>
</p>
</div>
<div v-if="s.rating" class="absolute top-1.5 left-1.5">
<span class="text-[9px] px-1.5 py-0.5 rounded bg-black/60 text-amber-400 backdrop-blur-sm font-medium">
<span class="text-xs px-1.5 py-0.5 rounded bg-black/60 text-amber-400 backdrop-blur-sm font-medium">
{{ s.rating.toFixed(1) }}
</span>
</div>
<div v-if="s.status === 'ongoing'" class="absolute top-1.5 right-1.5">
<span class="text-[8px] px-1 py-0.5 rounded bg-emerald-500/80 text-white backdrop-blur-sm">
<span class="text-xs px-1 py-0.5 rounded bg-emerald-500/80 text-white backdrop-blur-sm">
ongoing
</span>
</div>
@@ -93,7 +93,7 @@
<span
v-for="src in (s.sources ?? []).slice(0, 2)"
:key="src.type"
class="text-[8px] px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"
class="text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"
>
{{ src.type }}
</span>
@@ -2,15 +2,15 @@
<div :style="{ paddingLeft: `${Math.min(depth, 4) * 16}px` }">
<div class="rounded-lg bg-white/[0.03] border border-white/5 p-2.5 mb-1">
<div class="flex items-center gap-1.5 mb-1">
<div class="w-5 h-5 rounded-full shrink-0 flex items-center justify-center text-[8px] font-bold bg-purple-500/20 text-purple-400">
<div class="w-5 h-5 rounded-full shrink-0 flex items-center justify-center text-xs font-bold bg-purple-500/20 text-purple-400">
{{ node.note.authorName?.charAt(0)?.toUpperCase() ?? '?' }}
</div>
<span class="text-[10px] font-semibold text-white/70">{{ node.note.authorName ?? 'anon' }}</span>
<span class="text-[8px] ml-auto text-white/20">{{ formatTime(node.note.created_at) }}</span>
<span class="text-xs font-semibold text-white/70">{{ node.note.authorName ?? 'anon' }}</span>
<span class="text-xs ml-auto text-white/20">{{ formatTime(node.note.created_at) }}</span>
</div>
<p class="text-[11px] text-white/60 leading-relaxed whitespace-pre-wrap">{{ node.note.content }}</p>
<p class="text-xs text-white/60 leading-relaxed whitespace-pre-wrap">{{ node.note.content }}</p>
<button
class="text-[8px] text-white/25 hover:text-accent/60 mt-1 transition-colors"
class="text-xs text-white/25 hover:text-accent/60 mt-1 transition-colors"
@click="$emit('reply', node.note)"
>
Reply
@@ -17,7 +17,7 @@
:class="isDark ? 'text-white/90' : 'text-gray-900'">
{{ website.title || 'Website' }}
</p>
<p v-if="domain" class="text-[10px] truncate"
<p v-if="domain" class="text-xs truncate"
:class="isDark ? 'text-white/30' : 'text-gray-400'">
{{ domain }}
</p>
@@ -19,14 +19,14 @@
</div>
<!-- Target info -->
<p class="text-[10px] text-white/40 truncate font-mono">{{ targetName }}</p>
<p class="text-xs text-white/40 truncate font-mono">{{ targetName }}</p>
<!-- Amount presets -->
<div class="flex gap-1.5 flex-wrap">
<button
v-for="preset in amountPresets"
:key="preset"
class="text-[10px] px-2.5 py-1.5 rounded-lg transition-colors"
class="text-xs px-2.5 py-1.5 rounded-lg transition-colors"
:class="amount === preset
? 'bg-accent/20 text-accent border border-accent/30'
: 'bg-white/5 text-white/50 hover:bg-white/10'"
@@ -38,23 +38,23 @@
<!-- Custom amount -->
<div>
<label class="text-[10px] text-white/30 block mb-1">Amount (sats)</label>
<label class="text-xs text-white/30 block mb-1">Amount (sats)</label>
<input
v-model.number="amount"
type="number"
min="1"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors tabular-nums"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors tabular-nums"
placeholder="21"
/>
</div>
<!-- Optional message -->
<div>
<label class="text-[10px] text-white/30 block mb-1">Message (optional)</label>
<label class="text-xs text-white/30 block mb-1">Message (optional)</label>
<input
v-model="message"
type="text"
class="w-full px-3 py-2 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
class="w-full px-3 py-2 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors"
placeholder="Great post!"
/>
</div>
@@ -70,7 +70,7 @@
<!-- Invoice / QR -->
<div v-if="invoice" class="space-y-2">
<p class="text-[9px] text-white/30 text-center">Scan or tap to pay</p>
<p class="text-xs text-white/30 text-center">Scan or tap to pay</p>
<!-- QR placeholder (canvas) -->
<div class="flex justify-center">
@@ -82,10 +82,10 @@
<input
:value="invoice"
readonly
class="flex-1 px-2 py-1.5 rounded text-[9px] bg-white/5 text-white/40 font-mono truncate outline-none"
class="flex-1 px-2 py-1.5 rounded text-base bg-white/5 text-white/40 font-mono truncate outline-none"
/>
<button
class="px-2 py-1.5 rounded text-[9px] bg-white/5 text-white/40 hover:text-white/60 transition-colors"
class="px-2 py-1.5 rounded text-xs bg-white/5 text-white/40 hover:text-white/60 transition-colors"
@click="copyInvoice"
>
{{ copied ? 'Copied' : 'Copy' }}
@@ -102,7 +102,7 @@
</div>
<!-- Error -->
<p v-if="error" class="text-[10px] text-red-400/60 text-center">{{ error }}</p>
<p v-if="error" class="text-xs text-red-400/60 text-center">{{ error }}</p>
</div>
</div>
</template>
@@ -77,7 +77,7 @@
<path d="M6 18l8.5-6L6 6v12zm10-12v12h2V6h-2z" />
</svg>
</button>
<span class="text-[10px] font-mono tabular-nums"
<span class="text-xs font-mono tabular-nums"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
{{ formatTime(currentTime) }}
</span>
@@ -91,17 +91,17 @@
:style="{ width: `${progress}%` }"
/>
</div>
<span class="text-[10px] font-mono tabular-nums"
<span class="text-xs font-mono tabular-nums"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
{{ formatTime(duration) }}
</span>
</div>
<p v-if="error" class="text-[10px] text-red-400">{{ error }}</p>
<p v-if="error" class="text-xs text-red-400">{{ error }}</p>
</div>
<span
v-if="queue.length > 1"
class="text-[10px] font-mono tabular-nums shrink-0"
class="text-xs font-mono tabular-nums shrink-0"
:class="isDark ? 'text-white/30' : 'text-gray-400'"
>{{ queue.length }} songs</span>
<button
@@ -5,7 +5,7 @@
v-if="headings.length > 1"
class="hidden lg:flex flex-col w-56 shrink-0 border-r border-white/5 overflow-y-auto scrollbar-hide py-4 px-3"
>
<p class="text-[10px] uppercase tracking-wider text-white/30 mb-2 px-2">Contents</p>
<p class="text-xs uppercase tracking-wider text-white/30 mb-2 px-2">Contents</p>
<button
v-for="(h, i) in headings"
:key="i"
@@ -55,7 +55,7 @@
:disabled="fontSizeIdx <= 0"
@click="fontSizeIdx = Math.max(0, fontSizeIdx - 1)"
>
<span class="text-[10px] font-bold">A-</span>
<span class="text-xs font-bold">A-</span>
</button>
<button
class="w-8 h-8 flex items-center justify-center rounded-lg text-white/60 hover:text-white/80 hover:bg-white/10 transition-colors"
@@ -19,8 +19,8 @@
<div class="flex-1 min-w-0">
<div ref="waveformRef" class="waveform-container" />
<div class="flex justify-between mt-1">
<span class="text-[10px] text-white/30 tabular-nums">{{ formatTime(currentTime) }}</span>
<span class="text-[10px] text-white/30 tabular-nums">{{ formatTime(duration) }}</span>
<span class="text-xs text-white/30 tabular-nums">{{ formatTime(currentTime) }}</span>
<span class="text-xs text-white/30 tabular-nums">{{ formatTime(duration) }}</span>
</div>
</div>
</div>
@@ -30,7 +30,7 @@
</div>
<div v-if="loading" class="px-4 pb-3">
<p class="text-[10px] text-white/30">Loading audio...</p>
<p class="text-xs text-white/30">Loading audio...</p>
</div>
</div>
</template>
@@ -1,8 +1,8 @@
<template>
<div class="rounded-xl bg-white/5 border border-white/10 p-3 space-y-2">
<div class="flex items-center gap-2">
<span class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Bitcoin Address</span>
<span class="text-[9px] px-1.5 py-0.5 rounded bg-white/5 text-white/30">{{ addressType }}</span>
<span class="text-xs text-accent/60 uppercase tracking-wider font-bold">Bitcoin Address</span>
<span class="text-xs px-1.5 py-0.5 rounded bg-white/5 text-white/30">{{ addressType }}</span>
</div>
<!-- Address display -->
@@ -16,7 +16,7 @@
<!-- Actions -->
<div class="flex gap-2">
<button
class="flex-1 py-2 rounded-lg text-[10px] bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
class="flex-1 py-2 rounded-lg text-xs bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
@click="copyAddress"
>
{{ copied ? 'Copied!' : 'Copy Address' }}
@@ -25,7 +25,7 @@
:href="mempoolLink"
target="_blank"
rel="noopener noreferrer"
class="flex-1 py-2 rounded-lg text-[10px] text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="flex-1 py-2 rounded-lg text-xs text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
>
View on Mempool
</a>
@@ -1,10 +1,10 @@
<template>
<div class="rounded-xl bg-white/5 border border-white/10 p-3 space-y-2">
<div class="flex items-center gap-2">
<span class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Lightning Invoice</span>
<span class="text-xs text-accent/60 uppercase tracking-wider font-bold">Lightning Invoice</span>
<span
v-if="isExpired"
class="text-[9px] px-1.5 py-0.5 rounded bg-red-400/15 text-red-400/80"
class="text-xs px-1.5 py-0.5 rounded bg-red-400/15 text-red-400/80"
>
Expired
</span>
@@ -13,7 +13,7 @@
<!-- Amount -->
<div v-if="decodedAmount" class="flex items-center gap-2">
<span class="text-lg font-bold text-accent tabular-nums">{{ formatSats(decodedAmount) }}</span>
<span class="text-[10px] text-white/30">sats</span>
<span class="text-xs text-white/30">sats</span>
</div>
<!-- Description -->
@@ -22,7 +22,7 @@
<!-- Expiry countdown -->
<div v-if="expiryText" class="flex items-center gap-1.5">
<span
class="text-[9px] tabular-nums"
class="text-xs tabular-nums"
:class="isExpired ? 'text-red-400/60' : isExpiringSoon ? 'text-red-400/60' : 'text-white/30'"
>
{{ expiryText }}
@@ -30,18 +30,18 @@
</div>
<!-- Invoice string -->
<p class="text-[9px] font-mono text-white/30 break-all line-clamp-2 select-all">{{ invoice }}</p>
<p class="text-xs font-mono text-white/30 break-all line-clamp-2 select-all">{{ invoice }}</p>
<div class="flex gap-2">
<button
class="flex-1 py-2 rounded-lg text-[10px] bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
class="flex-1 py-2 rounded-lg text-xs bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
@click="copyInvoice"
>
{{ copied ? 'Copied!' : 'Copy' }}
</button>
<a
:href="'lightning:' + invoice"
class="flex-1 py-2 rounded-lg text-[10px] text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="flex-1 py-2 rounded-lg text-xs text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
>
Pay with wallet
</a>
@@ -1,7 +1,7 @@
<template>
<div class="rounded-xl bg-white/5 border border-white/10 p-3 space-y-2">
<div class="flex items-center gap-2">
<span class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">BOLT12 Offer</span>
<span class="text-xs text-accent/60 uppercase tracking-wider font-bold">BOLT12 Offer</span>
</div>
<p class="text-xs font-mono text-white/50 break-all line-clamp-2 select-all">{{ offer }}</p>
@@ -13,14 +13,14 @@
<div class="flex gap-2">
<button
class="flex-1 py-2 rounded-lg text-[10px] bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
class="flex-1 py-2 rounded-lg text-xs bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
@click="copyOffer"
>
{{ copied ? 'Copied!' : 'Copy Offer' }}
</button>
<a
:href="'lightning:' + offer"
class="flex-1 py-2 rounded-lg text-[10px] text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="flex-1 py-2 rounded-lg text-xs text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
>
Pay with wallet
</a>
@@ -2,17 +2,17 @@
<div class="code-runner rounded-xl bg-white/5 border border-white/10 overflow-hidden">
<!-- Toolbar -->
<div class="flex items-center gap-2 px-3 py-2 border-b border-white/5">
<span class="text-[10px] text-white/30 uppercase tracking-wider">{{ language }}</span>
<span class="text-xs text-white/30 uppercase tracking-wider">{{ language }}</span>
<div class="flex-1" />
<button
class="text-[10px] px-2.5 py-1 rounded bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="text-xs px-2.5 py-1 rounded bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
@click="runCode"
>
Run
</button>
<button
v-if="consoleOutput.length > 0"
class="text-[10px] px-2 py-1 rounded bg-white/5 text-white/40 hover:text-white/60 hover:bg-white/10 transition-colors"
class="text-xs px-2 py-1 rounded bg-white/5 text-white/40 hover:text-white/60 hover:bg-white/10 transition-colors"
@click="clearOutput"
>
Clear
@@ -38,11 +38,11 @@
v-if="consoleOutput.length > 0"
class="border-t border-white/5 bg-black/30 px-3 py-2 max-h-32 overflow-y-auto"
>
<p class="text-[9px] text-white/20 uppercase tracking-wider mb-1">Console</p>
<p class="text-xs text-white/20 uppercase tracking-wider mb-1">Console</p>
<div
v-for="(entry, i) in consoleOutput"
:key="i"
class="text-[11px] font-mono leading-relaxed"
class="text-xs font-mono leading-relaxed"
:class="entry.type === 'error' ? 'text-red-400/80' : 'text-white/60'"
>
{{ entry.text }}
@@ -3,7 +3,7 @@
<div class="flex items-start gap-3">
<!-- Date chip -->
<div class="shrink-0 w-14 text-center rounded-lg bg-accent/10 border border-accent/20 py-1.5">
<p class="text-[10px] text-accent/70 uppercase">{{ monthLabel }}</p>
<p class="text-xs text-accent/70 uppercase">{{ monthLabel }}</p>
<p class="text-lg font-bold text-accent leading-tight">{{ dayLabel }}</p>
</div>
@@ -13,14 +13,14 @@
<p v-if="event.description" class="text-xs text-white/40 mt-1 line-clamp-2">{{ event.description }}</p>
<!-- Countdown -->
<p v-if="countdownText" class="text-[10px] mt-1.5" :class="isPast ? 'text-white/30' : 'text-accent/70'">
<p v-if="countdownText" class="text-xs mt-1.5" :class="isPast ? 'text-white/30' : 'text-accent/70'">
{{ countdownText }}
</p>
<!-- Actions -->
<div class="flex gap-2 mt-2">
<button
class="text-[10px] px-2 py-1 rounded bg-white/5 border border-white/10 text-white/60 hover:text-white/80 hover:bg-white/10 transition-colors"
class="text-xs px-2 py-1 rounded bg-white/5 border border-white/10 text-white/60 hover:text-white/80 hover:bg-white/10 transition-colors"
title="Download ICS file"
@click.stop="downloadIcs"
>
@@ -30,7 +30,7 @@
:href="googleCalendarUrl"
target="_blank"
rel="noopener noreferrer"
class="text-[10px] px-2 py-1 rounded bg-white/5 border border-white/10 text-white/60 hover:text-white/80 hover:bg-white/10 transition-colors"
class="text-xs px-2 py-1 rounded bg-white/5 border border-white/10 text-white/60 hover:text-white/80 hover:bg-white/10 transition-colors"
@click.stop
>
Google Calendar
@@ -1,7 +1,7 @@
<template>
<div class="rounded-xl bg-white/5 border border-white/10 p-3 space-y-2">
<div class="flex items-center gap-2">
<span class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Fedimint Ecash</span>
<span class="text-xs text-accent/60 uppercase tracking-wider font-bold">Fedimint Ecash</span>
</div>
<p class="text-xs font-mono text-white/50 break-all line-clamp-2 select-all">{{ token }}</p>
@@ -12,14 +12,14 @@
<div class="flex gap-2">
<button
class="flex-1 py-2 rounded-lg text-[10px] bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
class="flex-1 py-2 rounded-lg text-xs bg-white/5 text-white/50 hover:text-white/70 hover:bg-white/10 transition-colors"
@click="copyToken"
>
{{ copied ? 'Copied!' : 'Copy Token' }}
</button>
<a
:href="fediLink"
class="flex-1 py-2 rounded-lg text-[10px] text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="flex-1 py-2 rounded-lg text-xs text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
>
Receive in Fedi
</a>
@@ -5,11 +5,11 @@
<input
v-model="filterQuery"
type="text"
class="flex-1 bg-white/5 border border-white/10 rounded px-2 py-1 text-xs text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
class="flex-1 bg-white/5 border border-white/10 rounded px-2 py-1 text-base text-white/80 outline-none focus:border-accent/40 placeholder:text-white/25"
placeholder="Filter rows..."
/>
<button
class="text-[10px] px-2 py-1 rounded bg-white/5 border border-white/10 text-white/50 hover:text-white/80 hover:bg-white/10 transition-colors"
class="text-xs px-2 py-1 rounded bg-white/5 border border-white/10 text-white/50 hover:text-white/80 hover:bg-white/10 transition-colors"
title="Export CSV"
@click="exportCsv"
>
@@ -59,7 +59,7 @@
No matching rows
</div>
<div v-if="filteredRows.length > 0" class="px-3 py-1.5 text-[10px] text-white/25 border-t border-white/5">
<div v-if="filteredRows.length > 0" class="px-3 py-1.5 text-xs text-white/25 border-t border-white/5">
{{ filteredRows.length }} row{{ filteredRows.length === 1 ? '' : 's' }}
<span v-if="filterQuery"> (filtered from {{ rows.length }})</span>
</div>
@@ -32,7 +32,7 @@
@click="selectPlace(i)"
>
<p class="text-xs text-white/80 truncate">{{ place.name }}</p>
<p v-if="place.address || place.city" class="text-[10px] text-white/40 truncate">
<p v-if="place.address || place.city" class="text-xs text-white/40 truncate">
{{ place.address || place.city }}
</p>
</button>
@@ -1,10 +1,10 @@
<template>
<div class="rounded-xl bg-white/5 border border-white/10 p-3 space-y-2">
<div class="flex items-center gap-2">
<span class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Transaction</span>
<span class="text-xs text-accent/60 uppercase tracking-wider font-bold">Transaction</span>
<span
v-if="txData"
class="text-[9px] px-1.5 py-0.5 rounded"
class="text-xs px-1.5 py-0.5 rounded"
:class="txData.confirmed ? 'bg-emerald-400/15 text-emerald-400/80' : 'bg-yellow-400/15 text-yellow-400/80'"
>
{{ txData.confirmed ? `${txData.confirmations} confirmations` : 'Unconfirmed' }}
@@ -12,10 +12,10 @@
</div>
<!-- TXID -->
<p class="text-[9px] font-mono text-white/40 break-all select-all">{{ txid }}</p>
<p class="text-xs font-mono text-white/40 break-all select-all">{{ txid }}</p>
<!-- TX details -->
<div v-if="txData" class="grid grid-cols-2 gap-2 text-[10px]">
<div v-if="txData" class="grid grid-cols-2 gap-2 text-xs">
<div>
<p class="text-white/25">Fee</p>
<p class="text-white/60 tabular-nums">{{ txData.fee.toLocaleString() }} sats</p>
@@ -35,18 +35,18 @@
</div>
<div v-else-if="isLoading" class="py-2">
<p class="text-[10px] text-white/30">Loading transaction...</p>
<p class="text-xs text-white/30">Loading transaction...</p>
</div>
<div v-else-if="error" class="py-2">
<p class="text-[10px] text-red-400/60">{{ error }}</p>
<p class="text-xs text-red-400/60">{{ error }}</p>
</div>
<a
:href="`https://mempool.space/tx/${txid}`"
target="_blank"
rel="noopener noreferrer"
class="block w-full py-2 rounded-lg text-[10px] text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="block w-full py-2 rounded-lg text-xs text-center bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
>
View on Mempool.space
</a>
@@ -46,7 +46,7 @@
>
<span class="text-xs font-bold"></span>
</button>
<span class="text-[10px] text-white/40 tabular-nums w-10 text-center">{{ Math.round(scale * 100) }}%</span>
<span class="text-xs text-white/40 tabular-nums w-10 text-center">{{ Math.round(scale * 100) }}%</span>
<button
class="w-8 h-8 flex items-center justify-center rounded-lg text-white/60 hover:text-white/80 hover:bg-white/10 transition-colors disabled:opacity-30"
:disabled="scale >= 2"
@@ -4,21 +4,21 @@
<div class="px-4 py-3 border-b border-white/5">
<h3 class="text-sm font-semibold text-white/90">{{ recipe.title }}</h3>
<div class="flex gap-3 mt-1.5">
<span v-if="recipe.time" class="text-[10px] text-white/40 flex items-center gap-1">
<span v-if="recipe.time" class="text-xs text-white/40 flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{{ recipe.time }}
</span>
<span v-if="recipe.servings" class="text-[10px] text-white/40 flex items-center gap-1">
<span v-if="recipe.servings" class="text-xs text-white/40 flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
{{ scaledServings }} servings
</span>
<span v-if="recipe.calories" class="text-[10px] text-white/40">{{ recipe.calories }} cal</span>
<span v-if="recipe.calories" class="text-xs text-white/40">{{ recipe.calories }} cal</span>
</div>
</div>
<!-- Scale slider -->
<div class="px-4 py-2 border-b border-white/5 flex items-center gap-3">
<label class="text-[10px] text-white/30">Scale</label>
<label class="text-xs text-white/30">Scale</label>
<input
v-model.number="scaleFactor"
type="range"
@@ -27,12 +27,12 @@
step="0.5"
class="flex-1 h-1 accent-[#F7931A] bg-white/10 rounded-full appearance-none cursor-pointer"
/>
<span class="text-[10px] text-white/50 tabular-nums w-8 text-right">{{ scaleFactor }}×</span>
<span class="text-xs text-white/50 tabular-nums w-8 text-right">{{ scaleFactor }}×</span>
</div>
<!-- Ingredients -->
<div class="px-4 py-3 border-b border-white/5">
<h4 class="text-[11px] text-white/40 uppercase tracking-wider mb-2">Ingredients</h4>
<h4 class="text-xs text-white/40 uppercase tracking-wider mb-2">Ingredients</h4>
<ul class="space-y-1">
<li
v-for="(ing, i) in scaledIngredients"
@@ -52,14 +52,14 @@
<!-- Steps -->
<div class="px-4 py-3">
<h4 class="text-[11px] text-white/40 uppercase tracking-wider mb-2">Steps</h4>
<h4 class="text-xs text-white/40 uppercase tracking-wider mb-2">Steps</h4>
<ol class="space-y-2">
<li
v-for="(step, i) in recipe.steps"
:key="i"
class="flex gap-2 text-xs text-white/70"
>
<span class="shrink-0 w-5 h-5 rounded-full bg-accent/15 text-accent text-[10px] flex items-center justify-center font-medium">{{ i + 1 }}</span>
<span class="shrink-0 w-5 h-5 rounded-full bg-accent/15 text-accent text-xs flex items-center justify-center font-medium">{{ i + 1 }}</span>
<span class="leading-relaxed">{{ step }}</span>
</li>
</ol>
@@ -20,16 +20,16 @@
<!-- Date (mobile: inline, desktop: left/right side) -->
<div class="hidden md:block w-[calc(50%-2rem)] text-right shrink-0" :class="i % 2 === 0 ? '' : 'order-last text-left'">
<p class="text-[10px] text-white/40 tabular-nums pt-1">{{ formatDate(event.date) }}</p>
<p class="text-xs text-white/40 tabular-nums pt-1">{{ formatDate(event.date) }}</p>
</div>
<!-- Card -->
<div class="ml-10 md:ml-0 md:w-[calc(50%-2rem)] shrink-0">
<div class="rounded-lg bg-white/5 border border-white/10 px-3 py-2">
<p class="text-[10px] text-white/30 md:hidden tabular-nums">{{ formatDate(event.date) }}</p>
<p class="text-xs text-white/30 md:hidden tabular-nums">{{ formatDate(event.date) }}</p>
<h4 class="text-xs font-medium text-white/90">{{ event.title }}</h4>
<p v-if="event.location" class="text-[10px] text-white/40 mt-0.5">{{ event.location }}</p>
<p v-if="event.description" class="text-[10px] text-white/50 mt-1 line-clamp-2">{{ event.description }}</p>
<p v-if="event.location" class="text-xs text-white/40 mt-0.5">{{ event.location }}</p>
<p v-if="event.description" class="text-xs text-white/50 mt-1 line-clamp-2">{{ event.description }}</p>
</div>
</div>
</div>
@@ -22,7 +22,7 @@
{{ provider.name }}
</div>
<div
class="text-[10px] font-mono mt-0.5"
class="text-xs font-mono mt-0.5"
:class="isDark ? 'text-white/30' : 'text-gray-400'"
>
{{ provider.masked ?? 'Not configured' }}
@@ -30,7 +30,7 @@
</div>
<button
v-if="provider.hasKey"
class="text-[10px] px-2 py-1 rounded-md transition-colors"
class="text-xs px-2 py-1 rounded-md transition-colors"
:class="isDark
? 'text-red-400/60 hover:text-red-400 hover:bg-red-500/10'
: 'text-red-500/60 hover:text-red-600 hover:bg-red-50'"
@@ -83,7 +83,7 @@
<p
v-if="statusMessage"
class="text-[10px]"
class="text-xs"
:class="statusError ? 'text-red-400' : isDark ? 'text-green-400' : 'text-green-600'"
>
{{ statusMessage }}
@@ -25,7 +25,7 @@
Connected
</div>
<button
class="text-[10px] font-mono mt-0.5 hover:underline"
class="text-xs font-mono mt-0.5 hover:underline"
:class="isDark ? 'text-purple-400/60' : 'text-purple-600/60'"
@click="copyNpub"
>
@@ -33,7 +33,7 @@
</button>
</div>
<button
class="text-[10px] px-2 py-1 rounded-md transition-colors"
class="text-xs px-2 py-1 rounded-md transition-colors"
:class="isDark
? 'text-red-400/60 hover:text-red-400 hover:bg-red-500/10'
: 'text-red-500/60 hover:text-red-600 hover:bg-red-50'"
@@ -85,7 +85,7 @@
<!-- Error -->
<p
v-if="error"
class="text-[10px] text-red-400"
class="text-xs text-red-400"
>
{{ error }}
</p>
@@ -4,7 +4,7 @@
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-white/90">Plugins</h3>
<div class="flex items-center gap-2">
<span v-if="store.hasUpdates" class="text-[8px] px-1.5 py-0.5 rounded-full bg-accent/20 text-accent/80">
<span v-if="store.hasUpdates" class="text-xs px-1.5 py-0.5 rounded-full bg-accent/20 text-accent/80">
Updates
</span>
</div>
@@ -14,7 +14,7 @@
<button
v-for="tab in tabs"
:key="tab.id"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeTab === tab.id
? 'nav-tab-active'
: 'text-white/40 hover:text-white/70 hover:bg-white/5'"
@@ -34,7 +34,7 @@
<div v-else-if="store.registryError && store.registryPlugins.length === 0" class="py-8 text-center">
<p class="text-xs text-red-400/60">{{ store.registryError }}</p>
<button class="mt-2 text-[10px] text-accent/60 hover:text-accent/80" @click="store.fetchRegistry()">Retry</button>
<button class="mt-2 text-xs text-accent/60 hover:text-accent/80" @click="store.fetchRegistry()">Retry</button>
</div>
<div
@@ -45,22 +45,22 @@
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<p class="text-xs font-semibold text-white/80">{{ plugin.name }}</p>
<p class="text-[9px] text-white/30">{{ plugin.author }} · v{{ plugin.version }}</p>
<p class="text-xs text-white/30">{{ plugin.author }} · v{{ plugin.version }}</p>
</div>
<button
v-if="!store.isInstalled(plugin.id)"
class="text-[10px] px-3 py-1.5 rounded-lg bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors shrink-0"
class="text-xs px-3 py-1.5 rounded-lg bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors shrink-0"
@click="showPermissionsDialog(plugin)"
>
Install
</button>
<span v-else class="text-[9px] text-emerald-400/60 shrink-0">Installed</span>
<span v-else class="text-xs text-emerald-400/60 shrink-0">Installed</span>
</div>
<p class="text-[10px] text-white/50">{{ plugin.description }}</p>
<p class="text-xs text-white/50">{{ plugin.description }}</p>
<div class="flex items-center gap-2">
<span class="text-[8px] px-1.5 py-0.5 rounded bg-white/5 text-white/30">{{ plugin.type }}</span>
<span class="text-xs px-1.5 py-0.5 rounded bg-white/5 text-white/30">{{ plugin.type }}</span>
<div class="flex items-center gap-0.5">
<span v-for="i in 5" :key="i" class="text-[8px]" :class="i <= plugin.rating ? 'text-accent/60' : 'text-white/10'"></span>
<span v-for="i in 5" :key="i" class="text-xs" :class="i <= plugin.rating ? 'text-accent/60' : 'text-white/10'"></span>
</div>
</div>
</div>
@@ -83,18 +83,18 @@
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<p class="text-xs font-semibold text-white/80">{{ plugin.name }}</p>
<p class="text-[9px] text-white/30">v{{ plugin.version }} · {{ plugin.author }}</p>
<p class="text-xs text-white/30">v{{ plugin.version }} · {{ plugin.author }}</p>
</div>
<div class="flex items-center gap-1.5 shrink-0">
<button
v-if="store.updatesAvailable.has(plugin.id)"
class="text-[9px] px-2 py-1 rounded bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="text-xs px-2 py-1 rounded bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
@click="store.updatePlugin(plugin.id)"
>
Update to v{{ store.updatesAvailable.get(plugin.id) }}
</button>
<button
class="text-[9px] p-1 rounded text-white/30 hover:text-white/60 hover:bg-white/5 transition-colors"
class="text-xs p-1 rounded text-white/30 hover:text-white/60 hover:bg-white/5 transition-colors"
@click="editingPlugin = editingPlugin === plugin.id ? null : plugin.id"
>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -103,7 +103,7 @@
</svg>
</button>
<button
class="text-[9px] px-2 py-1 rounded text-red-400/50 hover:text-red-400/80 hover:bg-red-400/10 transition-colors"
class="text-xs px-2 py-1 rounded text-red-400/50 hover:text-red-400/80 hover:bg-red-400/10 transition-colors"
@click="store.uninstallPlugin(plugin.id)"
>
Remove
@@ -116,7 +116,7 @@
<span
v-for="perm in plugin.permissions"
:key="perm"
class="text-[8px] px-1.5 py-0.5 rounded"
class="text-xs px-1.5 py-0.5 rounded"
:class="plugin.grantedPermissions.includes(perm)
? 'bg-emerald-400/15 text-emerald-400/60'
: 'bg-red-400/15 text-red-400/60'"
@@ -150,14 +150,14 @@
<!-- Import -->
<template v-else-if="activeTab === 'import'">
<div class="space-y-3">
<p class="text-[10px] text-white/40">
<p class="text-xs text-white/40">
Paste a GitHub raw URL or IPFS CID to the plugin's manifest (aiui-plugin.json).
</p>
<input
v-model="importUrl"
type="text"
placeholder="https://raw.githubusercontent.com/.../aiui-plugin.json"
class="w-full px-3 py-2.5 rounded-lg text-xs bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
class="w-full px-3 py-2.5 rounded-lg text-base bg-white/5 text-white/80 placeholder:text-white/25 outline-none focus:bg-white/10 transition-colors font-mono"
/>
<button
class="w-full py-2.5 rounded-lg text-xs font-medium bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors disabled:opacity-30"
@@ -166,15 +166,15 @@
>
{{ isImporting ? 'Fetching manifest...' : 'Import Plugin' }}
</button>
<p v-if="importError" class="text-[10px] text-red-400/60">{{ importError }}</p>
<p v-if="importError" class="text-xs text-red-400/60">{{ importError }}</p>
<!-- Imported plugin preview -->
<div v-if="importedManifest" class="rounded-xl bg-white/[0.03] border border-white/5 p-3 space-y-2">
<p class="text-xs font-semibold text-white/80">{{ importedManifest.name }}</p>
<p class="text-[10px] text-white/50">{{ importedManifest.description }}</p>
<p class="text-[9px] text-white/30">{{ importedManifest.author }} · v{{ importedManifest.version }}</p>
<p class="text-xs text-white/50">{{ importedManifest.description }}</p>
<p class="text-xs text-white/30">{{ importedManifest.author }} · v{{ importedManifest.version }}</p>
<button
class="w-full py-2 rounded-lg text-[10px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="w-full py-2 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
@click="showPermissionsDialog(importedManifest)"
>
Install
@@ -187,31 +187,31 @@
<template v-else-if="activeTab === 'dev'">
<div v-if="!isDevMode" class="flex flex-col items-center justify-center py-12 gap-2">
<p class="text-xs text-white/30">Dev mode not enabled</p>
<p class="text-[10px] text-white/20">Set VITE_PLUGIN_DEV=true to enable</p>
<p class="text-xs text-white/20">Set VITE_PLUGIN_DEV=true to enable</p>
</div>
<template v-else>
<div class="rounded-xl bg-white/[0.03] border border-white/5 p-3 space-y-2">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Plugin Dev Mode</p>
<p class="text-[10px] text-white/40">Hot-reload from src/plugins/dev/</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Plugin Dev Mode</p>
<p class="text-xs text-white/40">Hot-reload from src/plugins/dev/</p>
<div v-if="devErrors.length > 0" class="space-y-1 mt-2">
<p class="text-[9px] text-red-400/60 uppercase tracking-wider font-bold">Errors</p>
<p class="text-xs text-red-400/60 uppercase tracking-wider font-bold">Errors</p>
<div
v-for="(err, i) in devErrors"
:key="i"
class="text-[9px] text-red-400/50 font-mono bg-red-400/5 rounded p-2"
class="text-xs text-red-400/50 font-mono bg-red-400/5 rounded p-2"
>
{{ err }}
</div>
</div>
<div v-if="devTimings.length > 0" class="space-y-1 mt-2">
<p class="text-[9px] text-white/30 uppercase tracking-wider font-bold">Init Timing</p>
<p class="text-xs text-white/30 uppercase tracking-wider font-bold">Init Timing</p>
<div
v-for="t in devTimings"
:key="t.id"
class="flex items-center justify-between text-[9px]"
class="flex items-center justify-between text-xs"
>
<span class="text-white/50">{{ t.id }}</span>
<span class="text-white/30 tabular-nums">{{ t.ms }}ms</span>
@@ -227,7 +227,7 @@
<div v-if="pendingInstall" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
<div class="w-full max-w-sm mx-4 rounded-2xl bg-[#0a0a0a] border border-white/10 p-5 space-y-4">
<h4 class="text-sm font-bold text-white/90">Plugin Permissions</h4>
<p class="text-[10px] text-white/40">
<p class="text-xs text-white/40">
"{{ pendingInstall.name }}" requests the following permissions:
</p>
<div class="space-y-2">
@@ -243,8 +243,8 @@
@change="togglePermission(perm)"
/>
<div>
<p class="text-[11px] text-white/70">{{ permissionLabel(perm) }}</p>
<p class="text-[9px] text-white/30">{{ permissionDescription(perm) }}</p>
<p class="text-xs text-white/70">{{ permissionLabel(perm) }}</p>
<p class="text-xs text-white/30">{{ permissionDescription(perm) }}</p>
</div>
</label>
</div>
@@ -1,14 +1,14 @@
<template>
<div class="mt-2 pt-2 border-t border-white/[0.05] space-y-3">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Settings</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Settings</p>
<!-- Generic key-value settings editor -->
<div class="space-y-2">
<div v-for="(value, key) in localSettings" :key="key" class="flex items-center gap-2">
<span class="text-[10px] text-white/40 min-w-[60px]">{{ key }}</span>
<span class="text-xs text-white/40 min-w-[60px]">{{ key }}</span>
<input
:value="String(value ?? '')"
class="flex-1 px-2 py-1.5 rounded-md text-[10px] bg-white/5 text-white/70 outline-none focus:bg-white/10 transition-colors"
class="flex-1 px-2 py-1.5 rounded-md text-base bg-white/5 text-white/70 outline-none focus:bg-white/10 transition-colors"
@input="updateSetting(key as string, ($event.target as HTMLInputElement).value)"
/>
</div>
@@ -19,16 +19,16 @@
v-model="newSettingKey"
type="text"
placeholder="Key"
class="flex-1 px-2 py-1.5 rounded-md text-[10px] bg-white/5 text-white/70 placeholder:text-white/20 outline-none focus:bg-white/10 transition-colors"
class="flex-1 px-2 py-1.5 rounded-md text-base bg-white/5 text-white/70 placeholder:text-white/20 outline-none focus:bg-white/10 transition-colors"
/>
<input
v-model="newSettingValue"
type="text"
placeholder="Value"
class="flex-1 px-2 py-1.5 rounded-md text-[10px] bg-white/5 text-white/70 placeholder:text-white/20 outline-none focus:bg-white/10 transition-colors"
class="flex-1 px-2 py-1.5 rounded-md text-base bg-white/5 text-white/70 placeholder:text-white/20 outline-none focus:bg-white/10 transition-colors"
/>
<button
class="text-[9px] px-2 py-1.5 rounded-md bg-white/5 text-white/40 hover:text-white/70 hover:bg-white/10 transition-colors disabled:opacity-30"
class="text-xs px-2 py-1.5 rounded-md bg-white/5 text-white/40 hover:text-white/70 hover:bg-white/10 transition-colors disabled:opacity-30"
:disabled="!newSettingKey.trim()"
@click="addSetting"
>
@@ -39,12 +39,12 @@
<!-- Permissions toggles -->
<div v-if="permissions.length > 0">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold mb-2">Permissions</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold mb-2">Permissions</p>
<div class="space-y-1">
<label
v-for="perm in permissions"
:key="perm"
class="flex items-center gap-2 text-[10px] cursor-pointer"
class="flex items-center gap-2 text-xs cursor-pointer"
>
<input
type="checkbox"
@@ -58,7 +58,7 @@
</div>
<button
class="w-full py-2 rounded-lg text-[10px] bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
class="w-full py-2 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
@click="save"
>
Save Settings
@@ -19,7 +19,7 @@
<button
v-for="tab in tabs"
:key="tab.id"
class="text-[10px] px-2 py-1 rounded-md whitespace-nowrap transition-all duration-150"
class="text-xs px-2 py-1 rounded-md whitespace-nowrap transition-all duration-150"
:class="activeTab === tab.id
? 'nav-tab-active'
: 'text-white/40 hover:text-white/70 hover:bg-white/5'"
@@ -35,7 +35,7 @@
<template v-if="activeTab === 'appearance'">
<!-- M15.1 Accent colour -->
<div class="space-y-2">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Accent Colour</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Accent Colour</p>
<div class="flex gap-2 flex-wrap">
<button
v-for="color in accentPresets"
@@ -47,7 +47,7 @@
@click="store.accentColor = color.value"
/>
<label class="w-8 h-8 rounded-lg border-2 border-white/10 flex items-center justify-center cursor-pointer overflow-hidden relative">
<span class="text-[8px] text-white/30">+</span>
<span class="text-xs text-white/30">+</span>
<input
type="color"
:value="store.accentColor"
@@ -60,12 +60,12 @@
<!-- M15.2 Glass intensity -->
<div class="space-y-2">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Glass Intensity</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Glass Intensity</p>
<div class="flex gap-2">
<button
v-for="opt in glassOptions"
:key="opt.value"
class="flex-1 py-2 rounded-lg text-[10px] transition-all"
class="flex-1 py-2 rounded-lg text-xs transition-all"
:class="store.glassIntensity === opt.value
? 'bg-accent/15 text-accent/80'
: 'bg-white/5 text-white/40 hover:text-white/60 hover:bg-white/10'"
@@ -78,12 +78,12 @@
<!-- M15.3 Font size -->
<div class="space-y-2">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Font Size</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Font Size</p>
<div class="flex gap-2">
<button
v-for="opt in fontOptions"
:key="opt.value"
class="flex-1 py-2 rounded-lg text-[10px] transition-all"
class="flex-1 py-2 rounded-lg text-xs transition-all"
:class="store.fontSize === opt.value
? 'bg-accent/15 text-accent/80'
: 'bg-white/5 text-white/40 hover:text-white/60 hover:bg-white/10'"
@@ -99,8 +99,8 @@
<template v-else-if="activeTab === 'content'">
<!-- M15.4 Content type visibility -->
<div class="space-y-2">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Content Types</p>
<p class="text-[9px] text-white/30">Hidden types still extract but don't show in the panel</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Content Types</p>
<p class="text-xs text-white/30">Hidden types still extract but don't show in the panel</p>
<div class="space-y-1">
<label
v-for="ct in contentTypes"
@@ -113,7 +113,7 @@
class="rounded accent-[#F7931A]"
@change="store.toggleTabVisibility(ct.tab)"
/>
<span class="text-[11px] text-white/70">{{ ct.label }}</span>
<span class="text-xs text-white/70">{{ ct.label }}</span>
</label>
</div>
</div>
@@ -123,16 +123,16 @@
<template v-else-if="activeTab === 'shortcuts'">
<!-- M15.5 Keyboard shortcuts -->
<div class="space-y-2">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Keyboard Shortcuts</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Keyboard Shortcuts</p>
<div class="space-y-1">
<div
v-for="(binding, action) in store.settings.shortcuts"
:key="action"
class="flex items-center justify-between p-2.5 rounded-lg bg-white/[0.03] border border-white/5"
>
<span class="text-[11px] text-white/60">{{ formatAction(action as string) }}</span>
<span class="text-xs text-white/60">{{ formatAction(action as string) }}</span>
<button
class="text-[10px] px-2 py-1 rounded bg-white/5 text-white/40 hover:text-white/70 font-mono transition-colors"
class="text-xs px-2 py-1 rounded bg-white/5 text-white/40 hover:text-white/70 font-mono transition-colors"
:class="recordingAction === action ? 'bg-accent/15 text-accent/80' : ''"
@click="startRecording(action as string)"
>
@@ -147,7 +147,7 @@
<template v-else-if="activeTab === 'notifications'">
<!-- M15.6 Push notifications -->
<div class="space-y-3">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Notifications</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Notifications</p>
<label class="flex items-center gap-3 p-3 rounded-lg bg-white/[0.03] border border-white/5 cursor-pointer">
<input
type="checkbox"
@@ -156,11 +156,11 @@
@change="toggleNotifications"
/>
<div>
<p class="text-[11px] text-white/70">Generation complete</p>
<p class="text-[9px] text-white/30">Notify when AI finishes while tab is backgrounded</p>
<p class="text-xs text-white/70">Generation complete</p>
<p class="text-xs text-white/30">Notify when AI finishes while tab is backgrounded</p>
</div>
</label>
<p v-if="notificationStatus" class="text-[10px]" :class="notificationOk ? 'text-emerald-400/60' : 'text-yellow-400/60'">
<p v-if="notificationStatus" class="text-xs" :class="notificationOk ? 'text-emerald-400/60' : 'text-yellow-400/60'">
{{ notificationStatus }}
</p>
</div>
@@ -170,13 +170,13 @@
<template v-else-if="activeTab === 'storage'">
<!-- M15.7 Auto-archive -->
<div class="space-y-2">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Auto-Archive</p>
<p class="text-[9px] text-white/30">Archive conversations older than:</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Auto-Archive</p>
<p class="text-xs text-white/30">Archive conversations older than:</p>
<div class="flex gap-2">
<button
v-for="opt in archiveOptions"
:key="opt.value"
class="flex-1 py-2 rounded-lg text-[10px] transition-all"
class="flex-1 py-2 rounded-lg text-xs transition-all"
:class="store.autoArchiveDays === opt.value
? 'bg-accent/15 text-accent/80'
: 'bg-white/5 text-white/40 hover:text-white/60 hover:bg-white/10'"
@@ -189,19 +189,19 @@
<!-- M15.8 Full data export -->
<div class="space-y-2">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Data Export</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Data Export</p>
<button
class="w-full py-2.5 rounded-lg text-xs bg-accent/15 text-accent/80 hover:bg-accent/25 transition-colors"
@click="exportData"
>
{{ isExporting ? 'Exporting...' : 'Export All Data' }}
</button>
<p class="text-[9px] text-white/20">Exports conversations, favorites, settings, tags, and collections as JSON</p>
<p class="text-xs text-white/20">Exports conversations, favorites, settings, tags, and collections as JSON</p>
</div>
<!-- M15.9 Data wipe -->
<div class="space-y-2">
<p class="text-[10px] text-red-400/60 uppercase tracking-wider font-bold">Danger Zone</p>
<p class="text-xs text-red-400/60 uppercase tracking-wider font-bold">Danger Zone</p>
<button
v-if="!confirmWipe"
class="w-full py-2.5 rounded-lg text-xs bg-red-400/10 text-red-400/60 hover:bg-red-400/20 hover:text-red-400/80 transition-colors"
@@ -210,21 +210,21 @@
Wipe All Data
</button>
<div v-else class="space-y-2 p-3 rounded-lg bg-red-400/5 border border-red-400/20">
<p class="text-[11px] text-red-400/80 font-semibold">Are you sure?</p>
<p class="text-[9px] text-red-400/50">This will clear all conversations, favorites, settings, and cached data.</p>
<label class="flex items-center gap-2 text-[10px] text-red-400/60 cursor-pointer">
<p class="text-xs text-red-400/80 font-semibold">Are you sure?</p>
<p class="text-xs text-red-400/50">This will clear all conversations, favorites, settings, and cached data.</p>
<label class="flex items-center gap-2 text-xs text-red-400/60 cursor-pointer">
<input v-model="wipeApiKeys" type="checkbox" class="rounded" />
Also clear API key vault
</label>
<div class="flex gap-2">
<button
class="flex-1 py-2 rounded-lg text-[10px] text-white/40 hover:text-white/70 hover:bg-white/5 transition-colors"
class="flex-1 py-2 rounded-lg text-xs text-white/40 hover:text-white/70 hover:bg-white/5 transition-colors"
@click="confirmWipe = false"
>
Cancel
</button>
<button
class="flex-1 py-2 rounded-lg text-[10px] bg-red-400/20 text-red-400/80 hover:bg-red-400/30 transition-colors"
class="flex-1 py-2 rounded-lg text-xs bg-red-400/20 text-red-400/80 hover:bg-red-400/30 transition-colors"
@click="wipeData"
>
Confirm Wipe
@@ -238,17 +238,17 @@
<template v-else-if="activeTab === 'chat'">
<!-- M15.10 Default conversation settings -->
<div class="space-y-3">
<p class="text-[10px] text-accent/60 uppercase tracking-wider font-bold">Default Conversation Settings</p>
<p class="text-[9px] text-white/30">Applied to all new conversations</p>
<p class="text-xs text-accent/60 uppercase tracking-wider font-bold">Default Conversation Settings</p>
<p class="text-xs text-white/30">Applied to all new conversations</p>
<div class="space-y-2">
<label class="block">
<span class="text-[10px] text-white/40">Default Model</span>
<span class="text-xs text-white/40">Default Model</span>
<input
v-model="store.settings.defaultModel"
type="text"
placeholder="e.g. claude-sonnet-4-6"
class="w-full mt-1 px-3 py-2 rounded-lg text-xs bg-white/5 text-white/70 placeholder:text-white/20 outline-none focus:bg-white/10 transition-colors font-mono"
class="w-full mt-1 px-3 py-2 rounded-lg text-base bg-white/5 text-white/70 placeholder:text-white/20 outline-none focus:bg-white/10 transition-colors font-mono"
/>
</label>
@@ -258,7 +258,7 @@
type="checkbox"
class="rounded accent-[#F7931A]"
/>
<span class="text-[11px] text-white/60">Enable web search by default</span>
<span class="text-xs text-white/60">Enable web search by default</span>
</label>
<label class="flex items-center gap-3 p-2.5 rounded-lg bg-white/[0.03] border border-white/5 cursor-pointer">
@@ -267,7 +267,7 @@
type="checkbox"
class="rounded accent-[#F7931A]"
/>
<span class="text-[11px] text-white/60">Show token counts by default</span>
<span class="text-xs text-white/60">Show token counts by default</span>
</label>
</div>
</div>
@@ -36,7 +36,7 @@
<!-- Invoice string (truncated) -->
<div
class="text-[10px] font-mono break-all leading-relaxed"
class="text-xs font-mono break-all leading-relaxed"
:class="isDark ? 'text-white/30' : 'text-gray-400'"
>
{{ truncatedInvoice }}
@@ -15,7 +15,7 @@
<template v-else>
<div v-for="group in groupedResults" :key="group.type" class="py-1">
<div
class="px-3 py-1 text-[10px] font-bold uppercase tracking-wider"
class="px-3 py-1 text-xs font-bold uppercase tracking-wider"
:class="isDark ? 'text-white/30' : 'text-gray-400'"
>
{{ group.label }}
@@ -33,7 +33,7 @@
@mouseenter="selectedIndex = group.startIndex + i"
>
<span
class="text-[10px] w-5 h-5 rounded flex items-center justify-center shrink-0"
class="text-xs w-5 h-5 rounded flex items-center justify-center shrink-0"
:class="typeColor(result.type)"
>
{{ typeIcon(result.type) }}
@@ -46,7 +46,7 @@
{{ result.title }}
</div>
<div
class="text-[10px] truncate"
class="text-xs truncate"
:class="isDark ? 'text-white/40' : 'text-gray-500'"
>
{{ result.subtitle }}
+3 -3
View File
@@ -42,7 +42,7 @@
<button
v-for="tab in availableTabs"
:key="tab"
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
class="text-xs px-2 py-1 rounded-md transition-all duration-150"
:class="activeTab === tab
? 'nav-tab-active'
: isDark
@@ -195,7 +195,7 @@
<button
v-for="tab in availableTabs"
:key="tab"
class="text-[10px] px-2.5 py-1.5 rounded-lg font-medium whitespace-nowrap transition-all duration-150"
class="text-xs px-2.5 py-1.5 rounded-lg font-medium whitespace-nowrap transition-all duration-150"
:class="activeTab === tab
? 'nav-tab-active'
: isDark
@@ -273,7 +273,7 @@
</svg>
Prev
</button>
<span class="text-[10px] font-mono tabular-nums"
<span class="text-xs font-mono tabular-nums"
:class="isDark ? 'text-white/25' : 'text-black/25'">
{{ currentContextIndex + 1 }}/{{ contextItems.length }}
</span>
@@ -10,12 +10,12 @@
</router-link>
<div class="flex-1 min-w-0">
<h1 class="text-sm font-semibold text-white/90 truncate">{{ title }}</h1>
<p class="text-[10px] text-white/40">
<p class="text-xs text-white/40">
<template v-if="authorName">by {{ authorName }}</template>
<template v-if="publishedAt"> · {{ formattedDate }}</template>
</p>
</div>
<span class="text-[10px] px-2 py-1 rounded-full bg-white/5 text-white/40">Read-only</span>
<span class="text-xs px-2 py-1 rounded-full bg-white/5 text-white/40">Read-only</span>
</div>
</header>
@@ -44,7 +44,7 @@
>
<div class="flex items-center gap-2 mb-2">
<span
class="text-[10px] font-bold uppercase tracking-wider"
class="text-xs font-bold uppercase tracking-wider"
:class="msg.role === 'user' ? 'text-accent/70' : 'text-white/30'"
>
{{ msg.role === 'user' ? 'Human' : 'Assistant' }}
@@ -59,7 +59,7 @@
<!-- Footer -->
<footer class="max-w-3xl mx-auto px-4 py-8 text-center">
<p class="text-[10px] text-white/20">
<p class="text-xs text-white/20">
Shared via AIUI · Powered by Nostr
</p>
</footer>