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;