Imported from jpedroabq/catalogo-fokus (
AGENTS.md). Install upstream withnpx skills add jpedroabq/catalogo-fokus. Copyright stays with the author.
AGENTS.md
Agent guidance for the Catálogo Fokus repo. Complements README.md — read both. This file captures commands, the data flow, and the non-obvious gotchas that aren't visible from glancing at the code.
Tech stack
Vite + React 18 + TypeScript (strict, noEmit) + Tailwind 3. Single-page app,
no router, no backend. Libraries: papaparse (CSV), framer-motion (animations),
react-swipeable (touch), @heroicons/react, brandfetch CDN logos.
Commands
npm install
npm run dev # Vite dev server
npm run build # tsc (typecheck) + vite build — this is the only gate
npm run preview # serve the production build
node scripts/download-images.mjs # rebuild local images + imageMap.ts (run after CSV updates)
No tests, no linter, no CI. npm run build succeeding (tsc passes) is the
only correctness gate before considering work done.
Data source
- Products live in a Google Sheet published as CSV (File → Share → Publish
to web → CSV). URL =
GOOGLE_SHEETS_CSV_URLinsrc/constants.ts. - If the sheet isn't published as CSV the app shows an error and loads nothing ("Verifique se a planilha está publicada como CSV").
- The CSV header is Portuguese, mapped to English props in
src/utils/csv.ts(transformHeader): marca→brand, modelo→model, cor→color, armazenamento→storage, preço→price, estoque→stock, imagens→imageurl. - The CSV URL is duplicated in
scripts/download-images.mjs. Change both when the sheet URL changes.
Data flow
CSV (PapaParse) → ProductRow[] (src/utils/csv.ts) → groupProducts() →
GroupedProduct[] (src/utils/groupProducts.ts) → useProducts() hook
(src/hooks/useProducts.ts) → App.tsx (filter/sort) → ProductGrid →
ProductCard.
- One CSV row = one variant.
groupProductsgroups bybrand|model(lowercased) into aGroupedProductwithvariants[],colors[],storages[],rams[],minPrice/maxPrice. - Filtering (brand, free-text search, storage, RAM, price sort) lives in
src/App.tsx. Selected storage/RAM are passed to the grid asdefaultStorage/defaultRamso each card pre-selects that variant.
Image pipeline
scripts/download-images.mjsfetches every unique image URL from the CSV, saves topublic/images/products/<md5hash12>.<ext>, and auto-generatessrc/utils/imageMap.ts(original URL → local path).src/utils/groupProducts.tsmaps each URL throughimageMapintovariant.imagesand keeps originals invariant.fallbackImages.src/components/ProductCard.tsxrenders the local path assrc; ononErrorswaps to the fallback original URL.
Do not hand-edit src/utils/imageMap.ts — rerun the download script.
Gotchas
- Stock parsing:
estoque === 'sim'(trimmed, lowercased) → in stock. Anything else → out of stock, and the variant is dropped entirely (groupProductscontinues past it). Don't switch toparseInt— that was a real bug (commit cbd7ae6). - BRL price parsing (
src/utils/groupProducts.tsnormalizePrice): stripsR$, removes thousand-separator dots, converts comma decimal to dot —R$ 1.299,00→1299. Built to handle the spreadsheet's format. - iPhone 15 image order is reversed in code (
src/utils/groupProducts.ts) to compensate for an inverted order in the spreadsheet. Verify before touching. - RAM is excluded from Apple WhatsApp messages (
src/constants.tsWHATSAPP_MESSAGE) — Apple specs don't advertise RAM. - Brand logo / color lookups normalize with
.toLowerCase().trim().src/brandLogos.tsonly hasappleandxiaomi.
Design system
Apple-inspired; reference: Apple Design Patterns_ A Comprehensive Guide for Product Catalogs.md (repo root).
- Theme via CSS custom properties in
:root(src/index.css):--color-bg,--color-text, etc., mapped intailwind.config.jsasapple-bg,apple-text, … Light mode only — single-valued vars, no dark toggle (despitetransition-colorson the root div). - Tailwind tokens in
tailwind.config.js:rounded-card/rounded-card-lg,rounded-pill(980px),ease-apple(cubic-bezier(0.25,0.1,0.25,1)),apple-*font sizes,bg-grainnoise utility (also exposed asbg-grain). - Color swatches:
src/iphoneColors.tsmaps color names (PT + EN) → hex;isLightColor()picks the check-mark text color for light swatches. - House CSS style is Tailwind arbitrary values (
text-[17px],rounded-[980px],tracking-[-0.022em]) rather than custom CSS — match it when adding components.
Variant selection UX
ProductCard is filter-aware: changing one of color/storage/RAM filters
the other two selectors to only options that exist in some variant
(filteredColors/filteredStorages/filteredRams in
src/components/ProductCard.tsx). When the selected combo no longer exactly
matches a variant, an effect picks the closest fallback variant and updates the
selections. Gallery supports swipe (react-swipeable), a portal lightbox, and
framer-motion slide transitions keyed off animKey.
Generated / ignore files
src/utils/imageMap.ts— generated by the download script. Don't edit by hand.public/images/products/— bulk-downloaded product images.repomix-output.xml— repo snapshot, gitignored, safe to delete..astro/,.playwright-mcp/— local tool artifacts.