diff --git a/assets/app.js b/assets/app.js index e4af994b..a439c0b8 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,21 +1,3 @@ import './stimulus_bootstrap.js'; import './styles/app.css'; import '@fortawesome/fontawesome-free/css/all.css'; - -document.addEventListener('turbo:load', function() { - // User menu toggle - const btn = document.getElementById('user-menu-btn'); - const menu = document.getElementById('user-menu'); - - if (btn && menu) { - btn.addEventListener('click', function() { - menu.classList.toggle('hidden'); - }); - - document.addEventListener('click', function(event) { - if (!btn.contains(event.target) && !menu.contains(event.target)) { - menu.classList.add('hidden'); - } - }); - } -}); diff --git a/assets/controllers/account_expand_controller.js b/assets/controllers/account_expand_controller.js index 1d21e7de..feb2b307 100644 --- a/assets/controllers/account_expand_controller.js +++ b/assets/controllers/account_expand_controller.js @@ -60,13 +60,9 @@ export default class extends Controller { highlightActive() { this.linkTargets.forEach((link) => { const isActive = window.location.pathname === new URL(link.href).pathname; - link.classList.toggle('bg-blue-50', isActive); - link.classList.toggle('dark:bg-blue-950/40', isActive); - link.classList.toggle('text-blue-700', isActive); - link.classList.toggle('dark:text-blue-400', isActive); - link.classList.toggle('font-medium', isActive); - link.classList.toggle('text-gray-500', !isActive); - link.classList.toggle('dark:text-gray-400', !isActive); + link.classList.toggle('bg-accent-soft', isActive); + link.classList.toggle('text-accent', isActive); + link.classList.toggle('text-ink-muted', !isActive); }); } } diff --git a/assets/controllers/account_form_controller.js b/assets/controllers/account_form_controller.js index 099a1bff..6602feb6 100644 --- a/assets/controllers/account_form_controller.js +++ b/assets/controllers/account_form_controller.js @@ -18,14 +18,11 @@ export default class extends Controller { _activate(panelName) { this.tabTargets.forEach(tab => { const isActive = tab.dataset.panel === panelName; - tab.classList.toggle("border-blue-500", isActive); - tab.classList.toggle("text-blue-600", isActive); - tab.classList.toggle("dark:text-blue-400", isActive); - tab.classList.toggle("border-transparent", !isActive); - tab.classList.toggle("text-zinc-500", !isActive); - tab.classList.toggle("dark:text-zinc-400", !isActive); - tab.classList.toggle("hover:text-zinc-700", !isActive); - tab.classList.toggle("dark:hover:text-zinc-200", !isActive); + tab.classList.toggle("border-accent", isActive); + tab.classList.toggle("text-accent", isActive); + + tab.classList.toggle("text-ink-muted", !isActive); + tab.classList.toggle("hover:text-ink-soft", !isActive); }); this.panelTargets.forEach(panel => { diff --git a/assets/controllers/appearance_controller.js b/assets/controllers/appearance_controller.js index 800353af..58911122 100644 --- a/assets/controllers/appearance_controller.js +++ b/assets/controllers/appearance_controller.js @@ -8,8 +8,7 @@ export default class extends Controller { resetUrl: String, }; - static targets = ['paneAlpha', 'paneBlur', 'radius', 'scrimAlpha', 'accent', 'importInput', 'uploadInput']; - + static targets = ['paneAlpha', 'paneBlur', 'radius', 'scrimAlpha', 'accent', 'theme', 'importInput', 'uploadInput']; connect() { this.root = document.documentElement; this.pending = null; @@ -34,6 +33,8 @@ export default class extends Controller { const theme = event.currentTarget.dataset.theme; const isDark = event.currentTarget.dataset.dark === '1'; + this.themeTarget.value = theme; + this.root.dataset.theme = theme; this.root.classList.toggle('dark', isDark); @@ -61,7 +62,15 @@ export default class extends Controller { this.queue(); } - pickBackground() { + pickBackground(event) { + if (event.currentTarget.dataset.appearanceField === 'backgroundPreset') { + const kindInput = this.element.querySelector('[data-appearance-field="backgroundKind"][value="preset"]'); + + if (kindInput) { + kindInput.checked = true; + } + } + this.queue(0); } diff --git a/assets/controllers/compose_controller.js b/assets/controllers/compose_controller.js index 3e771672..f5ea8ff1 100644 --- a/assets/controllers/compose_controller.js +++ b/assets/controllers/compose_controller.js @@ -278,10 +278,8 @@ export default class extends Controller { this.fromDropdownTarget.querySelectorAll('button').forEach(b => { const selected = b.dataset.value === value; - b.classList.toggle('bg-blue-50', selected); - b.classList.toggle('dark:bg-blue-500/10', selected); - b.classList.toggle('text-blue-600', selected); - b.classList.toggle('dark:text-blue-300', selected); + b.classList.toggle('bg-accent-soft', selected); + b.classList.toggle('text-accent', selected); }); this._closeFromDropdown(); @@ -321,11 +319,11 @@ export default class extends Controller { const url = this.hasDraftUrlValue ? this.draftUrlValue : form.action; const status = this.hasSaveStatusTarget ? this.saveStatusTarget : null; - + const STATUS_CLASSES = ['text-ink-faint', 'text-danger', 'text-success']; if (status) { status.textContent = 'Saving…'; - status.classList.remove('text-red-500', 'text-green-600'); - status.classList.add('text-gray-400'); + status.classList.remove(...STATUS_CLASSES); + status.classList.add('text-ink-faint'); } try { @@ -355,8 +353,8 @@ export default class extends Controller { } status.textContent = 'Draft saved'; - status.classList.remove('text-gray-400', 'text-red-500'); - status.classList.add('text-green-600'); + status.classList.remove(...STATUS_CLASSES); + status.classList.add('text-success'); } } else { throw new Error('Server error'); @@ -364,8 +362,8 @@ export default class extends Controller { } catch (_) { if (status) { status.textContent = 'Failed to save'; - status.classList.remove('text-gray-400', 'text-green-600'); - status.classList.add('text-red-500'); + status.classList.remove(...STATUS_CLASSES); + status.classList.add('text-danger'); } } } diff --git a/assets/controllers/compose_toolbar_controller.js b/assets/controllers/compose_toolbar_controller.js index 71e28906..b3f2f9d9 100644 --- a/assets/controllers/compose_toolbar_controller.js +++ b/assets/controllers/compose_toolbar_controller.js @@ -401,10 +401,8 @@ export default class extends Controller { const capitalized = targetName.charAt(0).toUpperCase() + targetName.slice(1); if (!this[`has${capitalized}Target`]) { return; } const target = this[`${targetName}Target`]; - target.classList.toggle('bg-gray-200', active); - target.classList.toggle('dark:bg-gray-600', active); - target.classList.toggle('text-gray-900', active); - target.classList.toggle('dark:text-white', active); + target.classList.toggle('bg-raised', active); + target.classList.toggle('text-ink', active); } // ── Sync contenteditable → hidden input ─────────────────────────── diff --git a/assets/controllers/list_toolbar_controller.js b/assets/controllers/list_toolbar_controller.js index 829fbc94..5dd86a3e 100644 --- a/assets/controllers/list_toolbar_controller.js +++ b/assets/controllers/list_toolbar_controller.js @@ -11,8 +11,8 @@ import { Controller } from "@hotwired/stimulus"; // Classes applied to the checkbox button in each state -const CB_BASE = "border-gray-400 dark:border-gray-500 bg-white dark:bg-gray-800"; -const CB_ACTIVE = "border-blue-600 bg-blue-600 dark:border-blue-500 dark:bg-blue-500"; +const CB_BASE = "border-field bg-field"; +const CB_ACTIVE = "border-accent bg-accent"; // Base path prefix for all status actions — must match Symfony routing. // Individual action URLs are built as: STATUS_BASE + "/thread/{id}/{action}" diff --git a/assets/controllers/search_controller.js b/assets/controllers/search_controller.js index 06039d15..59a767df 100644 --- a/assets/controllers/search_controller.js +++ b/assets/controllers/search_controller.js @@ -154,18 +154,18 @@ export default class extends Controller { data-query="${this._escape(r)}" data-action="click->search#selectRecent" class="group w-full flex items-center gap-2.5 px-3 py-2 - text-sm text-gray-700 dark:text-gray-300 - hover:bg-gray-50 dark:hover:bg-gray-800 + text-sm text-ink-soft + hover:bg-hover transition-colors text-left" > - + ${this._escape(r)} diff --git a/assets/controllers/sidebar_controller.js b/assets/controllers/sidebar_controller.js index 363d93c3..bc1a6d7b 100644 --- a/assets/controllers/sidebar_controller.js +++ b/assets/controllers/sidebar_controller.js @@ -1,8 +1,7 @@ import { Controller } from "@hotwired/stimulus"; -const ACTIVE_CLASSES = ["bg-blue-50", "dark:bg-blue-950/40", "text-blue-700", "dark:text-blue-400", "font-medium"]; -const INACTIVE_CLASSES = ["text-gray-600", "dark:text-gray-400", "hover:bg-gray-100", "dark:hover:bg-gray-800"]; - +const ACTIVE_CLASSES = ["bg-accent-soft", "text-accent", "font-medium"]; +const INACTIVE_CLASSES = ["text-ink-muted", "hover:bg-hover"]; export default class extends Controller { static targets = ["link"]; diff --git a/assets/controllers/user_menu_controller.js b/assets/controllers/user_menu_controller.js new file mode 100644 index 00000000..ee23a0a3 --- /dev/null +++ b/assets/controllers/user_menu_controller.js @@ -0,0 +1,25 @@ +import { Controller } from '@hotwired/stimulus'; + +export default class extends Controller { + static targets = ['menu']; + + connect() { + this.onDocumentClick = this.onDocumentClick.bind(this); + document.addEventListener('click', this.onDocumentClick); + } + + disconnect() { + document.removeEventListener('click', this.onDocumentClick); + } + + toggle(event) { + event.stopPropagation(); + this.menuTarget.classList.toggle('hidden'); + } + + onDocumentClick(event) { + if (false === this.element.contains(event.target)) { + this.menuTarget.classList.add('hidden'); + } + } +} diff --git a/assets/images/backgrounds/aurora-thumb.jpg b/assets/images/backgrounds/aurora-thumb.jpg new file mode 100644 index 00000000..3437c8fd Binary files /dev/null and b/assets/images/backgrounds/aurora-thumb.jpg differ diff --git a/assets/images/backgrounds/aurora.jpg b/assets/images/backgrounds/aurora.jpg new file mode 100644 index 00000000..863cc058 Binary files /dev/null and b/assets/images/backgrounds/aurora.jpg differ diff --git a/assets/images/backgrounds/dunes-thumb.jpg b/assets/images/backgrounds/dunes-thumb.jpg new file mode 100644 index 00000000..1fffa414 Binary files /dev/null and b/assets/images/backgrounds/dunes-thumb.jpg differ diff --git a/assets/images/backgrounds/dunes.jpg b/assets/images/backgrounds/dunes.jpg new file mode 100644 index 00000000..7b1e819b Binary files /dev/null and b/assets/images/backgrounds/dunes.jpg differ diff --git a/assets/images/backgrounds/fog-thumb.jpg b/assets/images/backgrounds/fog-thumb.jpg new file mode 100644 index 00000000..126bd7a7 Binary files /dev/null and b/assets/images/backgrounds/fog-thumb.jpg differ diff --git a/assets/images/backgrounds/fog.jpg b/assets/images/backgrounds/fog.jpg new file mode 100644 index 00000000..7a336eec Binary files /dev/null and b/assets/images/backgrounds/fog.jpg differ diff --git a/assets/images/backgrounds/granite-thumb.jpg b/assets/images/backgrounds/granite-thumb.jpg new file mode 100644 index 00000000..a65d41d0 Binary files /dev/null and b/assets/images/backgrounds/granite-thumb.jpg differ diff --git a/assets/images/backgrounds/granite.jpg b/assets/images/backgrounds/granite.jpg new file mode 100644 index 00000000..7dfe5117 Binary files /dev/null and b/assets/images/backgrounds/granite.jpg differ diff --git a/assets/images/backgrounds/harbour-thumb.jpg b/assets/images/backgrounds/harbour-thumb.jpg new file mode 100644 index 00000000..b77c5a5a Binary files /dev/null and b/assets/images/backgrounds/harbour-thumb.jpg differ diff --git a/assets/images/backgrounds/harbour.jpg b/assets/images/backgrounds/harbour.jpg new file mode 100644 index 00000000..1c0caab1 Binary files /dev/null and b/assets/images/backgrounds/harbour.jpg differ diff --git a/assets/images/backgrounds/linen-thumb.jpg b/assets/images/backgrounds/linen-thumb.jpg new file mode 100644 index 00000000..027406b1 Binary files /dev/null and b/assets/images/backgrounds/linen-thumb.jpg differ diff --git a/assets/images/backgrounds/linen.jpg b/assets/images/backgrounds/linen.jpg new file mode 100644 index 00000000..ee9a391c Binary files /dev/null and b/assets/images/backgrounds/linen.jpg differ diff --git a/assets/images/backgrounds/pine-thumb.jpg b/assets/images/backgrounds/pine-thumb.jpg new file mode 100644 index 00000000..1bf9a593 Binary files /dev/null and b/assets/images/backgrounds/pine-thumb.jpg differ diff --git a/assets/images/backgrounds/pine.jpg b/assets/images/backgrounds/pine.jpg new file mode 100644 index 00000000..51ea84f1 Binary files /dev/null and b/assets/images/backgrounds/pine.jpg differ diff --git a/assets/images/backgrounds/tide-thumb.jpg b/assets/images/backgrounds/tide-thumb.jpg new file mode 100644 index 00000000..4fffae51 Binary files /dev/null and b/assets/images/backgrounds/tide-thumb.jpg differ diff --git a/assets/images/backgrounds/tide.jpg b/assets/images/backgrounds/tide.jpg new file mode 100644 index 00000000..685b39f0 Binary files /dev/null and b/assets/images/backgrounds/tide.jpg differ diff --git a/bin/theme-sweep.sh b/bin/theme-sweep.sh index 85281cc6..7cc3d3f0 100644 --- a/bin/theme-sweep.sh +++ b/bin/theme-sweep.sh @@ -6,35 +6,12 @@ FILES=$(find templates -name '*.html.twig') sweep() { perl -0777 -pi -e "s{(?setPaneAlpha($data['paneAlpha']); + $this->setPaneAlpha(floatval($data['paneAlpha'])); } if (true === isset($data['paneBlur'])) { - $this->setPaneBlur($data['paneBlur']); + $this->setPaneBlur(intval($data['paneBlur'])); } if (true === isset($data['radius'])) { - $this->setRadius($data['radius']); + $this->setRadius(floatval($data['radius'])); } if (true === isset($data['density'])) { - $this->setDensity(Density::tryFrom((string) $data['density']) ?? $this->density); + $this->setDensity(Density::tryFrom($data['density']) ?? $this->density); } if (true === isset($data['backgroundKind'])) { - $this->setBackgroundKind(BackgroundKind::tryFrom((string) $data['backgroundKind']) ?? $this->backgroundKind); + $this->setBackgroundKind(BackgroundKind::tryFrom($data['backgroundKind']) ?? $this->backgroundKind); } if (true === array_key_exists('backgroundPreset', $data)) { - $this->setBackgroundPreset(BackgroundPreset::tryFrom((string) $data['backgroundPreset'])); + $this->setBackgroundPreset(BackgroundPreset::tryFrom( $data['backgroundPreset'])); } if (true === array_key_exists('backgroundSolid', $data)) { - $solid = (string) $data['backgroundSolid']; + $solid = $data['backgroundSolid']; $this->setBackgroundSolid(1 === preg_match('/^#[0-9a-fA-F]{6}$/', $solid) ? strtolower($solid) : null); } if (true === isset($data['scrimAlpha'])) { - $this->setScrimAlpha((float) $data['scrimAlpha']); + $this->setScrimAlpha(floatval($data['scrimAlpha'])); } return $this; diff --git a/templates/_layout/_mailbox.html.twig b/templates/_layout/_mailbox.html.twig index 9fb0a714..2fc6a6a9 100644 --- a/templates/_layout/_mailbox.html.twig +++ b/templates/_layout/_mailbox.html.twig @@ -48,13 +48,13 @@ Pagination variables expected from the controller (all optional, fallback to 0/1 {% include '_partials/_sidebar.html.twig' with { _drawerMode: true } %} -
+
{# Full-width topbar — passes burger button context #} {% include '_partials/_topbar.html.twig' with { _showBurger: true } %} {# Sidebar + content #} -
+
{# Sidebar — desktop only (hidden on mobile, shown in drawer) #} diff --git a/templates/_partials/_sidebar.html.twig b/templates/_partials/_sidebar.html.twig index 07f54bdb..fb6527cf 100644 --- a/templates/_partials/_sidebar.html.twig +++ b/templates/_partials/_sidebar.html.twig @@ -1,6 +1,6 @@ {% macro unread_badge(count) %} {% if count > 0 %} - + {{ count }} {% endif %} @@ -15,7 +15,7 @@ 'amber': 'bg-amber-500', 'green': 'bg-green-500', 'teal': 'bg-teal-500', - 'blue': 'bg-blue-500', + 'blue': 'bg-accent', 'violet': 'bg-violet-500', 'pink': 'bg-pink-500', }[color ?? 'gray'] ?? 'bg-ink-faint' }}" @@ -74,14 +74,14 @@ When true, nav links also dispatch a close action to the parent sidebar-drawer c