# Publish flow (Hyperlocal), full documentation

Platform page for the Blindspot DOOH product: the complete **Hyperlocal publish flow**, the manual,
pick-them-yourself path from naming a campaign to publishing it. It rebuilds the legacy six-step portal
flow into the campaign-details design system, as a child of the rebuilt publish dashboard. One
self-contained page renders all six steps and is deep-linkable by step.

- **File:** `publish.html` (single, self-contained HTML file). `select.html` now redirects to
  `publish.html?step=3` (the old standalone screen-picker is step 3 of the flow).
- **Gold standard it matches:** `../campaign-details/campaign-details.html`. Front door:
  `WORK IN PROGRESS/dashboard/dashboard.html`.
- **Status:** built 2026-06-19, review build with deterministic sample data, no backend.

> Companion: `IMPLEMENTATION-GUIDE.md` (state model + the functions to wire to a backend). Review
> landing: `index.html` (links every step and useful state).

---

## 1. The six steps

Steps 1 and 2 use a **two-column layout**: the form/cards on the left, an **"About this step" guide panel**
(contextual copy + a billboard line-illustration + the "you buy location and time, not impressions" note)
on the right, matching the website theme.

1. **Campaign.** Choose the path, **Hyperlocal** (manual, selected) vs **Blinky** (the AI planner, beta and
   out of scope here). Name the campaign (we recommend brand, market and month), pick an industry, set a
   start date, and optionally a **target budget** (the flow paces against it). Continue is disabled until
   name, industry and start are set.
2. **Scope.** Where to look for screens: **Around me / United States / Europe / Worldwide**, and which
   screen types: **Indoor / Outdoor / Mobile** (any mix). This seeds the step-3 filters.
3. **Pick screens.** The screen-selection screen: **List / Split / Map**, search by location, operator,
   venue or **point of interest** (e.g. "Starbucks", "Penn Station"), region + screen-type + venue filters,
   an "All filters" drawer, a **photo-led** result card, an explorable sortable table, a **real Mapbox map**
   (light/dark tiles, **clustered** at scale with type-coloured brand price-pins for lone screens; offline
   fallback to the self-contained map), a deep-linked screen detail drawer, and a **compare tray + side-by-
   side modal** (venue, format, cost per play, plays per day, availability; explains the best-value rank).
   Every pick lands in a running plan.
4. **Schedule.** Two views via a **Calendar / Table** toggle. **Calendar:** a **global schedule** (a 7 by 24
   hour grid) that applies to every screen, plus **per-screen overrides**, drag-to-paint, quick presets, and
   **apply these hours to all screens**. **Table:** every screen on one row (venue, format, per-row **plays
   per hour** chips, hours/week, plays, avg price, scheduled cost) plus a **mass plays-per-hour** control.
   Plays per hour is 1 to 50. A live **overview bar** (locations, screens, plays, hours/week, watch time,
   all-in USD budget) sits on top. This is the unique part: you buy **location and time** (the screens you
   want, the hours they run), not impressions or reach, and you schedule each screen independently.
5. **Media.** Continue without media, or upload. A **required-formats** panel lists the exact creative
   formats your screens accept (resolution, orientation, screen count), each marked **Ready** or **Needs a
   creative**, with a download-formats action. Uploads (and the **media library**) populate a creative list;
   **create custom format** adds a creative scoped to specific screens or bundles; each creative can carry
   **contextual rules** ("when it rains", "on Thursday", "in the evening", "when a stock crosses your
   target"). Review warns if any screen has no matching creative and will not serve.
6. **Review.** The booked summary, a **schedule heatmap** (week by daypart, **plays or spend**, no-green
   coral scale) that visualises when the campaign plays, a **creative-coverage** banner, a per-screen table,
   the **bill** in USD with a **budget-pacing bar** (spent vs target, over/under), a **discount code**, and
   **Publish** (confirm) plus **share as CSV / PDF / live link**.

Navigation: a persistent bottom **flow bar** (Back, a step summary, and a Continue button labelled for the
next step; Publish on step 6). The **progress rail** is clickable to any reached step. State is in the URL
(`?step=`, the plan, and the step-3 filters), so any step is shareable and restorable.

---

## 2. Architecture & dependencies

- **One self-contained HTML file**, inline `<style>` and one inline `<script>` IIFE. Links
  `design-system/tokens.css` then `blindspot.css`; never hardcodes a brand hex (reads `var(--token)`).
- Reuses the shared chrome and components: the **gear menu** (Theme + Density), **toasts**, the **footer**
  (`.cd-foot`, same legal row + Cookiebot hook as campaign-details), pill buttons (`.pb`), the segmented
  control (`.seg`), the **drawer** + **scrim/modal** (focus-trapped, sharing the `.dwhead` / `.dwclose` /
  `.dwbody` and `.modal` / `.mbd` / `.mfoot` class vocabulary with campaign-details), and the lit-screen
  card motif. The app **navigation** is intentionally the **dashboard's vertical sidebar** (`.dash-side`),
  not campaign-details' horizontal top bar (`.bs-nav`): the publish flow is launched from the dashboard and
  carries its chrome.
- New components (catalogued as PLAT-11 in `design-system/library.html`): the **wizard shell + flow bar**,
  the **step 1 mode cards + form**, the **step 2 scope/screen-type cards**, the **screen result card**
  (`.rcard`) + **table** (`.stable`) + **self-contained map** (`.smap`) + **screen drawer** from step 3,
  the **schedule grid** (`.sgrid`) + PPH + overview, the **media uploader** + **contextual rule builder**,
  and the **review list + bill**.

### Class namespace (collision-safe)
`blindspot.css` owns `.steps` / `.step` (a vertical marketing stepper) and bare `.plan`. The progress rail
is namespaced **`.pubstep` / `.pubsteps` / `.pubstep-line`** and all plan classes are `.plan-*`. Grep
`blindspot.css` before naming any new class.

---

## 3. Design rules (must hold)

- **No green, anywhere.** Availability reads through a state-chip scale (wide open = `--red-deep`, filling
  up = `--ink`, almost gone = amber, fully booked = muted). **Map pins are colour-coded by screen type with
  no green: indoor amber `#C99A4B`, outdoor purple `#6E6A9B`, mobile blue `#6B8BB0`** (the legacy platform
  used green for mobile; we deliberately do not). Picked screens and painted schedule hours are coral.
- **USD only, average cost per play.** Every price is `$x.xx` per play, all-in; the bill totals all-in USD.
  Never CPM, never cost-per-install.
- **Voice (platform-quiet).** Mono, literal labels; buttons say exactly what happens ("Continue to
  schedule", "Add to plan", "Publish campaign"). One Instrument-Serif accent clause per step heading. No em
  dashes.
- **Confirm before a side effect that spends, books or messages.** Adding/removing screens, painting hours
  and adding creatives are reversible (no confirm; adds offer Undo). **Clearing the plan** and **Publish**
  both confirm. Continue between steps spends nothing.
- **Dark theme = token flip** with targeted keeps and landmine overrides (including the schedule grid: the
  dark `.sg-cell` background must not clobber the coral `.on` cells).
- **Reduced motion** covered via `@media (prefers-reduced-motion: reduce)`.

---

## 4. Interactions & state

- **Step navigation:** `goStep(n)` (rail, only to reached steps), `continueStep()` (validates the current
  step, advances, on step 6 publishes), `backStep()`. `ST.step` / `ST.maxStep` track position.
- **Plan:** one-click add/remove from card, table row, map pin or drawer, kept in sync via the `data-sid`
  hot-class cross-highlight; every change offers Undo. Live totals in the rail (split) and the flow bar.
- **Schedule:** drag to paint the 7 by 24 grid; `global` vs per-screen scope via tabs; PPH 1 to 50; quick
  actions (all hours / business hours / evenings / clear / reset to global). Plays, impressions, watch time
  and budget recompute live from the hours and PPH.
- **Media:** upload (simulated) and library both add creatives; per-creative rules pick a trigger family
  (weather / day / time / live data) and a condition.
- **Review:** discount code (`BLINDSPOT10`, `LAUNCH20`, `SUMMER15`) recomputes the total; Publish confirms;
  share exports (stubbed to toasts).
- **Deep-link:** `syncURL()` writes step, plan and the step-3 filters; `readState()` restores from URL then
  localStorage (`bs_pub_plan`, `bs_pub_theme`, `bs_pub_density`, `bs_pub_view`).
- **Keyboard / a11y:** `/` focuses the location search on step 3; cards are `role="button"` + Enter/Space;
  drawer, filter drawer and confirm modal are focus-trapped with Esc + focus return; the schedule cells and
  table headers are reachable.

---

## 5. How it was verified

- **jsdom** (matchMedia / rAF / pointer-capture / scroll polyfills): **zero JS errors** across the full
  six-step flow, exercised end to end (fill step 1, continue, toggle scope, add screens, schedule
  pph/quick/per-screen, media upload + library + rule, discount + publish-confirm, rail jump back).
- **Playwright Chromium** (sandbox needs an `libXdamage.so.1` symbol stub via `LD_LIBRARY_PATH`): all six
  steps light, plus dark for steps 3 and 4. No page errors.
- **House-rule greps:** 0 em dashes, 0 en dashes, 0 euro, no CPM or "green" in copy, USD cost-per-play
  throughout, 0 VOICE banned words, no new `blindspot.css` class collisions.

---

## 6. Notes / future

- Live data wiring is the next step (see `IMPLEMENTATION-GUIDE.md`). The **Blinky** path on step 1 is the
  seam to the AI planner (a later task). In this build the sample inventory stays New York regardless of the
  step-2 region (a toast says so). The "create custom format" action and the contextual-rule API triggers
  are scaffolded against the data contract.

*Blindspot platform · publish flow · documentation · 2026-06-19 · Operated by TPS Engage, LLC.*
