# va-themes/florial

Florial — flower-subscription product theme. Covers the three Florial client
environments (`savitarna`, `pardavimai`, `operacijos`) and is switch-ready for
the public `web` env (Protera stays the web default until Phase 6).

- **Namespace:** `ViartasThemes\Florial\`
- **Provider:** `FlorialThemeServiceProvider` (extends `BaseThemeServiceProvider`)
- **Brand tokens:** `Resources/assets/css/variables.css` (CSS custom properties only)
- **Fonts:** Playfair Display, self-hosted variable woff2 (`Resources/assets/fonts/`)
- **Layout:** `theme::layouts.app` (`Resources/views/layouts/app.blade.php`)
- **DB rows:** seeded by `FlorialThemeSeeder` (savitarna/pardavimai/operacijos =
  default; web = switch-ready, `is_default=false`).

## Build (per-package Vite)

Node 18+ required.

```bash
cd packages/va-themes/florial
npm ci
npm run build          # → public/build/assets/florial.{css,js} + woff2 (tracked)
cd -
php artisan themes:publish-assets   # copies public/build → public/vendor/va-themes/florial/build (gitignored)
```

The `florial.{css,js}` bundle is emitted for the `savitarna.*` / `pardavimai.*` /
`operacijos.*` route patterns via the host `@assets` directive.

## Public marketing site — CMS Pages, not theme routes

Since **PackageStructure/task-16** the four landings (`/`, `/imonems`, `/erdvems`,
`/privatiems`) are `va-modules/pages` rows, not routes owned by this theme. What the
theme contributes is the presentation half:

| Piece | Where |
|---|---|
| Page templates (`florial-*`) | `Resources/views/pages/web/florial-{index,imonems,erdvems,privatiems}.blade.php` |
| The `/` homepage | `Resources/views/pages/home.blade.php` (`theme::pages.home` — resolves only while Florial is the active WEB theme, which is the gate that replaced the old route condition) |
| The data each template reads | `src/Support/LandingData.php`, handed over by a view composer |
| Landing URLs for the chrome | `src/Support/LandingLinks.php` — resolved from the page row by TEMPLATE, so an admin's slug rename moves every link |
| Template options + text slots | `webPageTemplates()` / `templateSlots()` on the provider |

The rows themselves are seeded by `va-florial/core`'s `FlorialLandingPagesSeeder`
(baseline, so `florial:upgrade --force` creates them).

⚠️ `Resources/views/pages` is prepended to the `pages` view namespace
**unconditionally** — the same call `va-themes/seglo` makes, and for the same reason:
a theme reaches that namespace only while it is the active WEB theme, and the landings
must render on a host where another theme owns `web`. Only `florial-*` names live
there, never `pages::web.default`, so nothing else is shadowed.

## Legal, DUK and Kontaktai — also CMS Pages (task-17)

**PackageStructure/task-17** did the same for the remaining six public URLs, and with
them went the LAST route this theme owned: there is no `Routes/web.php` any more. (The
`APP_ENV=testing` seams that survived it left in task-18 — see below.)

| Piece | Where |
|---|---|
| Page templates | `Resources/views/pages/web/florial-{privatumas,salygos,slapukai,pristatymo-salygos,duk,kontaktai}.blade.php` |
| Shared bodies | `Resources/views/legal/document.blade.php` (the four documents) · `Resources/views/legal/duk.blade.php` |
| The data each template reads | `src/Support/LegalData.php`, handed over by a view composer |
| Legal URLs for the chrome | `src/Support/LegalLinks.php` (over `src/Support/PageLinks.php`, shared with `LandingLinks`) |
| The contact FORM | `va-modules/forms` definition `florial-kontaktai`, rendered through `Resources/views/forms/web/variants/flor.blade.php` |
| The `/duk` override | `Resources/views/faq/web/index.blade.php` |

Two of the six URLs are **contested**, and the theme does not win them by force:

- **`/duk`** — `va-modules/faq` mounts its public index on exactly that URL, and a
  concrete route always beats the Pages catch-all. So the theme OVERRIDES that index
  (`faq::web.index`) instead of registering a competing route. Both entry points
  `@include('florial::legal.duk')`, so whichever wins draws the same page. (The module's
  EN route is `/en/faq`, so `/en/duk` still comes through the catch-all — which is why
  both are needed.)
- **`/kontaktai`** — other products seed their own `kontaktai` Page. `FlorialLegalPagesSeeder`
  therefore creates these two rows ONLY where Florial is the default `web` theme. That is
  not a new condition: it is the `florialOwnsHomepage()` gate their routes used to carry,
  moved from the routing table into the seeder.

⚠️ **The contact POST belongs to `va-modules/forms` (`/forms/florial-kontaktai`).** The
markup is unchanged — the `flor` variant renders the controls through the same
`Http\Forms\ContactForm` declaration the page used before — but the submission is now
validated from the stored definition, rate-limited, honeypot-guarded, PERSISTED and
mailed, where the theme's old handler wrote it to the log. Adding a field means editing
BOTH `ContactForm` (markup + rules) and `FlorialContactFormSeeder` (the stored definition);
`tests/Feature/Florial/LegalOwnershipTest` fails when they drift.

## This theme owns no PHP entry point at all (task-17 + task-18)

`src/Http/` contains `Forms/` and nothing else. There is no `Routes/` directory, no
`Controllers/` directory, no migration — which is the whole of what
[`.claude/module-structure.md`](../../../.claude/module-structure.md) §5 asks of a theme.

The last things to leave were the seven `/florial/testing/*` diagnostic endpoints
(**PackageStructure/task-18**). They are E2E scaffolding: Playwright drives a browser and
has no database handle, so a seam has to answer "did that submit really write a `Company`
row?" over HTTP. They now live in the Viartas monorepo under `tests/Support/Florial/`,
mapped by composer's autoload-**dev** entry, so a `composer install --no-dev` cannot
autoload them at all. They were always gated on `APP_ENV=testing`, so no deployment loses
a URL it had.

If you fork this theme and need those seams, copy them from the monorepo into your own
`tests/` — do NOT re-add them here. A theme that carries a controller carries a route
sooner or later, and a route in a theme silently shadows the CMS catch-all.

## This theme owns no MODULE SCREEN either (task-19)

Until **PackageStructure/task-19** the theme also carried the entire client product: the
company cabinet (13 blades), the subscriber cabinet (4), the sales-rep cabinet (9), the
public delivery-status page and the `/savitarna/theme-check` placeholder — 30 files in all,
rendered by controllers in `va-florial/{companies,subscriptions,sales,orders,core}`. Those
packages were therefore empty shells: installed through Satis without this exact theme,
every cabinet URL answered `View [theme::savitarna.company.dashboard] not found`.

They now live in the packages that route them, under the package alias
(`flcompanies::savitarna.dashboard`, `flsales::pardavimai.wallet`, …). The theme keeps what
§2 says is its own — `layouts/`, `partials/`, `errors/` — plus module OVERRIDES under
`auth/`, `faq/`, `forms/`, `menus/` and `pages/`. The full before/after table is
[`.claude/tasks/PackageStructure/florial-blades.md`](../../../.claude/tasks/PackageStructure/florial-blades.md).

**Nothing about the rendered page changed.** With this theme installed the HTML is
byte-identical (measured across 20 authenticated cabinet renders), because a package view
and a theme view resolve through the same finder — only the file's home moved.

**What DID become possible:** those packages now render without any theme at all.
`va-florial/core` ships a bare app-shell (`Resources/views/theme-fallback/layouts/app.blade.php`)
that its `BareThemeFallback` middleware mounts as `theme::` — but ONLY when that namespace
has zero hints, so this theme always wins where it is active. If you fork this theme, that
fallback is what your consumers fall back to while your own layout is being written.

⚠️ If you override a cabinet screen here, the folder must be the package ALIAS and the
subfolder the ENVIRONMENT: `Resources/views/flcompanies/savitarna/dashboard.blade.php`.
`View::prependNamespace` matches on the full path, so a near-miss override stops applying
silently rather than erroring.

## Notes

- Brand tokens are **reconstructed** from the task spec because the design source
  `packages/viartas-design/ofisai/` is currently absent. Token NAMES are the
  stable contract — sync values 1:1 when the source returns.
- Flat radius (`--bs-border-radius: 0`) — magazine/editorial look.
