+ + + - -
- - - - - -
- - -
- - - 🐞 - - + + 🐞 + diff --git a/public/assets/css/app.css b/public/assets/css/app.css index 2c5ae64..a1ca4f5 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -1,51 +1,15 @@ -@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Space+Grotesk:wght@400;600;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=IBM+Plex+Mono:wght@400;600&display=swap'); :root { - --bg: #f4f6fb; + --bg: #f5f7fb; --panel: #ffffff; - --panel-2: #f0f3fb; - --text: #151a2d; - --muted: #5a6685; - --accent: #ff6b4a; - --accent-2: #0fb4a4; + --panel-2: #eef2ff; + --text: #111827; + --muted: #5b6475; + --accent: #ff5a3d; + --accent-2: #00b3a4; --line: #d7dceb; - --shadow: 0 20px 50px rgba(22, 32, 74, 0.12); -} - -body[data-theme="light"] { - --bg: #f4f6fb; - --panel: #ffffff; - --panel-2: #f0f3fb; - --text: #151a2d; - --muted: #5a6685; - --accent: #ff6b4a; - --accent-2: #0fb4a4; - --line: #d7dceb; - --shadow: 0 20px 50px rgba(22, 32, 74, 0.12); -} - -body[data-theme="ocean"] { - --bg: #eef6ff; - --panel: #ffffff; - --panel-2: #e6f0ff; - --text: #0b1b33; - --muted: #3a4c6e; - --accent: #2d7bff; - --accent-2: #00b6b2; - --line: #c9d9f3; - --shadow: 0 20px 50px rgba(22, 32, 74, 0.12); -} - -body[data-theme="graphite"] { - --bg: #f7f7f8; - --panel: #ffffff; - --panel-2: #eceff3; - --text: #1e222a; - --muted: #5c667a; - --accent: #ff7a00; - --accent-2: #6b7bff; - --line: #d5d8df; - --shadow: 0 20px 50px rgba(20, 24, 34, 0.12); + --shadow: 0 18px 36px rgba(20, 32, 64, 0.15); } * { box-sizing: border-box; } @@ -53,8 +17,8 @@ html, body { height: 100%; } body { margin: 0; font-family: "Space Grotesk", "Segoe UI", sans-serif; - background: radial-gradient(1200px 500px at 10% -10%, rgba(255, 107, 74, 0.12), transparent 60%), - radial-gradient(900px 500px at 90% 10%, rgba(15, 180, 164, 0.12), transparent 55%), + background: radial-gradient(1200px 500px at 10% -10%, rgba(255, 90, 61, 0.15), transparent 60%), + radial-gradient(900px 500px at 90% 10%, rgba(0, 179, 164, 0.12), transparent 55%), var(--bg); color: var(--text); } @@ -64,210 +28,58 @@ body { width: 420px; height: 420px; border-radius: 50%; - filter: blur(80px); - opacity: 0.4; + filter: blur(90px); + opacity: 0.35; z-index: 0; pointer-events: none; } -.orb-a { top: -120px; left: -80px; background: #ff5e5b; } -.orb-b { bottom: -160px; right: -120px; background: #20e3b2; } +.orb-a { top: -140px; left: -100px; background: #ff5a3d; } +.orb-b { bottom: -180px; right: -120px; background: #00b3a4; } -.app-shell { - position: relative; - z-index: 1; - min-height: 100vh; - display: grid; - grid-template-columns: 1fr; - gap: 14px; - padding: 18px 24px 24px; -} - -.app-sidebar { +.card { background: var(--panel); border: 1px solid var(--line); border-radius: 18px; - padding: 20px; box-shadow: var(--shadow); - display: flex; - flex-direction: column; - gap: 18px; +} + +.site-header { position: sticky; - top: 110px; - height: fit-content; - transform: translateX(-12px); - opacity: 0; - pointer-events: none; - transition: transform 260ms ease, opacity 260ms ease; -} - -.sidebar-toggle { - background: var(--panel-2); - border: 1px solid var(--line); - border-radius: 10px; - padding: 8px 10px; - cursor: pointer; - color: var(--text); - font-weight: 700; -} -.sidebar-collapse { display: none; } - -.floating-toggle { - display: none; - position: fixed; - left: 24px; - bottom: 24px; - width: 52px; - height: 52px; - border-radius: 50%; - background: var(--panel); - border: 1px solid var(--line); - box-shadow: var(--shadow); - align-items: center; - justify-content: center; - font-weight: 800; - z-index: 60; -} - -.layout-body.sidebar-open .app-sidebar { - transform: translateX(0); - opacity: 1; - pointer-events: auto; -} -.layout-body.sidebar-open .app-content { - transition: transform 260ms ease; -} - -.sidebar-collapsed .floating-toggle { - display: inline-flex; -} - -.brand { + top: 0; + z-index: 20; + margin: 18px 24px 8px; + padding: 10px 16px; display: flex; align-items: center; - gap: 14px; -} -.topbar-logo { - height: 80px; - width: auto; - filter: drop-shadow(0 6px 12px rgba(0,0,0,0.12)); - margin-right: 10px; -} -.brand img { - height: 46px; - width: auto; - filter: drop-shadow(0 8px 18px rgba(0,0,0,0.45)); -} -.brand-title { - font-weight: 700; - letter-spacing: 0.4px; - font-size: 1.2rem; -} -.brand-sub { - color: var(--muted); - font-size: 0.9rem; + justify-content: space-between; + gap: 16px; } -.sidebar-nav { +.logo-wrap { display: flex; align-items: center; } +.site-logo { height: 80px; width: auto; filter: drop-shadow(0 6px 12px rgba(0,0,0,0.12)); } + +.header-nav { display: flex; - flex-direction: column; - gap: 8px; -} -.nav-section { - color: var(--muted); - font-size: 0.8rem; - text-transform: uppercase; - letter-spacing: 0.08em; - margin-top: 6px; + align-items: center; + gap: 12px; } + .nav-link { color: var(--text); text-decoration: none; padding: 8px 14px; border-radius: 10px; border: 1px solid transparent; - transition: all 180ms ease; font-weight: 600; + transition: all 180ms ease; } .nav-link:hover { border-color: var(--line); background: var(--panel-2); } -.nav-link.is-active { - color: #ffffff; - background: var(--accent); - border-color: var(--accent); -} -.cta-button { - background: linear-gradient(120deg, var(--accent), #ff9f45); - border: none; - color: #ffffff; - font-weight: 700; - padding: 10px 18px; - border-radius: 12px; - cursor: pointer; - box-shadow: 0 14px 30px rgba(255, 94, 91, 0.35); -} - -.app-content { - display: flex; - flex-direction: column; - gap: 16px; -} - -.layout-body { - display: grid; - grid-template-columns: 1fr; - gap: 16px; - transition: grid-template-columns 260ms ease; -} -.layout-body.sidebar-open { - grid-template-columns: 260px 1fr; -} - -.topbar { - position: sticky; - top: 0; - z-index: 20; - margin: 0; - grid-column: 1 / -1; - background: var(--panel); - border: 1px solid var(--line); - border-radius: 18px; - padding: 6px 14px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - box-shadow: var(--shadow); - min-height: 88px; -} -.topbar { position: relative; } -.topbar::after { - content: ""; - position: absolute; - inset: 0; - border-radius: 18px; - box-shadow: 0 18px 30px rgba(22, 32, 74, 0.12); - pointer-events: none; -} -.page-title { - margin: 0; - font-size: 1.25rem; -} -.topbar-actions { - display: flex; - gap: 10px; - flex-wrap: wrap; -} - -.dropdown { - position: relative; - display: inline-flex; -} -.dropdown-toggle { - background: transparent; -} +.dropdown { position: relative; display: inline-flex; } +.dropdown-toggle { background: transparent; } .dropdown-menu { position: absolute; top: 110%; @@ -283,9 +95,7 @@ body { } .dropdown-menu-right { right: 0; left: auto; } .dropdown:hover .dropdown-menu, -.dropdown:focus-within .dropdown-menu { - display: block; -} +.dropdown:focus-within .dropdown-menu { display: block; } .dropdown-item { display: block; padding: 8px 10px; @@ -294,25 +104,11 @@ body { text-decoration: none; font-weight: 600; } -.dropdown-item:hover { - background: var(--panel-2); -} -.dropdown-divider { - height: 1px; - background: var(--line); - margin: 6px 4px; -} -.dropdown-header { - padding: 6px 10px; - font-size: 0.85rem; - color: var(--muted); -} -.avatar-btn { - background: transparent; - border: none; - padding: 0; - cursor: pointer; -} +.dropdown-item:hover { background: var(--panel-2); } +.dropdown-divider { height: 1px; background: var(--line); margin: 6px 4px; } +.dropdown-header { padding: 6px 10px; font-size: 0.85rem; color: var(--muted); } + +.avatar-btn { background: transparent; border: none; padding: 0; cursor: pointer; } .avatar { width: 34px; height: 34px; @@ -326,13 +122,78 @@ body { } .module-subnav { + margin: 0 24px 8px; + padding: 8px 12px; display: flex; gap: 10px; flex-wrap: wrap; - padding: 8px 12px; - border: 1px solid var(--line); - border-radius: 12px; +} + +.layout-body { + display: grid; + grid-template-columns: 1fr; + gap: 16px; + padding: 0 24px 24px; + transition: grid-template-columns 260ms ease; +} +.layout-body.sidebar-open { + grid-template-columns: 260px 1fr; +} + +.sidebar { + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; + position: sticky; + top: 120px; + height: fit-content; + transform: translateX(-12px); + opacity: 0; + pointer-events: none; + transition: transform 260ms ease, opacity 260ms ease; +} +.layout-body.sidebar-open .sidebar { + transform: translateX(0); + opacity: 1; + pointer-events: auto; +} + +.sidebar-toggle { + align-self: flex-start; background: var(--panel-2); + border: 1px solid var(--line); + border-radius: 10px; + padding: 6px 10px; + cursor: pointer; + font-weight: 700; +} + +.sidebar-items .nav-link { display: block; } + +.sidebar-fab { + position: fixed; + left: 24px; + bottom: 24px; + width: 52px; + height: 52px; + border-radius: 50%; + background: var(--panel); + border: 1px solid var(--line); + box-shadow: var(--shadow); + font-weight: 800; + z-index: 60; +} + +.main-content { min-height: 60vh; } + +.site-footer { + margin: 0 24px 24px; + padding: 10px 16px; + color: var(--muted); + display: flex; + justify-content: space-between; + font-size: 0.85rem; } .debug-fab { @@ -349,55 +210,10 @@ body { justify-content: center; text-decoration: none; font-size: 22px; - box-shadow: 0 16px 32px rgba(255, 107, 74, 0.28); + box-shadow: 0 16px 32px rgba(255, 90, 61, 0.28); z-index: 50; } -.debug-fab:hover { - filter: brightness(0.95); -} -.site-main { - flex: 1; -} - -.site-footer { - display: flex; - justify-content: space-between; - color: var(--muted); - font-size: 0.85rem; - padding: 20px 4px 0; -} - -.card { - background: var(--panel); - border: 1px solid var(--line); - border-radius: 18px; - padding: 24px; - box-shadow: var(--shadow); -} -.card input, -.card textarea { - background: #ffffff; - border: 1px solid var(--line); - color: var(--text); - padding: 10px 12px; - border-radius: 10px; - font-family: inherit; -} -.card select { - background: #ffffff; - border: 1px solid var(--line); - color: var(--text); - padding: 10px 12px; - border-radius: 10px; - font-family: inherit; -} -.card input:focus, -.card textarea:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 2px rgba(255, 94, 91, 0.2); -} .muted { color: var(--muted); } .pill { display: inline-flex; @@ -407,92 +223,51 @@ body { border-radius: 999px; border: 1px solid var(--line); color: var(--muted); - background: rgba(255,255,255,0.03); + background: rgba(255,255,255,0.6); font-size: 0.85rem; } -.grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 16px; + +.card input, +.card textarea, +.card select { + background: #ffffff; + border: 1px solid var(--line); + color: var(--text); + padding: 10px 12px; + border-radius: 10px; + font-family: inherit; } -@media (max-width: 1100px) { - .app-shell { grid-template-columns: 1fr; padding: 18px; } - .app-sidebar { position: relative; top: 0; } -} -@media (max-width: 720px) { - .grid { grid-template-columns: 1fr; } - .topbar { flex-direction: column; align-items: flex-start; } +.cta-button { + background: linear-gradient(120deg, var(--accent), #ff9f45); + border: none; + color: #ffffff; + font-weight: 700; + padding: 10px 18px; + border-radius: 12px; + cursor: pointer; + box-shadow: 0 14px 30px rgba(255, 90, 61, 0.35); } -/* Minimal Tailwind-like utility support for existing templates */ -.px-4 { padding-left: 1rem; padding-right: 1rem; } +/* Minimal utility support (tables + spacing) */ .px-6 { padding-left: 1.5rem; padding-right: 1.5rem; } .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; } .py-4 { padding-top: 1rem; padding-bottom: 1rem; } -.py-5 { padding-top: 1.25rem; padding-bottom: 1.25rem; } -.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; } -@media (min-width: 640px) { - .sm\\:px-0 { padding-left: 0; padding-right: 0; } - .sm\\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; } - .sm\\:rounded-lg { border-radius: 0.75rem; } -} - -.text-white { color: #ffffff; } -.text-gray-200 { color: #3a4c6e; } -.text-gray-300 { color: #4b5775; } -.text-gray-400 { color: #5a6685; } -.text-gray-500 { color: #6b7696; } -.text-red-100 { color: #8b1d1d; } -.text-indigo-400 { color: #2d7bff; } -.hover\\:text-indigo-300:hover { color: #1b63da; } -.font-medium { font-weight: 600; } -.font-semibold { font-weight: 700; } -.font-bold { font-weight: 700; } -.text-sm { font-size: 0.9rem; } -.text-lg { font-size: 1.1rem; } -.text-2xl { font-size: 1.6rem; } -.uppercase { text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.72rem; } -.tracking-wider { letter-spacing: 0.06em; } -.leading-6 { line-height: 1.5; } -.whitespace-nowrap { white-space: nowrap; } .text-left { text-align: left; } -.text-right { text-align: right; } -.divide-y > * + * { border-top: 1px solid var(--line); } -.border { border: 1px solid var(--line); } -.border-l-4 { border-left: 4px solid; } -.border-red-500 { border-color: #ff5e5b; } -.border-gray-700 { border-color: var(--line); } -.bg-gray-800 { background: var(--panel); } -.bg-gray-900 { background: var(--panel-2); } -.bg-red-900 { background: #ffe9e9; } -.shadow { box-shadow: var(--shadow); } -.overflow-hidden { overflow: hidden; } -.overflow-x-auto { overflow-x: auto; } -.min-w-full { min-width: 100%; } -.divide-gray-700 > * + * { border-top: 1px solid var(--line); } -.bg-indigo-600 { background: #4f46e5; } -.hover\\:bg-indigo-700:hover { background: #4338ca; } -.rounded { border-radius: 12px; } -.rounded-lg { border-radius: 14px; } -.transition-colors { transition: color 180ms ease, background 180ms ease; } -.flex { display: flex; } -.items-center { align-items: center; } -.justify-between { justify-content: space-between; } -.mb-6 { margin-bottom: 1.5rem; } -.mt-1 { margin-top: 0.25rem; } -.max-w-2xl { max-width: 42rem; } -.relative { position: relative; } -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} +.text-sm { font-size: 0.9rem; } +.text-xs { font-size: 0.75rem; } +.uppercase { text-transform: uppercase; letter-spacing: 0.08em; } +.font-medium { font-weight: 600; } .font-mono { font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; } -.hover\\:bg-gray-750:hover { background: #151a28; } +.bg-gray-900 { background: var(--panel-2); } +.bg-gray-800 { background: var(--panel); } +.divide-y > * + * { border-top: 1px solid var(--line); } +.border-gray-700 { border-color: var(--line); } + +@media (max-width: 900px) { + .site-header { margin: 12px; } + .layout-body { padding: 0 12px 12px; } + .module-subnav { margin: 0 12px 8px; } + .site-footer { margin: 0 12px 12px; } + .header-nav { flex-wrap: wrap; justify-content: flex-end; } +} diff --git a/public/assets/js/app.js b/public/assets/js/app.js index 3b3bb8a..7b53938 100755 --- a/public/assets/js/app.js +++ b/public/assets/js/app.js @@ -1,21 +1,29 @@ (() => { - const toggles = document.querySelectorAll('[data-sidebar-toggle]'); const layout = document.querySelector('.layout-body'); + if (!layout) return; + + const enabled = layout.dataset.sidebarEnabled === '1'; + const collapsible = layout.dataset.sidebarCollapsible === '1'; + const defaultState = layout.dataset.sidebarDefault || 'collapsed'; + + const toggles = document.querySelectorAll('[data-sidebar-toggle]'); + if (!enabled || !collapsible) { + toggles.forEach((t) => t.remove()); + return; + } + + const saved = localStorage.getItem('sidebar-state'); + const initial = saved || defaultState; + if (initial === 'open') { + layout.classList.add('sidebar-open'); + } else { + layout.classList.remove('sidebar-open'); + } + toggles.forEach((toggle) => { toggle.addEventListener('click', () => { - document.body.classList.toggle('sidebar-collapsed'); - layout?.classList.toggle('sidebar-open'); - const collapsed = document.body.classList.contains('sidebar-collapsed'); - localStorage.setItem('sidebar-collapsed', collapsed ? '1' : '0'); + layout.classList.toggle('sidebar-open'); + localStorage.setItem('sidebar-state', layout.classList.contains('sidebar-open') ? 'open' : 'collapsed'); }); }); - - const saved = localStorage.getItem('sidebar-collapsed'); - const collapsed = (saved === null || saved === '1'); - if (collapsed) { - document.body.classList.add('sidebar-collapsed'); - layout?.classList.remove('sidebar-open'); - } else { - layout?.classList.add('sidebar-open'); - } })(); diff --git a/src/App/ModuleManager.php b/src/App/ModuleManager.php index 517ee95..2ccde56 100644 --- a/src/App/ModuleManager.php +++ b/src/App/ModuleManager.php @@ -209,6 +209,7 @@ final class ModuleManager 'description' => $data['description'] ?? '', 'setup' => $data['setup'] ?? [], 'menu' => $data['menu'] ?? [], + 'sidebar' => $data['sidebar'] ?? [], 'db_defaults' => $data['db_defaults'] ?? [], 'path' => $dir, 'enabled' => false,