Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yml

This file was deleted.

26 changes: 15 additions & 11 deletions app/artists/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export default function ArtistProfilePage() {
);
}

const getErrorMessage = (err: unknown): string => {
if (err instanceof Error) return err.message;
if (typeof err === 'string') return err;
return "We couldn't find the artist you're looking for.";
};

if (error || !artist) {
return (
<div className="min-h-screen flex items-center justify-center bg-neutral-50 dark:bg-neutral-950">
Expand All @@ -54,13 +60,7 @@ export default function ArtistProfilePage() {
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white mb-2">
Artist Not Found
</h1>
<p className="text-neutral-500 dark:text-neutral-400">
{error instanceof Error
? error.message
: typeof error === 'string'
? error
: "We couldn't find the artist you're looking for."}
</p>
<p className="text-neutral-500 dark:text-neutral-400">{getErrorMessage(error)}</p>
</div>
</div>
);
Expand All @@ -76,12 +76,16 @@ export default function ArtistProfilePage() {
<div className="min-h-screen bg-neutral-50 dark:bg-neutral-950">
{/* Cover Image */}
<div className="relative h-56 sm:h-72 bg-gradient-to-r from-primary-600 via-primary-700 to-secondary-600">
{artist.coverImage && (
<img
{artist.coverImage ? (
<NextImage
src={artist.coverImage}
alt={`${artist.name} cover`}
className="w-full h-full object-cover"
fill
className="object-cover"
sizes="100vw"
/>
) : (
<div className="w-full h-full bg-gradient-to-r from-primary-600 to-secondary-600" />
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent" />
</div>
Expand All @@ -93,7 +97,6 @@ export default function ArtistProfilePage() {
<div className="relative w-32 h-32 sm:w-36 sm:h-36 rounded-full border-4 border-white dark:border-neutral-900 overflow-hidden bg-neutral-200 dark:bg-neutral-700 shadow-lg flex-shrink-0">
{artist.avatar ? (
<NextImage
<Image
src={artist.avatar}
alt={artist.name}
fill
Expand Down Expand Up @@ -186,6 +189,7 @@ export default function ArtistProfilePage() {
{tabs.map((tab) => (
<button
key={tab.key}
type="button"
role="tab"
aria-selected={activeTab === tab.key}
onClick={() => setActiveTab(tab.key)}
Expand Down
49 changes: 9 additions & 40 deletions app/components/landing/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Image from 'next/image';
import { useEffect, useRef, useState } from 'react';

/* ─── Floating orb (purely decorative) ─── */
function Orb({ className, delay = 0 }: { className: string; delay?: number }) {
function Orb({ className, delay = 0 }: Readonly<{ className: string; delay?: number }>) {
return (
<span
aria-hidden="true"
Expand All @@ -16,7 +16,7 @@ function Orb({ className, delay = 0 }: { className: string; delay?: number }) {
}

/* ─── Animated stat for social proof ─── */
function AnimatedStat({ value, label }: { value: string; label: string }) {
function AnimatedStat({ value, label }: Readonly<{ value: string; label: string }>) {
const [visible, setVisible] = useState(false);
const ref = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -47,13 +47,6 @@ function AnimatedStat({ value, label }: { value: string; label: string }) {

/* ─── Main Hero component ─── */
export default function Hero() {
const [mounted, setMounted] = useState(false);

useEffect(() => {
const t = setTimeout(() => setMounted(true), 80);
return () => clearTimeout(t);
}, []);

return (
<section
id="hero"
Expand All @@ -77,21 +70,13 @@ export default function Hero() {
{/* ── Content wrapper ── */}
<div className="relative z-10 flex flex-1 flex-col items-center justify-center px-4 sm:px-6 lg:px-8 pt-24 pb-16 sm:pt-28 sm:pb-20">
{/* Badge */}
<div
className={`mb-6 inline-flex items-center gap-2 rounded-full border border-primary-700/40 bg-primary-900/30 px-4 py-1.5 text-xs font-semibold uppercase tracking-widest text-primary-300 backdrop-blur-sm transition-all duration-700 ${
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-4'
}`}
>
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-primary-700/40 bg-primary-900/30 px-4 py-1.5 text-xs font-semibold uppercase tracking-widest text-primary-300 backdrop-blur-sm">
<span className="inline-block w-2 h-2 rounded-full bg-primary-400 animate-pulse" />
Powered by the Stellar Network
<span>Powered by the Stellar Network</span>
</div>

{/* Headline */}
<h1
className={`max-w-4xl text-center text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-extrabold leading-[1.1] tracking-tight text-white transition-all duration-700 delay-100 ${
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6'
}`}
>
<h1 className="max-w-4xl text-center text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-extrabold leading-[1.1] tracking-tight text-white">
Where{' '}
<span className="relative inline-block">
<span className="bg-gradient-to-r from-primary-400 via-accent-400 to-secondary-400 bg-clip-text text-transparent">
Expand All @@ -107,21 +92,13 @@ export default function Hero() {
</h1>

{/* Sub-headline */}
<p
className={`mt-6 max-w-2xl text-center text-lg sm:text-xl text-neutral-300 leading-relaxed transition-all duration-700 delay-200 ${
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6'
}`}
>
<p className="mt-6 max-w-2xl text-center text-lg sm:text-xl text-neutral-300 leading-relaxed">
Discover, fund, and collaborate with the world&apos;s most talented creatives — artists,
musicians, designers, and more — all on a transparent blockchain-powered platform.
</p>

{/* CTA buttons */}
<div
className={`mt-10 flex flex-col sm:flex-row items-center gap-4 transition-all duration-700 delay-300 ${
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6'
}`}
>
<div className="mt-10 flex flex-col sm:flex-row items-center gap-4">
<Link
href="/explore"
id="hero-cta-explore"
Expand Down Expand Up @@ -169,23 +146,15 @@ export default function Hero() {
</div>

{/* Social proof stats */}
<div
className={`mt-16 flex flex-wrap justify-center gap-8 sm:gap-16 transition-all duration-700 delay-500 ${
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6'
}`}
>
<div className="mt-16 flex flex-wrap justify-center gap-8 sm:gap-16">
<AnimatedStat value="12K+" label="Creative Profiles" />
<AnimatedStat value="$4.2M" label="Funds Raised" />
<AnimatedStat value="98" label="Countries Reached" />
</div>
</div>

{/* ── Artwork imagery panel ── */}
<div
className={`relative z-10 w-full px-4 sm:px-6 lg:px-8 pb-16 sm:pb-20 transition-all duration-1000 delay-500 ${
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'
}`}
>
<div className="relative z-10 w-full px-4 sm:px-6 lg:px-8 pb-16 sm:pb-20">
<div className="mx-auto max-w-5xl">
{/* Glow behind image */}
<div
Expand Down
6 changes: 5 additions & 1 deletion app/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function Header() {

useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 10);
window.addEventListener('scroll', handleScroll);
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);

Expand Down Expand Up @@ -79,6 +79,7 @@ export default function Header() {
<NotificationBell />
<div className="relative">
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setUserMenuOpen(!userMenuOpen);
Expand Down Expand Up @@ -139,6 +140,7 @@ export default function Header() {
Settings
</Link>
<button
type="button"
onClick={handleLogout}
className="w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-neutral-100 dark:hover:bg-neutral-700"
>
Expand Down Expand Up @@ -167,6 +169,7 @@ export default function Header() {
</div>

<button
type="button"
className="md:hidden p-2 rounded-xl text-neutral-600 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label="Toggle menu"
Expand Down Expand Up @@ -239,6 +242,7 @@ export default function Header() {
</Link>
)}
<button
type="button"
onClick={handleLogout}
className="w-full text-left py-2 text-red-600 font-medium"
>
Expand Down
5 changes: 3 additions & 2 deletions app/explore/components/ExploreProjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ async function fetchProjectsPage({
goalXlm,
backers: (n * 7) % 240,
};
} as ExploreProject;
}
).filter((item): item is ExploreProject => item !== null);

Expand All @@ -80,7 +79,7 @@ function ProjectCardSkeleton() {
);
}

function ProjectCard({ project }: { project: ExploreProject }) {
function ProjectCard({ project }: Readonly<{ project: ExploreProject }>) {
const pct = Math.min(100, Math.round((project.raisedXlm / project.goalXlm) * 100));
return (
<article className="flex flex-col rounded-2xl border border-gray-200 bg-white p-5 transition-shadow hover:shadow-md dark:border-gray-800 dark:bg-gray-900">
Expand Down Expand Up @@ -180,6 +179,7 @@ export default function ExploreProjects() {
{/* Category Filter */}
<div className="mb-6 flex flex-wrap gap-2">
<button
type="button"
onClick={() => handleCategoryChange('all')}
className={cn(
'rounded-full px-4 py-2 text-sm font-medium transition-colors',
Expand All @@ -193,6 +193,7 @@ export default function ExploreProjects() {
{CATEGORIES.map((category) => (
<button
key={category}
type="button"
onClick={() => handleCategoryChange(category)}
className={cn(
'rounded-full px-4 py-2 text-sm font-medium transition-colors',
Expand Down
21 changes: 17 additions & 4 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import { Header, Footer } from './components/layout';
import './globals.css';

const ToastProvider = dynamic(() => import('@/app/components/ui/ToastProvider'), { ssr: false });
// Font configuration
const inter = Inter({ subsets: ['latin'] });
// Optimized Font configuration for Core Web Vitals (zero CLS, instant text render)
const inter = Inter({
subsets: ['latin'],
display: 'swap',
preload: true,
variable: '--font-inter',
});
// Viewport configuration for responsive design and theme color
export const viewport: Viewport = {
width: 'device-width',
Expand Down Expand Up @@ -63,9 +68,17 @@ export const metadata: Metadata = {
},
};
// Root layout component that wraps the entire application with global providers and layout components
export default function RootLayout({ children }: { children: React.ReactNode }) {
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="en" className={inter.variable} suppressHydrationWarning>
<head>
<link rel="preconnect" href="https://images.unsplash.com" />
<link rel="dns-prefetch" href="https://images.unsplash.com" />
</head>
<body className={inter.className}>
<ThemeProvider>
<a href="#main-content" className="skip-to-content">
Expand Down
Loading