theming done for now
No files matched your search
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ───────────────────────────
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<i class="fa-solid fa-clock-rotate-left text-gray-300 dark:text-gray-600 w-3.5 flex-shrink-0" aria-hidden="true"></i>
|
||||
<i class="fa-solid fa-clock-rotate-left text-ink-faint w-3.5 flex-shrink-0" aria-hidden="true"></i>
|
||||
<span class="flex-1 truncate">${this._escape(r)}</span>
|
||||
<span
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-action="click->search#removeRecent"
|
||||
class="opacity-0 group-hover:opacity-100 p-0.5 rounded
|
||||
text-gray-400 hover:text-gray-600 dark:hover:text-gray-200
|
||||
text-ink-faint hover:text-ink-soft
|
||||
transition-opacity"
|
||||
aria-label="Remove"
|
||||
>
|
||||
|
||||
@@ -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"];
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 469 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 343 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 384 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 386 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 377 KiB |
@@ -6,35 +6,12 @@ FILES=$(find templates -name '*.html.twig')
|
||||
sweep() {
|
||||
perl -0777 -pi -e "s{(?<![\\w:./-])$1}{$2}g" $FILES
|
||||
}
|
||||
|
||||
# ── Muted → strong hover pairs ──────────────────────────────────────────────
|
||||
sweep 'text-zinc-300\s+hover:text-zinc-500\s+dark:text-zinc-600\s+dark:hover:text-zinc-400' 'text-ink-faint hover:text-ink-muted'
|
||||
sweep 'text-ink-muted\s+hover:text-gray-800\s+dark:hover:text-white' 'text-ink-muted hover:text-ink'
|
||||
sweep 'hover:text-zinc-200\s+dark:hover:text-zinc-300' 'hover:text-ink'
|
||||
sweep 'hover:text-blue-600\s+dark:hover:text-blue-300' 'hover:text-accent'
|
||||
sweep 'hover:text-red-500\s+dark:hover:text-red-400' 'hover:text-danger'
|
||||
sweep 'text-zinc-900\s+dark:text-zinc-300' 'text-ink'
|
||||
|
||||
# ── Hovers ──────────────────────────────────────────────────────────────────
|
||||
sweep 'hover:bg-gray-100\s+dark:hover:bg-white/10' 'hover:bg-hover'
|
||||
sweep 'hover:bg-zinc-50\s+dark:hover:bg-zinc-700' 'hover:bg-hover'
|
||||
sweep 'hover:bg-white/70\s+dark:hover:bg-white/10' 'hover:bg-hover'
|
||||
sweep 'focus:bg-white\s+dark:focus:bg-gray-700' 'focus:bg-field'
|
||||
|
||||
# ── Borders ─────────────────────────────────────────────────────────────────
|
||||
sweep 'border-gray-100/80\s+dark:border-white/\[0\.06\]' 'border-line'
|
||||
|
||||
# ── Status dots (bg-line is too faint at 0.06 — use ink-faint) ──────────────
|
||||
sweep 'bg-zinc-300 dark:bg-zinc-600' 'bg-ink-faint'
|
||||
sweep 'bg-gray-300 dark:bg-gray-700' 'bg-ink-faint'
|
||||
|
||||
# ── Toast (inverted surface) ────────────────────────────────────────────────
|
||||
sweep 'bg-zinc-900\s+dark:bg-zinc-100\s+text-zinc-100\s+dark:text-zinc-900' 'bg-inverse text-inverse-ink'
|
||||
|
||||
# ── Search filter pill ──────────────────────────────────────────────────────
|
||||
sweep 'bg-blue-50\s+dark:bg-blue-950/40\s+text-blue-700\s+dark:text-blue-300\s*\n?\s*border\s+border-blue-200/60\s+dark:border-blue-700/40' 'bg-accent-soft text-accent border border-accent/20'
|
||||
|
||||
# ── Avatar button ───────────────────────────────────────────────────────────
|
||||
sweep 'bg-blue-600\s+hover:ring-2\s+hover:ring-blue-400\s+hover:ring-offset-1\s+dark:hover:ring-offset-gray-900' 'bg-accent hover:ring-2 hover:ring-accent/60 hover:ring-offset-1 ring-offset-surface'
|
||||
|
||||
sweep 'bg-blue-600(?!\s+hover)' 'bg-accent'
|
||||
sweep 'text-blue-600\s+border-blue-600' 'text-accent border-accent'
|
||||
sweep 'focus:ring-blue-500' 'focus:ring-accent'
|
||||
sweep 'focus-visible:ring-blue-500' 'focus-visible:ring-accent'
|
||||
sweep 'hover:border-blue-500' 'hover:border-accent'
|
||||
sweep 'focus:border-blue-500' 'focus:border-accent'
|
||||
sweep 'text-blue-500' 'text-accent'
|
||||
sweep 'bg-blue-500(?![/\w-])' 'bg-accent'
|
||||
echo "Done. Now: git diff"
|
||||
@@ -1508,8 +1508,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||
* }
|
||||
* @psalm-type MercureConfig = array{
|
||||
* hubs?: array<string, array{ // Default: []
|
||||
* url?: scalar|Param|null, // URL of the hub's publish endpoint
|
||||
* public_url?: scalar|Param|null, // URL of the hub's public endpoint // Default: null
|
||||
* url?: scalar|Param|null, // URL of the hub's publish endpoint // Default: null
|
||||
* public_url?: scalar|Param|null, // URL of the hub's public endpoint
|
||||
* jwt?: string|array{ // JSON Web Token configuration.
|
||||
* value?: scalar|Param|null, // JSON Web Token to use to publish to this hub.
|
||||
* provider?: scalar|Param|null, // The ID of a service to call to provide the JSON Web Token.
|
||||
|
||||
@@ -172,36 +172,36 @@ final class Appearance
|
||||
}
|
||||
|
||||
if (true === isset($data['paneAlpha'])) {
|
||||
$this->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;
|
||||
|
||||
@@ -48,13 +48,13 @@ Pagination variables expected from the controller (all optional, fallback to 0/1
|
||||
{% include '_partials/_sidebar.html.twig' with { _drawerMode: true } %}
|
||||
</div>
|
||||
|
||||
<div class="flex h-full flex-col p-3 gap-3">
|
||||
<div class="flex h-full flex-col p-gutter gap-gutter">
|
||||
|
||||
{# Full-width topbar — passes burger button context #}
|
||||
{% include '_partials/_topbar.html.twig' with { _showBurger: true } %}
|
||||
|
||||
{# Sidebar + content #}
|
||||
<div class="flex flex-1 gap-3 overflow-hidden">
|
||||
<div class="flex flex-1 gap-gutter overflow-hidden">
|
||||
|
||||
{# Sidebar — desktop only (hidden on mobile, shown in drawer) #}
|
||||
<div class="hidden md:flex">
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 32 32%22><text y=%221em%22 font-size=%2224%22>✉️</text></svg>">
|
||||
|
||||
{# Must be first thing in <head> to avoid flash of wrong theme #}
|
||||
<script>
|
||||
if (localStorage.getItem('theme') === 'dark' ||
|
||||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
if (document.documentElement.dataset.theme === 'system' &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
</script>
|
||||
@@ -42,11 +41,5 @@
|
||||
{% include '_partials/_modal.html.twig' %}
|
||||
<turbo-frame id="compose_dock" class="fixed bottom-4 right-6 flex items-end gap-3"></turbo-frame>
|
||||
<div id="toast-region" class="fixed bottom-4 left-4 z-[60] flex flex-col gap-2 items-end"></div>
|
||||
<script>
|
||||
if (document.documentElement.dataset.theme === 'system' &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -33,7 +33,7 @@
|
||||
px-3 text-sm text-ink
|
||||
placeholder:text-ink-faint
|
||||
shadow-sm shadow-zinc-950/5
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent
|
||||
transition-colors duration-100
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
' ~ (attr.class ?? '')
|
||||
@@ -55,7 +55,7 @@
|
||||
px-3 text-sm text-ink
|
||||
placeholder:text-ink-faint
|
||||
shadow-sm shadow-zinc-950/5
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent
|
||||
transition-colors duration-100
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none
|
||||
@@ -74,7 +74,7 @@
|
||||
px-3 py-2 text-sm text-ink
|
||||
placeholder:text-ink-faint
|
||||
shadow-sm shadow-zinc-950/5
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent
|
||||
resize-y transition-colors duration-100
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
' ~ (attr.class ?? ''),
|
||||
@@ -90,7 +90,7 @@
|
||||
bg-field
|
||||
pl-3 pr-8 text-sm text-ink
|
||||
shadow-sm shadow-zinc-950/5 appearance-none
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent
|
||||
transition-colors duration-100
|
||||
cursor-pointer
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
@@ -125,7 +125,7 @@
|
||||
class: 'size-4 rounded border-field
|
||||
text-accent
|
||||
bg-field
|
||||
focus:ring-2 focus:ring-blue-500 focus:ring-offset-0
|
||||
focus:ring-2 focus:ring-accent focus:ring-offset-0
|
||||
cursor-pointer
|
||||
' ~ (attr.class ?? '')
|
||||
}) %}
|
||||
|
||||
@@ -121,8 +121,8 @@ The button itself carries `group` and `relative`.
|
||||
border border-field
|
||||
bg-field
|
||||
cursor-pointer transition-colors duration-100
|
||||
hover:border-blue-500 focus-visible:outline-none
|
||||
focus-visible:ring-2 focus-visible:ring-blue-500"
|
||||
hover:border-accent focus-visible:outline-none
|
||||
focus-visible:ring-2 focus-visible:ring-accent"
|
||||
aria-label="{{ 'toolbar.select_all'|trans }}"
|
||||
>
|
||||
{# Checkmark — shown when all selected #}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
text-ink-faint
|
||||
hover:text-ink
|
||||
hover:bg-hover
|
||||
transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500
|
||||
transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent
|
||||
"
|
||||
>
|
||||
<i class="fa-solid fa-xmark text-sm" aria-hidden="true"></i>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
bg-field
|
||||
border border-field
|
||||
hover:bg-hover
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1
|
||||
transition-colors duration-100"
|
||||
>{{ cancel_label }}</button>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
{% if danger %}
|
||||
bg-red-600 hover:bg-red-700 active:bg-red-800 focus-visible:ring-red-500
|
||||
{% else %}
|
||||
bg-accent hover:bg-accent-strong active:bg-blue-800 focus-visible:ring-blue-500
|
||||
bg-accent hover:bg-accent-strong active:bg-blue-800 focus-visible:ring-accent
|
||||
{% endif %}"
|
||||
>{{ submit_label }}</button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% macro unread_badge(count) %}
|
||||
{% if count > 0 %}
|
||||
<span class="text-xs font-semibold px-1.5 py-0.5 rounded-full bg-blue-600 text-white">
|
||||
<span class="text-xs font-semibold px-1.5 py-0.5 rounded-full bg-accent text-white">
|
||||
{{ count }}
|
||||
</span>
|
||||
{% 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
|
||||
<aside id="{{ _drawerMode is defined and _drawerMode ? 'sidebar-drawer-inner' : 'sidebar' }}"
|
||||
{% if not (_drawerMode is defined and _drawerMode) %}data-turbo-permanent{% endif %}
|
||||
data-controller="sidebar"
|
||||
class="flex flex-col w-56 h-full flex-shrink-0 bg-surface rounded-pane shadow-pop border border-line">
|
||||
class="flex flex-col w-56 h-full flex-shrink-0 pane">
|
||||
|
||||
{# Compose #}
|
||||
<div class="px-3 py-3 border-b border-line">
|
||||
<a href="{{ path('app_compose_new') }}"
|
||||
data-turbo-frame="compose_dock"
|
||||
data-action="{{ _drawerClose }}"
|
||||
class="w-full flex items-center justify-center gap-2 bg-accent hover:bg-accent-strong text-white text-sm font-medium rounded-lg px-3 py-2 transition-colors">
|
||||
class="w-full flex items-center justify-center gap-2 bg-accent hover:bg-accent-strong text-white text-sm font-medium rounded-lg px-3 py-row transition-colors">
|
||||
<i class="fa-solid fa-plus w-4 h-4" aria-hidden="true"></i>
|
||||
{{ 'sidebar.compose'|trans }}
|
||||
</a>
|
||||
|
||||
@@ -20,7 +20,7 @@ Variables:
|
||||
'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',
|
||||
}[chipLabel.color ?? 'gray'] ?? 'bg-ink-faint' }}"
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
></a>
|
||||
|
||||
<div
|
||||
class="pointer-events-auto flex items-center gap-3 pl-3 pr-4 py-2.5 cursor-pointer transition-colors
|
||||
class="pointer-events-auto flex items-center gap-3 pl-3 pr-4 py-row cursor-pointer transition-colors
|
||||
{{ unread ? 'bg-raised' : 'bg-transparent' }}
|
||||
hover:bg-hover"
|
||||
>
|
||||
@@ -63,7 +63,7 @@
|
||||
type="checkbox"
|
||||
data-action="change->message-row#toggleSelect"
|
||||
data-thread-select value="{{ rowId }}"
|
||||
class="z-10 w-4 h-4 rounded border-field text-blue-600 focus:ring-blue-500 focus:ring-offset-0 cursor-pointer flex-shrink-0"
|
||||
class="z-10 w-4 h-4 rounded border-field accent-accent focus:ring-accent focus:ring-offset-0 cursor-pointer flex-shrink-0"
|
||||
aria-label="{{ 'thread_row.select'|trans({'%subject%': subject}) }}"
|
||||
>
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
border border-transparent rounded-xl
|
||||
text-ink
|
||||
placeholder:text-ink-faint
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500
|
||||
focus:outline-none focus:ring-2 focus:ring-accent
|
||||
focus:bg-field
|
||||
transition-colors"
|
||||
>
|
||||
@@ -89,15 +89,16 @@
|
||||
{# Right actions #}
|
||||
<div class="flex items-center gap-1 flex-shrink-0">
|
||||
{# User menu #}
|
||||
<div class="relative ml-1">
|
||||
<div class="relative ml-1" data-controller="user-menu">
|
||||
<button id="user-menu-btn"
|
||||
data-action="click->user-menu#toggle"
|
||||
class="flex cursor-pointer items-center justify-center w-8 h-8 rounded-full bg-accent hover:ring-2 hover:ring-accent/60 hover:ring-offset-1 ring-offset-surface text-white text-xs font-semibold transition-all"
|
||||
aria-label="{{ 'header.user_menu.label'|trans({ '%email%': app.user.email }) }}"
|
||||
title="{{ app.user.email }}">
|
||||
{{ app.user.email | first | upper }}
|
||||
</button>
|
||||
|
||||
<div id="user-menu" class="hidden absolute right-0 mt-2 w-56 bg-surface border border-line rounded-xl shadow-lg py-1 z-[200]">
|
||||
<div id="user-menu" data-user-menu-target="menu" class="hidden absolute right-0 mt-2 w-56 bg-surface border border-line rounded-xl shadow-lg py-1 z-[200]">
|
||||
<div class="px-4 py-2.5 border-b border-line">
|
||||
<p class="text-xs text-gray-400">{{ 'header.user_menu.signed_in_as'|trans }}</p>
|
||||
<p class="text-sm font-medium text-ink truncate">{{ app.user.email }}</p>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<div role="alert" class="mx-5 mt-5 flex items-start gap-2.5 rounded-xl
|
||||
alert-danger
|
||||
px-3.5 py-3 text-sm text-danger">
|
||||
<i class="fa-solid fa-circle-exclamation mt-0.5 shrink-0 text-red-500/80" aria-hidden="true"></i>
|
||||
<i class="fa-solid fa-circle-exclamation mt-0.5 shrink-0 text-danger opacity-80" aria-hidden="true"></i>
|
||||
<div class="space-y-0.5">
|
||||
{% for error in form.vars.errors %}<p class="leading-snug">{{ error.message }}</p>{% endfor %}
|
||||
</div>
|
||||
@@ -96,7 +96,7 @@
|
||||
<div class="flex items-start gap-3 rounded-xl
|
||||
alert-info
|
||||
px-4 py-3 text-sm text-accent">
|
||||
<i class="fa-solid fa-circle-info mt-0.5 shrink-0 text-blue-500/70" aria-hidden="true"></i>
|
||||
<i class="fa-solid fa-circle-info mt-0.5 shrink-0 text-accent/70" aria-hidden="true"></i>
|
||||
<p>{{ 'account.oauth.info'|trans }}</p>
|
||||
</div>
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
bg-sunken
|
||||
border border-line
|
||||
hover:bg-hover
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1
|
||||
transition-colors duration-100 cursor-pointer"
|
||||
>{{ 'form.cancel'|trans }}</button>
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
data-account-form-target="submitBtn"
|
||||
class="inline-flex items-center justify-center h-8 px-3.5 rounded-lg text-sm font-medium text-white
|
||||
bg-blue-600 hover:bg-blue-500 active:bg-blue-700
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1
|
||||
transition-colors duration-100 cursor-pointer"
|
||||
>{{ 'account.add_title'|trans }}</button>
|
||||
</div>
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
{% include '_partials/_sidebar.html.twig' with { _drawerMode: true } %}
|
||||
</div>
|
||||
|
||||
<div class="flex h-full flex-col p-3 gap-3">
|
||||
<div class="flex h-full flex-col p-gutter gap-gutter">
|
||||
|
||||
{% include '_partials/_topbar.html.twig' with { _showBurger: true } %}
|
||||
|
||||
<div class="flex flex-1 gap-3 overflow-hidden">
|
||||
<div class="flex flex-1 gap-gutter overflow-hidden">
|
||||
|
||||
{# Sidebar — desktop #}
|
||||
<div class="hidden md:flex">
|
||||
|
||||
@@ -242,7 +242,7 @@
|
||||
hover:bg-hover cursor-pointer relative">
|
||||
<span class="font-semibold">A</span>
|
||||
<span data-compose-toolbar-target="colorSwatch"
|
||||
class="block w-3.5 h-[3px] bg-blue-500 rounded-sm"></span>
|
||||
class="block w-3.5 h-[3px] bg-accent rounded-sm"></span>
|
||||
<input type="color" data-action="input->compose-toolbar#changeColor"
|
||||
class="absolute opacity-0 w-0 h-0 pointer-events-none" tabindex="-1">
|
||||
</label>
|
||||
@@ -286,7 +286,7 @@
|
||||
|
||||
{# Action bar #}
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex items-center rounded-full overflow-hidden bg-blue-600 mr-2 shrink-0">
|
||||
<div class="flex items-center rounded-full overflow-hidden bg-accent mr-2 shrink-0">
|
||||
<button type="submit"
|
||||
class="text-white text-sm font-semibold px-4 py-1.5 hover:bg-white/10 transition-colors">
|
||||
Send
|
||||
|
||||
@@ -8,7 +8,7 @@ account form). Frosted-glass design language, zinc form fields, Tailwind only.
|
||||
{{ form_label(form.name, null, { label_attr: { class: 'block text-xs font-medium text-ink-muted mb-1' } }) }}
|
||||
{{ form_widget(form.name, { attr: {
|
||||
class: 'w-full rounded-lg bg-sunken border border-transparent
|
||||
focus:border-blue-500 focus:ring-0 text-sm px-3 py-2
|
||||
focus:border-accent focus:ring-0 text-sm px-3 py-2
|
||||
text-ink placeholder-gray-400'
|
||||
} }) }}
|
||||
{{ form_errors(form.name) }}
|
||||
@@ -18,7 +18,7 @@ account form). Frosted-glass design language, zinc form fields, Tailwind only.
|
||||
{{ form_label(form.account, null, { label_attr: { class: 'block text-xs font-medium text-ink-muted mb-1' } }) }}
|
||||
{{ form_widget(form.account, { attr: {
|
||||
class: 'w-full rounded-lg bg-sunken border border-transparent
|
||||
focus:border-blue-500 focus:ring-0 text-sm px-3 py-2
|
||||
focus:border-accent focus:ring-0 text-sm px-3 py-2
|
||||
text-ink'
|
||||
} }) }}
|
||||
{{ form_errors(form.account) }}
|
||||
@@ -28,7 +28,7 @@ account form). Frosted-glass design language, zinc form fields, Tailwind only.
|
||||
{{ form_label(form.parent, null, { label_attr: { class: 'block text-xs font-medium text-ink-muted mb-1' } }) }}
|
||||
{{ form_widget(form.parent, { attr: {
|
||||
class: 'w-full rounded-lg bg-sunken border border-transparent
|
||||
focus:border-blue-500 focus:ring-0 text-sm px-3 py-2
|
||||
focus:border-accent focus:ring-0 text-sm px-3 py-2
|
||||
text-ink'
|
||||
} }) }}
|
||||
{{ form_errors(form.parent) }}
|
||||
@@ -38,7 +38,7 @@ account form). Frosted-glass design language, zinc form fields, Tailwind only.
|
||||
{{ form_label(form.color, null, { label_attr: { class: 'block text-xs font-medium text-ink-muted mb-1' } }) }}
|
||||
{{ form_widget(form.color, { attr: {
|
||||
class: 'w-full rounded-lg bg-sunken border border-transparent
|
||||
focus:border-blue-500 focus:ring-0 text-sm px-3 py-2
|
||||
focus:border-accent focus:ring-0 text-sm px-3 py-2
|
||||
text-ink'
|
||||
} }) }}
|
||||
{{ form_errors(form.color) }}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{% if not label.isSystem %}
|
||||
{% set _unread = sidebarCounts.forLabel(label) %}
|
||||
{% if _unread > 0 %}
|
||||
<span class="text-xs font-semibold px-1.5 py-0.5 rounded-full bg-blue-600 text-white">
|
||||
<span class="text-xs font-semibold px-1.5 py-0.5 rounded-full bg-accent text-white">
|
||||
{{ _unread }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
<a
|
||||
href="{{ path('app_mail_inbox', {tab: tabCase.value}) }}"
|
||||
data-turbo-frame="inbox-list-frame"
|
||||
class="px-4 py-3 font-medium border-b-2 -mb-px transition-colors {{ isActive ? 'text-blue-600 border-blue-600' : 'text-ink-muted hover:text-gray-700 border-transparent hover:border-gray-300' }}"
|
||||
class="px-4 py-3 font-medium border-b-2 -mb-px transition-colors {{ isActive ? 'text-accent border-accent' : 'text-ink-muted hover:text-gray-700 border-transparent hover:border-gray-300' }}"
|
||||
>
|
||||
{{ ('tabs.' ~ tabCase.value)|trans }}
|
||||
{% if unreadCount > 0 %}
|
||||
<span class="ml-1 text-xs {{ isActive ? 'text-blue-600' : 'text-gray-400' }}">{{ unreadCount }}</span>
|
||||
<span class="ml-1 text-xs {{ isActive ? 'text-accent opacity-70' : 'text-gray-400' }}">{{ unreadCount }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{# Logo #}
|
||||
<div class="flex flex-col items-center mb-8">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i class="fa-solid fa-envelope text-blue-600 text-2xl" aria-hidden="true"></i>
|
||||
<i class="fa-solid fa-envelope text-accent text-2xl" aria-hidden="true"></i>
|
||||
<span class="text-xl font-semibold text-ink">{{ 'app.name'|trans }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-ink-muted">{{ 'login.tagline'|trans }}</p>
|
||||
@@ -41,7 +41,7 @@
|
||||
autocomplete="email"
|
||||
autofocus
|
||||
required
|
||||
class="w-full px-3 py-2 text-sm rounded-lg border border-field bg-field text-ink placeholder:text-ink-faint focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
class="w-full px-3 py-2 text-sm rounded-lg border border-field bg-field text-ink placeholder:text-ink-faint focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent transition-colors"
|
||||
placeholder="you@example.com"
|
||||
>
|
||||
</div>
|
||||
@@ -62,7 +62,7 @@
|
||||
autocomplete="current-password"
|
||||
required
|
||||
value="{{ 'APP_DEV_USER_PASSWORD'|get_env }}"
|
||||
class="w-full px-3 py-2 text-sm rounded-lg border border-field bg-field text-ink placeholder:text-ink-faint focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
class="w-full px-3 py-2 text-sm rounded-lg border border-field bg-field text-ink placeholder:text-ink-faint focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent transition-colors"
|
||||
placeholder="••••••••"
|
||||
>
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="mt-6 w-full bg-accent hover:bg-accent-strong text-white text-sm font-medium py-2 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ring-offset-surface"
|
||||
class="mt-6 w-full bg-accent hover:bg-accent-strong text-white text-sm font-medium py-2 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 ring-offset-surface"
|
||||
>
|
||||
{{ 'login.form.submit'|trans }}
|
||||
</button>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<input type="hidden" data-appearance-field="theme" value="{{ a.theme.value }}">
|
||||
<input type="hidden" data-appearance-target="theme" data-appearance-field="theme" value="{{ a.theme.value }}">
|
||||
</section>
|
||||
|
||||
{# ── Glass ─────────────────────────────────────────────────────────── #}
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
<div class="grid grid-cols-3 sm:grid-cols-5 gap-3 mb-4">
|
||||
<label class="cursor-pointer">
|
||||
<input type="radio" name="backgroundKind" class="peer sr-only" value="theme"
|
||||
<input type="radio" name="backgroundKind" class="sr-only" value="preset"
|
||||
data-appearance-field="backgroundKind"
|
||||
data-action="change->appearance#pickBackground"
|
||||
{{ a.backgroundKind.value == 'theme' ? 'checked' : '' }}>
|
||||
|
||||
@@ -39,7 +39,7 @@ Variables:
|
||||
'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',
|
||||
}[label.color ?? 'gray'] ?? 'bg-ink-faint' }}"
|
||||
@@ -65,7 +65,7 @@ Variables:
|
||||
aria-label="{{ label.isVisible ? 'settings.labels.hide'|trans : 'settings.labels.show'|trans }}"
|
||||
title="{{ label.isVisible ? 'settings.labels.hide'|trans : 'settings.labels.show'|trans }}"
|
||||
>
|
||||
<i class="fa-solid {{ label.isVisible ? 'fa-eye text-blue-500' : 'fa-eye-slash' }} text-sm" aria-hidden="true"></i>
|
||||
<i class="fa-solid {{ label.isVisible ? 'fa-eye text-accent' : 'fa-eye-slash' }} text-sm" aria-hidden="true"></i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
{% include '_partials/_sidebar.html.twig' with { _drawerMode: true } %}
|
||||
</div>
|
||||
|
||||
<div class="flex h-full flex-col p-3 gap-3">
|
||||
<div class="flex h-full flex-col p-gutter gap-gutter">
|
||||
|
||||
{% include '_partials/_topbar.html.twig' with { _showBurger: true } %}
|
||||
|
||||
<div class="flex flex-1 gap-3 overflow-hidden">
|
||||
<div class="flex flex-1 gap-gutter overflow-hidden">
|
||||
|
||||
{# Sidebar — desktop #}
|
||||
<div class="hidden md:flex">
|
||||
|
||||
@@ -134,6 +134,7 @@ tabs:
|
||||
promotions: Promotions
|
||||
updates: Updates
|
||||
social: Social
|
||||
forums: Forums
|
||||
|
||||
inbox:
|
||||
empty: No messages in this tab.
|
||||
@@ -156,6 +157,7 @@ trash:
|
||||
|
||||
thread_row:
|
||||
no_subject: (no subject)
|
||||
select: Select
|
||||
message_count: "({ count })"
|
||||
star: Star this message
|
||||
unstar: Unstar this message
|
||||
|
||||