292 lines
14 KiB
PHP
292 lines
14 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="{{ app()->getLocale() }}" data-theme="{{ request()->cookie('nimuli_theme') ?? auth()->user()?->theme ?? 'dark' }}">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<title>{{ ($title ?? '') ? $title . ' — Nimuli' : 'Nimuli' }}</title>
|
|
|
|
<!-- Favicon -->
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
|
<link rel="manifest" href="/site.webmanifest">
|
|
<meta name="theme-color" content="#0a0a0f" media="(prefers-color-scheme: dark)">
|
|
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
|
|
<!-- Sidebar-init: set collapsed attribute before first paint — eliminates width flash -->
|
|
<script>
|
|
(function () {
|
|
try {
|
|
if (localStorage.getItem('sidebarCollapsed') === 'true' && window.innerWidth >= 768) {
|
|
document.documentElement.dataset.sidebarCollapsed = '1';
|
|
}
|
|
} catch (e) {}
|
|
})();
|
|
</script>
|
|
|
|
<!-- Theme-init: runs synchronously before any paint — eliminates FOUC -->
|
|
<script>
|
|
(function () {
|
|
try {
|
|
var saved = localStorage.getItem('nimuli_theme') || '{{ request()->cookie('nimuli_theme') ?? auth()->user()?->theme ?? 'dark' }}';
|
|
var theme = saved === 'system'
|
|
? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
|
: saved;
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
} catch (e) {}
|
|
})();
|
|
</script>
|
|
|
|
<!-- Critical CSS: paint baseline before Vite/Tailwind loads -->
|
|
<style>
|
|
:root {
|
|
/* App tokens */
|
|
--c-bg: #0a0a0f; --c-s1: #111117; --c-s2: #16161e;
|
|
--c-b1: rgba(255,255,255,0.08); --c-accent: #7c3aed;
|
|
--c-t1: #f0f0f5; --c-t2: #b8b8d0;
|
|
/* Sidebar design tokens */
|
|
--nim-surface: #0e0e13; --nim-surface-2: #121218; --nim-surface-3: #16161e;
|
|
--nim-border: #1f1f29; --nim-border-strong: #2a2a36;
|
|
--nim-text: #e8e8ec; --nim-text-muted: #8a8a96; --nim-text-dim: #5b5b66;
|
|
--nim-accent: #8b5cf6; --nim-accent-2: #a78bfa;
|
|
--nim-accent-bg: rgba(139,92,246,.14); --nim-accent-bg-strong: rgba(139,92,246,.22);
|
|
--nim-accent-glow: rgba(139,92,246,.6);
|
|
--nim-brand-grad: linear-gradient(135deg,#a855f7 0%,#ec4899 100%);
|
|
--nim-sb-w: 264px; --nim-sb-c: 72px;
|
|
}
|
|
[data-theme="light"] {
|
|
--c-bg: #ffffff; --c-s1: #ffffff; --c-s2: #f9fafb;
|
|
--c-b1: #e5e7eb; --c-accent: #7c3aed;
|
|
--c-t1: #111827; --c-t2: #6b7280;
|
|
}
|
|
html { background: var(--c-bg); }
|
|
body {
|
|
margin: 0; padding: 0;
|
|
background: var(--c-bg); color: var(--c-t1);
|
|
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
-webkit-font-smoothing: antialiased;
|
|
opacity: 0; transition: opacity 120ms ease-in; min-height: 100vh;
|
|
}
|
|
body.app-ready { opacity: 1; }
|
|
|
|
/* Skeleton */
|
|
.skel-sidebar {
|
|
position: fixed; top: 0; left: 0; bottom: 0;
|
|
width: var(--nim-sb-w); background: var(--nim-surface);
|
|
border-right: 1px solid var(--nim-border); z-index: 1;
|
|
}
|
|
.skel-topbar {
|
|
position: fixed; top: 0; left: var(--nim-sb-w); right: 0;
|
|
height: 57px; background: var(--c-s1);
|
|
border-bottom: 1px solid var(--c-b1); z-index: 1;
|
|
}
|
|
@media (max-width: 768px) { .skel-sidebar { display:none; } .skel-topbar { left:0; } }
|
|
.app-spinner {
|
|
position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%);
|
|
width: 28px; height: 28px; border: 2.5px solid var(--c-b1);
|
|
border-top-color: var(--nim-accent); border-radius: 50%;
|
|
animation: nim-spin .55s linear infinite; z-index: 10;
|
|
}
|
|
html[data-sidebar-collapsed] .skel-sidebar { width: var(--nim-sb-c); }
|
|
html[data-sidebar-collapsed] .skel-topbar { left: var(--nim-sb-c); }
|
|
body.app-ready .app-spinner, body.app-ready .skel-sidebar, body.app-ready .skel-topbar { display:none; }
|
|
@keyframes nim-spin { to { transform: translate(-50%,-50%) rotate(360deg); } }
|
|
|
|
/* Sidebar shell */
|
|
.nim-sidebar {
|
|
width: var(--nim-sb-w);
|
|
background: var(--color-s1);
|
|
border-right: 1px solid var(--color-b1);
|
|
overflow-x: hidden;
|
|
font-family: 'Geist', 'Inter', ui-sans-serif, system-ui, sans-serif;
|
|
transition: width .28s cubic-bezier(.4,0,.2,1), translate .28s cubic-bezier(.4,0,.2,1);
|
|
}
|
|
|
|
/* Content margin */
|
|
@media (min-width: 768px) {
|
|
.nim-content { margin-left: var(--nim-sb-w); }
|
|
html[data-sidebar-collapsed] .nim-content { margin-left: var(--nim-sb-c); }
|
|
html[data-sidebar-collapsed] .nim-sidebar { width: var(--nim-sb-c); }
|
|
}
|
|
|
|
/* Nav items */
|
|
.nav-item { color: var(--color-t2); text-decoration: none; }
|
|
.nav-item:hover:not(.nim-nav-active) { background: var(--color-s3); color: var(--color-t1); }
|
|
.nav-item.nim-nav-active {
|
|
background: rgba(124,58,237,.12);
|
|
color: var(--color-accent);
|
|
box-shadow: inset 0 0 0 1px rgba(124,58,237,.2);
|
|
}
|
|
.nav-item.nim-nav-active svg { color: var(--color-accent); }
|
|
.nav-item.nim-nav-active::before {
|
|
content: ''; position: absolute;
|
|
left: -10px; top: 50%; transform: translateY(-50%);
|
|
width: 3px; height: 18px;
|
|
background: var(--color-accent); border-radius: 0 3px 3px 0;
|
|
box-shadow: 0 0 12px rgba(124,58,237,.5);
|
|
}
|
|
|
|
/* Nav scrollbar */
|
|
.nim-sb-nav { scrollbar-width: thin; scrollbar-color: var(--nim-border-strong) transparent; }
|
|
.nim-sb-nav::-webkit-scrollbar { width: 6px; }
|
|
.nim-sb-nav::-webkit-scrollbar-thumb { background: var(--nim-border-strong); border-radius: 3px; }
|
|
.nim-sb-nav::-webkit-scrollbar-track { background: transparent; }
|
|
|
|
/* Logout hover (Tailwind handles this via hover:text-red-400 hover:bg-red-400/10) */
|
|
|
|
/* Collapse state — desktop only */
|
|
@media (min-width: 768px) {
|
|
html[data-sidebar-collapsed] .ws-text,
|
|
html[data-sidebar-collapsed] .ws-chevron,
|
|
html[data-sidebar-collapsed] .nav-text,
|
|
html[data-sidebar-collapsed] .nav-label,
|
|
html[data-sidebar-collapsed] .nav-badge,
|
|
html[data-sidebar-collapsed] .nav-plan-usage,
|
|
html[data-sidebar-collapsed] .user-info,
|
|
html[data-sidebar-collapsed] .user-logout-btn,
|
|
html[data-sidebar-collapsed] .collapse-text { display: none !important; }
|
|
|
|
html[data-sidebar-collapsed] .ws-trigger { justify-content: center !important; padding: 8px !important; }
|
|
html[data-sidebar-collapsed] .nav-item { justify-content: center !important; padding: 10px !important; gap: 0 !important; }
|
|
html[data-sidebar-collapsed] .nav-item.nim-nav-active::before { display: none; }
|
|
html[data-sidebar-collapsed] .collapse-row { justify-content: center !important; padding: 12px !important; }
|
|
html[data-sidebar-collapsed] .collapse-row svg { transform: rotate(180deg); }
|
|
html[data-sidebar-collapsed] .user-row { justify-content: center !important; padding: 12px !important; gap: 0 !important; }
|
|
|
|
/* Tooltip on hover when collapsed */
|
|
html[data-sidebar-collapsed] .nav-item:hover::after {
|
|
content: attr(data-label);
|
|
position: absolute; left: calc(100% + 12px); top: 50%; transform: translateY(-50%);
|
|
background: var(--color-s3); color: var(--color-t1);
|
|
padding: 6px 10px; border-radius: 6px; font-size: 12.5px;
|
|
white-space: nowrap; pointer-events: none;
|
|
border: 1px solid var(--color-b2);
|
|
box-shadow: 0 8px 24px -6px rgba(0,0,0,.5); z-index: 100;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
@vite(['resources/js/app.js'])
|
|
@livewireStyles
|
|
</head>
|
|
<body>
|
|
<!-- Skeleton: visible during JS load -->
|
|
<div class="skel-sidebar" aria-hidden="true"></div>
|
|
<div class="skel-topbar" aria-hidden="true"></div>
|
|
<div class="app-spinner" aria-hidden="true"></div>
|
|
|
|
<div id="nim-app" class="bg-bg text-t1 font-sans antialiased min-h-screen">
|
|
<!-- Mobile Sidebar Overlay -->
|
|
<div
|
|
class="fixed inset-0 z-30 bg-black/60 md:hidden"
|
|
x-show="$store.sidebar?.mobileOpen"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="$store.sidebar.closeMobile()"
|
|
aria-hidden="true"
|
|
x-cloak
|
|
></div>
|
|
|
|
<!-- Sidebar: persisted across wire:navigate to prevent re-render -->
|
|
@persist('sidebar')
|
|
<x-sidebar />
|
|
@endpersist
|
|
|
|
<!-- Main content area: shifts right based on sidebar width -->
|
|
<div class="nim-content flex flex-col min-h-screen" style="transition: margin-left .28s cubic-bezier(.4,0,.2,1);">
|
|
@persist('topbar')
|
|
<x-topbar :title="$title ?? ''" />
|
|
@endpersist
|
|
|
|
<main class="flex-1 overflow-y-auto p-6">
|
|
{{ $slot }}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
@livewire('wire-elements-modal')
|
|
@livewire('components.toaster')
|
|
@livewireScripts
|
|
|
|
<script>
|
|
(function () {
|
|
function markReady() { document.body.classList.add('app-ready'); }
|
|
if (document.readyState === 'complete') {
|
|
markReady();
|
|
} else {
|
|
window.addEventListener('load', markReady);
|
|
setTimeout(markReady, 600);
|
|
}
|
|
document.addEventListener('livewire:navigated', markReady);
|
|
|
|
// Close mobile sidebar after SPA navigation
|
|
document.addEventListener('livewire:navigated', function () {
|
|
if (window.Alpine && Alpine.store('sidebar')) {
|
|
Alpine.store('sidebar').closeMobile();
|
|
}
|
|
});
|
|
|
|
// Highlight active nav link after SPA navigation
|
|
function updateActiveNav() {
|
|
var path = window.location.pathname;
|
|
document.querySelectorAll('[data-nav-link]').forEach(function (el) {
|
|
var href = new URL(el.href, location.origin).pathname;
|
|
var isActive = href && (path === href || (!el.dataset.navExact && path.startsWith(href + '/')));
|
|
el.classList.toggle('nim-nav-active', isActive);
|
|
if (isActive) {
|
|
el.style.background = 'var(--nim-accent-bg)';
|
|
el.style.color = 'var(--nim-accent-2)';
|
|
el.style.boxShadow = 'inset 0 0 0 1px rgba(139,92,246,.18)';
|
|
} else {
|
|
el.style.background = '';
|
|
el.style.color = '';
|
|
el.style.boxShadow = '';
|
|
}
|
|
// Update icon SVG colour
|
|
var icon = el.querySelector('span.flex-shrink-0 svg');
|
|
if (icon) icon.style.color = isActive ? 'var(--nim-accent-2)' : '';
|
|
});
|
|
}
|
|
document.addEventListener('DOMContentLoaded', updateActiveNav);
|
|
document.addEventListener('livewire:navigated', updateActiveNav);
|
|
|
|
// Sync server-saved theme to Alpine store + DOM
|
|
document.addEventListener('livewire:init', function () {
|
|
Livewire.on('theme-changed-server', function (event) {
|
|
var theme = event.theme ?? (Array.isArray(event) ? event[0]?.theme : null);
|
|
if (theme && window.Alpine && Alpine.store('theme')) {
|
|
Alpine.store('theme').apply(theme);
|
|
}
|
|
});
|
|
});
|
|
|
|
// copy-to-clipboard Livewire event
|
|
document.addEventListener('livewire:init', function () {
|
|
Livewire.on('copy-to-clipboard', function (event) {
|
|
var text = event.text ?? (Array.isArray(event) ? event[0]?.text : null);
|
|
if (text) {
|
|
navigator.clipboard.writeText(text).catch(function () {
|
|
var ta = document.createElement('textarea');
|
|
ta.value = text;
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(ta);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|