OXYGENHUB

Branding: the Oxygen mark as logo and favicon; palette tuned to its cyan and blue 5c6eb1feaa71

The RetroPlay theme stays as the rough shape (fonts, band, tabs, panels);
the colours now come from the mark shared with the VS Code extension.
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-27 17:41 UTC
Signature
✔ signed by Andy McCall
Hash
5c6eb1feaa71c26bcdc584fd171453dca0ebf265440864084d1b3ccd00078039
Depends on
93be9ce4653e d23464c0ac60
Depended on by
03bfb945f5c1 f6346d875ee5 6ad368c5958e 756fe44a0f4d 42a0ba4bac4e c40c2d3edb51 19bb31ff1f5c abb98fa9964e
In bookmarks
api-unknown-fields cancel-landed-runs clear-waiting close-proposal-140 diffusion-pin-bookmark draft-ready image-check job-waits main oh-comment-body-file proposal-list-fast run-one-job wiki-says-so
In tags
v0.1.0 v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
18 atoms, 1 tree operation, 5 files

Browse the tree at this change

added assets/icons/mark.svg +14 −0
1 +
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-24 -12 48 24" width="48" height="24" aria-hidden="true">
2 +
  <defs>
3 +
    <linearGradient id="oxyGrad" x1="0%" y1="0%" x2="100%" y2="100%">
4 +
      <stop offset="0%" stop-color="#00F0FF"/>
5 +
      <stop offset="100%" stop-color="#0072FF"/>
6 +
    </linearGradient>
7 +
  </defs>
8 +
  <path d="M -11 -6 L 11 -6" stroke="url(#oxyGrad)" stroke-width="3" stroke-linecap="round"/>
9 +
  <path d="M -11 6 L 11 6" stroke="url(#oxyGrad)" stroke-width="3" stroke-linecap="round"/>
10 +
  <circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGrad)" stroke-width="3"/>
11 +
  <circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/>
12 +
  <circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGrad)" stroke-width="3"/>
13 +
  <circle cx="11" cy="0" r="3.5" fill="#FFFFFF"/>
14 +
</svg>
modified assets/icons/oxygenhub.svg +20 −1
1 -
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges"><rect width="16" height="16" fill="#141430"/><path fill="#ffc83c" d="M5 3h6v1H5zM4 4h1v1H4zM11 4h1v1h-1zM3 5h1v6H3zM12 5h1v6h-1zM4 11h1v1H4zM11 11h1v1h-1zM5 12h6v1H5z"/><path fill="#ffdd00" d="M7 7h2v2H7z"/></svg>
1 +
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="100%" height="100%">
2 +
  <defs>
3 +
    <linearGradient id="oxyGrad" x1="0%" y1="0%" x2="100%" y2="100%">
4 +
      <stop offset="0%" stop-color="#00F0FF" />
5 +
      <stop offset="100%" stop-color="#0072FF" />
6 +
    </linearGradient>
7 +
8 +
    <g id="oxy-mark-mini">
9 +
      <path d="M -11 -6 L 11 -6" stroke="url(#oxyGrad)" stroke-width="3" stroke-linecap="round" />
10 +
      <path d="M -11 6 L 11 6" stroke="url(#oxyGrad)" stroke-width="3" stroke-linecap="round" />
11 +
      <circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGrad)" stroke-width="3" />
12 +
      <circle cx="-11" cy="0" r="3.5" fill="#00F0FF" />
13 +
      <circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGrad)" stroke-width="3" />
14 +
      <circle cx="11" cy="0" r="3.5" fill="#FFFFFF" />
15 +
    </g>
16 +
  </defs>
17 +
18 +
  <rect width="64" height="64" rx="14" fill="#070c18" />
19 +
  <use href="#oxy-mark-mini" x="32" y="32" />
20 +
</svg>
modified docs/spec.md +2 −2
…
196 unchanged lines
197 197
198 198
### 6.1 Theme
199 199
200 -
The visual language is the RetroPlay Network's: dark by default, the Commander X16 palette, `Barlow` for text and `Press Start 2P` for the wordmark, ranks and first columns, square corners, thin `--line` rules, a `--panel` header and footer, and a full-width `--band` navigation bar with the active tab underlined in `--acc`. The stylesheet starts from `retroplay-manager/templates/style.css` lines 1–171 (tokens, shell, band, tabs, main, tables, forms, footer) and adds forge components; the store, showcase and game components are not carried over.
200 +
The visual language follows the RetroPlay Network's shape (dark by default, `Barlow` for text and `Press Start 2P` for the wordmark and first columns, square corners, thin `--line` rules, a `--panel` header and footer, a full-width `--band` navigation bar with the active tab underlined in `--acc`) with the palette taken from the Oxygen mark shared with the VS Code extension: the mark's cyan `#00F0FF` to blue `#0072FF` gradient on `#070c18`, so `--acc` is cyan, `--acc-2` blue, and the band a deep blue. The mark itself is the logo in the header, the footer and the favicon; the wordmark beside it reads OXYGEN**HUB**. The stylesheet starts from `retroplay-manager/templates/style.css` lines 1–171 (tokens, shell, band, tabs, main, tables, forms, footer) and adds forge components; the store, showcase and game components are not carried over.
201 201
202 202
Tokens (dark default, light under `:root[data-theme="light"]`):
203 203
…
7 unchanged lines
211 211
212 212
Theme selection: cookie `theme=light|dark|auto`; `auto` follows `prefers-color-scheme`. The `<head>` sets `data-theme` before paint exactly as retroplay-manager does. Fonts are vendored under `/static/fonts/` rather than loaded from Google, so a hub on a private network paints without internet access.
213 213
214 -
The wordmark is `OXY<span>◆</span>GEN` in the pixel font with the diamond in `--acc`. Favicon: a 16×16 pixel-art "O" in amber on `--panel`, matching `retroplay.svg`'s style.
214 +
The logo is `assets/icons/oxygenhub.svg`, a copy of the extension's `oxygen.svg` (two linked rings, the molecule), inlined in the header at 22px beside the wordmark.
215 215
216 216
### 6.2 Page shell
217 217
…
325 unchanged lines
modified templates/base.html +2 −2
…
9 unchanged lines
10 10
</head>
11 11
<body{% block bodyattrs %}{% endblock %}>
12 12
<header>
13 -
  <a class="brand px" href="/">OXY<span>◆</span>GEN</a>
13 +
  <a class="brand" href="/"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-24 -12 48 24" width="48" height="24" aria-hidden="true"><defs><linearGradient id="oxyGradH" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00F0FF"/><stop offset="100%" stop-color="#0072FF"/></linearGradient></defs><path d="M -11 -6 L 11 -6" stroke="url(#oxyGradH)" stroke-width="3" stroke-linecap="round"/><path d="M -11 6 L 11 6" stroke="url(#oxyGradH)" stroke-width="3" stroke-linecap="round"/><circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGradH)" stroke-width="3"/><circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/><circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGradH)" stroke-width="3"/><circle cx="11" cy="0" r="3.5" fill="#FFFFFF"/></svg><span class="px">OXYGEN<em>HUB</em></span></a>
14 14
  <form class="find" action="/search" method="get" role="search"><input type="search" name="q" placeholder="Search…" aria-label="Search" autocomplete="off"></form>
15 15
  <nav>
16 16
    <a class="go" href="/explore">EXPLORE</a>
…
12 unchanged lines
29 29
<footer>
30 30
  <div class="foot">
31 31
    <div>
32 -
      <a class="brand px" href="/">OXY<span>◆</span>GEN</a>
32 +
      <a class="brand" href="/"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-24 -12 48 24" width="48" height="24" aria-hidden="true"><defs><linearGradient id="oxyGradF" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00F0FF"/><stop offset="100%" stop-color="#0072FF"/></linearGradient></defs><path d="M -11 -6 L 11 -6" stroke="url(#oxyGradF)" stroke-width="3" stroke-linecap="round"/><path d="M -11 6 L 11 6" stroke="url(#oxyGradF)" stroke-width="3" stroke-linecap="round"/><circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGradF)" stroke-width="3"/><circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/><circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGradF)" stroke-width="3"/><circle cx="11" cy="0" r="3.5" fill="#FFFFFF"/></svg><span class="px">OXYGEN<em>HUB</em></span></a>
33 33
      <p class="small">{{ shell.site }} · a forge for Oxygen repositories. Nothing here is inferred: what a page says, the object store says.</p>
34 34
    </div>
35 35
    <div class="cols">
…
12 unchanged lines
modified templates/style.css +21 −18
…
9 unchanged lines
10 10
@font-face { font-family: "Press Start 2P"; font-weight: 400; font-display: swap; src: url(/static/fonts/PressStart2P-400.woff2) format("woff2"); }
11 11
12 12
:root {
13 -
  --bg:#0b0b16; --panel:#141430; --soft:#1c1c3c; --ink:#e8e8f0; --mute:#8a8aa0; --line:#2c2c5a; --dim:#4a4a68;
14 -
  --acc:#ffc83c; --band:#3a5fd0; --band-ink:#ffffff; --band-line:rgba(11,11,22,.45); --now:#ffc83c; --now-ink:#0b0b16;
15 -
  --ok:#7dd87d; --bad:#ff5a5a; --warn:#ffb347;
16 -
  --add:#123a22; --del:#3f1520; --add-ink:#9fe8a8; --del-ink:#ffa5a5;
17 -
  --m1:#ffdd00; --m2:#d6d6e0; --m3:#d29a55; --m4:#c07a5a; --m5:#9a9ab8;
13 +
  /* Oxygen's mark: cyan #00F0FF to blue #0072FF on #070c18. */
14 +
  --bg:#070c18; --panel:#0e1526; --soft:#141d33; --ink:#e6ecf7; --mute:#8592ad; --line:#1f2b47; --dim:#43506d;
15 +
  --acc:#00e0f0; --acc-2:#0072ff; --band:#0b3f9e; --band-ink:#ffffff; --band-line:rgba(7,12,24,.5); --now:#00f0ff; --now-ink:#070c18;
16 +
  --ok:#5fe3a1; --bad:#ff5e7a; --warn:#ffb347;
17 +
  --add:#0d3a3a; --del:#3f1a2e; --add-ink:#8ff0d8; --del-ink:#ffa5b8;
18 +
  --m1:#00f0ff; --m2:#d6dcea; --m3:#5aa8ff; --m4:#0072ff; --m5:#8592ad;
18 19
  /* syntax */
19 -
  --sy-kw:#ff9de6; --sy-str:#a8e39b; --sy-num:#ffd479; --sy-cmt:#6e6e8c; --sy-fn:#8ad8ff; --sy-ty:#ffc83c; --sy-var:#e8e8f0; --sy-const:#ffb347;
20 +
  --sy-kw:#7fb4ff; --sy-str:#8ff0d8; --sy-num:#ffd479; --sy-cmt:#5b6785; --sy-fn:#00e0f0; --sy-ty:#c6a0ff; --sy-var:#e6ecf7; --sy-const:#ffb347;
20 21
  color-scheme: dark;
21 22
}
22 23
:root[data-theme="light"] {
23 -
  --bg:#f5f7f2; --panel:#ffffff; --soft:#eef2ea; --ink:#2b2f2a; --mute:#7a807a; --line:#d9ddd6; --dim:#b9bfb5;
24 -
  --acc:#5b9c1c; --band:#6aa91f; --band-ink:#ffffff; --band-line:rgba(255,255,255,.25); --now:#3e5f8f; --now-ink:#ffffff;
25 -
  --ok:#2e8b3a; --bad:#c8322b; --warn:#b8651b;
26 -
  --add:#e3f5e1; --del:#fbe3e1; --add-ink:#1f6b2a; --del-ink:#a12b25;
27 -
  --m1:#c9931a; --m2:#7a8290; --m3:#b0693a; --m4:#a25c46; --m5:#8a8f9c;
28 -
  --sy-kw:#a626a4; --sy-str:#50a14f; --sy-num:#986801; --sy-cmt:#9a9a9a; --sy-fn:#4078f2; --sy-ty:#c18401; --sy-var:#2b2f2a; --sy-const:#b8651b;
24 +
  --bg:#f4f7fc; --panel:#ffffff; --soft:#e9eef8; --ink:#1b2333; --mute:#66718a; --line:#d6dded; --dim:#b3bccf;
25 +
  --acc:#0072ff; --acc-2:#00b8d4; --band:#0b3f9e; --band-ink:#ffffff; --band-line:rgba(255,255,255,.25); --now:#0072ff; --now-ink:#ffffff;
26 +
  --ok:#1f8f5f; --bad:#c62d47; --warn:#b8651b;
27 +
  --add:#e1f5ee; --del:#fbe3e8; --add-ink:#176b48; --del-ink:#a1243c;
28 +
  --m1:#0090a8; --m2:#7a8290; --m3:#2f6fcf; --m4:#0055c0; --m5:#8a8f9c;
29 +
  --sy-kw:#0b4fc9; --sy-str:#1f8f5f; --sy-num:#986801; --sy-cmt:#8a94a8; --sy-fn:#0083a8; --sy-ty:#7a3fd1; --sy-var:#1b2333; --sy-const:#b8651b;
29 30
  color-scheme: light;
30 31
}
31 32
…
20 unchanged lines
52 53
53 54
/* shell */
54 55
header { background: var(--panel); border-bottom: 1px solid var(--line); padding: 14px 22px; display: flex; align-items: center; gap: 16px; }
55 -
header .brand span, footer .brand span { color: var(--acc); }
56 -
header .brand, footer .brand { color: var(--ink); }
56 +
.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); }
57 +
.brand svg { height: 22px; width: auto; }
58 +
.brand em { font-style: normal; color: var(--acc); }
59 +
.brand:hover { text-decoration: none; }
57 60
header nav { margin-left: auto; display: flex; gap: 8px; }
58 61
form.find { margin: 0; flex: 0 1 320px; }
59 62
form.find input { width: 100%; background: var(--bg); border: 1px solid var(--line); color: var(--ink); padding: 6px 10px; font: inherit; }
…
1 unchanged lines
61 64
.band { background: var(--band); display: flex; align-items: stretch; padding: 0 22px; }
62 65
.band a { color: var(--band-ink); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; padding: 9px 14px; border-right: 1px solid var(--band-line); }
63 66
.band a:first-child { border-left: 1px solid var(--band-line); }
64 -
.band a.on { box-shadow: inset 0 -3px 0 var(--acc); }
67 +
.band a.on { box-shadow: inset 0 -3px 0 var(--acc); background: rgba(0,0,0,.18); }
65 68
.band a:hover { text-decoration: none; background: rgba(0,0,0,.12); }
66 69
.band .grow { flex: 1; }
67 70
.band a.dim { opacity: .6; border: 0; }
…
9 unchanged lines
77 80
.foot { max-width: 1180px; margin: 0 auto; padding: 22px; display: grid; grid-template-columns: 1fr auto; gap: 22px; }
78 81
.foot .cols { display: flex; gap: 32px; }
79 82
.foot .cols a { display: block; font-size: 13px; }
80 -
.head { border-bottom: 2px solid var(--band); margin: 6px 0 16px; padding-bottom: 6px; }
83 +
.head { border-bottom: 2px solid var(--band); border-image: linear-gradient(90deg, var(--acc), var(--acc-2)) 1; margin: 6px 0 16px; padding-bottom: 6px; }
81 84
.head h1 { font-size: 30px; font-weight: 300; color: var(--mute); }
82 85
.head h1 small { display: block; font-size: 13px; color: var(--acc); font-weight: 400; }
83 86
.strip { padding: 8px 12px; margin: 0 0 12px; border: 1px solid; font-size: 13px; }
…
5 unchanged lines
89 92
90 93
/* buttons, tags, chips */
91 94
button, a.go, button.go { font: inherit; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; background: var(--panel); color: var(--acc); border: 1px solid var(--acc); padding: 5px 10px; cursor: pointer; }
92 -
button:hover, a.go:hover { background: var(--acc); color: var(--bg); text-decoration: none; }
95 +
button:hover, a.go:hover { background: var(--acc); color: var(--now-ink); text-decoration: none; }
93 96
button.small, a.go.small { padding: 2px 8px; font-size: 11px; }
94 97
.tag { display: inline-block; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--acc); background: var(--soft); padding: 1px 6px; vertical-align: middle; }
95 98
.tag.bad { color: var(--bad); }
…
90 unchanged lines
186 189
.lane { stroke: var(--band); stroke-width: 2; fill: none; }
187 190
.lane.none { stroke: none; }
188 191
.lane.dashed { stroke-dasharray: 3 3; stroke: var(--dim); }
189 -
.node { fill: var(--acc); }
192 +
.node { fill: var(--acc-2); stroke: var(--acc); stroke-width: 1.5; }
190 193
.node.head { fill: var(--now); stroke: var(--ink); stroke-width: 1.5; }
191 194
.node.res { fill: var(--ok); }
192 195
.chg { display: flex; justify-content: space-between; align-items: center; gap: 14px; padding: 8px 12px; flex: 1; min-width: 0; }
…
33 unchanged lines