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:
co-authored by
Claude Opus 4.6
parent
dde3859a2e
commit
b2fcc23623
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Binary file not shown.
@@ -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">·</span>
|
||||
<span class="text-[10px] text-white/30">
|
||||
<span class="text-xs text-white/20">·</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>
|
||||
|
||||
@@ -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)">×</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 }} × {{ 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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user