feat(shared-auth-ui): redesign login page with animations and theme support

- Migrate icons from custom implementation to @manacore/shared-icons (phosphor-svelte)
- Add CSS media queries for dark/light mode (no more flash on reload)
- Add subtle entrance animations (logo fadeInScale, form fadeInUp, slider fadeIn)
- Redesign custom checkbox with CSS-only styling
- Remove isDark prop dependency, use prefers-color-scheme instead
- Update auth layout to allow full-page rendering

Also updates AppSlider component:
- Add staggered entrance animations for app cards
- Redesign modal with glassmorphism style
- Add theme-aware styling via CSS media queries
- Improve status badge design in modal
- Add close button in top-right corner

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-11-29 08:55:56 +01:00
parent 61d181fbc2
commit 129692812b
10 changed files with 1083 additions and 603 deletions

View file

@ -1,30 +0,0 @@
<script lang="ts">
import { iconPaths, type IconName } from '../icons/iconPaths';
interface Props {
name: IconName;
size?: number;
class?: string;
color?: string;
}
let { name, size = 24, class: className = '', color }: Props = $props();
const path = $derived(iconPaths[name]);
</script>
{#if path}
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
fill={color || 'currentColor'}
viewBox="0 0 256 256"
class={className}
aria-hidden="true"
>
{@html path}
</svg>
{:else}
<span class="text-red-500" title="Icon '{name}' not found"></span>
{/if}

View file

@ -1,54 +0,0 @@
/**
* Phosphor Icons (Bold weight) SVG paths
* Only includes icons used in auth UI
*/
export const iconPaths = {
'user-plus':
'<path d="M256,136a8,8,0,0,1-8,8H232v16a8,8,0,0,1-16,0V144H200a8,8,0,0,1,0-16h16V112a8,8,0,0,1,16,0v16h16A8,8,0,0,1,256,136Zm-57.87,58.85a8,8,0,0,1-12.26,10.3C165.75,181.19,138.09,168,108,168s-57.75,13.19-77.87,37.15a8,8,0,0,1-12.26-10.3C32.09,177.09,52.67,163.91,76,157.53a68,68,0,1,1,64,0C163.33,163.91,183.91,177.09,198.13,194.85ZM108,152a52,52,0,1,0-52-52A52.06,52.06,0,0,0,108,152Z"/>',
'sign-in':
'<path d="M144,136v-16a8,8,0,0,0-8-8H48a8,8,0,0,0-8,8v16a8,8,0,0,0,8,8h88A8,8,0,0,0,144,136Zm-27.31,52.69a8,8,0,0,0-11.32,11.31L136,230.63V224a8,8,0,0,0-16,0v24a8,8,0,0,0,8,8h24a8,8,0,0,0,0-16h-6.63l30.32-30.31a8,8,0,0,0-11.31-11.32l-24,24ZM200,32H136a8,8,0,0,0,0,16h64V208H136a8,8,0,0,0,0,16h64a16,16,0,0,0,16-16V48A16,16,0,0,0,200,32Z"/>',
eye: '<path d="M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.47,133.47,0,0,1,25,128,133.33,133.33,0,0,1,48.07,97.25C70.33,75.19,97.22,64,128,64s57.67,11.19,79.93,33.25A133.46,133.46,0,0,1,231.05,128C223.84,141.46,192.43,192,128,192Zm0-112a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Z"/>',
'eye-off':
'<path d="M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.58A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z"/>',
key: '<path d="M216.57,39.43A80,80,0,0,0,83.91,120.78L28.69,176A15.86,15.86,0,0,0,24,187.31V216a16,16,0,0,0,16,16H72a8,8,0,0,0,8-8V208H96a8,8,0,0,0,8-8V184h16a8,8,0,0,0,5.66-2.34l9.56-9.57A79.73,79.73,0,0,0,160,176h.1A80,80,0,0,0,216.57,39.43ZM224,98.1c-1.09,34.09-29.75,61.86-63.89,61.9H160a63.7,63.7,0,0,1-23.65-4.51,8,8,0,0,0-8.84,1.68L116.69,168H96a8,8,0,0,0-8,8v16H72a8,8,0,0,0-8,8v16H40V187.31l58.83-58.82a8,8,0,0,0,1.68-8.84A63.72,63.72,0,0,1,96,95.92c0-34.14,27.81-62.8,61.9-63.89A64,64,0,0,1,224,98.1ZM192,76a12,12,0,1,1-12-12A12,12,0,0,1,192,76Z"/>',
'arrow-left':
'<path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"/>',
info: '<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/>',
'mail-open':
'<path d="M228.44,89.34l-96-64a8,8,0,0,0-8.88,0l-96,64A8,8,0,0,0,24,96V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V96A8,8,0,0,0,228.44,89.34ZM128,41.61l81.91,54.61-67,47.78a8,8,0,0,1-9.79,0l-67-47.78ZM40,200V111.53l65.9,47a24,24,0,0,0,29.44,0l65.9-47L216,200Z"/>',
lock: '<path d="M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96ZM208,208H48V96H208V208Zm-68-56a12,12,0,1,1-12-12A12,12,0,0,1,140,152Z"/>',
'shield-check':
'<path d="M208,40H48A16,16,0,0,0,32,56v56c0,52.72,25.52,84.67,46.93,102.19,23.06,18.86,46,26.07,47.48,26.53a8,8,0,0,0,5.18,0c1.52-.46,24.42-7.67,47.48-26.53C200.48,196.67,226,164.72,226,112V56A16,16,0,0,0,208,40Zm0,72c0,37.07-13.66,67.16-40.58,89.42A132.87,132.87,0,0,1,128,224.54a132.77,132.77,0,0,1-39.42-23.12C61.66,179.16,48,149.07,48,112V56H208ZM82.34,141.66a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32l-56,56a8,8,0,0,1-11.32,0Z"/>',
'arrows-left-right':
'<path d="M45.66,77.66A8,8,0,0,1,40,64h80a8,8,0,0,1,0,16H59.31l18.35,18.34a8,8,0,0,1-11.32,11.32ZM216,176H136a8,8,0,0,0,0,16h60.69l-18.35,18.34a8,8,0,0,0,11.32,11.32l32-32a8,8,0,0,0,0-11.32l-32-32a8,8,0,0,0-11.32,11.32L196.69,176Z"/>',
envelope:
'<path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM203.43,64,128,133.15,52.57,64ZM216,192H40V74.19l82.59,75.71a8,8,0,0,0,10.82,0L216,74.19V192Z"/>',
folder:
'<path d="M216,72H131.31L104,44.69A15.86,15.86,0,0,0,92.69,40H40A16,16,0,0,0,24,56V200.62A15.4,15.4,0,0,0,39.38,216H216.89A15.13,15.13,0,0,0,232,200.89V88A16,16,0,0,0,216,72ZM40,56H92.69l16,16H40ZM216,200H40V88H216Z"/>',
music:
'<path d="M212.92,17.69a8,8,0,0,0-6.86-1.45l-128,32A8,8,0,0,0,72,56V166.09A36,36,0,1,0,88,196V62.25l112-28v99.84A36,36,0,1,0,216,168V24A8,8,0,0,0,212.92,17.69ZM52,216a20,20,0,1,1,20-20A20,20,0,0,1,52,216Zm128-32a20,20,0,1,1,20-20A20,20,0,0,1,180,184Z"/>',
refresh:
'<path d="M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64A80,80,0,1,0,128,208a8,8,0,0,1,0,16A96,96,0,1,1,195.26,60.49L224,85.34V56a8,8,0,0,1,16,0Z"/>',
check:
'<path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"/>',
warning:
'<path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"/>',
} as const;
export type IconName = keyof typeof iconPaths;

View file

@ -4,7 +4,6 @@ export { default as RegisterPage } from './pages/RegisterPage.svelte';
export { default as ForgotPasswordPage } from './pages/ForgotPasswordPage.svelte';
// Components
export { default as Icon } from './components/Icon.svelte';
export { default as GoogleSignInButton } from './components/GoogleSignInButton.svelte';
export { default as AppleSignInButton } from './components/AppleSignInButton.svelte';
@ -30,12 +29,9 @@ export {
} from './utils/appleAuth';
// Types
export type { AuthUIConfig, AuthServiceInterface, AuthResult, IconName } from './types';
export type { AuthUIConfig, AuthServiceInterface, AuthResult } from './types';
// Page Translation Types
export type { LoginTranslations } from './pages/LoginPage.svelte';
export type { RegisterTranslations } from './pages/RegisterPage.svelte';
export type { ForgotPasswordTranslations } from './pages/ForgotPasswordPage.svelte';
// Icon paths
export { iconPaths } from './icons/iconPaths';

View file

@ -1,7 +1,7 @@
<script lang="ts">
import type { Component, Snippet } from 'svelte';
import type { AuthResult } from '../types';
import Icon from '../components/Icon.svelte';
import { Key, ArrowLeft, EnvelopeOpen, SignIn } from '@manacore/shared-icons';
type PageMode = 'form' | 'success';
@ -136,7 +136,7 @@
<div
class="flex min-h-screen flex-col justify-between"
style="background-color: {getPageBackground()};"
style="background-color: {getPageBackground()}; max-width: 100vw; overflow-x: hidden;"
>
<!-- Top Section - Logo -->
<div class="flex flex-col items-center justify-center pt-16 pb-8">
@ -221,7 +221,7 @@
? '#ffffff'
: '#000000'};"
>
<Icon name="key" size={20} />
<Key size={20} class="inline-block" />
{loading ? t.sending : t.sendResetLinkButton}
</button>
</form>
@ -233,7 +233,7 @@
class="flex h-10 w-full items-center justify-center gap-2 rounded-xl font-medium transition-all hover:opacity-80"
style="color: {isDark ? '#ffffff' : '#000000'};"
>
<Icon name="arrow-left" size={20} />
<ArrowLeft size={20} class="inline-block" />
{t.backToLogin}
</button>
</div>
@ -244,9 +244,9 @@
<div class="flex flex-col items-center mb-6">
<div
class="w-20 h-20 rounded-full flex items-center justify-center mb-6"
style="background-color: {primaryColor}30;"
style="background-color: {primaryColor}30; color: {primaryColor};"
>
<Icon name="mail-open" size={40} color={primaryColor} />
<EnvelopeOpen size={40} />
</div>
<p
@ -268,7 +268,7 @@
? '#ffffff'
: '#000000'};"
>
<Icon name="sign-in" size={20} />
<SignIn size={20} class="inline-block" />
{t.backToLogin}
</button>

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
<script lang="ts">
import type { Component } from 'svelte';
import type { AuthResult } from '../types';
import Icon from '../components/Icon.svelte';
import { Eye, EyeSlash, UserPlus, ArrowLeft } from '@manacore/shared-icons';
import type { Snippet } from 'svelte';
@ -218,7 +218,7 @@
<div
class="flex min-h-screen flex-col justify-between"
style="background-color: {getPageBackground()};"
style="background-color: {getPageBackground()}; max-width: 100vw; overflow-x: hidden;"
>
<!-- Top Section - Logo -->
<div class="flex flex-col items-center justify-center pt-16 pb-8">
@ -296,64 +296,68 @@
/>
</div>
<div class="mb-2 relative">
<input
type={showPassword ? 'text' : 'password'}
bind:value={password}
placeholder={t.passwordPlaceholder}
required
minlength={8}
class="h-14 w-full rounded-xl border px-4 pr-12 text-lg transition-colors focus:outline-none focus:ring-2"
style="background-color: {isDark
? 'rgba(0, 0, 0, 0.2)'
: 'rgba(255, 255, 255, 0.8)'}; border-color: {isDark
? 'rgba(255, 255, 255, 0.2)'
: 'rgba(0, 0, 0, 0.1)'}; color: {isDark
? '#ffffff'
: '#000000'}; --tw-ring-color: {primaryColor};"
/>
<button
type="button"
onclick={() => (showPassword = !showPassword)}
class="absolute right-3 top-1/2 -translate-y-1/2 p-2 rounded-lg hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
aria-label={showPassword ? t.hidePassword : t.showPassword}
>
<Icon
name={showPassword ? 'eye-off' : 'eye'}
size={20}
color={isDark ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)'}
<div class="mb-2">
<div class="relative">
<input
type={showPassword ? 'text' : 'password'}
bind:value={password}
placeholder={t.passwordPlaceholder}
required
minlength={8}
class="h-14 w-full rounded-xl border px-4 pr-14 text-lg transition-colors focus:outline-none focus:ring-2"
style="background-color: {isDark
? 'rgba(0, 0, 0, 0.2)'
: 'rgba(255, 255, 255, 0.8)'}; border-color: {isDark
? 'rgba(255, 255, 255, 0.2)'
: 'rgba(0, 0, 0, 0.1)'}; color: {isDark
? '#ffffff'
: '#000000'}; --tw-ring-color: {primaryColor};"
/>
</button>
<button
type="button"
onclick={() => (showPassword = !showPassword)}
class="absolute inset-y-0 right-0 flex items-center justify-center w-14 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
aria-label={showPassword ? t.hidePassword : t.showPassword}
>
{#if showPassword}
<EyeSlash size={20} />
{:else}
<Eye size={20} />
{/if}
</button>
</div>
</div>
<div class="mb-2 relative">
<input
type={showConfirmPassword ? 'text' : 'password'}
bind:value={confirmPassword}
placeholder={t.confirmPasswordPlaceholder}
required
minlength={8}
class="h-14 w-full rounded-xl border px-4 pr-12 text-lg transition-colors focus:outline-none focus:ring-2"
style="background-color: {isDark
? 'rgba(0, 0, 0, 0.2)'
: 'rgba(255, 255, 255, 0.8)'}; border-color: {isDark
? 'rgba(255, 255, 255, 0.2)'
: 'rgba(0, 0, 0, 0.1)'}; color: {isDark
? '#ffffff'
: '#000000'}; --tw-ring-color: {primaryColor};"
/>
<button
type="button"
onclick={() => (showConfirmPassword = !showConfirmPassword)}
class="absolute right-3 top-1/2 -translate-y-1/2 p-2 rounded-lg hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
aria-label={showConfirmPassword ? t.hidePassword : t.showPassword}
>
<Icon
name={showConfirmPassword ? 'eye-off' : 'eye'}
size={20}
color={isDark ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)'}
<div class="mb-2">
<div class="relative">
<input
type={showConfirmPassword ? 'text' : 'password'}
bind:value={confirmPassword}
placeholder={t.confirmPasswordPlaceholder}
required
minlength={8}
class="h-14 w-full rounded-xl border px-4 pr-14 text-lg transition-colors focus:outline-none focus:ring-2"
style="background-color: {isDark
? 'rgba(0, 0, 0, 0.2)'
: 'rgba(255, 255, 255, 0.8)'}; border-color: {isDark
? 'rgba(255, 255, 255, 0.2)'
: 'rgba(0, 0, 0, 0.1)'}; color: {isDark
? '#ffffff'
: '#000000'}; --tw-ring-color: {primaryColor};"
/>
</button>
<button
type="button"
onclick={() => (showConfirmPassword = !showConfirmPassword)}
class="absolute inset-y-0 right-0 flex items-center justify-center w-14 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
aria-label={showConfirmPassword ? t.hidePassword : t.showPassword}
>
{#if showConfirmPassword}
<EyeSlash size={20} />
{:else}
<Eye size={20} />
{/if}
</button>
</div>
</div>
<!-- Password Requirements -->
@ -372,7 +376,7 @@
? '#ffffff'
: '#000000'};"
>
<Icon name="user-plus" size={20} />
<UserPlus size={20} class="inline-block" />
{loading ? t.creatingAccount : t.createAccountButton}
</button>
</form>
@ -384,7 +388,7 @@
class="flex h-10 w-full items-center justify-center gap-2 rounded-xl font-medium transition-all hover:opacity-80"
style="color: {isDark ? '#ffffff' : '#000000'};"
>
<Icon name="arrow-left" size={20} />
<ArrowLeft size={20} class="inline-block" />
{t.backToLogin}
</button>
</div>

View file

@ -61,20 +61,3 @@ export interface AuthResult {
needsVerification?: boolean;
}
/**
* Icon names available in the icon set
*/
export type IconName =
| 'user-plus'
| 'sign-in'
| 'eye'
| 'eye-off'
| 'key'
| 'arrow-left'
| 'info'
| 'mail-open'
| 'lock'
| 'shield-check'
| 'arrows-left-right'
| 'envelope'
| 'folder';