mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Feat: Re-did Layout & Sidebar
This commit is contained in:
@@ -11,32 +11,38 @@
|
|||||||
import Tracking from "../pages/Tracking.svelte";
|
import Tracking from "../pages/Tracking.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="frame">
|
||||||
<Sidebar />
|
<div class="shell">
|
||||||
<main class="main">
|
<Sidebar />
|
||||||
{#if store.activeManga}
|
<main class="main">
|
||||||
<SeriesDetail />
|
{#if store.activeManga}
|
||||||
{:else if store.navPage === "home"}
|
<SeriesDetail />
|
||||||
<Home />
|
{:else if store.navPage === "home"}
|
||||||
{:else if store.navPage === "library"}
|
<Home />
|
||||||
<Library />
|
{:else if store.navPage === "library"}
|
||||||
{:else if store.navPage === "search"}
|
<Library />
|
||||||
<Search />
|
{:else if store.navPage === "search"}
|
||||||
{:else if store.navPage === "history"}
|
<Search />
|
||||||
<RecentActivity />
|
{:else if store.navPage === "history"}
|
||||||
{:else if store.navPage === "downloads"}
|
<RecentActivity />
|
||||||
<Downloads />
|
{:else if store.navPage === "downloads"}
|
||||||
{:else if store.navPage === "extensions"}
|
<Downloads />
|
||||||
<Extensions />
|
{:else if store.navPage === "extensions"}
|
||||||
{:else if store.navPage === "tracking"}
|
<Extensions />
|
||||||
<Tracking />
|
{:else if store.navPage === "tracking"}
|
||||||
{:else}
|
<Tracking />
|
||||||
<Home />
|
{:else}
|
||||||
{/if}
|
<Home />
|
||||||
</main>
|
{/if}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root { display: flex; height: 100%; background: var(--bg-base); overflow: hidden; }
|
:global(*, *::before, *::after) { scrollbar-width: none; }
|
||||||
.main { flex: 1; overflow: hidden; background: var(--bg-surface); transform: translateZ(0); contain: layout style; }
|
:global(*::-webkit-scrollbar) { display: none; }
|
||||||
</style>
|
|
||||||
|
.frame { display: flex; padding: 0 10px 10px; width: 100%; height: 100%; box-sizing: border-box; overflow: hidden; }
|
||||||
|
.shell { display: flex; flex: 1; border-radius: 14px; overflow: hidden; border: 1px solid var(--border-dim); background: var(--bg-base); min-height: 0; min-width: 0; }
|
||||||
|
.main { flex: 1; overflow: hidden; background: var(--bg-surface); transform: translateZ(0); contain: layout style; min-width: 0; }
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -13,8 +13,12 @@
|
|||||||
{ id: "tracking", label: "Tracking", icon: ChartLineUp },
|
{ id: "tracking", label: "Tracking", icon: ChartLineUp },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TAB_SIZE = 36;
|
||||||
|
const TAB_GAP = 4;
|
||||||
|
|
||||||
const anims = $derived(store.settings.qolAnimations ?? true);
|
const anims = $derived(store.settings.qolAnimations ?? true);
|
||||||
const activeIndex = $derived(TABS.findIndex(t => t.id === store.navPage));
|
const activeIndex = $derived(TABS.findIndex(t => t.id === store.navPage));
|
||||||
|
const indicatorY = $derived(activeIndex * (TAB_SIZE + TAB_GAP));
|
||||||
|
|
||||||
function navigate(id: NavPage) {
|
function navigate(id: NavPage) {
|
||||||
store.navPage = id;
|
store.navPage = id;
|
||||||
@@ -38,7 +42,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
{#if activeIndex >= 0}
|
{#if activeIndex >= 0}
|
||||||
<div class="slide-indicator" class:anims style="--idx: {activeIndex}"></div>
|
<div class="slide-indicator" class:anims style="transform: translateX(-50%) translateY({indicatorY}px)"></div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each TABS as tab}
|
{#each TABS as tab}
|
||||||
<button class="tab" class:active={store.navPage === tab.id} class:anims
|
<button class="tab" class:active={store.navPage === tab.id} class:anims
|
||||||
@@ -55,47 +59,36 @@
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root { width: var(--sidebar-width); flex-shrink: 0; background: var(--bg-void); display: flex; flex-direction: column; align-items: center; padding: var(--sp-4) 0; overflow: hidden; min-height: 0; height: 100%; }
|
.root { width: var(--sidebar-width); flex-shrink: 0; background: transparent; display: flex; flex-direction: column; align-items: center; padding: var(--sp-4) 0; overflow: hidden; min-height: 0; height: 100%; }
|
||||||
|
|
||||||
/* Logo */
|
.logo { width: 56px; height: 56px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; margin-bottom: var(--sp-4); background: none; border: none; outline: none; cursor: pointer; border-radius: var(--radius-lg); padding: 0; appearance: none; -webkit-appearance: none; }
|
||||||
.logo { width: 80px; height: 80px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; margin-bottom: var(--sp-3); background: none; border: none; outline: none; cursor: pointer; border-radius: var(--radius-lg); padding: 0; appearance: none; -webkit-appearance: none; }
|
|
||||||
.logo:hover { opacity: 0.8; }
|
.logo:hover { opacity: 0.8; }
|
||||||
.logo:active { }
|
|
||||||
.logo:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
.logo:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||||
/* QOL: logo press + hover scale */
|
|
||||||
.logo.anims { transition: opacity var(--t-base), transform var(--t-base); }
|
.logo.anims { transition: opacity var(--t-base), transform var(--t-base); }
|
||||||
.logo.anims:hover { transform: scale(0.96); }
|
.logo.anims:hover { transform: scale(0.96); }
|
||||||
.logo.anims:active { transform: scale(0.92); }
|
.logo.anims:active { transform: scale(0.92); }
|
||||||
|
|
||||||
.logo-icon { width: 67px; height: 67px; background-color: var(--accent); mask-image: url("../../assets/moku-icon-wordmark.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: contain; -webkit-mask-image: url("../../assets/moku-icon-wordmark.svg"); -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; -webkit-mask-size: contain; filter: drop-shadow(0 0 8px rgba(107,143,107,0.35)); pointer-events: none; }
|
.logo-icon { width: 52px; height: 52px; background-color: var(--accent); mask-image: url("../../assets/moku-icon-wordmark.svg"); mask-repeat: no-repeat; mask-position: center; mask-size: contain; -webkit-mask-image: url("../../assets/moku-icon-wordmark.svg"); -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; -webkit-mask-size: contain; filter: drop-shadow(0 0 8px rgba(107,143,107,0.35)); pointer-events: none; }
|
||||||
|
|
||||||
/* Nav */
|
.nav { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center; gap: 4px; width: 100%; padding: 0 var(--sp-2); overflow-y: auto; overflow-x: hidden; scrollbar-width: none; }
|
||||||
.nav { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); width: 100%; padding: 0 var(--sp-2); overflow-y: auto; overflow-x: hidden; scrollbar-width: none; }
|
|
||||||
.nav::-webkit-scrollbar { display: none; }
|
.nav::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
.slide-indicator { position: absolute; width: 36px; height: 36px; border-radius: var(--radius-md); background: var(--accent-muted); pointer-events: none; top: 0; left: 50%; translate: -50% calc(var(--idx) * (36px + var(--sp-1))); z-index: 0; }
|
.slide-indicator { position: absolute; width: 36px; height: 36px; border-radius: var(--radius-md); background: var(--accent-muted); pointer-events: none; top: 0; left: 50%; transform: translateX(-50%) translateY(0px); z-index: 0; }
|
||||||
.slide-indicator.anims { transition: translate 0.22s cubic-bezier(0.16,1,0.3,1); }
|
.slide-indicator.anims { transition: transform 0.22s cubic-bezier(0.16,1,0.3,1); }
|
||||||
|
|
||||||
/* Tab base — no transitions by default */
|
|
||||||
.tab { position: relative; z-index: 1; width: 36px; height: 36px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-md); color: var(--text-faint); background: none; border: none; outline: none; cursor: pointer; padding: 0; appearance: none; -webkit-appearance: none; }
|
.tab { position: relative; z-index: 1; width: 36px; height: 36px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-md); color: var(--text-faint); background: none; border: none; outline: none; cursor: pointer; padding: 0; appearance: none; -webkit-appearance: none; }
|
||||||
.tab:hover { color: var(--text-muted); background: var(--bg-raised); }
|
.tab:hover { color: var(--text-muted); background: var(--bg-raised); }
|
||||||
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
||||||
.tab.active { color: var(--accent-fg); background: transparent; }
|
.tab.active { color: var(--accent-fg); background: transparent; }
|
||||||
.tab.active:hover { color: var(--accent-fg); background: transparent; }
|
.tab.active:hover { color: var(--accent-fg); background: transparent; }
|
||||||
|
.tab.anims { transition: color var(--t-base), background var(--t-base); }
|
||||||
/* QOL: smooth color + bg transitions, subtle scale on click */
|
|
||||||
.tab.anims { transition: color var(--t-base), background var(--t-base), transform 80ms ease, box-shadow var(--t-base); }
|
|
||||||
.tab.anims:hover { transform: translateY(-1px); box-shadow: 0 2px 6px rgba(0,0,0,0.25); }
|
|
||||||
.tab.anims:active { transform: scale(0.88); }
|
.tab.anims:active { transform: scale(0.88); }
|
||||||
.tab.anims.active { box-shadow: none; }
|
|
||||||
|
|
||||||
/* Bottom / settings */
|
|
||||||
.bottom { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; width: 100%; padding: var(--sp-3) var(--sp-2) 0; border-top: 1px solid var(--border-dim); margin-top: var(--sp-3); }
|
.bottom { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; width: 100%; padding: var(--sp-3) var(--sp-2) 0; border-top: 1px solid var(--border-dim); margin-top: var(--sp-3); }
|
||||||
|
|
||||||
.settings-btn { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-md); color: var(--text-faint); background: none; border: none; outline: none; cursor: pointer; padding: 0; appearance: none; -webkit-appearance: none; }
|
.settings-btn { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-md); color: var(--text-faint); background: none; border: none; outline: none; cursor: pointer; padding: 0; appearance: none; -webkit-appearance: none; }
|
||||||
.settings-btn:hover { color: var(--text-muted); background: var(--bg-raised); }
|
.settings-btn:hover { color: var(--text-muted); background: var(--bg-raised); }
|
||||||
.settings-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
.settings-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
||||||
/* QOL: gear spin on hover */
|
|
||||||
.settings-btn.anims { transition: color var(--t-base), background var(--t-base), transform var(--t-slow); }
|
.settings-btn.anims { transition: color var(--t-base), background var(--t-base), transform var(--t-slow); }
|
||||||
.settings-btn.anims:hover { transform: rotate(30deg); }
|
.settings-btn.anims:hover { transform: rotate(30deg); }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if isWindows}
|
{:else if isWindows}
|
||||||
<!-- On Windows, fullscreen hides the native titlebar — show a hoverable overlay so the user isn't locked in -->
|
|
||||||
<div class="fullscreen-controls">
|
<div class="fullscreen-controls">
|
||||||
<button onclick={() => win.setFullscreen(false)} title="Exit Fullscreen" aria-label="Exit Fullscreen">
|
<button onclick={() => win.setFullscreen(false)} title="Exit Fullscreen" aria-label="Exit Fullscreen">
|
||||||
<svg width="10" height="10" viewBox="0 0 10 10">
|
<svg width="10" height="10" viewBox="0 0 10 10">
|
||||||
@@ -65,6 +64,55 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 6px 0 var(--sp-4);
|
||||||
|
background: transparent;
|
||||||
|
flex-shrink: 0;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mac-spacer {
|
||||||
|
width: 70px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-2xs);
|
||||||
|
color: var(--text-faint);
|
||||||
|
letter-spacing: var(--tracking-wider);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.5;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--text-faint);
|
||||||
|
transition: color var(--t-base), background var(--t-base);
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
button:hover { color: var(--text-muted); background: rgba(255,255,255,0.06); }
|
||||||
|
.close:hover { color: #fff; background: #c0392b; }
|
||||||
|
|
||||||
.fullscreen-controls {
|
.fullscreen-controls {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -79,49 +127,4 @@
|
|||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
.fullscreen-controls:hover { opacity: 1; }
|
.fullscreen-controls:hover { opacity: 1; }
|
||||||
|
|
||||||
.bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0 var(--sp-3) 0 var(--sp-4);
|
|
||||||
background: var(--bg-void);
|
|
||||||
border-bottom: 1px solid var(--border-dim);
|
|
||||||
flex-shrink: 0;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
}
|
|
||||||
/* Spacer to clear the native macOS traffic lights (~70px) */
|
|
||||||
.mac-spacer {
|
|
||||||
width: 70px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-family: var(--font-ui);
|
|
||||||
font-size: var(--text-2xs);
|
|
||||||
color: var(--text-faint);
|
|
||||||
letter-spacing: var(--tracking-wider);
|
|
||||||
text-transform: uppercase;
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
}
|
|
||||||
.controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 28px; height: 28px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
color: var(--text-faint);
|
|
||||||
transition: color var(--t-base), background var(--t-base);
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
button:hover { color: var(--text-muted); background: var(--bg-raised); }
|
|
||||||
.close:hover { color: #fff; background: #c0392b; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user