From ff775e3b8abf6149209c03d1731f3ff241e636cb Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Thu, 8 Oct 2026 00:16:50 -0400 Subject: [PATCH] Raise frontend contrast, unify the page frame, and add browser identity (#328) --- web/frontend/app/app.css | 150 +++++++++------- .../app/components/ui/navbar/Navbar.tsx | 4 +- .../account/components/AuthFlowDemoCard.tsx | 12 +- .../account/components/SectionCard.tsx | 2 +- .../account/components/SessionInfoCard.tsx | 2 +- .../app/features/account/routes/account.tsx | 8 +- .../app/features/account/routes/dashboard.tsx | 2 +- .../auth/components/ConfirmSuccess.tsx | 2 +- .../features/auth/components/LoginCallout.tsx | 2 +- .../auth/components/RegisterCallout.tsx | 2 +- .../components/detail/BeerListCard.tsx | 4 +- .../detail/BeerStyleBreakdownCard.tsx | 2 +- .../components/detail/BreweryDetailsCard.tsx | 2 +- .../components/detail/BreweryHeaderCard.tsx | 10 +- .../components/detail/CommunityStatsCard.tsx | 2 +- .../components/detail/YourRatingCard.tsx | 2 +- .../detail/comments/CommentItem.tsx | 4 +- .../components/directory/BreweryCard.tsx | 2 +- .../overview/FeaturedBreweryCard.tsx | 4 +- .../components/overview/RecentBreweries.tsx | 2 +- .../nearby/header/NearbyBreweriesHeader.tsx | 2 +- .../nearby/list/NearbyBreweryList.tsx | 2 +- .../nearby/list/NearbyBreweryListItem.tsx | 2 +- .../components/shared/SidebarCard.tsx | 2 +- .../features/breweries/routes/breweries.tsx | 4 +- .../breweries/routes/brewery-detail.tsx | 6 +- .../breweries/routes/brewery-directory.tsx | 10 +- .../components/FeatureNotAvailable.tsx | 77 ++++++++ .../features/catalog/routes/beer-styles.tsx | 8 +- .../app/features/catalog/routes/beers.tsx | 8 +- .../features/home/components/ClosingCta.tsx | 2 +- .../home/components/ThemeTryOut.stories.tsx | 10 +- .../features/home/components/ThemeTryOut.tsx | 16 +- .../app/features/theme/Themes.stories.tsx | 8 +- .../ThemeSegmentedControl.stories.tsx | 167 ++++++++++++++++++ .../components/ThemeSegmentedControl.tsx | 37 +++- web/frontend/app/features/theme/themes.ts | 6 + .../users/components/profile/EmptyState.tsx | 2 +- .../components/profile/ProfileSidebarCard.tsx | 6 +- .../users/components/profile/ProfileTabs.tsx | 2 +- .../components/profile/tabs/ActivityTab.tsx | 4 +- .../components/profile/tabs/FollowingTab.tsx | 2 +- .../components/profile/tabs/LikedTab.tsx | 2 +- .../features/users/routes/user-profile.tsx | 2 +- web/frontend/app/root.tsx | 7 +- web/frontend/package-lock.json | 30 ++-- 46 files changed, 492 insertions(+), 152 deletions(-) create mode 100644 web/frontend/app/features/catalog/components/FeatureNotAvailable.tsx create mode 100644 web/frontend/app/features/theme/components/ThemeSegmentedControl.stories.tsx diff --git a/web/frontend/app/app.css b/web/frontend/app/app.css index 8967dbd6..b9035c60 100644 --- a/web/frontend/app/app.css +++ b/web/frontend/app/app.css @@ -34,38 +34,38 @@ h6, --color-base-100: oklch(96% 0.012 82); /* warm parchment */ --color-base-200: oklch(92% 0.018 80); /* brushed paper */ --color-base-300: oklch(87% 0.025 78); /* tinted linen */ - --color-base-content: oklch(28% 0.025 58); /* dark brown ink — 15.6:1 on base-100 */ + --color-base-content: oklch(28% 0.025 58); /* dark brown ink */ - --color-primary: oklch(65% 0.085 62); /* mellow amber */ - --color-primary-content: oklch(97% 0.02 62); /* warm near-white — 7.2:1 on primary */ + --color-primary: oklch(47.5% 0.085 62); /* mellow amber */ + --color-primary-content: oklch(97% 0.02 62); /* warm near-white */ --color-secondary: oklch(42% 0.05 42); /* softened mahogany */ - --color-secondary-content: oklch(96% 0.01 76); /* off-white — 14.2:1 on secondary */ + --color-secondary-content: oklch(96% 0.01 76); /* off-white */ --color-accent: oklch(93% 0.015 90); /* frothy cream */ - --color-accent-content: oklch(28% 0.025 58); /* dark brown — 12.8:1 on accent */ + --color-accent-content: oklch(28% 0.025 58); /* dark brown */ --color-neutral: oklch(28% 0.02 46); /* warm roast dark */ - --color-neutral-content: oklch(92% 0.012 80); /* pale parchment — 12.0:1 on neutral */ + --color-neutral-content: oklch(92% 0.012 80); /* pale parchment */ --color-info: oklch(46% 0.065 145); /* muted hop green */ - --color-info-content: oklch(97% 0.008 145); /* near-white — 14.2:1 on info */ + --color-info-content: oklch(97% 0.008 145); /* near-white */ - --color-success: oklch(70% 0.06 122); /* soft barley gold */ - --color-success-content: oklch(97% 0.02 122); /* warm near-white — 5.7:1 on success */ + --color-success: oklch(46.5% 0.06 122); /* soft barley gold */ + --color-success-content: oklch(97% 0.02 122); /* warm near-white */ - --color-warning: oklch(72% 0.09 56); /* toned amber */ - --color-warning-content: oklch(97% 0.02 56); /* warm near-white — 4.7:1 on warning */ + --color-warning: oklch(47.5% 0.09 56); /* toned amber */ + --color-warning-content: oklch(97% 0.02 56); /* warm near-white */ - --color-error: oklch(54% 0.09 22); /* restrained cherry */ - --color-error-content: oklch(97% 0.006 15); /* near-white — 11.1:1 on error */ + --color-error: oklch(48% 0.09 22); /* restrained cherry */ + --color-error-content: oklch(97% 0.006 15); /* near-white */ --color-surface: oklch(88% 0.02 82); /* mid parchment, elevated cards */ - --color-surface-content: oklch(28% 0.025 58); /* dark brown — 9.2:1 on surface */ + --color-surface-content: oklch(28% 0.025 58); /* dark brown */ - --color-muted: oklch(42% 0.055 62); /* amber-brown — 14.2:1 on base-100, 8.3:1 on surface */ + --color-muted: oklch(42% 0.055 62); /* amber-brown */ --color-highlight: oklch(78% 0.055 65); /* warm amber, hover and active states */ - --color-highlight-content: oklch(22% 0.025 55); /* dark brown — 4.9:1 on highlight */ + --color-highlight-content: oklch(22% 0.025 55); /* dark brown */ --radius-selector: 0.375rem; --radius-field: 0.5rem; @@ -91,38 +91,38 @@ h6, --color-base-100: oklch(14% 0.006 45); /* charred barrel black */ --color-base-200: oklch(18% 0.008 43); /* roasted malt dark */ --color-base-300: oklch(23% 0.01 42); /* deep brown */ - --color-base-content: oklch(88% 0.008 75); /* warm off-white — 9.4:1 on base-100 */ + --color-base-content: oklch(88% 0.008 75); /* warm off-white */ --color-primary: oklch(68% 0.055 60); /* golden amber */ - --color-primary-content: oklch(92% 0.012 50); /* warm off-white — 4.6:1 on primary */ + --color-primary-content: oklch(30.5% 0.012 50); /* warm off-white */ - --color-secondary: oklch(48% 0.035 40); /* deep mahogany ale */ - --color-secondary-content: oklch(97% 0.005 75); /* near-white — 13.9:1 on secondary */ + --color-secondary: oklch(62.5% 0.035 40); /* deep mahogany ale */ + --color-secondary-content: oklch(23.5% 0.005 75); /* near-white */ --color-accent: oklch(82% 0.01 88); /* frothy cream head */ - --color-accent-content: oklch(18% 0.012 55); /* near-black — 6.2:1 on accent */ + --color-accent-content: oklch(18% 0.012 55); /* near-black */ --color-neutral: oklch(20% 0.008 45); /* near-black with warmth */ - --color-neutral-content: oklch(88% 0.007 78); /* warm off-white — 9.3:1 on neutral */ + --color-neutral-content: oklch(88% 0.007 78); /* warm off-white */ - --color-info: oklch(60% 0.04 145); /* cool hop green */ - --color-info-content: oklch(86% 0.006 145); /* pale green-white — 4.6:1 on info */ + --color-info: oklch(61.5% 0.04 145); /* cool hop green */ + --color-info-content: oklch(23% 0.006 145); /* pale green-white */ --color-success: oklch(66% 0.038 120); /* fresh barley */ - --color-success-content: oklch(90% 0.012 120); /* pale barley-white — 4.6:1 on success */ + --color-success-content: oklch(28.5% 0.012 120); /* pale barley-white */ --color-warning: oklch(70% 0.055 55); /* amber harvest */ - --color-warning-content: oklch(94% 0.012 55); /* warm near-white — 4.7:1 on warning */ + --color-warning-content: oklch(32.5% 0.012 55); /* warm near-white */ - --color-error: oklch(50% 0.06 20); /* deep cherry kriek */ - --color-error-content: oklch(97% 0.004 15); /* near-white — 13.1:1 on error */ + --color-error: oklch(62.5% 0.06 20); /* deep cherry kriek */ + --color-error-content: oklch(23% 0.004 15); /* near-white */ --color-surface: oklch(26% 0.012 45); /* elevated dark panel */ - --color-surface-content: oklch(88% 0.008 75); /* warm off-white — 9.2:1 on surface */ + --color-surface-content: oklch(88% 0.008 75); /* warm off-white */ - --color-muted: oklch(78% 0.018 72); /* warm grey — 4.7:1 on base-100, 4.6:1 on surface */ + --color-muted: oklch(78% 0.018 72); /* warm grey */ --color-highlight: oklch(32% 0.025 48); /* warm dark brown, hover and active states */ - --color-highlight-content: oklch(88% 0.008 75); /* warm off-white — 9.0:1 on highlight */ + --color-highlight-content: oklch(88% 0.008 75); /* warm off-white */ --radius-selector: 0.375rem; --radius-field: 0.5rem; @@ -148,40 +148,38 @@ h6, --color-base-100: oklch(13% 0.01 295); /* blackberry-stained near-black */ --color-base-200: oklch(17% 0.013 292); /* deep purple-black */ --color-base-300: oklch(22% 0.016 290); /* dark grape */ - --color-base-content: oklch(90% 0.014 300); /* pale lavender-white — 10.7:1 on base-100 */ + --color-base-content: oklch(90% 0.014 300); /* pale lavender-white */ --color-primary: oklch(72% 0.075 295); /* cassis berry purple */ - --color-primary-content: oklch(95% 0.01 295); /* pale lavender — 4.5:1 on primary */ + --color-primary-content: oklch(34% 0.01 295); /* pale lavender */ --color-secondary: oklch(68% 0.06 10); /* sour cherry rose */ - --color-secondary-content: oklch(92% 0.006 10); /* warm near-white — 4.6:1 on secondary */ + --color-secondary-content: oklch(30% 0.006 10); /* warm near-white */ --color-accent: oklch(75% 0.045 130); /* tart lime zest */ - --color-accent-content: oklch(98.5% 0.01 130); /* near-white — 4.8:1 on accent */ + --color-accent-content: oklch(37.5% 0.01 130); /* near-white */ --color-neutral: oklch(18% 0.016 290); /* deep blackened grape */ - --color-neutral-content: oklch(88% 0.01 295); /* pale lavender — 9.3:1 on neutral */ + --color-neutral-content: oklch(88% 0.01 295); /* pale lavender */ --color-info: oklch(62% 0.04 250); /* muted indigo */ - --color-info-content: oklch(88% 0.008 250); /* pale indigo-white — 4.8:1 on info */ + --color-info-content: oklch(23% 0.008 250); /* pale indigo-white */ --color-success: oklch(65% 0.04 145); /* elderberry green */ - --color-success-content: oklch(90% 0.008 145); /* pale green-white — 4.7:1 on success */ + --color-success-content: oklch(27.5% 0.008 145); /* pale green-white */ --color-warning: oklch(70% 0.05 65); /* sour apricot */ - --color-warning-content: oklch(97% 0.03 65); /* near-white — 5.6:1 on warning */ + --color-warning-content: oklch(32.5% 0.03 65); /* near-white */ - --color-error: oklch(50% 0.055 22); /* kriek red */ - --color-error-content: oklch(97% 0.006 22); /* near-white — 13.2:1 on error */ + --color-error: oklch(62% 0.055 22); /* kriek red */ + --color-error-content: oklch(22% 0.006 22); /* near-white */ --color-surface: oklch(27% 0.022 292); /* lifted purple-black panel */ - --color-surface-content: oklch(90% 0.014 300); /* pale lavender-white — 10.4:1 on surface */ + --color-surface-content: oklch(90% 0.014 300); /* pale lavender-white */ - --color-muted: oklch( - 77.6% 0.022 300 - ); /* desaturated lavender — 4.6:1 on base-100, 4.5:1 on surface */ + --color-muted: oklch(77.6% 0.022 300); /* desaturated lavender */ --color-highlight: oklch(35% 0.04 295); /* cassis-tinted hover and active state */ - --color-highlight-content: oklch(90% 0.014 300); /* pale lavender-white — 10.1:1 on highlight */ + --color-highlight-content: oklch(90% 0.014 300); /* pale lavender-white */ --radius-selector: 0.5rem; --radius-field: 0.5rem; @@ -207,38 +205,38 @@ h6, --color-base-100: oklch(99% 0.007 112); /* near-white with faint barley-green tint */ --color-base-200: oklch(96% 0.012 114); /* pale barley wash */ --color-base-300: oklch(92% 0.019 116); /* light straw */ - --color-base-content: oklch(20% 0.022 122); /* deep green-black — 19.5:1 on base-100 */ + --color-base-content: oklch(20% 0.022 122); /* deep green-black */ - --color-primary: oklch(52% 0.085 118); /* fresh-cut barley green */ - --color-primary-content: oklch(97% 0.005 118); /* near-white — 12.5:1 on primary */ + --color-primary: oklch(50% 0.085 118); /* fresh-cut barley green */ + --color-primary-content: oklch(97% 0.005 118); /* near-white */ --color-secondary: oklch(44% 0.055 128); /* muted sage stem */ - --color-secondary-content: oklch(97% 0.005 128); /* near-white — 14.8:1 on secondary */ + --color-secondary-content: oklch(97% 0.005 128); /* near-white */ --color-accent: oklch(93% 0.03 148); /* pale morning dew */ - --color-accent-content: oklch(22% 0.022 148); /* deep green — 13.4:1 on accent */ + --color-accent-content: oklch(22% 0.022 148); /* deep green */ --color-neutral: oklch(76% 0.028 118); /* dried straw, surface differentiation */ - --color-neutral-content: oklch(98.9% 0.005 118); /* near-white — 4.6:1 on neutral */ + --color-neutral-content: oklch(38.4% 0.005 118); /* near-white */ --color-info: oklch(38% 0.065 232); /* clear summer sky */ - --color-info-content: oklch(98% 0.005 232); /* near-white — 16.8:1 on info */ + --color-info-content: oklch(98% 0.005 232); /* near-white */ --color-success: oklch(38% 0.085 145); /* young shoot green */ - --color-success-content: oklch(98% 0.005 145); /* near-white — 16.8:1 on success */ + --color-success-content: oklch(98% 0.005 145); /* near-white */ - --color-warning: oklch(68% 0.1 76); /* ripening grain amber */ - --color-warning-content: oklch(92.5% 0.005 72); /* warm near-white — 4.5:1 on warning */ + --color-warning: oklch(51% 0.1 76); /* ripening grain amber */ + --color-warning-content: oklch(92.5% 0.005 72); /* warm near-white */ - --color-error: oklch(52% 0.1 18); /* dusty rose red */ - --color-error-content: oklch(98% 0.005 15); /* near-white — 12.5:1 on error */ + --color-error: oklch(51.5% 0.1 18); /* dusty rose red */ + --color-error-content: oklch(98% 0.005 15); /* near-white */ --color-surface: oklch(94% 0.012 112); /* soft barley-wash panel */ - --color-surface-content: oklch(20% 0.022 122); /* deep green-black — 14.0:1 on surface */ + --color-surface-content: oklch(20% 0.022 122); /* deep green-black */ - --color-muted: oklch(38% 0.055 120); /* sage green — 18.1:1 on base-100, 13.0:1 on surface */ + --color-muted: oklch(38% 0.055 120); /* sage green */ --color-highlight: oklch(85% 0.04 118); /* green-tinted hover and active state */ - --color-highlight-content: oklch(20% 0.022 122); /* deep green-black — 7.8:1 on highlight */ + --color-highlight-content: oklch(20% 0.022 122); /* deep green-black */ --radius-selector: 2rem; --radius-field: 2rem; @@ -249,3 +247,33 @@ h6, --depth: 0; --noise: 0; } + +/* WCAG 1.4.11: form-field borders at >= 3:1 against every base layer */ +:is(.input, .select, .textarea):not( + .input-neutral, + .input-primary, + .input-secondary, + .input-accent, + .input-info, + .input-success, + .input-warning, + .input-error, + .select-neutral, + .select-primary, + .select-secondary, + .select-accent, + .select-info, + .select-success, + .select-warning, + .select-error, + .textarea-neutral, + .textarea-primary, + .textarea-secondary, + .textarea-accent, + .textarea-info, + .textarea-success, + .textarea-warning, + .textarea-error +) { + --input-color: color-mix(in oklab, var(--color-base-content) 56%, #0000); +} diff --git a/web/frontend/app/components/ui/navbar/Navbar.tsx b/web/frontend/app/components/ui/navbar/Navbar.tsx index b781b464..a91423a6 100644 --- a/web/frontend/app/components/ui/navbar/Navbar.tsx +++ b/web/frontend/app/components/ui/navbar/Navbar.tsx @@ -49,8 +49,8 @@ const Navbar = ({ auth }: NavbarProps) => ( - - 🍺 The Biergarten App + + The Biergarten App diff --git a/web/frontend/app/features/account/components/AuthFlowDemoCard.tsx b/web/frontend/app/features/account/components/AuthFlowDemoCard.tsx index 37898699..0ad5bc82 100644 --- a/web/frontend/app/features/account/components/AuthFlowDemoCard.tsx +++ b/web/frontend/app/features/account/components/AuthFlowDemoCard.tsx @@ -11,7 +11,7 @@ const AuthFlowDemoCard = () => (
  • Login

    -

    +

    POST to /api/auth/login with username & password

    @@ -20,7 +20,7 @@ const AuthFlowDemoCard = () => (
  • Register

    -

    +

    POST to /api/auth/register with full user details

    @@ -29,7 +29,7 @@ const AuthFlowDemoCard = () => (
  • Session

    -

    +

    JWT access & refresh tokens stored in an HTTP-only cookie

    @@ -37,7 +37,7 @@ const AuthFlowDemoCard = () => (
  • Protected Routes

    -

    +

    This dashboard requires authentication via{' '} requireAuth()

    @@ -46,7 +46,7 @@ const AuthFlowDemoCard = () => (
  • Token Refresh

    -

    +

    POST to /api/auth/refresh with the refresh token in an X-Refresh-Token{' '} header @@ -56,7 +56,7 @@ const AuthFlowDemoCard = () => (

  • Account Management

    -

    +

    PATCH /api/auth/username,{' '} /email,{' '} /password,{' '} diff --git a/web/frontend/app/features/account/components/SectionCard.tsx b/web/frontend/app/features/account/components/SectionCard.tsx index b8d108ad..993e817f 100644 --- a/web/frontend/app/features/account/components/SectionCard.tsx +++ b/web/frontend/app/features/account/components/SectionCard.tsx @@ -14,7 +14,7 @@ const SectionCard = ({ icon, title, description, open, onToggle, children }: Sec

    - {icon} + {icon}

    {title}

    {description}

    diff --git a/web/frontend/app/features/account/components/SessionInfoCard.tsx b/web/frontend/app/features/account/components/SessionInfoCard.tsx index 4c7d4981..a215ebe2 100644 --- a/web/frontend/app/features/account/components/SessionInfoCard.tsx +++ b/web/frontend/app/features/account/components/SessionInfoCard.tsx @@ -15,7 +15,7 @@ const SessionInfoCard = ({ username, userAccountId }: SessionInfoCardProps) => (

    -

    +

    Session Info

    diff --git a/web/frontend/app/features/account/routes/account.tsx b/web/frontend/app/features/account/routes/account.tsx index 91f4a3d4..c26191ec 100644 --- a/web/frontend/app/features/account/routes/account.tsx +++ b/web/frontend/app/features/account/routes/account.tsx @@ -208,11 +208,13 @@ const AccountPage = ({ loaderData, actionData }: Route.ComponentProps) => { return (
    -
    +