Preview cards in sections (Community, the file's own @section, Seasonal, Haven). Members see built-ins and published themes; admins also get an Available to publish section with a publish switch on every theme file's card.
* Themes: Glassy Neon and seasonal themes (Christmas, Halloween, Thanksgiving)
- Add themes/glassy-neon.theme.css: Tron-inspired cyberpunk glassmorphism with perspective neon floor grid, laser glow borders, and rotating identity disc.
- Add themes/christmas.theme.css: Festive evergreen pine with twinkling string lights, falling snow, candy-cane borders, and glowing Christmas tree (@section Seasonal).
- Add themes/halloween.theme.css: Eerie gothic midnight with drifting ghostly mist, flying witch moon, carved jack-o'-lantern, blinking black cat, and dripping toxic slime borders (@section Seasonal).
- Add themes/thanksgiving.theme.css: Warm autumn harvest with tumbling fall leaves, waddling turkey watermark, harvest garland, and striped borders (@section Seasonal).
- Parse optional @section and @category metadata tags in src/themeMetadata.js.
- Organize theme pickers and admin settings so seasonal themes appear in a dedicated 'Seasonal' group.
- Support prefers-reduced-motion in all four new themes.
- Document bundled additions in GUIDE.md.
* Themes: rebuild Glassy Neon and the seasonal themes as full scenes
Restyle every surface of the four themes (server rail, sidebars, header,
topic bar, message list, composer, buttons, inputs, scrollbars, modals,
theme popup and the login page) and give each a layered, animated scene
drawn with self-contained SVG data URIs.
- Glassy Neon: Haven on the Grid. Opaque holo-glass panels with lit cyan
and orange edge rails, circuit traces, data streams running down the
rails, light-cycle trails along the header and composer, a perspective
light floor that races toward the viewer, horizon ridges, a scan sweep,
counter-rotating identity disc rings and glitch hover accents.
- Christmas: a cozy cabin looking out on a moonlit pine forest. Plaid
walls, a fireplace with stockings and flickering flames, a decorated tree
with twinkling lights and presents, garland and string lights on the
header, snow caps, frosted window corners and three parallax snow layers.
- Halloween: a cartoony haunted night. Bats crossing a big moon, graveyard
fog, a floating ghost, flickering jack-o'-lanterns, a black cat with
glowing eyes and a swishing tail, a bubbling cauldron, cobwebs, a
dangling spider and green goo that drips from the header and composer.
- Thanksgiving: a harvest evening. Two parallax layers of falling leaves,
swaying maple trees, a farmstead sunset, a leaf garland, flannel and
barn-wood panels, hay bales, corn shocks, pumpkins, and two turkeys that
wobble, gobble and strut.
Decorations use pointer-events: none and sit behind content in negative
z-index layers, the message column keeps a dark veil so message text stays
above WCAG AA, animations only change transform and opacity, and
prefers-reduced-motion stops every animation. Pseudo-element rules raise
their specificity so the effects overlay system does not strip them.
Move the seasonal-theme check into HavenThemeCompat.isSeasonalTheme so the
theme bar, plugin loader and admin list share one implementation, and test
it.
The Braid and Compact switches become a single picker: Original (Haven's
own layout) and every <Name>Layout.plugin.js on the server, one at a time.
A new layout plugin shows up without any change to Haven, so alternative
sidebars can ship as layouts while Original stays the default. The picker
shows the chosen layout even while it waits (Compact needs a wide window,
Braid waits for Mod Mode), and follows Braid's own shortcut and menu.
theme-authoring.md says what a plugin needs to appear there.
All 437 empty catch blocks in public/js were reviewed. Failures worth knowing
about (desktop bridge calls, native screen share cleanup, saved output device
routing, DM and thread render steps, resync steps, server list sync, settings
and library loads, offer rollbacks) now print a tagged console warning.
Expected failures (blocked storage in private mode, autoplay and fullscreen
refusals, connections that are already closed, malformed saved values) carry a
one-line reason. A few try blocks around code that cannot throw were removed.
Behaviour is otherwise unchanged.
File themes that set the page background (Braid, Braid Light, Compact) are treated as full palettes: turning one on in Settings selects it in the picker instead of layering its colours on top of Matrix or another built-in theme. The theme popup gains a Layout section with Braid and Compact switches so the chrome can be changed without touching the picker colours. Picking a built-in theme now saves first and then reapplies only the real CSS tweaks, so the Settings toggles and the picker agree.
Two separate faults, both because the login page does not load plugin-loader.js
and that is where everything about published themes lived.
The picker on the login page listed only the built-in themes, so a theme an
admin had published was nowhere to be found until after signing in.
A published theme chosen as the server default was worse. It is stored as
"file:something.theme.css", and the login page wrote that straight into the
data-theme attribute, which matches no stylesheet at all. First-time visitors
got an unstyled page. It appeared to fix itself on a later visit only because
signing in once left the theme in local storage, where the early theme script
picks it up.
Following birdcrazy's second suggestion on the issue: the two pieces both pages
need now live in theme.js, which every page loads. plugin-loader.js calls the
shared helper for the injection rather than keeping its own copy, so the two
cannot drift apart, and keeps its own plugin bookkeeping on top. The server
default is applied without being saved, since it is a suggestion for someone
who has not chosen rather than a choice they made.
theme.js and plugin-loader.js now depend on each other, so their cache-busting
version strings in app.html are bumped together.
Two problems showed up switching between the built-in themes and the file
themes in themes/*.theme.css:
Effects survived the switch. Every built-in theme button re-runs
applyEffects() after it switches, so picking a new theme tears down the old
one's overlays. The theme buttons injected for published file themes go
through applyFileTheme(), which never touched the effect layer — so the FFX
water and wave, the Matrix rain, the Nord snowfall and the rest kept running
on top of the file theme. Picking a light file theme while FFX was active
left a blue wash down the sidebar and a wave band across the bottom.
applyFileTheme() now re-applies the stored effect mode the same way, guarded
so it is a no-op where theme.js is not loaded.
An enabled published theme overrode the selected one. Settings -> Themes
lists every .theme.css with an on/off toggle, published ones included, and
applyFileTheme() ended with reapplyEnabledThemes(), which re-appended every
enabled file *after* the theme just selected. Toggling a published dark theme
on there meant it won the cascade over whichever theme the picker had active —
selecting its light sibling changed nothing on screen.
Published themes are what the picker offers, so they are now treated as
mutually exclusive selections rather than stackable overlays:
- reapplyEnabledThemes() skips the active theme and any other published one,
so it does what its comment always said: re-inject the unpublished CSS
tweaks on top of the active theme.
- Selecting a published theme drops the others from the enabled list, and
loadTheme() only injects a published theme when it is the selected one, so
a stale entry cannot resurface after a reload.
- Toggling a published theme on in Settings selects it; toggling the active
one off falls back to the built-in default instead of leaving the picker
pointing at a stylesheet that is no longer loaded.
Unpublished themes are unchanged and still stack on top of whatever theme is
selected.
Admin can now promote .theme.css files into the main theme picker:
- Admin Settings → Appearance → Custom Themes: checkboxes for every
.theme.css file in themes/. Checked themes are stored server-wide
in the published_themes DB setting.
- /api/themes now returns a 'published' flag on each theme so clients
know which ones to show in the picker.
- plugin-loader.js injects published themes as real buttons in
#theme-selector. Clicking one applies it exclusively: any previous
file-theme link is removed, inline custom vars are cleared, and the
new .theme.css link is injected after all other stylesheets so its
:root{} rules win without fighting built-in [data-theme] selectors.
- theme.js: switching to a built-in theme strips injected file-theme
links. applyThemeFromServer handles 'file:' values.
- The Default Theme admin dropdown now auto-populates with published
file themes alongside built-in options.
- themes/custom.css.example: reference file documenting all CSS vars
and @icon metadata for custom theme authors.