style(landing): add micro-interactions and polish public pages
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 29s
CI / tests-unit (push) Successful in 1m48s
CI / tests-integration (push) Successful in 1m52s
CI / tests-ui (push) Successful in 2m35s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m7s
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 29s
CI / tests-unit (push) Successful in 1m48s
CI / tests-integration (push) Successful in 1m52s
CI / tests-ui (push) Successful in 2m35s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m7s
This commit is contained in:
1 parent
57710a7fe3
commit
3265c149da
8 files changed
+235
-36
No files matched your search
Generated
+9
-8
@@ -166,6 +166,7 @@ overrides:
|
|||||||
glob: ^11.0.0
|
glob: ^11.0.0
|
||||||
'@esbuild-kit/core-utils': npm:tsx@^4.23.15
|
'@esbuild-kit/core-utils': npm:tsx@^4.23.15
|
||||||
'@esbuild-kit/esm-loader': npm:tsx@^4.23.15
|
'@esbuild-kit/esm-loader': npm:tsx@^4.23.15
|
||||||
|
source-map-js: 1.2.2
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
@@ -3463,8 +3464,8 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
[email protected].1:
|
[email protected].2:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
@@ -4862,7 +4863,7 @@ snapshots:
|
|||||||
jiti: 2.7.0
|
jiti: 2.7.0
|
||||||
lightningcss: 1.32.0
|
lightningcss: 1.32.0
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
tailwindcss: 4.3.3
|
tailwindcss: 4.3.3
|
||||||
|
|
||||||
'@tailwindcss/[email protected]':
|
'@tailwindcss/[email protected]':
|
||||||
@@ -5339,7 +5340,7 @@ snapshots:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
mdn-data: 2.27.1
|
mdn-data: 2.27.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
@@ -5831,7 +5832,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.29.9
|
'@babel/parser': 7.29.9
|
||||||
'@babel/types': 7.29.8
|
'@babel/types': 7.29.8
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
@@ -6086,13 +6087,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.19
|
nanoid: 3.3.19
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.19
|
nanoid: 3.3.19
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
|
|
||||||
[email protected]([email protected]):
|
[email protected]([email protected]):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6328,7 +6329,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.3.0
|
'@types/react': 19.3.0
|
||||||
|
|
||||||
[email protected].1: {}
|
[email protected].2: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
|
|||||||
@@ -16,3 +16,4 @@ overrides:
|
|||||||
glob: '^11.0.0'
|
glob: '^11.0.0'
|
||||||
'@esbuild-kit/core-utils': 'npm:tsx@^4.23.15'
|
'@esbuild-kit/core-utils': 'npm:tsx@^4.23.15'
|
||||||
'@esbuild-kit/esm-loader': 'npm:tsx@^4.23.15'
|
'@esbuild-kit/esm-loader': 'npm:tsx@^4.23.15'
|
||||||
|
source-map-js: '1.2.2'
|
||||||
@@ -47,7 +47,31 @@ export default async function LoginPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
|
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
|
||||||
<AuthTopBar hotelName={hotelName} logo={logo} />
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute -inset-x-16 -top-24 overflow-hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="aurora-blob -left-20 top-0 h-80 w-80"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"color-mix(in srgb, var(--color-primary) 34%, transparent)",
|
||||||
|
opacity: 0.45,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="aurora-blob -right-20 top-6 h-72 w-72"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"color-mix(in srgb, var(--color-accent) 30%, transparent)",
|
||||||
|
animationDelay: "-7s",
|
||||||
|
opacity: 0.4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<AuthTopBar hotelName={hotelName} logo={logo} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="flex flex-col gap-8 lg:flex-row lg:items-start">
|
<div className="flex flex-col gap-8 lg:flex-row lg:items-start">
|
||||||
|
|||||||
+26
-17
@@ -1,5 +1,5 @@
|
|||||||
import { count, desc, eq } from "drizzle-orm";
|
import { count, desc, eq } from "drizzle-orm";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ArrowRight, ChevronDown } from "lucide-react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@@ -15,6 +15,7 @@ import { LiveOnlineCounter } from "@/components/live-online-counter";
|
|||||||
import { Reveal } from "@/components/motion-reveal";
|
import { Reveal } from "@/components/motion-reveal";
|
||||||
import { PublicLoadError } from "@/components/public/load-error";
|
import { PublicLoadError } from "@/components/public/load-error";
|
||||||
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
|
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
|
||||||
|
import { SpotlightCard } from "@/components/spotlight-card";
|
||||||
import { ThemeSwitcher } from "@/components/theme-switcher";
|
import { ThemeSwitcher } from "@/components/theme-switcher";
|
||||||
import { TypewriterText } from "@/components/typewriter-text";
|
import { TypewriterText } from "@/components/typewriter-text";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
@@ -311,7 +312,7 @@ export default async function Home() {
|
|||||||
className="text-sm font-bold"
|
className="text-sm font-bold"
|
||||||
style={{ color: "rgba(255,255,255,0.95)" }}
|
style={{ color: "rgba(255,255,255,0.95)" }}
|
||||||
>
|
>
|
||||||
→
|
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -578,12 +579,13 @@ export default async function Home() {
|
|||||||
|
|
||||||
{recentUsers !== null && recentUsers.length > 0 && (
|
{recentUsers !== null && recentUsers.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className="mt-7 inline-flex items-center gap-3 rounded-full border px-3 py-1.5"
|
className="animate-fade-in-up mt-7 inline-flex items-center gap-3 rounded-full border px-3 py-1.5"
|
||||||
style={{
|
style={{
|
||||||
background: "rgba(255,255,255,0.08)",
|
background: "rgba(255,255,255,0.08)",
|
||||||
borderColor: "rgba(255,255,255,0.22)",
|
borderColor: "rgba(255,255,255,0.22)",
|
||||||
backdropFilter: "blur(12px)",
|
backdropFilter: "blur(12px)",
|
||||||
WebkitBackdropFilter: "blur(12px)",
|
WebkitBackdropFilter: "blur(12px)",
|
||||||
|
animationDelay: "0.08s",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex -space-x-2.5">
|
<div className="flex -space-x-2.5">
|
||||||
@@ -606,7 +608,10 @@ export default async function Home() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
|
<div
|
||||||
|
className="animate-fade-in-up mt-9 flex flex-wrap items-center justify-center gap-3"
|
||||||
|
style={{ animationDelay: "0.2s" }}
|
||||||
|
>
|
||||||
<Link
|
<Link
|
||||||
href="/register"
|
href="/register"
|
||||||
className="btn-brand btn-shine px-8 py-4 text-sm font-black uppercase tracking-wider"
|
className="btn-brand btn-shine px-8 py-4 text-sm font-black uppercase tracking-wider"
|
||||||
@@ -627,15 +632,18 @@ export default async function Home() {
|
|||||||
{th("login")}
|
{th("login")}
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="transition-transform duration-300 group-hover:translate-x-1"
|
className="inline-flex transition-transform duration-300 group-hover:translate-x-1"
|
||||||
>
|
>
|
||||||
→
|
<ArrowRight className="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating stat chips */}
|
{/* Floating stat chips */}
|
||||||
<div className="mt-12 flex flex-wrap items-center justify-center gap-2.5 sm:gap-3">
|
<div
|
||||||
|
className="animate-fade-in-up mt-12 flex flex-wrap items-center justify-center gap-2.5 sm:gap-3"
|
||||||
|
style={{ animationDelay: "0.32s" }}
|
||||||
|
>
|
||||||
{statChips.map((s) => (
|
{statChips.map((s) => (
|
||||||
<span key={s.label} className="glass-chip">
|
<span key={s.label} className="glass-chip">
|
||||||
<span
|
<span
|
||||||
@@ -723,15 +731,13 @@ export default async function Home() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{l.label}
|
{l.label}
|
||||||
<span
|
<ArrowRight
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="pill-arrow text-[11px]"
|
className="pill-arrow h-3.5 w-3.5"
|
||||||
style={{
|
style={{
|
||||||
color: "var(--color-primary-readable, var(--color-primary))",
|
color: "var(--color-primary-readable, var(--color-primary))",
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
→
|
|
||||||
</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
@@ -756,9 +762,9 @@ export default async function Home() {
|
|||||||
const accentVar =
|
const accentVar =
|
||||||
i % 2 === 0 ? "var(--color-primary)" : "var(--color-accent)";
|
i % 2 === 0 ? "var(--color-primary)" : "var(--color-accent)";
|
||||||
return (
|
return (
|
||||||
<div
|
<SpotlightCard
|
||||||
key={f.title}
|
key={f.title}
|
||||||
className="card-glow card-hairline group relative overflow-hidden rounded-2xl border p-6 transition-all duration-500 hover:-translate-y-1.5 hover:shadow-2xl sm:p-7"
|
className="spotlight card-glow card-hairline group relative overflow-hidden rounded-2xl border p-6 transition-all duration-500 hover:-translate-y-1.5 hover:shadow-2xl sm:p-7"
|
||||||
style={{
|
style={{
|
||||||
background: GLASS_TILE_BG,
|
background: GLASS_TILE_BG,
|
||||||
backdropFilter: "blur(16px)",
|
backdropFilter: "blur(16px)",
|
||||||
@@ -811,7 +817,7 @@ export default async function Home() {
|
|||||||
>
|
>
|
||||||
{f.desc}
|
{f.desc}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</SpotlightCard>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -1106,8 +1112,11 @@ export default async function Home() {
|
|||||||
className="btn-brand btn-shine group px-8 py-4 text-sm font-black uppercase tracking-wide"
|
className="btn-brand btn-shine group px-8 py-4 text-sm font-black uppercase tracking-wide"
|
||||||
>
|
>
|
||||||
{tp("ctaJoin")}
|
{tp("ctaJoin")}
|
||||||
<span className="transition-transform duration-300 group-hover:translate-x-1">
|
<span
|
||||||
→
|
aria-hidden="true"
|
||||||
|
className="inline-flex transition-transform duration-300 group-hover:translate-x-1"
|
||||||
|
>
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -57,7 +57,31 @@ export default async function RegisterPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
|
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
|
||||||
<AuthTopBar hotelName={hotelName} logo={logo} />
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute -inset-x-16 -top-24 overflow-hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="aurora-blob -left-20 top-0 h-80 w-80"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"color-mix(in srgb, var(--color-primary) 34%, transparent)",
|
||||||
|
opacity: 0.45,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="aurora-blob -right-20 top-6 h-72 w-72"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"color-mix(in srgb, var(--color-accent) 30%, transparent)",
|
||||||
|
animationDelay: "-7s",
|
||||||
|
opacity: 0.4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<AuthTopBar hotelName={hotelName} logo={logo} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="flex flex-col gap-8 lg:flex-row lg:items-start">
|
<div className="flex flex-col gap-8 lg:flex-row lg:items-start">
|
||||||
|
|||||||
@@ -2837,3 +2837,57 @@ html {
|
|||||||
background: color-mix(in srgb, var(--color-text-muted) 20%, transparent);
|
background: color-mix(in srgb, var(--color-text-muted) 20%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mouse-tracking spotlight glow for premium cards. The dot position comes
|
||||||
|
from CSS custom properties updated on pointermove (see SpotlightCard). */
|
||||||
|
.spotlight {
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
.spotlight::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s ease;
|
||||||
|
background: radial-gradient(
|
||||||
|
340px circle at var(--spot-x, 50%) var(--spot-y, 50%),
|
||||||
|
color-mix(in srgb, var(--color-primary) 20%, transparent),
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.spotlight:hover::before,
|
||||||
|
.spotlight:focus-within::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section number mark (01 / 02 / …) used as a faint watermark in headers. */
|
||||||
|
.number-mark {
|
||||||
|
font-family: var(--font-nunito);
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1;
|
||||||
|
background: linear-gradient(
|
||||||
|
120deg,
|
||||||
|
color-mix(in srgb, var(--color-primary) 65%, transparent),
|
||||||
|
color-mix(in srgb, var(--color-accent) 50%, transparent)
|
||||||
|
);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.spotlight::before {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
.animate-fade-in-up,
|
||||||
|
.animate-scale-in,
|
||||||
|
.animate-count {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Lock, Mail, UserRound } from "lucide-react";
|
import { KeyRound, Lock, Mail, ShieldCheck, UserRound } from "lucide-react";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
@@ -183,7 +183,24 @@ export function RegisterForm({
|
|||||||
|
|
||||||
{/* Username & Email */}
|
{/* Username & Email */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2.5">
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
|
||||||
|
boxShadow:
|
||||||
|
"inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 22%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserRound
|
||||||
|
className="h-3.5 w-3.5"
|
||||||
|
style={{
|
||||||
|
color: "var(--color-primary-readable, var(--color-primary))",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
className="text-[11px] font-black uppercase tracking-widest"
|
className="text-[11px] font-black uppercase tracking-widest"
|
||||||
style={{
|
style={{
|
||||||
@@ -199,6 +216,9 @@ export function RegisterForm({
|
|||||||
"linear-gradient(90deg, color-mix(in srgb, var(--color-text-muted) 18%, transparent), transparent)",
|
"linear-gradient(90deg, color-mix(in srgb, var(--color-text-muted) 18%, transparent), transparent)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<span aria-hidden="true" className="number-mark text-lg">
|
||||||
|
01
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 stagger-children">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 stagger-children">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
@@ -247,7 +267,24 @@ export function RegisterForm({
|
|||||||
|
|
||||||
{/* Password & Confirm */}
|
{/* Password & Confirm */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2.5">
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
|
||||||
|
boxShadow:
|
||||||
|
"inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 22%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KeyRound
|
||||||
|
className="h-3.5 w-3.5"
|
||||||
|
style={{
|
||||||
|
color: "var(--color-primary-readable, var(--color-primary))",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
className="text-[11px] font-black uppercase tracking-widest"
|
className="text-[11px] font-black uppercase tracking-widest"
|
||||||
style={{
|
style={{
|
||||||
@@ -263,6 +300,9 @@ export function RegisterForm({
|
|||||||
"linear-gradient(90deg, color-mix(in srgb, var(--color-text-muted) 18%, transparent), transparent)",
|
"linear-gradient(90deg, color-mix(in srgb, var(--color-text-muted) 18%, transparent), transparent)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<span aria-hidden="true" className="number-mark text-lg">
|
||||||
|
02
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 stagger-children">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 stagger-children">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
@@ -313,12 +353,19 @@ export function RegisterForm({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p
|
<div className="flex items-center gap-1.5">
|
||||||
className="text-[11px] font-extrabold uppercase tracking-wider"
|
<ShieldCheck
|
||||||
style={{ color: strength.color }}
|
aria-hidden="true"
|
||||||
>
|
className="h-3.5 w-3.5 shrink-0"
|
||||||
{strength.label}
|
style={{ color: strength.color }}
|
||||||
</p>
|
/>
|
||||||
|
<p
|
||||||
|
className="text-[11px] font-extrabold uppercase tracking-wider"
|
||||||
|
style={{ color: strength.color }}
|
||||||
|
>
|
||||||
|
{strength.label}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { CSSProperties, MouseEvent, ReactNode } from "react";
|
||||||
|
|
||||||
|
interface SpotlightCardProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
style?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a card with a mouse-tracking radial glow. The pointer position is
|
||||||
|
* written to `--spot-x` / `--spot-y` and consumed by the `.spotlight::before`
|
||||||
|
* overlay in globals.css.
|
||||||
|
*/
|
||||||
|
export function SpotlightCard({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
}: SpotlightCardProps) {
|
||||||
|
const handlePointerMove = (event: MouseEvent<HTMLDivElement>) => {
|
||||||
|
const el = event.currentTarget;
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
el.style.setProperty("--spot-x", `${event.clientX - rect.left}px`);
|
||||||
|
el.style.setProperty("--spot-y", `${event.clientY - rect.top}px`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
style={
|
||||||
|
{ "--spot-x": "50%", "--spot-y": "50%", ...style } as CSSProperties
|
||||||
|
}
|
||||||
|
onPointerMove={handlePointerMove}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user