Rebuild both UIs on the Cinder design #10
@@ -1173,7 +1173,10 @@
|
|||||||
// Cover and title are the only continue links. The subtitle and the action
|
// Cover and title are the only continue links. The subtitle and the action
|
||||||
// row live in the same text column but are not navigation targets — a thumb
|
// row live in the same text column but are not navigation targets — a thumb
|
||||||
// that misses ★ must not land on one, and Remove must never be one.
|
// that misses ★ must not land on one, and Remove must never be one.
|
||||||
return el("div", { class: "item" }, [
|
// hot = a chapter is out that has not been read (crimson title, ember rule);
|
||||||
|
// dim = archived (italic grey). Same two states the web UI paints.
|
||||||
|
const heat = (behind ? " hot" : "") + (statusOf(b) === "archived" ? " dim" : "");
|
||||||
|
return el("div", { class: "item" + heat }, [
|
||||||
el("a", { class: "go", href: cont }, [
|
el("a", { class: "go", href: cont }, [
|
||||||
b.cover
|
b.cover
|
||||||
? el("img", { class: "cover", src: b.cover, loading: "lazy", alt: "" })
|
? el("img", { class: "cover", src: b.cover, loading: "lazy", alt: "" })
|
||||||
@@ -1380,11 +1383,11 @@
|
|||||||
<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 Bookmarks</span>
|
<span>manga<em>Bookmark</em></span>
|
||||||
<button id="closeBtn" aria-label="Close">✕</button>
|
<button id="closeBtn" aria-label="Close">close</button>
|
||||||
</header>
|
</header>
|
||||||
<div id="nav">
|
<div id="nav">
|
||||||
<a class="chip" href="${WEB_BASE}" target="_blank" rel="noopener">🌐 Web</a>
|
<a class="chip" href="${WEB_BASE}" target="_blank" rel="noopener">Web</a>
|
||||||
<a class="chip" href="https://asurascans.com" target="_blank" rel="noopener">Asura</a>
|
<a class="chip" href="https://asurascans.com" target="_blank" rel="noopener">Asura</a>
|
||||||
<a class="chip" href="https://demonicscans.org" target="_blank" rel="noopener">Demonic</a>
|
<a class="chip" href="https://demonicscans.org" target="_blank" rel="noopener">Demonic</a>
|
||||||
<button id="pending" class="chip pending" hidden>⟳ 0 pending</button>
|
<button id="pending" class="chip pending" hidden>⟳ 0 pending</button>
|
||||||
@@ -1392,7 +1395,7 @@
|
|||||||
<section id="context"></section>
|
<section id="context"></section>
|
||||||
<div id="tabs" role="tablist">
|
<div id="tabs" role="tablist">
|
||||||
<button id="tabAll" class="tab active" role="tab">All</button>
|
<button id="tabAll" class="tab active" role="tab">All</button>
|
||||||
<button id="tabFav" class="tab" role="tab">★ Favourites</button>
|
<button id="tabFav" class="tab" role="tab">Favourites</button>
|
||||||
<button id="tabArc" class="tab" role="tab">Archived</button>
|
<button id="tabArc" class="tab" role="tab">Archived</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="list"></div>
|
<div id="list"></div>
|
||||||
@@ -1401,22 +1404,35 @@
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const CSS = `
|
const CSS = `
|
||||||
:host { all: initial; }
|
/* Cinder, ported to the panel: same tokens and the same rule that heat is
|
||||||
* { box-sizing: border-box; font-family: system-ui, -apple-system, sans-serif; }
|
typographic. Fonts are system stacks, not the web UI's Google fonts — an
|
||||||
|
@import here would be at the mercy of the host page's CSP.
|
||||||
|
See docs/design-system.md. */
|
||||||
|
:host {
|
||||||
|
all: initial;
|
||||||
|
--ink: #100f0e; --ash: #161413; --dim: #0d0c0b;
|
||||||
|
--rule: #221f1d; --field-line: #2c2926; --hover: #1a1816;
|
||||||
|
--paper: #f2ece5; --paper-hot: #f0d3cb; --paper-dim: #ddd5cb;
|
||||||
|
--mute: #8d857c; --mute-2: #5a5450; --faint: #3a3733; --faint-2: #57504b;
|
||||||
|
--ember: #e0452c; --ember-wash: #1a1211; --ember-ink: #150907;
|
||||||
|
--ember-soft: #eda798; --brass: #b8912f; --trash: #6b5450;
|
||||||
|
--font-display: Georgia, "Times New Roman", serif;
|
||||||
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
--font-body: system-ui, -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; font-family: var(--font-body); }
|
||||||
#fab {
|
#fab {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 7px; height: 44px; border: none; padding: 0;
|
width: 7px; height: 44px; border: none; padding: 0;
|
||||||
border-radius: 4px 0 0 4px;
|
background: var(--ember); opacity: .5;
|
||||||
background: #6d28d9; opacity: .45;
|
box-shadow: 0 2px 8px rgba(0,0,0,.5); z-index: 2147483646; cursor: pointer;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,.4); z-index: 2147483646; cursor: pointer;
|
|
||||||
touch-action: none; user-select: none; -webkit-touch-callout: none;
|
touch-action: none; user-select: none; -webkit-touch-callout: none;
|
||||||
}
|
}
|
||||||
#fab.left { border-radius: 0 4px 4px 0; }
|
|
||||||
#fab:active, #fab.armed { opacity: 1; }
|
#fab:active, #fab.armed { opacity: 1; }
|
||||||
#fab.armed { box-shadow: 0 0 0 2px #c4b5fd; }
|
#fab.armed { box-shadow: 0 0 0 2px var(--ember-soft); }
|
||||||
#fill {
|
#fill {
|
||||||
position: absolute; left: 0; right: 0; bottom: 0; height: 0;
|
position: absolute; left: 0; right: 0; bottom: 0; height: 0;
|
||||||
background: #c4b5fd; pointer-events: none; border-radius: inherit;
|
background: var(--paper); pointer-events: none;
|
||||||
}
|
}
|
||||||
/* Invisible tap extension: the visible sliver is 7x44, this makes it 28x72.
|
/* Invisible tap extension: the visible sliver is 7x44, this makes it 28x72.
|
||||||
It grows inward (toward the screen centre) because the tab is docked flush
|
It grows inward (toward the screen centre) because the tab is docked flush
|
||||||
@@ -1427,90 +1443,132 @@
|
|||||||
}
|
}
|
||||||
#fab.left #hit { left: 0; right: -21px; }
|
#fab.left #hit { left: 0; right: -21px; }
|
||||||
#backdrop {
|
#backdrop {
|
||||||
position: fixed; inset: 0; background: rgba(0,0,0,.5);
|
position: fixed; inset: 0; background: rgba(0,0,0,.6);
|
||||||
opacity: 0; pointer-events: none; transition: opacity .2s; z-index: 2147483646;
|
opacity: 0; pointer-events: none; transition: opacity .2s; z-index: 2147483646;
|
||||||
}
|
}
|
||||||
#backdrop.open { opacity: 1; pointer-events: auto; }
|
#backdrop.open { opacity: 1; pointer-events: auto; }
|
||||||
#panel {
|
#panel {
|
||||||
position: fixed; top: 0; right: 0; height: 100%;
|
position: fixed; top: 0; right: 0; height: 100%;
|
||||||
width: min(400px, 92vw); background: #1b1b22; color: #eee;
|
width: min(400px, 92vw); background: var(--ink); color: var(--paper-dim);
|
||||||
transform: translateX(105%); transition: transform .25s ease;
|
transform: translateX(105%); transition: transform .25s ease;
|
||||||
z-index: 2147483647; display: flex; flex-direction: column;
|
z-index: 2147483647; display: flex; flex-direction: column;
|
||||||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||||||
box-shadow: -4px 0 20px rgba(0,0,0,.5);
|
border-left: 1px solid var(--rule);
|
||||||
|
box-shadow: -4px 0 20px rgba(0,0,0,.6);
|
||||||
}
|
}
|
||||||
#panel.open { transform: translateX(0); }
|
#panel.open { transform: translateX(0); }
|
||||||
header {
|
header {
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
display: flex; align-items: baseline; justify-content: space-between;
|
||||||
padding: 14px 16px; border-bottom: 1px solid #33333d; font-weight: 600; font-size: 16px;
|
padding: 16px; border-bottom: 1px solid var(--rule);
|
||||||
|
font: 400 22px/1 var(--font-display); color: var(--paper);
|
||||||
}
|
}
|
||||||
#closeBtn { background: none; border: none; color: #aaa; font-size: 18px; cursor: pointer; }
|
header em { color: var(--ember); font-style: italic; }
|
||||||
#nav { display: flex; gap: 8px; padding: 10px 16px; border-bottom: 1px solid #33333d; }
|
#closeBtn {
|
||||||
|
background: none; border: none; color: var(--mute); cursor: pointer;
|
||||||
|
font: 500 10px var(--font-mono); letter-spacing: .12em; text-transform: uppercase;
|
||||||
|
border-bottom: 1px solid var(--field-line); padding: 0 0 2px;
|
||||||
|
}
|
||||||
|
#closeBtn:active { color: var(--paper); }
|
||||||
|
#nav { display: flex; gap: 14px; padding: 11px 16px; border-bottom: 1px solid var(--rule); }
|
||||||
|
/* Chips are set as small caps rules, not pills — the sheet has no corners. */
|
||||||
.chip {
|
.chip {
|
||||||
background: #2a2a33; color: #c4b5fd; text-decoration: none;
|
background: none; border: none; padding: 0 0 2px;
|
||||||
padding: 6px 11px; border-radius: 999px; font-size: 12px; font-weight: 600;
|
border-bottom: 1px solid var(--field-line);
|
||||||
|
color: var(--mute); text-decoration: none; cursor: pointer;
|
||||||
|
font: 500 10px var(--font-mono); letter-spacing: .12em; text-transform: uppercase;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.chip:active { opacity: .8; }
|
.chip:active { color: var(--paper); border-bottom-color: var(--paper); }
|
||||||
.chip.pending { background: #4c1d95; border: none; cursor: pointer; }
|
.chip.pending { color: var(--ember); border-bottom-color: var(--ember); }
|
||||||
.chip.pending[hidden] { display: none; }
|
.chip.pending[hidden] { display: none; }
|
||||||
#context {
|
#context {
|
||||||
padding: 12px 16px; border-bottom: 1px solid #33333d;
|
padding: 14px 16px; border-bottom: 1px solid var(--rule);
|
||||||
display: flex; flex-direction: column; gap: 8px;
|
display: flex; flex-direction: column; gap: 9px; align-items: flex-start;
|
||||||
}
|
}
|
||||||
.ctx-title { font-weight: 600; font-size: 15px; }
|
.ctx-title { font: 400 19px/1.2 var(--font-display); color: var(--paper); }
|
||||||
.ctx-sub { color: #9a9aa5; font-size: 13px; }
|
.ctx-sub {
|
||||||
#tabs { display: flex; gap: 14px; padding: 10px 16px 0; border-bottom: 1px solid #33333d; }
|
font: 500 10px/1.4 var(--font-mono); letter-spacing: .12em;
|
||||||
|
text-transform: uppercase; color: var(--mute);
|
||||||
|
}
|
||||||
|
#tabs { display: flex; gap: 16px; padding: 2px 16px 0; border-bottom: 1px solid var(--rule); }
|
||||||
.tab {
|
.tab {
|
||||||
background: none; border: none; color: #9a9aa5; cursor: pointer;
|
background: none; border: none; cursor: pointer; color: var(--mute);
|
||||||
font-size: 13px; font-weight: 600; padding: 4px 0 9px;
|
font: 400 16px var(--font-display); padding: 8px 0 12px;
|
||||||
border-bottom: 2px solid transparent; margin-bottom: -1px;
|
border-bottom: 2px solid transparent; margin-bottom: -1px;
|
||||||
}
|
}
|
||||||
.tab.active { color: #eee; border-bottom-color: #6d28d9; }
|
.tab.active { color: var(--paper); border-bottom-color: var(--paper); }
|
||||||
#list { overflow-y: auto; flex: 1; padding: 8px 0; }
|
#list { overflow-y: auto; flex: 1; }
|
||||||
.empty { color: #9a9aa5; text-align: center; padding: 30px 16px; font-size: 14px; }
|
.empty {
|
||||||
|
color: var(--mute); padding: 34px 16px;
|
||||||
|
font: 400 15px/1.6 var(--font-body);
|
||||||
|
}
|
||||||
|
/* Loading is the web UI's burning hairline, laid flat across the list. */
|
||||||
.spinner {
|
.spinner {
|
||||||
width: 26px; height: 26px; margin: 0 auto;
|
width: 100%; height: 1px; background: var(--rule); overflow: hidden; position: relative;
|
||||||
border: 3px solid #33333d; border-top-color: #6d28d9; border-radius: 50%;
|
|
||||||
animation: spin .8s linear infinite;
|
|
||||||
}
|
}
|
||||||
@keyframes spin { to { transform: rotate(360deg); } }
|
.spinner::after {
|
||||||
|
content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 30%;
|
||||||
|
background: var(--ember); animation: barSlide 1.15s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes barSlide { from { transform: translateX(-100%) } to { transform: translateX(320%) } }
|
||||||
.item {
|
.item {
|
||||||
display: flex; flex-direction: row; gap: 10px;
|
display: flex; flex-direction: row; gap: 12px;
|
||||||
padding: 10px 16px; border-bottom: 1px solid #2a2a33;
|
padding: 14px 16px; border-bottom: 1px solid var(--rule);
|
||||||
}
|
}
|
||||||
|
.item.dim { background: var(--dim); }
|
||||||
.go { display: block; flex: none; }
|
.go { display: block; flex: none; }
|
||||||
.go:active, .go-t:active { opacity: .7; }
|
.go:active, .go-t:active { opacity: .7; }
|
||||||
.go-t { display: block; text-decoration: none; color: inherit; }
|
.go-t { display: block; text-decoration: none; color: inherit; }
|
||||||
.cover { width: 46px; height: 62px; object-fit: cover; border-radius: 4px; flex: none; background: #333; }
|
.cover {
|
||||||
.cover.ph { display: block; }
|
width: 52px; height: 70px; object-fit: cover; flex: none;
|
||||||
.meta { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 6px; }
|
background: repeating-linear-gradient(135deg, #211d1b 0 5px, #191614 5px 10px);
|
||||||
.t { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
||||||
.c { color: #9a9aa5; font-size: 12px; margin: 0; }
|
|
||||||
.c.behind { color: #c4b5fd; }
|
|
||||||
.actions { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
||||||
.btn {
|
|
||||||
background: #33333d; color: #eee; border: none; border-radius: 6px;
|
|
||||||
padding: 8px 12px; font-size: 13px; cursor: pointer; text-decoration: none; display: inline-block;
|
|
||||||
}
|
}
|
||||||
.btn:active { opacity: .8; }
|
.cover.ph { display: block; }
|
||||||
.btn.primary { background: #6d28d9; color: #fff; }
|
.item.dim .cover { filter: grayscale(1); opacity: .85; }
|
||||||
.btn.small { padding: 6px 10px; font-size: 12px; }
|
.meta { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 7px; align-items: flex-start; }
|
||||||
.btn.danger { background: #7f1d1d; }
|
.t {
|
||||||
.btn.star { line-height: 1; }
|
font: 400 17px/1.2 var(--font-display); color: var(--paper-dim);
|
||||||
.btn.star.on { background: #6d28d9; color: #fff; }
|
max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.item.hot .t { color: var(--paper-hot); border-bottom: 1px solid var(--ember); padding-bottom: 3px; }
|
||||||
|
.item.dim .t { font-style: italic; color: var(--mute); }
|
||||||
|
.c {
|
||||||
|
margin: 0; color: var(--mute);
|
||||||
|
font: 500 10px/1.4 var(--font-mono); letter-spacing: .12em; text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.c.behind { color: var(--ember); }
|
||||||
|
.actions { display: flex; gap: 0; flex-wrap: wrap; margin-top: 2px; }
|
||||||
|
.btn {
|
||||||
|
background: none; color: var(--mute); border: 1px solid var(--rule);
|
||||||
|
margin: 0 -1px -1px 0; padding: 0 12px; height: 38px; line-height: 36px;
|
||||||
|
cursor: pointer; text-decoration: none; display: inline-block;
|
||||||
|
font: 500 10px var(--font-mono); letter-spacing: .12em; text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.btn:active { color: var(--paper); background: var(--hover); }
|
||||||
|
.btn.primary {
|
||||||
|
border-color: var(--paper); background: var(--paper); color: var(--ink);
|
||||||
|
font: 400 16px var(--font-display); letter-spacing: 0; text-transform: none;
|
||||||
|
height: 44px; line-height: 42px; padding: 0 18px;
|
||||||
|
}
|
||||||
|
.btn.small { height: 34px; line-height: 32px; padding: 0 10px; }
|
||||||
|
.btn.danger { color: var(--trash); }
|
||||||
|
.btn.danger:active { color: var(--ember); background: var(--ember-wash); }
|
||||||
|
.btn.star { font-size: 13px; letter-spacing: 0; }
|
||||||
|
.btn.star.on { color: var(--brass); border-color: #332b14; }
|
||||||
#toasts {
|
#toasts {
|
||||||
position: fixed; left: 50%; transform: translateX(-50%);
|
position: fixed; left: 50%; transform: translateX(-50%);
|
||||||
bottom: calc(80px + env(safe-area-inset-bottom, 0px));
|
bottom: calc(80px + env(safe-area-inset-bottom, 0px));
|
||||||
z-index: 2147483647; display: flex; flex-direction: column; gap: 8px; align-items: center;
|
z-index: 2147483647; display: flex; flex-direction: column; gap: 8px; align-items: center;
|
||||||
}
|
}
|
||||||
.toast {
|
.toast {
|
||||||
background: #2f2f3a; color: #fff; padding: 10px 16px; border-radius: 8px;
|
background: var(--ash); color: var(--paper); padding: 12px 16px;
|
||||||
font-size: 13px; opacity: 0; transform: translateY(10px); transition: all .3s;
|
border-left: 2px solid var(--paper);
|
||||||
box-shadow: 0 4px 14px rgba(0,0,0,.4); max-width: 80vw;
|
font: 400 16px var(--font-display);
|
||||||
|
opacity: 0; transform: translateY(10px); transition: all .3s;
|
||||||
|
box-shadow: 0 4px 14px rgba(0,0,0,.5); max-width: 80vw;
|
||||||
}
|
}
|
||||||
.toast.show { opacity: 1; transform: translateY(0); }
|
.toast.show { opacity: 1; transform: translateY(0); }
|
||||||
.toast.err { background: #7f1d1d; }
|
.toast.err { background: var(--ember-wash); border-left-color: var(--ember); color: var(--ember-soft); }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user