initial commit

This commit is contained in:
Moon.eth
2026-07-29 09:16:38 +09:00
commit 586b599d4c
67 changed files with 9906 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
# The styling contract
These stylesheets are the public API of plspace's appearance. They are written
so that a user's own CSS can override any of it without fighting the cascade.
## Rules the app follows
1. **No CSS framework, no utility classes.** Every class names the *thing* it
styles, not how it looks. `.friend-card-photo`, not `.w-16.rounded`.
2. **No Svelte scoped styles.** Not one component has a `<style>` block, so
Svelte never appends a `.svelte-1a2b3c` hash to a class. What you see in the
DOM inspector is what you write in your selector, permanently.
3. **Single-class selectors.** Almost nothing here is more specific than one
class. No IDs, no `!important`, no long descendant chains. A plain
`.blog-entry { … }` in your CSS ties on specificity and wins on order.
4. **User CSS goes last.** The theme store re-appends `#user-stylesheet` to the
end of `<head>` whenever it changes, so an equal-specificity tie always
resolves in your favour. This is why you never need `!important`.
5. **Everything visual is a custom property.** No rule hard-codes a colour, font
or size. Retinting the entire app is a matter of overriding tokens.
## Layer 1: tokens
`tokens.css` declares every colour, font, border and metric on `:root`. This is
the intended entry point — it changes the whole app coherently, including parts
you haven't looked at.
```css
:root {
--ms-chrome-bg: #2d0b3a;
--ms-module-header-bg: #4a1a5c;
--ms-link: #ff77cc;
--ms-font-family: 'Comic Sans MS', Verdana, sans-serif;
}
```
The five presets in `src/lib/themes.ts` are written *entirely* at this layer —
read them as worked examples.
Token groups: typography (`--ms-font-*`), page (`--ms-page-*`, `--ms-canvas-*`),
links (`--ms-link*`), chrome (`--ms-chrome-*`, `--ms-nav-*`), modules
(`--ms-module-*`, `--ms-band-*`), tables (`--ms-table-*`), forms
(`--ms-input-*`, `--ms-button-*`), avatars (`--ms-avatar-*`), layout
(`--ms-page-width*`, `--ms-column-*`).
## Layer 2: classes
When tokens aren't enough, target classes directly.
| Area | Classes |
| --- | --- |
| Page skeleton | `.page`, `.page-title`, `.page-subtitle`, `.layout--split`, `.layout--dashboard`, `.layout--single`, `.layout-column--left`, `.layout-column--main`, `.layout-column--right` |
| Chrome | `.site-header`, `.site-logo`, `.site-nav`, `.site-nav-link`, `.site-footer` |
| Boxes | `.module`, `.module-header`, `.module-body`, `.module--band`, `.module--plain`, `.section-heading` |
| Profile | `.profile-page`, `.profile-photo`, `.profile-headline`, `.profile-vitals`, `.profile-mood`, `.profile-badge` |
| Tables | `.data-table`, `.data-table-label`, `.data-table-value`, `.interests-table`, `.details-table` |
| Friends | `.friend-grid`, `.friend-card`, `.friend-card-name`, `.friend-card-photo`, `.person-row` |
| Entries | `.blog-list`, `.blog-entry`, `.blog-entry-header`, `.blog-entry-body`, `.blog-entry-actions`, `.blog-action`, `.entry-teaser` |
| Comments | `.comment-list`, `.comment`, `.comment-author`, `.comment-body` |
| Media | `.attachment`, `.attachment-media`, `.attachment-caption`, `.preview-card`, `.poll` |
| Mail | `.mail-folders`, `.mail-table`, `.mail-row`, `.mail-summary` |
| Forms | `.button`, `.field`, `.field-label`, `.link-button`, `.tab-bar`, `.tab` |
## Layer 3: state attributes
Rather than inventing a modifier class per combination, elements carry data
attributes describing what they *are*. Style by state with attribute selectors:
```css
.blog-entry[data-mine='true'] { background: #fffbe6; }
.blog-entry[data-visibility='private'] { background: #fff0f0; }
.blog-entry[data-boosted='true'] { opacity: 0.85; }
.blog-entry[data-sensitive='true'] { border-left: 3px solid red; }
.comment[data-depth='2'] { font-size: 11px; }
.mail-row[data-kind='follow_request'] { background: #ffffcc; }
.attachment[data-type='video'] { border-color: purple; }
.blog-action[aria-pressed='true'] { font-weight: 700; }
.site-nav-link[aria-current='page'] { background: orange; }
```
Available attributes: `data-account` (on entries, cards, rows), `data-status-id`,
`data-visibility`, `data-boosted`, `data-reply`, `data-sensitive`, `data-mine`,
`data-compact`, `data-depth`, `data-kind`, `data-type`, `data-verified`,
`data-timeline`, `data-view`, `data-folder`, `data-row`, `data-badge`,
`data-private`, `data-session`.
## There is no dark mode
Deliberately. A dark theme is a set of token overrides and nothing more, so it
lives at layer 1 like any other theme — see the **Midnight** and **Terminal**
presets in `src/lib/themes.ts`. Shipping a hardcoded toggle would have meant one
dark theme nobody could edit, next to a styling system built for exactly this.
Two things a dark theme must remember:
- Set `color-scheme: dark` on `:root` so native form controls and scrollbars
follow. Viewer CSS is unscoped, so this works.
- Recolour `--ms-chrome-bg`. The logo sits on the chrome band and is knocked
out to white by `--ms-logo-filter`, so any dark band works as-is. If you theme
the band to a *light* colour, set `--ms-logo-filter: none` to get the mark's
own navy back.
## Publishing a layout on your profile
Put CSS in a profile field named `css` (or `style`, `layout`, `stylesheet`) on
your server. Anyone viewing your profile in plspace gets it. Because it's an
ordinary profile field it federates normally — other clients just show it as
text.
Published CSS is **scoped to `.profile-page`** and filtered before it is applied:
- `@import` is removed (it would pull in an unbounded external stylesheet).
- `url()` is allowed only for `https:` and `data:image/` (no tracking pixels).
- `position: fixed` becomes `static` (no viewport-covering overlays).
- Every selector is prefixed with `.profile-page`, including rules nested inside
`@media` / `@supports`. Writing `body` or `html` targets the profile page
itself, which is the useful interpretation.
- `@keyframes` blocks pass through untouched, so animations still work.
The net effect: you can do anything to your own page, and nothing to anyone
else's or to the surrounding app. Viewers can switch the whole feature off in
Settings.
## Adding styles to the app itself
Add the rule to the file that owns that area — `chrome.css`, `layout.css`,
`module.css`, `profile.css`, `blog.css`, `forms.css` — and register any new
colour or metric as a token in `tokens.css` first. Do not introduce a `<style>`
block in a component; it would create a hashed class the contract above promises
doesn't exist.