From b19bb0c6208744f329cb3e99f4e26a83f191710a Mon Sep 17 00:00:00 2001
From: Luca <102960244+Luca-Timo@users.noreply.github.com>
Date: Tue, 5 May 2026 17:29:15 +0200
Subject: [PATCH] fix(branding): working tooltips, high-contrast selected
states, gallery chrome follows accent
---
.../admin/ThemeCustomizerEnhanced.tsx | 41 +++++++-----
.../src/components/gallery/GalleryLayout.tsx | 2 +-
.../src/components/gallery/GallerySidebar.tsx | 6 +-
.../gallery/GuestNamePromptModal.tsx | 2 +-
.../src/components/gallery/PhotoFilterBar.tsx | 10 +--
frontend/src/index.css | 67 +++++++++++++++++++
6 files changed, 101 insertions(+), 27 deletions(-)
diff --git a/frontend/src/components/admin/ThemeCustomizerEnhanced.tsx b/frontend/src/components/admin/ThemeCustomizerEnhanced.tsx
index 512dd117..ad47c708 100644
--- a/frontend/src/components/admin/ThemeCustomizerEnhanced.tsx
+++ b/frontend/src/components/admin/ThemeCustomizerEnhanced.tsx
@@ -80,7 +80,11 @@ const ColorPickerRow: React.FC<{
@@ -301,7 +305,7 @@ export const ThemeCustomizerEnhanced: React.FC
= (
onClick={() => handlePresetSelect(key)}
className={`relative p-4 rounded-lg border-2 transition-all text-left ${
selectedPreset === key
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
+ ? 'tile-selected'
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
}`}
>
@@ -390,7 +394,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
onClick={() => handleChange('galleryLayout', layout)}
className={`relative p-4 rounded-lg border-2 transition-all ${
localTheme.galleryLayout === layout
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
+ ? 'tile-selected'
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
}`}
>
@@ -737,7 +741,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
onClick={() => handleChange('headerStyle', style)}
className={`relative p-4 rounded-lg border-2 transition-all ${
(localTheme.headerStyle || 'standard') === style
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
+ ? 'tile-selected'
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
}`}
>
@@ -776,7 +780,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
onClick={() => handleChange('heroDividerStyle', divider)}
className={`relative p-3 rounded-lg border-2 transition-all ${
(localTheme.heroDividerStyle || 'wave') === divider
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
+ ? 'tile-selected'
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
}`}
>
@@ -816,7 +820,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
onClick={() => handleChange('controlsStyle', 'classic')}
className={`relative p-4 rounded-lg border-2 transition-all ${
(localTheme.controlsStyle || 'classic') === 'classic'
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
+ ? 'tile-selected'
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
}`}
>
@@ -840,7 +844,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
onClick={() => handleChange('controlsStyle', 'sidebar')}
className={`relative p-4 rounded-lg border-2 transition-all ${
localTheme.controlsStyle === 'sidebar'
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
+ ? 'tile-selected'
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
}`}
>
@@ -922,7 +926,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
}}
className={`px-4 py-2 text-sm font-medium rounded-lg border transition-colors ${
(localTheme.colorMode || 'light') === mode
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30 text-accent-dark'
+ ? 'border-accent-dark bg-accent-dark text-white'
: 'border-neutral-300 dark:border-neutral-600 text-neutral-600 dark:text-neutral-400 hover:bg-neutral-50 dark:hover:bg-neutral-800'
}`}
>
@@ -969,7 +973,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
onClick={() => onForceColorModeChange(value)}
className={`px-4 py-2 text-sm font-medium rounded-lg border transition-colors ${
active
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30 text-accent-dark'
+ ? 'border-accent-dark bg-accent-dark text-white'
: 'border-neutral-300 dark:border-neutral-600 text-neutral-600 dark:text-neutral-400 hover:bg-neutral-50 dark:hover:bg-neutral-800'
}`}
>
@@ -1004,11 +1008,12 @@ export const ThemeCustomizerEnhanced: React.FC = (
{t('branding.colorGroupSurfaces', 'Surfaces')}
@@ -1057,11 +1062,12 @@ export const ThemeCustomizerEnhanced: React.FC = (
{t('branding.colorGroupText', 'Text')}
@@ -1098,11 +1104,12 @@ export const ThemeCustomizerEnhanced: React.FC = (
{t('branding.colorGroupAccent', 'Accent')}
@@ -1293,7 +1300,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
onClick={() => onCssTemplateChange(null)}
className={`relative p-4 rounded-lg border-2 transition-all text-left ${
!cssTemplateId
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
+ ? 'tile-selected'
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
}`}
>
@@ -1315,7 +1322,7 @@ export const ThemeCustomizerEnhanced: React.FC = (
onClick={() => onCssTemplateChange(template.id)}
className={`relative p-4 rounded-lg border-2 transition-all text-left ${
cssTemplateId === template.id
- ? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
+ ? 'tile-selected'
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
}`}
>
diff --git a/frontend/src/components/gallery/GalleryLayout.tsx b/frontend/src/components/gallery/GalleryLayout.tsx
index 144a6871..46b23f22 100644
--- a/frontend/src/components/gallery/GalleryLayout.tsx
+++ b/frontend/src/components/gallery/GalleryLayout.tsx
@@ -495,7 +495,7 @@ export const GalleryLayout: React.FC = ({
{t('gallery.needHelp')}{' '}
{brandingSettings.support_email}
diff --git a/frontend/src/components/gallery/GallerySidebar.tsx b/frontend/src/components/gallery/GallerySidebar.tsx
index 43e9bd13..60faf9ab 100644
--- a/frontend/src/components/gallery/GallerySidebar.tsx
+++ b/frontend/src/components/gallery/GallerySidebar.tsx
@@ -255,7 +255,7 @@ export const GallerySidebar: React.FC = ({
className={`
gallery-btn w-full text-left px-3 py-2 rounded-lg transition-colors flex items-center justify-between
${selectedCategoryId === null
- ? 'bg-primary-600/20 text-primary-500'
+ ? 'bg-accent-dark text-white'
: 'hover:bg-black/10 text-muted-theme'
}
`}
@@ -278,7 +278,7 @@ export const GallerySidebar: React.FC = ({
className={`
gallery-btn w-full text-left px-3 py-2 rounded-lg transition-colors flex items-center justify-between
${isSelected
- ? 'bg-primary-600/20 text-primary-500'
+ ? 'bg-accent-dark text-white'
: 'hover:bg-black/10 text-muted-theme'
}
`}
@@ -362,7 +362,7 @@ export const GallerySidebar: React.FC = ({
className={`
gallery-btn w-full text-left px-3 py-2 rounded-lg transition-colors flex items-center gap-3
${isSelected
- ? 'bg-primary-600/20 text-primary-500'
+ ? 'bg-accent-dark text-white'
: 'hover:bg-black/10 text-muted-theme'
}
`}
diff --git a/frontend/src/components/gallery/GuestNamePromptModal.tsx b/frontend/src/components/gallery/GuestNamePromptModal.tsx
index 9e520bba..76dd135a 100644
--- a/frontend/src/components/gallery/GuestNamePromptModal.tsx
+++ b/frontend/src/components/gallery/GuestNamePromptModal.tsx
@@ -145,7 +145,7 @@ export const GuestNamePromptModal: React.FC = ({
closePrompt();
openRecovery();
}}
- className="text-sm text-primary-600 hover:underline w-full text-center pt-2"
+ className="text-sm text-accent hover:underline w-full text-center pt-2"
>
{t('gallery.guestPrompt.alreadyHere', "I've been here before")}
diff --git a/frontend/src/components/gallery/PhotoFilterBar.tsx b/frontend/src/components/gallery/PhotoFilterBar.tsx
index 3e67bc09..50527942 100644
--- a/frontend/src/components/gallery/PhotoFilterBar.tsx
+++ b/frontend/src/components/gallery/PhotoFilterBar.tsx
@@ -97,7 +97,7 @@ export const PhotoFilterBar: React.FC = ({
setShowSortMenu(false);
}}
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
- sortBy === 'date' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
+ sortBy === 'date' ? 'bg-accent-dark text-white' : 'text-muted-theme'
}`}
>
{t('gallery.sortByDate')}
@@ -108,7 +108,7 @@ export const PhotoFilterBar: React.FC = ({
setShowSortMenu(false);
}}
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
- sortBy === 'capture_date' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
+ sortBy === 'capture_date' ? 'bg-accent-dark text-white' : 'text-muted-theme'
}`}
>
{t('photoSort.dateTaken', 'Date Taken')}
@@ -119,7 +119,7 @@ export const PhotoFilterBar: React.FC = ({
setShowSortMenu(false);
}}
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
- sortBy === 'name' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
+ sortBy === 'name' ? 'bg-accent-dark text-white' : 'text-muted-theme'
}`}
>
{t('gallery.sortByName')}
@@ -130,7 +130,7 @@ export const PhotoFilterBar: React.FC = ({
setShowSortMenu(false);
}}
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
- sortBy === 'size' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
+ sortBy === 'size' ? 'bg-accent-dark text-white' : 'text-muted-theme'
}`}
>
{t('gallery.sortBySize')}
@@ -141,7 +141,7 @@ export const PhotoFilterBar: React.FC = ({
setShowSortMenu(false);
}}
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
- sortBy === 'rating' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
+ sortBy === 'rating' ? 'bg-accent-dark text-white' : 'text-muted-theme'
}`}
>
{t('gallery.sortByRating', 'Sort by Rating')}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 3b4664bc..8c2e8d4c 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -298,6 +298,73 @@
color: var(--color-accent);
}
+ /*
+ * Selected-tile state for picker grids (Gallery Layout, Filter Bar Style,
+ * Header Style, Hero Divider, Theme Presets). Used in place of the dim
+ * "border + light tint + accent text" pattern which had poor contrast on
+ * dark themes (accent text on dim accent bg). The full accent-dark fill
+ * with white descendants gives a strong, accessible selection cue and
+ * follows the user's CI palette.
+ */
+ .tile-selected {
+ background-color: var(--color-accent-dark) !important;
+ border-color: var(--color-accent-dark) !important;
+ color: #ffffff !important;
+ }
+
+ .tile-selected *,
+ .tile-selected svg {
+ color: #ffffff !important;
+ }
+
+ /*
+ * Inline hover tooltip used by the colour-picker info icons.
+ * Pure CSS — no library, no JS state. Wrap an `` icon (or any
+ * trigger) in and a
+ * positioned bubble fades in on hover/focus. The native HTML `title`
+ * attribute has a ~1.5s delay and is suppressed in some browsers, which
+ * is why earlier iterations appeared not to work for users.
+ */
+ .info-tooltip {
+ position: relative;
+ display: inline-flex;
+ cursor: help;
+ }
+
+ .info-tooltip::after {
+ content: attr(data-tooltip);
+ position: absolute;
+ bottom: calc(100% + 6px);
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 50;
+ min-width: 200px;
+ max-width: 320px;
+ padding: 0.5rem 0.625rem;
+ border-radius: 0.375rem;
+ background-color: #171717;
+ color: #fafafa;
+ font-size: 0.75rem;
+ line-height: 1.35;
+ font-weight: 400;
+ text-align: left;
+ white-space: normal;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 120ms ease-out;
+ }
+
+ .info-tooltip:hover::after,
+ .info-tooltip:focus-visible::after {
+ opacity: 1;
+ }
+
+ .dark .info-tooltip::after {
+ background-color: #fafafa;
+ color: #171717;
+ }
+
/* Image loading skeleton */
.skeleton {
@apply animate-pulse rounded-lg bg-neutral-200;