diff --git a/apps/frontend/src/components/LanguageSwitcher.tsx b/apps/frontend/src/components/LanguageSwitcher.tsx index 534e669..4c5e5f0 100644 --- a/apps/frontend/src/components/LanguageSwitcher.tsx +++ b/apps/frontend/src/components/LanguageSwitcher.tsx @@ -13,22 +13,20 @@ export function LanguageSwitcher({ className }: { className?: string }) { return (
- + {languages.map((item) => ( -
{children}
- {footer && ( -
- {footer} -
- )} +
{children}
+ {footer && }
, document.body, diff --git a/apps/frontend/src/pages/ArchivePage.tsx b/apps/frontend/src/pages/ArchivePage.tsx index 87e7ca3..f006377 100644 --- a/apps/frontend/src/pages/ArchivePage.tsx +++ b/apps/frontend/src/pages/ArchivePage.tsx @@ -15,9 +15,9 @@ export function ArchivePage() { return (
-
-

{t('archive.title')}

-

+

+

{t('archive.title')}

+

{t('archive.description')}

@@ -25,10 +25,10 @@ export function ArchivePage() { {isLoading &&
{t('common.loading')}
} {!isLoading && data && data.length === 0 && ( -
- -

{t('archive.emptyTitle')}

-

{t('archive.emptyText')}

+
+ +

{t('archive.emptyTitle')}

+

{t('archive.emptyText')}

)} diff --git a/apps/frontend/src/pages/CompletedPage.tsx b/apps/frontend/src/pages/CompletedPage.tsx index 3b517b1..34c45f7 100644 --- a/apps/frontend/src/pages/CompletedPage.tsx +++ b/apps/frontend/src/pages/CompletedPage.tsx @@ -15,9 +15,9 @@ export function CompletedPage() { return (
-
-

{t('completed.title')}

-

+

+

{t('completed.title')}

+

{t('completed.description')}

@@ -25,10 +25,10 @@ export function CompletedPage() { {isLoading &&
{t('common.loading')}
} {!isLoading && data && data.length === 0 && ( -
- -

{t('completed.emptyTitle')}

-

+

+ +

{t('completed.emptyTitle')}

+

{t('completed.emptyText')}

diff --git a/apps/frontend/src/pages/DashboardPage.tsx b/apps/frontend/src/pages/DashboardPage.tsx index 97f73c7..5597838 100644 --- a/apps/frontend/src/pages/DashboardPage.tsx +++ b/apps/frontend/src/pages/DashboardPage.tsx @@ -26,10 +26,10 @@ export function DashboardPage() { return (
-
+
-

{t('dashboard.title')}

-

+

{t('dashboard.title')}

+

{t('dashboard.description')} {user && ( - +

+
-

{t('dashboard.emptyTitle')}

-

+

{t('dashboard.emptyTitle')}

+

{t('dashboard.emptyText')}

diff --git a/apps/frontend/src/pages/LoginPage.tsx b/apps/frontend/src/pages/LoginPage.tsx index 904e5dc..f2cd062 100644 --- a/apps/frontend/src/pages/LoginPage.tsx +++ b/apps/frontend/src/pages/LoginPage.tsx @@ -47,11 +47,11 @@ export function LoginPage() { }); return ( -
-
+
+
-
+
@@ -60,7 +60,7 @@ export function LoginPage() {

{t('app.name')}

-
+

{t('login.title')}

{t('login.description')} diff --git a/apps/frontend/src/pages/NotFoundPage.tsx b/apps/frontend/src/pages/NotFoundPage.tsx index 7b15e12..ddcba09 100644 --- a/apps/frontend/src/pages/NotFoundPage.tsx +++ b/apps/frontend/src/pages/NotFoundPage.tsx @@ -5,8 +5,8 @@ import { useI18n } from '@/i18n/i18n'; export function NotFoundPage() { const { t } = useI18n(); return ( -

-
+
+

404

{t('notFound.text')}

diff --git a/apps/frontend/src/pages/ProfileSettingsPage.tsx b/apps/frontend/src/pages/ProfileSettingsPage.tsx index b206940..9b51f78 100644 --- a/apps/frontend/src/pages/ProfileSettingsPage.tsx +++ b/apps/frontend/src/pages/ProfileSettingsPage.tsx @@ -122,9 +122,9 @@ export function ProfileSettingsPage() { return (
-
-

{t('profile.title')}

-

+

+

{t('profile.title')}

+

{t('profile.publicPage')} /u/{data?.slug ?? '...'}.

@@ -133,18 +133,18 @@ export function ProfileSettingsPage() { {isLoading ? (
{t('common.loading')}
) : ( -
-
- + +
+ {avatarPreview ? ( - + ) : ( )} -
-

{t('profile.avatar')}

-

{t('profile.avatarHint')}

+
+

{t('profile.avatar')}

+

{t('profile.avatarHint')}

)}
-
+
-
+
{profile.isLoading &&
{t('common.loading')}
} {profile.isError && ( -
-

{t('public.notFoundTitle')}

-

+

+

{t('public.notFoundTitle')}

+

{t('public.notFoundText')}

@@ -85,32 +85,32 @@ export function PublicProfilePage() { {profile.data && ( <> -
- +
+ {profile.data.avatarUrl ? ( ) : ( )} -

+

{t('public.wishlistTitle', { name: profile.data.displayName })}

{profile.data.bio && ( -

{profile.data.bio}

+

{profile.data.bio}

)}
{wishes.isLoading &&
{t('public.loadingWishes')}
} {wishes.data && wishes.data.length === 0 && ( -
-

{t('public.emptyTitle')}

-

{t('public.emptyText')}

+
+

{t('public.emptyTitle')}

+

{t('public.emptyText')}

)} diff --git a/apps/frontend/src/pages/TrashPage.tsx b/apps/frontend/src/pages/TrashPage.tsx index 5c22522..c96f7d4 100644 --- a/apps/frontend/src/pages/TrashPage.tsx +++ b/apps/frontend/src/pages/TrashPage.tsx @@ -12,9 +12,9 @@ export function TrashPage() { return (
-
-

{t('trash.title')}

-

+

+

{t('trash.title')}

+

{t('trash.description', { days: dayCount(TRASH_RETENTION_DAYS) })}

@@ -22,10 +22,10 @@ export function TrashPage() { {isLoading &&
{t('common.loading')}
} {!isLoading && data && data.length === 0 && ( -
- -

{t('trash.emptyTitle')}

-

+

+ +

{t('trash.emptyTitle')}

+

{t('trash.emptyText', { days: dayCount(TRASH_RETENTION_DAYS) })}

diff --git a/apps/frontend/src/styles/global.css b/apps/frontend/src/styles/global.css index 3d8b699..387c403 100644 --- a/apps/frontend/src/styles/global.css +++ b/apps/frontend/src/styles/global.css @@ -29,6 +29,186 @@ @layer components { /* BEM-friendly classes: kept parallel to Tailwind utilities where possible. */ + .app-shell { + @apply flex min-h-screen flex-col; + } + .app-shell__main { + @apply mx-auto w-full max-w-6xl flex-1 px-4 py-6 sm:px-6 sm:py-10 lg:px-8; + } + + .app-header { + @apply mx-auto w-full max-w-6xl px-4 pt-6 sm:px-6 lg:px-8; + } + .app-header__inner { + @apply grid grid-cols-1 items-center gap-3 rounded-lg border border-border bg-surface/80 px-4 py-3 backdrop-blur lg:grid-cols-[auto_minmax(0,1fr)_auto]; + } + .app-header__brand { + @apply flex min-w-0 items-center gap-2; + } + .app-header__brand-mark { + @apply inline-flex h-9 w-9 items-center justify-center rounded-md bg-primary text-primary-foreground shadow-card; + } + .app-header__brand-title { + @apply font-display text-lg leading-tight; + } + .app-header__brand-subtitle { + @apply truncate text-xs text-muted; + } + .app-header__nav { + @apply flex min-w-0 items-center gap-1 overflow-x-auto whitespace-nowrap lg:justify-center; + } + .app-header__nav-link { + @apply inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-ink transition-colors hover:bg-ink/5; + } + .app-header__nav-link--active { + @apply bg-primary text-primary-foreground shadow-card hover:bg-primary; + } + .app-header__actions { + @apply flex shrink-0 items-center gap-1 justify-self-start lg:justify-self-end; + } + + .app-footer { + @apply mx-auto mt-10 w-full max-w-6xl px-4 py-6 text-xs text-muted sm:px-6 lg:px-8; + } + .app-footer__inner { + @apply flex flex-col items-center justify-between gap-2 sm:flex-row; + } + .app-footer__brand { + @apply flex items-center gap-2; + } + .app-footer__brand-name { + @apply font-display text-sm; + } + .app-footer__meta { + @apply flex items-center gap-3; + } + .app-footer__separator { + @apply opacity-50; + } + + .language-switcher { + @apply inline-flex items-center gap-1 rounded-md border border-border bg-surface/90 p-1 text-xs shadow-card; + } + .language-switcher__icon { + @apply ml-1 h-3.5 w-3.5 text-muted; + } + .language-switcher__button { + @apply rounded px-2 py-1 font-semibold text-muted transition-colors hover:bg-surface-muted hover:text-ink; + } + .language-switcher__button--active { + @apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground; + } + + .empty-state { + @apply flex flex-col items-center gap-2 rounded-xl border border-border bg-surface/80 p-10 text-center shadow-card; + } + .empty-state__icon { + @apply h-10 w-10 text-muted; + } + .empty-state__icon--image { + @apply h-40 w-40 opacity-90; + } + .empty-state__title { + @apply text-xl font-semibold; + } + .empty-state__text { + @apply text-sm text-muted; + } + + .page-section { + @apply grid gap-1; + } + .page-section__title { + @apply font-display text-3xl; + } + .page-section__text { + @apply text-sm text-muted; + } + + .public-profile { + @apply flex min-h-screen flex-col; + } + .public-profile__toolbar { + @apply mx-auto flex w-full max-w-6xl items-center justify-between gap-3 px-4 pt-6 sm:px-6 lg:px-8; + } + .public-profile__main { + @apply mx-auto w-full max-w-6xl flex-1 px-4 py-10 sm:px-6 lg:px-8; + } + .public-profile__hero { + @apply mb-10 flex flex-col items-center gap-3 text-center; + } + .public-profile__avatar { + @apply inline-flex h-14 w-14 items-center justify-center overflow-hidden rounded-full bg-primary text-primary-foreground shadow-card; + } + .public-profile__avatar-image { + @apply h-14 w-14 rounded-full object-cover; + } + .public-profile__title { + @apply font-display text-4xl; + } + .public-profile__bio { + @apply max-w-xl text-muted; + } + + .profile-form { + @apply grid gap-4 rounded-xl border border-border bg-surface p-6 shadow-card; + } + .profile-form__avatar-panel { + @apply flex flex-wrap items-center gap-4 rounded-md border border-border bg-surface-muted p-4; + } + .profile-form__avatar-preview { + @apply inline-flex h-16 w-16 items-center justify-center overflow-hidden rounded-full bg-primary text-primary-foreground; + } + .profile-form__avatar-image { + @apply h-16 w-16 object-cover; + } + .profile-form__avatar-copy { + @apply min-w-0 flex-1; + } + .profile-form__avatar-title { + @apply text-sm font-semibold; + } + .profile-form__avatar-hint { + @apply text-xs text-muted; + } + .profile-form__actions { + @apply flex items-center justify-end gap-2; + } + + .modal { + @apply fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-6; + } + .modal__backdrop { + @apply absolute inset-0 animate-fade-in-up bg-ink/40 backdrop-blur-sm; + } + .modal__panel { + @apply relative flex max-h-[90vh] w-full animate-fade-in-up flex-col overflow-hidden rounded-t-xl bg-surface shadow-pop sm:rounded-xl; + } + .modal__panel--md { + @apply sm:max-w-lg; + } + .modal__panel--lg { + @apply sm:max-w-2xl; + } + .modal__header { + @apply flex items-start justify-between gap-4 border-b border-border px-5 py-4; + } + .modal__title-wrap { + @apply min-w-0; + } + .modal__title { + @apply text-lg font-semibold text-ink; + } + .modal__description { + @apply mt-1 text-sm text-muted; + } + .modal__body { + @apply overflow-y-auto px-5 py-5; + } + .modal__footer { + @apply flex items-center justify-end gap-2 border-t border-border px-5 py-4; + } + .wish-card { @apply relative flex flex-col overflow-hidden rounded-lg bg-surface shadow-card transition-transform duration-200; }