feat: enhance login page design and functionality by integrating new components, updating styles, and improving user experience
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { class: className = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class={['absolute inset-0 h-full w-full', className]}>
|
||||
<div
|
||||
class="bg-primary/20 absolute top-[-10%] left-[-10%] h-[40%] w-[40%] animate-pulse rounded-full blur-[120px]"
|
||||
></div>
|
||||
<div
|
||||
class="bg-secondary/20 absolute right-[-10%] bottom-[-10%] h-[40%] w-[40%] animate-pulse rounded-full blur-[120px] delay-700"
|
||||
></div>
|
||||
</div>
|
||||
14
apps/web/src/lib/components/DecorativeTopLine.svelte
Normal file
14
apps/web/src/lib/components/DecorativeTopLine.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { class: className = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={[
|
||||
'via-primary absolute top-0 left-0 h-1 w-full bg-linear-to-r from-transparent to-transparent opacity-50',
|
||||
className
|
||||
]}
|
||||
></div>
|
||||
22
apps/web/src/lib/components/ErrorMessage.svelte
Normal file
22
apps/web/src/lib/components/ErrorMessage.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { XCircle } from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
message?: string | null;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { message = null, class: className = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if message}
|
||||
<div
|
||||
class={[
|
||||
'border-error/20 bg-error/10 text-error-content/90 mb-6 flex items-center gap-3 rounded-lg border p-4 backdrop-blur-md',
|
||||
className
|
||||
]}
|
||||
>
|
||||
<XCircle class="h-5 w-5 shrink-0" />
|
||||
<span class="text-sm font-medium">{message}</span>
|
||||
</div>
|
||||
{/if}
|
||||
19
apps/web/src/lib/components/GlassCard.svelte
Normal file
19
apps/web/src/lib/components/GlassCard.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { class: className = '', children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={[
|
||||
'border-base-content/10 bg-base-content/5 ring-base-content/10 relative overflow-hidden rounded-2xl border p-8 shadow-2xl ring-1 backdrop-blur-xl transition-all duration-300',
|
||||
className
|
||||
]}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -12,7 +12,7 @@
|
||||
</script>
|
||||
|
||||
<header
|
||||
class="bg-base-200 border-base-100 sticky top-0 z-50 w-full border-b shadow-sm backdrop-blur-md transition-all duration-300"
|
||||
class="bg-base-200 border-base-100 sticky top-0 z-50 w-full border-b py-3 shadow-sm backdrop-blur-md transition-all duration-300"
|
||||
>
|
||||
<div class=" flex h-16 w-full items-center justify-between px-4">
|
||||
<div class="flex items-center gap-3">
|
||||
|
||||
85
apps/web/src/lib/components/MenuToggleIcon.svelte
Normal file
85
apps/web/src/lib/components/MenuToggleIcon.svelte
Normal file
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { prefersReducedMotion, Spring } from 'svelte/motion';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
class?: string;
|
||||
stroke?: number;
|
||||
}
|
||||
|
||||
let { open, class: className = '', stroke = 2 }: Props = $props();
|
||||
|
||||
const progress = Spring.of(() => (open ? 1 : 0), {
|
||||
stiffness: 0.25,
|
||||
damping: 0.65,
|
||||
precision: 0.001
|
||||
});
|
||||
|
||||
const clamp01 = (n: number) => Math.max(0, Math.min(1, n));
|
||||
const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
|
||||
|
||||
let t = $derived(prefersReducedMotion.current ? (open ? 1 : 0) : progress.current);
|
||||
let tFast = $derived(clamp01(t * 1.15));
|
||||
|
||||
// Fechado: hambúrguer. Aberto: "outro menu" (linhas deslocadas + comprimentos diferentes).
|
||||
// Continua sendo ícone de menu (não vira X).
|
||||
let topY = $derived(lerp(-6, -7, tFast));
|
||||
let botY = $derived(lerp(6, 7, tFast));
|
||||
|
||||
let topX = $derived(lerp(0, 3.25, t));
|
||||
let midX = $derived(lerp(0, -2.75, t));
|
||||
let botX = $derived(lerp(0, 1.75, t));
|
||||
|
||||
// micro-inclinação só pra dar “vida”, sem cruzar em X
|
||||
let topR = $derived(lerp(0, 2.5, tFast));
|
||||
let botR = $derived(lerp(0, -2.5, tFast));
|
||||
|
||||
let topScaleX = $derived(lerp(1, 0.62, tFast));
|
||||
let midScaleX = $derived(lerp(1, 0.92, tFast));
|
||||
let botScaleX = $derived(lerp(1, 0.72, tFast));
|
||||
|
||||
let topOpacity = $derived(1);
|
||||
let midOpacity = $derived(1);
|
||||
let botOpacity = $derived(1);
|
||||
</script>
|
||||
|
||||
<span class="menu-toggle-icon {className}" aria-hidden="true" style="--stroke: {stroke}px">
|
||||
<span
|
||||
class="line"
|
||||
style="--x: {topX}px; --y: {topY}px; --r: {topR}deg; --o: {topOpacity}; --sx: {topScaleX}"
|
||||
></span>
|
||||
<span
|
||||
class="line"
|
||||
style="--x: {midX}px; --y: 0px; --r: 0deg; --o: {midOpacity}; --sx: {midScaleX}"
|
||||
></span>
|
||||
<span
|
||||
class="line"
|
||||
style="--x: {botX}px; --y: {botY}px; --r: {botR}deg; --o: {botOpacity}; --sx: {botScaleX}"
|
||||
></span>
|
||||
</span>
|
||||
|
||||
<style>
|
||||
.menu-toggle-icon {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
margin-top: calc(var(--stroke) / -2);
|
||||
height: var(--stroke);
|
||||
border-radius: 9999px;
|
||||
background: currentColor;
|
||||
opacity: var(--o, 1);
|
||||
transform-origin: center;
|
||||
transform: translateX(var(--x, 0px)) translateY(var(--y, 0px)) rotate(var(--r, 0deg))
|
||||
scaleX(var(--sx, 1));
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
</style>
|
||||
14
apps/web/src/lib/components/ShineEffect.svelte
Normal file
14
apps/web/src/lib/components/ShineEffect.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { class: className = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={[
|
||||
'via-base-content/20 absolute inset-0 -translate-x-full bg-linear-to-r from-transparent to-transparent transition-transform duration-1000 group-hover:translate-x-full',
|
||||
className
|
||||
]}
|
||||
></div>
|
||||
62
apps/web/src/lib/components/login/LoginInput.svelte
Normal file
62
apps/web/src/lib/components/login/LoginInput.svelte
Normal file
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { Field } from '@ark-ui/svelte/field';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
label: string;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
autocomplete?: HTMLInputAttributes['autocomplete'];
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
error?: string | null;
|
||||
right?: Snippet;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
id,
|
||||
label,
|
||||
type = 'text',
|
||||
placeholder = '',
|
||||
autocomplete,
|
||||
disabled = false,
|
||||
required = false,
|
||||
error = null,
|
||||
right,
|
||||
value = $bindable('')
|
||||
}: Props = $props();
|
||||
|
||||
const invalid = $derived(!!error);
|
||||
</script>
|
||||
|
||||
<Field.Root {invalid} {required} class="space-y-2">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<Field.Label
|
||||
for={id}
|
||||
class="text-base-content/60 text-xs font-semibold tracking-wider uppercase"
|
||||
>
|
||||
{label}
|
||||
</Field.Label>
|
||||
{@render right?.()}
|
||||
</div>
|
||||
|
||||
<div class="group relative">
|
||||
<Field.Input
|
||||
{id}
|
||||
{type}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
{autocomplete}
|
||||
{required}
|
||||
bind:value
|
||||
class="border-base-content/10 bg-base-200/25 text-base-content placeholder-base-content/40 focus:border-primary/50 focus:bg-base-200/35 focus:ring-primary/20 w-full rounded-xl border px-4 py-3 transition-all duration-300 focus:ring-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-60"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<Field.ErrorText class="text-error text-sm font-medium">{error}</Field.ErrorText>
|
||||
{/if}
|
||||
</Field.Root>
|
||||
Reference in New Issue
Block a user