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

@ -98,59 +98,35 @@
});
</script>
<div class="w-full">
<h3
class="mb-4 text-center text-sm font-medium"
style="color: {isDark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)'};"
>
{title}
</h3>
<div class="app-slider-root">
<h3 class="slider-title">{title}</h3>
<div class="relative">
<div
class="flex gap-4 justify-center overflow-x-auto pb-6 scrollbar-hide snap-x snap-mandatory scroll-smooth px-4 py-4"
style="perspective: 1000px;"
>
<div class="slider-scroll-container">
<div class="slider-items">
{#each apps as app, index}
<button
class="group relative flex-shrink-0 rounded-xl p-5 cursor-pointer snap-center transition-transform hover:scale-105"
style="width: 160px; background-color: {isDark
? 'rgba(255, 255, 255, 0.08)'
: 'rgba(255, 255, 255, 0.7)'}; backdrop-filter: blur(10px); border: 1px solid {isDark
? 'rgba(255, 255, 255, 0.1)'
: 'rgba(0, 0, 0, 0.1)'};"
class="app-card"
style="--index: {index};"
onmouseenter={() => (hoveredApp = index)}
onmouseleave={() => (hoveredApp = null)}
onclick={() => openModal(index)}
>
<div
class="absolute top-3 right-3 w-3 h-3 rounded-full status-indicator"
style="background-color: {getStatusColor(
app.status
)}; box-shadow: 0 0 8px {getStatusColor(app.status)};"
class="status-indicator"
style="background-color: {getStatusColor(app.status)}; box-shadow: 0 0 8px {getStatusColor(app.status)};"
></div>
<div
class="mb-2 flex h-20 w-20 mx-auto items-center justify-center rounded-xl transition-transform group-hover:scale-110"
>
<div class="app-icon-wrapper">
{#if app.icon}
<img src={app.icon} alt={app.name} class="w-16 h-16 object-contain" />
<img src={app.icon} alt={app.name} class="app-icon" />
{:else}
<div
class="flex h-10 w-10 items-center justify-center rounded font-bold text-lg"
style="color: {app.color};"
>
<div class="app-icon-fallback" style="color: {app.color};">
{app.name.charAt(0)}
</div>
{/if}
</div>
<h4
class="text-base font-semibold text-center"
style="color: {isDark ? '#ffffff' : '#000000'};"
>
{app.name}
</h4>
<h4 class="app-name">{app.name}</h4>
</button>
{/each}
</div>
@ -159,8 +135,7 @@
{#if selectedApp !== null}
<div
class="fixed inset-0 z-50 flex items-center justify-center"
style="background-color: rgba(0, 0, 0, 0.85);"
class="modal-overlay"
onclick={closeModal}
onkeydown={(e) => e.key === 'Escape' && closeModal()}
role="dialog"
@ -169,37 +144,24 @@
>
<button
onclick={closeModal}
class="absolute top-6 right-6 rounded-full p-2 transition-all hover:bg-white/10 z-10"
class="modal-close-btn"
aria-label="Close modal"
>
<svg class="h-8 w-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
<div
bind:this={modalScrollContainer}
class="absolute inset-0 flex items-center overflow-x-auto scrollbar-hide snap-x snap-mandatory scroll-smooth"
class="modal-scroll-container scrollbar-hide"
>
<div class="flex gap-6 px-8 py-8 mx-auto" style="perspective: 1000px;">
<div class="modal-cards-wrapper">
{#each apps as app, index}
<div
class="flex-shrink-0 rounded-3xl p-8 snap-center shadow-2xl relative"
style="min-width: 360px; max-width: 360px; background-color: {hoveredApp === index
? isDark
? '#2A2A2A'
: '#F5F5F5'
: isDark
? '#1E1E1E'
: '#ffffff'}; border: 3px solid {app.color}40; transform: perspective(1000px) rotateX({cardRotations[
index
]?.rotateX || 0}deg) rotateY({cardRotations[index]?.rotateY ||
0}deg); transform-style: preserve-3d; transition: transform 0.1s ease-out, background-color 0.2s ease-out;"
class="modal-card"
class:active={selectedApp === index}
style="transform: perspective(1000px) rotateX({cardRotations[index]?.rotateX || 0}deg) rotateY({cardRotations[index]?.rotateY || 0}deg);"
onclick={(e) => {
e.stopPropagation();
selectedApp = index;
@ -214,66 +176,37 @@
role="button"
tabindex="0"
>
<div class="absolute top-4 right-4 flex items-center gap-2">
<span
class="text-xs font-medium"
style="color: {isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.7)'};"
>
{getStatusLabel(app.status)}
</span>
<div class="modal-card-status">
<div
class="w-4 h-4 rounded-full status-indicator"
style="background-color: {getStatusColor(
app.status
)}; box-shadow: 0 0 12px {getStatusColor(app.status)};"
class="modal-status-dot"
style="background-color: {getStatusColor(app.status)};"
></div>
<span class="modal-status-label">{getStatusLabel(app.status)}</span>
</div>
{#if app.icon}
<img src={app.icon} alt={app.name} class="w-28 h-28 object-contain mb-3 mx-auto" />
<img src={app.icon} alt={app.name} class="modal-app-icon" />
{:else}
<div
class="flex h-16 w-16 items-center justify-center rounded font-bold text-3xl mb-3 mx-auto"
style="color: {app.color};"
>
<div class="modal-app-icon-fallback" style="color: {app.color};">
{app.name.charAt(0)}
</div>
{/if}
<h3
class="text-2xl font-bold mb-2 text-center"
style="color: {isDark ? '#ffffff' : '#000000'};"
>
{app.name}
</h3>
<h3 class="modal-app-name">{app.name}</h3>
<p class="text-sm mb-4 text-center font-medium" style="color: {app.color};">
<p class="modal-app-tagline" style="color: {app.color};">
{app.description}
</p>
<p
class="text-sm leading-relaxed mb-6 text-center"
style="color: {isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.7)'};"
>
{app.longDescription}
</p>
<p class="modal-app-description">{app.longDescription}</p>
<div class="text-center">
<div class="modal-app-action">
{#if app.comingSoon}
<div
class="inline-block rounded-full px-5 py-2.5 text-sm font-medium"
style="background-color: {isDark
? 'rgba(255, 255, 255, 0.1)'
: 'rgba(0, 0, 0, 0.1)'}; color: {isDark
? 'rgba(255, 255, 255, 0.5)'
: 'rgba(0, 0, 0, 0.5)'};"
>
{comingSoonLabel}
</div>
<span class="modal-coming-soon">{comingSoonLabel}</span>
{:else}
<button
class="rounded-xl px-8 py-3 text-sm font-semibold transition-all hover:opacity-80 border-2 text-white"
style="background-color: {app.color}60; border-color: {app.color};"
class="modal-open-btn"
style="background-color: {app.color}40; border-color: {app.color};"
onclick={(e) => {
e.stopPropagation();
handleAppAction(app, index);
@ -291,6 +224,163 @@
{/if}
<style>
.app-slider-root {
width: 100%;
max-width: 100%;
}
.slider-title {
margin-bottom: 1rem;
text-align: center;
font-size: 0.875rem;
font-weight: 500;
color: rgba(255, 255, 255, 0.5);
}
@media (prefers-color-scheme: light) {
.slider-title {
color: rgba(0, 0, 0, 0.5);
}
}
.slider-scroll-container {
width: 100%;
overflow-x: auto;
overflow-y: visible;
-ms-overflow-style: none;
scrollbar-width: none;
}
.slider-scroll-container::-webkit-scrollbar {
display: none;
}
.slider-items {
display: flex;
gap: 1rem;
padding: 1rem;
padding-bottom: 1.5rem;
width: max-content;
margin: 0 auto;
}
.app-card {
position: relative;
flex-shrink: 0;
width: 140px;
padding: 1.25rem 1rem;
border-radius: 1rem;
cursor: pointer;
border: 1px solid rgba(255, 255, 255, 0.1);
background-color: rgba(255, 255, 255, 0.06);
backdrop-filter: blur(10px);
transition: transform 0.2s ease, background-color 0.2s ease;
/* Staggered entrance animation */
animation: fadeInUp 0.4s ease-out both;
animation-delay: calc(0.3s + var(--index) * 0.08s);
}
.app-card:hover {
transform: scale(1.05);
background-color: rgba(255, 255, 255, 0.1);
}
@media (prefers-color-scheme: light) {
.app-card {
border-color: rgba(0, 0, 0, 0.08);
background-color: rgba(255, 255, 255, 0.7);
}
.app-card:hover {
background-color: rgba(255, 255, 255, 0.9);
}
}
.status-indicator {
position: absolute;
top: 0.75rem;
right: 0.75rem;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
animation: pulse 2s ease-in-out infinite;
}
.app-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 4rem;
height: 4rem;
margin: 0 auto 0.75rem;
transition: transform 0.2s ease;
}
.app-card:hover .app-icon-wrapper {
transform: scale(1.1);
}
.app-icon {
width: 3.5rem;
height: 3.5rem;
object-fit: contain;
}
.app-icon-fallback {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
font-size: 1.25rem;
font-weight: 700;
border-radius: 0.5rem;
}
.app-name {
font-size: 0.875rem;
font-weight: 600;
text-align: center;
color: #fff;
margin: 0;
}
@media (prefers-color-scheme: light) {
.app-name {
color: #000;
}
}
/* Entrance animation */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.app-card {
animation: none;
}
.status-indicator {
animation: none;
}
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
@ -300,17 +390,244 @@
scrollbar-width: none;
}
.status-indicator {
animation: pulse 2s ease-in-out infinite;
/* Modal Styles */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(8px);
animation: fadeIn 0.2s ease-out;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
.modal-close-btn {
position: fixed;
top: 1.5rem;
right: 1.5rem;
z-index: 60;
width: 3rem;
height: 3rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: rgba(255, 255, 255, 0.1);
color: #fff;
cursor: pointer;
transition: background-color 0.2s, transform 0.2s;
}
.modal-close-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: scale(1.05);
}
.modal-scroll-container {
position: absolute;
inset: 0;
display: flex;
align-items: center;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
.modal-cards-wrapper {
display: flex;
gap: 1.5rem;
padding: 2rem;
margin: 0 auto;
}
.modal-card {
flex-shrink: 0;
width: 340px;
padding: 2rem;
padding-top: 2.5rem;
border-radius: 1.5rem;
scroll-snap-align: center;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.1);
background-color: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(20px);
transform-style: preserve-3d;
transition: transform 0.1s ease-out, background-color 0.2s ease;
animation: modalCardIn 0.3s ease-out both;
}
.modal-card:hover {
background-color: rgba(255, 255, 255, 0.12);
}
@media (prefers-color-scheme: light) {
.modal-card {
background-color: rgba(255, 255, 255, 0.9);
border-color: rgba(0, 0, 0, 0.1);
}
50% {
opacity: 0.6;
.modal-card:hover {
background-color: rgba(255, 255, 255, 0.95);
}
}
.modal-card-status {
position: absolute;
top: 0.875rem;
right: 0.875rem;
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.625rem 0.25rem 0.5rem;
border-radius: 1rem;
background-color: rgba(0, 0, 0, 0.2);
}
@media (prefers-color-scheme: light) {
.modal-card-status {
background-color: rgba(0, 0, 0, 0.05);
}
}
.modal-status-dot {
width: 0.375rem;
height: 0.375rem;
border-radius: 50%;
flex-shrink: 0;
}
.modal-status-label {
font-size: 0.6875rem;
font-weight: 500;
color: rgba(255, 255, 255, 0.8);
}
@media (prefers-color-scheme: light) {
.modal-status-label {
color: rgba(0, 0, 0, 0.6);
}
}
.modal-app-icon {
width: 5rem;
height: 5rem;
object-fit: contain;
margin: 0 auto 1rem;
display: block;
}
.modal-app-icon-fallback {
width: 4rem;
height: 4rem;
margin: 0 auto 1rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
font-weight: 700;
border-radius: 0.75rem;
}
.modal-app-name {
font-size: 1.5rem;
font-weight: 700;
text-align: center;
margin: 0 0 0.5rem;
color: #fff;
}
@media (prefers-color-scheme: light) {
.modal-app-name {
color: #000;
}
}
.modal-app-tagline {
font-size: 0.875rem;
font-weight: 500;
text-align: center;
margin: 0 0 1rem;
}
.modal-app-description {
font-size: 0.875rem;
line-height: 1.6;
text-align: center;
margin: 0 0 1.5rem;
color: rgba(255, 255, 255, 0.7);
}
@media (prefers-color-scheme: light) {
.modal-app-description {
color: rgba(0, 0, 0, 0.6);
}
}
.modal-app-action {
text-align: center;
}
.modal-coming-soon {
display: inline-block;
padding: 0.625rem 1.25rem;
border-radius: 2rem;
font-size: 0.875rem;
font-weight: 500;
background-color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.5);
}
@media (prefers-color-scheme: light) {
.modal-coming-soon {
background-color: rgba(0, 0, 0, 0.08);
color: rgba(0, 0, 0, 0.4);
}
}
.modal-open-btn {
padding: 0.75rem 2rem;
border-radius: 0.75rem;
font-size: 0.875rem;
font-weight: 600;
border: 2px solid;
cursor: pointer;
transition: opacity 0.2s, transform 0.2s;
color: #fff;
}
.modal-open-btn:hover {
opacity: 0.85;
transform: scale(1.02);
}
@media (prefers-color-scheme: light) {
.modal-open-btn {
color: #000;
}
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes modalCardIn {
from {
opacity: 0;
transform: scale(0.95) translateY(10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.modal-overlay,
.modal-card {
animation: none;
}
}
</style>