rebrand: MangaBM → BookmarkManager, add novel library support #15
@@ -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;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
<span class="eyebrow">Private library</span>
|
||||
<h1 class="brand">{{template "mark" .}}<span>Bookmark<em>Manager</em></span></h1>
|
||||
</div>
|
||||
<figure class="login-art" aria-hidden="true">
|
||||
<img src="/static/login-art.png" alt="">
|
||||
</figure>
|
||||
<form method="post" action="/login">
|
||||
<div>
|
||||
<label for="password">Password</label>
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user