",
+ "css": ".ds-status-error { padding: 14px; border-radius: 6px; background: rgba(246, 90, 127, 0.12); color: hsl(348, 92%, 64%); border: 1px solid rgba(246, 90, 127, 0.25); font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; font-size: 18px; } .ds-status-error p { margin: 0; }"
+ }
+ ],
+ "narrative": {
+ "northStar": "The Accessibility Lab",
+ "overview": "Altifier is clinical but humane — a dark, calm workspace where a potentially intimidating task (auditing and fixing accessibility gaps across your entire post history) feels manageable. The interface uses color sparingly and only for affordance: Protocol Teal says \"act,\" Accessible Green says \"done,\" Alert Crimson says \"danger.\" Everything else recedes into a hierarchy of cool blue-grays.\n\nThe user is not a designer. They came here to fix alt text. The system's job is to guide them through that workflow without asking them to learn a new visual language. Every surface is a question answered: what do I do now, what needs my attention, what will happen if I proceed.",
+ "keyCharacteristics": [
+ "Dark-first: deep navy background with layered blue-gray surfaces",
+ "Single accent: Protocol Teal for primary actions and focus states",
+ "Status colors read at a glance: green for confirmation, red for warning",
+ "No decoration that doesn't earn its place: no gradients, no shadows, no glassmorphism",
+ "48px touch targets throughout; 18px base font; WCAG AA contrast"
+ ],
+ "rules": [
+ {
+ "name": "The One Accent Rule",
+ "body": "Protocol Teal is the only affirmative action color. There is no secondary accent, no purple, no orange. If it's not Protocol Teal, it's either neutral, success, or error. This prevents the \"Christmas tree\" effect common in status-heavy UIs.",
+ "section": "colors"
+ },
+ {
+ "name": "The Ghost Status Rule",
+ "body": "Status colors (green, red) never appear as solid fills except on the Confirm button in the engagement-loss modal. They always render at reduced opacity on backgrounds (12%) and borders (25%). This keeps the workspace calm even when many posts show status simultaneously.",
+ "section": "colors"
+ },
+ {
+ "name": "The 18px Floor Rule",
+ "body": "No text smaller than 18px for body copy. Meta and label text may go to 0.9em (~16px) but never smaller. This is a WCAG AA baseline decision, not a stylistic one.",
+ "section": "typography"
+ },
+ {
+ "name": "The Flat-By-Default Rule",
+ "body": "Surfaces are flat at rest. No shadows. No gradients. No glassmorphism. The only visual animation in the system is the flash-red-warning keyframe on destructive button hover — and even that is a color pulse, not a depth change.",
+ "section": "elevation"
+ }
+ ],
+ "dos": [
+ "Do use Protocol Teal for exactly one primary action per visible surface. If two things look equally actionable, one of them is wrong.",
+ "Do use the 4px rhythm (8, 12, 16, 20, 24) for all spacing. No custom px values outside this scale.",
+ "Do keep status indicators at reduced opacity (12% bg, 25% border) except the modal confirm button.",
+ "Do maintain 48px touch targets on all interactive elements.",
+ "Do set body text at 18px. Never shrink below 0.9em for meta text."
+ ],
+ "donts": [
+ "Don't add shadows, gradients, or glassmorphism effects. The system is deliberately flat.",
+ "Don't introduce a second accent color. Protocol Teal is the only affirmative action color.",
+ "Don't use box-shadows to create depth. Use tonal layering (Navy → Dark Blue-Gray → Darker Blue-Gray).",
+ "Don't use red as a solid fill anywhere except the engagement-loss confirmation button."
+ ]
+ }
+}
\ No newline at end of file
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..19dec49
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,195 @@
+---
+name: Altifier
+description: Scan your Bluesky posts for missing alt text and fix them with AI-generated descriptions.
+colors:
+ protocol-teal: "hsl(174, 83%, 38%)"
+ protocol-teal-deep: "hsl(174, 78%, 32%)"
+ deep-navy: "#111822"
+ dark-blue-gray: "#1C2B35"
+ darker-blue-gray: "#15232C"
+ medium-blue-gray: "#394A58"
+ near-white: "#F9FAFB"
+ cool-gray: "#A5B2C5"
+ accessible-green: "#2CF28F"
+ alert-crimson: "hsl(348, 92%, 64%)"
+typography:
+ heading:
+ fontFamily: "-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif"
+ fontWeight: 700
+ body:
+ fontFamily: "-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif"
+ fontSize: "18px"
+ fontWeight: 400
+ lineHeight: "1.6"
+rounded:
+ sm: "6px"
+ md: "8px"
+spacing:
+ xs: "10px"
+ sm: "12px"
+ md: "16px"
+ lg: "20px"
+ xl: "24px"
+components:
+ button-primary:
+ backgroundColor: "{colors.protocol-teal}"
+ textColor: "{colors.near-white}"
+ rounded: "{rounded.sm}"
+ height: "48px"
+ padding: "10px 24px"
+ button-primary-hover:
+ backgroundColor: "{colors.protocol-teal-deep}"
+ button-danger:
+ backgroundColor: "transparent"
+ textColor: "{colors.alert-crimson}"
+ rounded: "{rounded.sm}"
+ height: "44px"
+ padding: "8px 16px"
+ input:
+ backgroundColor: "{colors.dark-blue-gray}"
+ textColor: "{colors.near-white}"
+ rounded: "{rounded.sm}"
+ height: "48px"
+ padding: "10px 14px"
+ card:
+ backgroundColor: "{colors.dark-blue-gray}"
+ rounded: "{rounded.md}"
+ padding: "24px"
+ modal-overlay:
+ backgroundColor: "rgba(0, 0, 0, 0.7)"
+---
+
+# Design System: Altifier
+
+## Overview
+
+**Creative North Star: "The Accessibility Lab"**
+
+Altifier is clinical but humane — a dark, calm workspace where a potentially intimidating task (auditing and fixing accessibility gaps across your entire post history) feels manageable. The interface uses color sparingly and only for affordance: Protocol Teal says "act," Accessible Green says "done," Alert Crimson says "danger." Everything else recedes into a hierarchy of cool blue-grays.
+
+The user is not a designer. They came here to fix alt text. The system's job is to guide them through that workflow without asking them to learn a new visual language. Every surface is a question answered: what do I do now, what needs my attention, what will happen if I proceed.
+
+**Key Characteristics:**
+- Dark-first: deep navy background (`#111822`) with layered blue-gray surfaces
+- Single accent: Protocol Teal (`hsl(174, 83%, 38%)`) for primary actions and focus states
+- Status colors read at a glance: green for confirmation, red for warning
+- No decoration that doesn't earn its place: no gradients, no shadows, no glassmorphism
+- 48px touch targets throughout; 18px base font; WCAG AA contrast
+
+## Colors
+
+The palette is built on a single accent against a family of cool blue-gray neutrals. There is no secondary accent — Protocol Teal carries all affirmative action.
+
+### Primary
+- **Protocol Teal** (`hsl(174, 83%, 38%)` / `#0DB8A7`): Buttons, links, focus rings, active states. The only color that initiates action. Used on primary buttons, the home page CTA, navbar logo, and input focus borders. Deepens to `hsl(174, 78%, 32%)` on hover.
+
+### Neutral
+- **Deep Navy** (`#111822`): Page background. The canvas. Never used on interactive surfaces — those get one step up.
+- **Dark Blue-Gray** (`#1C2B35`): Card backgrounds, form backgrounds, input backgrounds. The work surface.
+- **Darker Blue-Gray** (`#15232C`): Navbar, footer, image label backgrounds. The framing surfaces that contain the workspace.
+- **Medium Blue-Gray** (`#394A58`): Borders, dividers. Visible but not loud.
+- **Near-White** (`#F9FAFB`): Body text, button text, headings. Maximum contrast for readability.
+- **Cool Gray** (`#A5B2C5`): Muted text, meta information, placeholders. Information that supports the primary text without competing.
+
+### Status
+- **Accessible Green** (`#2CF28F`): Success messages, "has alt text" indicators, approved state. Confirmation that something went right. Used at 12% opacity for backgrounds, 25% for borders.
+- **Alert Crimson** (`hsl(348, 92%, 64%)`): Error messages, "missing alt text" indicators, destructive actions, the engagement-loss warning. Used at 12% opacity for backgrounds, 25% for borders.
+
+### Named Rules
+**The One Accent Rule.** Protocol Teal is the only affirmative action color. There is no secondary accent, no purple, no orange. If it's not Protocol Teal, it's either neutral, success, or error. This prevents the "Christmas tree" effect common in status-heavy UIs.
+
+**The Ghost Status Rule.** Status colors (green, red) never appear as solid fills except on the Confirm button in the engagement-loss modal. They always render at reduced opacity on backgrounds (12%) and borders (25%). This keeps the workspace calm even when many posts show status simultaneously.
+
+## Typography
+
+**Body Font:** System font stack (-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif). No webfont dependency beyond the Adobe Typekit link for the original Verifier branding (retained for now).
+
+**Character:** Clean, neutral, highly readable. The system font stack ensures native rendering and zero load time. There is no display font — headings use the same family at larger sizes and heavier weights.
+
+### Hierarchy
+- **Heading 1** (700, 2em): Page titles on Home and Altifier pages. Appears once per page.
+- **Heading 2** (700, 1.5em, 1em margin-top): Section headers within cards. Separates the scan controls from the results.
+- **Heading 3** (700, 1.2em, 0.8em margin-top): Sub-section headers. Post lists, summary blocks.
+- **Body** (400, 18px, 1.6 line-height): All body copy, post text, labels, and form content. Never smaller than 18px.
+- **Label** (400, 0.9em, Cool Gray): Meta information, timestamps, counts, "Reply" badges. Smaller and lighter but still above 15px at default zoom.
+
+### Named Rules
+**The 18px Floor Rule.** No text smaller than 18px for body copy. Meta and label text may go to 0.9em (~16px) but never smaller. This is a WCAG AA baseline decision, not a stylistic one.
+
+## Layout
+
+Single-column, centered, max-width 700-800px. The layout is a stack of cards separated by 16px gaps, within a 20px-padded container. There is no sidebar, no multi-column grid, no fixed header. The navbar sits at the top, the footer at the bottom, and a scrollable card stack fills the space between.
+
+The spatial model uses a 4px base rhythm: 8px (image card label), 12px (form gaps, post meta), 16px (card margins), 20px (body padding), 24px (card internal padding).
+
+At 480px and below, form rows and list items stack vertically instead of horizontally.
+
+**Container width:** 700px for the Altifier scanner, 800px for the general main container.
+
+## Elevation & Depth
+
+This system is deliberately flat. There are no box-shadows on any surface. Depth is conveyed through tonal layering alone: Deep Navy (page) → Dark Blue-Gray (cards) → Darker Blue-Gray (navbar/footer). Each step is a slight lightness increase against the dark background.
+
+The modal overlay is a flat 70% black mask. The modal itself is a Dark Blue-Gray card — same background as a card, but positioned on top of the overlay. There is no shadow, no blur, no elevation effect.
+
+**The Flat-By-Default Rule.** Surfaces are flat at rest. No shadows. No gradients. No glassmorphism. The only visual animation in the system is the `flash-red-warning` keyframe on destructive button hover — and even that is a color pulse, not a depth change.
+
+## Shapes
+
+Consistent rounded corners with two steps: 6px for small interactive elements (buttons, inputs, status boxes, image thumbnails) and 8px for large containers (cards, sections, modals). There are no sharp corners and no pill shapes. Borders are always 1px solid using Medium Blue-Gray except on status indicators and focus states.
+
+Images inside cards are bounded by the card's 8px radius. Post images in the review view render at full width with `object-fit: contain` against a near-black background.
+
+## Components
+
+### Buttons
+- **Shape:** 6px border-radius. 48px minimum height for touch targets.
+- **Primary:** Protocol Teal background, Near-White text, 10px 24px padding. Hover deepens to Protocol Teal Deep.
+- **Danger/Confirm:** Transparent background with Alert Crimson border and text on hover. The confirmation button in the engagement-loss modal uses solid Alert Crimson fill — the only solid red button in the system.
+- **Disabled:** 60% opacity, cursor not-allowed. No color change.
+
+### Cards
+- **Shape:** 8px border-radius, Dark Blue-Gray background, 1px Medium Blue-Gray border.
+- **Padding:** 24px internal (sections), 16px (post cards).
+- **Stacking:** 16px gap between cards. Cards never nest more than one level deep.
+
+### Inputs & Textareas
+- **Style:** Dark Blue-Gray background, 1px Medium Blue-Gray border, 6px radius, 48px minimum height (inputs) or 60px (textareas). Near-White text.
+- **Focus:** Border shifts to Protocol Teal. No glow, no shadow.
+- **Textarea:** Resizable vertically. 1000 character max with right-aligned character counter in Cool Gray.
+
+### Status Boxes
+- **Shape:** 6px border-radius, 14px padding.
+- **Success:** 12% Accessible Green background, solid Accessible Green text, 25% border.
+- **Error:** 12% Alert Crimson background, solid Alert Crimson text, 25% border.
+
+### Navigation
+- **Navbar:** Darker Blue-Gray background, 1px Medium Blue-Gray bottom border, 12px 24px padding. Logo in Protocol Teal, 1.4em, 700 weight. Login/logout button to the right.
+- **Footer:** Same background and border (top), centered Cool Gray text at 0.9em.
+
+### Modal
+- **Shape:** 8px border-radius, Dark Blue-Gray background, 24px padding, max-width 440px.
+- **Overlay:** Fixed position, inset 0, 70% black background.
+- **Close behavior:** Clicking overlay dismisses. The modal itself stops event propagation.
+
+### Image Cards (Review Mode)
+- **Shape:** 2px border (Medium Blue-Gray default, Alert Crimson when missing alt text, Accessible Green when has alt text), 6px radius.
+- **Image:** Full width, max-height 300px, `object-fit: contain` on near-black background.
+- **Alt text textarea:** Darker Blue-Gray background, 1px Medium Blue-Gray border, Protocol Teal on focus.
+- **Approve button:** Solid Accessible Green fill, Near-Black text. Flex 1, 48px height.
+- **Skip button:** Transparent, Medium Blue-Gray border, Cool Gray text.
+
+## Do's and Don'ts
+
+### Do:
+- **Do** use Protocol Teal for exactly one primary action per visible surface. If two things look equally actionable, one of them is wrong.
+- **Do** use the 4px rhythm (8, 12, 16, 20, 24) for all spacing. No custom px values outside this scale.
+- **Do** keep status indicators at reduced opacity (12% bg, 25% border) except the modal confirm button.
+- **Do** maintain 48px touch targets on all interactive elements.
+- **Do** set body text at 18px. Never shrink below 0.9em for meta text.
+
+### Don't:
+- **Don't** add shadows, gradients, or glassmorphism effects. The system is deliberately flat.
+- **Don't** introduce a second accent color. Protocol Teal is the only affirmative action color.
+- **Don't** use box-shadows to create depth. Use tonal layering (Navy → Dark Blue-Gray → Darker Blue-Gray).
+- **Don't** use red as a solid fill anywhere except the engagement-loss confirmation button.
\ No newline at end of file
diff --git a/PRODUCT.md b/PRODUCT.md
new file mode 100644
index 0000000..c9aef77
--- /dev/null
+++ b/PRODUCT.md
@@ -0,0 +1,96 @@
+# Product
+
+
+
+## Platform
+
+web
+
+## Users
+
+Bluesky users who post images to the platform. They may have posted dozens or hundreds of images without alt text — either because they didn't know about it, the Bluesky composer didn't prompt them, or they post from third-party clients without alt text support. Their job is to make their existing image posts accessible to blind and visually impaired readers using screen readers, without manually reviewing every post they've ever made.
+
+## Product Purpose
+
+Altifier scans a user's Bluesky PDS for image posts and replies that lack alt text, generates Section 508-compliant descriptions via AI, lets the user review and edit inline, and recreates the posts with alt text embedded in the standard `app.bsky.embed.images` structure so it appears in the Bluesky app. Each post recreation deletes the original and creates a new one — losing likes, reposts, quotes, and replies — which the user must explicitly confirm.
+
+## Positioning
+
+Altifier is AT Protocol-native. It does not scrape the Bluesky firehose or rely on a third-party index. It authenticates via Bluesky OAuth, reads directly from the user's PDS, and writes back using standard `com.atproto.repo` CRUD operations. A generic alt-text tool could describe any image on the web; Altifier is the only tool that scans a Bluesky account's full post history, generates contextual descriptions, and applies them to the canonical post records so they render in the official Bluesky app.
+
+## Operating Context
+
+The user opens Altifier in a browser, authenticates with their Bluesky handle via OAuth, and clicks "Scan My Posts." The app enumerates all `app.bsky.feed.post` records (posts and replies), filters to those with `app.bsky.embed.images`, and identifies images where `embed.images[].alt` is missing or empty. Results are displayed as a reviewable list. The user can generate AI alt text per image, edit it inline, approve, and apply. Application triggers a confirmation modal warning of engagement loss, then deletes the original post and creates a new one.
+
+This is an on-demand, re-runnable, interactive tool. There is no scheduler, no background scanning, and no continuous monitoring.
+
+## Capabilities and Constraints
+
+**Confirmed:**
+- Bluesky OAuth login (AT Protocol OAuth, DPoP-bound tokens)
+- Enumerate all image posts and replies from the authenticated user's PDS
+- Display images via Bluesky CDN (`cdn.bsky.app`)
+- Generate alt text via AI vision model (stubbed, planned: Gemini 2.5 Flash free tier)
+- Review/edit/approve alt text per image
+- Section 508 guidelines linked in the UI
+- Recreate posts: delete original, create new with updated `embed.images[].alt`
+- Engagement-loss confirmation modal with explicit warning
+- Deploy as static SPA to wisp.place and Tangled
+- Filter: posts with missing alt text only (default) or all image posts
+
+**Undecided:**
+- AI service selection (stubbed; Gemini Flash or Ollama planned)
+- Whether to keep the legacy Verifier component code in the repo (currently unreferenced but present on disk)
+
+**Terminology:**
+- "Alt text" — alternative text descriptions for images, consumed by screen readers
+- "PDS" — Personal Data Server, the user's AT Protocol data host
+- "Post recreation" — delete-then-create pattern to add alt text to immutable records
+- "Section 508" — US federal accessibility standard for electronic content
+
+**Technical constraints:**
+- AT Protocol posts are content-addressed and immutable; `putRecord` is rejected by Bluesky app view
+- Static SPA — no backend, no database, no server-side rendering
+- Build and deploy from `/tmp` to avoid OneDrive Files On-Demand ETIMEDOUT issues
+- Single CSS file, no CSS-in-JS
+
+## Brand Commitments
+
+**Visual:**
+- Witchsky cyan palette (`CYAN_PALETTE` from ALF themes):
+ - Primary: teal-cyan `hsl(174, 83%, 38%)`
+ - Background: `#111822`
+ - Card BG: `#1C2B35`
+ - Navbar: `#15232C`
+ - Text: `#F9FAFB`
+ - Success: `#2CF28F`
+ - Error: `hsl(348, 92%, 64%)`
+- No glassmorphism, gradients, box-shadows, or decorative animations
+- Exception: `flash-red-warning` animation on destructive-action hover only
+- Minimal functional CSS
+
+**Accessibility:**
+- WCAG AA compliance
+- 48px minimum touch targets
+- 18px base font size
+
+**Content:**
+- No emojis in code, docs, comments, or generated text
+
+## Evidence on Hand
+
+- Running deployment at `https://altifier.psingletary.com` (wisp.place)
+- Source repo at `https://tangled.org/psingletary.com/altifier`
+- One planned feature (AI alt text generation) is stubbed; no real user data exists yet
+- No testimonials, case studies, or usage data — pre-launch
+
+## Product Principles
+
+1. **User owns the action.** No automatic posting, no silent background writes. Every post recreation requires explicit review and confirmation.
+2. **PDS-first.** Read from the user's own PDS, write to the user's own PDS. No intermediary databases, no third-party indexes.
+3. **Transparency about tradeoffs.** The engagement-loss warning is not buried in a tooltip. It's a modal the user must interact with.
+4. **Accessibility as product, not feature.** Alt text generation follows a government standard (Section 508). The link to the standard is visible in the UI.
+
+## Accessibility & Inclusion
+
+WCAG AA compliance required. Alt text generated by the tool must follow Section 508 guidelines. The UI itself must meet the same standard — 48px touch targets, sufficient color contrast, semantic HTML, no reliance on color alone for meaning.
\ No newline at end of file