mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Fix: CSS Issues
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
{#if store.toasts.length}
|
||||
<div class="toaster" aria-live="polite">
|
||||
{#each store.toasts as t (t.id)}
|
||||
<div
|
||||
<button
|
||||
class="toast toast-{t.kind}"
|
||||
role="alert"
|
||||
onclick={() => dismissToast(t.id)}
|
||||
@@ -43,7 +43,7 @@
|
||||
<p class="title">{t.title}</p>
|
||||
{#if t.body}<p class="sub">{t.body}</p>{/if}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -76,6 +76,10 @@
|
||||
cursor: pointer;
|
||||
animation: slideIn 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.toast:hover { opacity: 0.85; transform: translateX(-2px); }
|
||||
|
||||
@@ -284,7 +284,7 @@
|
||||
.header { display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4) var(--sp-6); border-bottom: 1px solid var(--border-dim); flex-shrink: 0; }
|
||||
.header-right { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; }
|
||||
.heading { font-family: var(--font-ui); font-size: var(--text-xs); font-weight: var(--weight-normal); color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; }
|
||||
.header-actions { display: flex; gap: var(--sp-1); }
|
||||
|
||||
.icon-btn { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-md); color: var(--text-muted); transition: color var(--t-base), background var(--t-base); }
|
||||
.icon-btn:hover:not(:disabled) { color: var(--text-primary); background: var(--bg-raised); }
|
||||
.icon-btn:disabled { opacity: 0.4; }
|
||||
|
||||
@@ -921,9 +921,9 @@
|
||||
</div>
|
||||
|
||||
{#if showResumeBanner}
|
||||
<div class="resume-banner" class:fading={resumeFading} role="status" onclick={() => { resumeVisible = false; resumeFading = false; }}>
|
||||
<button class="resume-banner" class:fading={resumeFading} onclick={() => { resumeVisible = false; resumeFading = false; }}>
|
||||
<span>Bookmark at page {resumePage}</span>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
@@ -1193,7 +1193,7 @@
|
||||
.slider-tooltip { position: absolute; bottom: calc(100% + 2px); transform: translateX(-50%); background: var(--bg-raised); border: 1px solid var(--border-base); border-radius: var(--radius-sm); padding: 2px 6px; font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-secondary); white-space: nowrap; pointer-events: none; z-index: 10; letter-spacing: var(--tracking-wide); }
|
||||
.slider-wrap:hover .slider-track-bg, .slider-wrap.dragging .slider-track-bg { height: 5px; }
|
||||
|
||||
.resume-banner { position: fixed; top: 48px; left: 50%; display: flex; align-items: center; gap: var(--sp-2); background: var(--bg-raised); border: 1px solid var(--border-base); border-radius: var(--radius-lg); padding: 6px var(--sp-3); font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-secondary); z-index: 20; box-shadow: 0 4px 16px rgba(0,0,0,0.4); animation: bannerIn 0.2s cubic-bezier(0.16,1,0.3,1) both; white-space: nowrap; cursor: pointer; }
|
||||
.resume-banner { position: fixed; top: 48px; left: 50%; display: flex; align-items: center; gap: var(--sp-2); background: var(--bg-raised); border: 1px solid var(--border-base); border-radius: var(--radius-lg); padding: 6px var(--sp-3); font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-secondary); z-index: 20; box-shadow: 0 4px 16px rgba(0,0,0,0.4); animation: bannerIn 0.2s cubic-bezier(0.16,1,0.3,1) both; white-space: nowrap; cursor: pointer; text-align: left; }
|
||||
.resume-banner.fading { animation: bannerOut 1s ease forwards; }
|
||||
@keyframes bannerIn { from { opacity: 0; transform: translateX(-50%) translateY(-6px) scale(0.97); } to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); } }
|
||||
@keyframes bannerOut { from { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); } to { opacity: 0; transform: translateX(-50%) translateY(-4px) scale(0.97); } }
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
let { editingId = $bindable(null), onClose }: Props = $props();
|
||||
|
||||
// ── Token group definitions ───────────────────────────────────────────────
|
||||
|
||||
const TOKEN_GROUPS: { label: string; tokens: (keyof ThemeTokens)[] }[] = [
|
||||
{
|
||||
label: "Backgrounds",
|
||||
@@ -65,8 +63,6 @@
|
||||
"color-info-bg": "Info background",
|
||||
};
|
||||
|
||||
// ── State ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function loadInitial(): { name: string; tokens: ThemeTokens } {
|
||||
if (editingId) {
|
||||
const existing = store.settings.customThemes.find(t => t.id === editingId);
|
||||
@@ -81,13 +77,10 @@
|
||||
let saveStatus: "idle" | "saved" = $state("idle");
|
||||
let importError: string | null = $state(null);
|
||||
|
||||
// ── CSS vars helper ───────────────────────────────────────────────────────
|
||||
function toCssVars(t: ThemeTokens): string {
|
||||
return Object.entries(t).map(([k, v]) => `--${k}: ${v};`).join(" ");
|
||||
}
|
||||
|
||||
// ── Actions ───────────────────────────────────────────────────────────────
|
||||
|
||||
function handleSave() {
|
||||
const name = themeName.trim() || "Untitled Theme";
|
||||
const id = editingId ?? `custom:${Math.random().toString(36).slice(2, 10)}`;
|
||||
@@ -154,17 +147,15 @@
|
||||
|
||||
<svelte:window onkeydown={onKey} />
|
||||
|
||||
<!-- ── Main editor ────────────────────────────────────────────────────────────── -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div class="te-backdrop" onclick={onClose} role="presentation">
|
||||
<div class="te-backdrop" tabindex="-1" onclick={onClose} onkeydown={(e) => e.key === "Escape" && onClose()}>
|
||||
<div
|
||||
class="te-shell"
|
||||
role="dialog"
|
||||
aria-label="Theme editor"
|
||||
tabindex="0"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
|
||||
<!-- ── Header ──────────────────────────────────────────────────────── -->
|
||||
<header class="te-header">
|
||||
<div class="te-header-left">
|
||||
<button class="te-icon-btn" onclick={onClose} title="Close editor">
|
||||
@@ -209,25 +200,17 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ── Body ───────────────────────────────────────────────────────── -->
|
||||
<div class="te-body">
|
||||
|
||||
<!-- Left: live preview -->
|
||||
<aside class="te-preview-pane">
|
||||
<div class="te-pane-label">Live Preview</div>
|
||||
|
||||
<!--
|
||||
FIX 1: toCssVars scoped only to this element, so only the
|
||||
preview UI sees the draft tokens — not the editor shell.
|
||||
-->
|
||||
<div class="te-preview-ui" style={toCssVars(tokens)}>
|
||||
<!-- Sidebar -->
|
||||
<div class="prv-sidebar">
|
||||
{#each [true, false, false, false] as active}
|
||||
<div class="prv-sb-dot" class:active></div>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- Main -->
|
||||
<div class="prv-main">
|
||||
<div class="prv-titlebar">
|
||||
<div class="prv-win-dots">
|
||||
@@ -262,7 +245,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Swatch strip — scoped to draft tokens too -->
|
||||
<div class="te-swatches" style={toCssVars(tokens)}>
|
||||
{#each [
|
||||
["bg-base","bg-base"],["bg-surface","bg-surface"],
|
||||
@@ -279,7 +261,6 @@
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Right: token editor -->
|
||||
<div class="te-editor-pane">
|
||||
{#each TOKEN_GROUPS as group}
|
||||
<div class="te-group">
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
onClose: () => void;
|
||||
} = $props();
|
||||
|
||||
// ── Prefs helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
const mangaPrefs = $derived(
|
||||
(store.settings.mangaPrefs?.[mangaId] ?? {}) as Partial<MangaPrefs>
|
||||
);
|
||||
@@ -30,15 +28,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ── Scanlator list — derived from loaded chapters ──────────────────────────
|
||||
|
||||
const scanlators = $derived(
|
||||
[...new Set(chapters.map(c => c.scanlator).filter((s): s is string => !!s?.trim()))]
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
);
|
||||
|
||||
// ── Options ────────────────────────────────────────────────────────────────
|
||||
|
||||
const DOWNLOAD_AHEAD_OPTIONS = [
|
||||
{ value: 0, label: "Off" },
|
||||
{ value: 2, label: "2" },
|
||||
@@ -66,18 +60,14 @@
|
||||
{ value: "manual", label: "Manual" },
|
||||
];
|
||||
|
||||
// ── Backdrop close ─────────────────────────────────────────────────────────
|
||||
|
||||
function onBackdrop(e: MouseEvent) {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="backdrop" onmousedown={onBackdrop}>
|
||||
<div class="backdrop" role="presentation" tabindex="-1" onmousedown={onBackdrop}>
|
||||
<div class="modal" role="dialog" aria-modal="true" aria-label="Automation">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<div class="header-left">
|
||||
<span class="modal-title">Automation</span>
|
||||
@@ -86,10 +76,8 @@
|
||||
<button class="close-btn" onclick={onClose} aria-label="Close"><X size={16} weight="light" /></button>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="modal-body">
|
||||
|
||||
<!-- ── Downloads ────────────────────────────────────────────────────── -->
|
||||
<p class="section-label">Downloads</p>
|
||||
|
||||
<div class="auto-row">
|
||||
@@ -100,6 +88,7 @@
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked={getPref("autoDownload")}
|
||||
aria-label="Auto-download new chapters"
|
||||
class="auto-toggle"
|
||||
class:auto-toggle-on={getPref("autoDownload")}
|
||||
onclick={() => setPref("autoDownload", !getPref("autoDownload"))}
|
||||
@@ -140,7 +129,6 @@
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- ── On Read ──────────────────────────────────────────────────────── -->
|
||||
<p class="section-label">On Read</p>
|
||||
|
||||
<div class="auto-row">
|
||||
@@ -151,6 +139,7 @@
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked={getPref("deleteOnRead")}
|
||||
aria-label="Delete after reading"
|
||||
class="auto-toggle"
|
||||
class:auto-toggle-on={getPref("deleteOnRead")}
|
||||
onclick={() => setPref("deleteOnRead", !getPref("deleteOnRead"))}
|
||||
@@ -174,7 +163,6 @@
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- ── Updates ─────────────────────────────────────────────────────── -->
|
||||
<p class="section-label">Updates</p>
|
||||
|
||||
<div class="auto-row">
|
||||
@@ -185,6 +173,7 @@
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked={getPref("pauseUpdates")}
|
||||
aria-label="Pause updates"
|
||||
class="auto-toggle"
|
||||
class:auto-toggle-on={getPref("pauseUpdates")}
|
||||
onclick={() => setPref("pauseUpdates", !getPref("pauseUpdates"))}
|
||||
@@ -210,7 +199,6 @@
|
||||
{#if scanlators.length > 1}
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- ── Scanlator ──────────────────────────────────────────────────── -->
|
||||
<p class="section-label">Scanlator</p>
|
||||
|
||||
<div class="auto-row auto-row-align-start">
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
onClose: () => void;
|
||||
} = $props();
|
||||
|
||||
// ── State ──────────────────────────────────────────────────────────────────
|
||||
|
||||
type TabId = "records" | number;
|
||||
|
||||
let trackers: Tracker[] = $state([]);
|
||||
@@ -39,8 +37,6 @@
|
||||
let editingChapter: number | null = $state(null);
|
||||
let chapterDraft: number = $state(0);
|
||||
|
||||
// ── Load ───────────────────────────────────────────────────────────────────
|
||||
|
||||
async function load() {
|
||||
loading = true;
|
||||
try {
|
||||
@@ -61,7 +57,6 @@
|
||||
|
||||
$effect(() => { load(); });
|
||||
|
||||
// Auto-search with manga title when switching to a tracker tab
|
||||
$effect(() => {
|
||||
const tab = activeTab;
|
||||
if (typeof tab !== "number") return;
|
||||
@@ -71,14 +66,10 @@
|
||||
doSearch(tab, mangaTitle);
|
||||
});
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
function trackerFor(id: number) { return trackers.find(t => t.id === id); }
|
||||
function recordFor(trackerId: number){ return records.find(r => r.trackerId === trackerId); }
|
||||
const loggedInTrackers = $derived(trackers.filter(t => t.isLoggedIn));
|
||||
|
||||
// ── Search ─────────────────────────────────────────────────────────────────
|
||||
|
||||
let searchTimer: ReturnType<typeof setTimeout>;
|
||||
|
||||
function onSearchInput() {
|
||||
@@ -105,8 +96,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Bind / Unbind ──────────────────────────────────────────────────────────
|
||||
|
||||
async function bind(result: TrackSearch) {
|
||||
if (typeof activeTab !== "number") return;
|
||||
binding = true;
|
||||
@@ -137,8 +126,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Update ─────────────────────────────────────────────────────────────────
|
||||
|
||||
async function updateStatus(record: TrackRecord, status: number) {
|
||||
updatingRecord = record.id;
|
||||
try {
|
||||
@@ -234,7 +221,6 @@
|
||||
>
|
||||
<div class="modal" role="dialog" aria-label="Tracking">
|
||||
|
||||
<!-- ── Header ─────────────────────────────────────────────────────────── -->
|
||||
<div class="modal-header">
|
||||
<div class="header-left">
|
||||
<span class="modal-title">Tracking</span>
|
||||
@@ -256,7 +242,6 @@
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<!-- ── Tabs ──────────────────────────────────────────────────────────── -->
|
||||
<div class="tabs">
|
||||
<button
|
||||
class="tab"
|
||||
@@ -282,7 +267,6 @@
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- ── My List tab ───────────────────────────────────────────────────── -->
|
||||
{#if activeTab === "records"}
|
||||
<div class="tab-body">
|
||||
{#if records.length === 0}
|
||||
@@ -434,7 +418,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- ── Tracker search tab ─────────────────────────────────────────────── -->
|
||||
{:else}
|
||||
{@const tracker = trackerFor(activeTab as number)}
|
||||
{@const boundRecord = recordFor(activeTab as number)}
|
||||
@@ -644,7 +627,6 @@
|
||||
.result-bound { background: color-mix(in srgb, var(--accent) 8%, transparent) !important; }
|
||||
.result-cover { width: 44px; height: 62px; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--border-dim); flex-shrink: 0; }
|
||||
.result-cover-empty { background: var(--bg-raised); }
|
||||
.hidden { display: none; }
|
||||
.result-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); padding-top: 2px; }
|
||||
.result-title { font-size: var(--text-sm); color: var(--text-secondary); line-height: var(--leading-snug); text-align: left; }
|
||||
.result-meta { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
|
||||
|
||||
Reference in New Issue
Block a user