133 lines
3.4 KiB
Svelte
133 lines
3.4 KiB
Svelte
<script>
|
|
import { page, navigating } from '$app/stores';
|
|
import '../app.css';
|
|
let { children } = $props();
|
|
|
|
// Label shown under the nav progress bar while loading a page
|
|
const navLabel = $derived(
|
|
$navigating?.to?.url?.pathname === '/portfolio' ? 'Loading portfolio…' :
|
|
$navigating?.to?.url?.pathname?.startsWith('/calls') ? 'Loading market calls…' :
|
|
$navigating?.to?.url?.pathname === '/safe-buys' ? 'Screening safe buys…' :
|
|
'Loading…'
|
|
);
|
|
</script>
|
|
|
|
<div class="shell">
|
|
<nav>
|
|
<span class="brand">📊 Market Screener</span>
|
|
<div class="links">
|
|
<a href="/" class:active={$page.url.pathname === '/'}>Screener</a>
|
|
<a href="/portfolio" class:active={$page.url.pathname === '/portfolio'}>Portfolio</a>
|
|
<a href="/calls" class:active={$page.url.pathname.startsWith('/calls')}>Market Calls</a>
|
|
<a href="/safe-buys" class:active={$page.url.pathname === '/safe-buys'}>🛡 Safe Buys</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Thin progress bar at top of screen — always visible even on first nav -->
|
|
{#if $navigating}
|
|
<div class="nav-progress">
|
|
<div class="nav-bar"></div>
|
|
</div>
|
|
{/if}
|
|
|
|
<main>
|
|
{#if $navigating}
|
|
<!-- Replace old page content immediately — old page disappears, spinner takes over -->
|
|
<div class="nav-overlay">
|
|
<div class="nav-spinner"></div>
|
|
<span class="nav-label">{navLabel}</span>
|
|
</div>
|
|
{:else}
|
|
{@render children()}
|
|
{/if}
|
|
</main>
|
|
</div>
|
|
|
|
<style>
|
|
.shell { min-height: 100vh; display: flex; flex-direction: column; }
|
|
|
|
nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 24px;
|
|
padding: 14px 32px;
|
|
border-bottom: 1px solid #1e293b;
|
|
background: #0f1117;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
|
|
.brand { font-size: 15px; font-weight: 700; color: #f1f5f9; }
|
|
|
|
.links { display: flex; gap: 4px; margin-left: auto; }
|
|
|
|
.links a {
|
|
color: #64748b;
|
|
text-decoration: none;
|
|
padding: 6px 14px;
|
|
border-radius: 6px;
|
|
font-weight: 500;
|
|
transition: color 0.15s, background 0.15s;
|
|
}
|
|
.links a:hover { color: #e2e8f0; background: #1e293b; }
|
|
.links a.active { color: #e2e8f0; background: #1e293b; }
|
|
|
|
main { flex: 1; padding: 28px 32px; }
|
|
|
|
/* ── Navigation progress ─────────────────────────────────────────── */
|
|
.nav-progress {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
z-index: 100;
|
|
background: #1e293b;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.nav-bar {
|
|
height: 100%;
|
|
background: #3b82f6;
|
|
animation: progress 1.5s ease-in-out infinite;
|
|
transform-origin: left;
|
|
}
|
|
|
|
@keyframes progress {
|
|
0% { transform: translateX(-100%) scaleX(0.3); }
|
|
50% { transform: translateX(0%) scaleX(0.7); }
|
|
100% { transform: translateX(100%) scaleX(0.3); }
|
|
}
|
|
|
|
/* Centered spinner + label in the page body */
|
|
.nav-overlay {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 14px;
|
|
padding: 100px 0;
|
|
flex: 1;
|
|
}
|
|
|
|
.nav-spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 3px solid #1e293b;
|
|
border-top-color: #3b82f6;
|
|
border-radius: 50%;
|
|
animation: spin 0.7s linear infinite;
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 12px;
|
|
color: #475569;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
</style>
|