6.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
Two independent apps in one repo:
- Frontend (repo root) — a Vite + React 19 + TypeScript marketing/content site for "اندیشکده فولاد آینده" (a steel-industry think tank). Persian-first, RTL, bilingual (fa/en). Deploys as a static build via Docker to Hugging Face Spaces on port 7860.
- Admin panel (
panel/) — a standalone Node/Express + SQLite app with its ownpackage.jsonandnode_modules. Manages content the site is meant to publish. The frontend does NOT consume the panel yet — every section renders hardcoded mock data. Wiring them is a deliberate future step.
These two have no shared build or dependency tree. Treat panel/ as a separate project.
Commands
Frontend (run from repo root):
npm run dev # Vite dev server (HMR)
npm run build # tsc -b && vite build (type-checks then bundles)
npm run lint # eslint .
npm run preview # serve the production build
npx tsc -b --noEmit # type-check only — the fast inner loop; use after every edit
There is no test framework configured. "Verifying" a change means npx tsc -b --noEmit (must be clean) and looking at the running dev server.
Admin panel (run from panel/):
cd panel
npm install # first time only
npm run seed # create the admin user from .env (ADMIN_USERNAME/PASSWORD)
npm start # node server.js → http://localhost:3001
npm run dev # node --watch server.js
npm rebuild better-sqlite3 # REQUIRED if Node's ABI changed — native addon, fails to load otherwise
Critical build gotcha
panel/ lives inside the frontend's project root, so Vite's dependency scanner reads panel/package.json. If that file (or anything Vite scans) has unresolved git merge-conflict markers (<<<<<<<), npm run dev/build fails with a cryptic JSONError ... expected value at line 1. Keep panel/package.json valid JSON.
Frontend architecture
- Routing:
src/app/router.tsx(createBrowserRouter).RootLayout(src/app/layout/RootLayout.tsx) wraps every page withHeader,Footer, and the smooth-scroll engine. Pages live insrc/pages/<Name>/<Name>.tsx; the homepage composes section components fromsrc/pages/Home/sections/. - Language:
useLang()fromsrc/context/LangContextreturns{ lang: 'fa'|'en', toggle }. Components hold inline{ fa: {...}, en: {...} }objects and index bylang. RTL is driven bydir={lang==='fa'?'rtl':'ltr'}near the page root; many flex containers forcedirection:'ltr'locally to stop RTL from reversing their child order, then set inner text back tortl. - Smooth scroll:
RootLayoutruns Lenis synced to the GSAP ticker (the joinspread.app recipe) — one shared RAF clock so scroll-driven animations don't stutter. It is desktop-only: on touch/coarse pointers it hands off to native momentum (avoids the iOS double-smoothing lag). Do not re-add CSSscroll-behavior: smooth— it fights Lenis. - Hero (
src/pages/Home/sections/HeroSection.tsx): a GSAPScrollTriggerscrub timeline pinned over a200vhsection (de-zoom + satellite scatter). This is GSAP, not framer-motion. - Styling: mostly inline
styleobjects, with Tailwind utility classes for responsive overrides (max-md:,max-lg:) and a few!importantoverrides. Global CSS variables insrc/index.css(--ink,--paper,--red,--color-orange, etc.). Brand colors are gold#CD9E53and navy#032340— these are also hardcoded asconst GOLD/const NAVYin many section files, so a rebrand is a repo-wide find-and-replace, not just a CSS-var change. - Globe (
src/components/ui/globe.tsx): three-globe mounted in react-three-fiber via<primitive>(NOT viaextend/JSX-element augmentation — that augmentation poisons global JSX types and breaks unrelated components likeReact.ElementTypeicons). Country polygons load at runtime frompublic/globe-countries.geojson(bundled, public-domain Natural Earth). - Calendar (
src/components/ui/EventCalendar.tsx): a native Jalali (Persian) month grid built withIntl.DateTimeFormat('en-US-u-ca-persian', …)— no date library. It walks GregorianDates and reads their Persian parts to lay out the grid. - Content layer:
src/content/*.tsand inline arrays hold all section data. These are mock/seed data and are explicitly intended to be replaced byfetchcalls to the panel API later (see "Wiring", below). Keep new editorial data in this shape so the swap stays mechanical.
Admin panel architecture (panel/)
- Stack: Express 4 +
better-sqlite3(single filepanel/data.db),bcryptjs+jsonwebtoken(HTTP-only cookie namedsession),multeruploads topanel/uploads/. Admin UI is a vanilla-JS SPA inpanel/public/(app.js, no build step). - Schema:
panel/db.js(users,articles,risk_signals,prices) plus row→object mappers.articlesis a flexible universal content model discriminated bycategory/type. - API (
panel/server.js): public reads (GET /api/articles,/api/risks,/api/prices) + auth-gated writes via theauthRequiredmiddleware. CRUD for articles, risk signals, and users;/api/uploads;/api/contact. CORS is gated by theALLOWED_ORIGINSenv var. - Prices are scraped from tgju.org by
panel/scraper.json a 120s interval when the server runs. - Config:
panel/.env(copy from.env.example) —JWT_SECRET,ADMIN_*,PORT(3001),ALLOWED_ORIGINS, mail creds.
Wiring the panel to the site (not done yet)
When asked to make published content appear on the site: add a frontend fetch client (e.g. src/lib/api.ts reading import.meta.env.VITE_API_URL), replace a section's src/content/* import with a hook that calls GET /api/articles?category=<section>, and keep the static file as the loading fallback. Do it one section at a time. The articles table's category column is the per-section discriminator. Recommended schema additions before going live: a status (draft/published) column, and an events resource for the calendar.