diff --git a/README.md b/README.md
index 3d03f1a..ff36bc0 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,7 @@ with this machine's shell settings — the same keys as the checked-in
| `adminPin` | The admin PIN, as a scrypt hash. **Set it in the admin page's Admin PIN section**, not by hand — 6 digits, and only the hash is stored. Absent means admin is open to anyone on the network. |
| `adminPasscode` | An older free-text passcode. Still honoured, so an existing install keeps working; new installs should use `adminPin`. |
| `forge` | Where updates and module installs are fetched from. Absent or `"github"` uses GitHub; a Gitea or Forgejo base URL such as `"http://10.3.8.90:3000"` uses a server of your own. Set it per machine in the data directory, alongside `repo`. |
-| `theme` | The dashboard palette: `booth` (default), `harbor`, `graphite`, `ember` or `daylight`. The admin page's **Theme** section writes this one — see [Themes](#themes). |
+| `theme` | The dashboard palette: `booth` (default), `harbor`, `graphite`, `ember`, `daylight`, `midnight`, `moss`, `plum`, `sepia`, `blackout`, `paper` or `iron`. The admin page's **Theme** section writes this one — see [Themes](#themes). |
**Locked out?** Delete `adminPin` from the data directory's `proddash.json`
and restart, or start the server with `PRODDASH_PASSCODE` set — that passcode
@@ -140,12 +140,26 @@ ignored from then on.
The whole dashboard — every tile, on every browser — draws with one palette,
chosen in the admin page's **Theme** section and switched live: pick a swatch
-and open dashboards change without a reload. Five presets ship: **Booth**
-(the default dark, mint accent), **Harbor** (deep navy, cyan), **Graphite**
-(neutral charcoal, electric blue), **Ember** (warm broadcast black, orange)
-and **Daylight**, a light palette for a front-of-house desk in daytime. Each
-keeps the status colours apart — the accent is never near the danger red or
-the warning amber — and muted text readable in a dark booth. The choice is
+and open dashboards change without a reload. The section starts folded —
+a building picks its palette once — and holds twelve presets:
+
+| | |
+|---|---|
+| **Booth** | the default dark, mint accent |
+| **Harbor** | deep navy, cyan |
+| **Graphite** | neutral charcoal, electric blue |
+| **Ember** | warm broadcast black, orange |
+| **Daylight** | a cool light palette, for a front-of-house desk in daytime |
+| **Midnight** | indigo night, violet |
+| **Moss** | forest floor, lime |
+| **Plum** | aubergine, purple |
+| **Sepia** | warm brown room, cool teal — Ember's arrangement reversed |
+| **Blackout** | true black, cyan, for a dark room where any glow carries |
+| **Paper** | the warm light one, indigo accent |
+| **Iron** | no colour of its own: a near-white accent, so the only hues on screen are a module's own and the red and amber that mean something |
+
+Each keeps the status colours apart — the accent is never near the danger red
+or the warning amber — and muted text readable in a dark booth. The choice is
stored as `theme` in the data directory's `proddash.json` (table above), so
it holds across restarts and updates; a browser remembers the last theme it
saw and applies it before the server answers, so nothing flashes. Modules
diff --git a/public/admin/admin.css b/public/admin/admin.css
index 7eec5df..569076a 100644
--- a/public/admin/admin.css
+++ b/public/admin/admin.css
@@ -451,6 +451,29 @@ a.btn { text-decoration: none; }
/* one swatch per preset: a miniature dashboard (page, a tile on it, the
accent as a status dot) and the name; the current one wears the accent */
+/* A whole admin section that folds, wearing the same disclosure triangle as
+ a module's config groups. Theme is set once for a building and then left
+ alone, so it starts closed and keeps the rest of the page in reach. */
+details.admin-fold > summary.admin-fold-title {
+ cursor: pointer;
+ list-style: none;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ user-select: none;
+}
+details.admin-fold > summary.admin-fold-title::-webkit-details-marker { display: none; }
+details.admin-fold > summary.admin-fold-title::before {
+ content: "";
+ width: 0; height: 0;
+ border-left: 6px solid var(--muted);
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ transition: transform 0.15s;
+}
+details.admin-fold[open] > summary.admin-fold-title::before { transform: rotate(90deg); }
+details.admin-fold > summary.admin-fold-title:hover { color: var(--accent); }
+
.theme-swatches {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
diff --git a/public/admin/index.html b/public/admin/index.html
index a327283..df0e1d4 100644
--- a/public/admin/index.html
+++ b/public/admin/index.html
@@ -41,9 +41,11 @@
- Theme
-
-
+
+ Theme
+
+
+
diff --git a/public/style.css b/public/style.css
index 03b880f..f7869a0 100644
--- a/public/style.css
+++ b/public/style.css
@@ -941,8 +941,10 @@ body.fullscreen #grid { padding-bottom: 0; }
every shell element follows, and a colour a tile is configured with (set
on the tile itself) still wins. The :root block at the top of this file is
"booth", the default; the list of ids and names lives in server.js
- (GET /api/theme). All four are booth-grade: high contrast, the accent
- nowhere near the danger red or the warning amber, muted still readable. */
+ (GET /api/theme). Every one is booth-grade: high contrast, the accent
+ nowhere near the danger red or the warning amber, muted still readable.
+ Each sets --accent-text too when the default (a near-black green, for
+ booth's mint) would look wrong against its own accent. */
/* Harbor — deep navy, cyan accent */
html[data-theme="harbor"] {
@@ -993,3 +995,100 @@ html[data-theme="daylight"] {
--danger: #d1242f;
--warn: #b26a00;
}
+
+/* Midnight — indigo night, violet accent */
+html[data-theme="midnight"] {
+ --bg: #0a0b18;
+ --panel: #14162b;
+ --border: #272a4a;
+ --text: #e8e9fb;
+ --muted: #9a9cc8;
+ --accent: #9d8cff;
+ --accent-text: #12103a;
+ --danger: #ff5a72;
+ --warn: #f5b93a;
+}
+
+/* Moss — forest floor, lime accent (warn pushed orange so the two greens
+ and the amber never read as one another) */
+html[data-theme="moss"] {
+ --bg: #08120c;
+ --panel: #101e15;
+ --border: #1f3728;
+ --text: #e9f4ea;
+ --muted: #8fb09a;
+ --accent: #8fdb3f;
+ --accent-text: #15250a;
+ --danger: #ff5a52;
+ --warn: #ff9f1c;
+}
+
+/* Plum — aubergine, purple accent */
+html[data-theme="plum"] {
+ --bg: #110a17;
+ --panel: #1d1228;
+ --border: #332042;
+ --text: #f2e9f8;
+ --muted: #b195c2;
+ --accent: #c084fc;
+ --accent-text: #260a37;
+ --danger: #ff4d4f;
+ --warn: #f7b53b;
+}
+
+/* Sepia — warm brown room, cool teal accent: the opposite arrangement to
+ Ember, which is warm throughout */
+html[data-theme="sepia"] {
+ --bg: #12100b;
+ --panel: #1d1913;
+ --border: #33291d;
+ --text: #f4ede0;
+ --muted: #b3a48b;
+ --accent: #5eead4;
+ --accent-text: #06231e;
+ --danger: #ff5a52;
+ --warn: #ffc247;
+}
+
+/* Blackout — true black for a dark room, where any glow off the screen
+ carries; everything else is as bright as it can be against it */
+html[data-theme="blackout"] {
+ --bg: #000000;
+ --panel: #0a0a0a;
+ --border: #1f1f1f;
+ --text: #ffffff;
+ --muted: #9e9e9e;
+ --accent: #00e5ff;
+ --accent-text: #00212a;
+ --danger: #ff3b30;
+ --warn: #ffcc00;
+}
+
+/* Paper — the warm light one, next to Daylight's cool white. Danger and warn
+ darken, as they must on a light background, and the indigo accent takes
+ white text. */
+html[data-theme="paper"] {
+ --bg: #f5f1e8;
+ --panel: #fffdf8;
+ --border: #ddd5c4;
+ --text: #1c1a15;
+ --muted: #6b6455;
+ --accent: #4f46e5;
+ --accent-text: #ffffff;
+ --danger: #c8102e;
+ --warn: #9a6700;
+}
+
+/* Iron — no colour of its own: the accent is near-white, so the only hues on
+ screen are a module's own and the red/amber that mean something */
+html[data-theme="iron"] {
+ --bg: #0c0c0d;
+ --panel: #161617;
+ --border: #2b2b2d;
+ --text: #f2f2f3;
+ --muted: #9a9a9d;
+ --accent: #d8d8dc;
+ --accent-text: #131314;
+ --danger: #ff4d4f;
+ --warn: #ffb224;
+}
diff --git a/server.js b/server.js
index 1d85a7f..e722e12 100644
--- a/server.js
+++ b/server.js
@@ -712,6 +712,13 @@ const THEMES = [
{ id: 'graphite', name: 'Graphite', colors: { bg: '#0f1012', panel: '#17191d', accent: '#6cb4ff' } },
{ id: 'ember', name: 'Ember', colors: { bg: '#120c0a', panel: '#1d1411', accent: '#ff8f3a' } },
{ id: 'daylight', name: 'Daylight', colors: { bg: '#eef1f5', panel: '#ffffff', accent: '#0b8a5f' } },
+ { id: 'midnight', name: 'Midnight', colors: { bg: '#0a0b18', panel: '#14162b', accent: '#9d8cff' } },
+ { id: 'moss', name: 'Moss', colors: { bg: '#08120c', panel: '#101e15', accent: '#8fdb3f' } },
+ { id: 'plum', name: 'Plum', colors: { bg: '#110a17', panel: '#1d1228', accent: '#c084fc' } },
+ { id: 'sepia', name: 'Sepia', colors: { bg: '#12100b', panel: '#1d1913', accent: '#5eead4' } },
+ { id: 'blackout', name: 'Blackout', colors: { bg: '#000000', panel: '#0a0a0a', accent: '#00e5ff' } },
+ { id: 'paper', name: 'Paper', colors: { bg: '#f5f1e8', panel: '#fffdf8', accent: '#4f46e5' } },
+ { id: 'iron', name: 'Iron', colors: { bg: '#0c0c0d', panel: '#161617', accent: '#d8d8dc' } },
];
const DEFAULT_THEME = THEMES[0].id;