Files
kreios/CLAUDE.md
T

7.7 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project overview

Web-based 3D configurator for infissi (windows/doors) — "kreiosinfissi-vite". It renders a Three.js scene (GLB models) driven by JSON product data and a jQuery-based menu UI, and is meant to run inside an <iframe> embedded on https://www.kreiosinfissi.it (or https://kreiosinfissi.test in dev), with the final "add to cart" action posted back to the parent window via postMessage.

Commands

Package manager is pnpm 10.34.7 (pinned in packageManager; build.sh/dev.sh run it via npx pnpm@10.34.7, so lockfiles stay at lockfileVersion: '9.0'). pnpm-lock.yaml is the lockfile of record even though package-lock.json is also present.

pnpm install       # install deps
./dev.sh          # single server: Laravel on :8000 + both frontends in `vite build --watch`
./build.sh        # build configuratore + app cliente into backoffice/public
pnpm build        # configuratore only -> backoffice/public/configuratore (root is src/)

Everything is served by Laravel on one origin: configuratore at /configuratore/?sistema=…&type=…, quote app (backoffice/cliente, Vue) at /preventivo/, API at /api, admin at /admin. The index.html of each frontend is served by routes in backoffice/routes/web.php; there is no HMR, reload after the rebuild.

There is no test suite (pnpm test is a stub that exits with an error) and no linter configured.

Regenerating PVC price lists

scripts/extract_listini_pvc.py parses PVC_LISTINO.xls and regenerates src/assets/data/modelli/pvc/<modello>/listino.json files. Requires a Python venv with xlrd (pip install -r scripts/requirements.txt). Run with python3 scripts/extract_listini_pvc.py from repo root. Any new title in the spreadsheet needs an entry in that script's KNOWN_FOLDERS map or it won't be matched to an existing model folder.

Architecture

Entry point is src/js/starter.js, loaded from src/index.html. Flow on load (start()):

  1. ParamsParser.getQueryParams() (src/js/params_parser.js) reads the URL query string. Two params drive everything: sistema (pvc | alluminio | legno | acciaio) and type (model id). See docs/URL_PARAMS.md for the full param contract and the list of valid type values per sistema. Note: that doc describes a more modular refactor (Application.js, communication/UrlParamsParser.js, communication/IframeBridge.js) that does not exist yet in src/js — the current implementation is the flatter starter.js/params_parser.js described here.
  2. Catalog data comes from the Laravel backoffice (backoffice/, API base in src/js/api_base.js): GET /api/sistemi (systems + active models that have a price list; models without one are hidden and flagged in the admin list) and GET /api/sistemi/<sistema>/modelli/<modello>/configurazione, which returns the menu wiring (form: radios, selects, finish menus — the old form.json shape), finishes, handles, glass types and the price-grid sizes, with image URLs served by GET /api/media/... (CORS-enabled, needed for WebGL textures). Only avvolgibile.json and sistemi/<sistema>/altezza_maniglia_<categoria>.json are still read from src/assets/data.
  3. MenuBuilder.build() (src/js/menu_builder.js) injects an HTML template (assets/_menu.html, or assets/_menu-cassonetto.html when type=cassonetto) into #left-container, builds the model-picker list, handles the mobile-portrait/landscape layout reflow, and initializes PriceCalculator. It returns the price calculator instance used by the rest of the app.
  4. Configurator.init() / .enable() (src/js/configurator.js) wires a single delegated input handler on the size fields, selects and radios. On every change (debounced) it sends the selected codes to POST /api/sistemi/<sistema>/modelli/<modello>/prezzo, stores the result in window.configurazione and updates the DOM. "Add to cart" sends the same codes (scelte): the backend recomputes the price and ignores any price coming from the client.
  5. APP.Player (src/js/app.js) wraps a Three.js renderer/scene/camera (scene graph loaded from src/assets/app.json, a three.js-editor-style export) plus the actual product GLB (src/assets/models/<sistema>/<modello.modello>). Two players are created: player (visible canvas) and playerOff (offscreen, used only to render the PNG snapshot sent to the cart). Key methods: swapModel/ hideModel (toggle between pre-loaded variants in the same scene), changeTexture/changeColor (applied per userData.categoria mesh tag — categories like interno, esterno, maniglia, vetro, cerniere), flipView (180° GSAP-animated turn between front/back view), addMarker (in-scene hotspot with a video popup).
  6. Pricing is server-side only: backoffice/app/Support/Configuratore/CalcolatorePrezzo.php. Computed in cascading phases, each phase's percentages applied to the previous phase's total (VAT excluded): serramento = base × (1 + finitura %), then profili extra on the serramento (measures + finishes only), then the other options (telaio, apertura, accessori), then + variazione vetro. Base is the first grid size ≥ the requested one (mm). Supplements come from opzioni rows (finish type bianco/uniforme/bicolore, hinges, profili extra, ...): percentage, fixed, or per linear metre (ml from L/H multipliers + extra mm). Groups with ui = multipla (profili extra) accept several codes, with same-alternativa options mutually exclusive. Sistema-level rows can be overridden per model by group. Option groups themselves live in gruppi_opzioni (title, control radio/select/multipla, menu section, and the model moduli key that enables them): a group without fixed markup in _menu.html is rendered by MenuBuilder into .opzioni-sistema / .opzioni-accessori. Select options can call a window[func] (e.g. nascondiCategoria with categoria: maniglia for the motor) via their parametri. The glass variation is Σ max(pane area, minimo_mq) × maggiorazione_mq, with panes from the model's regole_vetro (spec: "Specifica tecnica vetri"). All of it is edited in Filament under "Configuratore". src/js/price_calculator.js only wraps the API call and the grid sizes.

Data conventions

  • Every 3D-visible part is tagged with userData.categoria in the GLB/scene so JS can show/hide/retexture it without knowing mesh names (interno, esterno, maniglia, cerniere, vetro, ...).
  • Which menu sections a model shows (and which items enter the price) is driven by its moduli in the backoffice (Modello::MODULI, e.g. finitura_esterna, apertura, telaio, vetro). The per-model JSON files under src/assets/data/modelli/ are legacy and no longer read (except avvolgibile.json).
  • Texture and icon images live in the backoffice (storage/app/public/configuratore/..., seeded once by ConfiguratoreSeeder from backoffice/database/seeders/data/configuratore/) and are loaded lazily per selection (APP.Player.changeTexture) from absolute URLs; a monotonically-checked isStale() guard discards a texture load if the user changed the selection again before it finished.
  • window.* globals (window.configurazione, window.cambiaTexture, window.caricaModello, window.rotate, window.sistema, etc.) are the integration surface between the jQuery-driven menu markup (inline onclick/onchange in the _menu*.html templates) and the JS modules — expect to grep for a window.<name> = assignment when tracing menu behavior, not for exported module functions.