Compare commits

..

No commits in common. "fix/web-ui-theme-sync" and "main" have entirely different histories.

7 changed files with 20 additions and 61 deletions

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

@ -823,32 +823,6 @@ pub fn write_cookies(text: &str) -> Result<usize, String> {
mod tests { mod tests {
use super::*; use super::*;
/// The web UI keeps its own hardcoded theme list (a JS `THEMES` array plus
/// per-theme CSS `.theme-*` blocks) that has to stay in sync with the
/// desktop's `theme::THEMES`. This pins that: every desktop theme must be
/// selectable *and* styled in the SPA, so newer themes can't silently go
/// missing from the web (as cemetery-moss/vampire/cyberpunk/… once did).
#[test]
fn web_ui_offers_every_desktop_theme() {
for (id, _label) in crate::theme::THEMES {
// Present in the JS picker array as `['id',…]` (label formatting
// varies — some need JS-escaping — so key on the id token).
let entry = format!("['{id}',");
assert!(
HTML_UI.contains(&entry),
"theme '{id}' missing from the web THEMES picker array (expected {entry})"
);
// Styled: `dark` is the default `:root{{…}}`, the rest are classes.
if *id != "dark" {
let css = format!(".theme-{id}{{");
assert!(
HTML_UI.contains(&css),
"theme '{id}' has no CSS block in the web UI (expected `{css}`)"
);
}
}
}
#[test] #[test]
fn merge_keeps_blank_password_by_url() { fn merge_keeps_blank_password_by_url() {
use crate::config::{RemoteKind, RemoteSection}; use crate::config::{RemoteKind, RemoteSection};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

View file

@ -1,31 +1,31 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<!-- Catacomb app icon: a goth coffin with a play triangle punched out of <!-- Catacomb app icon: a crypt archway sheltering a play wedge, in the
it (negative space), in the UI's default palette (bg #1a1a2e / accent UI's default palette (bg #1a1a2e / accent #e94560). Full-bleed
#e94560). Full-bleed background with the coffin inside the central background with all strokes inside the central maskable safe zone,
maskable safe zone, so the same art serves purpose:any and so the same art serves purpose:any and purpose:maskable.
purpose:maskable. Regenerate the PNGs after editing:
Regenerate every derived PNG after editing: rsvg-convert -w 512 -h 512 icon.svg -o icon-512.png
rsvg-convert -w 512 -h 512 src/web_ui/icon.svg -o src/web_ui/icon-512.png rsvg-convert -w 192 -h 192 icon.svg -o icon-192.png
rsvg-convert -w 192 -h 192 src/web_ui/icon.svg -o src/web_ui/icon-192.png rsvg-convert -w 180 -h 180 icon.svg -o apple-touch-icon.png -->
rsvg-convert -w 180 -h 180 src/web_ui/icon.svg -o src/web_ui/apple-touch-icon.png
rsvg-convert -w 256 -h 256 src/web_ui/icon.svg -o icon.png # desktop window / tray / packaging -->
<defs> <defs>
<radialGradient id="bg" cx="30%" cy="12%" r="130%"> <radialGradient id="bg" cx="30%" cy="12%" r="130%">
<stop offset="0" stop-color="#272747"/> <stop offset="0" stop-color="#272747"/>
<stop offset="1" stop-color="#131322"/> <stop offset="1" stop-color="#131322"/>
</radialGradient> </radialGradient>
<linearGradient id="acc" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="arch" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ff5f7d"/> <stop offset="0" stop-color="#ff5f7d"/>
<stop offset="1" stop-color="#cc2b4d"/> <stop offset="1" stop-color="#cc2b4d"/>
</linearGradient> </linearGradient>
<!-- punch the play wedge out of the solid coffin so the bg shows through -->
<mask id="cut">
<rect width="512" height="512" fill="white"/>
<polygon points="230,206 230,308 312,257" fill="black"/>
</mask>
</defs> </defs>
<rect width="512" height="512" fill="url(#bg)"/> <rect width="512" height="512" fill="url(#bg)"/>
<!-- hexagonal coffin: flat head edge, sharp shoulder, long taper, flat foot --> <!-- doorway interior: faint accent glow so the opening reads as depth -->
<polygon points="206,104 306,104 354,172 314,426 198,426 158,172" <path d="M192 396 V240 a64 64 0 0 1 128 0 V396 Z" fill="#e94560" opacity="0.16"/>
fill="url(#acc)" mask="url(#cut)"/> <!-- arch -->
<path d="M154 396 V240 a102 102 0 0 1 204 0 V396" fill="none"
stroke="url(#arch)" stroke-width="28" stroke-linecap="round"/>
<!-- floor (solid colour: a zero-height bounding box degenerates the
objectBoundingBox gradient and rsvg drops the stroke entirely) -->
<path d="M118 396 H394" stroke="#cc2b4d" stroke-width="28" stroke-linecap="round"/>
<!-- play wedge in the doorway -->
<path d="M233 282 l60 34 -60 34 Z" fill="#ff5f7d"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

View file

@ -25,19 +25,6 @@
.theme-emo-nocturnal{--bg:#0a0a0a;--panel:#0d0d0d;--card:#1a1a1a;--accent:#ff0090;--text:#e8e8e8;--muted:#888;--border:#2a2a2a} .theme-emo-nocturnal{--bg:#0a0a0a;--panel:#0d0d0d;--card:#1a1a1a;--accent:#ff0090;--text:#e8e8e8;--muted:#888;--border:#2a2a2a}
.theme-emo-coffin{--bg:#0d0009;--panel:#110010;--card:#1a0018;--accent:#cc2222;--text:#c0c0c0;--muted:#666;--border:#3a0030} .theme-emo-coffin{--bg:#0d0009;--panel:#110010;--card:#1a0018;--accent:#cc2222;--text:#c0c0c0;--muted:#666;--border:#3a0030}
.theme-emo-scene-queen{--bg:#080818;--panel:#0a0a1e;--card:#111128;--accent:#39ff14;--text:#c8c8ff;--muted:#666;--border:#222244} .theme-emo-scene-queen{--bg:#080818;--panel:#0a0a1e;--card:#111128;--accent:#39ff14;--text:#c8c8ff;--muted:#666;--border:#222244}
/* Palettes ported from the desktop theme.rs (Catacomb/goth, neon, dev, cozy).
Keep this list in sync with theme::THEMES — web::tests::web_ui_offers_every_desktop_theme guards it. */
.theme-cemetery-moss{--bg:#1a1f1a;--panel:#1e241e;--card:#242a24;--accent:#7a8a6a;--text:#b8b8a8;--muted:#8a8a7a;--border:#363a30}
.theme-vampire{--bg:#0d0006;--panel:#120008;--card:#18000c;--accent:#c9a227;--text:#d8c8a8;--muted:#9a8a6a;--border:#3a0014}
.theme-witching-hour{--bg:#0a0a1f;--panel:#0e0e28;--card:#14142e;--accent:#8f77c0;--text:#c0c8e0;--muted:#8a90b0;--border:#2a2a55}
.theme-cyberpunk{--bg:#0a0a12;--panel:#0e0e18;--card:#14141f;--accent:#00fff5;--text:#c8c8e0;--muted:#7a7a95;--border:#2a2a3a}
.theme-synthwave{--bg:#2b0a3d;--panel:#330a48;--card:#3a1055;--accent:#ff2a6d;--text:#e8c0e0;--muted:#a98ac0;--border:#5a1a7a}
.theme-vaporwave{--bg:#1a0033;--panel:#200040;--card:#28004e;--accent:#ff71ce;--text:#e0c0ff;--muted:#a986cc;--border:#4a0070}
.theme-gruvbox{--bg:#282828;--panel:#32302f;--card:#3c3836;--accent:#fe8019;--text:#ebdbb2;--muted:#a89984;--border:#504945}
.theme-tokyo-night{--bg:#1a1b26;--panel:#20202f;--card:#24253a;--accent:#7aa2f7;--text:#c0caf5;--muted:#787c99;--border:#3b4261}
.theme-paper{--bg:#f4ecd8;--panel:#f8f0dc;--card:#efe5cc;--accent:#8b6f3a;--text:#3d2b1f;--muted:#8a7a5a;--border:#c4a86a}
.theme-honey{--bg:#fff4e0;--panel:#fff9ec;--card:#ffecc8;--accent:#c78820;--text:#5c3818;--muted:#a8834a;--border:#e2c48a}
.theme-candlelight{--bg:#f2e6d0;--panel:#f6ecd9;--card:#ead8b8;--accent:#a8703a;--text:#3a2818;--muted:#8a6a48;--border:#c8a06a}
*{box-sizing:border-box;margin:0;padding:0} *{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--text);font:14px/1.5 system-ui,sans-serif;display:flex;flex-direction:column;height:100dvh;min-height:100dvh;overflow:hidden;overscroll-behavior:none} body{background:var(--bg);color:var(--text);font:14px/1.5 system-ui,sans-serif;display:flex;flex-direction:column;height:100dvh;min-height:100dvh;overflow:hidden;overscroll-behavior:none}
header{background:var(--panel);padding:8px 12px;display:flex;gap:8px;align-items:center;border-bottom:1px solid var(--border);flex-shrink:0;flex-wrap:wrap;position:sticky;top:0;z-index:20} header{background:var(--panel);padding:8px 12px;display:flex;gap:8px;align-items:center;border-bottom:1px solid var(--border);flex-shrink:0;flex-wrap:wrap;position:sticky;top:0;z-index:20}
@ -2413,9 +2400,7 @@ async function showMetadata(id){
function switchMetaTab(tab,which){tab.parentElement.querySelectorAll('.meta-tab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');document.getElementById('meta-summary').style.display=which==='summary'?'':'none';document.getElementById('meta-raw').style.display=which==='raw'?'':'none'} function switchMetaTab(tab,which){tab.parentElement.querySelectorAll('.meta-tab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');document.getElementById('meta-summary').style.display=which==='summary'?'':'none';document.getElementById('meta-raw').style.display=which==='raw'?'':'none'}
/* ── Settings ───────────────────────────────────────────────────── */ /* ── Settings ───────────────────────────────────────────────────── */
// Keep in sync with the desktop theme::THEMES (web::tests::web_ui_offers_every_desktop_theme const THEMES=[['dark','Dark'],['light','Light'],['dracula','Dracula'],['trans','Trans'],['emo-nocturnal','Emo: Nocturnal'],['emo-coffin','Emo: Coffin'],['emo-scene-queen','Emo: Scene Queen'],['solarized','Solarized'],['nord','Nord'],['amoled','AMOLED']];
// guards it). solarized + amoled are web-only extras kept at the end.
const THEMES=[['dark','Dark'],['light','Light'],['dracula','Dracula'],['trans','Trans'],['emo-nocturnal','Emo: Nocturnal'],['emo-coffin','Emo: Coffin'],['emo-scene-queen','Emo: Scene Queen'],['cemetery-moss','Cemetery Moss'],['vampire','Vampire'],['witching-hour','Witching Hour'],['cyberpunk','Cyberpunk'],['synthwave',"Synthwave '84"],['vaporwave','Vaporwave'],['nord','Nord'],['gruvbox','Gruvbox'],['tokyo-night','Tokyo Night'],['paper','Paper'],['honey','Honey'],['candlelight','Candlelight'],['solarized','Solarized'],['amoled','AMOLED']];
function applyTheme(t){document.body.className=t==='dark'?'':'theme-'+t;localStorage.setItem('theme',t); function applyTheme(t){document.body.className=t==='dark'?'':'theme-'+t;localStorage.setItem('theme',t);
// Keep the browser/OS chrome (Android status bar, installed-PWA title bar) // Keep the browser/OS chrome (Android status bar, installed-PWA title bar)
// in step with the active theme's panel colour. // in step with the active theme's panel colour.