103 lines
7.7 KiB
Markdown
103 lines
7.7 KiB
Markdown
# 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.
|
||
|
||
```bash
|
||
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.
|