# Portals

Each audience has its own URL, OAuth client, redirect URIs, session cookie and permission policy. The user never picks a role inside the product.

Source: conversation export §14 and spec §7 / §14. Decision: ADR 0018.

## Audiences

| Portal | Intended host (example) | Audience | Authentication |
|---|---|---|---|
| Player | `my.playerpal.io` | Players | Passkey, approved social, provisional PlayerPal ID/password (A0), recovery |
| Staff | `admin.playerpal.io` | PlayerPal workforce | Enterprise SSO, MFA, privileged-access controls |
| Operator | `partners.playerpal.io/operator` | Operator workforce | Enterprise OIDC/SAML |
| Provider | `partners.playerpal.io/provider` | Provider workforce | Enterprise OIDC/SAML |
| Affiliate | `partners.playerpal.io/affiliate` | Affiliate workforce | Enterprise SSO or approved passkey |

Opening the wrong portal returns "you don't have access to this workspace", not a role switcher. One person may hold several roles; they enter each through its URL and authorization context.

UI navigation is not an authorization boundary. APIs enforce tenant, audience, role and resource policies (see [authorization.md](authorization.md)).

## What each audience may see

| Actor | Player identifier visible | Player email stored? |
|---|---|---|
| Player | Private `pp_plr_...` | No |
| Operator | `pp_plr_opr_...` only | No |
| Provider | `pp_plr_prv_...` only | No |
| Affiliate | Attributed or aggregate context | No |
| PlayerPal staff | Internal subject only where authorised | No player email |

## Demo mapping

Local demos use **host** routing (ADR 0018), not path roles:

| Host | Audience |
|---|---|
| `player.localhost:8080` | Player (real Nest + ZITADEL auth) |
| `staff.localhost:8080` | Staff (simulated sign-in) |
| `operator.localhost:8080` | Operator |
| `provider.localhost:8080` | Provider |
| `affiliate.localhost:8080` | Affiliate |
| `portals.localhost:8080` / `localhost:5173` | Launcher |

Path bypass (`/player`, …) remains for Vite direct mode. Presentation apps in [demos/](../demos/README.md). HTML prototypes under [spec/prototype/](../spec/prototype/README.md). Simulated sign-in outside the player portal is not production authentication.

## Analytics (partner KPIs)

The operator, provider and staff portals have an **Analytics** screen, and the operator and provider Overviews show live tiles. The screen has three tabs:

- **Your performance** (Own tier)
- **You vs PlayerPal network** (anonymous comparison)
- **Peer benchmarks**

The rules are in [kpis.md](kpis.md) and ADR 0033. The partner portals still use simulated sign-in, so locally they read a seeded demo party through the API's `/demo/kpis/*` helper (`DEMO_PARTNER_HELPER=1`, demo parties only). They never use a partner credential in the browser. Running it locally needs Cube (`task cube:db-role && task cube:up`), `CUBE_API_URL` and `CUBEJS_API_SECRET` on the API. With only a handful of local parties, comparisons stay suppressed unless the API also sets `BENCHMARK_ALLOW_LOW_THRESHOLDS=1` with lower `BENCHMARK_*` values.

Charts use Apache ECharts 5.6.0 (modular build, SVG renderer). The library is lazy-loaded, so the player portal never downloads it. One shared theme (`components/kpi/echarts.ts`) encodes the dataviz specs and the series colour `#1b9bc8`, which passes the validator on the portal surface. Every chart has a crosshair or item tooltip, arrow-key reading and a table view. Stat tiles and layout stay plain HTML.

## Partner overview (Spec v0.1 §13)

The operator, studio and affiliate portals open on a registry-driven overview (`components/kpi/overview/`):

- two KPI rows
- activity over time
- partner mix
- campaigns (affiliates)
- geo mix and player segments
- rewards redeemed, with redemption and mission performance
- retention by cohort and missions

Every value comes from `GET /v1/kpis/overview`, and components never compute KPIs. A KPI whose feed hasn't delivered shows the API's requirement text (e.g. "Requires operator commercial feed"), never a zero. KPIs that don't apply to an audience aren't rendered. The affiliate portal shows PrimeWins (`pp_aff_SIMPRIMEWINS`) from the simulated network.

Multi-series charts use the validated 4-slot categorical palette in `echarts.ts` (dataviz reference dark slots, checked on the portal surface). Anything beyond four slices goes into a neutral "Other".
