From f24924031edefb5509474d37fc6b02e21de3bbd5 Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Thu, 6 Aug 2026 03:15:36 +0700 Subject: [PATCH] =?UTF-8?q?feat(web):=20apply=20design=20snapshot=20?= =?UTF-8?q?=E2=80=94=20libswitch,=20novel=20site=20colours,=20login=20art?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/web/static/style.css | 71 ++++++++++++++++++++++- backend/internal/web/templates/login.html | 3 + backend/web_test.go | 2 +- 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/backend/internal/web/static/style.css b/backend/internal/web/static/style.css index 6b6a9fe..d5e5756 100644 --- a/backend/internal/web/static/style.css +++ b/backend/internal/web/static/style.css @@ -97,6 +97,10 @@ --comix: #8a9a7d; --kagane: #9a8aa5; + /* novel sources: same muted family, two hues the manga sites do not use */ + --novelfull: #a59a7d; + --lightnovelworld: #7da59a; + /* Covers are often missing; the hatch keeps the slot honest instead of faking artwork. */ --hatch: repeating-linear-gradient(135deg, #211d1b 0 5px, #191614 5px 10px); @@ -150,6 +154,8 @@ --demonic: #8a6a55; --comix: #5f7250; --kagane: #6f5f7d; + --novelfull: #7d6f4f; + --lightnovelworld: #4f7d70; --hatch: repeating-linear-gradient(135deg, #e6e0d8 0 5px, #efeae3 5px 10px); --hatch-dim: repeating-linear-gradient(135deg, #ebe6de 0 5px, #f2eee8 5px 10px); @@ -485,12 +491,51 @@ button { cursor: pointer; } .site-demonic { color: var(--demonic); } .site-comix { color: var(--comix); } .site-kagane { color: var(--kagane); } +.site-novelfull { color: var(--novelfull); } +.site-lightnovelworld { color: var(--lightnovelworld); } .new-chapter { color: var(--ember); } .state { display: flex; align-items: center; gap: 4px; color: var(--mute); } .state svg { width: 10px; height: 10px; } .is-dim .meta { color: var(--mute-2); } .is-dim .site-asura, .is-dim .site-demonic, -.is-dim .site-comix, .is-dim .site-kagane { color: var(--mute); filter: grayscale(.6); } +.is-dim .site-comix, .is-dim .site-kagane, +.is-dim .site-novelfull, .is-dim .site-lightnovelworld { color: var(--mute); filter: grayscale(.6); } + +/* ---- library switch: manga and novels are separate libraries, so the pair + sits in the topbar next to the wordmark rather than among the buckets. ---- */ +.libswitch { + display: flex; + margin-left: auto; + border: 1px solid var(--field-line); +} +.libswitch a { + padding: 7px 13px; + font: 500 10px/1 var(--font-mono); + letter-spacing: .12em; + text-transform: uppercase; + color: var(--mute); + text-decoration: none; +} +.libswitch a + a { border-left: 1px solid var(--field-line); } +.libswitch a:hover { color: var(--paper-dim); } +/* The library you are in carries the ember, the same heat the wordmark and the + Updated tab use — it is the one piece of chrome that has to be unmistakable. */ +.libswitch a.active { + background: var(--ember-wash); + color: var(--ember); + box-shadow: inset 0 -2px 0 var(--ember); +} +.topbar form { margin-left: 18px; } +/* At phone width brand + switch + Log out do not fit on one line, so the + switch takes its own row under the wordmark rather than pushing Log out + off-screen. */ +@media (max-width: 719px) { + .topbar { flex-wrap: wrap; row-gap: 12px; } + .brand { flex: 1 1 auto; min-width: 0; } + .libswitch { order: 3; margin-left: 0; } + .libswitch a { flex: 1; text-align: center; padding: 8px 14px; } + .topbar form { margin-left: 12px; } +} /* ---- action strip: full-width on a phone, hairline-divided cells ---- */ .actions { @@ -714,6 +759,20 @@ button { cursor: pointer; } color: var(--paper); } .login-card h1 em { color: var(--ember); font-style: italic; } +.login-art { + margin: 8px auto 0; + width: 240px; + aspect-ratio: 1; + display: grid; + place-items: center; + background: radial-gradient(circle, var(--ember-wash) 0%, transparent 70%); +} +.login-art img { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 0 34px var(--ember-wash)) drop-shadow(0 18px 24px rgba(0,0,0,.5)); +} .login-card form { display: flex; flex-direction: column; gap: 18px; } .login-card label { font: 500 10px/1 var(--font-mono); @@ -754,12 +813,22 @@ button { cursor: pointer; } color: #fff; } +/* ---- laptop and up: the whole sheet is drawn 20% larger, which is what + reading it at 120% zoom on a 1920-wide screen was doing by hand. Everything + in this file is sized in px, so scaling the root is the one adjustment that + keeps every proportion — hairlines, cover ratios, hit targets — + intact. ---- */ +@media (min-width: 1280px) { + :root { zoom: 1.2; } +} + /* ---- desktop: same measure, actions fold up beside the row ---- */ @media (min-width: 720px) { :root { --cover-w: 80px; --row-gap: 20px; } .topbar { padding: 26px 32px 18px; } .brand { font-size: 30px; } .brand .mark { width: 35px; height: 30px; } + .libswitch a { padding: 9px 16px; font-size: 11px; } .chrome { flex-direction: row; diff --git a/backend/internal/web/templates/login.html b/backend/internal/web/templates/login.html index 1a31e19..781ce81 100644 --- a/backend/internal/web/templates/login.html +++ b/backend/internal/web/templates/login.html @@ -16,6 +16,9 @@ Private library

{{template "mark" .}}BookmarkManager

+
diff --git a/backend/web_test.go b/backend/web_test.go index 82fd594..e6c4550 100644 --- a/backend/web_test.go +++ b/backend/web_test.go @@ -193,7 +193,7 @@ func TestBookmarksAPIStillBearerOnly(t *testing.T) { func TestStaticAssetsServed(t *testing.T) { srv, _ := newWebTestServer(t, webConfig()) - for _, path := range []string{"/static/style.css", "/static/htmx.min.js", "/static/filter.js", "/static/logo.svg"} { + for _, path := range []string{"/static/style.css", "/static/htmx.min.js", "/static/filter.js", "/static/logo.svg", "/static/login-art.png"} { rr := httptest.NewRecorder() srv.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, path, nil)) if rr.Code != http.StatusOK {