Action key under the tabs, plus the brand mark #12

Merged
sulthan merged 2 commits from ui-improve-v2 into main 2026-07-31 17:14:16 +07:00
8 changed files with 207 additions and 32 deletions
+23
View File
@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 172" role="img" aria-label="mangaBookmark">
<title>mangaBookmark</title>
<g fill="#100f0e" stroke="#f2ece5" stroke-width="5" stroke-linejoin="round" stroke-linecap="round">
<path fill="none" d="M28 36H4v114h192V36h-24"></path>
<path fill="none" d="M28 23H17v127h166V23h-11"></path>
<g id="mb-half">
<path d="M28 7 88 55v97L28 138z"></path>
<g fill="#f2ece5" stroke="none">
<path d="M37 25 55 39v41L37 66z"></path>
<path d="M60 42 79 57v42L60 84z"></path>
<path d="M37 75 79 108v13L37 88z"></path>
<path d="M37 98 79 129v11L37 131z"></path>
</g>
</g>
<use href="#mb-half" transform="matrix(-1 0 0 1 200 0)"></use>
<g stroke="#e0452c">
<path d="M100 4l9 5v11l-9 5-9-5V9z"></path>
<path d="M94 24h12v24H94z"></path>
<path d="M70 47h60v14H70z"></path>
<path d="M91 61h18v87l-9 20-9-20z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 973 B

+88 -18
View File
@@ -81,6 +81,11 @@
--danger-ink: #150808; --danger-ink: #150808;
--danger-soft: #e2aaa1; --danger-soft: #e2aaa1;
--brass: #b8912f; /* favourite — a second, cooler metal */ --brass: #b8912f; /* favourite — a second, cooler metal */
/* One accent per action, so a press says which lane it belongs to. All three
are held at the same weight as --brass: muted, no ember competition. */
--slate: #7fa0c0; /* archive */
--moss: #7fae86; /* finished */
--clay: #b5906f; /* set chapter */
--trash: #977671; /* remove, resting — icons need 3:1, not 4.5:1 */ --trash: #977671; /* remove, resting — icons need 3:1, not 4.5:1 */
/* Desktop cell borders for the two coloured action states. */ /* Desktop cell borders for the two coloured action states. */
@@ -98,7 +103,7 @@
--measure: 760px; --measure: 760px;
/* Cover width + row gap: the disclosure panels indent past the cover on /* Cover width + row gap: the disclosure panels indent past the cover on
desktop, so both live here rather than as magic numbers. */ desktop, so both live here rather than as magic numbers. */
--cover-w: 86px; --cover-w: 93px;
--row-gap: 14px; --row-gap: 14px;
} }
@@ -131,6 +136,9 @@
--danger-ink: #fff; --danger-ink: #fff;
--danger-soft: #7c2c22; --danger-soft: #7c2c22;
--brass: #8a681c; --brass: #8a681c;
--slate: #3f6689;
--moss: #3d6c46;
--clay: #7c5533;
--trash: #8c6558; --trash: #8c6558;
--play-hot-line: #f0cfc6; --play-hot-line: #f0cfc6;
@@ -189,14 +197,21 @@ button { cursor: pointer; }
/* ---- brand + chrome ---- */ /* ---- brand + chrome ---- */
.brand { .brand {
margin: 0; margin: 0;
display: flex;
align-items: center;
gap: 10px;
font: 400 26px/1 var(--font-display); font: 400 26px/1 var(--font-display);
color: var(--paper); color: var(--paper);
} }
.brand em { color: var(--ember); font-style: italic; } .brand em { color: var(--ember); font-style: italic; }
.brand .mark { width: 30px; height: 26px; flex: none; overflow: visible; }
/* The line art is drawn at a 5px stroke on a 200-unit grid; at brand size that
thins out, so it is nudged up rather than scaled down blindly. */
.brand .mark g { stroke-width: 6.5; }
.topbar { .topbar {
display: flex; display: flex;
align-items: baseline; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 16px; gap: 16px;
padding: 22px 20px 14px; padding: 22px 20px 14px;
@@ -285,6 +300,35 @@ button { cursor: pointer; }
border: 1px solid currentColor; border: 1px solid currentColor;
} }
/* ---- action key: one permanent line under the tabs, so the icon strip below
never has to be guessed at. Lean on a phone (28px, edge to edge), a step
bigger on desktop where there is room to read it. ---- */
.keyrow {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
padding: 9px 18px 10px;
border-bottom: 1px solid var(--rule);
}
/* On a phone each key stacks: icon over word, so the word gets the full cell
width and can stay the long form. */
.keyrow .pair {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
color: var(--mute);
}
.keyrow .pair svg { width: 14px; height: 14px; flex: none; }
.keyrow .pair span {
font: 500 10px/1 var(--font-mono);
letter-spacing: .04em;
text-transform: uppercase;
}
.keyrow .pair.brass svg { color: var(--brass); }
.keyrow .pair.trash svg { color: var(--trash); }
/* ---- continue reading ---- */ /* ---- continue reading ---- */
.recent { .recent {
display: flex; display: flex;
@@ -311,7 +355,7 @@ button { cursor: pointer; }
} }
.recent-strip::-webkit-scrollbar { display: none; } .recent-strip::-webkit-scrollbar { display: none; }
.recent-card { .recent-card {
width: 92px; width: var(--cover-w);
flex: none; flex: none;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -319,8 +363,8 @@ button { cursor: pointer; }
} }
.recent-cover { .recent-cover {
position: relative; position: relative;
width: 92px; width: var(--cover-w);
height: 123px; height: calc(var(--cover-w) * 4 / 3);
background: var(--hatch); background: var(--hatch);
display: grid; display: grid;
place-items: center; place-items: center;
@@ -328,7 +372,7 @@ button { cursor: pointer; }
} }
.recent-cover img { width: 100%; height: 100%; object-fit: cover; } .recent-cover img { width: 100%; height: 100%; object-fit: cover; }
.recent-title { .recent-title {
font: 400 15px/1.2 var(--font-display); font: 400 16px/1.25 var(--font-display);
color: var(--paper-dim); color: var(--paper-dim);
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
@@ -336,7 +380,7 @@ button { cursor: pointer; }
overflow: hidden; overflow: hidden;
} }
.recent-chapter { .recent-chapter {
font: 500 10px/1 var(--font-mono); font: 500 11px/1 var(--font-mono);
letter-spacing: .1em; letter-spacing: .1em;
color: var(--mute-2); color: var(--mute-2);
} }
@@ -374,7 +418,7 @@ button { cursor: pointer; }
} }
.card.is-dim { background: var(--dim); } .card.is-dim { background: var(--dim); }
.row { display: flex; flex-wrap: wrap; gap: var(--row-gap); } .row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--row-gap); }
.cover { .cover {
position: relative; position: relative;
@@ -387,7 +431,7 @@ button { cursor: pointer; }
overflow: hidden; overflow: hidden;
} }
.cover img { width: 100%; height: 100%; object-fit: cover; } .cover img { width: 100%; height: 100%; object-fit: cover; }
.cover .monogram { font-size: 30px; } .cover .monogram { font-size: 32px; }
.is-dim .cover { background: var(--hatch-dim); filter: grayscale(1); opacity: .85; } .is-dim .cover { background: var(--hatch-dim); filter: grayscale(1); opacity: .85; }
.body { .body {
@@ -395,6 +439,7 @@ button { cursor: pointer; }
min-width: 0; min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
gap: 8px; gap: 8px;
} }
@@ -403,7 +448,7 @@ button { cursor: pointer; }
margin: 0; margin: 0;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
font: 400 19px/1.2 var(--font-display); font: 400 21px/1.2 var(--font-display);
color: var(--paper-dim); color: var(--paper-dim);
} }
/* Heat: crimson title over an ember hairline sized to the text, not the row. */ /* Heat: crimson title over an ember hairline sized to the text, not the row. */
@@ -416,7 +461,9 @@ button { cursor: pointer; }
} }
.is-dim .title { font-style: italic; color: var(--mute); } .is-dim .title { font-style: italic; color: var(--mute); }
.is-dim .fav-mark { color: var(--mute-2); } .is-dim .fav-mark { color: var(--mute-2); }
.fav-mark { flex: none; width: 13px; height: 13px; margin-top: 5px; color: var(--brass); } /* The mark always sits at the right edge of the measure, not after the last
word — a new-chapter title shrink-wraps, so without this it drifts. */
.fav-mark { flex: none; margin-left: auto; width: 14px; height: 14px; margin-top: 5px; color: var(--brass); }
.meta { .meta {
margin: 0; margin: 0;
@@ -424,7 +471,7 @@ button { cursor: pointer; }
align-items: center; align-items: center;
gap: 9px; gap: 9px;
flex-wrap: wrap; flex-wrap: wrap;
font: 500 10px/1 var(--font-mono); font: 500 11px/1 var(--font-mono);
letter-spacing: .12em; letter-spacing: .12em;
text-transform: uppercase; text-transform: uppercase;
color: var(--mute); color: var(--mute);
@@ -434,7 +481,7 @@ button { cursor: pointer; }
.site-demonic { color: var(--demonic); } .site-demonic { color: var(--demonic); }
.new-chapter { color: var(--ember); } .new-chapter { color: var(--ember); }
.state { display: flex; align-items: center; gap: 4px; color: var(--mute); } .state { display: flex; align-items: center; gap: 4px; color: var(--mute); }
.state svg { width: 9px; height: 9px; } .state svg { width: 10px; height: 10px; }
.is-dim .meta { color: var(--mute-2); } .is-dim .meta { color: var(--mute-2); }
.is-dim .site-asura, .is-dim .site-demonic { color: var(--mute); filter: grayscale(.6); } .is-dim .site-asura, .is-dim .site-demonic { color: var(--mute); filter: grayscale(.6); }
@@ -459,6 +506,12 @@ button { cursor: pointer; }
.actions > *:last-child { border-right: none; } .actions > *:last-child { border-right: none; }
.actions svg { width: 17px; height: 17px; } .actions svg { width: 17px; height: 17px; }
.actions > *:hover { color: var(--paper); } .actions > *:hover { color: var(--paper); }
/* Per-action accent on hover and press: gold favourite, slate archive, moss
finished, clay chapter. Remove keeps --danger, play keeps paper/ember. */
.actions .fav:hover, .actions .fav:active, .actions .fav:focus-visible { color: var(--brass); }
.actions .pencil:hover, .actions .pencil:active, .actions .pencil:focus-visible { color: var(--clay); }
.actions .box:hover, .actions .box:active, .actions .box:focus-visible { color: var(--slate); }
.actions .finish:hover, .actions .finish:active, .actions .finish:focus-visible { color: var(--moss); }
.actions .play { color: var(--paper); } .actions .play { color: var(--paper); }
.is-new .actions .play { color: var(--ember); } .is-new .actions .play { color: var(--ember); }
.actions .play:hover { background: var(--hover); } .actions .play:hover { background: var(--hover); }
@@ -696,9 +749,10 @@ button { cursor: pointer; }
/* ---- desktop: same measure, actions fold up beside the row ---- */ /* ---- desktop: same measure, actions fold up beside the row ---- */
@media (min-width: 720px) { @media (min-width: 720px) {
:root { --cover-w: 74px; --row-gap: 20px; } :root { --cover-w: 80px; --row-gap: 20px; }
.topbar { padding: 26px 32px 18px; } .topbar { padding: 26px 32px 18px; }
.brand { font-size: 30px; } .brand { font-size: 30px; }
.brand .mark { width: 35px; height: 30px; }
.chrome { .chrome {
flex-direction: row; flex-direction: row;
@@ -711,13 +765,25 @@ button { cursor: pointer; }
.tabs a { padding: 10px 0 14px; font-size: 18px; } .tabs a { padding: 10px 0 14px; font-size: 18px; }
.searchbar { order: 2; flex: 1; margin: 0; border-bottom: none; } .searchbar { order: 2; flex: 1; margin: 0; border-bottom: none; }
.recent h2, .recent-strip { padding-left: 32px; padding-right: 32px; } .recent h2, .recent-strip { padding-left: 32px; padding-right: 32px; }
.recent-card, .recent-cover { width: 100px; } .keyrow {
.recent-cover { height: 133px; } align-items: center;
justify-content: flex-start;
gap: 26px;
height: 36px;
padding: 0 32px;
}
.keyrow .pair { flex-direction: row; gap: 7px; }
.keyrow .pair svg { width: 14px; height: 14px; }
.keyrow .pair span { font-size: 11px; letter-spacing: .1em; }
.keyrow .full { display: inline; }
.recent-card, .recent-cover { width: var(--cover-w); }
.recent-cover { height: calc(var(--cover-w) * 4 / 3); }
.recent-title { font-size: 17px; }
.card { padding: 18px 32px; } .card { padding: 18px 32px; }
.row { flex-wrap: nowrap; align-items: center; } .row { flex-wrap: nowrap; align-items: center; }
.cover .monogram { font-size: 26px; } .cover .monogram { font-size: 28px; }
.title { font-size: 21px; } .title { font-size: 22px; }
.actions { flex: none; gap: 4px; border-top: none; } .actions { flex: none; gap: 4px; border-top: none; }
.actions > * { .actions > * {
@@ -732,6 +798,10 @@ button { cursor: pointer; }
.actions > *:not(.lifecycle) + .lifecycle { margin-left: 10px; box-shadow: none; } .actions > *:not(.lifecycle) + .lifecycle { margin-left: 10px; box-shadow: none; }
.is-new .actions .play { border-color: var(--play-hot-line); } .is-new .actions .play { border-color: var(--play-hot-line); }
.actions .on { border-color: var(--fav-line); } .actions .on { border-color: var(--fav-line); }
/* The cell border follows the icon on hover, so the accent reads as a state
rather than a stray colour. */
.actions .fav:hover, .actions .pencil:hover,
.actions .box:hover, .actions .finish:hover { border-color: currentColor; }
/* Panels line up with the body text, i.e. past the cover and its gap. */ /* Panels line up with the body text, i.e. past the cover and its gap. */
.chapter-form, .confirm-row, .error-inline { .chapter-form, .confirm-row, .error-inline {
+4 -1
View File
@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="color-scheme" content="dark light"> <meta name="color-scheme" content="dark light">
<title>mangaBookmark</title> <title>mangaBookmark</title>
<link rel="icon" href="/static/logo.svg" type="image/svg+xml">
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
<link rel="preload" href="/static/fonts/instrument-serif-400-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/static/fonts/instrument-serif-400-latin.woff2" as="font" type="font/woff2" crossorigin>
{{/* Body text before meta lines: DM Sans is the biggest face and the one {{/* Body text before meta lines: DM Sans is the biggest face and the one
@@ -18,7 +19,7 @@
{{template "icons" .}} {{template "icons" .}}
<div class="sheet"> <div class="sheet">
<header class="topbar"> <header class="topbar">
<h1 class="brand">manga<em>Bookmark</em></h1> <h1 class="brand">{{template "mark" .}}<span>manga<em>Bookmark</em></span></h1>
<form method="post" action="/logout"> <form method="post" action="/logout">
<button type="submit" class="ghost">Log out</button> <button type="submit" class="ghost">Log out</button>
</form> </form>
@@ -61,6 +62,8 @@
</nav> </nav>
</div> </div>
{{template "keyrow" .}}
{{template "recent" .}} {{template "recent" .}}
<main id="list" class="list"> <main id="list" class="list">
+53 -5
View File
@@ -1,9 +1,9 @@
{{/* The two regions that live outside the swapped #list: the "Continue {{/* The regions that live outside the swapped #list: the "Continue reading"
reading" strip and the Updated badge. Both are rendered inline by app.html strip, the Updated badge and the action key. All are rendered inline by
and again, out of band, on every /ui/ response — a mutation must not leave app.html and again, out of band, on every /ui/ response — a mutation must
them describing the library as it was before the tap. not leave them describing the library as it was before the tap.
Both always render, hidden when they have nothing to say, so an out-of-band All always render, hidden when they have nothing to say, so an out-of-band
swap always has an element with the right id to replace. */}} swap always has an element with the right id to replace. */}}
{{define "recent"}} {{define "recent"}}
@@ -27,4 +27,52 @@
</section> </section>
{{end}} {{end}}
{{/* The action key. The icon strip on a card is unlabelled, so one permanent
line under the tabs names every glyph. It follows the tab rather than the
row: the archived and finished buckets swap Archive for Restore, and a
finished series has no Done to offer. */}}
{{define "keyrow"}}
<div class="keyrow" id="keyrow" aria-label="Action key"{{if .OOB}} hx-swap-oob="true"{{end}}>
<span class="pair"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#i-play"/></svg><span>Read</span></span>
<span class="pair brass"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#i-star"/></svg><span>Fav</span></span>
<span class="pair"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#i-pencil"/></svg><span>Chapter</span></span>
{{if or (eq .Tab "archived") (eq .Tab "finished")}}
<span class="pair"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#i-undo"/></svg><span>Restore</span></span>
{{else}}
<span class="pair"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#i-box"/></svg><span>Archive</span></span>
{{end}}
{{if ne .Tab "finished"}}
<span class="pair"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#i-check"/></svg><span>Done</span></span>
{{end}}
<span class="pair trash"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#i-trash"/></svg><span>Delete</span></span>
</div>
{{end}}
{{/* The brand mark, inline so it takes the page's ink and ember rather than the
fixed palette of /static/logo.svg (which the favicon needs). */}}
{{define "mark"}}
<svg class="mark" viewBox="0 0 200 172" aria-hidden="true">
<g fill="var(--ink)" stroke="currentColor" stroke-width="5" stroke-linejoin="round" stroke-linecap="round">
<path fill="none" d="M28 36H4v114h192V36h-24"></path>
<path fill="none" d="M28 23H17v127h166V23h-11"></path>
<g id="mb-half">
<path d="M28 7 88 55v97L28 138z"></path>
<g fill="currentColor" stroke="none">
<path d="M37 25 55 39v41L37 66z"></path>
<path d="M60 42 79 57v42L60 84z"></path>
<path d="M37 75 79 108v13L37 88z"></path>
<path d="M37 98 79 129v11L37 131z"></path>
</g>
</g>
<use href="#mb-half" transform="matrix(-1 0 0 1 200 0)"></use>
<g stroke="var(--ember)">
<path d="M100 4l9 5v11l-9 5-9-5V9z"></path>
<path d="M94 24h12v24H94z"></path>
<path d="M70 47h60v14H70z"></path>
<path d="M91 61h18v87l-9 20-9-20z"></path>
</g>
</g>
</svg>
{{end}}
{{define "newcount"}}<span class="count" id="new-count"{{if .OOB}} hx-swap-oob="true"{{end}}{{if not .NewCount}} hidden{{end}}>{{.NewCount}}</span>{{end}} {{define "newcount"}}<span class="count" id="new-count"{{if .OOB}} hx-swap-oob="true"{{end}}{{if not .NewCount}} hidden{{end}}>{{.NewCount}}</span>{{end}}
+2 -1
View File
@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="color-scheme" content="dark light"> <meta name="color-scheme" content="dark light">
<title>mangaBookmark</title> <title>mangaBookmark</title>
<link rel="icon" href="/static/logo.svg" type="image/svg+xml">
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
<link rel="preload" href="/static/fonts/instrument-serif-400-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/static/fonts/instrument-serif-400-latin.woff2" as="font" type="font/woff2" crossorigin>
</head> </head>
@@ -13,7 +14,7 @@
<main class="login-card"> <main class="login-card">
<div> <div>
<span class="eyebrow">Private library</span> <span class="eyebrow">Private library</span>
<h1>manga<em>Bookmark</em></h1> <h1 class="brand">{{template "mark" .}}<span>manga<em>Bookmark</em></span></h1>
</div> </div>
<form method="post" action="/login"> <form method="post" action="/login">
<div> <div>
+6 -4
View File
@@ -248,12 +248,14 @@ func currentTab(r *http.Request) string {
return u.Query().Get("tab") return u.Query().Get("tab")
} }
// writeChromeOOB appends the two regions that live outside #list — the recent // writeChromeOOB appends the regions that live outside #list — the recent
// strip and the Updated badge — as out-of-band swaps, so a mutation cannot // strip, the Updated badge and the action key — as out-of-band swaps, so a
// leave them describing the library as it was before the tap. // mutation cannot leave them describing the library as it was before the tap.
// The key is in here because it is tab-shaped too: archived and finished swap
// Archive for Restore.
func (h *webHandler) writeChromeOOB(w http.ResponseWriter, view listView) { func (h *webHandler) writeChromeOOB(w http.ResponseWriter, view listView) {
view.OOB = true view.OOB = true
for _, name := range []string{"recent", "newcount"} { for _, name := range []string{"recent", "newcount", "keyrow"} {
if err := h.tmpl.ExecuteTemplate(w, name, view); err != nil { if err := h.tmpl.ExecuteTemplate(w, name, view); err != nil {
// The card is already written; stale chrome beats a torn response. // The card is already written; stale chrome beats a torn response.
log.Printf("render %s oob: %v", name, err) log.Printf("render %s oob: %v", name, err)
+1 -1
View File
@@ -189,7 +189,7 @@ func TestBookmarksAPIStillBearerOnly(t *testing.T) {
func TestStaticAssetsServed(t *testing.T) { func TestStaticAssetsServed(t *testing.T) {
srv, _ := newWebTestServer(t, webConfig()) srv, _ := newWebTestServer(t, webConfig())
for _, path := range []string{"/static/style.css", "/static/htmx.min.js", "/static/filter.js"} { for _, path := range []string{"/static/style.css", "/static/htmx.min.js", "/static/filter.js", "/static/logo.svg"} {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
srv.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, path, nil)) srv.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, path, nil))
if rr.Code != http.StatusOK { if rr.Code != http.StatusOK {
+30 -2
View File
@@ -1383,7 +1383,31 @@
<div id="backdrop"></div> <div id="backdrop"></div>
<aside id="panel" role="dialog" aria-label="Manga bookmarks"> <aside id="panel" role="dialog" aria-label="Manga bookmarks">
<header> <header>
<span>manga<em>Bookmark</em></span> <span class="brand">
<svg class="mark" viewBox="0 0 200 172" aria-hidden="true">
<g fill="var(--ink)" stroke="currentColor" stroke-width="6.5" stroke-linejoin="round" stroke-linecap="round">
<path fill="none" d="M28 36H4v114h192V36h-24"></path>
<path fill="none" d="M28 23H17v127h166V23h-11"></path>
<g id="mb-half">
<path d="M28 7 88 55v97L28 138z"></path>
<g fill="currentColor" stroke="none">
<path d="M37 25 55 39v41L37 66z"></path>
<path d="M60 42 79 57v42L60 84z"></path>
<path d="M37 75 79 108v13L37 88z"></path>
<path d="M37 98 79 129v11L37 131z"></path>
</g>
</g>
<use href="#mb-half" transform="matrix(-1 0 0 1 200 0)"></use>
<g stroke="var(--ember)">
<path d="M100 4l9 5v11l-9 5-9-5V9z"></path>
<path d="M94 24h12v24H94z"></path>
<path d="M70 47h60v14H70z"></path>
<path d="M91 61h18v87l-9 20-9-20z"></path>
</g>
</g>
</svg>
<span>manga<em>Bookmark</em></span>
</span>
<button id="closeBtn" aria-label="Close">close</button> <button id="closeBtn" aria-label="Close">close</button>
</header> </header>
<div id="nav"> <div id="nav">
@@ -1458,11 +1482,15 @@
} }
#panel.open { transform: translateX(0); } #panel.open { transform: translateX(0); }
header { header {
display: flex; align-items: baseline; justify-content: space-between; display: flex; align-items: center; justify-content: space-between;
padding: 16px; border-bottom: 1px solid var(--rule); padding: 16px; border-bottom: 1px solid var(--rule);
font: 400 22px/1 var(--font-display); color: var(--paper); font: 400 22px/1 var(--font-display); color: var(--paper);
} }
header em { color: var(--ember); font-style: italic; } header em { color: var(--ember); font-style: italic; }
.brand { display: flex; align-items: center; gap: 9px; }
/* 5px stroke on a 200-unit grid thins out at this size: the markup carries
6.5 instead. overflow visible keeps the topmost blade pip from clipping. */
.mark { width: 26px; height: 22px; flex: none; overflow: visible; }
#closeBtn { #closeBtn {
background: none; border: none; color: var(--mute); cursor: pointer; background: none; border: none; color: var(--mute); cursor: pointer;
font: 500 10px var(--font-mono); letter-spacing: .12em; text-transform: uppercase; font: 500 10px var(--font-mono); letter-spacing: .12em; text-transform: uppercase;