From 14498890ad9679cdb4d3f1b42c1c4dd68d1e0ab5 Mon Sep 17 00:00:00 2001 From: DariusIII Date: Fri, 28 Aug 2026 21:01:47 +0200 Subject: [PATCH] Update the theme --- app/Http/Controllers/ProfileController.php | 2 +- app/Http/Requests/UpdateProfileRequest.php | 2 +- app/Http/Requests/UpdateThemeRequest.php | 2 +- resources/css/app.css | 288 ++++++++++++++++++ resources/js/alpine/stores/theme.js | 9 +- .../views/components/page-header.blade.php | 33 +- resources/views/movies/index.blade.php | 43 +-- .../views/partials/scheme-switcher.blade.php | 40 ++- resources/views/partials/theme-init.blade.php | 20 ++ resources/views/profile/edit.blade.php | 49 +-- tests/Unit/LayoutShellMarkupTest.php | 15 + tests/Unit/MoviesPageMarkupTest.php | 14 + tests/Unit/ThemeTableStylesTest.php | 85 ++++++ 13 files changed, 528 insertions(+), 74 deletions(-) diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 007b36777..772276973 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -176,7 +176,7 @@ class ProfileController extends BasePageController // Update color scheme if ($request->has('color_scheme')) { $schemeValue = $request->input('color_scheme'); - if (in_array($schemeValue, ['blue', 'emerald', 'violet'], true)) { + if (in_array($schemeValue, ['blue', 'indigo', 'cyan', 'teal', 'emerald', 'violet', 'pink', 'rose', 'red', 'orange', 'amber'], true)) { User::where('id', $userid)->update(['color_scheme' => $schemeValue]); } } diff --git a/app/Http/Requests/UpdateProfileRequest.php b/app/Http/Requests/UpdateProfileRequest.php index 5baf1194a..d2553e282 100644 --- a/app/Http/Requests/UpdateProfileRequest.php +++ b/app/Http/Requests/UpdateProfileRequest.php @@ -20,7 +20,7 @@ class UpdateProfileRequest extends FormRequest 'email' => ['nullable', 'string', 'email', 'max:255', 'unique:users,email,'.$userId, new ValidEmailDomain], 'password' => ['nullable', 'string', 'min:8', 'confirmed', 'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/'], 'theme_preference' => ['sometimes', 'in:light,dark,system'], - 'color_scheme' => ['sometimes', 'in:blue,emerald,violet'], + 'color_scheme' => ['sometimes', 'in:blue,indigo,cyan,teal,emerald,violet,pink,rose,red,orange,amber'], ]; } } diff --git a/app/Http/Requests/UpdateThemeRequest.php b/app/Http/Requests/UpdateThemeRequest.php index 427530323..96629c46a 100644 --- a/app/Http/Requests/UpdateThemeRequest.php +++ b/app/Http/Requests/UpdateThemeRequest.php @@ -15,7 +15,7 @@ class UpdateThemeRequest extends FormRequest { return [ 'theme_preference' => ['sometimes', 'in:light,dark,system'], - 'color_scheme' => ['sometimes', 'in:blue,emerald,violet'], + 'color_scheme' => ['sometimes', 'in:blue,indigo,cyan,teal,emerald,violet,pink,rose,red,orange,amber'], ]; } } diff --git a/resources/css/app.css b/resources/css/app.css index 280475fc8..0ced84d73 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -121,6 +121,294 @@ --color-primary-950: #2e1065; } +[data-color-scheme="rose"] { + --surface-body: #fff1f2; + --surface-body-dark: #1f0a12; + --surface-sidebar: #3b1825; + --surface-sidebar-dark: #13050a; + --surface-header: #3b1825; + --surface-header-dark: #13050a; + --surface-card: #ffffff; + --surface-card-dark: #32131e; + --surface-dropdown: #2b1019; + --surface-dropdown-dark: #13050a; + --surface-footer: #3b1825; + --surface-footer-dark: #13050a; + --surface-hover: #fff1f2; + --surface-hover-dark: #4c0519; + --border-default: #fecdd3; + --border-default-dark: #9f1239; + --text-muted: #be123c; + --text-muted-dark: #fda4af; + --surface-panel-alt: #fff1f2; + --surface-panel-alt-dark: #3b1825; + --surface-chrome-border: rgba(255,255,255,0.08); + --surface-chrome-border-dark: rgba(255,255,255,0.05); + --color-primary-50: #fff1f2; + --color-primary-100: #ffe4e6; + --color-primary-200: #fecdd3; + --color-primary-300: #fda4af; + --color-primary-400: #fb7185; + --color-primary-500: #f43f5e; + --color-primary-600: #e11d48; + --color-primary-700: #be123c; + --color-primary-800: #9f1239; + --color-primary-900: #881337; + --color-primary-950: #4c0519; +} + +[data-color-scheme="amber"] { + --surface-body: #fffbeb; + --surface-body-dark: #1c1304; + --surface-sidebar: #3b2a0e; + --surface-sidebar-dark: #120b02; + --surface-header: #3b2a0e; + --surface-header-dark: #120b02; + --surface-card: #ffffff; + --surface-card-dark: #33240d; + --surface-dropdown: #2c1d08; + --surface-dropdown-dark: #120b02; + --surface-footer: #3b2a0e; + --surface-footer-dark: #120b02; + --surface-hover: #fffbeb; + --surface-hover-dark: #78350f; + --border-default: #fde68a; + --border-default-dark: #92400e; + --text-muted: #b45309; + --text-muted-dark: #fcd34d; + --surface-panel-alt: #fffbeb; + --surface-panel-alt-dark: #3b2a0e; + --surface-chrome-border: rgba(255,255,255,0.08); + --surface-chrome-border-dark: rgba(255,255,255,0.05); + --color-primary-50: #fffbeb; + --color-primary-100: #fef3c7; + --color-primary-200: #fde68a; + --color-primary-300: #fcd34d; + --color-primary-400: #fbbf24; + --color-primary-500: #f59e0b; + --color-primary-600: #d97706; + --color-primary-700: #b45309; + --color-primary-800: #92400e; + --color-primary-900: #78350f; + --color-primary-950: #451a03; +} + +[data-color-scheme="cyan"] { + --surface-body: #ecfeff; + --surface-body-dark: #06191d; + --surface-sidebar: #12343c; + --surface-sidebar-dark: #031014; + --surface-header: #12343c; + --surface-header-dark: #031014; + --surface-card: #ffffff; + --surface-card-dark: #10313a; + --surface-dropdown: #0b2a32; + --surface-dropdown-dark: #031014; + --surface-footer: #12343c; + --surface-footer-dark: #031014; + --surface-hover: #ecfeff; + --surface-hover-dark: #164e63; + --border-default: #a5f3fc; + --border-default-dark: #155e75; + --text-muted: #0e7490; + --text-muted-dark: #67e8f9; + --surface-panel-alt: #ecfeff; + --surface-panel-alt-dark: #12343c; + --surface-chrome-border: rgba(255,255,255,0.08); + --surface-chrome-border-dark: rgba(255,255,255,0.05); + --color-primary-50: #ecfeff; + --color-primary-100: #cffafe; + --color-primary-200: #a5f3fc; + --color-primary-300: #67e8f9; + --color-primary-400: #22d3ee; + --color-primary-500: #06b6d4; + --color-primary-600: #0891b2; + --color-primary-700: #0e7490; + --color-primary-800: #155e75; + --color-primary-900: #164e63; + --color-primary-950: #083344; +} + +[data-color-scheme="indigo"] { + --surface-body: #eef2ff; + --surface-body-dark: #0b1024; + --surface-sidebar: #1e2850; + --surface-sidebar-dark: #070b1b; + --surface-header: #1e2850; + --surface-header-dark: #070b1b; + --surface-card: #ffffff; + --surface-card-dark: #182143; + --surface-dropdown: #141c39; + --surface-dropdown-dark: #070b1b; + --surface-footer: #1e2850; + --surface-footer-dark: #070b1b; + --surface-hover: #eef2ff; + --surface-hover-dark: #312e81; + --border-default: #c7d2fe; + --border-default-dark: #3730a3; + --text-muted: #4338ca; + --text-muted-dark: #a5b4fc; + --surface-panel-alt: #eef2ff; + --surface-panel-alt-dark: #1e2850; + --surface-chrome-border: rgba(255,255,255,0.08); + --surface-chrome-border-dark: rgba(255,255,255,0.05); + --color-primary-50: #eef2ff; + --color-primary-100: #e0e7ff; + --color-primary-200: #c7d2fe; + --color-primary-300: #a5b4fc; + --color-primary-400: #818cf8; + --color-primary-500: #6366f1; + --color-primary-600: #4f46e5; + --color-primary-700: #4338ca; + --color-primary-800: #3730a3; + --color-primary-900: #312e81; + --color-primary-950: #1e1b4b; +} + +[data-color-scheme="teal"] { + --surface-body: #f0fdfa; + --surface-body-dark: #061a18; + --surface-sidebar: #123b36; + --surface-sidebar-dark: #021412; + --surface-header: #123b36; + --surface-header-dark: #021412; + --surface-card: #ffffff; + --surface-card-dark: #10332f; + --surface-dropdown: #0b2b27; + --surface-dropdown-dark: #021412; + --surface-footer: #123b36; + --surface-footer-dark: #021412; + --surface-hover: #f0fdfa; + --surface-hover-dark: #134e4a; + --border-default: #99f6e4; + --border-default-dark: #115e59; + --text-muted: #0f766e; + --text-muted-dark: #5eead4; + --surface-panel-alt: #f0fdfa; + --surface-panel-alt-dark: #123b36; + --surface-chrome-border: rgba(255,255,255,0.08); + --surface-chrome-border-dark: rgba(255,255,255,0.05); + --color-primary-50: #f0fdfa; + --color-primary-100: #ccfbf1; + --color-primary-200: #99f6e4; + --color-primary-300: #5eead4; + --color-primary-400: #2dd4bf; + --color-primary-500: #14b8a6; + --color-primary-600: #0d9488; + --color-primary-700: #0f766e; + --color-primary-800: #115e59; + --color-primary-900: #134e4a; + --color-primary-950: #042f2e; +} + +[data-color-scheme="orange"] { + --surface-body: #fff7ed; + --surface-body-dark: #1f1007; + --surface-sidebar: #452515; + --surface-sidebar-dark: #160a03; + --surface-header: #452515; + --surface-header-dark: #160a03; + --surface-card: #ffffff; + --surface-card-dark: #3a2012; + --surface-dropdown: #32190b; + --surface-dropdown-dark: #160a03; + --surface-footer: #452515; + --surface-footer-dark: #160a03; + --surface-hover: #fff7ed; + --surface-hover-dark: #7c2d12; + --border-default: #fed7aa; + --border-default-dark: #9a3412; + --text-muted: #c2410c; + --text-muted-dark: #fdba74; + --surface-panel-alt: #fff7ed; + --surface-panel-alt-dark: #452515; + --surface-chrome-border: rgba(255,255,255,0.08); + --surface-chrome-border-dark: rgba(255,255,255,0.05); + --color-primary-50: #fff7ed; + --color-primary-100: #ffedd5; + --color-primary-200: #fed7aa; + --color-primary-300: #fdba74; + --color-primary-400: #fb923c; + --color-primary-500: #f97316; + --color-primary-600: #ea580c; + --color-primary-700: #c2410c; + --color-primary-800: #9a3412; + --color-primary-900: #7c2d12; + --color-primary-950: #431407; +} + +[data-color-scheme="red"] { + --surface-body: #fef2f2; + --surface-body-dark: #200a0a; + --surface-sidebar: #461818; + --surface-sidebar-dark: #170505; + --surface-header: #461818; + --surface-header-dark: #170505; + --surface-card: #ffffff; + --surface-card-dark: #3a1414; + --surface-dropdown: #310f0f; + --surface-dropdown-dark: #170505; + --surface-footer: #461818; + --surface-footer-dark: #170505; + --surface-hover: #fef2f2; + --surface-hover-dark: #7f1d1d; + --border-default: #fecaca; + --border-default-dark: #991b1b; + --text-muted: #b91c1c; + --text-muted-dark: #fca5a5; + --surface-panel-alt: #fef2f2; + --surface-panel-alt-dark: #461818; + --surface-chrome-border: rgba(255,255,255,0.08); + --surface-chrome-border-dark: rgba(255,255,255,0.05); + --color-primary-50: #fef2f2; + --color-primary-100: #fee2e2; + --color-primary-200: #fecaca; + --color-primary-300: #fca5a5; + --color-primary-400: #f87171; + --color-primary-500: #ef4444; + --color-primary-600: #dc2626; + --color-primary-700: #b91c1c; + --color-primary-800: #991b1b; + --color-primary-900: #7f1d1d; + --color-primary-950: #450a0a; +} + +[data-color-scheme="pink"] { + --surface-body: #fdf2f8; + --surface-body-dark: #200a17; + --surface-sidebar: #45172f; + --surface-sidebar-dark: #17040f; + --surface-header: #45172f; + --surface-header-dark: #17040f; + --surface-card: #ffffff; + --surface-card-dark: #391326; + --surface-dropdown: #310f20; + --surface-dropdown-dark: #17040f; + --surface-footer: #45172f; + --surface-footer-dark: #17040f; + --surface-hover: #fdf2f8; + --surface-hover-dark: #831843; + --border-default: #fbcfe8; + --border-default-dark: #9d174d; + --text-muted: #be185d; + --text-muted-dark: #f9a8d4; + --surface-panel-alt: #fdf2f8; + --surface-panel-alt-dark: #45172f; + --surface-chrome-border: rgba(255,255,255,0.08); + --surface-chrome-border-dark: rgba(255,255,255,0.05); + --color-primary-50: #fdf2f8; + --color-primary-100: #fce7f3; + --color-primary-200: #fbcfe8; + --color-primary-300: #f9a8d4; + --color-primary-400: #f472b6; + --color-primary-500: #ec4899; + --color-primary-600: #db2777; + --color-primary-700: #be185d; + --color-primary-800: #9d174d; + --color-primary-900: #831843; + --color-primary-950: #500724; +} + /* Theme customization (replaces theme.extend in tailwind.config.js) */ @theme { --font-sans: "Figtree", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; diff --git a/resources/js/alpine/stores/theme.js b/resources/js/alpine/stores/theme.js index 606e19d92..0018ab77b 100644 --- a/resources/js/alpine/stores/theme.js +++ b/resources/js/alpine/stores/theme.js @@ -5,6 +5,7 @@ import Alpine from '@alpinejs/csp'; const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); +const colorSchemes = ['blue', 'indigo', 'cyan', 'teal', 'emerald', 'violet', 'pink', 'rose', 'red', 'orange', 'amber']; Alpine.store('theme', { current: 'light', @@ -19,18 +20,19 @@ Alpine.store('theme', { const schemeMeta = document.querySelector('meta[name="color-scheme-preference"]'); const schemeData = document.getElementById('current-theme-data'); - this.colorScheme = (isAuth && isAuth.content === 'true') + const preferredScheme = (isAuth && isAuth.content === 'true') ? (schemeMeta ? schemeMeta.content : (schemeData?.dataset?.colorScheme || 'blue')) : (localStorage.getItem('color_scheme') || 'blue'); + this.colorScheme = colorSchemes.includes(preferredScheme) ? preferredScheme : 'blue'; this.apply(); this.applyScheme(); this._listenOS(); }, - /** Set and persist color scheme (blue, emerald, violet) */ + /** Set and persist a supported color scheme. */ setScheme(scheme) { - if (!['blue', 'emerald', 'violet'].includes(scheme)) return; + if (!colorSchemes.includes(scheme)) return; this.colorScheme = scheme; this.applyScheme(); this._save(); @@ -114,6 +116,7 @@ Alpine.store('theme', { // Update color scheme swatch buttons document.querySelectorAll('.dropdown-scheme-btn, .mobile-scheme-btn').forEach(function(btn) { var isActive = btn.dataset.scheme === self.colorScheme; + btn.setAttribute('aria-pressed', isActive ? 'true' : 'false'); btn.classList.remove('ring-2', 'ring-offset-2', 'ring-offset-gray-900', 'ring-primary-500', 'ring-white'); if (isActive) { btn.classList.add('ring-2', 'ring-offset-2', 'ring-offset-gray-900', 'ring-primary-500', 'dark:ring-offset-gray-950'); diff --git a/resources/views/components/page-header.blade.php b/resources/views/components/page-header.blade.php index ca7ecfada..77aed1f91 100644 --- a/resources/views/components/page-header.blade.php +++ b/resources/views/components/page-header.blade.php @@ -6,13 +6,13 @@ 'current' => null, ]) -
merge(['class' => 'app-page-header workspace-hero relative mb-6 overflow-hidden rounded-2xl px-5 py-6 text-white shadow-lg sm:px-7 sm:py-8']) }}> +
merge(['class' => 'app-page-header workspace-hero relative mb-6 overflow-hidden rounded-2xl px-5 py-4 text-white shadow-lg sm:px-7 sm:py-5']) }}> -
-
-
diff --git a/resources/views/movies/index.blade.php b/resources/views/movies/index.blade.php index 555b2636a..77340fe57 100644 --- a/resources/views/movies/index.blade.php +++ b/resources/views/movies/index.blade.php @@ -16,13 +16,13 @@ @endphp
-
+
-
-
-
diff --git a/resources/views/partials/scheme-switcher.blade.php b/resources/views/partials/scheme-switcher.blade.php index 7d2ff9517..5a548cbac 100644 --- a/resources/views/partials/scheme-switcher.blade.php +++ b/resources/views/partials/scheme-switcher.blade.php @@ -1,16 +1,28 @@ {{-- Shared color scheme switcher. JS hooks by button class (see alpine/stores/theme.js). Expects: $switcherId, $btnClass, $mobile (bool) --}} -@php $currentScheme = $userColorScheme ?? 'blue'; @endphp -
- - - +@php + $currentScheme = $userColorScheme ?? 'blue'; + $colorSchemes = [ + ['value' => 'blue', 'label' => 'Blue', 'swatch' => 'bg-blue-600'], + ['value' => 'indigo', 'label' => 'Indigo', 'swatch' => 'bg-indigo-600'], + ['value' => 'cyan', 'label' => 'Cyan', 'swatch' => 'bg-cyan-600'], + ['value' => 'teal', 'label' => 'Teal', 'swatch' => 'bg-teal-600'], + ['value' => 'emerald', 'label' => 'Emerald', 'swatch' => 'bg-emerald-600'], + ['value' => 'violet', 'label' => 'Violet', 'swatch' => 'bg-violet-600'], + ['value' => 'pink', 'label' => 'Pink', 'swatch' => 'bg-pink-600'], + ['value' => 'rose', 'label' => 'Rose', 'swatch' => 'bg-rose-600'], + ['value' => 'red', 'label' => 'Red', 'swatch' => 'bg-red-600'], + ['value' => 'orange', 'label' => 'Orange', 'swatch' => 'bg-orange-600'], + ['value' => 'amber', 'label' => 'Amber', 'swatch' => 'bg-amber-600'], + ]; +@endphp +
+ @foreach($colorSchemes as $scheme) + + @endforeach
diff --git a/resources/views/partials/theme-init.blade.php b/resources/views/partials/theme-init.blade.php index 118a7953f..23a6418b4 100644 --- a/resources/views/partials/theme-init.blade.php +++ b/resources/views/partials/theme-init.blade.php @@ -16,6 +16,10 @@ var t = localStorage.getItem('theme') || 'light'; var scheme = localStorage.getItem('color_scheme') || 'blue'; @endauth + var allowedSchemes = ['blue', 'indigo', 'cyan', 'teal', 'emerald', 'violet', 'pink', 'rose', 'red', 'orange', 'amber']; + if (allowedSchemes.indexOf(scheme) === -1) { + scheme = 'blue'; + } var isDark = t === 'dark' || (t === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); if (isDark) { d.classList.add('dark'); @@ -34,4 +38,20 @@ html[data-color-scheme="emerald"], html[data-color-scheme="emerald"] body { back html.dark[data-color-scheme="emerald"], html.dark[data-color-scheme="emerald"] body { background-color: #071a12; color: #d1fae5; } html[data-color-scheme="violet"], html[data-color-scheme="violet"] body { background-color: #faf5ff; color: #1e293b; } html.dark[data-color-scheme="violet"], html.dark[data-color-scheme="violet"] body { background-color: #120b20; color: #e9d5ff; } +html[data-color-scheme="rose"], html[data-color-scheme="rose"] body { background-color: #fff1f2; color: #1e293b; } +html.dark[data-color-scheme="rose"], html.dark[data-color-scheme="rose"] body { background-color: #1f0a12; color: #fecdd3; } +html[data-color-scheme="amber"], html[data-color-scheme="amber"] body { background-color: #fffbeb; color: #1e293b; } +html.dark[data-color-scheme="amber"], html.dark[data-color-scheme="amber"] body { background-color: #1c1304; color: #fde68a; } +html[data-color-scheme="cyan"], html[data-color-scheme="cyan"] body { background-color: #ecfeff; color: #1e293b; } +html.dark[data-color-scheme="cyan"], html.dark[data-color-scheme="cyan"] body { background-color: #06191d; color: #cffafe; } +html[data-color-scheme="indigo"], html[data-color-scheme="indigo"] body { background-color: #eef2ff; color: #1e293b; } +html.dark[data-color-scheme="indigo"], html.dark[data-color-scheme="indigo"] body { background-color: #0b1024; color: #c7d2fe; } +html[data-color-scheme="teal"], html[data-color-scheme="teal"] body { background-color: #f0fdfa; color: #1e293b; } +html.dark[data-color-scheme="teal"], html.dark[data-color-scheme="teal"] body { background-color: #061a18; color: #ccfbf1; } +html[data-color-scheme="orange"], html[data-color-scheme="orange"] body { background-color: #fff7ed; color: #1e293b; } +html.dark[data-color-scheme="orange"], html.dark[data-color-scheme="orange"] body { background-color: #1f1007; color: #ffedd5; } +html[data-color-scheme="red"], html[data-color-scheme="red"] body { background-color: #fef2f2; color: #1e293b; } +html.dark[data-color-scheme="red"], html.dark[data-color-scheme="red"] body { background-color: #200a0a; color: #fee2e2; } +html[data-color-scheme="pink"], html[data-color-scheme="pink"] body { background-color: #fdf2f8; color: #1e293b; } +html.dark[data-color-scheme="pink"], html.dark[data-color-scheme="pink"] body { background-color: #200a17; color: #fce7f3; } diff --git a/resources/views/profile/edit.blade.php b/resources/views/profile/edit.blade.php index ea9bc6f17..ad2f75ce6 100644 --- a/resources/views/profile/edit.blade.php +++ b/resources/views/profile/edit.blade.php @@ -128,26 +128,35 @@ Color Scheme

Choose the main color scheme for the site (sidebar, header, buttons, and accents).

-
- @php $currentScheme = $user->color_scheme ?? 'blue'; @endphp - - - + @php + $currentScheme = $user->color_scheme ?? 'blue'; + $colorSchemes = [ + ['value' => 'blue', 'label' => 'Blue', 'swatch' => 'bg-blue-600'], + ['value' => 'indigo', 'label' => 'Indigo', 'swatch' => 'bg-indigo-600'], + ['value' => 'cyan', 'label' => 'Cyan', 'swatch' => 'bg-cyan-600'], + ['value' => 'teal', 'label' => 'Teal', 'swatch' => 'bg-teal-600'], + ['value' => 'emerald', 'label' => 'Emerald', 'swatch' => 'bg-emerald-600'], + ['value' => 'violet', 'label' => 'Violet', 'swatch' => 'bg-violet-600'], + ['value' => 'pink', 'label' => 'Pink', 'swatch' => 'bg-pink-600'], + ['value' => 'rose', 'label' => 'Rose', 'swatch' => 'bg-rose-600'], + ['value' => 'red', 'label' => 'Red', 'swatch' => 'bg-red-600'], + ['value' => 'orange', 'label' => 'Orange', 'swatch' => 'bg-orange-600'], + ['value' => 'amber', 'label' => 'Amber', 'swatch' => 'bg-amber-600'], + ]; + @endphp +
+ @foreach($colorSchemes as $scheme) + + @endforeach

Save your profile to apply the color scheme. You can also change it from the user menu in the header. diff --git a/tests/Unit/LayoutShellMarkupTest.php b/tests/Unit/LayoutShellMarkupTest.php index d57f37dd9..23bf03ac7 100644 --- a/tests/Unit/LayoutShellMarkupTest.php +++ b/tests/Unit/LayoutShellMarkupTest.php @@ -96,6 +96,21 @@ class LayoutShellMarkupTest extends TestCase $this->assertStringContainsString('aria-label="Pagination"', $this->view('components/admin/pagination.blade.php')); } + public function test_shared_user_hero_uses_compact_vertical_spacing(): void + { + $hero = $this->view('components/page-header.blade.php'); + + $this->assertStringContainsString('px-5 py-4', $hero); + $this->assertStringContainsString('sm:px-7 sm:py-5', $hero); + $this->assertStringContainsString('flex flex-col gap-4', $hero); + $this->assertStringContainsString('aria-label="Breadcrumb" class="mb-2', $hero); + $this->assertStringContainsString('class="mt-2 max-w-2xl text-sm leading-5', $hero); + $this->assertStringContainsString('app-page-header__meta flex min-w-0 flex-wrap', $hero); + $this->assertStringContainsString('@if(isset($stats) || isset($actions))', $hero); + $this->assertStringNotContainsString('mt-3 flex flex-wrap gap-2 text-sm', $hero); + $this->assertStringNotContainsString('sm:py-8', $hero); + } + public function test_shared_view_styles_cover_application_admin_and_guest_surfaces(): void { $stylesheet = $this->resource('css/app.css'); diff --git a/tests/Unit/MoviesPageMarkupTest.php b/tests/Unit/MoviesPageMarkupTest.php index 8f58534c7..9b36de1bd 100644 --- a/tests/Unit/MoviesPageMarkupTest.php +++ b/tests/Unit/MoviesPageMarkupTest.php @@ -23,6 +23,20 @@ class MoviesPageMarkupTest extends TestCase $this->assertStringContainsString(':aria-label="layout === 1', $markup); } + public function test_movie_hero_matches_the_compact_shared_hero_spacing(): void + { + $markup = $this->resource('views/movies/index.blade.php'); + + $this->assertStringContainsString('movies-hero relative overflow-hidden rounded-2xl px-5 py-4', $markup); + $this->assertStringContainsString('sm:px-7 sm:py-5', $markup); + $this->assertStringContainsString('relative z-10 flex flex-col gap-4', $markup); + $this->assertStringContainsString('aria-label="Breadcrumb" class="mb-2', $markup); + $this->assertStringContainsString('class="mt-2 max-w-2xl text-sm leading-5', $markup); + $this->assertStringContainsString('movies-hero__meta flex min-w-0 flex-wrap', $markup); + $this->assertStringNotContainsString('mt-3 flex flex-wrap gap-2 text-sm', $markup); + $this->assertStringNotContainsString('sm:py-8', $markup); + } + public function test_movie_cards_reserve_poster_space_and_reuse_release_rows(): void { $card = $this->resource('views/movies/partials/movie-card.blade.php'); diff --git a/tests/Unit/ThemeTableStylesTest.php b/tests/Unit/ThemeTableStylesTest.php index b985dcbc0..e058a3f95 100644 --- a/tests/Unit/ThemeTableStylesTest.php +++ b/tests/Unit/ThemeTableStylesTest.php @@ -2,10 +2,19 @@ namespace Tests\Unit; +use App\Http\Requests\UpdateThemeRequest; +use Illuminate\Translation\ArrayLoader; +use Illuminate\Translation\Translator; +use Illuminate\Validation\Factory; use PHPUnit\Framework\TestCase; class ThemeTableStylesTest extends TestCase { + /** + * @var list + */ + private const COLOR_SCHEMES = ['blue', 'indigo', 'cyan', 'teal', 'emerald', 'violet', 'pink', 'rose', 'red', 'orange', 'amber']; + public function test_light_color_schemes_use_pale_table_hover_surfaces(): void { $stylesheet = (string) file_get_contents(__DIR__.'/../../resources/css/app.css'); @@ -13,6 +22,14 @@ class ThemeTableStylesTest extends TestCase $this->assertStringContainsString('--surface-hover: #f1f5f9;', $stylesheet); $this->assertStringContainsString('--surface-hover: #ecfdf5;', $stylesheet); $this->assertStringContainsString('--surface-hover: #f5f3ff;', $stylesheet); + $this->assertStringContainsString('--surface-hover: #fff1f2;', $stylesheet); + $this->assertStringContainsString('--surface-hover: #fffbeb;', $stylesheet); + $this->assertStringContainsString('--surface-hover: #ecfeff;', $stylesheet); + $this->assertStringContainsString('--surface-hover: #eef2ff;', $stylesheet); + $this->assertStringContainsString('--surface-hover: #f0fdfa;', $stylesheet); + $this->assertStringContainsString('--surface-hover: #fff7ed;', $stylesheet); + $this->assertStringContainsString('--surface-hover: #fef2f2;', $stylesheet); + $this->assertStringContainsString('--surface-hover: #fdf2f8;', $stylesheet); $this->assertMatchesRegularExpression( '/main tbody tr:hover\s*\{\s*background-color: var\(--surface-hover\) !important;\s*\}/', $stylesheet, @@ -26,9 +43,77 @@ class ThemeTableStylesTest extends TestCase $this->assertStringContainsString('--surface-hover-dark: #1e293b;', $stylesheet); $this->assertStringContainsString('--surface-hover-dark: #14532d;', $stylesheet); $this->assertStringContainsString('--surface-hover-dark: #3b0764;', $stylesheet); + $this->assertStringContainsString('--surface-hover-dark: #4c0519;', $stylesheet); + $this->assertStringContainsString('--surface-hover-dark: #78350f;', $stylesheet); + $this->assertStringContainsString('--surface-hover-dark: #164e63;', $stylesheet); + $this->assertStringContainsString('--surface-hover-dark: #312e81;', $stylesheet); + $this->assertStringContainsString('--surface-hover-dark: #134e4a;', $stylesheet); + $this->assertStringContainsString('--surface-hover-dark: #7c2d12;', $stylesheet); + $this->assertStringContainsString('--surface-hover-dark: #7f1d1d;', $stylesheet); + $this->assertStringContainsString('--surface-hover-dark: #831843;', $stylesheet); $this->assertMatchesRegularExpression( '/\.dark main tbody tr:hover\s*\{\s*background-color: var\(--surface-hover-dark\) !important;\s*\}/', $stylesheet, ); } + + public function test_every_color_scheme_defines_complete_surface_and_accent_tokens(): void + { + $stylesheet = (string) file_get_contents(__DIR__.'/../../resources/css/app.css'); + + foreach (self::COLOR_SCHEMES as $scheme) { + $matched = preg_match( + '/\[data-color-scheme="'.preg_quote($scheme, '/').'"\]\s*\{(?.*?)\}/s', + $stylesheet, + $matches, + ); + + $this->assertSame(1, $matched, "{$scheme} must define a color-scheme token block."); + + foreach (['--surface-body:', '--surface-body-dark:', '--surface-hover:', '--surface-hover-dark:', '--color-primary-50:', '--color-primary-500:', '--color-primary-950:'] as $token) { + $this->assertStringContainsString($token, $matches['tokens'], "{$scheme} must define {$token}"); + } + } + } + + public function test_color_scheme_options_stay_in_sync_across_the_ui_and_persistence_layers(): void + { + $switcher = (string) file_get_contents(__DIR__.'/../../resources/views/partials/scheme-switcher.blade.php'); + $profile = (string) file_get_contents(__DIR__.'/../../resources/views/profile/edit.blade.php'); + $themeInit = (string) file_get_contents(__DIR__.'/../../resources/views/partials/theme-init.blade.php'); + $themeStore = (string) file_get_contents(__DIR__.'/../../resources/js/alpine/stores/theme.js'); + $profileRequest = (string) file_get_contents(__DIR__.'/../../app/Http/Requests/UpdateProfileRequest.php'); + $profileController = (string) file_get_contents(__DIR__.'/../../app/Http/Controllers/ProfileController.php'); + $schemeList = implode(',', self::COLOR_SCHEMES); + $javascriptSchemeList = "'".implode("', '", self::COLOR_SCHEMES)."'"; + + $this->assertStringContainsString("const colorSchemes = [{$javascriptSchemeList}]", $themeStore); + $this->assertStringContainsString("in:{$schemeList}", $profileRequest); + $this->assertStringContainsString($javascriptSchemeList, $themeInit); + $this->assertStringContainsString($javascriptSchemeList, $profileController); + + foreach (self::COLOR_SCHEMES as $scheme) { + $this->assertStringContainsString("'value' => '{$scheme}'", $switcher); + $this->assertStringContainsString("'value' => '{$scheme}'", $profile); + + if ($scheme !== 'blue') { + $this->assertStringContainsString("data-color-scheme=\"{$scheme}\"", $themeInit); + } + } + } + + public function test_theme_update_validation_accepts_each_supported_scheme_and_rejects_unknown_values(): void + { + $validator = new Factory(new Translator(new ArrayLoader, 'en')); + $rules = (new UpdateThemeRequest)->rules(); + + foreach (self::COLOR_SCHEMES as $scheme) { + $this->assertFalse( + $validator->make(['color_scheme' => $scheme], $rules)->fails(), + "{$scheme} should be accepted as a color scheme.", + ); + } + + $this->assertTrue($validator->make(['color_scheme' => 'unknown'], $rules)->fails()); + } }