",
+ "css": ".ds-card{font-family:var(--font-sans);color:var(--text-primary);background:var(--bg-card);padding:var(--space-6);border-radius:var(--radius-md);overflow:hidden;min-width:0}"
+ },
+ {
+ "name": "Repository workspace",
+ "kind": "custom",
+ "description": "Read-first list and inspector; the application switches list/detail at 650px and requires explicit editing. Static snippet illustrates the selected reading state.",
+ "html": "
Record key
{}
",
+ "css": ".ds-workspace{display:grid;grid-template-columns:minmax(14rem,.7fr) minmax(0,1.3fr);min-height:28rem;background:var(--bg-card);border:1px solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;font-family:var(--font-sans);color:var(--text-primary)}.ds-browser{min-width:0;border-inline-end:1px solid var(--border-color);max-height:70dvh;overflow-y:auto}.ds-record{display:flex;width:100%;padding:1.25rem;border:0;border-bottom:1px solid var(--border-color);text-align:start;background:var(--bg-secondary);color:var(--text-primary);font:600 var(--text-sm) var(--font-sans);cursor:pointer}.ds-record:hover{background:var(--bg-secondary)}.ds-record:focus-visible,.ds-json:focus-visible{outline:2px solid var(--text-primary);outline-offset:3px}.ds-inspector{padding:1.5rem;min-width:0}.ds-inspector h2{font-size:var(--text-lg);overflow-wrap:anywhere}.ds-json{padding:1.25rem;border-radius:var(--radius-sm);background:var(--bg-secondary);font:var(--text-xs)/1.7 var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;max-height:60dvh;overflow-y:auto}@media(max-width:1200px){.ds-workspace{grid-template-columns:minmax(12rem,.6fr) minmax(0,1fr)}}@media(max-width:650px){.ds-workspace{display:block}.ds-browser{display:none}.ds-inspector{padding:1.25rem}}"
+ }
+ ],
+ "narrative": {
+ "northStar": "A personal account home",
+ "overview": "Cool neutral surfaces, direct hierarchy, softly rounded controls, and visible server identity. Spacious public explanation leads into dedicated account, access, data, repository, DID, migration, and administration workspaces. Source-only documentation; no browser or visual verification.",
+ "keyCharacteristics": [
+ "Neutral ink with configurable accents",
+ "Flat surfaces and restrained borders",
+ "Sora public headings and platform interface text",
+ "Explicit account context and progress",
+ "Read-first records and explicit draft editing",
+ "Separate phase position, transfer progress, and recovery"
+ ],
+ "rules": [
+ {
+ "name": "Neutral Ink Rule",
+ "body": "Use contrast-aware foregrounds on solid primary fills and neutral ink on tinted surfaces; keep destructive colors semantic.",
+ "section": "colors"
+ }
+ ],
+ "dos": [
+ "Do share the public tokens with the application.",
+ "Do retain keyboard focus and reduced-motion behavior.",
+ "Do label draft previews and preserve explicit edit/save/cancel controls.",
+ "Do distinguish phase count from confirmed migration success."
+ ],
+ "donts": [
+ "Don't use Sniglet or literal reference copying.",
+ "Don't present source checks as visual verification."
+ ]
+ }
+}
diff --git a/.impeccable/surfaces/frontend-src-routes-dashboard-svelte.md b/.impeccable/surfaces/frontend-src-routes-dashboard-svelte.md
new file mode 100644
index 0000000..c64150a
--- /dev/null
+++ b/.impeccable/surfaces/frontend-src-routes-dashboard-svelte.md
@@ -0,0 +1,31 @@
+---
+version: 1
+slug: "frontend-src-routes-dashboard-svelte"
+primary_target: "frontend/src/routes/Dashboard.svelte"
+related_targets: ["frontend/public/homepage.html","frontend/src/components/AuthShell.svelte","frontend/src/styles/design.css"]
+---
+
+# PDS redesign implementation brief
+
+The user approved the three-reference plan and asked to proceed directly with the source. Public pages persuade; account, auth, consent, and migration pages operate. Preserve server identity, existing permissions, and account-state behavior.
+
+## Direction contract
+
+THESIS: A personal account home with a clear hosting relationship and direct routes to identity, access, and data.
+
+OWN-WORLD: Cool neutral surfaces, dark ink, configurable server accents, softly rounded controls, compact system interface type. Sora provides an independent public display voice; interface screens use the platform sans. The user explicitly rejected copying the reference designs or Sniglet. No borrowed logos or UI code.
+
+STORY: Understand what this server stores, enter the account, then move from the overview into dedicated identity, security, app-access, and data workspaces. Inspect records before editing, review DID drafts, and keep migration progress and recovery explicit.
+
+FIRST VIEWPORT: Public home has a large left-aligned headline, clear account actions, and a handle/server/apps diagram. Account home has a narrow grouped sidebar, a restrained page heading, a large handle and server summary, followed by task rows. Mobile keeps an overview and explicit navigation disclosure. Auth uses a centered, opaque form under server branding. The signature interaction is the account menu and continuous mobile navigation, without decorative motion.
+
+FORM: The user-pinned Marque/Margin/official-PDS direction supersedes concept seed b10ef725; no new concept selection or browser use is needed after their approval.
+
+IMPLEMENTED SURFACES: Shared public/app tokens; static home; account shell and overview; auth/consent and registration progress; identity/contact settings; authentication methods and sessions; dedicated Data and Connected Apps; app-password disclosure/search; read-first repository list/inspector with explicit editing and in-workspace draft protection; DID fields/live draft; admin section switcher and light/dark preview; migration choice, phase/transfer progress, resume, and recovery. All seven locales contain matching work.* keys.
+
+FINISH: Source review and documentation accompany compiler, build, and test checks. Final source checks passed: 282 tests across 22 files, zero Svelte errors/warnings, production build, mechanical detection, and whitespace checks. The build retains the existing JavaScript chunk-size warning above 500 kB. Sora provenance and SIL OFL are retained in public/fonts; no raster assets were generated.
+
+Verification follows the user's source-only constraint: compiler, build, interaction tests, and source review. No browser-based visual verdict is implied.
+
+
+Homepage direction: preserve the original server header, short AT Protocol introduction, account actions, and capabilities section using the shared design system. Add actual public repository counts and recent public Bluesky posts through same-origin AT Protocol reads. No promotional diagram or stripped-down utility-only replacement. Public repository counts paginate (capped at 10,000 with a lower-bound indicator); posts cover up to 48 recently updated active repositories, six records each, with sampling and partial failures disclosed. Preview screenshots use sample data.
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..09d3deb
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,312 @@
+---
+{
+ "name": "Tranquil PDS",
+ "description": "An independent, server-branded account interface with public clarity and restrained operational density.",
+ "colors": {
+ "bg-primary": "#f6f7f9",
+ "bg-secondary": "#edeff3",
+ "bg-tertiary": "#e4e7ed",
+ "bg-card": "#ffffff",
+ "bg-input": "#ffffff",
+ "text-primary": "#252c37",
+ "text-secondary": "#586172",
+ "text-muted": "#687182",
+ "border-color": "#d7dce5",
+ "border-light": "#e4e7ed",
+ "border-dark": "#a4adbd",
+ "accent": "#252c37",
+ "accent-contrast": "#ffffff",
+ "secondary": "#252c37",
+ "success-bg": "#e7f3ec",
+ "success-border": "#a3cbb5",
+ "success-text": "#005a00",
+ "error-bg": "#fcebea",
+ "error-border": "#e8b8b5",
+ "error-text": "#c00",
+ "warning-bg": "#faf0d8",
+ "warning-border": "#d4a03c",
+ "warning-text": "#5f4700",
+ "danger-bg": "#c00",
+ "danger-bg-hover": "#a00",
+ "danger-contrast": "#ffffff",
+ "info-bg": "#e0f2fe",
+ "info-text": "#0369a1",
+ "dark-bg-primary": "#14171e",
+ "dark-bg-secondary": "#1d222c",
+ "dark-bg-tertiary": "#252c37",
+ "dark-bg-card": "#1d222c",
+ "dark-bg-input": "#252c37",
+ "dark-text-primary": "#edf0f6",
+ "dark-text-secondary": "#b0b9c9",
+ "dark-text-muted": "#a1aabc",
+ "dark-border-color": "#353d4c",
+ "dark-border-light": "#2a3240",
+ "dark-border-dark": "#647087",
+ "dark-accent": "#edf0f6",
+ "dark-accent-contrast": "#14171e",
+ "dark-secondary": "#edf0f6",
+ "dark-success-bg": "#0f1f1a",
+ "dark-success-border": "#1a3d2d",
+ "dark-success-text": "#7bc6a0",
+ "dark-error-bg": "#1f0f0f",
+ "dark-error-border": "#3d1a1a",
+ "dark-error-text": "#ff8a8a",
+ "dark-warning-bg": "#1f1a0f",
+ "dark-warning-border": "#3d351a",
+ "dark-warning-text": "#c6b87b",
+ "dark-danger-bg": "#ff8a8a",
+ "dark-danger-bg-hover": "#ff6b6b",
+ "dark-danger-contrast": "#14171e",
+ "dark-info-bg": "#0c2d48",
+ "dark-info-text": "#7bc6f0",
+ "accent-hover": "color-mix(in srgb, var(--accent) 88%, var(--bg-primary))",
+ "accent-muted": "color-mix(in srgb, var(--accent) 10%, transparent)",
+ "accent-muted-strong": "color-mix(in srgb, var(--accent) 18%, transparent)",
+ "secondary-hover": "color-mix(in srgb, var(--secondary) 88%, var(--bg-primary))",
+ "secondary-muted": "color-mix(in srgb, var(--secondary) 10%, transparent)",
+ "secondary-muted-strong": "color-mix(in srgb, var(--secondary) 18%, transparent)"
+ },
+ "typography": {
+ "display": {
+ "fontFamily": "\"Sora\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
+ "fontSize": "clamp(2.6rem, 1.5rem + 3.5vw, 4.5rem)",
+ "fontWeight": 550,
+ "lineHeight": 1.12,
+ "letterSpacing": "-0.035em"
+ },
+ "headline": {
+ "fontFamily": "\"Sora\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
+ "fontSize": "clamp(1.6rem, 1.15rem + 1.3vw, 2.2rem)",
+ "fontWeight": 550,
+ "lineHeight": 1.3,
+ "letterSpacing": "-0.025em"
+ },
+ "title": {
+ "fontFamily": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
+ "fontSize": "1.25rem",
+ "fontWeight": 650,
+ "lineHeight": 1.25,
+ "letterSpacing": "-0.02em"
+ },
+ "body": {
+ "fontFamily": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
+ "fontSize": "1rem",
+ "lineHeight": 1.5
+ },
+ "label": {
+ "fontFamily": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
+ "fontSize": "0.75rem",
+ "fontWeight": 600
+ },
+ "mono": {
+ "fontFamily": "ui-monospace, \"SF Mono\", Menlo, Monaco, monospace",
+ "fontSize": "0.75rem"
+ }
+ },
+ "rounded": {
+ "sm": "0.5rem",
+ "md": "0.75rem",
+ "lg": "1rem"
+ },
+ "spacing": {
+ "0": "0",
+ "1": "0.125rem",
+ "2": "0.25rem",
+ "3": "0.5rem",
+ "4": "0.75rem",
+ "5": "1rem",
+ "6": "1.5rem",
+ "7": "2rem",
+ "8": "3rem",
+ "9": "4rem"
+ },
+ "components": {
+ "button-primary": {
+ "backgroundColor": "var(--accent)",
+ "textColor": "var(--accent-contrast)",
+ "rounded": "{rounded.sm}",
+ "padding": "0.25rem 0.75rem"
+ },
+ "button-secondary": {
+ "backgroundColor": "transparent",
+ "textColor": "var(--secondary-ink, var(--text-primary))",
+ "rounded": "{rounded.sm}",
+ "padding": "0.25rem 0.75rem"
+ },
+ "button-tertiary": {
+ "backgroundColor": "transparent",
+ "textColor": "var(--text-secondary)",
+ "rounded": "{rounded.sm}",
+ "padding": "0.25rem 0.5rem"
+ },
+ "button-danger": {
+ "backgroundColor": "var(--danger-bg)",
+ "textColor": "var(--danger-contrast)",
+ "rounded": "{rounded.sm}",
+ "padding": "0.25rem 0.75rem"
+ },
+ "button-ghost": {
+ "backgroundColor": "transparent",
+ "textColor": "var(--text-secondary)",
+ "rounded": "{rounded.sm}",
+ "padding": "0.25rem 0.75rem"
+ },
+ "input": {
+ "backgroundColor": "var(--bg-input)",
+ "textColor": "var(--text-primary)",
+ "rounded": "{rounded.sm}",
+ "padding": "{spacing.4}"
+ },
+ "navigation-active": {
+ "backgroundColor": "var(--accent-muted)",
+ "textColor": "var(--accent-ink, var(--text-primary))",
+ "rounded": "{rounded.sm}",
+ "padding": "0.65rem 0.75rem"
+ },
+ "badge-success": {
+ "backgroundColor": "{colors.success-bg}",
+ "textColor": "{colors.success-text}",
+ "rounded": "0.3rem",
+ "padding": "0.2rem 0.45rem"
+ },
+ "card": {
+ "backgroundColor": "var(--bg-card)",
+ "rounded": "{rounded.md}",
+ "padding": "{spacing.6}"
+ },
+ "task-row": {
+ "textColor": "var(--text-primary)",
+ "padding": "1.25rem 0"
+ }
+ }
+}
+---
+
+# Design System: Tranquil PDS
+
+## Overview
+
+**Creative North Star: A personal account home.** This is the approved direction expressed as a visual system: cool neutral surfaces, direct hierarchy, softly rounded controls, and visible server identity. Spacious public explanation leads into compact, quiet account work. Sora gives public headings their own voice; platform type keeps operational screens familiar.
+
+This identity is independent. The references informed clarity, navigation, and task focus; they supplied no copied layouts, UI code, logos, artwork, or Sniglet. The implementation covers public home, application shell and overview, auth/consent framing, registration progress, dedicated account/security/data/app-access surfaces, repository and DID workspaces, migration, and administrative navigation and brand previews. [The implementation record](docs/pds-redesign-plan.md#implemented-redesign) describes the scope.
+
+**Key Characteristics:**
+
+- Neutral ink stays readable when an administrator changes the accent.
+- Flat surfaces and borders organize content; typography establishes hierarchy.
+- Public display typography gives way to restrained interface typography.
+- Status, progress, and account context remain explicit.
+
+This document records source, not rendered observations. Final source checks passed: 282 tests across 22 files, zero Svelte errors/warnings, production build, mechanical detection, and whitespace checks. The build retains the existing JavaScript chunk-size warning above 500 kB. No browser or visual verification was performed. Source checks do not establish a screenshot or rendered-accessibility verdict.
+
+## Colors
+
+The cool neutral palette has paired light and dark roles. Frontmatter records the shared CSS defaults; `dark-` entries map to the same CSS variables under the dark media query. These are extracted values, not a second implementation source. Edit [shared tokens](frontend/public/design-tokens.css), then regenerate this document and its sidecar.
+
+### Primary
+
+The default accent is neutral ink. Server-configured primary colors replace fills at runtime through [brand-colors.js](frontend/public/brand-colors.js), shared by the static home and application. Valid three- or six-digit hex colors receive whichever black/white foreground has the higher contrast. Configured primary hover retains the same fill so the foreground remains appropriate. Missing or invalid settings fall back to the stylesheet.
+
+### Secondary
+
+Secondary color supports tint and border states. Text and icons use neutral `accent-ink`/`secondary-ink` aliases, with primary-text fallbacks; a configured color must not become body text merely because it is a brand color. Muted accents mix the configured color with transparency and require the intended neutral surface beneath them.
+
+### Neutral
+
+Use primary background for the canvas, card for opaque content, secondary for grouped supporting material, and tertiary for stronger local separation. Primary, secondary, and muted text form the reading hierarchy. Border-dark gives fields a clearer edge than ordinary dividers. Semantic success, error, warning, danger, and info colors remain separate from server branding.
+
+**Neutral Ink Rule.** Use the runtime contrast foreground on solid primary fills and neutral ink on tinted navigation, outlined links, and identity marks. Destructive controls use danger tokens and their own foreground.
+
+## Typography
+
+Public display and section headings use Sora with the platform sans fallback. Account screens, authentication, consent, controls, and navigation use the platform sans stack. Technical identifiers use the monospace stack. Font provenance is recorded in [README.txt](frontend/public/fonts/README.txt) and [Sora-OFL.txt](frontend/public/fonts/Sora-OFL.txt); the independently sourced font is unmodified. No raster assets were generated for this phase.
+
+The homepage uses Sora for its short introduction and platform sans for navigation, stats, and public posts. The application heading scale remains 1.5rem, 1.25rem, and 1.125rem. Long server names, handles, and DIDs wrap.
+
+## Layout
+
+The homepage centers a maximum 68rem width. The introduction (Sora headline, lede, account actions, migration link) is followed by one quiet line of server facts (accounts, active, registration), not a stats card. Below, a community grid pairs a single-column timeline with a sticky 19rem side column listing people hosted here and what every account includes (Phosphor icons inlined). The timeline shows top-level posts only, at most two per person, interleaved so no single account dominates; replies appear only when nothing else exists. Timestamps are short relative links to the post; galleries use fixed aspect ratios. Below 880px the side column follows the timeline. Account links remain usable without optional JavaScript.
+
+The application uses a 17rem sticky sidebar and a flexible content column. Content is capped at 62rem, with fluid 1.5–3.5rem padding. At 800px the sidebar becomes an in-flow navigation disclosure under the mobile brand bar; it is not an off-canvas overlay. Group headings, an account switcher, and the current location sustain orientation. The overview pairs task rows and a hosting note, stacking at 1100px; identity facts stack at 600px. Account identity/contact and security methods use two-column groups that stack at 1200px.
+
+Repository browsing pairs a scrollable record list with a read-first inspector, using a 0.7:1.3 column ratio. At 650px it switches between list and detail with an explicit Back control and focus movement. DID fields sit beside a sticky live JSON preview; the columns stack and the preview becomes static at 1200px. Search, technical identifiers, and copy actions remain visible in these workspaces.
+
+Auth content is centered in an opaque bordered surface up to 42rem. Consent and migration may use 60rem. Consent splits into account/client and permission columns from 900px. Narrow forms retain readable padding and wrap actions. Shared spacing is the extracted scale, with intrinsic grids and local measured values where needed. Administrative light/dark previews sit side by side, stacking at 500px.
+
+## Elevation & Depth
+
+Depth comes mainly from tonal surfaces and borders. Default cards and auth frames are flat. Account dropdowns and modal overlays use the shared large shadow; an interactive card may use the existing tinted hover shadow. The sidecar carries shadow values and the focus-ring treatment because those are outside the frontmatter schema. Keyboard focus uses a neutral two-pixel outline with offset, independent of configurable brand color. Do not add ambient shadows or continuous decorative motion to establish hierarchy.
+
+## Shapes
+
+Controls use the small radius, cards and grouped surfaces the medium radius, and auth/modal surfaces the large radius. Status badges use a tighter 0.3rem corner. Transfer markers and toggle knobs remain circular; the expandable phase list uses small rounded number tiles. Most controls have a 2.75rem minimum height; small button variants use 2.25rem. Borders delineate forms, operational sections, session cards, and dividers without enclosing every text row.
+
+## Components
+
+### Buttons
+
+Primary, secondary, tertiary, danger, and ghost variants share control geometry. Primary uses the runtime contrast foreground; secondary uses neutral ink on transparent/tinted backgrounds; tertiary and ghost are quieter actions. Danger uses semantic danger colors. The component accepts small, medium, and large sizes, full width, and a loading state that disables interaction. Disabled buttons have reduced opacity and a not-allowed cursor. Sidecar examples preserve actual hover and keyboard focus states.
+
+### Inputs / Fields
+
+Fields use an opaque input background, stronger border, small radius, visible label, and optional hint or inline error. Focus combines the existing secondary border/tint ring with the global neutral keyboard outline. Disabled fields use a separate background. Searchable sessions keep the search field above the list, capped at 28rem, and retain retry and empty-result states.
+
+### Chips
+
+Status badges pair a compact shape with explicit text and semantic colors. They communicate account state, not navigation. Forced-colors mode adds a visible border.
+
+### Cards / Containers
+
+Default cards are flat with medium corners and shared padding. Operational sections add a border; session/device cards use 1.25rem padding and wrap on narrow screens. Current-session emphasis strengthens the border rather than inventing a new color role. Auth cards stay opaque. Modals retain their separate elevated treatment.
+
+### Navigation
+
+The server brand anchors grouped navigation. Active items combine a muted accent surface with neutral ink and stronger weight; `aria-current` accompanies the visual state. The account menu supports switching and sign-out. Mobile navigation remains available through an explicit disclosure while content stays in the same reading flow. The shell retains role and account-state restrictions.
+
+### Account overview
+
+Overview leads with the actual handle, DID, hosting context, and applicable state notices. Task rows use dividers, concise descriptions, and a small directional icon; hover underlines the label and shifts the arrow slightly. Reduced-motion disables the transition.
+
+### Auth, consent, and registration progress
+
+Server branding sits above a focused form. Consent keeps the requesting app, active account, scopes, and explicit denial/authorization actions visible; selected permissions use muted tint with neutral text. Registration reuses the step indicator for account, security, and verification phases, mapped from the existing state machine. The current phase, count, native progress bar, and expandable complete phase list communicate position; the final phase is not itself a success assertion. This phase changes presentation without replacing protocol permission semantics or migration recovery behavior.
+
+### Account, security, data, and app access
+
+Settings groups identity/contact and language controls, with destructive account actions in a distinct final section. Security groups authentication methods and trusted devices, linking directly to sessions and connected applications. Data has its own export and portability surface with account facts. Connected Apps separates app sessions from remembered consent choices and exposes permissions on demand. App passwords use a creation disclosure above a searchable compact list; edit and secret-display states stay explicit.
+
+### Repository and DID workspaces
+
+Repository collections are searchable by family; record filtering is explicitly limited to loaded records. Selecting a record opens read-only JSON, URI/CID copy actions, and an explicit Edit control. While editing or creating, in-workspace navigation is disabled to protect the draft; save/cancel restores ordinary browsing. This does not promise persistence across route changes or reloads. Validation precedes submission, and destructive deletion remains separate.
+
+DID fields and validation occupy the editing column while a live draft JSON preview supports review and copying. Technical guidance uses disclosure. Keep the preview visibly identified as a draft until the server confirms the save.
+
+### Migration
+
+The entry screen presents online migration and offline restoration as distinct choices, followed by a prepare/transfer/identity-switch outline. Phase count and transfer progress are separate: operation rows reflect actual state, and numeric transfer progress appears only when a total exists. Review uses labeled source/target facts. Resume shows stored context and reauthentication guidance; error screens retain recovery/resume actions. Success follows the existing state machine rather than the progress bar reaching its final phase.
+
+### Administration and brand preview
+
+A wrapping section switcher separates statistics, server configuration, users, and integrations with neutral active-state treatment. Only existing server data populates operational figures. A paired light/dark preview shows server identity, neutral navigation text, accent tint, and a primary action with computed contrast text. It previews actual branding rules; it does not certify every possible custom surface or add unsupported operational metrics.
+
+## Do's and Don'ts
+
+- **Do** import the shared public tokens for both the homepage and application; retain runtime server configuration.
+- **Do** use neutral ink for reading, contrast-aware primary foregrounds for fills, and separate semantic status colors.
+- **Do** keep display type on public headings and platform type on consent, security, and account screens.
+- **Do** preserve keyboard outlines, reduced-motion behavior, long-identifier wrapping, and explicit mobile navigation.
+- **Do** preserve read-first inspection, explicit editing, draft labeling, and recovery actions when extending operational workspaces.
+- **Don't** use Sniglet or copy reference layouts, logos, artwork, or component source.
+- **Don't** treat administrator accent colors as universally legible text colors.
+- **Don't** invent account statistics, visual trust claims, or success states to fill the interface.
+- **Don't** describe source checks as browser or visual verification.
+
+
+Homepage direction: preserve the original server header, short AT Protocol introduction, account actions, and capabilities section using the shared design system. Add actual public repository counts and recent public Bluesky posts through same-origin AT Protocol reads. No promotional diagram or stripped-down utility-only replacement. Public repository counts paginate (capped at 10,000 with a lower-bound indicator); posts cover up to 48 recently updated active repositories, six records each, with sampling and partial failures disclosed. Preview screenshots use sample data.
+
+
+Brand identity: this product is Cafeteria, a downstream fork of Tranquil. Use administrator-configured serverName and the uploaded server logo served by /favicon.ico. Default to Cafeteria and text-only branding when no logo is configured or the logo cannot load. Do not substitute the generic server-rack icon for a logo. Retain Tranquil only as upstream attribution.
+
+Sidebar navigation uses a fixed 20px icon column and a flexible, left-aligned label column. Group headings reset paragraph margins; the header and footer do not shrink, while navigation scrolls on short screens. Desktop rows are at least 40px and mobile rows at least 44px.
+
+Recent posts show each author's public profile avatar, display name, and handle, plus image attachments with authored alt text. Dashboard overview and account switching use the same profile source with a bounded, one-minute request cache. Missing or failed avatars fall back to an initial. Profile records and image blobs load directly from this PDS through public reads. Avatar update previews use real public profiles and posts; the dashboard session is a local preview.
diff --git a/PRODUCT.md b/PRODUCT.md
new file mode 100644
index 0000000..56842e1
--- /dev/null
+++ b/PRODUCT.md
@@ -0,0 +1,42 @@
+# Tranquil PDS
+
+
+
+## Platform
+
+web
+
+## Product Purpose
+
+An AT Protocol personal data server with a web interface for managing identity, authentication, app access, stored records, and account portability. Administrators also configure and operate the server.
+
+## Users
+
+Account holders managing their own account and administrators managing a deployment. This audience is established by the existing routes and the approved redesign plan.
+
+## Capabilities and Constraints
+
+Preserve existing API behavior, OAuth permission semantics, account switching, account-state restrictions, migration recovery, and seven locales. The frontend is Svelte 5; the public homepage is static HTML. Server name, logo, and light/dark accent colors are deployment configuration. Display only facts supplied by the server or authenticated session.
+
+The interface separates identity/contact settings, authentication methods, connected applications, and data export/portability. Repository records open for inspection before explicit editing; DID changes have a live draft preview. Migration retains online/offline choices, resumable state, transfer progress, and recovery actions. Administration separates server statistics, configuration, users, and integrations. These surfaces organize existing capabilities without adding backend guarantees.
+
+## Brand Commitments
+
+The user approved combining atregistrar/Marque's public clarity, project-agua/Margin's application organization, and the official Bluesky PDS's focused account and consent patterns. Use the server's own identity. The user explicitly rejected copying the designs or using Marque's Sniglet; references inform interaction principles only. The approved direction is recorded in docs/pds-redesign-plan.md. The user requested source-based work without browser use.
+
+## Product Principles
+
+- Make account ownership and the hosting service understandable.
+- Keep common actions easy to find on small and large screens.
+- Explain access before authorization and consequences before destructive actions.
+- Preserve server identity and actual account state throughout each workflow.
+
+## Evidence on Hand
+
+Existing frontend routes, API types, local reference repositories, and the source/license references in the redesign plan. All seven locale files contain the same 47 `work.*` keys for the expanded interface. No invented usage metrics, testimonials, or service guarantees. Verification is source-based; no browser or visual verification was performed.
+
+
+Homepage direction: preserve the original server header, short AT Protocol introduction, account actions, and capabilities section using the shared design system. Add actual public repository counts and recent public Bluesky posts through same-origin AT Protocol reads. No promotional diagram or stripped-down utility-only replacement. Public repository counts paginate (capped at 10,000 with a lower-bound indicator); posts cover up to 48 recently updated active repositories, six records each, with sampling and partial failures disclosed. Preview screenshots use sample data.
+
+
+Brand identity: this product is Cafeteria, a downstream fork of Tranquil. Use administrator-configured serverName and the uploaded server logo served by /favicon.ico. Default to Cafeteria and text-only branding when no logo is configured or the logo cannot load. Do not substitute the generic server-rack icon for a logo. Retain Tranquil only as upstream attribution.
diff --git a/crates/tranquil-api/src/admin/config.rs b/crates/tranquil-api/src/admin/config.rs
index 05b87ea..cbe889e 100644
--- a/crates/tranquil-api/src/admin/config.rs
+++ b/crates/tranquil-api/src/admin/config.rs
@@ -63,7 +63,7 @@ pub async fn get_server_config(
server_name: config_map
.get("server_name")
.cloned()
- .unwrap_or_else(|| "Tranquil PDS".to_string()),
+ .unwrap_or_else(|| "Cafeteria".to_string()),
primary_color: config_map.get("primary_color").cloned(),
primary_color_dark: config_map.get("primary_color_dark").cloned(),
secondary_color: config_map.get("secondary_color").cloned(),
diff --git a/docs/pds-design-surface.md b/docs/pds-design-surface.md
new file mode 100644
index 0000000..cec4762
--- /dev/null
+++ b/docs/pds-design-surface.md
@@ -0,0 +1,26 @@
+# PDS redesign implementation brief
+
+The user approved the three-reference plan and asked to proceed directly with the source. Public pages persuade; account, auth, consent, and migration pages operate. Preserve server identity, existing permissions, and account-state behavior.
+
+## Direction contract
+
+THESIS: A personal account home with a clear hosting relationship and direct routes to identity, access, and data.
+
+OWN-WORLD: Cool neutral surfaces, dark ink, configurable server accents, softly rounded controls, compact system interface type. Sora provides an independent public display voice; interface screens use the platform sans. The user explicitly rejected copying the reference designs or Sniglet. No borrowed logos or UI code.
+
+STORY: Understand what this server stores, enter the account, then move from the overview into dedicated identity, security, app-access, and data workspaces. Inspect records before editing, review DID drafts, and keep migration progress and recovery explicit.
+
+FIRST VIEWPORT: Public home has a large left-aligned headline, clear account actions, and a handle/server/apps diagram. Account home has a narrow grouped sidebar, a restrained page heading, a large handle and server summary, followed by task rows. Mobile keeps an overview and explicit navigation disclosure. Auth uses a centered, opaque form under server branding. The signature interaction is the account menu and continuous mobile navigation, without decorative motion.
+
+FORM: The user-pinned Marque/Margin/official-PDS direction supersedes concept seed b10ef725; no new concept selection or browser use is needed after their approval.
+
+IMPLEMENTED SURFACES: Shared public/app tokens; static home; account shell and overview; auth/consent and registration progress; identity/contact settings; authentication methods and sessions; dedicated Data and Connected Apps; app-password disclosure/search; read-first repository list/inspector with explicit editing and in-workspace draft protection; DID fields/live draft; admin section switcher and light/dark preview; migration choice, phase/transfer progress, resume, and recovery. All seven locales contain matching work.* keys.
+
+FINISH: Source review and documentation accompany compiler, build, and test checks. Final source checks passed: 282 tests across 22 files, zero Svelte errors/warnings, production build, mechanical detection, and whitespace checks. The build retains the existing JavaScript chunk-size warning above 500 kB. Sora provenance and SIL OFL are retained in public/fonts; no raster assets were generated.
+
+Verification follows the user's source-only constraint: compiler, build, interaction tests, and source review. No browser-based visual verdict is implied.
+
+
+Homepage direction: preserve the original server header, short AT Protocol introduction, account actions, and capabilities section using the shared design system. Add actual public repository counts and recent public Bluesky posts through same-origin AT Protocol reads. No promotional diagram or stripped-down utility-only replacement. Public repository counts paginate (capped at 10,000 with a lower-bound indicator); posts cover up to 48 recently updated active repositories, six records each, with sampling and partial failures disclosed. Preview screenshots use sample data.
+
+Avatar extension: public post authors, account overview, and account switching share public profile records and same-origin image blobs. Circular cropped avatars use the existing muted accent background and neutral ink, falling back to an initial when absent or unavailable. Sizes are 44px on posts, 56px in overview, 32px on the account trigger, and 28px in its menu. Adjacent names/handles provide identity; redundant avatars are hidden from assistive technology. Profile reads omit credentials and use a bounded one-minute cache. Post image attachments retain authored alt text, lazy loading, small-radius corners, and readable failure text. This extends existing tokens without changing the palette, typography, spacing scale, or server-logo identity. The avatar previews use real public profiles/posts and a local dashboard preview session; this documentation check is source-based and makes no additional visual-verification claim.
diff --git a/docs/pds-redesign-plan.md b/docs/pds-redesign-plan.md
new file mode 100644
index 0000000..96c6c5a
--- /dev/null
+++ b/docs/pds-redesign-plan.md
@@ -0,0 +1,83 @@
+# Tranquil PDS page redesign plan
+
+Status: implemented frontend redesign, based on the approved direction and the local source of Tranquil PDS, atregistrar (Marque), project-agua (Margin), and the official Bluesky PDS distribution and account UI. The page plan below records the approved intent; the implementation record at the end describes the resulting scope. Verification is source-only.
+
+## Design judgment
+
+Marque is strongest at explaining a technical product in human terms. Its landing page leads with one clear task, uses expressive display type sparingly, and supports claims with concrete product examples. Its account shell groups navigation, gives each page a clear heading and primary action, and treats warnings and empty states as part of the workflow. Useful references: `atregistrar/apps/web/src/routes/+page.svelte`, `dashboard/+layout.svelte`, `dashboard/+page.svelte`, and `dashboard/domains/+page.svelte`.
+
+Margin is strongest when someone is already using the product. Its persistent app navigation, compact content surfaces, filter controls, account context, and mobile navigation let frequent users move through dense material without losing their place. Useful references: `project-agua/web/src/views/AppShell.tsx`, `components/navigation/Sidebar.tsx`, `components/navigation/MobileNav.tsx`, `components/ui/Tabs.tsx`, and `components/ui/EmptyState.tsx`.
+
+The official Bluesky PDS is strongest at making account trust and authorization understandable. Its user-facing UI uses a narrow, branded auth card; a simple account home that identifies the hosting service; first-class Devices and Apps pages; searchable sessions with a current-device marker; and consent that shows the active account, requesting app, human-readable permissions, technical details, and separate Deny/Authorize actions. Sign-up shows step count and progress. The account shell becomes an off-canvas menu on mobile. Its configurable service name, logo, footer links, colors, and light/dark auth backgrounds are useful precedents for server branding. The PDS distribution's dashboard screenshot is a Grafana **operator monitoring** view, not a user account dashboard; its request, latency, session, and account metrics belong in the admin/operations plan only. Sources: [PDS customization and dashboard](https://github.com/bluesky-social/pds/blob/7cccef654a9d94d935deba1ee62490b316549ef6/README.md), [official account UI source](https://github.com/bluesky-social/atproto/tree/2e583a4ed26659923b2a1effd952fce937f0feeb/packages/oauth/oauth-provider-ui/src).
+
+Before this redesign, the PDS had more capability than its presentation revealed: multiple accounts, passkeys and TOTP, app passwords, granular OAuth consent, communication channels, repository browsing, migration, delegation, DID editing, admin controls, and seven locales. The previous public homepage was a separate static HTML page with hard-coded Margin wording and a continuously animated canvas. The previous authenticated dashboard had a long flat menu and redirected desktop `/app/dashboard` to Settings. Settings mixed identity, language, export, and deletion. Useful references: `frontend/public/homepage.html`, `homepage.js`, `frontend/src/routes/Dashboard.svelte`, `frontend/src/components/dashboard/SettingsContent.svelte`, `frontend/src/lib/i18n.ts`, and `frontend/src/lib/serverConfig.svelte.ts`.
+
+**Direction:** an account home for the open web with its own visual identity. The user explicitly rejected literal design copying and Marque’s Sniglet font; use the references for principles and workflows only. Give the public pages Marque's clarity and personality; give the operational pages Margin's calm density and wayfinding; and give identity, access, and consent the official PDS's task focus and trust cues. The PDS should feel related to the local products through typography, spacing, direct copy, and component craft, while retaining its own server name, logo, and administrator-configured colors. Do not transplant any reference's accent or logo as a hard-coded global brand, and do not make an infrastructure account manager look like a social feed.
+
+## Information architecture
+
+| Area | Pages and content | Main user job |
+| --- | --- | --- |
+| Public | Home, join/register, sign in, recovery | Understand this server and enter safely |
+| Overview | New `/app/dashboard` overview | See account identity, status, and next actions |
+| Account | Handle, email/contact, language | Manage identity and reachability |
+| Security | Passkeys, password, TOTP, trusted devices, sessions, app passwords, connected apps, linked accounts | Understand and control access |
+| Data | Repository explorer, export, migration | Inspect and move personal data |
+| Advanced | Delegation, DID document, invite codes when applicable | Perform less frequent technical tasks |
+| Administration | Server configuration, users, operational information | Run the PDS, visibly separate from personal account settings |
+
+Preserve existing URLs as aliases or redirects during the transition. Keep role- and account-kind-specific navigation rules. A migrated or deactivated account must keep its current restrictions and clear status message.
+
+## Page plan
+
+1. **Public home:** Replace the generic feature paragraph and quote with a first viewport that names the actual server and answers three questions: what a PDS stores, why someone would join this one, and what to do next. Show one primary action based on state (join, sign in, or open dashboard), with migration and self-hosting as secondary paths. Use a small, factual example of handle → PDS → apps as the visual centerpiece. Put server facts and capabilities in short, scannable sections; only show facts the server can verify. Remove the always-running canvas or make any decorative motion optional and reduced-motion aware.
+2. **Sign in, registration, verification, recovery:** Use a shared, focused auth layout with the server's name/logo, clear title, step context, inline errors, and one primary action, taking the official auth card as a hierarchy reference. Preserve saved-account switching and OAuth behavior. Show step count and progress during registration; never hide recovery paths behind decorative copy. Keep the same design language for SSO, password, and passkey variants, with a legible opaque form surface over any optional background image.
+3. **Account overview and shell:** Stop redirecting desktop users from Dashboard to Settings. Lead with handle, DID, current hosting server, and actionable account state. Explain what it means for this server to host the account, as the official account home does. Show a small set of task cards such as secure account, manage app access, inspect data, and migrate/export, with real status when available. Group the sidebar by Account, Security, Data, and Advanced, as Marque does, while keeping Devices/Sessions and Connected Apps directly reachable. On narrow screens, use a compact primary navigation plus a full grouped menu; give subpages an obvious way back to account home instead of forcing users through the current menu-as-page before every task.
+4. **Account and Security:** Break the current Settings page into coherent sections. Show authentication methods and active sessions together, and app passwords and connected apps as access management. Use Margin-like compact lists for repeated items and the official UI's desktop-table/mobile-list adaptation. Include names, last-used or created dates when actually available, permission summaries, a current-device badge where verified, and clear revoke/sign-out actions. Offer search when lists are long. Keep destructive controls in a distinct final section with explicit consequences.
+5. **OAuth consent and app passwords:** Put the requesting app, active account, and requested capabilities at the top. Group scopes by what the app can do, show technical details on demand, and distinguish read from write or account-level access. Follow the official consent screen's plain-language permission descriptions and explicit Deny/Authorize choices. Show optional permissions separately when the protocol allows a real choice. Identify app trust or affiliation only when verified, and expose client legal links when supplied. Keep this screen deliberately quiet: trust and comprehension matter more than marketing.
+6. **Repository explorer and DID document:** Adapt Margin's content-first browsing rather than its social feed. Make collections and records searchable/scannable; retain breadcrumbs and show a detail pane or detail view for record JSON, CID, URI, and copy actions. Keep technical strings in monospace with wrapping/copy affordances. In the DID editor, separate editable values from the resulting document preview and explain validation before save.
+7. **Migration:** Keep the existing resumable wizard and its distinct online/offline routes. Explain the real stages of account creation, data transfer, identity switch, and finalization only where those stages match Tranquil's implementation; the official PDS migration diagram is a useful conceptual reference, not a substitute for Tranquil's state machine. Rework each step around a single decision, a plain-language description of what moves and what remains, a visible progress state, and specific recovery actions. Do not make a successful-looking screen until the server confirms completion.
+8. **Admin:** Keep server branding, users, stats, integrations, and operator health in an admin-only area. Use the same shell but a clearly marked administrative context. Draw on the official Grafana dashboard's useful questions—account creation, sessions, request volume and latency—only where Tranquil exposes reliable metrics. Preview server-color changes in both themes and reject combinations that make controls or status text unreadable.
+
+## System to build once
+
+- Define semantic tokens for surfaces, text, borders, accent, focus, and status. Keep administrator-supplied colors behind the existing runtime configuration; do not use those values blindly for all text or destructive actions.
+- Use a restrained two-level type system: approachable display treatment on public/onboarding pages; highly readable interface type in account screens; monospace only for DIDs, CIDs, scopes, and code. The same family resemblance can be achieved without putting playful display type on consent or security dialogs.
+- Build reusable Svelte components for page headings, grouped navigation, status/empty/loading/error states, form fields, action rows, confirmation dialogs, steps, and permission summaries. Borrow behavior and hierarchy from the reference projects, not their components, source code, literal copy, or artwork.
+- Align the static homepage and Svelte app through shared design tokens and assets so server branding is consistent across the transition. Preserve a functional home page even if its optional JavaScript fails.
+- Treat all seven existing locales, light/dark themes, keyboard focus, screen-reader labels, reduced motion, 320 px mobile widths, and long handles/DIDs as baseline states. Use semantic layout and intrinsic sizing; avoid controls that reorder visually without matching keyboard order.
+
+## Delivery sequence and review gates
+
+1. Map the existing routes and data available to each screen; settle the server-branding rule and the content of the new overview. Confirm what Tranquil can truthfully show for app permissions, device identity, account status, and operator metrics. Produce desktop/mobile designs for public home, overview, security, OAuth consent, and migration before broad implementation.
+2. Build shared tokens and components, then the public home and auth flows. Check join/sign-in/recovery paths with JavaScript disabled where applicable and with real server configuration.
+3. Build the dashboard shell and overview, then move Account and Security content into the new navigation without changing the underlying API behavior. Verify old URLs, account switching, role restrictions, and all supported locales.
+4. Redesign consent, app passwords, repository/DID tools, migration, and admin. Test critical success, empty, loading, denied, expired, and failed states as well as desktop/mobile keyboard navigation.
+
+## Source and license boundary
+
+The [official PDS distribution](https://github.com/bluesky-social/pds/blob/7cccef654a9d94d935deba1ee62490b316549ef6/LICENSE.txt) offers MIT or Apache-2.0 licensing for its code; the [official OAuth provider UI package](https://github.com/bluesky-social/atproto/blob/2e583a4ed26659923b2a1effd952fce937f0feeb/packages/oauth/oauth-provider-ui/package.json) declares MIT. Tranquil's `LICENSE` states AGPL-3.0-or-later for code and CC BY-SA 4.0 for its `docs` directory. This plan uses design and interaction ideas only: no official source, screenshots, logos, illustrations, or copy have been incorporated. Before any later direct reuse, check the license and provenance of each file or asset, preserve required copyright/license notices, and separately review rights to names and logos. Server-supplied branding remains the default.
+
+The implemented identity uses its own cool neutral system, independent Sora public headings, platform interface text, and server-configurable branding. The user-approved direction replaced the existing presentation without copying the references.
+
+
+## Implemented redesign
+
+The implementation replaces the existing presentation across the planned public and operational surfaces while retaining the existing API and workflow semantics:
+
+- Shared light/dark tokens, contrast-aware runtime branding, platform typography, and compact server-entry layout connect the static homepage and application.
+- Public home, branded authentication/consent/recovery frames, registration phase progress, grouped navigation, account switching, and a genuine account overview establish consistent entry and orientation.
+- Dedicated Data and Connected Apps pages separate export/portability and app access from identity/contact settings and authentication methods. Sessions and app passwords are searchable; app-password creation uses a disclosure, and remembered app choices expose permissions on demand.
+- Repository browsing uses a searchable collection directory and a read-first list/inspector workspace with URI/CID/JSON copy actions. Explicit edit/create modes disable competing in-workspace navigation to protect drafts; filtering identifies its loaded-record scope.
+- DID editing separates fields and validation from a live draft JSON preview. Administration has a section switcher for existing statistics, configuration, users, and integrations, plus paired light/dark brand previews.
+- Migration distinguishes online migration from offline restoration, separates phase position from transfer progress, shows review facts, and preserves resume, reauthentication, recovery, and confirmed-completion behavior.
+
+All seven existing locales contain matching sets of 47 `work.*` keys. Sniglet and reference-project assets are not used. Sora is independently sourced under the SIL OFL; provenance and license are in `frontend/public/fonts/README.txt` and `Sora-OFL.txt`. No raster assets were generated.
+
+Verification follows the user's source-only constraint: Svelte/TypeScript diagnostics, production build, interaction/unit tests, and source review. Final source checks passed: 282 tests across 22 files, zero Svelte errors/warnings, production build, mechanical detection, and whitespace checks. The build retains the existing JavaScript chunk-size warning above 500 kB. Responsive screenshots, browser interaction validation, and visual verification have not been performed. No new backend capability, unsupported operator metric, or draft persistence across reloads is implied by this redesign.
+
+
+Homepage direction: preserve the original server header, short AT Protocol introduction, account actions, and capabilities section using the shared design system. Add actual public repository counts and recent public Bluesky posts through same-origin AT Protocol reads. No promotional diagram or stripped-down utility-only replacement. Public repository counts paginate (capped at 10,000 with a lower-bound indicator); posts cover up to 48 recently updated active repositories, six records each, with sampling and partial failures disclosed. Preview screenshots use sample data.
+
+
+Brand identity: this product is Cafeteria, a downstream fork of Tranquil. Use administrator-configured serverName and the uploaded server logo served by /favicon.ico. Default to Cafeteria and text-only branding when no logo is configured or the logo cannot load. Do not substitute the generic server-rack icon for a logo. Retain Tranquil only as upstream attribution.
diff --git a/frontend/index.html b/frontend/index.html
index 87fc750..33c9b0b 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -3,15 +3,16 @@
+
- Tranquil PDS
+ Cafeteria
diff --git a/frontend/public/activity-taxonomy.js b/frontend/public/activity-taxonomy.js
new file mode 100644
index 0000000..485d9d5
--- /dev/null
+++ b/frontend/public/activity-taxonomy.js
@@ -0,0 +1,97 @@
+// Activity taxonomy for the public homepage.
+//
+// Adapted from Singi Labs' Sifa SDK (MIT, https://github.com/singi-labs/sifa-sdk):
+// record types are sorted into what a person made (creation) versus what they did
+// (likes, follows) or configuration, and each app belongs to one category. Only
+// creation records appear here. Icons are Phosphor regular (MIT) 256×256 paths.
+
+export const CATEGORY_ICONS = {
+ "Posts": 'M128,24A104,104,0,0,0,36.18,176.88L24.83,210.93a16,16,0,0,0,20.24,20.24l34.05-11.35A104,104,0,1,0,128,24Zm0,192a87.87,87.87,0,0,1-44.06-11.81,8,8,0,0,0-6.54-.67L40,216,52.47,178.6a8,8,0,0,0-.66-6.54A88,88,0,1,1,128,216Z',
+ "Articles": 'M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM184,96a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,96Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,128Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,160Z',
+ "Code": 'M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z',
+ "Photos": 'M208,56H180.28L166.65,35.56A8,8,0,0,0,160,32H96a8,8,0,0,0-6.65,3.56L75.71,56H48A24,24,0,0,0,24,80V192a24,24,0,0,0,24,24H208a24,24,0,0,0,24-24V80A24,24,0,0,0,208,56Zm8,136a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V80a8,8,0,0,1,8-8H80a8,8,0,0,0,6.66-3.56L100.28,48h55.43l13.63,20.44A8,8,0,0,0,176,72h32a8,8,0,0,1,8,8ZM128,88a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,88Zm0,72a28,28,0,1,1,28-28A28,28,0,0,1,128,160Z',
+ "Events": 'M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM72,48v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80H48V48ZM208,208H48V96H208V208Z',
+ "Reviews": 'M239.18,97.26A16.38,16.38,0,0,0,224.92,86l-59-4.76L143.14,26.15a16.36,16.36,0,0,0-30.27,0L90.11,81.23,31.08,86a16.46,16.46,0,0,0-9.37,28.86l45,38.83L53,211.75a16.38,16.38,0,0,0,24.5,17.82L128,198.49l50.53,31.08A16.4,16.4,0,0,0,203,211.75l-13.76-58.07,45-38.83A16.43,16.43,0,0,0,239.18,97.26Zm-15.34,5.47-48.7,42a8,8,0,0,0-2.56,7.91l14.88,62.8a.37.37,0,0,1-.17.48c-.18.14-.23.11-.38,0l-54.72-33.65a8,8,0,0,0-8.38,0L69.09,215.94c-.15.09-.19.12-.38,0a.37.37,0,0,1-.17-.48l14.88-62.8a8,8,0,0,0-2.56-7.91l-48.7-42c-.12-.1-.23-.19-.13-.5s.18-.27.33-.29l63.92-5.16A8,8,0,0,0,103,91.86l24.62-59.61c.08-.17.11-.25.35-.25s.27.08.35.25L153,91.86a8,8,0,0,0,6.75,4.92l63.92,5.16c.15,0,.24,0,.33.29S224,102.63,223.84,102.73Z',
+ "Links": 'M165.66,90.34a8,8,0,0,1,0,11.32l-64,64a8,8,0,0,1-11.32-11.32l64-64A8,8,0,0,1,165.66,90.34ZM215.6,40.4a56,56,0,0,0-79.2,0L106.34,70.45a8,8,0,0,0,11.32,11.32l30.06-30a40,40,0,0,1,56.57,56.56l-30.07,30.06a8,8,0,0,0,11.31,11.32L215.6,119.6a56,56,0,0,0,0-79.2ZM138.34,174.22l-30.06,30.06a40,40,0,1,1-56.56-56.57l30.05-30.05a8,8,0,0,0-11.32-11.32L40.4,136.4a56,56,0,0,0,79.2,79.2l30.06-30.07a8,8,0,0,0-11.32-11.31Z',
+ "Art": 'M232,32a8,8,0,0,0-8-8c-44.08,0-89.31,49.71-114.43,82.63A60,60,0,0,0,32,164c0,30.88-19.54,44.73-20.47,45.37A8,8,0,0,0,16,224H92a60,60,0,0,0,57.37-77.57C182.3,121.31,232,76.08,232,32ZM92,208H34.63C41.38,198.41,48,183.92,48,164a44,44,0,1,1,44,44Zm32.42-94.45q5.14-6.66,10.09-12.55A76.23,76.23,0,0,1,155,121.49q-5.9,4.94-12.55,10.09A60.54,60.54,0,0,0,124.42,113.55Zm42.7-2.68a92.57,92.57,0,0,0-22-22c31.78-34.53,55.75-45,69.9-47.91C212.17,55.12,201.65,79.09,167.12,110.87Z',
+ "Video": 'M251.77,73a8,8,0,0,0-8.21.39L208,97.05V72a16,16,0,0,0-16-16H32A16,16,0,0,0,16,72V184a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V159l35.56,23.71A8,8,0,0,0,248,184a8,8,0,0,0,8-8V80A8,8,0,0,0,251.77,73ZM192,184H32V72H192V184Zm48-22.95-32-21.33V116.28L240,95Z',
+ "Pastes": 'M200,32H163.74a47.92,47.92,0,0,0-71.48,0H56A16,16,0,0,0,40,48V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V48A16,16,0,0,0,200,32Zm-72,0a32,32,0,0,1,32,32H96A32,32,0,0,1,128,32Zm72,184H56V48H82.75A47.93,47.93,0,0,0,80,64v8a8,8,0,0,0,8,8h80a8,8,0,0,0,8-8V64a47.93,47.93,0,0,0-2.75-16H200Z',
+ "Recipes": 'M88,48V16a8,8,0,0,1,16,0V48a8,8,0,0,1-16,0Zm40,8a8,8,0,0,0,8-8V16a8,8,0,0,0-16,0V48A8,8,0,0,0,128,56Zm32,0a8,8,0,0,0,8-8V16a8,8,0,0,0-16,0V48A8,8,0,0,0,160,56Zm92.8,46.4L224,124v60a32,32,0,0,1-32,32H64a32,32,0,0,1-32-32V124L3.2,102.4a8,8,0,0,1,9.6-12.8L32,104V80a8,8,0,0,1,8-8H216a8,8,0,0,1,8,8v24l19.2-14.4a8,8,0,0,1,9.6,12.8ZM208,88H48v96a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16Z',
+ "Verification": 'M216.57,39.43A80,80,0,0,0,83.91,120.78L28.69,176A15.86,15.86,0,0,0,24,187.31V216a16,16,0,0,0,16,16H72a8,8,0,0,0,8-8V208H96a8,8,0,0,0,8-8V184h16a8,8,0,0,0,5.66-2.34l9.56-9.57A79.73,79.73,0,0,0,160,176h.1A80,80,0,0,0,216.57,39.43ZM224,98.1c-1.09,34.09-29.75,61.86-63.89,61.9H160a63.7,63.7,0,0,1-23.65-4.51,8,8,0,0,0-8.84,1.68L116.69,168H96a8,8,0,0,0-8,8v16H72a8,8,0,0,0-8,8v16H40V187.31l58.83-58.82a8,8,0,0,0,1.68-8.84A63.72,63.72,0,0,1,96,95.92c0-34.14,27.81-62.8,61.9-63.89A64,64,0,0,1,224,98.1ZM192,76a12,12,0,1,1-12-12A12,12,0,0,1,192,76Z',
+ "Q&A": 'M140,180a12,12,0,1,1-12-12A12,12,0,0,1,140,180ZM128,72c-22.06,0-40,16.15-40,36v4a8,8,0,0,0,16,0v-4c0-11,10.77-20,24-20s24,9,24,20-10.77,20-24,20a8,8,0,0,0-8,8v8a8,8,0,0,0,16,0v-.72c18.24-3.35,32-17.9,32-35.28C168,88.15,150.06,72,128,72Zm104,56A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z',
+ "Places": 'M128,64a40,40,0,1,0,40,40A40,40,0,0,0,128,64Zm0,64a24,24,0,1,1,24-24A24,24,0,0,1,128,128Zm0-112a88.1,88.1,0,0,0-88,88c0,31.4,14.51,64.68,42,96.25a254.19,254.19,0,0,0,41.45,38.3,8,8,0,0,0,9.18,0A254.19,254.19,0,0,0,174,200.25c27.45-31.57,42-64.85,42-96.25A88.1,88.1,0,0,0,128,16Zm0,206c-16.53-13-72-60.75-72-118a72,72,0,0,1,144,0C200,161.23,144.53,209,128,222Z',
+ "Research": 'M200,168a32.06,32.06,0,0,0-31,24H72a32,32,0,0,1,0-64h96a40,40,0,0,0,0-80H72a8,8,0,0,0,0,16h96a24,24,0,0,1,0,48H72a48,48,0,0,0,0,96h97a32,32,0,1,0,31-40Zm0,48a16,16,0,1,1,16-16A16,16,0,0,1,200,216Z',
+};
+
+// Apps, with a per-record link where the app has one. Placeholders: {did}, {handle}, {rkey}.
+export const APPS = {
+ bluesky: { name: 'Bluesky', item: 'https://bsky.app/profile/{did}/post/{rkey}', profile: 'https://bsky.app/profile/{did}' },
+ whitewind: { name: 'WhiteWind', item: 'https://whtwnd.com/{handle}/{rkey}', profile: 'https://whtwnd.com/{handle}' },
+ leaflet: { name: 'Leaflet', item: 'https://leaflet.pub/{rkey}', profile: 'https://leaflet.pub' },
+ standard: { name: 'a Standard.site blog' },
+ greengale: { name: 'GreenGale', profile: 'https://greengale.app/{handle}' },
+ tangled: { name: 'Tangled', profile: 'https://tangled.sh/{handle}' },
+ events: { name: 'atmo.rsvp', item: 'https://atmo.rsvp/p/{did}/e/{rkey}', profile: 'https://atmo.rsvp/p/{did}' },
+ grain: { name: 'Grain', item: 'https://grain.social/profile/{did}/gallery/{rkey}', profile: 'https://grain.social/profile/{did}' },
+ flashes: { name: 'Flashes' },
+ pixl: { name: 'pixl' },
+ popfeed: { name: 'Popfeed', profile: 'https://popfeed.social/profile/{handle}' },
+ bookhive: { name: 'BookHive', profile: 'https://bookhive.buzz/profile/{handle}' },
+ frontpage: { name: 'Frontpage', item: 'https://frontpage.fyi/post/{did}/{rkey}', profile: 'https://frontpage.fyi/profile/{did}' },
+ pinksea: { name: 'PinkSea' },
+ streamplace: { name: 'Streamplace', profile: 'https://stream.place/{handle}' },
+ pastesphere: { name: 'PasteSphere', item: 'https://pastesphere.link/user/{handle}/snippet/{rkey}', profile: 'https://pastesphere.link/user/{handle}' },
+ plonk: { name: 'plonk.li' },
+ recipe: { name: 'recipe.exchange' },
+ keytrace: { name: 'Keytrace', profile: 'https://keytrace.dev/@{handle}' },
+ asq: { name: 'asq', item: 'https://asq.fyi/q/{did}/{rkey}', profile: 'https://asq.fyi' },
+ spark: { name: 'Spark', item: 'https://sprk.so/post/{did}/{rkey}', profile: 'https://sprk.so/profile/{handle}' },
+ beaconbits: { name: 'Beaconbits', profile: 'https://beaconbits.app' },
+ dropanchor: { name: 'Drop Anchor' },
+ margin: { name: 'Margin', profile: 'https://margin.at' },
+ semble: { name: 'Semble', profile: 'https://semble.so/profile/{handle}' },
+ pckt: { name: 'pckt' },
+};
+
+// Record type → app, category and how the action reads ("Wrote an article on WhiteWind").
+export const LEXICONS = {
+ 'app.bsky.feed.post': { app: 'bluesky', category: 'Posts', verb: 'Posted' },
+ 'com.whtwnd.blog.entry': { app: 'whitewind', category: 'Articles', verb: 'Wrote an article' },
+ 'pub.leaflet.document': { app: 'leaflet', category: 'Articles', verb: 'Published a document' },
+ 'site.standard.document': { app: 'standard', category: 'Articles', verb: 'Published an article' },
+ 'app.greengale.blog.entry': { app: 'greengale', category: 'Articles', verb: 'Wrote an article' },
+ 'blog.pckt.mini.post': { app: 'pckt', category: 'Posts', verb: 'Posted' },
+ 'sh.tangled.repo': { app: 'tangled', category: 'Code', verb: 'Created a repository' },
+ 'sh.tangled.repo.issue': { app: 'tangled', category: 'Code', verb: 'Opened an issue' },
+ 'sh.tangled.repo.pull': { app: 'tangled', category: 'Code', verb: 'Opened a pull request' },
+ 'community.lexicon.calendar.event': { app: 'events', category: 'Events', verb: 'Created an event' },
+ 'community.lexicon.calendar.rsvp': { app: 'events', category: 'Events', verb: 'RSVP’d to an event' },
+ 'social.grain.gallery': { app: 'grain', category: 'Photos', verb: 'Shared a gallery' },
+ 'blue.flashes.feed.post': { app: 'flashes', category: 'Photos', verb: 'Shared a photo' },
+ 'pics.pixl.image': { app: 'pixl', category: 'Photos', verb: 'Shared a photo' },
+ 'social.popfeed.feed.review': { app: 'popfeed', category: 'Reviews', verb: 'Wrote a review' },
+ 'buzz.bookhive.book': { app: 'bookhive', category: 'Reviews', verb: 'Logged a book' },
+ 'fyi.unravel.frontpage.post': { app: 'frontpage', category: 'Links', verb: 'Shared a link' },
+ 'com.shinolabs.pinksea.oekaki': { app: 'pinksea', category: 'Art', verb: 'Drew something' },
+ 'place.stream.livestream': { app: 'streamplace', category: 'Video', verb: 'Went live' },
+ 'link.pastesphere.snippet': { app: 'pastesphere', category: 'Pastes', verb: 'Shared a snippet' },
+ 'li.plonk.paste': { app: 'plonk', category: 'Pastes', verb: 'Shared a paste' },
+ 'exchange.recipe.recipe': { app: 'recipe', category: 'Recipes', verb: 'Shared a recipe' },
+ 'dev.keytrace.claim': { app: 'keytrace', category: 'Verification', verb: 'Linked an account' },
+ 'fyi.asq.question': { app: 'asq', category: 'Q&A', verb: 'Asked a question' },
+ 'so.sprk.feed.post': { app: 'spark', category: 'Video', verb: 'Posted' },
+ 'app.beaconbits.beacon': { app: 'beaconbits', category: 'Places', verb: 'Checked in' },
+ 'app.dropanchor.checkin': { app: 'dropanchor', category: 'Places', verb: 'Checked in' },
+ 'at.margin.note': { app: 'margin', category: 'Research', verb: 'Wrote a note' },
+ 'at.margin.annotation': { app: 'margin', category: 'Research', verb: 'Annotated a page' },
+ 'app.sidetrail.trail': { app: 'semble', category: 'Research', verb: 'Made a trail' },
+};
+
+export const CATEGORY_ORDER = ['Posts', 'Articles', 'Code', 'Photos', 'Events', 'Reviews', 'Links', 'Video', 'Art', 'Places', 'Recipes', 'Pastes', 'Q&A', 'Research', 'Verification'];
+
+export function appLink(appId, { did, handle, rkey }) {
+ const app = APPS[appId];
+ const template = (rkey && app?.item) || app?.profile;
+ if (!template || (template.includes('{handle}') && !handle)) return null;
+ return template.replace(/\{(did|handle|rkey)\}/g, (_, key) => encodeURIComponent({ did, handle, rkey }[key]));
+}
diff --git a/frontend/public/actor-profile.d.ts b/frontend/public/actor-profile.d.ts
new file mode 100644
index 0000000..77ebd34
--- /dev/null
+++ b/frontend/public/actor-profile.d.ts
@@ -0,0 +1,4 @@
+export interface ActorProfile { displayName?: string; description?: string; avatarUrl?: string | null }
+export function imageBlobUrl(did: string, blob: unknown): string | null;
+export function readActorProfile(did: string, fetcher?: typeof fetch): Promise;
+export function loadActorProfile(did: string): Promise;
diff --git a/frontend/public/actor-profile.js b/frontend/public/actor-profile.js
new file mode 100644
index 0000000..ceaff51
--- /dev/null
+++ b/frontend/public/actor-profile.js
@@ -0,0 +1,32 @@
+// Profiles and their blobs are public AT Protocol records on this PDS.
+export function imageBlobUrl(did, blob) {
+ const cid = blob?.ref?.$link || blob?.cid;
+ if (typeof did !== 'string' || !did.startsWith('did:') || typeof cid !== 'string' || !/^[a-zA-Z0-9]{1,256}$/.test(cid)) return null;
+ if (!/^image\/(png|jpeg|webp|gif|avif)$/.test(blob.mimeType || '')) return null;
+ return `/xrpc/com.atproto.sync.getBlob?${new URLSearchParams({ did, cid })}`;
+}
+
+export async function readActorProfile(did, fetcher = fetch) {
+ try {
+ const params = new URLSearchParams({ repo: did, collection: 'app.bsky.actor.profile', rkey: 'self' });
+ const response = await fetcher(`/xrpc/com.atproto.repo.getRecord?${params}`, { credentials: 'omit', signal: AbortSignal.timeout(12000) });
+ if (!response.ok) return {};
+ const { value } = await response.json();
+ return {
+ displayName: typeof value?.displayName === 'string' ? value.displayName.trim() : undefined,
+ description: typeof value?.description === 'string' ? value.description.trim() : undefined,
+ avatarUrl: imageBlobUrl(did, value?.avatar),
+ };
+ } catch { return {}; }
+}
+
+const profiles = new Map();
+export function loadActorProfile(did) {
+ const cached = profiles.get(did);
+ if (cached && cached.expires > Date.now()) return cached.promise;
+ const promise = readActorProfile(did);
+ profiles.set(did, { expires: Date.now() + 60000, promise });
+ // Keep long-lived account-manager tabs bounded.
+ if (profiles.size > 100) profiles.delete(profiles.keys().next().value);
+ return promise;
+}
diff --git a/frontend/public/brand-colors.d.ts b/frontend/public/brand-colors.d.ts
new file mode 100644
index 0000000..0ac2d26
--- /dev/null
+++ b/frontend/public/brand-colors.d.ts
@@ -0,0 +1,8 @@
+export interface BrandColors {
+ primaryColor?: string | null;
+ primaryColorDark?: string | null;
+ secondaryColor?: string | null;
+ secondaryColorDark?: string | null;
+}
+export function contrastColor(color: string): string | null;
+export function applyBrandColors(root: HTMLElement, config: BrandColors, dark: boolean): void;
diff --git a/frontend/public/brand-colors.js b/frontend/public/brand-colors.js
new file mode 100644
index 0000000..b382f56
--- /dev/null
+++ b/frontend/public/brand-colors.js
@@ -0,0 +1,36 @@
+// Shared by the static homepage and the account application.
+function luminance(hex) {
+ const normalized = /^#[\da-f]{3}$/i.test(hex)
+ ? '#' + [...hex.slice(1)].map((value) => value + value).join('')
+ : hex;
+ if (!/^#[\da-f]{6}$/i.test(normalized)) return null;
+ const channels = [1, 3, 5].map((offset) => {
+ const value = parseInt(normalized.slice(offset, offset + 2), 16) / 255;
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
+ });
+ return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
+}
+
+export function contrastColor(color) {
+ const value = luminance(color);
+ if (value === null) return null;
+ return (value + 0.05) / 0.05 >= 1.05 / (value + 0.05) ? '#000000' : '#ffffff';
+}
+
+export function applyBrandColors(root, config, dark) {
+ const primary = dark ? config.primaryColorDark : config.primaryColor;
+ const secondary = dark ? config.secondaryColorDark : config.secondaryColor;
+ for (const [property, color] of [['--accent', primary], ['--secondary', secondary]]) {
+ if (color && luminance(color) !== null) root.style.setProperty(property, color);
+ else root.style.removeProperty(property);
+ }
+ const contrast = primary && contrastColor(primary);
+ if (contrast) root.style.setProperty('--accent-contrast', contrast);
+ else root.style.removeProperty('--accent-contrast');
+ // Keep configured fills stable on hover so their computed foreground stays safe.
+ if (contrast) root.style.setProperty('--accent-hover', primary);
+ else root.style.removeProperty('--accent-hover');
+ // Neutral ink works on both neutral and lightly tinted navigation surfaces.
+ root.style.setProperty('--accent-ink', 'var(--text-primary)');
+ root.style.setProperty('--secondary-ink', 'var(--text-primary)');
+}
diff --git a/frontend/public/design-tokens.css b/frontend/public/design-tokens.css
new file mode 100644
index 0000000..f34582d
--- /dev/null
+++ b/frontend/public/design-tokens.css
@@ -0,0 +1,166 @@
+@font-face {
+ font-family: "Sora";
+ src: url("/fonts/Sora.ttf") format("truetype");
+ font-weight: 100 800;
+ font-style: normal;
+ font-display: swap;
+}
+
+:root {
+ color-scheme: light dark;
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ --font-display: "Sora", var(--font-sans);
+ --radius-sm: 0.5rem;
+ --radius-md: 0.75rem;
+ --radius-lg: 1rem;
+ --accent-contrast: #ffffff;
+ --danger-contrast: #ffffff;
+ --control-height: 2.75rem;
+ --space-0: 0;
+ --space-1: 0.125rem;
+ --space-2: 0.25rem;
+ --space-3: 0.5rem;
+ --space-4: 0.75rem;
+ --space-5: 1rem;
+ --space-6: 1.5rem;
+ --space-7: 2rem;
+ --space-8: 3rem;
+ --space-9: 4rem;
+
+ --text-xs: 0.75rem;
+ --text-sm: 0.875rem;
+ --text-base: 1rem;
+ --text-lg: 1.125rem;
+ --text-xl: 1.25rem;
+ --text-2xl: 1.5rem;
+ --text-3xl: 2rem;
+ --text-4xl: 2.5rem;
+
+ --font-normal: 400;
+ --font-medium: 500;
+ --font-semibold: 600;
+ --font-bold: 700;
+
+ --leading-tight: 1.25;
+ --leading-normal: 1.5;
+ --leading-relaxed: 1.75;
+
+ --width-xs: 360px;
+ --width-sm: 480px;
+ --width-md: 760px;
+ --width-lg: 960px;
+ --width-xl: 1100px;
+
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+ --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1);
+ --shadow-lg: 0 12px 32px rgba(20, 24, 32, 0.14);
+ --shadow-focus: 0 0 0 2px var(--secondary-muted);
+
+ --z-modal: 1000;
+ --overlay-bg: rgba(0, 0, 0, 0.5);
+
+ --font-mono: ui-monospace, "SF Mono", Menlo, Monaco, monospace;
+
+ --bg-primary: #f6f7f9;
+ --bg-secondary: #edeff3;
+ --bg-tertiary: #e4e7ed;
+ --bg-hover: #e4e7ed;
+ --bg-card: #ffffff;
+ --bg-input: #ffffff;
+ --bg-input-disabled: #edeff3;
+ --bg-elevated: #ffffff;
+ --select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath fill='%23586172' d='M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z'/%3E%3C/svg%3E");
+
+ --text-primary: #252c37;
+ --text-secondary: #586172;
+ --text-muted: #687182;
+ --text-inverse: #ffffff;
+
+ --border-color: #d7dce5;
+ --border-light: #e4e7ed;
+ --border-dark: #a4adbd;
+
+ --accent: #252c37;
+ --accent-hover: color-mix(in srgb, var(--accent) 88%, var(--bg-primary));
+ --accent-muted: color-mix(in srgb, var(--accent) 10%, transparent);
+ --accent-muted-strong: color-mix(in srgb, var(--accent) 18%, transparent);
+ --accent-light: #3a403f;
+
+ --secondary: #252c37;
+ --secondary-hover: color-mix(in srgb, var(--secondary) 88%, var(--bg-primary));
+ --secondary-muted: color-mix(in srgb, var(--secondary) 10%, transparent);
+ --secondary-muted-strong: color-mix(in srgb, var(--secondary) 18%, transparent);
+
+ --success-bg: #e7f3ec;
+ --success-border: #a3cbb5;
+ --success-text: #005a00;
+
+ --error-bg: #fcebea;
+ --error-border: #e8b8b5;
+ --error-text: #c00;
+
+ --warning-bg: #faf0d8;
+ --warning-border: #d4a03c;
+ --warning-text: #5f4700;
+
+ --danger-bg: #c00;
+ --danger-bg-hover: #a00;
+
+ --info-bg: #e0f2fe;
+ --info-text: #0369a1;
+
+ --border-color-light: var(--border-dark);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --accent-contrast: #0a0b0d;
+ --danger-contrast: #0a0b0d;
+ --bg-primary: #0a0b0d;
+ --bg-secondary: #16181c;
+ --bg-tertiary: #1e2126;
+ --bg-hover: #1b1d22;
+ --bg-card: #111316;
+ --bg-input: #0a0b0d;
+ --bg-input-disabled: #16181c;
+ --bg-elevated: #16181c;
+ --select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath fill='%23a9afba' d='M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z'/%3E%3C/svg%3E");
+
+ --text-primary: #eceef2;
+ --text-secondary: #a9afba;
+ --text-muted: #8c929e;
+ --text-inverse: #0a0b0d;
+
+ --border-color: #25282e;
+ --border-light: #1c1e23;
+ --border-dark: #474c56;
+
+ --accent: #eceef2;
+ --accent-light: #ffffff;
+
+ --secondary: #eceef2;
+
+ --success-bg: #0c1812;
+ --success-border: #1f3b2b;
+ --success-text: #82cea5;
+
+ --error-bg: #1b0c0d;
+ --error-border: #45211f;
+ --error-text: #ff9b94;
+
+ --warning-bg: #19140a;
+ --warning-border: #4a3a16;
+ --warning-text: #e3c675;
+
+ --danger-bg: #ff8a84;
+ --danger-bg-hover: #ff736c;
+
+ --info-bg: #0a1824;
+ --info-text: #8ccbf2;
+
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
+ --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
+ --overlay-bg: rgba(0, 0, 0, 0.7);
+ }
+}
diff --git a/frontend/public/fonts/README.txt b/frontend/public/fonts/README.txt
new file mode 100644
index 0000000..6525f54
--- /dev/null
+++ b/frontend/public/fonts/README.txt
@@ -0,0 +1,5 @@
+Sora variable font, by the Sora Project Authors.
+Source: https://github.com/google/fonts/tree/main/ofl/sora
+Downloaded from Sora[wght].ttf on 2026-09-24.
+Licensed under the SIL Open Font License 1.1; see Sora-OFL.txt.
+The font is unmodified. No fonts or assets were copied from the design reference projects.
diff --git a/frontend/public/fonts/Sora-OFL.txt b/frontend/public/fonts/Sora-OFL.txt
new file mode 100644
index 0000000..f1d9d6a
--- /dev/null
+++ b/frontend/public/fonts/Sora-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2019 The Sora Project Authors (https://github.com/sora-xor/sora-font)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+
+This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/frontend/public/fonts/Sora.ttf b/frontend/public/fonts/Sora.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..3b93d661fa07f5948799ba29979009b409cd4740
GIT binary patch
literal 111400
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 xOLi6wLyM2sq0zdpSBf_((}g
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