Standardise page wrapper width and top padding and refresh catalog and theme styling

This commit is contained in:
Aaron Po
2026-10-04 04:49:50 -04:00
parent 9581435578
commit 2b8a03148f
41 changed files with 235 additions and 128 deletions

View File

@@ -34,38 +34,38 @@ h6,
--color-base-100: oklch(96% 0.012 82); /* warm parchment */ --color-base-100: oklch(96% 0.012 82); /* warm parchment */
--color-base-200: oklch(92% 0.018 80); /* brushed paper */ --color-base-200: oklch(92% 0.018 80); /* brushed paper */
--color-base-300: oklch(87% 0.025 78); /* tinted linen */ --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: oklch(47.5% 0.085 62); /* mellow amber */
--color-primary-content: oklch(97% 0.02 62); /* warm near-white — 7.2:1 on primary */ --color-primary-content: oklch(97% 0.02 62); /* warm near-white */
--color-secondary: oklch(42% 0.05 42); /* softened mahogany */ --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: 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: 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: 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: oklch(46.5% 0.06 122); /* soft barley gold */
--color-success-content: oklch(97% 0.02 122); /* warm near-white — 5.7:1 on success */ --color-success-content: oklch(97% 0.02 122); /* warm near-white */
--color-warning: oklch(72% 0.09 56); /* toned amber */ --color-warning: oklch(47.5% 0.09 56); /* toned amber */
--color-warning-content: oklch(97% 0.02 56); /* warm near-white — 4.7:1 on warning */ --color-warning-content: oklch(97% 0.02 56); /* warm near-white */
--color-error: oklch(54% 0.09 22); /* restrained cherry */ --color-error: oklch(48% 0.09 22); /* restrained cherry */
--color-error-content: oklch(97% 0.006 15); /* near-white — 11.1:1 on error */ --color-error-content: oklch(97% 0.006 15); /* near-white */
--color-surface: oklch(88% 0.02 82); /* mid parchment, elevated cards */ --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: 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-selector: 0.375rem;
--radius-field: 0.5rem; --radius-field: 0.5rem;
@@ -91,38 +91,38 @@ h6,
--color-base-100: oklch(14% 0.006 45); /* charred barrel black */ --color-base-100: oklch(14% 0.006 45); /* charred barrel black */
--color-base-200: oklch(18% 0.008 43); /* roasted malt dark */ --color-base-200: oklch(18% 0.008 43); /* roasted malt dark */
--color-base-300: oklch(23% 0.01 42); /* deep brown */ --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: 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: oklch(62.5% 0.035 40); /* deep mahogany ale */
--color-secondary-content: oklch(97% 0.005 75); /* near-white — 13.9:1 on secondary */ --color-secondary-content: oklch(23.5% 0.005 75); /* near-white */
--color-accent: oklch(82% 0.01 88); /* frothy cream head */ --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: 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: oklch(61.5% 0.04 145); /* cool hop green */
--color-info-content: oklch(86% 0.006 145); /* pale green-white — 4.6:1 on info */ --color-info-content: oklch(23% 0.006 145); /* pale green-white */
--color-success: oklch(66% 0.038 120); /* fresh barley */ --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: 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: oklch(62.5% 0.06 20); /* deep cherry kriek */
--color-error-content: oklch(97% 0.004 15); /* near-white — 13.1:1 on error */ --color-error-content: oklch(23% 0.004 15); /* near-white */
--color-surface: oklch(26% 0.012 45); /* elevated dark panel */ --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: 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-selector: 0.375rem;
--radius-field: 0.5rem; --radius-field: 0.5rem;
@@ -148,40 +148,38 @@ h6,
--color-base-100: oklch(13% 0.01 295); /* blackberry-stained near-black */ --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-200: oklch(17% 0.013 292); /* deep purple-black */
--color-base-300: oklch(22% 0.016 290); /* dark grape */ --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: 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: 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: 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: 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: 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: 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: 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: oklch(62% 0.055 22); /* kriek red */
--color-error-content: oklch(97% 0.006 22); /* near-white — 13.2:1 on error */ --color-error-content: oklch(22% 0.006 22); /* near-white */
--color-surface: oklch(27% 0.022 292); /* lifted purple-black panel */ --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( --color-muted: oklch(77.6% 0.022 300); /* desaturated lavender */
77.6% 0.022 300
); /* desaturated lavender — 4.6:1 on base-100, 4.5:1 on surface */
--color-highlight: oklch(35% 0.04 295); /* cassis-tinted hover and active state */ --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-selector: 0.5rem;
--radius-field: 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-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-200: oklch(96% 0.012 114); /* pale barley wash */
--color-base-300: oklch(92% 0.019 116); /* light straw */ --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: oklch(50% 0.085 118); /* fresh-cut barley green */
--color-primary-content: oklch(97% 0.005 118); /* near-white — 12.5:1 on primary */ --color-primary-content: oklch(97% 0.005 118); /* near-white */
--color-secondary: oklch(44% 0.055 128); /* muted sage stem */ --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: 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: 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: 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: 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: oklch(51% 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-content: oklch(92.5% 0.005 72); /* warm near-white */
--color-error: oklch(52% 0.1 18); /* dusty rose red */ --color-error: oklch(51.5% 0.1 18); /* dusty rose red */
--color-error-content: oklch(98% 0.005 15); /* near-white — 12.5:1 on error */ --color-error-content: oklch(98% 0.005 15); /* near-white */
--color-surface: oklch(94% 0.012 112); /* soft barley-wash panel */ --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: 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-selector: 2rem;
--radius-field: 2rem; --radius-field: 2rem;
@@ -249,3 +247,33 @@ h6,
--depth: 0; --depth: 0;
--noise: 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);
}

View File

@@ -11,7 +11,7 @@ const AuthFlowDemoCard = () => (
<li className="list-row"> <li className="list-row">
<div> <div>
<p className="font-semibold">Login</p> <p className="font-semibold">Login</p>
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/70">
POST to <code className="kbd kbd-sm">/api/auth/login</code> with POST to <code className="kbd kbd-sm">/api/auth/login</code> with
username &amp; password username &amp; password
</p> </p>
@@ -20,7 +20,7 @@ const AuthFlowDemoCard = () => (
<li className="list-row"> <li className="list-row">
<div> <div>
<p className="font-semibold">Register</p> <p className="font-semibold">Register</p>
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/70">
POST to <code className="kbd kbd-sm">/api/auth/register</code> with full POST to <code className="kbd kbd-sm">/api/auth/register</code> with full
user details user details
</p> </p>
@@ -29,7 +29,7 @@ const AuthFlowDemoCard = () => (
<li className="list-row"> <li className="list-row">
<div> <div>
<p className="font-semibold">Session</p> <p className="font-semibold">Session</p>
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/70">
JWT access &amp; refresh tokens stored in an HTTP-only cookie JWT access &amp; refresh tokens stored in an HTTP-only cookie
</p> </p>
</div> </div>
@@ -37,7 +37,7 @@ const AuthFlowDemoCard = () => (
<li className="list-row"> <li className="list-row">
<div> <div>
<p className="font-semibold">Protected Routes</p> <p className="font-semibold">Protected Routes</p>
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/70">
This dashboard requires authentication via{' '} This dashboard requires authentication via{' '}
<code className="kbd kbd-sm">requireAuth()</code> <code className="kbd kbd-sm">requireAuth()</code>
</p> </p>
@@ -46,7 +46,7 @@ const AuthFlowDemoCard = () => (
<li className="list-row"> <li className="list-row">
<div> <div>
<p className="font-semibold">Token Refresh</p> <p className="font-semibold">Token Refresh</p>
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/70">
POST to <code className="kbd kbd-sm">/api/auth/refresh</code> with the POST to <code className="kbd kbd-sm">/api/auth/refresh</code> with the
refresh token in an <code className="kbd kbd-sm">X-Refresh-Token</code>{' '} refresh token in an <code className="kbd kbd-sm">X-Refresh-Token</code>{' '}
header header
@@ -56,7 +56,7 @@ const AuthFlowDemoCard = () => (
<li className="list-row"> <li className="list-row">
<div> <div>
<p className="font-semibold">Account Management</p> <p className="font-semibold">Account Management</p>
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/70">
PATCH <code className="kbd kbd-sm">/api/auth/username</code>,{' '} PATCH <code className="kbd kbd-sm">/api/auth/username</code>,{' '}
<code className="kbd kbd-sm">/email</code>,{' '} <code className="kbd kbd-sm">/email</code>,{' '}
<code className="kbd kbd-sm">/password</code>,{' '} <code className="kbd kbd-sm">/password</code>,{' '}

View File

@@ -14,7 +14,7 @@ const SectionCard = ({ icon, title, description, open, onToggle, children }: Sec
<div className="card-body"> <div className="card-body">
<div className="flex w-full items-center justify-between gap-5"> <div className="flex w-full items-center justify-between gap-5">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<span className="text-base-content/60 mt-1">{icon}</span> <span className="text-base-content/70 mt-1">{icon}</span>
<div> <div>
<h2 className="card-title text-lg">{title}</h2> <h2 className="card-title text-lg">{title}</h2>
<p className="text-sm text-base-content/70">{description}</p> <p className="text-sm text-base-content/70">{description}</p>

View File

@@ -15,7 +15,7 @@ const SessionInfoCard = ({ username, userAccountId }: SessionInfoCardProps) => (
</p> </p>
<div className="bg-base-200 rounded-box p-4 mt-2"> <div className="bg-base-200 rounded-box p-4 mt-2">
<p className="text-xs font-semibold uppercase tracking-widest text-base-content/50 mb-3"> <p className="text-xs font-semibold uppercase tracking-widest text-base-content/70 mb-3">
Session Info Session Info
</p> </p>
<div className="stats stats-vertical w-full"> <div className="stats stats-vertical w-full">

View File

@@ -208,11 +208,11 @@ const AccountPage = ({ loaderData, actionData }: Route.ComponentProps) => {
return ( return (
<div className="min-h-screen bg-base-200"> <div className="min-h-screen bg-base-200">
<div className="mx-auto max-w-2xl px-6 py-10 space-y-6"> <div className="mx-auto max-w-7xl px-5 pt-10 pb-10 space-y-6">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Settings className="size-7" aria-hidden="true" /> <Settings className="size-7" aria-hidden="true" />
<div> <div>
<h1 className="text-3xl font-bold">Account Settings</h1> <h1 className="font-serif text-5xl font-bold leading-tight">Account Settings</h1>
<p className="text-base-content/70"> <p className="text-base-content/70">
Manage the account details for{' '} Manage the account details for{' '}
<span className="font-mono">{username}</span> <span className="font-mono">{username}</span>
@@ -257,7 +257,7 @@ const AccountPage = ({ loaderData, actionData }: Route.ComponentProps) => {
<Link <Link
to="/dashboard" to="/dashboard"
className="link link-hover text-sm text-base-content/60 inline-block" className="link link-hover text-sm text-base-content/70 inline-block"
> >
← Back to dashboard ← Back to dashboard
</Link> </Link>

View File

@@ -20,7 +20,7 @@ const Dashboard = ({ loaderData }: Route.ComponentProps) => {
return ( return (
<div className="min-h-screen bg-base-200"> <div className="min-h-screen bg-base-200">
<div className="mx-auto max-w-4xl px-6 py-10 space-y-6"> <div className="mx-auto max-w-7xl px-5 pt-10 pb-10 space-y-6">
<SessionInfoCard username={username} userAccountId={userAccountId} /> <SessionInfoCard username={username} userAccountId={userAccountId} />
<AuthFlowDemoCard /> <AuthFlowDemoCard />
</div> </div>

View File

@@ -10,7 +10,7 @@ const ConfirmSuccess = ({ confirmedDate }: ConfirmSuccessProps) => (
<h1 className="card-title text-2xl">Email Confirmed!</h1> <h1 className="card-title text-2xl">Email Confirmed!</h1>
<p className="text-base-content/70">Your email address has been successfully verified.</p> <p className="text-base-content/70">Your email address has been successfully verified.</p>
<div className="bg-base-200 rounded-box w-full p-3 text-sm text-left"> <div className="bg-base-200 rounded-box w-full p-3 text-sm text-left">
<span className="text-base-content/50 text-xs uppercase tracking-widest font-semibold"> <span className="text-base-content/70 text-xs uppercase tracking-widest font-semibold">
Confirmed at Confirmed at
</span> </span>
<p className="font-mono mt-1">{new Date(confirmedDate).toLocaleString()}</p> <p className="font-mono mt-1">{new Date(confirmedDate).toLocaleString()}</p>

View File

@@ -8,7 +8,7 @@ const LoginCallout = () => (
<Link to="/login" className="btn btn-outline btn-sm w-full"> <Link to="/login" className="btn btn-outline btn-sm w-full">
Sign in Sign in
</Link> </Link>
<Link to="/" className="link link-hover text-sm text-base-content/60"> <Link to="/" className="link link-hover text-sm text-base-content/70">
Back to home Back to home
</Link> </Link>
</div> </div>

View File

@@ -11,7 +11,7 @@ const RegisterCallout = () => (
</Link> </Link>
<Link <Link
to="/" to="/"
className="link link-hover text-sm text-base-content/60 inline-flex items-center gap-1" className="link link-hover text-sm text-base-content/70 inline-flex items-center gap-1"
> >
<HomeSimpleDoor className="size-4" aria-hidden="true" /> <HomeSimpleDoor className="size-4" aria-hidden="true" />
Back to home Back to home

View File

@@ -23,12 +23,12 @@ const BeerListCard = ({ beers }: BeerListCardProps) => (
> >
<div className="flex flex-col gap-0.5 min-w-0"> <div className="flex flex-col gap-0.5 min-w-0">
<span className="font-bold text-sm">{beer.name}</span> <span className="font-bold text-sm">{beer.name}</span>
<span className="text-xs text-base-content/60">{beer.description}</span> <span className="text-xs text-base-content/70">{beer.description}</span>
</div> </div>
<span className="badge badge-ghost bg-base-300 text-xs font-semibold whitespace-nowrap"> <span className="badge badge-ghost bg-base-300 text-xs font-semibold whitespace-nowrap">
{beer.style} {beer.style}
</span> </span>
<span className="text-xs text-base-content/60 tabular-nums whitespace-nowrap"> <span className="text-xs text-base-content/70 tabular-nums whitespace-nowrap">
{beer.abv.toFixed(1)}% ABV {beer.abv.toFixed(1)}% ABV
</span> </span>
<StarRating value={beer.rating} size="xs" /> <StarRating value={beer.rating} size="xs" />

View File

@@ -17,7 +17,7 @@ const BeerStyleBreakdownCard = ({ beers }: BeerStyleBreakdownCardProps) => {
<div key={style} className="flex flex-col gap-1"> <div key={style} className="flex flex-col gap-1">
<div className="flex items-baseline justify-between gap-2"> <div className="flex items-baseline justify-between gap-2">
<span className="text-sm font-semibold">{style}</span> <span className="text-sm font-semibold">{style}</span>
<span className="text-xs text-base-content/60 tabular-nums">{count}</span> <span className="text-xs text-base-content/70 tabular-nums">{count}</span>
</div> </div>
<progress <progress
className="progress progress-primary h-1.5" className="progress progress-primary h-1.5"

View File

@@ -8,7 +8,7 @@ interface BreweryDetailsCardProps {
const DetailRow = ({ label, value }: { label: string; value: string | number }) => ( const DetailRow = ({ label, value }: { label: string; value: string | number }) => (
<div className="flex items-center justify-between gap-2 text-sm"> <div className="flex items-center justify-between gap-2 text-sm">
<span className="text-base-content/60">{label}</span> <span className="text-base-content/70">{label}</span>
<span className="font-semibold">{value}</span> <span className="font-semibold">{value}</span>
</div> </div>
); );

View File

@@ -26,16 +26,16 @@ const BreweryHeaderCard = ({
<div className="card-body gap-4 p-7"> <div className="card-body gap-4 p-7">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<span className="text-xs font-bold uppercase tracking-widest text-base-content/50"> <span className="text-xs font-bold uppercase tracking-widest text-base-content/70">
Brewery Brewery
</span> </span>
<span className="badge bg-[var(--color-highlight)] text-[var(--color-highlight-content)]"> <span className="badge bg-[var(--color-highlight)] text-[var(--color-highlight-content)]">
Est. {foundedYear} Est. {foundedYear}
</span> </span>
</div> </div>
<h1 className="text-4xl leading-tight m-0">{brewery.breweryName}</h1> <h1 className="font-serif text-5xl font-bold leading-tight m-0">{brewery.breweryName}</h1>
{brewery.location && ( {brewery.location && (
<p className="text-sm text-base-content/60 flex items-center gap-1 m-0"> <p className="text-sm text-base-content/70 flex items-center gap-1 m-0">
<MapPin className="size-4" aria-hidden="true" /> <MapPin className="size-4" aria-hidden="true" />
{formatBreweryAddress(brewery.location)} {formatBreweryAddress(brewery.location)}
</p> </p>
@@ -57,7 +57,7 @@ const BreweryHeaderCard = ({
<div className="flex items-baseline gap-1.5"> <div className="flex items-baseline gap-1.5">
<StarRating value={avgRating} size="sm" /> <StarRating value={avgRating} size="sm" />
<span className="font-bold">{avgRating.toFixed(1)}</span> <span className="font-bold">{avgRating.toFixed(1)}</span>
<span className="text-sm text-base-content/60">({ratingsCount} ratings)</span> <span className="text-sm text-base-content/70">({ratingsCount} ratings)</span>
</div> </div>
</div> </div>
<p className="text-base-content/70 leading-relaxed text-pretty m-0"> <p className="text-base-content/70 leading-relaxed text-pretty m-0">

View File

@@ -8,7 +8,7 @@ interface CommunityStatsCardProps {
const StatRow = ({ label, value }: { label: string; value: number }) => ( const StatRow = ({ label, value }: { label: string; value: number }) => (
<div className="flex items-center justify-between gap-2 text-sm"> <div className="flex items-center justify-between gap-2 text-sm">
<span className="text-base-content/60">{label}</span> <span className="text-base-content/70">{label}</span>
<span className="font-semibold tabular-nums">{value}</span> <span className="font-semibold tabular-nums">{value}</span>
</div> </div>
); );

View File

@@ -10,7 +10,7 @@ const YourRatingCard = ({ value, onChange }: YourRatingCardProps) => (
<div className="card-body flex-row flex-wrap items-center justify-between gap-4 p-6"> <div className="card-body flex-row flex-wrap items-center justify-between gap-4 p-6">
<div className="flex flex-col gap-0.5"> <div className="flex flex-col gap-0.5">
<h3 className="text-xl m-0">Your rating</h3> <h3 className="text-xl m-0">Your rating</h3>
<p className="text-sm text-base-content/60 m-0"> <p className="text-sm text-base-content/70 m-0">
{value {value
? `You rated this ${value} of 5. Tap the same star to clear.` ? `You rated this ${value} of 5. Tap the same star to clear.`
: 'Tap a star to rate this brewery.'} : 'Tap a star to rate this brewery.'}

View File

@@ -31,14 +31,14 @@ const CommentItem = ({ comment, onToggleLike }: CommentItemProps) => {
<span className="font-bold text-sm">{comment.user}</span> <span className="font-bold text-sm">{comment.user}</span>
)} )}
<StarRating value={comment.rating} size="xs" /> <StarRating value={comment.rating} size="xs" />
<span className="text-xs text-base-content/60">{comment.time}</span> <span className="text-xs text-base-content/70">{comment.time}</span>
</div> </div>
<p className="text-sm leading-relaxed m-0">{comment.text}</p> <p className="text-sm leading-relaxed m-0">{comment.text}</p>
<button <button
type="button" type="button"
onClick={() => onToggleLike(comment.id)} onClick={() => onToggleLike(comment.id)}
className={`self-start inline-flex items-center gap-1 text-xs font-semibold ${ className={`self-start inline-flex items-center gap-1 text-xs font-semibold ${
comment.liked ? 'text-primary' : 'text-base-content/60' comment.liked ? 'text-primary' : 'text-base-content/70'
}`} }`}
> >
{comment.liked ? ( {comment.liked ? (

View File

@@ -24,7 +24,7 @@ const BreweryCard = ({ brewery }: BreweryCardProps) => (
</p> </p>
)} )}
{brewery.location && ( {brewery.location && (
<p className="text-sm text-base-content/50 mt-2"> <p className="text-sm text-base-content/70 mt-2">
{formatBreweryAddress(brewery.location)} {formatBreweryAddress(brewery.location)}
</p> </p>
)} )}

View File

@@ -19,14 +19,14 @@ const FeaturedBreweryCard = ({ brewery, onShowOnMap }: FeaturedBreweryCardProps)
<div className="card-body gap-3.5 p-8 md:grid md:grid-cols-[1.35fr_1fr] md:gap-10 md:items-start"> <div className="card-body gap-3.5 p-8 md:grid md:grid-cols-[1.35fr_1fr] md:gap-10 md:items-start">
<div className="flex flex-col gap-3.5"> <div className="flex flex-col gap-3.5">
{brewery.location && ( {brewery.location && (
<div className="text-sm font-semibold text-base-content/60"> <div className="text-sm font-semibold text-base-content/70">
{brewery.location.cityName}, {brewery.location.stateProvinceCode} {brewery.location.cityName}, {brewery.location.stateProvinceCode}
</div> </div>
)} )}
<h3 className="font-serif text-4xl leading-tight m-0">{brewery.breweryName}</h3> <h3 className="font-serif text-4xl leading-tight m-0">{brewery.breweryName}</h3>
<p className="text-lg leading-snug text-pretty m-0">{brewery.description}</p> <p className="text-lg leading-snug text-pretty m-0">{brewery.description}</p>
{brewery.location && ( {brewery.location && (
<p className="text-sm text-base-content/60 m-0"> <p className="text-sm text-base-content/70 m-0">
{formatBreweryAddress(brewery.location)} {formatBreweryAddress(brewery.location)}
</p> </p>
)} )}

View File

@@ -17,7 +17,7 @@ const RecentBreweries = ({ breweries }: RecentBreweriesProps) => {
<div className="card-body gap-1.5"> <div className="card-body gap-1.5">
<h3 className="card-title font-serif text-xl">{brewery.breweryName}</h3> <h3 className="card-title font-serif text-xl">{brewery.breweryName}</h3>
{brewery.location && ( {brewery.location && (
<div className="text-sm text-base-content/60"> <div className="text-sm text-base-content/70">
{brewery.location.cityName},{' '} {brewery.location.cityName},{' '}
{brewery.location.stateProvinceCode} {brewery.location.stateProvinceCode}
</div> </div>

View File

@@ -20,7 +20,7 @@ const NearbyBreweriesHeader = ({
return ( return (
<div className="flex items-baseline justify-between gap-4 flex-wrap mb-3.5"> <div className="flex items-baseline justify-between gap-4 flex-wrap mb-3.5">
<h2 className="font-serif text-2xl m-0">Breweries near you</h2> <h2 className="font-serif text-2xl m-0">Breweries near you</h2>
<span className="text-sm text-base-content/60"> <span className="text-sm text-base-content/70">
{center {center
? `${withDistance.length} within ${formatDistance(radiusKm * 1000, unit)} of ${centerLabel}` ? `${withDistance.length} within ${formatDistance(radiusKm * 1000, unit)} of ${centerLabel}`
: 'Finding breweries near you…'} : 'Finding breweries near you…'}

View File

@@ -23,7 +23,7 @@ const NearbyBreweryList = ({
return ( return (
<> <>
{withDistance.length === 0 && center && ( {withDistance.length === 0 && center && (
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/70">
No partner breweries within {formatDistance(radiusKm * 1000, unit)}. No partner breweries within {formatDistance(radiusKm * 1000, unit)}.
</p> </p>
)} )}

View File

@@ -37,7 +37,7 @@ const NearbyBreweryListItem = ({
</span> </span>
</div> </div>
{brewery.location && ( {brewery.location && (
<div className="text-sm text-base-content/60"> <div className="text-sm text-base-content/70">
{brewery.location.cityName}, {brewery.location.stateProvinceCode} {brewery.location.cityName}, {brewery.location.stateProvinceCode}
</div> </div>
)} )}

View File

@@ -8,7 +8,7 @@ interface SidebarCardProps {
const SidebarCard = ({ title, children }: SidebarCardProps) => ( const SidebarCard = ({ title, children }: SidebarCardProps) => (
<div className="card bg-base-100 shadow"> <div className="card bg-base-100 shadow">
<div className="card-body gap-3 p-6"> <div className="card-body gap-3 p-6">
<p className="text-xs font-bold uppercase tracking-widest text-base-content/50 m-0"> <p className="text-xs font-bold uppercase tracking-widest text-base-content/70 m-0">
{title} {title}
</p> </p>
{children} {children}

View File

@@ -44,8 +44,8 @@ const Breweries = ({ loaderData }: Route.ComponentProps) => {
return ( return (
<div className="min-h-screen bg-base-200 text-base-content pb-16"> <div className="min-h-screen bg-base-200 text-base-content pb-16">
<div className="max-w-7xl mx-auto px-5 pt-10"> <div className="max-w-7xl mx-auto px-5 pt-10">
<h1 className="font-serif text-5xl leading-tight mb-2">Breweries</h1> <h1 className="font-serif text-5xl font-bold leading-tight mb-2">Breweries</h1>
<p className="text-lg text-base-content/60 max-w-xl m-0"> <p className="text-lg text-base-content/70 max-w-xl m-0">
Discover our partner breweries — start with this week&apos;s feature, then see Discover our partner breweries — start with this week&apos;s feature, then see
what&apos;s pouring near you. what&apos;s pouring near you.
</p> </p>

View File

@@ -80,15 +80,15 @@ const BreweryDetail = ({ loaderData }: Route.ComponentProps) => {
return ( return (
<div className="min-h-screen bg-base-200"> <div className="min-h-screen bg-base-200">
<div className="max-w-4xl mx-auto px-6 pt-8 pb-16"> <div className="mx-auto max-w-7xl px-5 pt-10 pb-16">
<Link <Link
to="/breweries" to="/breweries"
className="link link-hover text-sm text-base-content/60 mb-4 inline-block" className="link link-hover text-sm text-base-content/70 mb-4 inline-block"
> >
&larr; Back to breweries &larr; Back to breweries
</Link> </Link>
<div className="grid grid-cols-1 lg:grid-cols-[1fr_19rem] gap-5 items-start"> <div className="grid grid-cols-1 lg:grid-cols-[1fr_23rem] gap-5 items-start">
<div className="flex flex-col gap-5"> <div className="flex flex-col gap-5">
<BreweryHeaderCard <BreweryHeaderCard
brewery={brewery} brewery={brewery}

View File

@@ -72,14 +72,14 @@ const Breweries = ({ loaderData }: Route.ComponentProps) => {
return ( return (
<div className="min-h-screen bg-base-200"> <div className="min-h-screen bg-base-200">
<div className="container mx-auto p-14"> <div className="mx-auto max-w-7xl px-5 pt-10">
<Link <Link
to="/breweries" to="/breweries"
className="link link-hover text-sm text-base-content/60 mb-4 inline-block" className="link link-hover text-sm text-base-content/70 mb-4 inline-block"
> >
&larr; Back to Breweries &larr; Back to Breweries
</Link> </Link>
<h1 className="text-4xl font-bold mb-4">Full brewery directory</h1> <h1 className="font-serif text-5xl font-bold leading-tight mb-4">Full brewery directory</h1>
<p className="text-base-content/70 mb-6"> <p className="text-base-content/70 mb-6">
All of our partner breweries, newest first. All of our partner breweries, newest first.
</p> </p>
@@ -93,7 +93,7 @@ const Breweries = ({ loaderData }: Route.ComponentProps) => {
</ClientOnly> </ClientOnly>
{allBreweries.length === 0 ? ( {allBreweries.length === 0 ? (
<p className="text-center text-base-content/60 mt-12"> <p className="text-center text-base-content/70 mt-12">
No breweries found yet. No breweries found yet.
</p> </p>
) : ( ) : (

View File

@@ -0,0 +1,72 @@
import { Clock, Lock, Tools } from 'iconoir-react';
import type { ComponentType } from 'react';
import { Link } from 'react-router';
type Reason = 'soon' | 'maintenance' | 'auth';
type Action = { to: string; label: string; className: string };
type Content = {
Icon: ComponentType<{ className?: string }>;
title: string;
description: string;
actions: Action[];
};
const CONTENT: Record<Reason, Content> = {
soon: {
Icon: Clock,
title: 'This feature is not available yet',
description: 'This feature is still in development.',
actions: [
{ to: '/breweries', label: 'Browse breweries', className: 'btn-primary' },
{ to: '/', label: 'Back to home', className: 'btn-ghost' },
],
},
maintenance: {
Icon: Tools,
title: 'This feature is temporarily unavailable',
description: 'This feature is offline for maintenance. Try again in a few minutes.',
actions: [],
},
auth: {
Icon: Lock,
title: 'Sign in to use this feature',
description: 'This feature requires a valid session. Log in or create an account to continue.',
actions: [
{ to: '/login', label: 'Login', className: 'btn-primary' },
{ to: '/register', label: 'Register', className: 'btn-outline' },
],
},
};
type FeatureNotAvailableProps = {
reason?: Reason;
};
const FeatureNotAvailable = ({ reason = 'soon' }: FeatureNotAvailableProps) => {
const { Icon, title, description, actions } = CONTENT[reason];
return (
<section className="flex flex-col items-center gap-3 rounded-box border border-base-300 bg-base-100 px-6 py-12 text-center">
<div className="flex size-14 items-center justify-center rounded-full bg-base-200 text-base-content/70">
<Icon className="size-6" />
</div>
<div className="flex max-w-md flex-col gap-2">
<h2 className="font-serif text-2xl font-bold leading-tight">{title}</h2>
<p className="text-base-content/70 text-pretty">{description}</p>
</div>
{actions.length > 0 && (
<div className="flex flex-wrap justify-center gap-3 pt-2">
{actions.map(({ to, label, className }) => (
<Link key={to} to={to} className={`btn btn-sm ${className}`}>
{label}
</Link>
))}
</div>
)}
</section>
);
};
export default FeatureNotAvailable;

View File

@@ -1,3 +1,4 @@
import FeatureNotAvailable from '../components/FeatureNotAvailable';
import type { Route } from './+types/beer-styles'; import type { Route } from './+types/beer-styles';
export const meta = ({}: Route.MetaArgs) => { export const meta = ({}: Route.MetaArgs) => {
@@ -7,9 +8,12 @@ export const meta = ({}: Route.MetaArgs) => {
const BeerStyles = () => { const BeerStyles = () => {
return ( return (
<div className="min-h-screen bg-base-200"> <div className="min-h-screen bg-base-200">
<div className="container mx-auto p-4"> <div className="mx-auto max-w-7xl px-5 pt-10">
<h1 className="text-4xl font-bold mb-4">Beer Styles</h1> <h1 className="font-serif text-5xl font-bold leading-tight mb-4">Beer Styles</h1>
<p className="text-base-content/70">Learn about different beer styles.</p> <p className="text-base-content/70">Learn about different beer styles.</p>
<div className="mt-8">
<FeatureNotAvailable />
</div>
</div> </div>
</div> </div>
); );

View File

@@ -1,3 +1,4 @@
import FeatureNotAvailable from '../components/FeatureNotAvailable';
import type { Route } from './+types/beers'; import type { Route } from './+types/beers';
export const meta = ({}: Route.MetaArgs) => { export const meta = ({}: Route.MetaArgs) => {
@@ -7,9 +8,12 @@ export const meta = ({}: Route.MetaArgs) => {
const Beers = () => { const Beers = () => {
return ( return (
<div className="min-h-screen bg-base-200"> <div className="min-h-screen bg-base-200">
<div className="container mx-auto p-4"> <div className="mx-auto max-w-7xl px-5 pt-10">
<h1 className="text-4xl font-bold mb-4">Beers</h1> <h1 className="font-serif text-5xl font-bold leading-tight mb-4">Beers</h1>
<p className="text-base-content/70">Explore our collection of beers.</p> <p className="text-base-content/70">Explore our collection of beers.</p>
<div className="mt-8">
<FeatureNotAvailable />
</div>
</div> </div>
</div> </div>
); );

View File

@@ -21,10 +21,9 @@ export const meta = ({}: Route.MetaArgs) => [
export const loader = async ({ request }: Route.LoaderArgs) => { export const loader = async ({ request }: Route.LoaderArgs) => {
const auth = await getOptionalAuth(request); const auth = await getOptionalAuth(request);
// The rest of the landing page stands on its own, so a brewery service
// outage degrades to the empty state instead of an error page.
let recentBreweries: Brewery[] = []; let recentBreweries: Brewery[] = [];
const randomOffset = Math.random() * 1.0
try { try {
recentBreweries = await getBreweries(PARTNER_BREWERY_COUNT, 0); recentBreweries = await getBreweries(PARTNER_BREWERY_COUNT, 0);
} catch { } catch {

View File

@@ -31,7 +31,7 @@ const TextTokenSample = ({
}) => { }) => {
return ( return (
<div className="rounded-box p-3" style={{ backgroundColor: `var(${background})` }}> <div className="rounded-box p-3" style={{ backgroundColor: `var(${background})` }}>
<p className="text-xs font-semibold uppercase tracking-wide text-base-content/50"> <p className="text-xs font-semibold uppercase tracking-wide text-base-content/70">
{label} {label}
</p> </p>
<p className="mt-1 text-sm font-medium" style={{ color: `var(${text})` }}> <p className="mt-1 text-sm font-medium" style={{ color: `var(${text})` }}>
@@ -54,7 +54,7 @@ const ThemePanel = ({ label, value, vibe }: { label: string; value: string; vibe
</div> </div>
<div> <div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50"> <p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/70">
Core Core
</p> </p>
<div className="grid gap-2 sm:grid-cols-2"> <div className="grid gap-2 sm:grid-cols-2">
@@ -69,7 +69,7 @@ const ThemePanel = ({ label, value, vibe }: { label: string; value: string; vibe
</div> </div>
<div> <div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50"> <p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/70">
Status Status
</p> </p>
<div className="grid gap-2 grid-cols-2 sm:grid-cols-4"> <div className="grid gap-2 grid-cols-2 sm:grid-cols-4">
@@ -81,7 +81,7 @@ const ThemePanel = ({ label, value, vibe }: { label: string; value: string; vibe
</div> </div>
<div> <div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50"> <p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/70">
Content Content
</p> </p>
<div className="grid gap-2 sm:grid-cols-3"> <div className="grid gap-2 sm:grid-cols-3">

View File

@@ -1,7 +1,7 @@
const EmptyState = ({ message }: { message: string }) => ( const EmptyState = ({ message }: { message: string }) => (
<div className="card bg-base-100 shadow"> <div className="card bg-base-100 shadow">
<div className="card-body items-center py-12"> <div className="card-body items-center py-12">
<p className="text-sm text-base-content/60 text-center m-0">{message}</p> <p className="text-sm text-base-content/70 text-center m-0">{message}</p>
</div> </div>
</div> </div>
); );

View File

@@ -33,7 +33,7 @@ const ProfileSidebarCard = ({
<div className="card-body gap-4 p-6"> <div className="card-body gap-4 p-6">
<div> <div>
<h1 className="text-2xl font-bold m-0 leading-tight">{displayName}</h1> <h1 className="text-2xl font-bold m-0 leading-tight">{displayName}</h1>
<p className="text-sm text-base-content/60 m-0 mt-0.5">@{username}</p> <p className="text-sm text-base-content/70 m-0 mt-0.5">@{username}</p>
</div> </div>
{isOwnProfile ? ( {isOwnProfile ? (
@@ -52,7 +52,7 @@ const ProfileSidebarCard = ({
<p className="text-sm leading-normal m-0">{bio}</p> <p className="text-sm leading-normal m-0">{bio}</p>
<div className="flex flex-col gap-1.5 text-sm text-base-content/60"> <div className="flex flex-col gap-1.5 text-sm text-base-content/70">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<MapPin className="size-4 shrink-0" aria-hidden="true" /> <MapPin className="size-4 shrink-0" aria-hidden="true" />
{location} {location}
@@ -68,7 +68,7 @@ const ProfileSidebarCard = ({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{stats.map((stat) => ( {stats.map((stat) => (
<div key={stat.label} className="flex items-center justify-between gap-2"> <div key={stat.label} className="flex items-center justify-between gap-2">
<span className="text-sm text-base-content/60">{stat.label}</span> <span className="text-sm text-base-content/70">{stat.label}</span>
<span className="font-serif text-lg font-bold tabular-nums"> <span className="font-serif text-lg font-bold tabular-nums">
{stat.value} {stat.value}
</span> </span>

View File

@@ -24,7 +24,7 @@ const ProfileTabs = ({ activeTab, onChange, panels }: ProfileTabsProps) => {
{TABS.map((tab) => ( {TABS.map((tab) => (
<Tab <Tab
key={tab.id} key={tab.id}
className="-mb-px border-b-2 border-transparent py-3 text-sm font-semibold text-base-content/60 transition-colors hover:text-base-content focus:outline-none data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-primary data-[selected]:border-primary data-[selected]:text-primary" className="-mb-px border-b-2 border-transparent py-3 text-sm font-semibold text-base-content/70 transition-colors hover:text-base-content focus:outline-none data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-primary data-[selected]:border-primary data-[selected]:text-primary"
> >
{tab.label} {tab.label}
</Tab> </Tab>

View File

@@ -36,11 +36,11 @@ const ActivityTab = ({ activity }: ActivityTabProps) => {
</div> </div>
)} )}
{item.comment && ( {item.comment && (
<p className="text-sm text-base-content/60 italic mt-1.5 mb-0"> <p className="text-sm text-base-content/70 italic mt-1.5 mb-0">
&ldquo;{item.comment}&rdquo; &ldquo;{item.comment}&rdquo;
</p> </p>
)} )}
<p className="text-xs text-base-content/60 mt-1.5 mb-0"> <p className="text-xs text-base-content/70 mt-1.5 mb-0">
{item.time} {item.time}
</p> </p>
</div> </div>

View File

@@ -42,7 +42,7 @@ const FollowingTab = ({ following, onToggleFollow }: FollowingTabProps) => {
) : ( ) : (
<p className="font-semibold text-sm m-0 truncate">{f.name}</p> <p className="font-semibold text-sm m-0 truncate">{f.name}</p>
)} )}
<p className="text-xs text-base-content/60 m-0 truncate">{f.meta}</p> <p className="text-xs text-base-content/70 m-0 truncate">{f.meta}</p>
</div> </div>
<button <button
type="button" type="button"

View File

@@ -18,7 +18,7 @@ const LikedTab = ({ liked }: LikedTabProps) => {
<div className="h-[120px] bg-base-300" /> <div className="h-[120px] bg-base-300" />
<div className="card-body p-4 gap-1"> <div className="card-body p-4 gap-1">
<p className="font-serif font-bold m-0">{item.name}</p> <p className="font-serif font-bold m-0">{item.name}</p>
<p className="text-sm text-base-content/60 m-0">{item.meta}</p> <p className="text-sm text-base-content/70 m-0">{item.meta}</p>
<div className="mt-1"> <div className="mt-1">
<StarRating value={item.rating} size="xs" /> <StarRating value={item.rating} size="xs" />
</div> </div>

View File

@@ -75,7 +75,7 @@ const ProfileContent = ({ profile, isOwnProfile }: ProfileContentProps) => {
return ( return (
<div className="min-h-screen bg-base-200 pb-16"> <div className="min-h-screen bg-base-200 pb-16">
<div className="max-w-[1080px] mx-auto px-6 pt-8"> <div className="mx-auto max-w-7xl px-5 pt-10">
<div className="rounded-box h-[190px] bg-base-300" /> <div className="rounded-box h-[190px] bg-base-300" />
<div className="grid grid-cols-1 lg:grid-cols-[300px_1fr] gap-8 items-start -mt-12"> <div className="grid grid-cols-1 lg:grid-cols-[300px_1fr] gap-8 items-start -mt-12">

View File

@@ -13,7 +13,7 @@ import './app.css';
import Navbar from './components/ui/navbar/Navbar'; import Navbar from './components/ui/navbar/Navbar';
import ToastProvider from './components/ui/toast/ToastProvider'; import ToastProvider from './components/ui/toast/ToastProvider';
import { getOptionalAuth } from './features/auth/auth.server'; import { getOptionalAuth } from './features/auth/auth.server';
import AiDisclosureDialog from './features/disclosure/components/AiDisclosureDialog'; import AiDisclosureDialog from './features/home/components/AiDisclosureDialog';
import { defaultThemeName, parseThemeCookie } from './features/theme/themes'; import { defaultThemeName, parseThemeCookie } from './features/theme/themes';
export const links: Route.LinksFunction = () => [ export const links: Route.LinksFunction = () => [
@@ -86,7 +86,7 @@ export const ErrorBoundary = ({ error }: Route.ErrorBoundaryProps) => {
} }
return ( return (
<main className="pt-16 p-4 container mx-auto"> <main className="mx-auto max-w-7xl px-5 pt-10">
<h1>{message}</h1> <h1>{message}</h1>
<p>{details}</p> <p>{details}</p>
{stack && ( {stack && (