# Style module

Full **app-appearance designer** for a project. An appearance is **pure data** — design
tokens + dark overrides + an app **shell** (topbar / sidebar / logo / menu / footer) +
**grid** appearance + density + custom CSS — compiled to CSS custom properties (`--pp-*`).
Users pick a theme per page in the Designer; the Style SPA is where appearances are
authored, with a **live full app-shell preview**.

## The appearance bundle

```jsonc
{ "id":"…", "label":"…", "base":"default",
  "tokens":{…}, "dark":{…},                                   // palette / type / radius (+ light/dark)
  "shell": { "topbar":{enabled,theme,fixed,gradient,height,search,user},
             "sidebar":{enabled,position,theme,width,compact,fixed},
             "logo":{text,theme}, "menu":{style,placement}, "footer":{enabled,text} },
  "grid":  { panel, verticalBorders, zebra, condensed, rollover, panelColor, gridColor },
  "density":"normal", "css":"" }
```

`base` inheritance is **deep-merged** (a custom theme can override just `shell.sidebar.theme`
and inherit the rest). `shell.*.theme` is a named palette (`navy/dark/slate/light/white/
primary`; `primary` follows the brand). The compiler emits shell vars (`--pp-topbar-bg`,
`--pp-sidebar-w`, …) + density + grid colours alongside the token/alias vars. The project
**navigation menu** (nav tree) is stored separately at project level (`ModuleState 'themes'
→ menu`), since one menu serves all themes.

## Architecture (modular master classes)

```
core/Theme/ThemeCompiler.php   tokens + shell -> scoped `--pp-*` CSS (+ `--ppd-*` aliases, dark, radius scale)
core/Theme/ThemeRegistry.php   merges presets + project themes, deep-merges `base` inheritance
core/Theme/NavbarRegistry.php  catalogue of navbar layout TYPES (classic/horizontal/sidebar/rail/boxed/hero)
core/Theme/MenuModel.php       navigation menu item tree — normalize / validate / resolve href
resources/Themes/presets/*.json  10 built-in themes (default, slate, emerald, violet, midnight, rose, amber, teal, nord, dracula)
modules/Style/                 the SPA + CRUD endpoints
```

Each concern is an independent module — updating the navbar catalogue, menu model,
compiler, or a theme preset doesn't touch the others.

### Navbar types (`NavbarRegistry`)

Six selectable layouts, each pure data (`layout`, `menuPlacement`, partial shell
`defaults`): **classic** (topbar + left sidebar), **horizontal** (top bar only, menu in
the topbar), **sidebar** (full-height vertical), **rail** (icon-only sidebar), **boxed**
(centered contained), **hero** (tall gradient topbar). Picking one in the Layout tab deep-
merges its defaults into `shell` and sets `shell.navbar`; the preview renders that layout.

### Menu (`MenuModel`)

A tree of items (max depth 3). Each item is `{ id, type: hyperlink|submenu, label, icon,
showIcon, link:{kind:page|external|none, page, subpage, url, params, target}, css, children }`.
Stored at project level (`ModuleState 'themes' → menu`, normalized on save/load). The Style
SPA **Menu tab** is a drag-and-drop tree (reorder + drop-onto-submenu to nest, add item /
add submenu, per-row edit / delete) with a rich **Item Editor** modal — item type, label,
**icon** (via the shared icon browser), show-icon, link type (application page → page +
subpage dropdowns, or external URL), URL params, open-in-new-window, custom CSS. The full
menu (icons + submenus) renders live in the preview sidebar or topnav per navbar type.

### Data model

```jsonc
{ "id": "emerald", "label": "Emerald", "base": "default",   // base = inherit another theme's tokens
  "tokens": { "primary": "#059669", "bg": "#f4fbf7", … },   // any subset overrides the base
  "dark":   { "bg": "#04120c", "primary": "#34d399", … } }  // optional per-theme dark overrides
```

Token keys → CSS vars: `bg surface surfaceSoft text muted border primary primarySoft
primaryText success warning danger radius shadow font mono` →
`--pp-bg … --pp-mono` (plus derived `--pp-radius-sm/-lg`). Dark mode is a class
(`.is-dark`) toggled on the themed root, so one theme carries both modes.

- **Built-in presets** live in `resources/Themes/presets/*.json` (read-only).
- **Project themes** live in `ModuleState 'themes'` → `{ themes: { id: {…} }, default: id }`,
  created by duplicating a preset (custom themes are stored self-contained).

## Routes

| Method | URI | Handler |
|---|---|---|
| GET  | `/project/{slug}/style`        | `StyleController@index` (SPA) |
| GET  | `/project/{slug}/style/load`   | catalogue + default (JSON) |
| POST | `/project/{slug}/style/save`   | create/update a project theme, or set `default` |
| POST | `/project/{slug}/style/delete` | remove a project theme |

## SPA (`Views/index.php` + `style-spa.js`)

Left: theme list (built-in + custom, with swatches + default badge, **New**).
Middle: token editor grouped Surfaces / Text / Brand / Status / Shape & type, with a
**Light / Dark** tab; colour tokens get a swatch + hex field. Actions: **Save / Duplicate /
Set default / Export / Import / Delete** (built-in themes are read-only → duplicate to
customise; Export/Import move themes as JSON).

Right: a **live preview** that runs in two modes (segmented toggle):
- **Sample** — a representative UI mockup (top-bar, card, buttons, form, table, badges,
  stat cards) built from `--pp-*`.
- **Real page** — pick a **table** + **page type** and the preview renders that *actual
  designed page* (`PpStyleBootstrap.designerPages`) by reusing the designer's `registry.js`
  + a small static renderer, themed live. A **device** segmented control (desktop / tablet /
  mobile) constrains width and drives responsive field spans.

Both update on every keystroke; the compiled theme is scoped to `#spPreview`.

### How the theme drives real components

`ThemeCompiler` (and its JS mirrors in `app/theme.js` and `style-spa.js`) emit, alongside
each `--pp-*`, an **alias to the designer's existing `--ppd-*` component tokens** (234+
usages — `--ppd-primary`, `--ppd-surface`, `--ppd-bg`, `--ppd-border`, `--ppd-ok`,
`--ppd-danger`, `--ppd-shadow`, …). Scoped to `.ppd-canvas` / `#spPreview`, this re-themes
the whole component library with no per-component CSS changes. A few hardcoded holdouts
(e.g. `.ppd-field-token`) were switched to `var(--ppd-*, fallback)` so they follow too.

## Designer integration

`DesignerController` ships the catalogue as `PpDesignerBootstrap.themes` + `themeDefault`.
The page inspector's **Theme** field is a dropdown; the canvas applies the compiled theme
(`app/theme.js` — the client mirror of `ThemeCompiler`) scoped to `.ppd-canvas`, with a
**Preview dark mode** toggle. `layout.theme` on a page selects its theme.

> For a theme to visibly restyle rendered components, those components must consume the
> `--pp-*` tokens. The Style preview already does; tokenizing the full designer component
> library is a follow-up.
