diff --git a/.gitignore b/.gitignore index f9318ae2..606ac357 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ node_modules/ tmp/ mise.local.toml **/skills/npm-* +src/public/uno.css +.playwright-mcp/ +/*.png coverage/** !coverage/.gitkeep diff --git a/AGENTS.md b/AGENTS.md index 4f7d28ae..f4225826 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -126,6 +126,7 @@ Key architectural components | *src/federation/index.ts* | Federation setup with inbox listeners | | *src/oauth/middleware.ts* | Authentication middleware | | *src/entities/status.ts* | Status entity serialization | +| *DESIGN.md* | Design system and front-end conventions | Technology stack @@ -187,6 +188,24 @@ export function MyComponent({ name }: { name: string }) { import React from 'react'; // Don't do this ~~~~ +### Design system and front-end conventions + +When working on any user-facing page (admin dashboard, profile, post, +auth, OAuth screens, etc.), read *DESIGN.md* first. It defines: + + - the visual design principles (simplicity, modernness, content first, + lightweight SSR, accessibility), + - the color system (achromatic neutrals plus per-account theme color + via CSS custom properties on ``), + - typography, spacing, iconography, and component recipes, + - the UnoCSS toolchain conventions (preset choices, prose application + areas, theme token injection, variant groups). + +Treat *DESIGN.md* as the single source of truth for front-end decisions +that aren't directly answered by the source code. Never introduce ad-hoc +CSS or inline styling that contradicts it; if the document is missing +guidance on a real case, update *DESIGN.md* in the same change. + ### Database guidelines - *Migrations*: Always generate migrations for schema changes diff --git a/CHANGES.md b/CHANGES.md index b84bbba2..06a246bb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -49,9 +49,53 @@ To be released. post. If the quoted post is unavailable, the fallback link remains visible so the quoted URL is not lost. + - Posts on the public profile and hashtag pages now render Open Graph link + previews—a thumbnail (when `og:image` is set), the host name, the page + title, and a short description—for each post that has a stored + `preview_card`. Posts with attached media or shown as quoted posts hide + the preview to avoid visual clutter. [[#458]] + + - Refreshed the entire server-rendered front-end. Hollo replaces Pico CSS + with a new design system documented in `DESIGN.md` and styled through + UnoCSS (Wind4, Icons, Typography, and Web Fonts presets). [[#458]] + + - The design language is achromatic by default; each account owner's + *theme color* tints the profile, post, hashtag, and owner-specific + dashboard pages through `--theme-50` through `--theme-950` CSS + variables injected on ``. + - Web Fonts are loaded from bunny.net: Inter for Latin, Noto Sans + KR/JP/SC for CJK, and JetBrains Mono for code. Lucide icons + replace ad-hoc iconography. + - Every public and dashboard page was rebuilt: login, setup, OTP, the + public home, account profiles, single post permalinks, the hashtag + stream, the account list and editor, custom emojis, federation, + thumbnail cleanup, the OAuth consent screen, and the dashboard + auth (2FA) panel. + - Posts render their bodies as `prose` markdown with brand-colored + links, attached media as a two-column grid, polls as brand-tinted + bars, quoted posts as an inset card, and link previews as a + media-object card. The single-post permalink page additionally + enlarges the focal post. + - Forms share a small set of primitives (`Field`, `TextField`, + `TextareaField`, `SelectField`, `CheckboxField`, `FieldSection`, + and `SubmitButton`) that the auth, account, emoji, federation, + thumbnail cleanup, and migrate forms all use. The theme color + picker on the account form is a 20-swatch grid; the username + field is bracketed by `@` and `@host` chips so the resulting + fediverse handle is obvious. + - Dark mode follows `prefers-color-scheme: dark` automatically, with + primary buttons stepping from `brand-600` to `brand-700` so they + don't dominate dark surfaces. Text selection adopts the active + brand color. + - Hollo's logos are self-hosted from `/public/` instead of being + fetched from jsDelivr. The 22 Pico-generated `.min.css` files + are removed; UnoCSS emits a single `src/public/uno.css` whose + URL is cache-busted by file mtime. + - Upgraded Fedify to 2.2.0. [#457]: https://github.com/fedify-dev/hollo/pull/457 +[#458]: https://github.com/fedify-dev/hollo/pull/458 Version 0.8.1 @@ -223,6 +267,7 @@ Released on April 27, 2026. - Upgraded Fedify to 2.1.10. +[Fedify debugger]: https://fedify.dev/manual/debug [#173]: https://github.com/fedify-dev/hollo/issues/173 [#348]: https://github.com/fedify-dev/hollo/issues/348 [#350]: https://github.com/fedify-dev/hollo/issues/350 @@ -238,7 +283,6 @@ Released on April 27, 2026. [#445]: https://github.com/fedify-dev/hollo/issues/445 [#447]: https://github.com/fedify-dev/hollo/pull/447 [#448]: https://github.com/fedify-dev/hollo/pull/448 -[Fedify debugger]: https://fedify.dev/manual/debug Version 0.7.13 @@ -331,8 +375,8 @@ Version 0.7.7 Released on March 13, 2026. - Fixed video thumbnail generation failing for some MP4/MOV files by writing - the video data to a temporary file instead of piping it via stdin (`pipe:0`), - which does not support seeking. [[#397], [#398] by NTSK] + the video data to a temporary file instead of piping it via stdin + (`pipe:0`), which does not support seeking. [[#397], [#398] by NTSK] [#397]: https://github.com/fedify-dev/hollo/issues/397 [#398]: https://github.com/fedify-dev/hollo/pull/398 @@ -418,6 +462,8 @@ Released on February 10, 2026. request to the outbox could previously retrieve all posts regardless of their visibility setting. [[CVE-2026-25808]] +[CVE-2026-25808]: https://github.com/fedify-dev/hollo/security/advisories/GHSA-6r2w-3pcj-v4v5 + Version 0.7.1 ------------- @@ -483,7 +529,8 @@ Released on January 24, 2026. - `GET /api/v2/notifications`: Get paginated grouped notifications with deduplicated accounts and statuses - - `GET /api/v2/notifications/:group_key`: Get a specific notification group + - `GET /api/v2/notifications/:group_key`: Get a specific notification + group - `GET /api/v2/notifications/:group_key/accounts`: Get all accounts in a notification group - `POST /api/v2/notifications/:group_key/dismiss`: Dismiss a notification @@ -544,24 +591,24 @@ Released on January 24, 2026. `text/plain`. - Implemented Mastodon 4.5.0 quote notification types (`quote` and - `quoted_update`) for improved quote post interaction tracking. - Users now receive notifications when their posts are quoted by others - and when posts they've quoted are edited by the original authors. - Key features include: - - - Added `quote` notification type that triggers when someone quotes - your post, with the notification showing the quote post itself. - - Added `quoted_update` notification type that triggers when a post - you quoted is edited, with the notification showing your quote post - to provide context. - - Both notification types are non-groupable, meaning each quote or edit - generates an individual notification for better visibility. - - Self-quotes (quoting your own posts) do not generate notifications - to avoid unnecessary noise. - - Existing quote posts are automatically backfilled with notifications - during migration to ensure consistent notification history. - - Added database index on `posts.quote_target_id` for improved query - performance when looking up quote relationships. + `quoted_update`) for improved quote post interaction tracking. + Users now receive notifications when their posts are quoted by others + and when posts they've quoted are edited by the original authors. + Key features include: + + - Added `quote` notification type that triggers when someone quotes + your post, with the notification showing the quote post itself. + - Added `quoted_update` notification type that triggers when a post + you quoted is edited, with the notification showing your quote post + to provide context. + - Both notification types are non-groupable, meaning each quote or edit + generates an individual notification for better visibility. + - Self-quotes (quoting your own posts) do not generate notifications + to avoid unnecessary noise. + - Existing quote posts are automatically backfilled with notifications + during migration to ensure consistent notification history. + - Added database index on `posts.quote_target_id` for improved query + performance when looking up quote relationships. - Removed dependency on deprecated *fluent-ffmpeg* package and now invoke ffmpeg binary directly for video screenshot generation. This change @@ -599,15 +646,15 @@ Released on January 24, 2026. consistent WebFinger handle validation across v1 and v2 APIs. [#94]: https://github.com/fedify-dev/hollo/issues/94 -[#210]: https://github.com/fedify-dev/hollo/issues/210 -[#312]: https://github.com/fedify-dev/hollo/issues/312 [#170]: https://github.com/fedify-dev/hollo/issues/170 [#171]: https://github.com/fedify-dev/hollo/pull/171 [#174]: https://github.com/fedify-dev/hollo/pull/174 [#177]: https://github.com/fedify-dev/hollo/issues/177 [#179]: https://github.com/fedify-dev/hollo/pull/179 +[#210]: https://github.com/fedify-dev/hollo/issues/210 [#295]: https://github.com/fedify-dev/hollo/pull/295 [#296]: https://github.com/fedify-dev/hollo/pull/296 +[#312]: https://github.com/fedify-dev/hollo/issues/312 [#333]: https://github.com/fedify-dev/hollo/pull/333 [#334]: https://github.com/fedify-dev/hollo/pull/334 @@ -623,8 +670,6 @@ Released on February 10, 2026. request to the outbox could previously retrieve all posts regardless of their visibility setting. [[CVE-2026-25808]] -[CVE-2026-25808]: https://github.com/fedify-dev/hollo/security/advisories/GHSA-6r2w-3pcj-v4v5 - Version 0.6.19 -------------- @@ -781,8 +826,8 @@ Version 0.6.8 Released on August 21, 2025. - Fixed a critical bug introduced in 0.6.7 where the search query would return - too many results, causing out-of-memory errors and query timeouts. The issue - was caused by incorrect logical operator precedence when filtering + too many results, causing out-of-memory errors and query timeouts. The + issue was caused by incorrect logical operator precedence when filtering future-dated posts. [[#207], [#208] by aliceif] [#207]: https://github.com/fedify-dev/hollo/issues/207 @@ -812,6 +857,8 @@ Released on August 8, 2025. fix [CVE-2025-54888] that addresses an authentication bypass vulnerability allowing actor impersonation. [[CVE-2025-54888]] +[CVE-2025-54888]: https://github.com/fedify-dev/fedify/security/advisories/GHSA-6jcc-xgcr-q3h4 + Version 0.6.5 ------------- @@ -834,10 +881,10 @@ Version 0.6.4 Released on July 7, 2025. - - Fixed a regression bug where follower-only posts were returning `404 Not - Found` errors when accessed through conversation threads. This was caused - by improper OAuth scope checking that only accepted `read:statuses` scope - but tokens contain `read` scope: [[#169], [#172]] + - Fixed a regression bug where follower-only posts were returning + `404 Not Found` errors when accessed through conversation threads. This was + caused by improper OAuth scope checking that only accepted `read:statuses` + scope but tokens contain `read` scope: [[#169], [#172]] - `GET /api/v1/statuses/:id` - `GET /api/v1/statuses/:id/context` @@ -900,9 +947,9 @@ Released on June 5, 2025. - Deprecated `FS_ASSET_PATH` in favor of `FS_STORAGE_PATH`. - Deprecated `ASSET_URL_BASE` in favor of `STORAGE_URL_BASE`. - - Implemented OAuth 2.0 Authorization Code flow with support for access grants. - This improves the security of the OAuth authorization process by separating - the authorization code from the access token issuance. + - Implemented OAuth 2.0 Authorization Code flow with support for access + grants. This improves the security of the OAuth authorization process by + separating the authorization code from the access token issuance. [[#130] by Emelia Smith] - Hollo now requires the `SECRET_KEY` environment variable to be at least 44 @@ -948,10 +995,11 @@ Released on June 5, 2025. authorization code interception attacks in the OAuth authorization flow. [[#155] by Emelia Smith] - - Added support for the `profile` OAuth scope for enhanced user authentication. - This allows applications to request limited profile information using the - new `/oauth/userinfo` endpoint and enables the `profile` scope to be used - with the `GET /api/v1/accounts/verify_credentials` endpoint. + - Added support for the `profile` OAuth scope for enhanced user + authentication. This allows applications to request limited profile + information using the new `/oauth/userinfo` endpoint and enables the + `profile` scope to be used with the + `GET /api/v1/accounts/verify_credentials` endpoint. [[#45], [#156] by Emelia Smith] - Made few Mastodon API endpoints publicly accessible without @@ -969,6 +1017,7 @@ Released on June 5, 2025. [complete list of supported languages]: https://shiki.style/languages [#45]: https://github.com/fedify-dev/hollo/issues/45 [#50]: https://github.com/fedify-dev/hollo/issues/50 +[#99]: https://github.com/fedify-dev/hollo/issues/99 [#110]: https://github.com/fedify-dev/hollo/pull/110 [#111]: https://github.com/fedify-dev/hollo/issues/111 [#114]: https://github.com/fedify-dev/hollo/pull/114 @@ -1049,7 +1098,7 @@ Version 0.5.2 Released on February 20, 2025. -- Fixed a bug where the `follows.follower_id` column had not referenced the + - Fixed a bug where the `follows.follower_id` column had not referenced the `accounts.id` column. [[#112]] - Fixed a bug where `GET /api/v1/notifications` had returned server errors @@ -1060,6 +1109,9 @@ Released on February 20, 2025. - Upgrade Fedify to 1.4.2. +[#112]: https://github.com/fedify-dev/hollo/issues/112 +[#113]: https://github.com/fedify-dev/hollo/issues/113 + Version 0.5.1 ------------- @@ -1069,6 +1121,8 @@ Released on February 14, 2025. - Fixed a bug where `GET /api/v1/accounts/:id/statuses` had tried to fetch remote posts for local accounts. [[#107]] +[#107]: https://github.com/fedify-dev/hollo/issues/107 + Version 0.5.0 ------------- @@ -1113,16 +1167,15 @@ Released on February 12, 2025. - The `S3_REGION` environment variable became required if `DRIVE_DISK` is set to `s3`. [[#95]] +[`GET /api/v1/mutes`]: https://docs.joinmastodon.org/methods/mutes/#get +[`GET /api/v1/blocks`]: https://docs.joinmastodon.org/methods/blocks/#get [#95]: https://github.com/fedify-dev/hollo/issues/95 -[#99]: https://github.com/fedify-dev/hollo/issues/99 [#100]: https://github.com/fedify-dev/hollo/pull/100 [#101]: https://github.com/fedify-dev/hollo/issues/101 [#103]: https://github.com/fedify-dev/hollo/issues/103 [#104]: https://github.com/fedify-dev/hollo/issues/104 [#105]: https://github.com/fedify-dev/hollo/pull/105 [#106]: https://github.com/fedify-dev/hollo/pull/106 -[`GET /api/v1/mutes`]: https://docs.joinmastodon.org/methods/mutes/#get -[`GET /api/v1/blocks`]: https://docs.joinmastodon.org/methods/blocks/#get Version 0.4.12 @@ -1186,9 +1239,6 @@ Released on February 20, 2025. - Upgrade Fedify to 1.3.9. -[#112]: https://github.com/fedify-dev/hollo/issues/112 -[#113]: https://github.com/fedify-dev/hollo/issues/113 - Version 0.4.7 ------------- @@ -1240,8 +1290,11 @@ Version 0.4.4 Released on January 21, 2025. - - Upgrade Fedify to 1.3.4, which includes [security - fixes][@fedify-dev/fedify#200]. [[CVE-2025-23221]] + - Upgrade Fedify to 1.3.4, which includes + [security fixes][@fedify-dev/fedify#200]. [[CVE-2025-23221]] + +[@fedify-dev/fedify#200]: https://github.com/fedify-dev/fedify/discussions/200 +[CVE-2025-23221]: https://github.com/fedify-dev/fedify/security/advisories/GHSA-c59p-wq67-24wx Version 0.4.3 @@ -1254,8 +1307,8 @@ Released on January 11, 2025. - Fixed a bug where importing follows from CSV generated by Iceshrimp had failed. [[#85]] -[#92]: https://github.com/fedify-dev/hollo/issues/92 [#85]: https://github.com/fedify-dev/hollo/issues/85 +[#92]: https://github.com/fedify-dev/hollo/issues/92 Version 0.4.2 @@ -1328,8 +1381,6 @@ Released on August 8, 2025. fix [CVE-2025-54888] that addresses an authentication bypass vulnerability allowing actor impersonation. [[CVE-2025-54888]] -[CVE-2025-54888]: https://github.com/fedify-dev/fedify/security/advisories/GHSA-6jcc-xgcr-q3h4 - Version 0.3.10 -------------- @@ -1375,19 +1426,14 @@ Released on February 14, 2025. remote posts for local accounts. [[#107]] - Upgrade Fedify to 1.3.8. -[#107]: https://github.com/fedify-dev/hollo/issues/107 - Version 0.3.6 ------------- Released on January 21, 2025. - - Upgrade Fedify to 1.3.4, which includes [security - fixes][@fedify-dev/fedify#200]. [[CVE-2025-23221]] - -[@fedify-dev/fedify#200]: https://github.com/fedify-dev/fedify/discussions/200 -[CVE-2025-23221]: https://github.com/fedify-dev/fedify/security/advisories/GHSA-c59p-wq67-24wx + - Upgrade Fedify to 1.3.4, which includes + [security fixes][@fedify-dev/fedify#200]. [[CVE-2025-23221]] Version 0.3.5 @@ -1421,6 +1467,8 @@ Released on December 19, 2024. - Fixed a bug where generated thumbnails had been cropped incorrectly if the original image had not the EXIF orientation metadata. [[#76]] +[#76]: https://github.com/fedify-dev/hollo/issues/76 + Version 0.3.2 ------------- @@ -1435,7 +1483,6 @@ Released on December 18, 2024. - Upgrade Fedify to 1.3.2. -[#76]: https://github.com/fedify-dev/hollo/issues/76 [#78]: https://github.com/fedify-dev/hollo/issues/78 @@ -1532,6 +1579,8 @@ Released on November 4, 2024. Sharkey, Akkoma) had empty `url` fields, causing them to be displayed incorrectly in client apps. [[#58]] +[#58]: https://github.com/fedify-dev/hollo/issues/58 + Version 0.2.0 ------------- @@ -1590,8 +1639,6 @@ Released on November 4, 2024. Sharkey, Akkoma) had empty `url` fields, causing them to be displayed incorrectly in client apps. [[#58]] -[#58]: https://github.com/fedify-dev/hollo/issues/58 - Version 0.1.6 ------------- @@ -1600,8 +1647,8 @@ Released on October 30, 2024. - Fixed a bug where followers-only posts from accounts that had had set their follower lists to private had been recognized as direct messages. - Even after upgrading to this version, such accounts need to be force-refreshed - from the administration dashboard to fix the issue. + Even after upgrading to this version, such accounts need to be + force-refreshed from the administration dashboard to fix the issue. - Fixed the federated (public) timeline showing the shared posts from the blocked or muted accounts. diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 00000000..6be50152 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,354 @@ +Hollo design system +=================== + +This document defines the visual design language and front-end conventions +of Hollo's web pages. Hollo is primarily a headless ActivityPub server, but +it ships a small surface of server-rendered HTML pages — the admin +dashboard, account profiles, individual posts, the OAuth consent screen, +and a handful of utility screens. This document specifies how those pages +should look, feel, and be implemented. + +The design language values *simplicity* and *modernness* over decoration. +Surfaces are achromatic by default; color only enters the page through +the account owner's chosen *theme color*, which tints the profile and +post pages of that account. The visual center of every page is the +content the user came for, not the chrome around it. + + +Brand identity +-------------- + +The name *Hollo* is always written with a capital “H” and lowercase +“ollo”. The mark is a circular badge with the wordmark “Hollo” inside, +distributed as two SVGs at the project root: + + - *logo-black.svg* — for use on light backgrounds. + - *logo-white.svg* — for use on dark backgrounds. + +Both files are served from */public/* and embedded inline only when an +icon-sized rendering is needed. Don't recolor the mark; switch between +black and white based on the surrounding surface. + +The voice of the UI is short and matter-of-fact. Prefer plain English +sentences over imperative shouts; trust the reader. + + +Design principles +----------------- + + - *Simplicity*: prefer fewer controls, fewer borders, fewer surfaces. + Visual hierarchy comes from typography and spacing, not from boxes. + - *Modernness*: use modern CSS features (logical properties, OKLCH + color, container queries when needed) but never at the cost of + progressive degradation. + - *Content first*: text columns stay at a comfortable measure. Media + expands only when it earns the room. + - *Lightweight SSR*: pages are server-rendered with Hono JSX and ship + zero client-side JavaScript by default. Never reach for a runtime + framework to solve a styling problem that CSS can answer. + - *Accessibility*: every interactive element is keyboard-reachable, has + a visible focus state, and meets WCAG AA contrast in both light and + dark color schemes. + + +Color system +------------ + +### Neutral palette + +The default surface is achromatic. Hollo uses UnoCSS's *Wind4* neutral +scale (`neutral-50` through `neutral-950`) for backgrounds, borders, +surfaces, and text. No saturation enters a page until a theme color is +applied. + +| Role | Light scheme | Dark scheme | +| --------------- | ------------- | ------------- | +| Page background | `neutral-50` | `neutral-950` | +| Surface | `white` | `neutral-900` | +| Subtle border | `neutral-200` | `neutral-800` | +| Body text | `neutral-900` | `neutral-100` | +| Muted text | `neutral-500` | `neutral-400` | + +### Account theme colors + +Each account owner picks a theme color from a fixed set of twenty named +hues, defined as the `theme_color` PostgreSQL enum in *src/schema.ts*: + +~~~~ +amber azure blue cyan fuchsia green grey indigo +jade lime orange pink pumpkin purple red sand +slate violet yellow zinc +~~~~ + +The palette comes from Pico CSS's named color palette. Each hue is +expressed at nine tonal stops (`50`, `100`, `200`, `300`, `400`, `500`, +`600`, `700`, `800`, `900`), stored as RGB triples in +*src/theme/colors.ts*. + +### CSS variable injection + +The theme color is applied through CSS custom properties on the +`` element. *Layout.tsx* reads the account's `themeColor` and +emits inline declarations: + +~~~~ html + +~~~~ + +The UnoCSS configuration exposes these variables as a generic `brand` +color token: + +~~~~ ts +theme: { + colors: { + brand: { + 50: "rgb(var(--theme-50))", + // ... 100 through 900 + DEFAULT: "rgb(var(--theme-500))", + }, + }, +} +~~~~ + +This means components write `bg-brand`, `text-brand-700`, +`border-brand-200`, and so on, without ever knowing which of the twenty +hues is currently active. No safelist is needed because no class name +varies with the theme color. + +### Alpha modifiers + +Wind4 wraps every brand-colored utility in +`color-mix(in srgb, ... var(--un-bg-opacity), transparent)`, so a +`--un-bg-opacity` (and the matching `--un-text-opacity`, +`--un-border-opacity`, `--un-ring-opacity`, `--un-divide-opacity`, +`--un-placeholder-opacity`) custom property must be defined before any +brand utility resolves. *uno.config.ts* sets all of these to `100%` on +`:root` via a preflight, which makes plain `bg-brand-500` behave like +fully opaque rgb. + +Slash modifiers work as expected on top of this default: +`bg-brand-500/50`, `text-brand-700/80`, `ring-brand-200/40`, and so on +resolve to a 50%/80%/40% mix against transparent. + +### Dark mode + +Dark mode follows the operating system via `prefers-color-scheme: dark`. +There is no manual toggle in the first pass; that may be added later +without changing the underlying tokens. All component recipes specify +both light and dark variants up front. + + +Typography +---------- + +### Type families + +| Role | Family | Source | +| -------- | ---------------------------------------------- | ------------------------- | +| Sans | *Inter* | bunny.net (Google mirror) | +| Sans CJK | *Noto Sans KR*, *Noto Sans JP*, *Noto Sans SC* | bunny.net | +| Mono | *JetBrains Mono* | bunny.net | + +Fonts are loaded through UnoCSS's `presetWebFonts` with the `bunny` +provider, which is a privacy-respecting mirror of Google Fonts. The CSS +font stack lists Inter first, then the three Noto Sans CJK families, and +falls back to the system stack so initial paint never blocks on a +network request. + +### Type scale + +Use the Wind4 default scale unchanged (`text-xs` through `text-5xl`). +Body copy is `text-base` with `leading-relaxed`. Headings step down by +one level per nesting depth. + +### Long-form content + +Rendered Markdown — post bodies, account bio fields, reply chains — is +wrapped in the `prose` class from `presetTypography`, with +`prose-neutral` and `dark:prose-invert` variants. Inline code uses the +mono family; block code is rendered through Shiki and keeps its own +colors. + + +Spacing and layout +------------------ + +The spacing scale is Wind4's default 4 px grid. Use multiples of `2` +(`0.5rem`), `3`, `4`, `6`, `8`, `12`, and `16` for almost all gaps. + +Page widths: + + - Reading column (post body, profile bio, settings forms): + `max-w-2xl` (~42 rem). + - Dashboard column (timelines, account list): `max-w-3xl` (~48 rem). + - Wide chrome (top nav, footer): full width with internal `max-w-5xl`. + +Breakpoints follow the Wind4 defaults (`sm` 640 px, `md` 768 px, `lg` +1024 px, `xl` 1280 px). Mobile is the design start point; widen by +adding `md:` and `lg:` variants. + + +Iconography +----------- + +Hollo uses a single icon collection: *Lucide*, surfaced through +UnoCSS's `presetIcons` with the *@iconify-json/lucide* package. Icons +are written as CSS classes: + +~~~~ tsx +