A dark, atmospheric UI theme for Phlix with coral accents, film grain and a vignette-darkened surface stack.
The payload registers one theme — id void-bloom, name Void Bloom, dark: true, extending the built-in midnight base — and contributes the 28 CSS
custom properties below. Each row names the actual token it ships; tests/TokenContrastTest.php
recomputes WCAG math from these values on every run.
| Token | Value | Role |
|---|---|---|
--accent |
#ff6b6b |
Coral accent — fills, active states |
--accent-hover |
#ff8787 |
Accent hover step |
--accent-active |
#fa5252 |
Accent pressed step |
--accent-soft |
rgba(255, 107, 107, 0.15) |
Translucent accent background (soft tints, selected rows) |
--accent-ring |
rgba(255, 107, 107, 0.7) |
Keyboard focus ring — the ui paints box-shadow: 0 0 0 Npx var(--accent-ring) after outline: none, so this is the only desktop focus cue; 0.7 alpha keeps it ≥3:1 (WCAG 1.4.11) against every ground adjacency |
--accent-text |
#ff6b6b |
The accent hue as foreground text/icon — per the contract (phlix-tokens/src/css/colors.css: the accent as foreground, AA ≥4.5:1 on bg/surfaces); here it is the coral itself, 6.01–7.34:1 on the surface stack. This is not the ink painted on accent-filled controls — that role is --text-on-accent |
| Token | Value | Role |
|---|---|---|
--bg |
#050505 |
Primary background |
--surface |
#0c0c0c |
Card/panel surfaces |
--surface-2 |
#141414 |
Elevated surfaces |
--surface-3 |
#1e1e1e |
Highest elevation |
--surface-glass |
rgba(12, 12, 12, 0.65) |
Glass panel overlay |
--surface-glass-strong |
rgba(5, 5, 5, 0.85) |
Stronger glass overlay |
| Token | Value | Role |
|---|---|---|
--text |
#f5f0e8 |
Primary text (warm white) |
--text-muted |
#c9c2b8 |
Secondary text |
--text-subtle |
#9c948a |
Tertiary text — placeholders/hints, AA ≥4.5:1 on every ground (pinned) |
--text-faint |
#8f877d |
Disabled/hint text — carries form placeholders and help copy, AA ≥4.5:1 on every ground (pinned) |
--text-on-accent |
#050505 |
Dark ink painted on accent-filled controls (Button, Badge, Switch knob) — the ink-on-fill role, distinct from --accent-text |
| Token | Value | Role |
|---|---|---|
--border |
#252220 |
Default borders |
--border-subtle |
#1a1918 |
Subtle dividers |
--border-strong |
#3d3835 |
Emphasized borders |
--grain-opacity |
0.035 |
Film grain overlay strength |
--vignette |
rgba(0, 0, 0, 0.7) |
Edge darkening |
--ambient |
rgba(255, 107, 107, 0.18) |
Ambient coral glow |
The five keys the built-in generation still reads, each mirroring its modern
twin one-for-one (other --color-* halves are not overridden here — they
inherit midnight):
| Token | Value | Alias of |
|---|---|---|
--color-bg |
#050505 |
--bg |
--color-surface |
#0c0c0c |
--surface |
--color-text |
#f5f0e8 |
--text |
--color-text-muted |
#c9c2b8 |
--text-muted |
--color-border |
#252220 |
--border |
The swatch block below is deliberately a language-less plain fence — ASCII art, not code; do not let a linter "fix" it into a tagged block.
Coral Accent: #ff6b6b ████
Coral Hover: #ff8787 ████
Coral Active: #fa5252 ████
Background: #050505 ████████
Surface: #0c0c0c ████████
Surface 2: #141414 ████████
Surface 3: #1e1e1e ████████
Text: #f5f0e8 ████████
Text Muted: #c9c2b8 ████████
Text Subtle: #9c948a ████████
Text Faint: #8f877d ████████
- Dark atmospheric design with warm coral accents
- Layered four-tier surface stack under the
--vignetteedge-darkening (values in the table above) — shadow ramps are host-baseline layout tokens inherited frommidnightunchanged; this theme ships none and deepens nothing on its own - Ambient coral glow effects
- Film grain texture overlay
- Vignette edge darkening
- Extends the built-in
midnightbase: the 28 tokens above are what this theme overrides — everything else it inherits frommidnightunchanged
The Phlix server installs plugins from a catalog (plugins.json). A catalog
entry pins an exact commit (ref) and the sha256 of that commit's tarball
(artifactSha256), and the server verifies those bytes before it extracts
anything. Without a pin, PluginLoader::install() refuses outright — that is a
deliberate default-deny, not a bug to work around.
Admin UI — Admin → Plugins (/app/admin/plugins), find Void Bloom
Theme, press Install, then flip its switch to enable it.
CLI — from the server root, pass the repository URL, not the plugin name (the URL is what gets rewritten to the pinned tarball):
php bin/phlix plugin:install https://github.com/detain/phlix-plugin-void-bloom-theme
php bin/phlix plugin:enable phlix-plugin-void-bloom-themeWhile iterating on a local fork (no catalog entry yet), build an archive and
install it over file://, which is exempt from the pin requirement because the
bytes are already yours:
tar --exclude=vendor --exclude=.git -czf /tmp/void-bloom.tar.gz phlix-plugin-void-bloom-theme/
php bin/phlix plugin:install file:///tmp/void-bloom.tar.gz
php bin/phlix plugin:enable phlix-plugin-void-bloom-themecomposer require detain/phlix-plugin-void-bloom-theme into the server, or
cloning this repository into the plugins directory, does not register anything —
the plugin loader installs catalog-pinned archives or local tarballs, never an
ambient vendor checkout or a bare directory.
Installing downloads and stages the plugin. Enabling it is what registers
the theme: PluginLoader::enable() finds the ThemeSourceInterface on the
entry class and contributes the void-bloom theme id. Choosing the theme is a
per-user preference, not an admin action — each user picks Void Bloom
under Settings → Appearance. None of the three steps implies the next.
- PHP 8.3+
- Phlix 0.44.0+
Install dependencies:
composer installRun the test suite (phpunit.xml boots tests/bootstrap.php):
vendor/bin/phpunit --colors=alwaysRun static analysis and coding standard checks:
composer phpstan
composer phpcsphpstan.neon scans dev-stubs/, which holds minimal copies of the theme's
TWO host contracts: Phlix\Shared\Plugin\LifecycleInterface, which ships in
detain/phlix-shared and so is supplied by composer install, and
Phlix\Theming\ThemeSourceInterface, which is declared by the server and ships
in no Composer package at all — only that second one is host-only.
tests/bootstrap.php loads each stub only when the real interface is absent,
so a run inside a server checkout still exercises the host's own contracts.
phpcs.xml applies PSR12 to src/, tests/ and dev-stubs/.
.github/workflows/test.yml runs those three gates on PHP 8.3 and 8.4, plus a
fourth: the S440 composer-audit job, which audits the whole committed
composer.lock — runtime AND development dependencies. Run it locally with
php scripts/security-audit-check.php (PHP 8.3 leg in CI). Its containment
rules — a single bare run line, no continue-on-error, the job kept LAST in
the file — are policed by tests/SecurityAuditCheckTest.php, so neutering the
gate fails the suite.
MIT License — see LICENSE file for details.