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
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,31 @@ jobs:
run: npm run build
env:
NEXT_TELEMETRY_DISABLED: 1

lighthouse:
name: Lighthouse
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v4

- name: Use 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
env:
NEXT_TELEMETRY_DISABLED: 1

- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v12
with:
configPath: './lighthouserc.json'
uploadArtifacts: true
temporaryPublicStorage: true
11 changes: 10 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

Etags is a Next.js 16 application for product tagging and blockchain stamping. It manages brands, products, and tags with blockchain transaction tracking for authentication/verification purposes.

**Node.js requirement:** 20.x (LTS)

## Commands

- `npm run dev` - Start development server
Expand Down Expand Up @@ -221,7 +223,14 @@ Solidity contracts are in `smartcontracts/` directory with separate Hardhat setu
- `ETagRegistry.sol` - Main contract for tag lifecycle management (create, validate, update status, revoke)
- `ETagCollectible.sol` - ERC721 NFT contract for collectibles (one NFT per tag)

See `smartcontracts/README.md` for contract development and testing.
Contract commands (run from `smartcontracts/` directory):

- `npm run compile` - Compile contracts
- `npm run test` - Run contract tests
- `npm run deploy:local` - Deploy to local Hardhat node
- `npm run deploy:sepolia` - Deploy to Base Sepolia testnet

See `smartcontracts/README.md` for full contract development documentation.

## Environment Variables

Expand Down
30 changes: 30 additions & 0 deletions lighthouserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"ci": {
"collect": {
"startServerCommand": "npm run start",
"startServerReadyPattern": "Ready",
"url": ["http://localhost:3000/"],
"numberOfRuns": 3,
"settings": {
"preset": "desktop",
"onlyCategories": [
"performance",
"accessibility",
"best-practices",
"seo"
]
}
},
"assert": {
"assertions": {
"categories:performance": ["warn", { "minScore": 0.7 }],
"categories:accessibility": ["error", { "minScore": 0.9 }],
"categories:best-practices": ["warn", { "minScore": 0.8 }],
"categories:seo": ["warn", { "minScore": 0.8 }]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
21 changes: 21 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,24 @@
@apply bg-background text-foreground;
}
}

/* Reduced motion support for older devices and accessibility */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

/* GPU acceleration hints for smoother animations */
.blur-xl,
.blur-2xl,
.blur-3xl,
.blur-\[80px\] {
will-change: auto;
transform: translateZ(0);
}
1 change: 0 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export const viewport = {
themeColor: '#0c0a09',
width: 'device-width',
initialScale: 1,
maximumScale: 1,
};

import { SessionProvider } from '@/components/providers/session-provider';
Expand Down
33 changes: 3 additions & 30 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
'use client';

import { motion } from 'framer-motion';
import { Navbar } from '@/components/landing/Navbar';
import { Hero } from '@/components/landing/Hero';
import { Features } from '@/components/landing/Features';
Expand All @@ -9,37 +6,13 @@ import { FAQ } from '@/components/landing/FAQ';
import { CTA } from '@/components/landing/CTA';
import { Footer } from '@/components/landing/Footer';

const MotionDiv = motion.div;

export default function Home() {
return (
<div className="relative min-h-screen bg-white font-sans selection:bg-[#2B4C7E]/20 selection:text-[#0C2340] overflow-hidden">
{/* Animated Background Effects */}
{/* Static Background Effects - CSS only, no JS animations */}
<div className="fixed inset-0 pointer-events-none z-0">
<MotionDiv
className="absolute top-[-10%] left-[-5%] w-[50vw] h-[50vw] rounded-full bg-[#2B4C7E]/20 blur-[120px]"
animate={{
scale: [1, 1.1, 1],
opacity: [0.3, 0.5, 0.3],
}}
transition={{
duration: 8,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
<MotionDiv
className="absolute top-[10%] right-[-10%] w-[40vw] h-[40vw] rounded-full bg-[#A8A8A8]/30 blur-[120px]"
animate={{
scale: [1, 1.2, 1],
opacity: [0.2, 0.4, 0.2],
}}
transition={{
duration: 10,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
<div className="absolute top-[-10%] left-[-5%] w-[50vw] h-[50vw] rounded-full bg-[#2B4C7E]/15 blur-[80px]" />
<div className="absolute top-[10%] right-[-10%] w-[40vw] h-[40vw] rounded-full bg-[#A8A8A8]/20 blur-[80px]" />
</div>

<Navbar />
Expand Down
21 changes: 4 additions & 17 deletions src/components/landing/CTA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,12 @@ export function CTA() {
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.4 }}
>
{/* Animated background */}
{/* Static background - no animations */}
<div className="absolute top-0 left-0 w-full h-full overflow-hidden opacity-20 pointer-events-none">
<MotionDiv
className="absolute top-[-50%] left-[-20%] w-[80%] h-[80%] rounded-full bg-[#2B4C7E] blur-3xl"
animate={{
scale: [1, 1.2, 1],
x: [0, 50, 0],
}}
transition={{ duration: 10, repeat: Infinity }}
/>
<MotionDiv
className="absolute bottom-[-50%] right-[-20%] w-[80%] h-[80%] rounded-full bg-[#A8A8A8] blur-3xl"
animate={{
scale: [1, 1.3, 1],
x: [0, -50, 0],
}}
transition={{ duration: 12, repeat: Infinity }}
/>
<div className="absolute top-[-50%] left-[-20%] w-[80%] h-[80%] rounded-full bg-[#2B4C7E] blur-2xl" />
<div className="absolute bottom-[-50%] right-[-20%] w-[80%] h-[80%] rounded-full bg-[#A8A8A8] blur-2xl" />
</div>

<div className="relative z-10 max-w-2xl mx-auto">
Expand Down
46 changes: 24 additions & 22 deletions src/components/landing/FAQ.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ import { motion } from 'framer-motion';

const MotionDiv = motion.div;

const faqItems = [
{
q: 'Apakah saya perlu crypto untuk menggunakan Etags?',
a: 'Tidak. Kami mengabstraksikan semua kompleksitas blockchain. Anda membayar dalam fiat, kami menangani biaya gas dan manajemen wallet.',
},
{
q: 'Bisakah QR code disalin?',
a: 'Meskipun penyalinan fisik dimungkinkan, sistem kami mendeteksi scan duplikat dan anomali lokasi/waktu, menandai potensi pemalsuan segera.',
},
{
q: 'Berapa lama proses integrasi?',
a: 'Anda dapat mulai menandai produk secara manual dalam hitungan menit. Integrasi API untuk lini manufaktur otomatis biasanya memakan waktu 1-2 minggu.',
},
{
q: 'Apakah data saya publik?',
a: 'Hanya hash verifikasi yang publik. Intelijen bisnis sensitif dan data rantai pasokan Anda tetap pribadi dan terenkripsi.',
},
];

export function FAQ() {
return (
<section className="relative z-10 py-20 sm:py-32">
Expand All @@ -13,44 +32,27 @@ export function FAQ() {
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4 }}
>
<h2 className="text-3xl font-bold text-[#0C2340] mb-4">
Pertanyaan yang Sering Diajukan
</h2>
</MotionDiv>

<div className="grid md:grid-cols-2 gap-6 max-w-5xl mx-auto">
{[
{
q: 'Apakah saya perlu crypto untuk menggunakan Etags?',
a: 'Tidak. Kami mengabstraksikan semua kompleksitas blockchain. Anda membayar dalam fiat, kami menangani biaya gas dan manajemen wallet.',
},
{
q: 'Bisakah QR code disalin?',
a: 'Meskipun penyalinan fisik dimungkinkan, sistem kami mendeteksi scan duplikat dan anomali lokasi/waktu, menandai potensi pemalsuan segera.',
},
{
q: 'Berapa lama proses integrasi?',
a: 'Anda dapat mulai menandai produk secara manual dalam hitungan menit. Integrasi API untuk lini manufaktur otomatis biasanya memakan waktu 1-2 minggu.',
},
{
q: 'Apakah data saya publik?',
a: 'Hanya hash verifikasi yang publik. Intelijen bisnis sensitif dan data rantai pasokan Anda tetap pribadi dan terenkripsi.',
},
].map((item, i) => (
{faqItems.map((item, i) => (
<MotionDiv
key={i}
className="bg-white p-6 rounded-xl border-2 border-[#A8A8A8]/20 shadow-lg hover:shadow-xl hover:shadow-[#2B4C7E]/10 hover:border-[#2B4C7E]/30 transition-all duration-300"
className="bg-white p-6 rounded-xl border-2 border-[#A8A8A8]/20 shadow-lg hover:shadow-xl hover:shadow-[#2B4C7E]/10 hover:border-[#2B4C7E]/30 transition-all duration-200 hover:-translate-y-1"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1 }}
whileHover={{ y: -5 }}
transition={{ duration: 0.3, delay: i * 0.05 }}
>
<h3 className="font-bold text-[#0C2340] mb-3 text-lg">
{item.q}
</h3>
<p className="text-[#808080] text-sm leading-relaxed">{item.a}</p>
<p className="text-[#606060] text-sm leading-relaxed">{item.a}</p>
</MotionDiv>
))}
</div>
Expand Down
Loading
Loading