# Shop Portal — Design-Parity Audit (typography · color · spacing)

**Date:** 2026-07-06 · **Scope:** the SHOP portal (`frontend/`) only, every page + menu item +
navbar + drawers, compared class-for-class against the demo `Navagoo_MI/navagoo-app` (v0.19,
origin/dev). Method: **code-level Tailwind-class parity** (fonts/sizes/weights, colors, margin/
padding/gap, structural fidelity) — the utility classes ARE the spec. One sub-agent per surface;
each produced the per-surface plan below. Synthesized by the orchestrator.

This file is the living audit. `[H]`/`[M]`/`[L]` = severity.

## ✅ EXECUTION STATUS (2026-07-06) — plans implemented

The plans below were **executed**: 4 systemic fixes + all shared chrome (orchestrator), then **14
sub-agents** applied their per-surface class changes (80 changes total). Verified: every changed PHP
`php -l` clean, `aurora-core.js`/`aurora-tabs.js` `node -c` clean, `npm run build:css` rebuilt, **render
smoke 30/30**, and browser spot-checks (chrome avatars, focus, toast, confirm dialog, dashboard).

**Done (global / systemic):**
- Focus-visible rings on all chrome controls + a global `:focus-visible` outline + a `shadow-focus` token.
- Header/sidebar avatars → colored ShopTile gradient tile (`rounded-full`/`rounded-xl`), was a flat white square.
- Booking-status `st-*` tokens re-aligned to demo hues; `navy-*` ramp added.
- **Toast** rebuilt to the demo white-card (bottom-end, colored icon, X dismiss); **modal backdrops** →
  `navy-950/45`; **ngConfirm** title `text-lg tracking-tight` + `rounded-xl` demo-token buttons.
- **Badges** (`Aurora::TONES`) gained `ring-1 ring-inset` + `-700` text (demo Badge) — applies to every badge.
- Active segmented-tab label → `text-brand-700`; dashboard hero → `bg-aurora-h`; content max-width 1400→1600 +
  responsive gutters.
- 80 per-surface class fixes across Finance, Settings, Services, Notifications, Team, Customers, Calendar,
  Booking-detail, Marketing, Reviews, Agents-wallet, Social/Help, Navagoo Plans.

**Deferred (flagged — its own pass):**
- **Dashboard/Home — full re-port to the demo v0.19 Home** (schedule-hero timeline, attention feed, balance
  panel, week-at-a-glance bottom band). A dedicated PORTING WAVE, not a class tweak — the bulk of why Dashboard
  scored 42%. The old Dashboard is now polished but is not the new Home IA.
- A few LOW items agents skipped where the premise didn't hold (e.g. runtime `--color-st-*` vars the portal
  doesn't use) or that needed NEW i18n strings (a design-only pass forbids new keys).

---

## 0. Foundation — design tokens (portal `tailwind.config.js` v3 ⇄ demo `@theme` v4)

**Verdict: ~95% identical.** brand 50–900, accent 50–700, ink/ink-soft/ink-muted/page,
amberbrand/cyanbrand, radii (xl 14 · 2xl 20 · 3xl 28), shadow-card, shadow-pop, font-sans
(Google Sans Text) — all byte-identical. So parity gaps are almost entirely PER-SURFACE class
usage, not tokens. The real token deltas to know:

| Token | Portal | Demo | Note |
|---|---|---|---|
| `st-scheduled` | `#594279` | `#6a3ab8` | demo calendar STATUS_COLOR; applied new-route-locally already (playbook) |
| `st-progress` | `#e8a833` | `#f5b71b` | " |
| `st-completed` | `#2ebf91` | `#57b49b` | " |
| `st-noshow` | `#6f7682` | `#4c5663` | " |
| `st-cancelled` | `#dc5757` | `#dc5757` | same |
| `shadow-focus` | — (missing) | `0 0 0 4px rgba(131,96,195,.22)` | portal has no focus-ring token; inputs may differ |
| `navy-600..950` | — | `#46345f..#160f20` | admin chrome only; shop uses brand — N/A for shop |
| `--topbar-h` | hardcoded | `3.75rem` (60px) | verify navbar height = 60px |
| `font-ar` | Noto Kufi Arabic | — | portal-only RTL family (fine) |

**Type scale:** both use Tailwind's DEFAULT type scale (no `--text-*` overrides) → sizes match
IFF the same `text-*` utilities are used. **This is the #1 thing every per-surface agent checks.**

**Shell:** demo TopBar + Sidebar brand band are both `--topbar-h` (60px); the sidebar's flat
gradient zone derives from it + radius-2xl. Portal `.bg-side-shop` / `.bg-head-shop` gradients
(tailwind.config) must match the demo `.bg-side-shop`/`.bg-head-shop` in index.css.

---

## Per-surface plans

<!-- SYNTHESIS_ANCHOR: per-surface sections are appended below by the audit synthesis -->

## Summary — surfaces by work needed

| Surface | Parity | High | Med | Low |
|---|---:|---:|---:|---:|
| [Finance (5 tabs + shell)](#finance-5-tabs-shell) | ~82% | 3 | 2 | 3 |
| [Services + Packages](#services-packages) | ~55% | 2 | 4 | 2 |
| [Notifications](#notifications) | ~58% | 2 | 4 | 2 |
| [Settings (5 tabs: General · Scheduling · Payments · Commercials · Notifications)](#settings-5-tabs-general-scheduling-payments-commercials-notifications) | ~78% | 2 | 3 | 3 |
| [Marketing / Promo codes (frontend/views/promo-code/index.php ⇄ shop/Marketing.tsx → marketing/Deals.tsx)](#marketing-promo-codes-frontend-views-promo-code-index-php-shop-marketing-tsx-marketing-deals-tsx) | ~80% | 2 | 3 | 3 |
| [Dashboard / Home (shop portal — frontend/views/site/index.php)](#dashboard-home-shop-portal-frontend-views-site-index-php) | ~42% | 2 | 3 | 2 |
| [Chrome — Modals, Drawers, Toasts](#chrome-modals-drawers-toasts) | ~68% | 2 | 3 | 2 |
| [Chrome — Navbar/TopBar (frontend/views/layouts/_tw_navbar.php ⇄ Navagoo_MI TopBar.tsx, shop variant)](#chrome-navbar-topbar-frontend-views-layouts-tw-navbar-php-navagoo-mi-topbar-tsx-shop-variant) | ~80% | 2 | 2 | 3 |
| [Team / Agents (Specialists · Payroll · Structure tabs)](#team-agents-specialists-payroll-structure-tabs) | ~82% | 2 | 3 | 2 |
| [Booking detail + booking modals (frontend/views/booking-calendar/_detail.php, _new_booking.php, _group_booking.php vs demo BookingDetailBody.tsx, BookingTimeline.tsx, modals.tsx)](#booking-detail-booking-modals-frontend-views-booking-calendar-detail-php-new-booking-php-group-booking-php-vs-demo-bookingdetailbody-tsx-bookingtimeline-tsx-modals-tsx) | ~90% | 2 | 3 | 2 |
| [Chrome — Sidebar](#chrome-sidebar) | ~82% | 2 | 1 | 3 |
| [Agents-wallet — Transfer Requests (index.php) + Specialist Tips (specialist-tips.php)](#agents-wallet-transfer-requests-index-php-specialist-tips-specialist-tips-php) | ~88% | 2 | 2 | 2 |
| [Customers + Customer Invitations](#customers-customer-invitations) | ~72% | 1 | 4 | 2 |
| [Foundation — Layout shell + tokens + gradients (frontend/views/layouts/tailwind.php + tailwind.config.js vs demo ShopLayout/RootLayout/index.css)](#foundation-layout-shell-tokens-gradients-frontend-views-layouts-tailwind-php-tailwind-config-js-vs-demo-shoplayout-rootlayout-index-css) | ~88% | 1 | 2 | 4 |
| [Calendar (day / month / list) — booking-calendar v2](#calendar-day-month-list-booking-calendar-v2) | ~88% | 1 | 3 | 3 |
| [Social Media + Help Center + Agent Slots (portal-only, no demo counterpart)](#social-media-help-center-agent-slots-portal-only-no-demo-counterpart) | ~88% | 1 | 3 | 3 |
| [Reviews (rate) — frontend/views/rate/index.php + _detail.php](#reviews-rate-frontend-views-rate-index-php-detail-php) | ~88% | 0 | 2 | 5 |
| [Navagoo Plans (frontend/views/navagoo-plans/index.php + _offer_ticket.php)](#navagoo-plans-frontend-views-navagoo-plans-index-php-offer-ticket-php) | ~93% | 0 | 2 | 2 |
| **TOTAL (18 surfaces)** | — | **29** | **49** | **48** |

## Systemic findings (repeat across 3+ surfaces — fix once, globally)

- **[color] The interactive focus state is missing across every sidebar control. Demo nav links use 'outline-none focus-visible:ring-2 focus-visible:rin** — hits 6 surfaces: Chrome, Chrome, Foundation, Chrome, Navagoo Plans, Reviews.
  Fix: Add 'outline-none focus-visible:ring-2 focus-visible:ring-white/40' to the tw-navlink class (line 100) and to the collapse button (line 166); add 'outline-none focus-visible:ring-2 focus-visible:ring-white/30' to the tw-sec-sum <summary> (line 151). Rebuild with npm run build:css.
- **[structure] The header-chip avatar square diverges from the demo's ShopTile (md). Portal (line 138) uses 'size-9 rounded-lg bg-white/15 text-sm font-bol** — hits 5 surfaces: Chrome, Chrome, Dashboard / Home, Calendar, Customers + Customer Invitations.
  Fix: Change the chip square to size-10 rounded-xl to match ShopTile md. If a per-shop hue is available, apply the same 135deg oklch gradient; otherwise keep a brand-toned fill rather than bg-white/15 so it reads as a colored tile, not a flat chip.
- **[spacing] Header action buttons (New group / New walk-in) are shorter and smaller-typed than the demo. Demo PageHeader actions use Button default size** — hits 4 surfaces: Calendar, Finance, Notifications, Marketing / Promo codes.
  Fix: If matching the demo button metrics, bump to `px-5 text-[15px]` with a fixed `h-11` (keep the icon `gap-1.5`). Low priority — this is a consistent aurora-wide button substitution.
- **[structure] Portal service/routine/addon/bundle/package cards (article.tw-catalog-card, ~line 292) render a full-width gradient banner header (`relative** — hits 3 surfaces: Services + Packages, Customers + Customer Invitations, Notifications.
  Fix: Replace the banner-header body with the demo layout: card = `group flex flex-col rounded-2xl bg-white p-4 shadow-card ring-1 ring-slate-900/5`; inside a `flex items-start gap-3` row put `<div class='grid size-24 shrink-0 place-items-center overflow-hidden rounded-2xl bg-slate-100 text-slate-300'>` (image or fallback icon size-8) then a `min-w-0 flex-1` column with badge/status + LifecycleActions on top and `p.mt-2 truncate text-base font-bold text-ink` name below. Drop $thumbTints/gradient strip and the h-28 header.

---

## Per-surface findings + plans

### Finance (5 tabs + shell)
<a id="finance-5-tabs-shell"></a>
**Parity ~82%** · 3H / 2M / 3L

Structurally faithful port — all 5 panes exist with the correct columns, cards, filters, modals and drawer, and the shared Card/table-header/PageHeader classes match the demo. But two signature finance widgets diverge from the demo primitives (the P&L StatTile quartet and the Settlement FormulaStrip), plus a cluster of control-level deltas (tab underline color, Segmented filter shape, chip padding), so it reads as a close-but-not-pixel-parity rebuild rather than a 1:1 port.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | typography | P&L quartet (line 124-172) does not match the demo StatTile (shared.tsx). Demo tile value is `text-xl font-extrabold tracking-tight` in a `p-4` card with NO icon; portal renders the value at `text-[26px] font-extrabold leading-none`, uses a `p-5` card, and adds a `size-10` lucide icon chip the demo tile never has. Grid also differs: demo `grid-cols-2 gap-4 lg:grid-cols-4` vs portal `grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-4`. | Match StatTile: card `p-4`, value `text-xl font-extrabold tracking-tight text-ink tnum`, caption `mt-1`, action `mt-3`. Remove the icon chip + top accent bar OR keep as a deliberate aurora enhancement applied to all 4. Change grid to `grid grid-cols-2 gap-4 lg:grid-cols-4`. | `views/earnings/_earnings.php` |
| H | structure | Settlement formula strip (line 93-109) is not the demo FormulaStrip (shared.tsx). Demo renders each term as a tone-tinted BOXED chip (`rounded-xl px-3.5 py-2.5 ring-1 ring-inset` with teal/sky/purple/cyan/amber/orange tint), operators `text-lg font-bold text-ink-muted`, centered (`justify-center`), value `text-sm font-bold`. Portal renders bare `flex flex-col items-center` text columns (no box, no ring, no per-term tone; loss terms only recolored red), plain operators, left-aligned, value `text-base font-extrabold`. | Rebuild each term as a boxed chip using demo TONES per term (Collected->teal, Tips->sky, Marketing->purple, Payment Processing->cyan, Notifications->amber, Fee VAT->orange): `rounded-xl px-3.5 py-2.5 ring-1 ring-inset <tone>` with label `text-[10px] font-bold uppercase tracking-wide opacity-80` + value `text-sm font-bold tnum text-ink`. Wrap in `justify-center`, make operators `text-lg font-bold`. Result box: keep `bg-brand-600` but `px-4 py-2.5` with value `text-base font-extrabold`. | `views/earnings/_settlement.php` |
| H | color | Active-tab underline uses the wrong purple (registerCss, line 56-67). Demo TabNav underline is `bg-brand-600` (2px) and the active label is `text-brand-700`; portal draws the ::after underline off `--ng-purple-600, #594279` (the old brand-900/st-scheduled purple) and — because aurora-tabs.js pill classes are neutralized — leaves the active label at `text-ink` instead of brand-700. | In the injected CSS set the ::after background to the brand-600 token (var(--color-brand-600), not #594279). Add an `[aria-selected="true"]` rule coloring the active tab label `text-brand-700` (color: var(--color-brand-700)) so the active tab reads brand like the demo. | `views/earnings/index.php` |
| M | structure | Status Segmented control (line 159-170) differs from demo Field.tsx Segmented. Demo: container `rounded-xl bg-slate-100 p-1`, buttons `rounded-lg px-3 py-1.5`, active `bg-white text-brand-700 shadow-xs`. Portal: container `rounded-full ... p-0.5`, buttons `rounded-full px-4 py-1.5`, active `bg-white text-ink shadow-xs` (pill-shaped, wrong active text color). | Change container to `rounded-xl ... p-1`, buttons to `rounded-lg px-3 py-1.5`, and the active branch from `text-ink` to `text-brand-700` (keep `bg-white shadow-xs`). | `views/earnings/_charges.php` |
| M | spacing | Invoice status chip padding + ring diverge from the demo Badge PILL and are internally inconsistent with the Earnings pane. Demo InvoiceBadge = `inline-flex ... px-2.5 py-0.5 text-xs font-semibold ring-1 ring-inset`; portal statusChip (line 43) and the JS-built doc chip (line 404) use `inline-block ... px-3 py-1` with no ring, while _earnings.php chips correctly use `px-2.5 py-0.5`. | Change the invoice statusChip wrapper to `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ring-1 ring-inset` plus the matching ring tone; update the JS-generated doc status chip (line 404) to the same padding for consistency. | `views/earnings/_invoices.php` |
| L | spacing | Type Select is `h-9` (line 174); demo Field.tsx Select (and Input baseControl) is `h-10`. Control sits one notch shorter than the demo beside the segmented row. | Change the select height from `h-9` to `h-10` to match the demo control height. | `views/earnings/_charges.php` |
| L | spacing | Earnings table cells use `px-4` (line 189-252); the demo passes `compact` to this 10-column DataTable, which sets `px-3` on th and td. The Charges 10-column table is likewise `px-4` where the demo would compact it. Slightly wider horizontal rhythm than the demo across the wide finance tables. | For the 10-column Earnings and Charges tables, switch th/td horizontal padding from `px-4` to `px-3` to match the demo compact DataTable; leave the narrower Settlement eligibility table as-is. | `views/earnings/_earnings.php` |
| L | spacing | Shell rhythm is 24px where the demo is 20px. Demo ShopFinanceLayout wraps header + TabNav + content in `space-y-5` (20px); portal uses `mb-6` (24px) on both the page header (line 71) and the tab-nav wrapper (line 80). | Change the header wrapper and tab-nav `mb-6` to `mb-5` (or wrap the three blocks in a `space-y-5` container) to match the demo's 20px vertical rhythm. | `views/earnings/index.php` |

**Plan:**
1. Fix the Earnings P&L quartet (_earnings.php): reconcile StatTile to the demo — value text-xl not text-[26px], card p-4 not p-5, grid gap-4 + lg:grid-cols-4, and decide the icon chip in/out consistently across all 4 tiles. Highest visual weight, above the fold.
2. Rebuild the Settlement FormulaStrip (_settlement.php) as demo tone-boxed chips (ring-1 ring-inset tints per term, centered, text-lg operators). Second signature widget.
3. Correct the active-tab underline + label color in index.php (brand-600 underline, text-brand-700 active label) — affects every tab on every pane.
4. Align the Charges status Segmented control (_charges.php): rounded-xl container p-1, rounded-lg px-3 buttons, active text-brand-700.
5. Normalize chip padding: invoice status chips to px-2.5 py-0.5 + ring (matching demo Badge and the Earnings pane), including the JS-built doc chip.
6. Sweep the small deltas: Charges select h-10; wide (10-col) Earnings + Charges tables to px-3 (compact); shell rhythm mb-6 -> mb-5.
7. Run npm run build:css and browser-verify each tab side-by-side against the demo at /shop/finance/*.


### Services + Packages
<a id="services-packages"></a>
**Parity ~55%** · 2H / 4M / 2L

The five-tab shell (PageHeader, Segmented tabs, showInactive control) is close, but the card body is a different composition from the demo: the portal ships a gradient banner-header card (tw-catalog-card, h-28 colored strip + centered icon), while the demo uses a horizontal Card with a square left thumbnail, badges-above-name, and a text-lg price. Structure and price-treatment diffs dominate; roughly 55% visual parity.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | structure | Portal service/routine/addon/bundle/package cards (article.tw-catalog-card, ~line 292) render a full-width gradient banner header (`relative flex h-28 items-center justify-center ... rounded-t-2xl bg-gradient-to-br` + centered lucide icon) then a body. The demo ServicesGrid/BundlesGrid use `<Card className='group flex flex-col p-4'>` (= `rounded-2xl bg-white shadow-card ring-1 ring-slate-900/5`) with a HORIZONTAL top row: a `size-24` rounded-2xl square Thumb on the start side, then a min-w-0 column holding badges+actions on row one and the name (`text-base font-bold`) on row two. No gradient banner, no centered icon. This is the dominant visual gap on the surface. | Replace the banner-header body with the demo layout: card = `group flex flex-col rounded-2xl bg-white p-4 shadow-card ring-1 ring-slate-900/5`; inside a `flex items-start gap-3` row put `<div class='grid size-24 shrink-0 place-items-center overflow-hidden rounded-2xl bg-slate-100 text-slate-300'>` (image or fallback icon size-8) then a `min-w-0 flex-1` column with badge/status + LifecycleActions on top and `p.mt-2 truncate text-base font-bold text-ink` name below. Drop $thumbTints/gradient strip and the h-28 header. | `views/shop-service/index.php` |
| H | color | Service price (~line 322) is a small accent PILL: `inline-flex ... rounded-full bg-accent-50 px-2.5 py-0.5 text-xs font-semibold text-accent-600`. The demo shows the service price as a large plain figure via `<Money className='text-lg font-extrabold text-ink'/>` (no pill, no accent bg), right-aligned in a `mt-3 flex items-center justify-between` row opposite the duration. Bundle price (~line 436) has the same mismatch vs demo `text-lg font-extrabold text-ink`. | For services and bundles, drop the `bg-accent-50 ... text-accent-600` price pill and render the price as `text-lg font-extrabold text-ink tnum` inside a `mt-3 flex items-center justify-between` row, with the Clock duration (`text-xs text-ink-muted`) opposite; keep the struck line-through before-price as `text-xs text-ink-muted tnum` immediately before it. | `views/shop-service/index.php` |
| M | spacing | Grid metrics differ. Portal uses `grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-3` for every tab (~line 275); the demo uses `grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3`. Portal has a wider gap (gap-5 vs gap-4) and stays 2-column until xl while the demo goes 3-up at lg — a visibly looser, later-breaking grid. | Change every catalogue grid on this surface (services, routines/addons, bundles, packages) to `grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3`. | `views/shop-service/index.php` |
| M | color | Badges don't match the demo Badge primitive. Portal category badge (~line 303) = `bg-brand-50 px-2.5 py-0.5 text-xs font-semibold text-brand-600` (no ring, text -600). Demo Badge tone='purple' = `bg-brand-50 text-brand-700 ring-1 ring-inset ring-brand-600/12`. The $badges() Inactive chip (~line 106) = `bg-slate-200 text-ink-soft` vs demo slate `bg-slate-100 text-ink-soft ring-1 ring-inset ring-slate-300/50`; Hidden chip (~line 109) = `bg-amber-100 text-amber-700` vs demo amber `bg-[#FDF3DC] text-[#8A6516] ring-[#E8A833]/25`. | Give badges the demo tone recipe: category -> `bg-brand-50 text-brand-700 ring-1 ring-inset ring-brand-600/12`; Inactive -> `bg-slate-100 text-ink-soft ring-1 ring-inset ring-slate-300/50`; Hidden -> `bg-[#FDF3DC] text-[#8A6516] ring-1 ring-inset ring-[#E8A833]/25`. Add ring-1 ring-inset to all pill badges. | `views/shop-service/index.php` |
| M | color | Lifecycle action buttons ($lifecycle, ~line 119) use wrong idle/hover colors vs the demo LifecycleActions. Portal eye button hovers `hover:bg-amber-50 hover:text-amber-600` and the power/active button is `text-ink-muted hover:bg-brand-50 hover:text-brand-600` regardless of state. Demo: visibility idle reflects state (`hidden ? 'text-amber-500 hover:bg-amber-50' : 'text-ink-muted hover:bg-slate-100 hover:text-ink'`) and the active button reflects state (`active ? 'text-accent-600 hover:bg-accent-50' : 'text-st-cancelled hover:bg-red-50'`). Portal's power icon never turns accent-green when active or red when inactive. | Match demo per-state coloring: eye -> `text-amber-500 hover:bg-amber-50` when hidden else `text-ink-muted hover:bg-slate-100 hover:text-ink`; active/power -> `text-accent-600 hover:bg-accent-50` when active else `text-st-cancelled hover:bg-red-50`. Keep edit `hover:bg-brand-50 hover:text-brand-600` and delete `hover:bg-red-50 hover:text-st-cancelled`. | `views/shop-service/index.php` |
| M | structure | Tab bar + showInactive control diverge from the demo (~lines 193-209). Demo Segmented = `inline-flex items-center gap-0.5 rounded-xl bg-slate-100 p-1`, active tab `bg-white text-brand-700 shadow-xs`, idle `text-ink-muted hover:text-ink`, no per-tab count pill. Portal tab bar = `rounded-2xl bg-white p-1 shadow-card ring-1 ring-slate-900/5`, active `bg-brand-600 text-white`, with a count pill per tab. Demo showInactive is a plain Toggle switch beside a `text-xs font-semibold text-ink-soft` label; portal renders a rounded pill button (`rounded-full ... shadow-card ring-1`). | For parity: tab container `inline-flex items-center gap-0.5 rounded-xl bg-slate-100 p-1`, active `bg-white text-brand-700 shadow-xs`, idle `text-ink-muted hover:text-ink`; fold the count into the label text. Replace the showInactive pill-link with a Toggle switch + `text-xs font-semibold text-ink-soft` label. Lower priority than card body — flag for product decision (the tabbed shell is a portal-specific adaptation). | `views/shop-service/index.php` |
| L | structure | Freebie FREE marker differs (~line 371). Portal puts a `bg-white/90 text-accent-600 text-[11px] font-bold` FREE pill on the gradient banner. Demo FreebieGrid uses `<Badge tone='teal'>` (= `bg-accent-50 text-accent-700 ring-1 ring-inset ring-accent-500/15`) inline in the badge row, and the freebie duration is `inline-flex items-center gap-1 text-xs text-ink-muted` + Clock (no slate pill); portal wraps it in a `bg-slate-100 ... rounded-full` chip (~line 387). | Render the FREE marker as a teal Badge (`bg-accent-50 text-accent-700 ring-1 ring-inset ring-accent-500/15`) in the badge row of the reworked card, and show the freebie duration as plain `inline-flex items-center gap-1 text-xs text-ink-muted` + Clock instead of a slate pill. | `views/shop-service/index.php` |
| L | structure | package/index.php (~line 120) and package/subscriptions.php (~line 84) also use the gradient banner-header card (`h-28 ... bg-gradient-to-br` + status chip on the strip) rather than the demo Card layout, and package/index price is an accent pill (`bg-accent-50 text-accent-600`, ~line 184) instead of `text-lg font-extrabold text-ink`. These should track whatever the services surface adopts so all three catalogue surfaces stay consistent. | Once shop-service cards are reworked to the demo Card layout, apply the same square-thumb + badges-above-name + `text-lg font-extrabold text-ink` price treatment to package/index.php and package/subscriptions.php (they currently share the tint/banner pattern). | `views/package/index.php` |

**Plan:**
1. Rework the catalogue card body across the surface: drop the tw-catalog-card gradient banner header + $thumbTints, and rebuild each card as the demo Card — `group flex flex-col rounded-2xl bg-white p-4 shadow-card ring-1 ring-slate-900/5` with a `flex items-start gap-3` row (square `size-24 rounded-2xl bg-slate-100` thumb on start; min-w-0 column with badge+actions on top and `text-base font-bold text-ink` name below).
2. Fix the price treatment: for services and bundles replace the accent price pill with a plain `text-lg font-extrabold text-ink tnum` figure in a `mt-3 flex items-center justify-between` row opposite the Clock/duration, keeping any struck before-price as `text-xs text-ink-muted line-through`.
3. Align grid metrics: switch every tab's grid to `gap-4 sm:grid-cols-2 lg:grid-cols-3` (from `gap-5 ... xl:grid-cols-3`).
4. Bring badges onto the demo tone recipe (add ring-1 ring-inset; category `text-brand-700 ring-brand-600/12`; Inactive slate-100 ring; Hidden #FDF3DC/#8A6516), and give the FREE marker the teal Badge treatment inline.
5. Match lifecycle-button per-state colors (eye amber-when-hidden; power accent-when-active / st-cancelled-when-inactive).
6. (Product decision) Reconcile the tabbed shell: consider the demo Segmented (`bg-slate-100 p-1`, active `bg-white text-brand-700 shadow-xs`, count in label) and a real Toggle for showInactive instead of the brand-filled pill tabs + pill toggle.
7. Propagate the same card rework to package/index.php and package/subscriptions.php so all three catalogue surfaces stay consistent.


### Notifications
<a id="notifications"></a>
**Parity ~58%** · 2H / 4M / 2L

The portal reproduces the PageHeader block faithfully, but the feed itself is a substantial visual reinterpretation rather than a port of the demo: the demo is a single width-capped Card (max-w-3xl, p-0) holding a divide-y list of compact rows with a uniform brand Bell chip and a teal unread dot, whereas the portal renders each notification as its own full-width standalone shadow-card with a left accent bar, purple unread indicators, per-module colored icon chips, and a two-line title/body/timestamp layout. Structurally recognizable but noticeably richer and differently colored than the source of truth.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | structure | Demo wraps the whole feed in ONE Card `rounded-2xl bg-white shadow-card ring-1 ring-slate-900/5` with `max-w-3xl p-0`, and renders rows as `<li>` inside `<ul class="divide-y divide-slate-100">` (hairline-separated, no per-row shadow/ring). Portal (line 123-166) drops the container Card entirely, has no max-width cap, and renders each notification as an independent `<article rounded-2xl bg-white p-4 shadow-card ring-1 ... sm:p-5>` in a `space-y-3` stack. Result: full-bleed stack of floating cards vs a single contained divided list. | Wrap the feed in a single `<div class="mt-6 max-w-3xl rounded-2xl bg-white shadow-card ring-1 ring-slate-900/5">` with a `divide-y divide-slate-100` list inside; change each `<article>` from a shadowed rounded card to a plain row (`px-5 py-3.5`, no rounded-2xl / shadow-card / ring per row). | `views/notifications/index.php` |
| H | color | Unread indicator uses the wrong semantic color. Demo unread dot is `bg-accent-500` (teal) at `absolute -end-0.5 -top-0.5 size-2.5 ring-2 ring-white` on the chip. Portal uses purple `bg-brand-500` for the corner/inline dot (lines 98, 152) AND adds a purple left accent bar `before:bg-brand-500` + ring `ring-brand-500/25` (lines 130-131, 134) that the demo has no equivalent for. | Recolor unread affordances from brand→accent: dot `bg-accent-500`; drop the `before:` left-accent bar and the `ring-brand-500/25` unread ring (demo has neither) or, if kept, tint them accent, not brand. Position the unread dot on the icon chip (`-end-0.5 -top-0.5 size-2.5 ring-2 ring-white`) instead of beside the title. | `views/notifications/index.php` |
| M | color | Icon chip differs in color strategy and size. Demo uses a single uniform chip `grid size-9 rounded-xl bg-brand-50 text-brand-600` with one `Bell` icon `size-4` for every row. Portal (lines 48-64, 142-143) uses `size-10` chips with per-module multicolor tints (sky/accent/amberbrand/st-cancelled/slate) and per-module Lucide icons at `size-5`. | To match parity, make the chip `size-9 rounded-xl bg-brand-50 text-brand-600` with a `bell` icon at `size-4`. If per-module iconography is a deliberate portal enhancement, at minimum reduce chip to `size-9` and icon to `size-4` and default the tint to `bg-brand-50 text-brand-600`. | `views/notifications/index.php` |
| M | typography | Title element and weight logic diverge. Demo renders title as a `<p>` sized `text-sm`, bold ONLY when unread (`text-sm font-bold text-ink` vs `text-sm text-ink`). Portal renders an `<h3>` at `text-[15px] font-bold leading-snug tracking-tight text-ink` that is ALWAYS bold regardless of read state (line 148), so read items look heavier than the demo. | Change title to `text-sm` and gate the weight on read state — `font-bold` for unread, normal weight for read — matching the demo; drop `text-[15px]`/`tracking-tight` on this element. | `views/notifications/index.php` |
| M | structure | Row content structure adds a block the demo doesn't have. Demo row is two lines: title `<p>` + timestamp `<p class="text-xs text-ink-muted">{date}</p>` (no icon). Portal adds a separate message paragraph (`mt-1 text-sm text-ink-soft`, line 157) AND a timestamp row with a `clock` Lucide icon (`size-3.5`, lines 160-162), producing a three-line row with iconography the demo lacks. | Collapse to the demo's two-line row: title then a plain `text-xs text-ink-muted` timestamp with no clock icon. If the message line is intentionally kept, at least drop the clock icon so the timestamp matches the demo's bare `<p class="text-xs text-ink-muted">`. | `views/notifications/index.php` |
| M | spacing | Mark-all button is far smaller than the demo's Button. Demo uses `Button variant=secondary size=md` = `h-11 px-5 text-[15px] gap-2 rounded-full` + `bg-white text-ink ring-1 ring-inset ring-slate-200 hover:bg-brand-50 hover:ring-brand-200`. Portal (lines 101-105) uses `px-3.5 py-1.5 text-xs font-semibold text-ink-soft` with `border border-slate-200` and a bare `shadow-xs` — a pill roughly half the height and two type-steps smaller, and border instead of ring. | Match the secondary Button: `h-11 px-5 text-[15px] gap-2 rounded-full bg-white text-ink ring-1 ring-inset ring-slate-200 hover:bg-brand-50 hover:ring-brand-200` (drop `border`, `text-xs`, `text-ink-soft`, `shadow-xs`); icon `size-4`. | `views/notifications/index.php` |
| L | structure | Portal adds an extra 'New count' pill in the header actions row (lines 97-100: `rounded-full bg-brand-600/10 px-3 py-1 text-xs font-bold text-brand-700` with a `bg-brand-500` dot) that has no counterpart in the demo's PageHeader (demo actions = just the Mark-all button). | Remove the standalone 'New' count pill from the actions row to match the demo, which surfaces the unread count only in the subtitle, not as a header badge. | `views/notifications/index.php` |
| L | structure | Empty state is a richer bespoke block than the demo. Demo empty state is a single line inside the card: `<p class="px-5 py-10 text-center text-sm text-ink-muted">You're all caught up.</p>`. Portal (lines 112-120) builds an illustrated tile (`size-14 rounded-2xl bg-brand-50`, `bell-off` icon, an `<h3>` heading, and a paragraph) at `py-16` inside its own shadow-card. | To match parity, reduce the empty state to a single `text-sm text-ink-muted` centered line (`px-5 py-10`) inside the same container Card, or keep the illustrated version only if the enhancement is intentionally out of scope for parity. | `views/notifications/index.php` |

**Plan:**
1. Reunify the feed into one container: wrap rows in a single `max-w-3xl` Card (`rounded-2xl bg-white shadow-card ring-1 ring-slate-900/5`) with a `divide-y divide-slate-100` list; strip per-row rounded-2xl/shadow-card/ring so rows become hairline-separated lines (px-5 py-3.5) instead of floating cards.
2. Recolor unread affordances from brand(purple) to accent(teal): unread dot `bg-accent-500` positioned on the icon chip (`-end-0.5 -top-0.5 size-2.5 ring-2 ring-white`); remove the purple left `before:` accent bar and `ring-brand-500/25` unread ring that the demo has no equivalent for.
3. Normalize the icon chip to the demo: `size-9 rounded-xl bg-brand-50 text-brand-600` with a `bell` icon at `size-4` (down from size-10 chip / size-5 icon); default tint to brand-50 even if per-module icons are kept.
4. Fix title typography: use `text-sm` on the title and apply `font-bold` only when unread (read rows render at normal weight), dropping the always-bold `text-[15px] tracking-tight`.
5. Collapse each row to the demo's two-line shape (title + bare `text-xs text-ink-muted` timestamp, no clock icon); drop the extra message paragraph and the clock glyph, or at minimum remove the clock icon.
6. Restore the Mark-all button to the secondary Button spec: `h-11 px-5 text-[15px] gap-2 rounded-full bg-white text-ink ring-1 ring-inset ring-slate-200 hover:bg-brand-50 hover:ring-brand-200`, icon size-4.
7. Remove the extra 'New' count pill from the header actions row so only the Mark-all button remains, matching the demo PageHeader.
8. Simplify the empty state to a single centered `text-sm text-ink-muted` line (px-5 py-10) inside the container Card unless the illustrated version is deliberately retained.
9. After class changes, run `npm run build:css` so the Tailwind bundle reflects the edits (including any classes referenced only from JS).


### Settings (5 tabs: General · Scheduling · Payments · Commercials · Notifications)
<a id="settings-5-tabs-general-scheduling-payments-commercials-notifications"></a>
**Parity ~78%** · 2H / 3M / 3L

Notifications, Commercials, Payments and Scheduling are strong ports of the demo (card shells, tables, pill rows and usage tiles match class-for-class). The dominant parity gaps are systemic: field LABELS across every tab drop the demo's Field/SectionLabel style (bold ink text + brand accent bar) for a lighter text-xs muted label, and the top tab bar plus the General tab diverge in shape/structure from the demo's Segmented pill and single compact card. Roughly 78% visual parity.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | typography | Every field label uses the local $label helper `mb-1.5 block text-xs font-semibold text-ink-soft` (line 50), but the demo renders all Field labels via SectionLabel: `flex items-center gap-2 text-sm font-bold text-ink` PLUS a `h-3.5 w-[3px] shrink-0 rounded-full bg-brand-500` accent bar. Portal labels are smaller (text-xs vs text-sm), lighter (font-semibold vs font-bold), muted (text-ink-soft vs text-ink) and drop the brand accent bar. Repeats on _scheduling.php labels (text-xs font-semibold text-ink-soft, lines 109/137/161/190/210/237) and _payments.php deposit-% label (line 117). | Add a shared SectionLabel-style label helper rendering `<span class="mb-1.5 flex items-center gap-2 text-sm font-bold text-ink"><span class="h-3.5 w-[3px] shrink-0 rounded-full bg-brand-500"></span>{text}</span>` and use it for all field labels across _general/_scheduling/_payments (replace every `text-xs font-semibold text-ink-soft` label span). | `views/settings/_general.php` |
| H | structure | General tab is `max-w-4xl space-y-5` (line 60) with FIVE stacked section cards (Quick info, Media, Basic Information, Location, Additional Information) + a Bank card. The demo General is ONE `Card max-w-2xl p-5` with a single CardHeader and 4 fields (Commercial name, Email, Owner mobile, Bank, IBAN). Biggest visual divergence of the surface — documented merge of the legacy /shop-settings profile — changing width, card count and rhythm vs the demo. | Acknowledged intentional merge — for closer parity at minimum narrow the container to the demo `max-w-2xl`, or explicitly record the divergence in the parity doc. Section headers already match demo CardHeader via Aurora::sectionHead, so the label+width fixes carry most of the visual gain. | `views/settings/_general.php` |
| M | structure | Top tab switcher is a pill: `inline-flex gap-0 rounded-full bg-slate-100 p-0.5` (line 47) with `rounded-full px-4 py-1.5` buttons. Demo Segmented is `inline-flex items-center gap-0.5 rounded-xl bg-slate-100 p-1` with `rounded-lg px-3 py-1.5` buttons. Portal uses fully-round (rounded-full) vs the demo's rounded-xl container / rounded-lg buttons, gap-0 vs gap-0.5, p-0.5 vs p-1, px-4 vs px-3. Active state (bg-white text-brand-700 shadow-xs) matches, but the shape reads different; portal also adds icons the demo lacks. | Change the tab container to `inline-flex items-center gap-0.5 rounded-xl bg-slate-100 p-1` and each tab button to `rounded-lg px-3 py-1.5` to match the demo Segmented primitive. Keep icons only if intended, else drop for exact parity. | `views/settings/index.php` |
| M | spacing | Commercials data rows render `divide-y divide-slate-100 bg-slate-50 rounded-b-2xl` (line 102) with each row `flex items-center justify-between px-5 py-3`. Demo wraps DataRows in an inner `rounded-2xl bg-slate-50 px-4 py-1` block and each DataRow is `flex items-baseline justify-between gap-4 py-2` (no per-row horizontal padding, py-2 not py-3, no divider lines). Portal rows are taller and add divider borders the demo lacks. | Put rows inside an inner `rounded-2xl bg-slate-50 px-4 py-1` container (not a full-bleed slate card body), use `flex items-baseline justify-between gap-4 py-2` per row, and drop the `divide-y divide-slate-100` borders. | `views/settings/_commercials.php` |
| M | typography | Commercials row LABEL uses `text-xs font-semibold text-ink-soft` (line 105); the demo DataRow label is `text-sm text-ink-muted` (larger, regular weight, muted tone). The value side `text-sm font-medium text-ink` matches demo except the demo also adds `tnum` for tabular figures. | Change the row label span to `text-sm text-ink-muted` and add `tnum` to the value span (`text-sm font-medium text-ink tnum`) to match the demo DataRow. | `views/settings/_commercials.php` |
| L | structure | Time Format Segmented buttons read `24h` / `12h` in that order with `rounded-lg px-4 py-1.5` (lines 214/218). Demo Segmented options are `12-hour` / `24-hour` (12h FIRST) with `rounded-lg px-3 py-1.5`. Label text, option order, and horizontal padding (px-4 vs px-3) all differ. | Reorder to 12h first, relabel to `12-hour` / `24-hour`, and use `px-3` (not px-4) on the buttons. Container `rounded-xl bg-slate-100 p-1` already matches the demo. | `views/settings/_scheduling.php` |
| L | color | Scheduling inputs use `bg-slate-50` with `focus:bg-white` (e.g. line 117 and the other selects/inputs); the demo Field Input/Select/TimePicker baseControl uses `bg-white` (no slate-50 resting fill, no focus bg swap). General-tab inputs correctly use `bg-white` ($control, line 46), so Scheduling is internally inconsistent with both the demo and the General tab. | Change Scheduling select/number/text inputs from `bg-slate-50 ... focus:bg-white` to `bg-white`, keeping the shared focus ring `focus:border-brand-400 focus:ring-2 focus:ring-brand-400/20`, to match the demo baseControl and the General tab. | `views/settings/_scheduling.php` |
| L | color | Usage-tile overage line uses `text-rose-600` (line 161); the demo UsageStat overage line uses `text-st-cancelled` (the semantic cancelled token, #dc5757). rose-600 (#e11d48) is a visibly different red, and this is a token-vs-raw-slate/rose mismatch. | Change the overage `<p>` color from `text-rose-600` to `text-st-cancelled` to match the demo's semantic token. | `views/settings/_notifications.php` |

**Plan:**
1. Introduce a shared SectionLabel-style field-label helper (text-sm font-bold text-ink + brand-500 accent bar) and swap it in for every field label across _general.php, _scheduling.php and _payments.php — the single highest-impact fix, touching all form tabs.
2. Align the top tab switcher (index.php) to the demo Segmented: container rounded-xl bg-slate-100 p-1 gap-0.5, buttons rounded-lg px-3 py-1.5; decide whether to keep the extra tab icons.
3. Fix the Commercials data-row block: inner rounded-2xl bg-slate-50 px-4 py-1 wrapper, per-row flex items-baseline py-2 with no divider borders, label text-sm text-ink-muted, value + tnum.
4. Normalize Scheduling input fills from bg-slate-50/focus:bg-white to bg-white to match the demo baseControl and the General tab.
5. Reorder + relabel the Time Format segmented to 12-hour/24-hour (12h first) with px-3 buttons.
6. Swap the Notifications usage-tile overage color rose-600 → st-cancelled.
7. Decide on the General tab structure: accept the documented multi-card /shop-settings merge (annotate the parity doc) or narrow the container to max-w-2xl to reduce the width divergence from the demo.


### Marketing / Promo codes (frontend/views/promo-code/index.php ⇄ shop/Marketing.tsx → marketing/Deals.tsx)
<a id="marketing-promo-codes-frontend-views-promo-code-index-php-shop-marketing-tsx-marketing-deals-tsx"></a>
**Parity ~80%** · 2H / 3M / 3L

The portal is a deliberately richer, real-data version of the demo's minimal DealsManager (7 columns + filter form + status + pager vs the demo's 5-column read-only table), and the shared chrome — PageHeader classes, Card shell (rounded-2xl bg-white shadow-card ring-1 ring-slate-900/5), table header typography (text-[11px] font-bold uppercase tracking-[0.08em]) — matches the demo primitives closely. The real parity gaps are at the pill/badge level (portal chips drop the demo Badge's ring-1 ring-inset, use px-3 py-1 vs px-2.5 py-0.5, and text-*-600 vs the demo's text-*-700) plus a few token deltas in the usage bar and hover tint. Structurally faithful; visually ~80%.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | color | Discount chip is a flat pill: bg-brand-50 px-3 py-1 text-xs font-semibold text-brand-600 with NO ring. The demo equivalent is Badge tone="purple" = bg-brand-50 text-brand-700 ring-brand-600/12 with the base PILL classes rounded-full px-2.5 py-0.5 text-xs font-semibold ring-1 ring-inset. Portal is missing the ring, uses text-brand-600 (demo text-brand-700), and looser px-3 py-1 (demo px-2.5 py-0.5). | Match the demo Badge: change the discount chip to inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ring-1 ring-inset bg-brand-50 text-brand-700 ring-brand-600/12 (keep whitespace-nowrap tnum). | `views/promo-code/index.php` |
| H | color | Status chips via Aurora::badgeTone() render bg-accent-50 text-accent-600 / bg-slate-100 text-ink-soft / bg-[#fbeaea] text-st-cancelled with NO ring, and the view wraps them in px-3 py-1. Every demo Badge carries ring-1 ring-inset ring-*/12-25, px-2.5 py-0.5, and darker -700 text (e.g. red = bg-[#FBEAEA] text-[#B43A3A] ring-[#DC5757]/20, success = accent-700). Portal pills read flat and brighter than the demo's ringed pills. | Add ring-1 ring-inset + matching ring token and bump text to -700 in the badgeTone map (success: bg-accent-50 text-accent-700 ring-accent-500/15; neutral: bg-slate-100 text-ink-soft ring-slate-300/50; danger: bg-[#FBEAEA] text-[#B43A3A] ring-[#DC5757]/20), and change the status-chip wrapper padding from px-3 py-1 to px-2.5 py-0.5. | `components/Aurora.php` |
| M | structure | Usage cell composition differs from the demo. Demo (Deals.tsx) uses w-32 with a flex row 'mb-1 flex justify-between text-xs text-ink-muted' showing count (left) and cap (right) above the Bar. Portal uses mx-auto w-32 text-center with a single font-semibold text-ink 'N / M' line above the bar plus an extra 'Remaining Uses: N' line — heavier weight/darker color and a different split. | To match the demo, render the count/cap as a justify-between text-xs text-ink-muted row above the bar (left=uses, right=cap) instead of a combined bold 'N / M' string; drop the extra Remaining-Uses line or demote it, and left-align the column (demo Table default align is text-start, portal uses text-center). | `views/promo-code/index.php` |
| M | color | Usage bar tokens differ from the demo Bar. Portal: track bg-slate-100, fill bg-brand-500. Demo Bar: track bg-navy-900/8, fill var(--color-brand-600) = brand-600. Fill is one step lighter and the track is a solid slate vs the demo's translucent ink tint (both are h-1.5 rounded-full). | Change the usage-bar track to bg-navy-900/8 and the fill to bg-brand-600 to match the demo Bar primitive. | `views/promo-code/index.php` |
| M | color | Row hover tint diverges. Portal rows use hover:bg-brand-50. In the demo, DealsManager rows are NOT clickable (no onRowClick), so the demo table applies no hover tint at all; the DataTable default when hovering is hover:bg-slate-50. Portal adds a brand-50 hover the demo surface does not have. | For strict parity with this surface, drop the row hover tint (rows are non-interactive in the demo). If keeping a hover for the portal's action buttons, use hover:bg-slate-50 to match the DataTable default rather than brand-50. | `views/promo-code/index.php` |
| L | color | Delete-action idle color is darker than the demo. Demo delete button: text-slate-300 hover:text-st-cancelled (very light idle icon). Portal: text-ink-soft with hover:bg-[#fbeaea] hover:text-st-cancelled — a much darker idle icon plus a hover background chip the demo doesn't use. | Set the delete icon idle color to text-slate-300 to match the demo; the hover:text-st-cancelled is already correct. Drop the hover:bg-[#fbeaea] chip if mirroring the demo exactly. | `views/promo-code/index.php` |
| L | spacing | Table horizontal inset differs. Demo wraps DataTable directly in Card with no extra padding; DataTable here is non-compact so cells/headers use px-4. Portal adds px-2 py-2 on the scroll container AND uses px-3 (compact-style) on every th/td. Net horizontal rhythm is tighter than the demo (px-3 vs px-4) with an extra px-2 gutter. | Remove the px-2 py-2 wrapper on the overflow container and bump cell/header padding from px-3 to px-4 to match the demo's non-compact DataTable (or intentionally keep compact px-3 but drop the outer px-2). | `views/promo-code/index.php` |
| L | structure | Page-header copy differs from the demo (classes already match PageHeader exactly). Demo title is 'Marketing' with subtitle '{commercialName} · promo codes & deals'; portal title is 'Promo codes List' with subtitle = shop title only. | Align copy to the demo: title 'Marketing' and append the '· promo codes & deals' descriptor to the subtitle (typography classes need no change). | `views/promo-code/index.php` |

**Plan:**
1. Fix the pill system first (highest visual impact): give the discount chip and all Aurora::badgeTone() status chips the demo Badge treatment — ring-1 ring-inset + matching ring token, text-*-700 (not -600), and px-2.5 py-0.5 instead of px-3 py-1. This aligns both the discount and status pills to the demo in one pass.
2. Retune the usage bar tokens to the demo Bar primitive: track bg-navy-900/8, fill bg-brand-600 (from slate-100 / brand-500).
3. Rework the usage cell composition to the demo layout: a justify-between text-xs text-ink-muted count/cap row above the bar (left-aligned column), demoting or removing the bold 'N / M' and 'Remaining Uses' lines.
4. Normalize table spacing to the demo: drop the outer px-2 py-2 gutter and use px-4 cell/header padding (demo non-compact DataTable), or deliberately keep compact px-3 without the extra gutter.
5. Match hover + delete affordances: set row hover to slate-50 (or none, since the demo rows are non-interactive) and set the delete icon idle color to text-slate-300 with hover:text-st-cancelled.
6. Optional copy alignment: rename the header title to 'Marketing' and extend the subtitle with '· promo codes & deals' to match ShopMarketing (classes already correct).


### Dashboard / Home (shop portal — frontend/views/site/index.php)
<a id="dashboard-home-shop-portal-frontend-views-site-index-php"></a>
**Parity ~42%** · 2H / 3M / 2L

Large structural gap: the portal is still the OLD finance Dashboard (booking-count + finance KPIs, costs donut, deep-link/action cards), while demo v0.19 replaced Home entirely with a schedule-hero layout (5-KPI band, today's-schedule timeline, AttentionFeed + BalancePanel right column, WeekAtAGlance/ThisWeek/RecentActivity/QuickActions bottom band). On the surfaces that DO overlap the type/spacing tokens are largely faithful, but the aurora banner gradient, KPI icon treatment, and several accent colors drift from the demo primitives.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | structure | Portal is the pre-v0.19 finance Dashboard; demo Home.tsx is a completely different surface. Missing entirely: the 5-tile KPI band (today-bookings / expected-value / revenue-week / utilization-ring / team-on-shift), the ScheduleHero today-timeline card (per-booking time + specialist avatar + service + payment chip + tomorrow-preview footer), the right column (AttentionFeed + BalancePanel), and the 4-card bottom band (WeekAtAGlance bars / ThisWeek / RecentActivity / QuickActions). Portal instead shows booking-count KPIs, an upcoming-bookings table, a costs-to-date donut, store-rating, deep-link + action cards — none of which exist in the demo Home. | Track as the primary parity gap: re-port site/index.php to the demo home/ structure (ScheduleHero + RightColumn + BottomBand + 5-KPI band). This is a rebuild, not a class tweak; scope it as its own wave. The audit below covers only the overlapping elements that survive. | `views/site/index.php` |
| H | color | Subscription banner (line 114) uses bg-head-shop = linear-gradient(90deg,#3d2960,#594279,#7554a8,#28937b) — a 4-stop darker ramp ending in a muddy teal #28937b. Demo banner uses bg-aurora-h = linear-gradient(90deg,#594279 0%,#7554a8 50%,#2ebf91 100%) — 3 stops, brighter, ending in the true accent teal #2ebf91. The portal banner reads noticeably darker/greener than the demo. | Swap the banner class to the demo aurora-h ramp (add a bg-aurora-h utility = linear-gradient(90deg,#594279 0%,#7554a8 50%,#2ebf91 100%) with an RTL 270deg variant) or redefine head-shop to match; rebuild CSS. | `views/site/index.php` |
| M | color | Banner crown icon roundel (line 117) is bg-white/15 with a plain white crown (text-white). Demo uses the same bg-white/15 chip but the Crown is text-amberbrand (#e8a833), giving the banner a gold focal accent the portal drops. | Add text-amberbrand to the crown icon (keep the bg-white/15 chip). | `views/site/index.php` |
| M | color | KPI cards (lines 140-184) render flat tinted icon chips (e.g. bg-brand-600/10 text-brand-700, bg-accent-400/15) and a solid full-width top bar (h-1 bg-brand-600). Demo Home passes variant="gradientIcon": each icon roundel gets a diagonal self-gradient (purple 135deg #8360c3→#46345f, teal #5bd0a2→#1c8865, amber #f0bd63→#c9860f, etc.) with white icons, plus a faint linear-gradient(180deg, accent14, transparent 60%) card wash and a thin rounded-t accent bar. Portal cards read flatter and less premium than the demo band. | Give the four booking-count KPIs (and the two finance KPIs) gradientIcon-style roundels: inline linear-gradient(135deg,...) per tone with white icons, add the 180deg accent wash on the card, and use the rounded-t-2xl thin accent bar instead of the square h-1 full bar. | `views/site/index.php` |
| M | color | Banner CTA "Manage/upgrade" (line 132) is a solid white pill: bg-white text-brand-700 rounded-full shadow-sm. On the demo dark banner the equivalent action is a frosted glass button: bg-white/12 text-white ring-1 ring-inset ring-white/20 backdrop-blur-md rounded-xl (hover:bg-white/20). Portal's solid-white pill fights the aurora backdrop instead of sitting on it. | Change the banner CTA to the frosted variant (bg-white/12 text-white ring-1 ring-inset ring-white/20 backdrop-blur-md rounded-xl) to match the demo on-gradient button; note demo uses rounded-xl here, not the rounded-full pill. | `views/site/index.php` |
| L | color | Costs-to-date donut stroke (line 255) is hardcoded #7c3aed (violet) with a single full-circumference arc; legend dots use #594279/#2ebf91/#e8a833/#4aa6b5 hex literals. #7c3aed is not a Navagoo token (brand-600 is #594279). The demo Home has no costs donut, so there is no reference — but the violet arc clashes with the brand purple used elsewhere on the same page. | If the donut is kept during the interim, recolor the arc from #7c3aed to brand-600 #594279 (or segment it by the four cost-part token colors) so it stays on-palette; ideally this card is removed when Home is re-ported. | `views/site/index.php` |
| L | typography | Banner plan title (line 120) is h2 text-base font-bold; demo renders the plan name as p text-sm font-bold. Minor: portal heading is one step larger (16px vs 14px) than the demo's banner label. | Drop the banner title to text-sm to match the demo (font-bold stays); low priority since this block is slated for rebuild. | `views/site/index.php` |

**Plan:**
1. PRIMARY: scope a dedicated wave to re-port site/index.php to the demo v0.19 Home structure — 5-KPI band (today-bookings/expected/revenue-week/utilization-ring/team-on-shift), ScheduleHero today-timeline, AttentionFeed + BalancePanel right column, and the WeekAtAGlance/ThisWeek/RecentActivity/QuickActions bottom band. Everything below is interim polish on the current Dashboard.
2. Fix the subscription banner gradient: replace bg-head-shop (4-stop dark ramp ending #28937b) with the demo bg-aurora-h ramp linear-gradient(90deg,#594279 0%,#7554a8 50%,#2ebf91 100%) + RTL 270deg variant; rebuild tailwind.css.
3. Convert the banner CTA to the demo frosted glass button (bg-white/12 text-white ring-1 ring-inset ring-white/20 backdrop-blur-md rounded-xl) and color the crown icon text-amberbrand.
4. Upgrade the KPI cards to the demo gradientIcon treatment: diagonal self-gradient icon roundels (135deg per-tone, white icons), a faint 180deg accent card wash, and a thin rounded-t accent bar instead of the square full-width h-1 bar.
5. Recolor the interim costs-to-date donut arc from the off-palette #7c3aed to brand-600 #594279 (or segment by the four cost-part token colors) so it stops clashing with the brand purple.
6. After class changes, run npm run build:css and browser-verify the banner + KPI band against the demo at :5173 #/shop (both LTR and RTL).


### Chrome — Modals, Drawers, Toasts
<a id="chrome-modals-drawers-toasts"></a>
**Parity ~68%** · 2H / 3M / 2L

The modal shell (_tw_modal.php) is a near-perfect class-for-class match to demo Modal.tsx, but the JS-built primitives in aurora-core.js diverge on several dimensions: the toast is a pill at top-center vs the demo's white card at bottom-right, ngConfirm ignores the demo's shared Modal shell entirely (different radius/header/footer/button anatomy), and every backdrop uses bg-ink/50 vs the demo's bg-navy-950/45.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | structure | ngToast is a rounded-full pill (rounded-full px-4 py-2.5) with a solid brand/status fill and white text, mounted top-center (layout ng-toast-root: fixed inset-x-0 top-4 items-center). The demo Toaster (toast.tsx) is a WHITE card: rounded-2xl border border-slate-200 bg-white p-3.5 shadow-pop, a colored icon (text-accent-600 / text-brand-600 / text-[#b8860b]) plus text-sm font-bold text-ink title + text-xs text-ink-muted description + an X dismiss, positioned bottom-right (fixed bottom-5 end-5, w-[min(92vw,360px)]). Completely different visual language, palette, shape, and screen corner. | Rebuild ngToast to match toast.tsx: white card 'pointer-events-auto flex items-start gap-3 rounded-2xl border border-slate-200 bg-white p-3.5 shadow-pop', colored lucide icon (success text-accent-600, info text-brand-600, warning text-[#b8860b]), title 'text-sm font-bold text-ink' + optional description 'text-xs text-ink-muted', X dismiss button. Move ng-toast-root in tailwind.php from 'fixed inset-x-0 top-4 ... items-center' to 'fixed bottom-5 end-5 z-[80] ... w-[min(92vw,360px)] items-stretch'. Then npm run build:css. | `web/js/aurora-core.js` |
| H | structure | ngConfirm hand-rolls its own dialog instead of reusing the modal shell, so its anatomy diverges from the demo's confirm.tsx (which renders <Modal size='sm'> + <Button>). Portal shows a leading icon badge (grid size-10 rounded-full bg-brand-50) beside the title and pill buttons (rounded-full); the demo confirm has NO icon badge, a plain header (title only), a bordered/tinted footer (border-t border-slate-200 bg-slate-50/60), and rounded-xl pill-less buttons from Button.tsx. Extra icon-badge block + wrong button/footer treatment change the visual result. | Restructure ngConfirm to mirror confirm.tsx via the modal shell: drop the leading icon badge, render title as header 'text-lg font-bold tracking-tight text-ink', body as 'text-sm text-ink-soft', and a footer with border-t border-slate-200 bg-slate-50/60 holding a secondary Button (bg-white text-ink ring-1 ring-inset ring-slate-200) + primary/danger Button (rounded-xl, not rounded-full). | `web/js/aurora-core.js` |
| M | color | Every portal overlay backdrop uses 'bg-ink/50 backdrop-blur-[2px]' (ink #1f1629 at 50%). The demo backdrops use 'bg-navy-950/45' (navy-950 #160f20 at 45%) for Modal/Drawer/confirm. Both hue (#1f1629 vs #160f20) and opacity (50% vs 45%) differ — the portal scrim is lighter-hued and slightly darker. Affects _tw_modal.php line 45, ngConfirm line 162, ngIframeModal line 255. | Add a navy-950 (#160f20) color to tailwind.config.js and swap all backdrop classes to 'bg-navy-950/45 backdrop-blur-[2px]' across _tw_modal.php, ngConfirm, and ngIframeModal; rebuild CSS. | `web/js/aurora-core.js` |
| M | color | ngConfirm buttons diverge from demo Button.tsx tokens: portal OK button 'bg-brand-600 hover:bg-brand-700' with 'hover:-translate-y-px' and no shadow ring; demo primary Button adds shadow-[0_6px_18px_rgba(89,66,121,0.22)] and focus-visible:ring-brand-400. Portal cancel is a bare ghost 'text-ink-soft hover:bg-slate-100'; demo secondary is 'bg-white text-ink ring-1 ring-inset ring-slate-200 hover:bg-brand-50'. Portal danger 'bg-st-cancelled hover:brightness-95'; demo danger adds shadow-[0_6px_18px_rgba(220,87,87,0.22)] hover:brightness-105. | Align ngConfirm button classes to Button.tsx: primary = rounded-xl bg-brand-600 text-white shadow-[0_6px_18px_rgba(89,66,121,0.22)] hover:bg-brand-700; secondary(cancel) = rounded-xl bg-white text-ink ring-1 ring-inset ring-slate-200 hover:bg-brand-50; danger = shadow-[0_6px_18px_rgba(220,87,87,0.22)] hover:brightness-105. Drop rounded-full. | `web/js/aurora-core.js` |
| M | structure | ngDrawer panel background is set per-page in view markup (.drawer-panel) rather than by the controller, but the demo Drawer.tsx pins a specific chrome: panel 'bg-page shadow-pop', header 'border-b border-slate-200 bg-white px-6 py-4', body 'px-6 py-5', footer 'border-t border-slate-200 bg-white px-6 py-4'. aurora-core.js only styles transform/opacity and leaves surface/padding to each caller, so drawers can drift from the demo's bg-page body + white header/footer split unless every view repeats it. | Document/enforce the demo drawer chrome in the DRAWER CONTRACT comment (panel bg-page shadow-pop; header border-b border-slate-200 bg-white px-6 py-4; body px-6 py-5; footer border-t border-slate-200 bg-white px-6 py-4) or provide a _tw_drawer.php partial mirroring _tw_modal.php so callers can't diverge. | `web/js/aurora-core.js` |
| L | color | ngToast warning fill uses 'bg-amberbrand' (#e8a833) as a solid background with white text; the demo warning tone uses icon color 'text-[#b8860b]' on a white card (no amber fill). Even after the card rework, the warning accent hue should be #b8860b to match, not amberbrand. Success maps portal bg-accent-600 vs demo text-accent-600 (same token, different application) and info portal bg-ink vs demo text-brand-600. | In the reworked white-card ngToast, use icon colors success=text-accent-600, info=text-brand-600, warning=text-[#b8860b] (matching TONES in toast.tsx) instead of solid palette fills. | `web/js/aurora-core.js` |
| L | typography | ngConfirm title is 'text-base font-bold text-ink' (line 167) whereas the demo confirm renders through Modal.tsx whose header title is 'text-lg font-bold tracking-tight text-ink'. Portal is one step smaller (text-base vs text-lg) and drops tracking-tight. | Change ngConfirm title class to 'text-lg font-bold tracking-tight text-ink' to match the shared Modal header used by confirm.tsx. | `web/js/aurora-core.js` |

**Plan:**
1. Rebuild ngToast as a white card (rounded-2xl border border-slate-200 bg-white p-3.5 shadow-pop) with colored icon + text-sm font-bold title + text-xs text-ink-muted description + X dismiss, and move ng-toast-root in layouts/tailwind.php from top-center pill placement to 'fixed bottom-5 end-5 z-[80] w-[min(92vw,360px)]' to match toast.tsx.
2. Restructure ngConfirm to mirror confirm.tsx: remove the leading icon badge, use the modal-shell header (text-lg font-bold tracking-tight) + bordered/tinted footer (border-t border-slate-200 bg-slate-50/60), and swap pill buttons for rounded-xl Button.tsx variants (secondary cancel, primary/danger OK with the demo shadow rings).
3. Introduce a navy-950 (#160f20) token and replace 'bg-ink/50' with 'bg-navy-950/45' on all three backdrops (_tw_modal.php line 45, ngConfirm line 162, ngIframeModal line 255).
4. Align ngToast warning/success/info accents to the demo TONES icon colors (text-accent-600 / text-brand-600 / text-[#b8860b]).
5. Codify the demo drawer chrome (panel bg-page shadow-pop; white header/footer with border-slate-200; px-6 py-4/py-5 rhythm) in the DRAWER CONTRACT comment or a shared _tw_drawer.php partial so drawer callers stay faithful.
6. Run npm run build:css after the class changes so the new toast/confirm/backdrop utilities land in the minified bundle.


### Chrome — Navbar/TopBar (frontend/views/layouts/_tw_navbar.php ⇄ Navagoo_MI TopBar.tsx, shop variant)
<a id="chrome-navbar-topbar-frontend-views-layouts-tw-navbar-php-navagoo-mi-topbar-tsx-shop-variant"></a>
**Parity ~80%** · 2H / 2M / 3L

Structurally faithful — header height, right-alignment, clock, bell dropdown, and account menu all match the demo's shop variant, and admin-only chrome (identity switcher, what's-new, how-to, outbox, EnvBadge) is correctly omitted. The gaps are class-level: the account avatar is the wrong shape/size/fill, focus-visible rings and transition-colors are missing on every control, and the notification badge and account-dropdown metrics drift a few utilities off the demo.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | structure | Account trigger avatar (L122): portal renders a square white tile `grid size-8 place-items-center rounded-lg bg-white/15 text-xs font-bold text-white`; demo uses `<Avatar size="sm">` = `inline-grid size-7 rounded-full text-[10px] font-bold` with an oklch name-hashed tinted background. Shape (rounded-lg vs rounded-full), size (size-8 vs size-7), text (text-xs vs text-[10px]) and fill (solid white/15 vs colored initials) all differ. The account-dropdown header (L127) also omits the `size-9` md Avatar the demo shows beside the name. | Match the demo Avatar: `rounded-full size-7 text-[10px]` on the trigger tile, and add a `size-9 rounded-full` avatar to the dropdown header row. If a tinted background is out of scope, at minimum switch to rounded-full + size-7 + text-[10px] to match geometry. | `views/layouts/_tw_navbar.php` |
| H | color | No focus-visible rings or transition-colors on any control. Demo gives every button `outline-none transition-colors ... focus-visible:ring-2 focus-visible:ring-white/40` (amber controls use `focus-visible:ring-amber-400/50`). Portal buttons (flask L58, bell L74, globe L118, account L121) have hover states only. This is the class-level form of the missing shadow-focus token noted in the reference. | Add `outline-none transition-colors focus-visible:ring-2 focus-visible:ring-white/40` to the white controls (bell, globe, account trigger) and `focus-visible:ring-amber-400/50` to the amber flask button, mirroring the demo. | `views/layouts/_tw_navbar.php` |
| M | structure | Notification badge drift (L79): portal uses `min-w-[18px] ... px-1 py-px text-[9px] ... ring-2 ring-brand-800`; demo uses `h-4 min-w-4 ... px-1 text-[10px]` with NO ring. Portal's text is a step smaller (9px vs 10px), the sizing model differs (min-w-[18px]+py-px vs h-4 min-w-4), and it adds a brand-800 ring the demo does not have. | Align to demo: `absolute -end-0.5 -top-0.5 grid h-4 min-w-4 place-items-center rounded-full bg-accent-500 px-1 text-[10px] font-bold text-white` and drop `ring-2 ring-brand-800`. | `views/layouts/_tw_navbar.php` |
| M | spacing | Account dropdown metrics (L126-127): portal panel is `w-56` with header `px-4 py-3` + `border-b border-slate-100`; demo is `w-60` with an avatar+text header row at `px-3 py-2.5` followed by a `my-1 border-t border-slate-100` divider. Width and header padding differ. | Set panel width to `w-60`, header padding to `px-3 py-2.5`, and use a `my-1 border-t` divider (with the avatar row) instead of `px-4 py-3 border-b`. | `views/layouts/_tw_navbar.php` |
| L | typography | Account trigger name label (L123) lacks the `max-w-32` truncation cap the demo applies (`hidden max-w-32 truncate text-sm font-semibold text-white md:inline`). Portal omits `max-w-32`, so a long shop name won't clip at the same width. | Add `max-w-32` to the trigger name span so long names truncate at the demo's width. | `views/layouts/_tw_navbar.php` |
| L | structure | First divider span (L62) `<span class="h-5 w-px bg-white/20">` is always visible; the demo's first divider is `hidden ... sm:block` so it hides on mobile alongside the clock (which is `sm:flex` in both). On narrow viewports the portal shows a stray divider next to a hidden clock. | Add `hidden sm:block` to the first divider span (L62) to match the demo. | `views/layouts/_tw_navbar.php` |
| L | structure | Non-demo clock (L50) shows date only (`EEE d MMM`); the demo LiveClock always renders `{date} · {time}` with a running H:M:S. Visually the portal's standard (non-demo) navbar is missing the ` · HH:MM` segment the demo always shows. (Partly behavioral, but it changes the rendered clock string.) | If matching the demo clock is in scope, append a live ` · HH:MM` to the non-demo branch; otherwise accept as an intentional simplification. | `views/layouts/_tw_navbar.php` |

**Plan:**
1. Replace the account avatar tile with the demo Avatar geometry: `rounded-full size-7 text-[10px]` on the trigger (L122) and add a `size-9 rounded-full` avatar to the dropdown header row (L127) — highest visual impact.
2. Add `outline-none transition-colors focus-visible:ring-2 focus-visible:ring-white/40` to the white controls (bell L74, globe L118, account L121) and `focus-visible:ring-amber-400/50` to the amber flask (L58); this restores the missing focus affordance across the bar.
3. Normalize the notification badge to the demo: `h-4 min-w-4 ... px-1 text-[10px]` and remove `ring-2 ring-brand-800` (L79).
4. Widen the account dropdown to `w-60` and set its header to `px-3 py-2.5` with a `my-1 border-t` divider instead of `px-4 py-3 border-b` (L126-127).
5. Add `max-w-32` to the account trigger name label (L123).
6. Add `hidden sm:block` to the first divider span (L62) so it hides with the clock on mobile.
7. Optional: append a live ` · HH:MM` to the non-demo clock branch (L50) if full clock parity with the demo LiveClock is desired.
8. After edits, run `npm run build:css` so the new focus/ring/size utilities land in the minified bundle.


### Team / Agents (Specialists · Payroll · Structure tabs)
<a id="team-agents-specialists-payroll-structure-tabs"></a>
**Parity ~82%** · 2H / 3M / 2L

Strong parity on the shared aurora idioms — page header, cards, org-chart, and colors mostly match the demo. The Specialists tab is an intentional card-grid re-imagining of the demo's DataTable (a documented design diff, not a defect). The real, fixable gaps are the segmented tab/orientation control shape (rounded-full vs demo's rounded-xl) and active-label color, the wage-badge tone map being semantically inverted vs the demo's WAGE_TONE, and a few weight/tracking/padding drifts in the Payroll table.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | color | Wage-type badge tones are inverted vs the demo's WAGE_TONE (~L141 $wageMap). Demo Team.tsx L46-50: both=purple, commission=teal, fixed=slate. Portal maps fixed->'pending' (brand/purple), commission->'success' (teal), both->'neutral' (slate). So 'Fixed' renders purple and 'Fixed + Commission' renders slate — the exact opposite of the demo for those two tones. | Swap tones to match WAGE_TONE: 'fixed' => Aurora::badgeTone('neutral') (slate), 'both' => Aurora::badgeTone('pending') (brand/purple); leave 'commission' => badgeTone('success') (teal). | `views/agents/index.php` |
| H | structure | The tab segmented control shape differs from the demo Segmented primitive (~L56). Portal container 'rounded-full bg-slate-100 p-0.5' with pills 'rounded-full px-4 py-1.5'; demo Segmented (Field.tsx L131-152) uses 'rounded-xl bg-slate-100 p-1 gap-0.5' with pills 'rounded-lg px-3 py-1.5'. Pill radius (full vs lg) and container radius (full vs xl) make it read as a different control. Same mismatch on the Structure orientation toggle in _structure.php (~L97). | Match the demo: container 'rounded-xl ... p-1 gap-0.5', pills 'rounded-lg px-3 py-1.5' in index.php; apply the identical change to the _structure.php orientation toggle. | `views/agents/index.php` |
| M | color | Active tab label color differs (~L58). Portal active pill uses 'bg-white text-ink shadow-xs'; demo Segmented active state is 'bg-white text-brand-700 shadow-xs' (Field.tsx L144). Demo tints the active label brand-700 (purple); portal keeps it near-black ink. Same delta on the Structure orientation toggle active pill (_structure.php ~L99) and its toggle JS (~L762-765 sets text-ink). | Change active pill text 'text-ink' -> 'text-brand-700' in index.php and _structure.php, and update the aurora-tabs / orientation JS that toggles 'text-ink' to toggle 'text-brand-700'. | `views/agents/index.php` |
| M | typography | Payroll table header cells (~L34) use 'text-[11px] font-semibold uppercase tracking-wide'; the demo DataTable th (Table.tsx L56) is 'text-[11px] font-bold uppercase tracking-[0.08em]'. Weight (semibold vs bold) and letter-spacing (0.025em vs 0.08em) both differ, so portal headers read lighter and tighter than the demo. | Change the thead header row classes to 'font-bold ... tracking-[0.08em]' (keep text-[11px] uppercase text-ink-muted). | `views/agents/_payroll.php` |
| M | typography | Payroll card-header title (~L19) is a bare '<p class="font-bold text-ink">' with no size or tracking, rendering at 16px base. The demo CardHeader title (Card.tsx L45) is 'text-[15px] font-bold tracking-tight'. Note view.php ratings header (L59) already uses the correct 'text-[15px] font-bold tracking-tight', so payroll is the inconsistent one within this surface. | Set the 'Monthly Payroll' title to 'text-[15px] font-bold tracking-tight text-ink' to match CardHeader and the ratings-card header. | `views/agents/_payroll.php` |
| L | spacing | All specialist-card chips (status, MOL, Mostaqqel, tips, wage, salary, commission — ~L187-228) use 'rounded-full px-2.5 py-1'; the demo Badge PILL (Badge.tsx L54) is 'rounded-full px-2.5 py-0.5'. The extra vertical padding (py-1 vs py-0.5) makes every pill ~4px taller than the demo's badge. | Change chip vertical padding py-1 -> py-0.5 across the chip spans to match the demo Badge height. | `views/agents/index.php` |
| L | typography | The Structure orientation Segmented (~L97) uses 'text-xs font-semibold', but the demo renders <Segmented> at its default (md) size which is 'text-sm' (Field.tsx L135). Portal labels ('Top-down' / 'Left-right') render one step smaller than the demo. | Change 'text-xs' -> 'text-sm' on the orientation control container (keeping px-3 py-1.5 consistent with the tab-control fix). | `views/agents/_structure.php` |

**Plan:**
1. Fix the wage-badge tone inversion in index.php $wageMap: fixed->neutral(slate), both->pending(brand), commission stays success(teal) — highest-signal color correction, matches demo WAGE_TONE.
2. Reshape both segmented controls to the demo Segmented primitive: container rounded-xl + p-1 + gap-0.5, pills rounded-lg px-3 py-1.5 — in index.php (tabs) and _structure.php (orientation toggle).
3. Recolor the active pill label to text-brand-700 (from text-ink) in both index.php and _structure.php, including the aurora-tabs JS and the _structure.php orientation toggle handler that swaps text-ink.
4. Bring the Payroll table header row to demo Table parity: font-bold + tracking-[0.08em] (from font-semibold + tracking-wide).
5. Give the Payroll card-header title text-[15px] tracking-tight to match CardHeader (already correct on the ratings card in view.php).
6. Trim chip vertical padding py-1 -> py-0.5 across the specialist-card pills to match Badge height.
7. Optional polish: bump the Structure orientation labels text-xs -> text-sm to match the demo's default Segmented size.
8. Rebuild the Tailwind bundle (npm run build:css) after the edits — changed classes (rounded-xl, tracking-[0.08em], text-brand-700 in JS toggles) must be present in the minified output.


### Booking detail + booking modals (frontend/views/booking-calendar/_detail.php, _new_booking.php, _group_booking.php vs demo BookingDetailBody.tsx, BookingTimeline.tsx, modals.tsx)
<a id="booking-detail-booking-modals-frontend-views-booking-calendar-detail-php-new-booking-php-group-booking-php-vs-demo-bookingdetailbody-tsx-bookingtimeline-tsx-modals-tsx"></a>
**Parity ~90%** · 2H / 3M / 2L

High-fidelity ports: the 3-column detail grid, status/collection/settlement strip, timeline node treatment, DataRow/pill styling, and the picker/pay-now sub-screens all match the demo closely (~90% parity). Remaining gaps are the detail action-button row (sizing + center-gutter alignment), the timeline "reached" connector color, and heavier-than-demo header typography on the New-booking/Group modals.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | structure | Detail action-button row uses plain `grid grid-cols-2 ... gap-2.5` (line 305), so each button stretches full-width across its column. Demo BookingDetailBody (line 226) adds `gap-x-2.5 gap-y-2.5` PLUS `[&>*:nth-child(odd)]:justify-self-end [&>*:nth-child(even)]:justify-self-start`, so pill buttons hug the center gutter at natural width instead of stretching — a clearly different visual result. | Change the wrapper to `mt-5 grid grid-cols-2 items-center gap-x-2.5 gap-y-2.5 border-t border-slate-200 pt-4 [&>*:nth-child(odd)]:justify-self-end [&>*:nth-child(even)]:justify-self-start` and drop the `w-full`-implying full-stretch (buttons already inline-flex, so they will size to content). | `views/booking-calendar/_detail.php` |
| H | color | Happy-path timeline connector (line 278) is ALWAYS `border-l border-dashed border-slate-300` for non-last steps. Demo BookingTimeline (lines 152-155) renders a SOLID `bg-brand-300` line when the next node is reached (`nextReached`) and only falls back to dashed slate for still-future segments. For an in-progress/completed booking the portal shows grey dashes where the demo shows a solid brand rail. | Compute whether the next step is reached (`$i + 1 <= $curIdx`) and emit `absolute left-[13.5px] top-7 bottom-1 w-px bg-brand-300` for reached segments, keeping `border-l border-dashed border-slate-300` only for future ones (mirror the terminal branch, which already uses a solid color-mix line). | `views/booking-calendar/_detail.php` |
| M | typography | Modal header uses `text-xl font-extrabold tracking-tight` (line 136); sub-screen titles use `text-lg font-extrabold` (lines 307, 378). The demo renders these through the Modal primitive whose title is `text-lg font-bold tracking-tight` (Modal.tsx line 63). Portal titles are one size/weight step heavier than every demo modal. | Change the New-booking `<h1>` to `text-lg font-bold tracking-tight text-ink` and the picker/pay-now `<h2>` titles from `font-extrabold` to `font-bold` to match the demo Modal chrome. | `views/booking-calendar/_new_booking.php` |
| M | typography | Group-booking header `<h1>` is `text-xl font-extrabold tracking-tight` (line 123) and the picker/pay-now `<h2>` titles are `text-lg font-extrabold` (lines 306, 377). Demo NewGroupBookingModal title comes from the Modal primitive = `text-lg font-bold` (Modal.tsx line 63). | Set the header to `text-lg font-bold tracking-tight text-ink` and switch the sub-screen `<h2>` titles from `font-extrabold` to `font-bold` for parity with the demo Modal. | `views/booking-calendar/_group_booking.php` |
| M | spacing | Detail status/reschedule/cancel action buttons use `px-4 py-1.5` (btn recipe, line 307). Demo status buttons use `px-3.5 py-1.5` (BookingDetailBody line 240) and its Reschedule/Cancel are `Button size="sm"` = `h-9 px-4 text-[13px]` (Button.tsx line 27). Portal pads slightly wider and renders all five buttons at a uniform `text-sm px-4`, losing the demo's tighter `px-3.5` status pills and the `text-[13px]` secondary/danger sizing. | Change the shared `$btn` recipe padding to `px-3.5 py-1.5`; optionally give the Reschedule/Cancel buttons `text-[13px]` to match the demo's sm Button, though `text-sm` is close enough if minimizing churn. | `views/booking-calendar/_detail.php` |
| L | color | The 'Start service' action button background is `var(--color-accent-500,#25a47b)` (line 310). Demo uses `var(--color-accent-400)` for the Start action (BookingDetailBody line 242). Portal's Start pill is one accent shade darker than the demo. | Change the Start-service button style to `background:var(--color-accent-400)` to match the demo's decoupled Start action hue. | `views/booking-calendar/_detail.php` |
| L | color | Walk-in pay-now method cards apply the active state `border-brand-400 bg-brand-50 text-brand-700` (lines 861-863). Demo MethodButton (modals.tsx line 891) uses `border-brand-500 bg-brand-50 text-brand-700` for the active card. Portal's selected card border is one brand step lighter than the demo (the _detail.php collect overlay already uses the correct `has-[:checked]:border-brand-500`). | Toggle `border-brand-500` (not `border-brand-400`) for the active pay-method card in booking-new.js so it matches the demo MethodButton and the _detail.php collect overlay. | `web/js/booking-new.js` |

**Plan:**
1. Fix the detail action-button row layout (highest visual impact): add the demo's `[&>*:nth-child(odd)]:justify-self-end [&>*:nth-child(even)]:justify-self-start` + `gap-x-2.5 gap-y-2.5` so pill buttons hug the center gutter at natural width instead of full-stretching, and tighten the `$btn` recipe padding from `px-4` to `px-3.5`.
2. Make the happy-path timeline connector solid `bg-brand-300` when the next node is reached (compute `$i + 1 <= $curIdx`), keeping dashed slate only for future segments — matches BookingTimeline's `nextReached` branch.
3. Downgrade modal-header typography in _new_booking.php and _group_booking.php from `text-xl font-extrabold` to `text-lg font-bold` on the `<h1>`, and picker/pay-now `<h2>` from `font-extrabold` to `font-bold`, to match the demo Modal primitive chrome.
4. Fix the two accent/brand shade mismatches: Start-service button to `accent-400` (from accent-500) in _detail.php, and the walk-in pay-method active card to `border-brand-500` (from brand-400) in booking-new.js.
5. Run `npm run build:css` after the class edits (JS class change in booking-new.js must be picked up by the Tailwind build) and browser-verify the detail modal + New-booking/Group modals against the demo at :5173.


### Chrome — Sidebar
<a id="chrome-sidebar"></a>
**Parity ~82%** · 2H / 1M / 3L

Strong parity on the rail shell, brand band, section headers, and nav-link states (active/hover, sizes, colors, spacing all match class-for-class). Two real divergences drag it down: Navagoo Plans + Settings live in a scrolling collapsible section instead of the footer, and the header chip's avatar square is the wrong size/shape/fill vs the demo's colored ShopTile. Focus-visible rings present throughout the demo are entirely missing in the portal.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | structure | Navagoo Plans and Settings are rendered as ordinary items inside the 'Payments' collapsible <details> section in the scrolling nav (lines 53-54, 145-161). In the demo (Sidebar.tsx lines 46-49, 172-193) both are pulled OUT of the nav list and pinned in the FOOTER — Plans on its own row on top, then Settings on the left with the collapse chevron on the right. The portal footer (lines 165-167) contains only the collapse toggle. This changes where two primary links appear on screen. | Filter 'navagoo-plans' and 'settings' out of $sections so they don't render in <nav>, and render them in the footer: Plans as a full-width nav link on top, then a flex row with Settings (min-w-0 flex-1) on the left and the #tw-collapse-btn on the right — mirroring Sidebar.tsx's expanded footer block (space-y-0.5 wrapper; inner div flex items-center gap-1). | `views/layouts/_tw_sidebar.php` |
| H | color | The interactive focus state is missing across every sidebar control. Demo nav links use 'outline-none focus-visible:ring-2 focus-visible:ring-white/40' (Sidebar.tsx line 61); the section-header button uses 'focus-visible:ring-2 focus-visible:ring-white/30' (line 146); the collapse toggle uses 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40' (line 91). The portal nav link (line 100), <summary> (line 151) and #tw-collapse-btn (line 166) have NO focus-visible ring, so keyboard focus is invisible. | Add 'outline-none focus-visible:ring-2 focus-visible:ring-white/40' to the tw-navlink class (line 100) and to the collapse button (line 166); add 'outline-none focus-visible:ring-2 focus-visible:ring-white/30' to the tw-sec-sum <summary> (line 151). Rebuild with npm run build:css. | `views/layouts/_tw_sidebar.php` |
| M | structure | The header-chip avatar square diverges from the demo's ShopTile (md). Portal (line 138) uses 'size-9 rounded-lg bg-white/15 text-sm font-bold' = 36px, 8px radius, flat translucent-white fill. Demo ShopTile md (Avatar.tsx lines 69, 75) is 'size-10 text-sm rounded-xl' = 40px, 14px radius, with a per-shop colored linear-gradient(135deg, oklch(...)) fill and font-bold text-white. Portal square is smaller, tighter-cornered, and monochrome instead of the demo's colored tile. | Change the chip square to size-10 rounded-xl to match ShopTile md. If a per-shop hue is available, apply the same 135deg oklch gradient; otherwise keep a brand-toned fill rather than bg-white/15 so it reads as a colored tile, not a flat chip. | `views/layouts/_tw_sidebar.php` |
| L | color | Nav-link icons lack the demo's color transition. Demo icon (Sidebar.tsx lines 72-75) carries 'transition-colors' alongside the white/55→white hover swap; the portal icon <i> (line 96) sets the same color classes ('text-white/55 group-hover/nav:text-white') but omits transition-colors, so the icon color snaps instead of easing on hover like the label does. | Add 'transition-colors' to the icon class string built at line 96 (the $ic variable / <i> class) so the icon eases in step with the link's transition-colors. | `views/layouts/_tw_sidebar.php` |
| L | structure | Collapse-toggle button geometry differs slightly. Demo toggle (Sidebar.tsx lines 91, 94) uses 'p-1.5' padding (icon-sized hit area) with ChevronsLeft strokeWidth={2.75}. Portal (line 166) uses a fixed 'grid size-8 place-items-center' box with the default Lucide stroke weight, giving a marginally different button footprint and a lighter chevron. | Optional polish: swap 'size-8 place-items-center' for 'p-1.5' to match the demo's intrinsic sizing, and thicken the chevron (Lucide stroke-width ~2.75, e.g. add stroke-width via the icon) for visual parity. | `views/layouts/_tw_sidebar.php` |
| L | structure | Chip subtitle omits the city. Demo header (ShopLayout.tsx line 45) renders 'Shop Portal · {shop.city}'; the portal chip subtitle (line 141) renders only 'Shop Portal'. Minor text-content divergence (portal has no city bound into this partial). | If a city value is available on $shop, append ' · <city>' to the subtitle to match the demo; otherwise acceptable to leave as-is (translation-safe). | `views/layouts/_tw_sidebar.php` |

**Plan:**
1. Move Navagoo Plans + Settings out of the scrolling <nav> and into the footer: filter both out of $sections, then render Plans as a top row and a Settings-left / collapse-toggle-right flex row below it, matching Sidebar.tsx's expanded footer layout.
2. Add the missing focus-visible rings: focus-visible:ring-2 ring-white/40 on nav links and the collapse button, ring-white/30 on the section-header <summary>, each with outline-none.
3. Fix the header-chip avatar square: size-9→size-10, rounded-lg→rounded-xl, and replace bg-white/15 with a colored tile (per-shop 135deg oklch gradient if a hue is available) to match ShopTile md.
4. Add transition-colors to the nav-link icon so its hover color eases in step with the label.
5. Polish the collapse toggle: p-1.5 instead of size-8 place-items-center, and thicken the chevron stroke (~2.75).
6. Optionally append ' · {city}' to the chip subtitle when a city is available.
7. Run npm run build:css after the class changes so the minified bundle reflects them.


### Agents-wallet — Transfer Requests (index.php) + Specialist Tips (specialist-tips.php)
<a id="agents-wallet-transfer-requests-index-php-specialist-tips-specialist-tips-php"></a>
**Parity ~88%** · 2H / 2M / 2L

Structurally near-perfect: both views hardcode the demo's exact Card / CardHeader / PageHeader / DataTable class strings, so shell, headers, th/td cells, and typography match the design system closely (~88% parity). The only real deltas are in the settlement status chip — the portal reuses generic Aurora TONES (wrong padding, no ring, and a purple tint the demo settlement palette never uses) instead of the demo Badge geometry — plus a couple of minor money-weight and id-color drifts.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | spacing | Settlement chip geometry differs from the demo Badge primitive. Portal renders via Aurora::statusPill = 'inline-block rounded-full px-3 py-1 text-xs font-semibold' with NO ring. Demo Badge PILL = 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold' + 'ring-1 ring-inset'. Every settlement chip on both tables is thus taller/wider and lacks the demo's tint ring. | Render settlement chips with a Badge that matches the demo primitive: px-2.5 py-0.5 (not px-3 py-1) and add ring-1 ring-inset with a tone-matched ring color. Either add a matching helper (e.g. Aurora::badge already uses gap-1 px-2.5 py-1 — align it to px-2.5 py-0.5 + ring) or pass an $extra ring class through statusPill. | `views/agents-wallet/index.php` |
| H | color | Settlement chip TONES don't map to the demo settlement palette. Portal maps NEW and IN_PROGRESS to tone 'pending' = 'bg-brand-50 text-brand-600' (PURPLE). The demo settlement/transfer badge family has NO purple: transfer.settled #25a47b (teal-green), transfer.requested #fbbf24 (amber), settlement.pending #64748b (slate), settlement.in_transfer #f59e0b (amber). Purple is reserved for booking status, so purple settlement chips read as the wrong semantic family. | Remap: NEW -> neutral/slate (matches settlement.pending #64748b), IN_PROGRESS -> warning/amber (matches settlement.in_transfer #f59e0b), REQUESTED -> warning/amber, SETTLED -> success/teal (matches transfer.settled). Drop tone 'pending' (purple) for settlement states entirely. Applies identically in specialist-tips.php (same $settlementChip map). | `views/agents-wallet/index.php` |
| M | typography | Net-amount emphasis weight is lighter than the demo. Portal 'Total Net Collectible Amount' cell uses 'font-semibold text-brand-700' (line 145); the demo net-payout column renders <Money className="font-bold" /> (Settlement.tsx col 'net', line 127). Same lighter weight on specialist-tips.php Total Tips cell (line 117, font-semibold). | Use font-bold (not font-semibold) on the primary net/total money cell to match the demo's <Money className="font-bold">. Keep text-brand-700 or drop the brand tint to match demo (demo net Money is default ink, not brand). | `views/agents-wallet/index.php` |
| M | color | id-mono link color is inconsistent with the demo and internally. Demo Settlement renders the booking/transfer id as 'id-mono font-semibold text-brand-700' (Settlement.tsx line 74). Portal index.php TR-id link uses text-accent-700 (line 138); specialist-tips.php uses text-accent-700 for Specialist ID (line 111) but text-brand-700 for the TR ID (line 120). The TR-id color is brand in one view, accent in the other. | Standardize the transfer-request id color to text-brand-700 across both views to match the demo's id-mono token usage. Keep a single distinct accent color for the specialist-id if a second identity color is intentional, but make the TR id consistently brand. | `views/agents-wallet/specialist-tips.php` |
| L | structure | Portal table rows carry an always-on hover tint (hover:bg-brand-50 on every <tr>, index.php line 133 / specialist-tips.php line 108). The demo Settlement DataTable passes NO onRowClick, so in the demo Table primitive the hoverClassName never applies — its settlement rows have no hover affordance and rows aren't clickable. Portal adds a hover affordance the demo lacks. | Low priority. For strict parity, drop hover:bg-brand-50 from the non-interactive Specialist Tips rows (no row action). Transfer Requests rows do deep-link the id cell (not the whole row), so a subtle hover is defensible; if kept, prefer the demo's neutral hover:bg-slate-50 over brand-50 to avoid implying the whole row is a brand action. | `views/agents-wallet/index.php` |
| L | spacing | Card header padding vs demo CardHeader: portal index.php uses 'px-5 pt-5 pb-3' and adds items-end + a filter form on the right; demo CardHeader is 'flex items-start justify-between gap-3 px-5 pt-5 pb-3'. Portal uses items-end (line 70) vs demo items-start — the header title/subtitle block bottom-aligns to the filter controls instead of top-aligning. | Use items-start to match CardHeader alignment; let the filter form align via its own self-end if vertical centering with the taller controls is desired, rather than items-end on the whole header. | `views/agents-wallet/index.php` |

**Plan:**
1. Fix the settlement chip to match the demo Badge primitive: change geometry to px-2.5 py-0.5 and add ring-1 ring-inset with a tone-matched ring (single change in Aurora::statusPill or a dedicated settlement-badge helper — fixes both tables at once).
2. Remap settlement tones off purple: NEW->neutral/slate, IN_PROGRESS->warning/amber, REQUESTED->warning/amber, SETTLED->success/teal, so chips align with the demo settlement palette (#64748b / #f59e0b / #fbbf24 / #25a47b) instead of brand purple. Apply the same $settlementChip map edit in index.php and specialist-tips.php.
3. Bump the primary net/total money cell from font-semibold to font-bold (index.php line 145, specialist-tips.php line 117) to match demo <Money className="font-bold">.
4. Standardize the transfer-request id link/text to text-brand-700 in both views (currently accent-700 in index.php, brand-700 in specialist-tips.php) to match the demo id-mono token.
5. Optional low-priority polish: switch header alignment to items-start (index.php line 70) and reconsider the always-on hover:bg-brand-50 on non-clickable Specialist Tips rows (demo Settlement rows have no hover).
6. Run npm run build:css after any class change so the minified Tailwind bundle picks up new utilities (e.g. any ring-* on the chip).


### Customers + Customer Invitations
<a id="customers-customer-invitations"></a>
**Parity ~72%** · 1H / 4M / 2L

The portal is broadly on-token and structurally recognizable, but it is a deliberate real-feature ENRICHMENT of the demo rather than a class-for-class port — several shared primitives (segmented switcher, avatar, classification badge, grace banner) are re-implemented with a different look. The single most visible divergence is the tab/segmented control: the demo uses a subtle grey-track / white-thumb toggle, while the portal renders a saturated solid-purple active pill on a white card. Customer-Invitations has no demo counterpart and only needs to stay consistent with aurora conventions (it does).

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | color | Segmented switcher does not match the demo Segmented primitive. Demo (ui/Field.tsx Segmented) is a compact grey track: `inline-flex items-center gap-0.5 rounded-xl bg-slate-100 p-1`, active thumb `bg-white text-brand-700 shadow-xs`, inactive `text-ink-muted hover:text-ink`. Portal uses `rounded-2xl bg-white p-1 shadow-card ring-1 ring-slate-900/5` with a SOLID `#594279` active pill + white text (see <style> .ng-tab-btn). This reads as a heavy card with a saturated purple pill instead of a light toggle — the biggest at-a-glance difference on the page. | Re-skin the tab bar to the demo Segmented: container `inline-flex items-center gap-0.5 rounded-xl bg-slate-100 p-1` (drop shadow-card + ring + rounded-2xl); active button `bg-white text-brand-700 shadow-xs` (not solid #594279/white); inactive `text-ink-muted hover:text-ink`. Update the .ng-tab-btn CSS block accordingly and adjust the active .ng-count tint (demo has no count-recolor). | `views/customers/index.php` |
| M | color | Directory/Classifications avatar uses a uniform brand tint `bg-brand-50 text-[11px] font-bold text-brand-700` at `size-8`. Demo Avatar (ui/Avatar.tsx, size="sm") is `size-7 text-[10px]` with a per-name oklch hue-hashed background/foreground (colorful, one hue per customer). Portal loses the multi-hue avatar identity and is one step larger/heavier. | Match demo Avatar size sm: `size-7` + `text-[10px]`. If feasible, replace the flat bg-brand-50 with the demo's deterministic oklch hue tint (hash the name → `oklch(0.92 0.045 h)` bg / `oklch(0.42 0.13 h)` text). If a per-name hue is out of scope, at minimum drop to size-7/text-[10px] to match footprint. | `views/customers/index.php` |
| M | color | ClassificationBadge diverges from demo. Demo (ui/Badge.tsx ClassificationBadge) renders NO icon, labels `Navagoo customer` / `Shop customer`, via admin-configurable colorVar tint. Portal `$classBadge` hardcodes `bg-brand-50 text-brand-700` + a `sparkles` icon and `bg-accent-50 text-accent-600` + a `store` icon, labels `Navagoo-sourced` / `Shop-owned`. The added icons and `text-accent-600` (demo teal tone is `text-accent-700`) change the pill look. | For visual parity, drop the sparkles/store icons from the classification pills and use `text-accent-700` (not accent-600) for the shop-owned tone to match the demo Badge teal tone. Keep the brand-50/accent-50 backgrounds. (Labels are a copy decision — leave if intentional.) | `views/customers/index.php` |
| M | spacing | Grace-window banner is heavier than the demo. Demo (Customers.tsx FreezeList) banner = `flex items-start gap-2 rounded-2xl px-4 py-3 text-sm` with `bg-accent-50 text-accent-700` (in grace) or `bg-amberbrand/12 text-[#8a6200]` (ended), a single ShieldAlert + one line of text, and NO ring. Portal uses `p-4 ring-1 ring-inset` (ring-accent-100 / ring-amber-200/60), a bold title line PLUS a body line, and `size-5` icon. Extra ring + bold heading + larger padding make it a bigger block. | Align to demo: use `px-4 py-3` (not p-4), remove the `ring-1 ring-inset` rings, use `gap-2` and `size-4` icon, and collapse to a single text line (or keep the two lines but drop the ring + bold-title emphasis). For the ended state use `bg-amberbrand/12 text-[#8a6200]` to match demo instead of `bg-[#fdf3dc] text-amberbrand`. | `views/customers/index.php` |
| M | structure | BookingLinkCard header/layout differs from demo. Demo header row = `Link2 size-4 text-brand-600` icon + `h3 text-sm font-bold text-ink` ("Your booking link"); the deep link sits in a single `rounded-xl bg-slate-50 px-3 py-2` row with one inline secondary Copy button; layout is `flex ... sm:items-center` (vertically centered). Portal drops the Link2 icon, uses `text-[15px]` heading, `sm:items-start`, and a 3.5rem-tall QR at `size-28` vs demo 96px (~size-24). The action set is intentionally expanded (WhatsApp primary + SMS) which is a real-feature add, but the header treatment and QR size drifted. | Add the `Link2 size-4 text-brand-600` icon before the "Your booking link" heading and set the heading to `text-sm font-bold` (demo) instead of `text-[15px]`. Reduce QR tile to `size-24` and align the flex to `sm:items-center` to match the demo card rhythm. Keep the extra WhatsApp/SMS buttons (real feature) but style the primary as the demo Button primary `bg-brand-600 rounded-full`. | `views/customers/index.php` |
| L | structure | Classifications table column set differs from the demo. Demo columns: Customer, Mobile, Classification(center), Reason (`<Badge tone="slate">titleCase</Badge>`), Set on, and a trailing Overridden (`<Badge tone="amber">`). Portal renders Reason as a plain-text "Source" cell (`text-ink-soft`, no badge) and keeps Set on + an Overridden amber pill. The demo shows the reason as a slate Badge, not bare text. | Render the Source/Reason value as a demo slate Badge (`bg-slate-100 text-ink-soft ring-1 ring-inset ring-slate-300/50 rounded-full px-2.5 py-0.5 text-xs font-semibold`) instead of bare `text-ink-soft` text, to match the demo Reason column. Header rename Source→Reason is optional (copy). | `views/customers/index.php` |
| L | color | Freeze-list remove control differs from the demo affordance. Demo (Customers.tsx FreezeList) remove is a bare Trash2 icon button `text-slate-300 hover:text-st-cancelled` (icon only, no chip). Portal renders a full pill: `rounded-full px-2.5 py-1 text-xs font-semibold text-st-cancelled ring-1 ring-inset ring-st-cancelled/30 hover:bg-st-cancelled/10` with an `x` icon + "Remove" label. Heavier than the demo's ghost trash icon. | If strict parity is wanted, replace the labeled red pill with a bare icon button: Trash2/x at `size-4 text-slate-300 hover:text-st-cancelled` (no ring, no chip, no label). Otherwise this is an acceptable stronger affordance — low priority. | `views/customers/index.php` |

**Plan:**
1. Re-skin the segmented tab switcher to the demo Segmented primitive (grey `bg-slate-100 p-1 rounded-xl` track, white `shadow-xs` active thumb with `text-brand-700`, `text-ink-muted` inactive) — highest at-a-glance impact; update the .ng-tab-btn <style> block on both customers/index.php and customer-invitations/index.php for consistency.
2. Lighten the grace-window banner to demo spec: `px-4 py-3`, no ring, `gap-2` + `size-4` icon, ended-state `bg-amberbrand/12 text-[#8a6200]`.
3. Bring the classification pills to demo parity: drop the sparkles/store icons and use `text-accent-700` for the shop-owned tone.
4. Match the directory/classifications avatar to demo Avatar size sm (`size-7 text-[10px]`), ideally with the per-name oklch hue tint instead of flat bg-brand-50.
5. Fix the BookingLinkCard header: add the `Link2 size-4 text-brand-600` icon, set heading to `text-sm font-bold`, shrink the QR tile to `size-24`, and center the row (`sm:items-center`) — keep the extra WhatsApp/SMS real-feature buttons.
6. Render the Classifications Reason/Source value as a demo slate Badge instead of bare text.
7. (Optional, low) Simplify the freeze-list remove control to a bare ghost trash icon (`text-slate-300 hover:text-st-cancelled`) to match the demo.


### Foundation — Layout shell + tokens + gradients (frontend/views/layouts/tailwind.php + tailwind.config.js vs demo ShopLayout/RootLayout/index.css)
<a id="foundation-layout-shell-tokens-gradients-frontend-views-layouts-tailwind-php-tailwind-config-js-vs-demo-shoplayout-rootlayout-index-css"></a>
**Parity ~88%** · 1H / 2M / 4L

The visible shell is very close to demo parity: the sidebar/header gradient stops are byte-identical, page bg matches, shell height renders at the same 60px, and the RTL corner join works. The gaps are token-hygiene deltas that don't yet show pixels (missing --topbar-h and shadow-focus tokens, st-* status hexes drifted) plus two small layout-rhythm differences (content max-width 1400 vs 1600, non-responsive horizontal padding). No hardcoded-hex-over-token issues on the shell itself.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | color | shadow-focus token is MISSING from the portal. Demo index.css defines --shadow-focus: 0 0 0 4px rgba(131,96,195,.22) and the demo ui primitives (Button/Field/Toggle focus rings) rely on it. Portal boxShadow only declares xs/card/pop, so any demo element using shadow-[--shadow-focus] / a shadow-focus utility has no equivalent and focus rings fall back to Tailwind's ring-brand-400/20, a visibly different (thinner, wrong-alpha) ring. | Add boxShadow.focus: '0 0 0 4px rgba(131,96,195,.22)' to tailwind.config.js theme.extend.boxShadow so a shadow-focus utility exists, then rebuild CSS (npm run build:css). | `tailwind.config.js` |
| M | structure | The --topbar-h token (3.75rem/60px in demo index.css) does not exist in the portal. The shell height is hardcoded h-[60px] in _tw_navbar.php:36, _tw_sidebar.php:132, and the bg-side-shop flat band is hardcoded to a literal 80px in tailwind.config.js, whereas demo derives it as calc(var(--topbar-h) + var(--radius-2xl)). Values coincide today (60+20=80) so it renders identically, but the seam is no longer self-correcting and drifts if either value changes. | Declare --topbar-h: 3.75rem in a :root/@layer base block (or theme), switch the two h-[60px] chrome partials to h-[var(--topbar-h)], and rewrite bg-side-shop's second stop from the literal 80px to calc(var(--topbar-h) + 20px). Rebuild CSS. | `tailwind.config.js` |
| M | color | st-* status palette has drifted from the demo. Portal st.scheduled #594279 vs demo #6a3ab8; st.progress #e8a833 vs #f5b71b; st.completed #2ebf91 vs #57b49b; st.noshow #6f7682 vs #4c5663 (st.cancelled #dc5757 matches). Every booking/status badge, calendar chip, and st-tinted surface renders a different hue than the demo. | Update tailwind.config.js theme.extend.colors.st to scheduled:#6a3ab8, progress:#f5b71b, completed:#57b49b, noshow:#4c5663 (leave cancelled:#dc5757). Rebuild CSS. Flag first if the mobile-shared API/calendar lib/status.ts depends on these exact hexes. | `tailwind.config.js` |
| L | spacing | Content column max-width differs: portal wrapper is max-w-[1400px] (tailwind.php:83) while demo main inner is max-w-[1600px] (ShopLayout.tsx:55). On wide monitors the portal content stops ~200px narrower than the demo, changing line lengths and grid column counts. | Change max-w-[1400px] to max-w-[1600px] on the content wrapper div in tailwind.php. | `views/layouts/tailwind.php` |
| L | spacing | Content horizontal padding is fixed px-8 (tailwind.php:83) whereas demo is responsive px-5 lg:px-8 (ShopLayout.tsx:55) — the demo tightens to 20px gutters below the lg breakpoint. Portal over-pads narrow viewports vs the demo. (Vertical py-6 matches demo pt-6 pb-6.) | Change px-8 to px-5 lg:px-8 on the content wrapper in tailwind.php. | `views/layouts/tailwind.php` |
| L | structure | Content-panel scroll owner differs from demo. Portal puts overflow-y-auto on <main> with a non-flex inner div (tailwind.php:82-83); demo main is overflow-hidden and the inner max-w container is the flex column that scrolls: mx-auto flex h-full w-full ... overflow-y-auto overflow-x-hidden (ShopLayout.tsx:54-55). Portal also lacks the demo's explicit overflow-x-hidden guard and mx-auto centering on the inner container, so ultra-wide content can behave differently and the panel isn't centered. | Mirror the demo: set <main> to overflow-hidden and move overflow-y-auto overflow-x-hidden + mx-auto + flex h-full flex-col onto the inner max-w wrapper in tailwind.php. | `views/layouts/tailwind.php` |
| L | color | Global keyboard-focus outline appears absent from the portal base layer. Demo index.css sets :focus-visible { outline: 2px solid var(--color-brand-500); outline-offset: 2px } as a global affordance; grep of tailwind.src.css found no :focus-visible rule (only per-field focus:ring-brand-400/20 via @apply). Keyboard focus on non-field elements (buttons, links, nav items) has no brand outline, unlike the demo. | Add a base-layer :focus-visible { outline: 2px solid var(--color-brand-500); outline-offset: 2px } to tailwind.src.css and rebuild, matching demo index.css. | `web/css/tailwind.src.css` |

**Plan:**
1. Add the missing tokens to tailwind.config.js: boxShadow.focus (shadow-focus 0 0 0 4px rgba(131,96,195,.22)) — highest impact, it changes every focus ring to match the demo.
2. Correct the st-* status palette in tailwind.config.js (scheduled #6a3ab8, progress #f5b71b, completed #57b49b, noshow #4c5663; keep cancelled) — re-tints all status badges/chips. Flag to API/mobile owners first since lib/status.ts may share these hexes.
3. Introduce a --topbar-h: 3.75rem token, replace the two hardcoded h-[60px] chrome partials with h-[var(--topbar-h)], and re-derive bg-side-shop's flat band from calc(var(--topbar-h)+20px) instead of the literal 80px — makes the seamless corner self-correcting (no visible change today).
4. Add the global base-layer :focus-visible outline (2px solid brand-500, offset 2px) to tailwind.src.css to match the demo's keyboard-focus affordance on non-field elements.
5. Bump the content wrapper in tailwind.php to max-w-[1600px] and make horizontal padding responsive (px-5 lg:px-8) to match the demo's wide-screen line length and mobile gutters.
6. Align the scroll structure in tailwind.php: main overflow-hidden, inner max-w container gets mx-auto flex h-full flex-col overflow-y-auto overflow-x-hidden — mirrors demo ShopLayout and centers/guards wide content.
7. Run npm run build:css after the token/CSS changes so the minified bundle reflects them.


### Calendar (day / month / list) — booking-calendar v2
<a id="calendar-day-month-list-booking-calendar-v2"></a>
**Parity ~88%** · 1H / 3M / 3L

A high-fidelity port. Booking blocks, month grid, list table, zoom rail, now-line, empty state and the pixel geometry are near-verbatim copies of the demo. The remaining gaps are a genuinely different day-column header layout (centered-stacked vs left-aligned row), an extra day sub-heading line, and a handful of toolbar-button shape/size and list-column alignment deltas. Status colors correctly resolve through the shared --color-st-* vars, so the block palette matches the demo STATUS_COLOR by construction.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | structure | Day-column header uses a LEFT-ALIGNED ROW: `sticky ... flex items-center gap-2 border-b-2 px-2` with avatar on the left and name+title stacked to its right. The demo SpecialistColumn header is a CENTERED, STACKED COLUMN: `flex flex-col items-center justify-center gap-0.5 border-b-2 px-2 text-center`, with avatar+name on one centered row (`flex max-w-full items-center gap-1.5`) and the title centered below. This changes the look of every specialist column header. | Match the demo: outer header `flex flex-col items-center justify-center gap-0.5 ... text-center`; wrap avatar+name in an inner `flex max-w-full items-center gap-1.5` row; render the title `<p>` as a separate centered line (`max-w-full truncate text-[10px] leading-tight text-ink-muted`). | `views/booking-calendar/_specialist_column.php` |
| M | structure | An extra sub-heading line is rendered under the date h3: `<p class="text-sm text-ink-muted">Specialist columns · 30-min slots</p>` (line ~90). The demo DayCalendar date-nav renders ONLY the `<h3 class="ms-1 text-base font-bold text-ink">` date — there is no subtitle beneath it. Extra block adds a row the demo does not have. | Remove the sub-heading `<p>` so the date h3 stands alone (the portal wraps it in `<div class="ms-1 min-w-0">`; drop that wrapper's second child and keep `ms-1` on the h3 to match the demo). | `views/booking-calendar/_day.php` |
| M | spacing | Toolbar Block-time and Fullscreen buttons use the wrong shape vs the demo Button size="sm" (`h-9 px-4 text-[13px] rounded-full`). Portal Block-time: `rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-sm shadow-xs` (line ~114) — square-ish radius, border instead of ring, larger text. Portal Fullscreen: `rounded-lg bg-gradient-to-r from-brand-600 to-brand-500 px-3 py-1.5 text-sm` (line ~121) vs demo gradient variant `rounded-full h-9 px-4 text-[13px]`. | Give both buttons `rounded-full px-4 text-[13px]` with a fixed `h-9`; on Block-time swap `border border-slate-200` for `ring-1 ring-inset ring-slate-200` and `hover:bg-brand-50` to match Button secondary. | `views/booking-calendar/_day.php` |
| M | color | Numeric/status columns are aligned differently from the demo. Demo Bookings columns Value, Status, Collection, Settlement all use `align: 'center'`. Portal `_list.php`: Value header/cell is `text-end` (lines ~192, ~336) and Status/Collection/Settlement headers+cells have NO alignment class (default start). Reads as left/right-aligned instead of centered. | Add `text-center` to the Value, Status, Collection and Settlement `<th>` and their `<td>`s (replace the Value column's `text-end`/`dir=ltr` right-alignment with centered) to match the demo DataTable `align:'center'`. | `views/booking-calendar/_list.php` |
| L | spacing | Header action buttons (New group / New walk-in) are shorter and smaller-typed than the demo. Demo PageHeader actions use Button default size="md" (`rounded-full h-11 px-5 text-[15px]`); portal uses `rounded-full ... px-4 py-2 text-sm` (lines ~91, ~99) — roughly h-9 height and 14px text. | If matching the demo button metrics, bump to `px-5 text-[15px]` with a fixed `h-11` (keep the icon `gap-1.5`). Low priority — this is a consistent aurora-wide button substitution. | `views/booking-calendar/index.php` |
| L | spacing | AvatarStack overflow chip is one step smaller than the demo. Demo AvatarStack `+N` chip is `size-8` (matching `Avatar size="sm"` visual weight within the ring), portal `$avatarStack` renders `size-7` (line ~80). 4px smaller than the demo overflow chip. | Change the `+N` chip from `size-7` to `size-8` in `$avatarStack` (the individual avatars stay `size-7`, matching demo Avatar sm). | `views/booking-calendar/_list.php` |
| L | structure | Month status legend is built as bespoke ring-pill spans (`rounded-full px-2.5 py-0.5 text-xs font-semibold ring-1 ring-inset` with per-status tint tuples, lines ~56-99). The demo renders the legend via the shared BookingBadge (same PILL geometry, but tints come from the live `--color-st-*`/`badgeTint` vars, e.g. cancelled `bg-[#FBEAEA] text-[#B43A3A] ring-[#DC5757]/20`). The hardcoded tuples happen to match the demo TONES today but will NOT recolor if an admin re-themes a status, unlike the day/list badges which do use color-mix on the live color. | Optional consistency: drive the four legend pills from the same color-mix(var(--color-st-*)) idiom the day chips/list badges use, so a status re-theme recolors the month legend too. Visual output is identical at default theme. | `views/booking-calendar/_month.php` |

**Plan:**
1. Rework the day-column header in _specialist_column.php to the demo's centered-stacked layout (flex-col items-center justify-center gap-0.5 text-center; inner avatar+name row; title centered below) — the single most visible divergence across every column.
2. Remove the extra 'Specialist columns · 30-min slots' sub-heading in _day.php so the date h3 stands alone like the demo.
3. Normalize the Block-time and Fullscreen toolbar buttons in _day.php to the demo Button sm shape (rounded-full, h-9, px-4, text-[13px]; ring instead of border on secondary).
4. Center the Value / Status / Collection / Settlement columns in _list.php (add text-center to those th/td; drop the Value column's right-alignment) to match the demo DataTable align:'center'.
5. Low-impact polish: bump header action buttons in index.php to h-11 px-5 text-[15px], enlarge the AvatarStack +N chip to size-8, and (optionally) drive the month legend pills from the live --color-st-* vars for re-theme consistency.
6. Rebuild the Tailwind bundle (npm run build:css) after the class changes, then browser-verify day/list/month at shop.navagoo.localhost against the demo (#/shop/bookings).


### Social Media + Help Center + Agent Slots (portal-only, no demo counterpart)
<a id="social-media-help-center-agent-slots-portal-only-no-demo-counterpart"></a>
**Parity ~88%** · 1H / 3M / 3L

All three surfaces are strong, faithful uses of the Navagoo aurora design system — they reuse the demo's Card (rounded-2xl/shadow-card/ring-slate-900/5), PageHeader (text-2xl font-extrabold), card-header (px-5 pt-5 pb-3, text-[15px] font-bold), field controls, table, and pager patterns almost verbatim. The gaps are minor internal-consistency drifts: two competing primary-button recipes across the trio, a couple of hardcoded hex values where tokens exist, and small padding/label deviations from the canonical Field/CardHeader primitives.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| H | color | Primary buttons on Social Media (lines 61/135/227) use `bg-brand-600 ... shadow-card ... hover:bg-brand-700 active:scale-[0.97]` with NO hover lift, whereas demo primary Button (Button.tsx VARIANTS.primary) is `bg-brand-600 shadow-[0_6px_18px_rgba(89,66,121,0.22)] hover:bg-brand-700 hover:-translate-y-px active:scale-[0.97]`, and the sibling portal surfaces (Help Center line 150, Agent Slots line 71) use `shadow-sm ... hover:-translate-y-px`. Three portal surfaces ship three different primary-button treatments and none matches the demo's shadow token. | Standardize all primary CTAs on the demo recipe: `bg-brand-600 text-white shadow-[0_6px_18px_rgba(89,66,121,0.22)] hover:bg-brand-700 hover:-translate-y-px active:scale-[0.97]`. Replace `shadow-card`/`shadow-sm` and add the missing `hover:-translate-y-px` on social-media buttons (61,135,227). | `views/social-media/index.php` |
| M | color | The period-expired banner (line 74) and 'waiting' chip (line 88) hardcode `text-[#8a6200]` amber text (no token backing; amberbrand #e8a833 exists), while Help Center (line 57) styles the equivalent warning banner as `bg-[#fdf3dc] text-amberbrand ring-amber-200`. Two surfaces render the same 'warning' state with different hardcoded hex/token choices. | Pick one warning treatment across both surfaces: use `text-amberbrand` (or a defined darker-amber token) instead of the ad-hoc `#8a6200`, and align the amber banner bg/ring between social-media and technical-support. | `views/social-media/index.php` |
| M | structure | Card headers on Social Media prepend a 40px icon tile `inline-grid size-10 place-items-center rounded-xl bg-brand-600/10 text-brand-700` before the title (lines 111-113/152-154/176-178), whereas the demo canonical CardHeader (Card.tsx) is title + optional subtitle with NO leading tile, and Help Center's header (line 78) uses only a small inline `size-[18px]` icon. This makes Social Media's card headers heavier and inconsistent with both the demo CardHeader and the sibling portal card. | Apply the icon-tile motif consistently or drop it to match demo CardHeader. At minimum align with Help Center's lighter header (`text-[15px] font-bold` title + inline `size-[18px] text-brand-600` icon) so both portal-only surfaces present cards the same way. | `views/social-media/index.php` |
| M | typography | Field labels here are `text-xs font-semibold text-ink-soft` (lines 115/129), whereas the demo Field label (Field.tsx SectionLabel) is `text-sm font-bold text-ink` with a 3px brand accent bar (`h-3.5 w-[3px] rounded-full bg-brand-500`). The portal label is smaller, lighter-weight, muted-colored, and omits the accent bar. | Match demo SectionLabel: bump field labels to `text-sm font-bold text-ink` and optionally add the leading `h-3.5 w-[3px] rounded-full bg-brand-500` accent bar — or codify the portal's lighter label as the intended aurora form-label and apply it everywhere consistently. | `views/technical-support/index.php` |
| L | spacing | The table hardcodes compact `px-3` header/body cells inside a `px-2 py-2` wrapper (lines 114-124) and a `px-3 py-16` empty row (line 130). Demo DataTable (Table.tsx) uses `px-4` default (`px-3` only in `compact` mode) and empty state `px-4 py-12`. This differs from the canonical table container padding without the compact-mode justification. | Either declare this a compact table (keep `px-3` intentionally) or move to `px-4`, and change the empty-state cell from `py-16` to `py-12` to match Table.tsx and the booking-list sibling. | `views/agent-slots/index.php` |
| L | color | The active pager page is tinted with hardcoded hex `background:#594279;border-color:#594279` in the inline <style> (line 201, mirrored in booking/index.php). #594279 is the brand-800/st-scheduled value that already exists as a token (`bg-brand-800`), so this is a hardcoded hex where a token exists. | Replace the inline `#594279` with the `brand-800` token — apply `bg-brand-800 border-brand-800 text-white` on the active pager link via `activePageCssClass` (or reference the CSS var) instead of the raw hex. | `views/agent-slots/index.php` |
| L | color | The 'accounts connected' success chip (line 93) uses `bg-accent-400/15 text-accent-700 ring-accent-500/15`, while Help Center's success banner (line 56) uses `bg-accent-50 text-accent-700 ring-accent-200`. Same semantic 'success' state expressed with two different accent recipes (alpha-tint vs solid-50), a minor cross-surface token drift. | Standardize the success tone: prefer the solid `bg-accent-50 ... ring-accent-200` for both chip and banner (closer to a defined Badge tone), or codify one alpha recipe and reuse it. | `views/social-media/index.php` |

**Plan:**
1. Unify the primary-button recipe across all three surfaces to match demo Button.tsx primary (`shadow-[0_6px_18px_rgba(89,66,121,0.22)]` + `hover:-translate-y-px` + `active:scale-[0.97]`); replace the `shadow-card`/`shadow-sm` variants and add the missing hover lift on social-media — highest impact since it touches every CTA on the trio.
2. Consolidate warning + success semantic tones: replace hardcoded `text-[#8a6200]` with the `amberbrand` token and align the amber warning banner between social-media and technical-support; standardize the success chip/banner on one accent recipe (`bg-accent-50 text-accent-700 ring-accent-200`).
3. Decide on the card-header icon-tile motif: either apply the `size-10 bg-brand-600/10` tile consistently or drop it so both portal-only surfaces match the demo CardHeader / Help Center's lighter inline-icon header.
4. Align Help Center field labels to the demo SectionLabel (`text-sm font-bold text-ink` + optional brand accent bar), or lock in the portal's lighter label as the intended aurora form-label everywhere.
5. Tidy Agent Slots table spacing (empty state `py-16`→`py-12`; confirm `px-3` compact intent vs demo `px-4`) and swap the inline `#594279` pager hex for the `brand-800` token.
6. Run `npm run build:css` after any class change so the minified aurora bundle reflects the updates.


### Reviews (rate) — frontend/views/rate/index.php + _detail.php
<a id="reviews-rate-frontend-views-rate-index-php-detail-php"></a>
**Parity ~88%** · 0H / 2M / 5L

High parity. There is no demo twin for reviews, so this is a design-system-consistency audit against Card/Table/PageHeader/Field/Button primitives + tokens. The Card shell, PageHeader, table-header micro-labels, star/avatar helpers, and detail view all track the demo primitives faithfully. The only real divergences are in the bespoke filter bar (portal reuses the table-header label style instead of the demo Field/SectionLabel, and the submit/reset buttons drift from Button primary/secondary), plus a couple of minor spacing/token bypasses. Nothing high-severity; all cosmetic-to-medium.

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| M | typography | Filter field labels (Agent / User Rating / User Review) use the table-HEADER micro-label style `text-[11px] font-bold uppercase tracking-[0.08em] text-ink-muted`, but the demo form-field label primitive (Field → SectionLabel in components/ui/Field.tsx) is `text-sm font-bold text-ink` preceded by a brand accent bar `<span class="h-3.5 w-[3px] rounded-full bg-brand-500">`. Portal form labels are smaller, uppercased, muted, and missing the accent bar. | Render the three filter labels with the demo SectionLabel pattern: `mb-1.5 flex items-center gap-2 text-sm font-bold text-ink` with a leading `<span class="h-3.5 w-[3px] shrink-0 rounded-full bg-brand-500"></span>`, instead of the current `text-[11px] font-bold uppercase tracking-[0.08em] text-ink-muted`. | `views/rate/index.php` |
| M | color | Reset link diverges from the demo Button `secondary` variant. Portal: `border border-slate-200 bg-white text-ink-soft hover:bg-slate-50`. Demo secondary (components/ui/Button.tsx): `bg-white text-ink ring-1 ring-inset ring-slate-200 hover:bg-brand-50 hover:ring-brand-200`. Wrong text token (ink-soft vs ink), border vs ring-1 ring-inset, and a slate hover instead of the on-brand brand-50 hover. | Swap the Reset classes to `ring-1 ring-inset ring-slate-200 text-ink hover:bg-brand-50 hover:ring-brand-200` (drop `border border-slate-200`, `text-ink-soft`, `hover:bg-slate-50`) to match the demo secondary button. | `views/rate/index.php` |
| L | color | Submit button omits the demo primary Button's colored shadow + hover-lift. Portal: `bg-brand-600 ... shadow-sm hover:bg-brand-700` at h-10. Demo primary adds `shadow-[0_6px_18px_rgba(89,66,121,0.22)] hover:-translate-y-px hover:shadow-[0_10px_24px_rgba(89,66,121,0.30)] active:scale-[0.97]` at h-11. Radius is fine (rounded-xl matches the token reference). | Give the submit button the demo primary shadow/interaction (`shadow-[0_6px_18px_rgba(89,66,121,0.22)] hover:-translate-y-px active:scale-[0.97]`); optionally bump h-10→h-11 to match Button md height. | `views/rate/index.php` |
| L | spacing | Reviews table uses the demo's COMPACT horizontal padding (`px-3` on th and td) even though it is only a 6-column table. The demo DataTable default (non-compact) is `px-4`; `px-3` is reserved for wide, column-heavy tables via the `compact` prop. Booking-style 5-6 col tables in the demo render at px-4. | Change the th/td horizontal padding from `px-3` to `px-4` (keep `py-2.5` header / `py-3` body) to match the demo DataTable non-compact default. | `views/rate/index.php` |
| L | color | Row hover uses `hover:bg-brand-50`, but the demo DataTable default hover is `hover:bg-slate-50` (brand-purple hover is an explicit per-table override in the demo, and only on clickable rows). These rows are not clickable, so a brand hover on a non-interactive row is a mild inconsistency. | Use `hover:bg-slate-50` to match the demo DataTable default hover for non-clickable rows (or make rows link to the review detail if a brand hover affordance is intended). | `views/rate/index.php` |
| L | color | Active-pager color is hardcoded as `background:#594279; border-color:#594279` in the inline <style> block (~line 190). That hex is the brand-600 / st-scheduled value; a brand token exists, so this is a token bypass (and if brand-600 ever shifts, the pager won't follow). | Drive the active pager from the brand token instead of the raw hex — e.g. apply Tailwind `bg-brand-600 border-brand-600 text-white` on the `.is-active a` (via the LinkPager activePageCssClass utilities) rather than the hardcoded `#594279` in the <style> tag. | `views/rate/index.php` |
| L | spacing | Filter <select>/<input> controls use `px-3`, whereas the demo control base (baseControl in components/ui/Field.tsx) uses `px-3.5`. Height (h-10), radius (rounded-xl), border, shadow-xs, and focus ring already match. | Change the filter control horizontal padding from `px-3` to `px-3.5` (search input keeps its `ps-9` icon offset) to match the demo Input/Select base. | `views/rate/index.php` |

**Plan:**
1. Align the filter bar labels with the demo form-field primitive: replace the `text-[11px] uppercase tracking-[0.08em] text-ink-muted` labels with the SectionLabel pattern (`text-sm font-bold text-ink` + leading `h-3.5 w-[3px] rounded-full bg-brand-500` accent bar). Highest visual impact — it is the most noticeable place the surface reads as 'not the demo design system'.
2. Bring the Reset button onto the demo `secondary` Button variant: `ring-1 ring-inset ring-slate-200 text-ink hover:bg-brand-50 hover:ring-brand-200` (drop border/ink-soft/slate hover).
3. Bring the Submit button onto the demo `primary` Button: add the colored shadow + hover-lift/active-scale interaction (optionally h-11).
4. Nudge control padding px-3 → px-3.5 on the filter select/input to match the demo Input/Select base.
5. Set the reviews table to the demo non-compact padding (th/td px-3 → px-4).
6. Switch the non-clickable row hover from `hover:bg-brand-50` to `hover:bg-slate-50` (demo DataTable default), or make rows link to the detail view if a brand hover is deliberate.
7. Replace the hardcoded `#594279` active-pager color in the <style> block with brand-token utilities (`bg-brand-600 border-brand-600 text-white`).


### Navagoo Plans (frontend/views/navagoo-plans/index.php + _offer_ticket.php)
<a id="navagoo-plans-frontend-views-navagoo-plans-index-php-offer-ticket-php"></a>
**Parity ~93%** · 0H / 2M / 2L

A very faithful, freshly-ported surface — hero banner, plan cards, offer tickets, payment-cards/billing cards and the confirm modal match the demo class-for-class. The only clear drifts are on the billing-period segmented toggle (pill vs demo's rounded-xl, plus active-state color/shadow/size deltas) and the add-card modal's field labels (portal drops the demo Field's brand accent bar and uses a lighter/smaller label).

| ⚠ | Dim | Issue | Fix | File |
|---|---|---|---|---|
| M | structure | Billing-period segmented (#ng-billing-period) container + buttons drift from the demo Segmented primitive. Container: portal `inline-flex items-center rounded-full bg-slate-100 p-1` vs demo `inline-flex items-center gap-0.5 rounded-xl bg-slate-100 p-1` — portal is pill-shaped and drops `gap-0.5`. Buttons (.ng-period-btn): portal `rounded-full px-3.5 py-1.5 text-xs` with active `bg-white text-ink shadow-sm` vs demo `rounded-lg px-3 py-1.5 text-sm` with active `bg-white text-brand-700 shadow-xs`. | Container → `inline-flex items-center gap-0.5 rounded-xl bg-slate-100 p-1`. Buttons → `rounded-lg px-3 py-1.5 text-sm font-semibold`; active state → `bg-white text-brand-700 shadow-xs` (drop `text-ink shadow-sm`); update the JS class toggles in applyState/period-click to swap `text-brand-700` instead of `text-ink` and `shadow-xs` instead of `shadow-sm`. | `views/navagoo-plans/index.php` |
| M | typography | Add-card modal field labels don't match the demo Field/SectionLabel. Portal uses `<label class="mb-1 block text-xs font-semibold text-ink-soft">`; demo Field renders SectionLabel = `text-sm font-bold text-ink` preceded by a brand accent bar (`span h-3.5 w-[3px] rounded-full bg-brand-500`) with `mb-1.5`. Portal loses the accent bar and uses a smaller, lighter label. | Wrap each label in `flex items-center gap-2 text-sm font-bold text-ink mb-1.5` and prepend `<span class="h-3.5 w-[3px] shrink-0 rounded-full bg-brand-500"></span>` before the label text (or factor a small partial that mirrors SectionLabel). | `views/navagoo-plans/index.php` |
| L | color | Modal action buttons are hand-rolled instead of matching the demo Button primary primitive. Portal footer/CTA buttons use `rounded-full ... px-5 py-2 text-sm ... shadow-sm hover:-translate-y-px hover:bg-brand-700`; demo Button primary (md) is `h-11 px-5 text-[15px] rounded-full` with `shadow-[0_6px_18px_rgba(89,66,121,0.22)]`, `hover:shadow-[0_10px_24px_rgba(89,66,121,0.30)]`, `active:scale-[0.97]` and `focus-visible:ring-2 focus-visible:ring-brand-400`. Deltas: text-sm vs text-[15px], py-2 (~h-9) vs h-11, flat shadow-sm vs branded elevation, missing focus-visible ring. | If matching the demo exactly, bump modal primary buttons to `text-[15px]`, use `shadow-[0_6px_18px_rgba(89,66,121,0.22)] hover:shadow-[0_10px_24px_rgba(89,66,121,0.30)] active:scale-[0.97]` and add `focus-visible:ring-2 focus-visible:ring-brand-400 focus-visible:ring-offset-2`. Only do this if the portal isn't intentionally standardizing modal buttons on `text-sm`/`shadow-sm` across surfaces. | `views/navagoo-plans/index.php` |
| L | structure | Add-card modal omits the demo's second field: demo has `grid grid-cols-2 gap-3` with Last-4-digits + Expiry (MM/YY); portal shows only brand + last-4, each full-width stacked. This is a deliberate real-DB difference (only brand + last4 stored) rather than a styling bug, so the visual result legitimately diverges. | No change needed if the data model intentionally stores only brand + last4. If a two-column visual balance is wanted, keep last-4 full-width — do not add a fake Expiry field. | `views/navagoo-plans/index.php` |

**Plan:**
1. Align the billing-period segmented to the demo Segmented primitive: container `gap-0.5 rounded-xl bg-slate-100 p-1`; buttons `rounded-lg px-3 py-1.5 text-sm font-semibold`; active `bg-white text-brand-700 shadow-xs`. Update the matching JS classList toggles (text-brand-700/shadow-xs) so live period switching keeps parity. Run npm run build:css.
2. Restore the demo Field look on the add-card modal labels: `flex items-center gap-2 text-sm font-bold text-ink mb-1.5` + the `h-3.5 w-[3px] rounded-full bg-brand-500` accent bar before each label.
3. Optionally reconcile the hand-rolled modal buttons with the demo Button primary (text-[15px], branded shadow, active:scale, focus-visible ring) — only if the portal isn't deliberately using a lighter `text-sm`/`shadow-sm` modal-button convention across all aurora surfaces.
4. Leave the add-card single-column layout and the 'Added {date}' / storage-note copy as-is — those are intentional real-DB deviations, not visual drift.

