Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions frontend/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ input[type="radio"]:checked + label .neon-indicator { display: block; }

/* ── Semantic border utility classes ── */
.border-b-medium { border-bottom: 1px solid rgba(255,255,255,0.08); }
.border-t-medium { border-top: 1px solid rgba(255,255,255,0.08); }

/* ── Search container ── */
.search-container {
Expand Down Expand Up @@ -375,3 +376,213 @@ input[type="radio"]:checked + label .neon-indicator { display: block; }
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: flex; }
.htmx-request.htmx-indicator { display: flex; }

/* ── Theme toggle button ── */
.btn-icon-theme {
display: flex; align-items: center; justify-content: center;
width: 34px; height: 34px; border-radius: 8px;
color: var(--color-text-muted);
background: transparent; border: none;
transition: color 0.15s, background 0.15s;
}
.btn-icon-theme:hover {
color: var(--color-text-body);
background: rgba(255,255,255,0.07);
}

/* ── Theme toggle icon visibility ── */
.theme-moon { display: none; }
[data-theme="light"] .theme-sun { display: none; }
[data-theme="light"] .theme-moon { display: block; }

/* ══════════════════════════════════════════════
LIGHT THEME
══════════════════════════════════════════════ */

[data-theme="light"] {
--color-text-bright: #0e1018;
--color-text-active: #181c28;
--color-text-body: #2e3650;
--color-text-muted: #606880;
--color-text-subtle: #98a0b4;
--color-bg-base: #f0f2f8;
--color-bg-card: #ffffff;
}

[data-theme="light"] select { color-scheme: light; }

[data-theme="light"] ::selection {
background: rgba(0,229,255,0.25); color: #0e1018;
}
[data-theme="light"] ::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.14);
}

/* Light mode neon grid — tone down blobs, remove scanlines */
[data-theme="light"] .neon-blob-1 { background: radial-gradient(ellipse, rgba(255,45,123,0.05), transparent 70%); }
[data-theme="light"] .neon-blob-2 { background: radial-gradient(ellipse, rgba(0,229,255,0.04), transparent 65%); }
[data-theme="light"] .neon-blob-3 { background: radial-gradient(ellipse, rgba(180,77,255,0.03), transparent 60%); }
[data-theme="light"] .neon-blob-4 { background: radial-gradient(ellipse, rgba(255,215,0,0.02), transparent 55%); }
[data-theme="light"] .neon-scanlines { display: none; }

/* Light mode glass containers */
[data-theme="light"] .glass-outer {
background: rgba(255,255,255,0.80);
border-color: rgba(0,0,0,0.09);
box-shadow: 0 8px 40px rgba(0,0,0,0.07);
}
[data-theme="light"] .glass-inner {
background: rgba(0,0,0,0.04);
border-color: rgba(0,0,0,0.14);
}
[data-theme="light"] .glass-inset {
background: rgba(0,0,0,0.05);
border-color: rgba(0,0,0,0.16);
}
[data-theme="light"] .glass-inset:hover {
background: rgba(0,0,0,0.09);
border-color: rgba(0,0,0,0.22);
}

/* Light mode nav */
[data-theme="light"] .nav-active { background: rgba(0,0,0,0.08); }
[data-theme="light"] .nav-link-active { background: rgba(0,0,0,0.08); }
[data-theme="light"] .btn-icon-theme:hover { background: rgba(0,0,0,0.08); }

/* Light mode header / footer */
[data-theme="light"] .site-header {
background: rgba(240,242,248,0.92);
border-bottom-color: rgba(0,0,0,0.14);
}
[data-theme="light"] .site-footer {
background: rgba(240,242,248,0.92);
border-top-color: rgba(0,0,0,0.14);
}

/* Light mode mobile menu nav drawer */
[data-theme="light"] #mobile-menu nav {
background: rgba(242,244,250,0.98);
}
[data-theme="light"] #mobile-menu nav > div:last-child {
border-top-color: rgba(0,0,0,0.14);
}
[data-theme="light"] #mobile-menu button:hover,
[data-theme="light"] #mobile-menu a:hover {
background: rgba(0,0,0,0.08) !important;
}

/* Light mode search */
[data-theme="light"] .search-container {
background: rgba(255,255,255,0.92);
border-color: rgba(0,0,0,0.16);
box-shadow: 0 8px 40px rgba(0,0,0,0.07);
}
[data-theme="light"] .search-container:focus-within {
border-color: rgba(0,150,200,0.55);
box-shadow: 0 0 0 5px rgba(0,150,200,0.08), 0 8px 40px rgba(0,0,0,0.09);
}

/* Light mode cards / toggles */
[data-theme="light"] .source-toggle {
background: rgba(0,0,0,0.05);
border-color: rgba(0,0,0,0.16);
}
[data-theme="light"] .recent-search-card {
background: rgba(0,0,0,0.05);
border-color: rgba(0,0,0,0.16);
}
[data-theme="light"] .recent-search-card:hover {
background: rgba(0,0,0,0.09);
border-color: rgba(0,0,0,0.22);
}
[data-theme="light"] .data-source-card {
background: rgba(0,0,0,0.05);
border-color: rgba(0,0,0,0.16);
}
[data-theme="light"] .data-source-card:hover {
background: rgba(0,0,0,0.09);
border-color: rgba(0,0,0,0.22);
}

/* Light mode buttons — use solid tones so they're clearly visible */
[data-theme="light"] .btn-ghost {
background: #e6e8ef;
border-color: #bfc2cc;
}
[data-theme="light"] .btn-ghost:hover {
background: #d8dbe5;
border-color: #b0b3be;
}

/* Light mode dividers / progress */
[data-theme="light"] .border-b-medium { border-bottom-color: rgba(0,0,0,0.14); }
[data-theme="light"] .border-t-medium { border-top-color: rgba(0,0,0,0.14); }
[data-theme="light"] .neon-progress { background: rgba(0,0,0,0.12); }

/* Light mode docs */
[data-theme="light"] .docs-code {
background: #e8eaf0;
border-color: rgba(0,0,0,0.14);
}
[data-theme="light"] .docs-toc-link:hover { background: rgba(0,0,0,0.07); }
[data-theme="light"] .docs-table th { border-bottom-color: rgba(0,0,0,0.16); }
[data-theme="light"] .docs-table td { border-bottom-color: rgba(0,0,0,0.10); }

/* ── Light mode: flip Tailwind white-overlay utilities → dark-overlay ── */
/*
* rgba(0,0,0,X) on bg #f0f2f8 produces:
* 0.07 → #dfe1e6 0.10 → #d8d9df 0.14 → #cfd1d7 0.20 → #c4c6cb
* Borders need ≥0.18 to be clearly visible; backgrounds ≥0.07 to read as a surface.
*/

/* Backgrounds */
[data-theme="light"] .bg-white\/5,
[data-theme="light"] .bg-white\/\[0\.05\] { background-color: rgba(0,0,0,0.07); }
[data-theme="light"] .bg-white\/\[0\.03\] { background-color: rgba(0,0,0,0.05); }
[data-theme="light"] .bg-white\/\[0\.04\] { background-color: rgba(0,0,0,0.06); }
[data-theme="light"] .bg-white\/\[0\.06\] { background-color: rgba(0,0,0,0.08); }
[data-theme="light"] .bg-white\/\[0\.08\] { background-color: rgba(0,0,0,0.09); }
[data-theme="light"] .bg-white\/10 { background-color: rgba(0,0,0,0.11); }

/* Hover backgrounds */
[data-theme="light"] .hover\:bg-white\/5:hover { background-color: rgba(0,0,0,0.09); }
[data-theme="light"] .hover\:bg-white\/\[0\.02\]:hover { background-color: rgba(0,0,0,0.05); }
[data-theme="light"] .hover\:bg-white\/\[0\.06\]:hover { background-color: rgba(0,0,0,0.09); }
[data-theme="light"] .hover\:bg-white\/\[0\.08\]:hover { background-color: rgba(0,0,0,0.11); }
[data-theme="light"] .hover\:bg-white\/10:hover { background-color: rgba(0,0,0,0.13); }

/* Borders */
[data-theme="light"] .border-white\/\[0\.06\] { border-color: rgba(0,0,0,0.14); }
[data-theme="light"] .border-white\/\[0\.08\] { border-color: rgba(0,0,0,0.16); }
[data-theme="light"] .border-white\/10 { border-color: rgba(0,0,0,0.20); }
[data-theme="light"] .border-white\/20 { border-color: rgba(0,0,0,0.28); }
[data-theme="light"] .hover\:border-white\/\[0\.14\]:hover { border-color: rgba(0,0,0,0.22); }

/* Ring (avatar images etc.) */
[data-theme="light"] .ring-white\/10 { --tw-ring-color: rgba(0,0,0,0.10); }

/* Search extension article — dark overlay card → white card */
[data-theme="light"] article.rounded-xl { background-color: rgba(255,255,255,0.90); }

/* ── Light mode: accessible text colors ── */
/* neon-cyan (#00e5ff) is ~1.2:1 on white — replace with sky-600 (#0284c7 ~4.7:1) */
[data-theme="light"] .text-neon-cyan { color: #0284c7; }

/* neon-pink / brand (#ff2d7b) is ~3.6:1 — replace with pink-700 (#be185d ~5.0:1) */
[data-theme="light"] .text-neon-pink,
[data-theme="light"] .hover\:text-neon-pink:hover,
[data-theme="light"] .text-brand-400,
[data-theme="light"] .text-brand-500,
[data-theme="light"] .text-brand-600 { color: #be185d; }
[data-theme="light"] .text-brand-700,
[data-theme="light"] .hover\:text-brand-700:hover { color: #9d174d; } /* pink-800 ~6.9:1 */

/* Pastel status colors fail on light backgrounds: */
[data-theme="light"] .text-green-400 { color: #16a34a; } /* green-600 ~5.1:1 */
[data-theme="light"] .text-amber-400 { color: #b45309; } /* amber-700 ~5.4:1 */
[data-theme="light"] .text-red-400 { color: #dc2626; } /* red-600 ~5.9:1 */

/* Adjust badge backgrounds to match the darker text colors */
[data-theme="light"] .bg-green-500\/10 { background-color: rgba(22,163,74,0.08); }
[data-theme="light"] .bg-amber-500\/10 { background-color: rgba(180,83,9,0.08); }
[data-theme="light"] .bg-red-500\/10 { background-color: rgba(220,38,38,0.08); }
16 changes: 16 additions & 0 deletions internal/ui/icon/theme.templ
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,24 @@ package icon

import "strconv"

// Theme is the WordPress/data-source theme icon (image/picture).
templ Theme(size int) {
<svg width={ strconv.Itoa(size) } height={ strconv.Itoa(size) } viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" aria-hidden="true">
<path d="M16.375 4.5H4.625a.125.125 0 0 0-.125.125v8.254l2.859-1.54a.75.75 0 0 1 .68-.016l2.384 1.142 2.89-2.074a.75.75 0 0 1 .874 0l2.313 1.66V4.625a.125.125 0 0 0-.125-.125Zm.125 9.398-2.75-1.975-2.813 2.02a.75.75 0 0 1-.76.067l-2.444-1.17L4.5 14.583v1.792c0 .069.056.125.125.125h11.75a.125.125 0 0 0 .125-.125v-2.477ZM4.625 3C3.728 3 3 3.728 3 4.625v11.75C3 17.273 3.728 18 4.625 18h11.75c.898 0 1.625-.727 1.625-1.625V4.625C18 3.728 17.273 3 16.375 3H4.625ZM20 8v11c0 .69-.31 1-.999 1H6v1.5h13.001c1.52 0 2.499-.982 2.499-2.5V8H20Z"></path>
</svg>
}

// Sun icon — shown in dark mode (click to switch to light)
templ Sun(size int) {
<svg width={ strconv.Itoa(size) } height={ strconv.Itoa(size) } viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path>
</svg>
}

// Moon icon — shown in light mode (click to switch to dark)
templ Moon(size int) {
<svg width={ strconv.Itoa(size) } height={ strconv.Itoa(size) } viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
}
26 changes: 25 additions & 1 deletion internal/ui/layout/base.templ
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

templ Base(pd ui.PageData) {
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
Expand All @@ -16,6 +16,7 @@ templ Base(pd ui.PageData) {
@Head(pd)
<link rel="icon" href="/favicon.svg" type="image/svg+xml"/>
<link rel="icon" href="/favicon.ico" sizes="32x32"/>
@themeInitScript()
<link rel="stylesheet" href={ "/static/css/styles.css?v=" + pd.Version }/>
<script src={ "/static/js/htmx.min.js?v=" + pd.Version }></script>
</head>
Expand All @@ -34,6 +35,7 @@ templ Base(pd ui.PageData) {
@nav.Footer(pd.Version, pd.RequestStart)
<div id="context-modal"></div>
@mobileMenuScript()
@themeToggleScript()
</body>
</html>
}
Expand All @@ -48,6 +50,28 @@ templ neonGrid() {
</div>
}

// themeInitScript runs before CSS loads to avoid flash-of-wrong-theme.
templ themeInitScript() {
<script>
(function() {
var t = localStorage.getItem('veloria-theme');
if (!t) t = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
document.documentElement.dataset.theme = t;
})();
</script>
}

templ themeToggleScript() {
<script>
function toggleTheme() {
var html = document.documentElement;
var next = html.dataset.theme === 'light' ? 'dark' : 'light';
html.dataset.theme = next;
localStorage.setItem('veloria-theme', next);
}
</script>
}

templ mobileMenuScript() {
<script>
function toggleMobileMenu() {
Expand Down
4 changes: 4 additions & 0 deletions internal/ui/nav/header.templ
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ templ DesktopHeader(currentPath string, u *user.User) {
@DesktopNavLink(item)
}
<div class="w-px h-6 mx-2 bg-gradient-to-b from-transparent via-white/10 to-transparent"></div>
<button onclick="toggleTheme()" class="btn-icon-theme" aria-label="Toggle theme" hx-boost="false">
<span class="theme-sun">@icon.Sun(18)</span>
<span class="theme-moon">@icon.Moon(18)</span>
</button>
if u != nil {
@userSection(u)
} else {
Expand Down
12 changes: 9 additions & 3 deletions internal/ui/nav/mobile_menu.templ
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ templ MobileHeader(currentPath string, u *user.User) {
@icon.Logo(28)
<span class="text-lg font-bold tracking-wide text-text-bright">Veloria</span>
</a>
<button type="button" onclick="toggleMobileMenu()" class="p-2 rounded-xl hover:bg-white/5 transition" aria-label="Open menu" aria-expanded="false" id="mobile-menu-button" hx-boost="false">
@icon.Menu(24)
</button>
<div class="flex items-center gap-1">
<button onclick="toggleTheme()" class="btn-icon-theme" aria-label="Toggle theme" hx-boost="false">
<span class="theme-sun">@icon.Sun(20)</span>
<span class="theme-moon">@icon.Moon(20)</span>
</button>
<button type="button" onclick="toggleMobileMenu()" class="p-2 rounded-xl hover:bg-white/5 transition" aria-label="Open menu" aria-expanded="false" id="mobile-menu-button" hx-boost="false">
@icon.Menu(24)
</button>
</div>
</div>
</header>
<!-- Mobile Menu Overlay -->
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/page/extension.templ
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ templ Extension(data ui.ExtensionData) {
if data.Indexed && len(data.LargestFiles) > 0 {
<!-- Largest Files -->
<div class="glass-inner overflow-hidden mb-6">
<div class="px-6 py-4" style="border-bottom: 1px solid rgba(255,255,255,0.08);">
<div class="px-6 py-4 border-b-medium">
<h2 class="text-xl font-semibold text-text-bright">Largest Files</h2>
<p class="text-sm text-text-muted">Top { strconv.Itoa(len(data.LargestFiles)) } by size</p>
</div>
Expand Down
4 changes: 2 additions & 2 deletions internal/ui/partial/datasource_items.templ
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ templ DataSourceItems(data ui.DataSourceItemsData) {
if len(data.Items) > 0 {
<div>
for _, item := range data.Items {
<a href={ templ.SafeURL("/data-sources/" + data.DataSource + "/" + item.Slug) } class="px-6 py-4 flex items-center justify-between hover:bg-white/[0.08] transition-colors block" style="border-bottom: 1px solid rgba(255,255,255,0.06);">
<a href={ templ.SafeURL("/data-sources/" + data.DataSource + "/" + item.Slug) } class="px-6 py-4 flex items-center justify-between hover:bg-white/[0.08] transition-colors block border-b-medium">
<div class="flex items-center gap-4">
<div>
<h3 class="text-text-bright font-medium">{ item.Name }</h3>
Expand All @@ -42,7 +42,7 @@ templ DataSourceItems(data ui.DataSourceItemsData) {
</div>
<!-- Pagination -->
if data.TotalPages > 1 {
<div class="px-6 py-4 flex items-center justify-between" style="border-top: 1px solid rgba(255,255,255,0.08);">
<div class="px-6 py-4 flex items-center justify-between border-t-medium">
<p class="text-sm text-text-muted">
{ fmt.Sprintf("Page %d of %d", data.Page, data.TotalPages) }
</p>
Expand Down
Loading