bd68a32708
Extract eleven topic areas into loadable skills so AGENTS.md can serve as a scannable overview instead of a specification dump. The file shrinks from 1480 to 358 lines (~76%) while keeping every concrete rule, critical code pattern, and pointer that an agent needs on first read. New Ditto-specific skills: - nostr-kinds: NIP-vs-custom-kind decision framework, kind ranges, tag design, content-vs-tags, NIP.md update rule, and Ditto's seven-location UI registration checklist for new kinds (NoteCard, PostDetailPage, extraKinds.ts, KIND_LABELS/KIND_ICONS in CommentContext, WELL_KNOWN_KIND_LABELS in ExternalContentHeader, EmbeddedNote/EmbeddedNaddr, ReplyComposeModal). - nostr-publishing: useNostrPublish, the read-modify-write pattern via fetchFreshEvent + prev for replaceable/addressable events, published_at contract, and d-tag collision prevention. - nostr-queries: the standard useNostr + useQuery pattern, combining kinds into one filter to avoid rate limits, and the NIP-52 validator walkthrough. - theming: @fontsource install flow, the Ditto runtime font-loader path (sanitizeUrl + sanitizeCssString), color scheme variables, useTheme toggle, and the isolate + negative-z-index gotcha. - ci-cd-publishing: Zapstore NIP-46 bunker auth (zsp + nip46-auth.mjs), nsite deploys (nsyte nbunksec + configured relays/servers), and Google Play AAB uploads via fastlane supply (service-account JSON base64 encoding and rotation). - capacitor-compat: WKWebView/WebView limitations, the downloadTextFile / openUrl helpers in src/lib/downloadFile.ts, platform detection, and the full plugin list. - git-workflow: pre-commit validation order and the Regression-of: trailer convention used by the release skill's changelog generator. Ported from mkstack, lightly adapted where needed: - nip19-routing: root-level /:nip19 routing and filter construction patterns (adapted to reference Ditto's existing NIP19Page). - nostr-relay-pools: nostr.relay() and nostr.group() for targeted queries. - nostr-encryption: NIP-44 / NIP-04 via the user's signer. - file-uploads: useUploadFile + Blossom + NIP-94 imeta tag construction. AGENTS.md itself now follows mkstack's density — concrete rules inline, one code example per section, pointer to the matching skill for details. The enumerations that previously bloated it (every shadcn primitive, every hook, every Capacitor plugin, the full NostrMetadata type dump, the NIP-19 prefix reference table, etc.) are either removed in favor of "ls the directory" or moved into their skill.
128 lines
6.0 KiB
Markdown
128 lines
6.0 KiB
Markdown
---
|
|
name: theming
|
|
description: Customize Ditto's visual design — install Google Fonts via @fontsource, change the color scheme, configure light/dark themes, and apply consistent component styling patterns with Tailwind and CSS variables.
|
|
---
|
|
|
|
# Theming, Fonts, and Color Schemes
|
|
|
|
Use this skill when the user wants to change fonts, colors, light/dark appearance, or general visual styling. Ditto ships with a light/dark theme system built on CSS custom properties and Tailwind v3, plus a `useTheme` hook for runtime switching.
|
|
|
|
## Adding Fonts
|
|
|
|
Any Google Font can be installed via the `@fontsource` / `@fontsource-variable` packages.
|
|
|
|
1. **Install the font package.** Prefer the variable version when available.
|
|
```bash
|
|
npm install @fontsource-variable/inter
|
|
```
|
|
Package naming:
|
|
- `@fontsource-variable/<font-name>` — variable fonts (preferred; one file, all weights)
|
|
- `@fontsource/<font-name>` — static fonts
|
|
|
|
2. **Import the font once** in `src/main.tsx`:
|
|
```ts
|
|
import '@fontsource-variable/inter';
|
|
```
|
|
|
|
3. **Register the family** in `tailwind.config.ts`:
|
|
```ts
|
|
export default {
|
|
theme: {
|
|
extend: {
|
|
fontFamily: {
|
|
sans: ['Inter Variable', 'Inter', 'system-ui', 'sans-serif'],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
```
|
|
|
|
### Suggested families by use case
|
|
|
|
- **Modern / Clean:** Inter Variable, Outfit Variable, Manrope
|
|
- **Professional / Corporate:** Roboto, Open Sans, Source Sans Pro
|
|
- **Creative / Artistic:** Poppins, Nunito, Comfortaa
|
|
- **Monospace / Code:** JetBrains Mono, Fira Code, Source Code Pro
|
|
|
|
For expressive hierarchies, pair a sans body font with a display/serif heading font (e.g. Inter + Playfair Display) and expose the second family as `fontFamily.serif` or `fontFamily.display` in Tailwind.
|
|
|
|
### Runtime font loading from Nostr events
|
|
|
|
Ditto also supports loading fonts referenced from Nostr events (theme events, letter stationery, etc.) through `src/lib/fontLoader.ts`. That path is separate from the build-time `@fontsource` approach — it constructs `@font-face` rules at runtime from sanitized URLs. Never feed event data through the `@fontsource` path; always go through `fontLoader` so the URL and family name are passed through `sanitizeUrl()` and `sanitizeCssString()` (see the `nostr-security` skill).
|
|
|
|
## Color Schemes
|
|
|
|
Colors are defined as CSS custom properties in `src/index.css` under two selectors:
|
|
|
|
- `:root` — light-mode values
|
|
- `.dark` — dark-mode overrides
|
|
|
|
When the user requests a new color scheme:
|
|
|
|
1. **Update both `:root` and `.dark`** in `src/index.css`. Each variable is an HSL triplet (no `hsl()` wrapper), e.g. `--primary: 222 47% 11%;`.
|
|
2. **Keep contrast ratios ≥ 4.5:1** for body text and interactive elements. Test both modes.
|
|
3. **Prefer extending Tailwind's palette** (`tailwind.config.ts`) over hard-coding hex values in components — this keeps the theme consistent and dark-mode-friendly.
|
|
4. **Apply colors through semantic tokens** (`bg-primary`, `text-muted-foreground`, `border-input`) rather than raw palette names when possible, so future theme changes propagate.
|
|
|
|
The shadcn/ui components consume these semantic tokens, so changing the variables automatically restyles the entire component library.
|
|
|
|
## Light/Dark Theme Switching
|
|
|
|
Ditto includes:
|
|
|
|
- **`useTheme` hook** (`src/hooks/useTheme.ts`) — read and set the current theme programmatically.
|
|
- **CSS custom properties** in `src/index.css` — one set in `:root`, dark overrides in `.dark`.
|
|
- **Automatic persistence** via the `AppContext` config (`config.theme`), saved to local storage.
|
|
|
|
To add a theme toggle:
|
|
|
|
```tsx
|
|
import { useTheme } from '@/hooks/useTheme';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Moon, Sun } from 'lucide-react';
|
|
|
|
export function ThemeToggle() {
|
|
const { theme, setTheme } = useTheme();
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
|
>
|
|
{theme === 'dark' ? <Sun className="size-4" /> : <Moon className="size-4" />}
|
|
</Button>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Component Styling Patterns
|
|
|
|
- **Class merging:** use the `cn()` utility (`@/lib/utils`) to combine conditional classes and override defaults without class-order bugs.
|
|
- **Variants:** follow shadcn/ui's `class-variance-authority` pattern for component variants (`variant`, `size`). Copy an existing `ui/` component as a template.
|
|
- **Responsive design:** lean on Tailwind breakpoints (`sm:`, `md:`, `lg:`) rather than JS media queries. Use `useIsMobile` only when layout must change based on JS-measured viewport.
|
|
- **Interactive states:** always define `hover:`, `focus-visible:`, and `disabled:` states for clickable elements. Focus rings should use `ring-ring` / `ring-offset-background` so they pick up theme colors.
|
|
- **Spacing:** an 8px grid (Tailwind's default 4-based scale) keeps visual rhythm consistent. Common paddings: `p-4`, `p-6`; gaps: `gap-2`, `gap-4`.
|
|
- **Depth:** soft shadows (`shadow-sm`, `shadow-md`), subtle gradients, and `rounded-lg` / `rounded-xl` corners match Ditto's aesthetic. Avoid heavy drop shadows.
|
|
|
|
### Negative z-index gotcha
|
|
|
|
When placing decorative elements behind content with `-z-10` (e.g. blurred background gradients), **add `isolate` to the parent container**. Without `isolate`, the negative z-index escapes the local stacking context and the element disappears behind the page's background color.
|
|
|
|
```tsx
|
|
<section className="relative isolate">
|
|
<div className="absolute inset-0 -z-10 bg-gradient-to-br from-primary/20 to-transparent" />
|
|
{/* content */}
|
|
</section>
|
|
```
|
|
|
|
## Design Quality Checklist
|
|
|
|
Before finishing a visual change, verify:
|
|
|
|
- [ ] Both light and dark modes look correct — no hard-coded colors, all text readable.
|
|
- [ ] Contrast ratios meet WCAG AA (≥ 4.5:1 for body, ≥ 3:1 for large text).
|
|
- [ ] Interactive elements have visible `hover`, `focus-visible`, and `disabled` states.
|
|
- [ ] Layout is responsive down to ~360px width without horizontal scroll.
|
|
- [ ] Animations respect `prefers-reduced-motion` (Tailwind: `motion-safe:` / `motion-reduce:`).
|
|
- [ ] Spacing is consistent — no one-off `p-[13px]` style values.
|