Skip to content
Open
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
67 changes: 67 additions & 0 deletions .github/lighthouse/lighthouserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"ci": {
"collect": {
"startServerCommand": "npm run preview",
"url": [
"http://localhost:4173/"
],
"numberOfRuns": 3
},
"assert": {
"preset": "lighthouse:recommended",
"assertions": {
"categories:performance": [
"error",
{
"minScore": 0.9
}
],
"categories:accessibility": [
"warn",
{
"minScore": 0.9
}
],
"categories:best-practices": [
"warn",
{
"minScore": 0.9
}
],
"categories:seo": [
"warn",
{
"minScore": 0.9
}
],
"first-contentful-paint": [
"warn",
{
"maxNumericValue": 2000
}
],
"largest-contentful-paint": [
"error",
{
"maxNumericValue": 2500
}
],
"cumulative-layout-shift": [
"error",
{
"maxNumericValue": 0.1
}
],
"total-blocking-time": [
"warn",
{
"maxNumericValue": 200
}
]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
32 changes: 32 additions & 0 deletions .github/workflows/lighthouse-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Lighthouse CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v11
with:
uploadArtifacts: true
temporaryPublicStorage: true
configPath: ./.github/lighthouse/lighthouserc.json
20 changes: 11 additions & 9 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { Navbar } from './components/Navbar';
import { Footer } from './components/Footer';
import { ErrorBoundary } from './components/ErrorBoundary';
import { AuthProvider } from './components/AuthProvider';
// Halaman yang dimuat secara eager
// Halaman yang dimuat secara eager (Cuma Home buat LCP)
import { Home } from './pages/Home';
import { Login } from './pages/Auth/Login';
import { Register } from './pages/Auth/Register';

// Halaman yang dimuat secara lazy (biar enteng pas buka awal)
const Login = React.lazy(() => import('./pages/Auth/Login').then(module => ({ default: module.Login })));
const Register = React.lazy(() => import('./pages/Auth/Register').then(module => ({ default: module.Register })));

// Halaman yang dimuat secara lazy (biar enteng pas buka awal)
const Karya = React.lazy(() => import('./pages/Karya'));
Expand Down Expand Up @@ -137,13 +139,13 @@ const AppContent = () => {

return (
<div className="min-h-screen bg-[#030303] text-white selection:bg-rose-500/30 font-sans overflow-x-hidden flex flex-col relative">
<div className="fixed inset-0 z-0 pointer-events-none overflow-hidden">
<div className="fixed inset-0 z-0 pointer-events-none overflow-hidden origin-center">
{/* Efek noise tekstur */}
<div className="absolute inset-0 opacity-[0.02]" style={{ backgroundImage: `url("https://grainy-gradients.vercel.app/noise.svg")`, backgroundSize: '100px 100px' }}></div>
{/* Gradien dekoratif buat estetika zen */}
<div className="absolute top-[-20%] left-[10%] w-[800px] h-[800px] bg-rose-900/10 blur-[100px] rounded-full" />
<div className="absolute top-[20%] right-[-10%] w-[600px] h-[600px] bg-indigo-900/10 blur-[100px] rounded-full" />
<div className="absolute bottom-[-20%] left-[20%] w-[900px] h-[900px] bg-[#0a0a0a] blur-[80px] rounded-full" />
<div className="absolute inset-0 opacity-[0.015]" style={{ backgroundImage: `url("https://grainy-gradients.vercel.app/noise.svg")`, backgroundSize: '100px 100px' }}></div>
{/* Gradien dekoratif buat estetika zen - Dioptimalkan blurnya biar enteng */}
<div className="absolute top-[-20%] left-[10%] w-[800px] h-[800px] bg-rose-900/[0.07] blur-[80px] rounded-full will-change-[filter]" />
<div className="absolute top-[20%] right-[-10%] w-[600px] h-[600px] bg-indigo-900/[0.07] blur-[80px] rounded-full will-change-[filter]" />
<div className="absolute bottom-[-20%] left-[20%] w-[900px] h-[900px] bg-[#0a0a0a] blur-[60px] rounded-full" />
</div>

<div className="relative z-10 flex flex-col min-h-screen">
Expand Down
8 changes: 7 additions & 1 deletion components/BentoGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ interface CardProps {
}

// Efek Noise Modern - Biar tekstur gak flat
// Inlined SVG data URI to eliminate external fetch (LCP optimization)
const NOISE_SVG_DATA_URI = `data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E`;

const Noise = () => (
<div
className="absolute inset-0 opacity-[0.03] pointer-events-none z-0 mix-blend-overlay"
style={{
backgroundImage: `url("https://grainy-gradients.vercel.app/noise.svg")`,
backgroundImage: `url("${NOISE_SVG_DATA_URI}")`,
backgroundRepeat: 'repeat',
backgroundSize: '100px 100px'
}}
Expand Down Expand Up @@ -89,6 +92,9 @@ export const BentoGrid = () => {
alt="Art"
loading="lazy"
decoding="async"
width="800"
height="600"
style={{ aspectRatio: '800 / 600' }}
className="absolute inset-0 w-full h-full object-cover opacity-60 group-hover:opacity-80 group-hover:scale-105 transition-all duration-700"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/20 to-transparent"></div>
Expand Down
4 changes: 2 additions & 2 deletions components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export const Hero = () => {

{/* Judul Utama - Pelacakan Ketat & Kontras Tinggi */}
<motion.h1
// Optimasi LCP: Biar cepet nongol, pake animasi tipis-tipis aja
// LCP already optimized: no initial animation delay
initial={{ opacity: 1, y: 0 }}
animate={{ opacity: 1, y: 0 }}
className="font-serif text-6xl md:text-8xl lg:text-9xl leading-[0.9] md:leading-[0.85] tracking-tight mb-8"
className="font-serif text-6xl md:text-8xl lg:text-9xl leading-[0.9] md:leading-[0.85] tracking-tight mb-8 min-h-[1.8em]"
>
<span className="block text-white mix-blend-difference">Merangkai</span>
<span className="block relative">
Expand Down
4 changes: 2 additions & 2 deletions components/KaryaCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export const KaryaCard: React.FC<KaryaCardProps> = ({ art, index, onClick, rende
className="break-inside-avoid group relative rounded-[1.5rem] md:rounded-[2.5rem] overflow-hidden cursor-pointer bg-[#0a0a0a] shadow-2xl hover:shadow-rose-500/20 transition-all border border-white/5"
>
{/* Area Konten Utama - Aspek Rasio Standar biar Gridnya gak berantakan */}
<div className="relative w-full aspect-[4/5] md:aspect-[3/4]">
<div className="w-full h-full overflow-hidden">
<div className="relative w-full aspect-[4/5] md:aspect-[3/4] overflow-hidden bg-neutral-900">
<div className="w-full h-full overflow-hidden will-change-transform">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Penggunaan will-change-transform di sini mungkin tidak memberikan manfaat performa yang diharapkan dan berpotensi menyebabkan penggunaan memori yang tidak perlu. Properti will-change sebaiknya diterapkan langsung pada elemen yang akan dianimasikan.

Dalam kasus ini, animasi transform (seperti scale pada gambar atau y pada kartu) terjadi pada elemen lain (elemen motion.div induk atau img di dalamnya). Sebaiknya hapus will-change-transform dari div ini. Jika optimasi spesifik diperlukan, terapkan langsung pada elemen gambar yang memiliki transisi scale.

Suggested change
<div className="w-full h-full overflow-hidden will-change-transform">
<div className="w-full h-full overflow-hidden">

{renderContent(art)}
</div>

Expand Down
4 changes: 2 additions & 2 deletions components/Navbar.tsx
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Missing clearTimeout on component unmount causes potential memory leak

The scroll handler in Navbar.tsx doesn't clear the pending timeout when the component unmounts, which can cause a memory leak and React state update warnings.

Click to expand

Issue

In the scroll effect at components/Navbar.tsx:37-48, a timeoutId is set but never cleared on cleanup:

useEffect(() => {
    let timeoutId: NodeJS.Timeout;
    const handleScroll = () => {
        if (timeoutId) return;
        timeoutId = setTimeout(() => {
            setIsScrolled(window.scrollY > 50);
            timeoutId = undefined!;
        }, 100);
    };
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
}, []);

Actual vs Expected

  • Actual: If user scrolls then navigates away before the 100ms timeout fires, setIsScrolled will be called on an unmounted component, causing a React warning and potential memory leak.
  • Expected: The cleanup function should also call clearTimeout(timeoutId) to cancel any pending timeout.

Impact

This can cause "Can't perform a React state update on an unmounted component" warnings and minor memory leaks during rapid navigation.

(Refers to lines 37-48)

Recommendation: Add if (timeoutId) clearTimeout(timeoutId); to the cleanup function: return () => { if (timeoutId) clearTimeout(timeoutId); window.removeEventListener('scroll', handleScroll); };

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const Navbar = () => {
transition={springTransition as any}
onHoverStart={() => setIsHovered(true)}
onHoverEnd={() => setIsHovered(false)}
className="bg-[#0a0a0a]/90 backdrop-blur-xl border border-white/10 shadow-[0_8px_40px_-12px_rgba(0,0,0,0.5)] flex flex-col overflow-hidden relative"
className="bg-[#0a0a0a]/90 backdrop-blur-xl border border-white/10 shadow-[0_8px_40px_-12px_rgba(0,0,0,0.5)] flex flex-col overflow-hidden relative will-change-transform"
>
<div className={`flex items-center justify-between w-full relative z-20 ${showFullMenu && !isMobileMenuOpen && !isProfileOpen ? 'gap-12' : 'gap-3'} ${(isMobile && (isProfileOpen || isMobileMenuOpen)) ? 'mb-4' : 'h-full'}`}>
{/* Pembungkus Logo & Judul */}
Expand All @@ -127,7 +127,7 @@ export const Navbar = () => {
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -10 }}
transition={{ duration: motionConfig.durations.fast, ease: "easeOut" }}
className="font-serif font-bold text-lg tracking-tight text-white whitespace-nowrap"
className="font-serif font-bold text-lg tracking-tight text-white whitespace-nowrap will-change-transform"
>
Our Creativity.
</motion.span>
Expand Down
152 changes: 78 additions & 74 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,90 +1,94 @@
<!DOCTYPE html>
<html lang="id">

<meta charset="UTF-8" />
<link rel="icon" type="image/jpeg" href="/logo oc.jpg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#030303" />
<meta name="description"
content="OurCreativity adalah platform komunitas kreatif untuk seniman digital, desainer, dan penulis. Pamerkan karya, kolaborasi, dan bangun portofolio profesional Anda." />
<meta name="keywords" content="kreatif, portfolio, digital art, indonesia, komunitas, desain grafis, coding" />
<title>Our Creativity.</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://didjpfzpxwvamtlzgcbt.supabase.co" crossorigin>
<link rel="preconnect" href="https://grainy-gradients.vercel.app" crossorigin>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/jpeg" href="/logo oc.jpg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#030303" />
<meta name="description"
content="OurCreativity adalah platform komunitas kreatif untuk seniman digital, desainer, dan penulis. Pamerkan karya, kolaborasi, dan bangun portofolio profesional Anda." />
<meta name="keywords" content="kreatif, portfolio, digital art, indonesia, komunitas, desain grafis, coding" />
<title>Our Creativity.</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://didjpfzpxwvamtlzgcbt.supabase.co" crossorigin>
<link rel="preconnect" href="https://images.unsplash.com" crossorigin>

<!-- Preload critical fonts for speed and CLS prevention -->
<link rel="preload"
href="https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZhrib2Bg-4.woff2"
as="font" type="font/woff2" crossorigin>
<link rel="preload"
href="https://fonts.gstatic.com/s/playfairdisplay/v37/nuFvD-vYSZviVYUb_rj3ij__koIsUzZXMBe462Afjw.woff2" as="font"
type="font/woff2" crossorigin>
<!-- Preload critical fonts for speed and CLS prevention -->
<link rel="preload"
href="https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZhrib2Bg-4.woff2"
as="font" type="font/woff2" crossorigin>
<link rel="preload"
href="https://fonts.gstatic.com/s/playfairdisplay/v37/nuFvD-vYSZviVYUb_rj3ij__koIsUzZXMBe462Afjw.woff2"
as="font" type="font/woff2" crossorigin>

<link rel="preload"
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&display=swap"
as="style">
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&display=swap"
rel="stylesheet">
</head>
<!-- Async Font Loading to prevent render-blocking -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&display=swap"
media="print" onload="this.media='all'">
<noscript>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&display=swap"
rel="stylesheet">
</noscript>

<body>
<div id="root">
<!-- App Shell / Loading Skeleton for FCP -->
<style>
body {
background-color: #030303;
margin: 0;
font-family: 'Inter', sans-serif;
}
<!-- Critical Shell Loader CSS -->
<style>
body {
background-color: #030303;
margin: 0;
font-family: 'Inter', sans-serif;
}

.shell-loader {
position: fixed;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(circle at 50% 50%, #1a1a1a 0%, #030303 100%);
z-index: 9999;
}
.shell-loader {
position: fixed;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(circle at 50% 50%, #1a1a1a 0%, #030303 100%);
z-index: 9999;
}

.shell-logo {
width: 40px;
height: 40px;
border-radius: 50%;
background: #222;
margin-bottom: 20px;
animation: pulse 2s infinite;
}
.shell-logo {
width: 40px;
height: 40px;
border-radius: 50%;
background: #222;
margin-bottom: 20px;
animation: pulse 2s infinite;
}

.shell-text {
width: 120px;
height: 12px;
background: #222;
border-radius: 6px;
}
.shell-text {
width: 120px;
height: 12px;
background: #222;
border-radius: 6px;
}

@keyframes pulse {
0% {
opacity: 0.5;
transform: scale(0.95);
}
@keyframes pulse {
0% {
opacity: 0.5;
transform: scale(0.95);
}

50% {
opacity: 1;
transform: scale(1.05);
}
50% {
opacity: 1;
transform: scale(1.05);
}

100% {
opacity: 0.5;
transform: scale(0.95);
}
100% {
opacity: 0.5;
transform: scale(0.95);
}
</style>
}
</style>
</head>

<body>
<div id="root">
<div class="shell-loader">
<div class="shell-logo"></div>
<div class="shell-text"></div>
Expand Down
Loading
Loading