# Destina Holidays flights redesign — session handoff

Attach this file in the next chat so a new agent can continue without the transcript.

Work spans **two sibling repos** (not inside this workspace). Most checkout polish lives in `ng-checkout-github`. Results / search-band / loaders live in `homepage-ssr-app`. This file is the single handoff; do not scatter extra CONTEXT/HANDOFF files.

---

## Product / goal

Bring the entire Destina Holidays flights product (results + checkout) to design consistency with hotels/activities. Iterative polish from live screenshots.

Constraints:

- No comments in final application code
- Angular 11 checkout (`ng-checkout-github`)
- Concise surgical edits
- Do not commit unless asked

---

## Repos & ports

| Repo | Path | Port | Role |
|---|---|---|---|
| `homepage-ssr-app` | `/Applications/MAMP/htdocs/homepage-ssr-app` | **localhost:4200** | Flight results, search band, loaders, hotels/activities checkout reference |
| `ng-checkout-github` | `/Applications/MAMP/htdocs/ng-checkout-github` | **localhost:4300** | Angular 11 checkout: review / book / confirm / sold-out / unavailable |

`ng serve` on :4300 may have been user-terminated. Restart checkout locally if the page does not load.

---

## Design system / tokens

Brand tokens (Destina):

- `--navy`
- `--loader` `#ed5e3c` (orange)
- `--bad`
- `--ok`
- Purple CTA `#7b5cf5`

### `checkout-format.ts` helpers

- `fmt` — money / display formatting
- `stopsCount`
- `layovers`
- `stopsLabel` — **count + airport codes only**. Do **not** put layover duration on the compact line.
- `layoverTip` — navy multi-line tooltip content (layover duration lives here / in expanded details)

### `checkout-shared.scss` (ng-checkout-github)

- `.fld` — hotel-style fields: label above, **46px** height, **10px** radius, tick / `!` validation, red `em *` on required
- `.fld-grid`
- Nested `.fld-err` **under the field** — **not** sibling grid cells (Bootstrap `.col-3` collision)
- `.cc-wrap`
- `.m-pricebar` — fixed + safe-area
- `.sk` shimmer
- `.f-via` orange pills (airport codes)

### `homepage-ssr-app` `brand.scss`

- `@mixin brand-band-progress` / `band-indeterminate`
- Hotels / flights loaders match activities: **4px orange LTR shimmer**

### Tooltips

- Navy tooltips via **ngx-bootstrap tooltip** (not white popover)
- Hide tooltips on scroll: `app.component` capture-phase `scroll` / `wheel` / `touchmove`
- Ignore info-icon clicks when opening (do not treat the open click as a dismiss)

---

## Affirm

- Show for **USD / CAD** on results, review, book, confirm
- `isAffirmEligible` is **currency** `USD|CAD`, **not** origin country
- Production promo keys used locally (sandbox CORS-blocked):
  - **USD** `U76SJ0SGVF2UL76D` / `cdn1.affirm.com`
  - **CAD** `RUN3U5N0RFA3ZFN1` / `cdn1.affirm.ca`
- Use `affirm.ui.ready(cb)` — **not** a 20×300ms poll
- **Hide Affirm when Book and Hold is on** (`fareData.holdItinerary`); `displayAffirm` resets type to `cc`
- Mobile sticky promo is **one line**: `Starting at $X/mo`
- No giant “Pay monthly” box
- Pay tabs: `1fr 1fr` — card | Affirm on the same line

---

## validateRef / currency

- `waitForCurrency` **then** `validateRef`
- Fallback to response `translateToCurrency` / `currencies` / `Recommendation.Currency`
- Common-header currency via `localStorage` + `CURRENCY_LOADED`

---

## Copy / UX decisions (final, after several reversals)

Treat these as **settled**. Do not re-litigate unless the user asks.

- **Remove COVID-19 copy**
- **Review baggage & policies:** row on review opens Booking terms / Baggage / Policies. Use real fare/itinerary baggage and `fareData.policies` / upsell fare-rule text. Booking terms is a brief Destina Holidays T&C summary plus a full-terms link — not the legal page dump, not Trip.com copy.
- **Compact stops:** `1 stop` + orange airport pill only. **No layover duration on the compact line.** Layover stays in navy tooltips / expanded details.
- Flight-info mobile bottom sheet like Filters:
  - Drag-to-close from the **whole header**
  - Working **X** (`sheetDragStart` ignores `.sheet-close`)
- Hover autosuggest must be readable
- Search-band fields / icons aligned
- Arrival time: `10:25 PM +1` — not jammed on the route line
- Multi-city header: `MIA → DXB · MIA → CDG` via `routeLabel()` / `multiRoute()` **per-leg**. **Not** `MIA→MIA→CDG`.
- Replace “Fetching itinerary” modal with **skeletons**
- Tap-anywhere on checkout flight cards toggles details; chevron flips
- Single-result mobile Select bar attaches to the card when `displayFlights.length === 1`
- Sticky checkout footer not clipped on iPhone:
  - Trip-strip: flex + ellipsis
  - `.m-pricebar`: `left` / `right` / `bottom` `0`, `max-width: 100%`, padding `env(safe-area-inset-*)`
  - `index.html` `viewport-fit=cover`
  - Page `padding-bottom` includes bar + safe area
- Review / book share the same safe-area footer; confirm / sold-out inherit CSS only
- **NG0100:** bind `timerLabel` property — **not** `getTimerMinutes()` in templates. Update `timerLabel` in the `sessionTick` interval.
- Card / pax / billing errors nest under their own fields
- Mobile extras condensed so insurance is unmissable and aligned
- Required fields: red `*`

---

## Latest change (just done)

**Mobile search calendar (CheapOair-style, Destina tokens):** shared `mobile-calendar` sheet used by flights, hotels, and activities (home + results edit-search + hotel-info / activity-info / activity-checkout). White header title + X; top pills Departure/Return or Check-in/Check-out (tap focuses start vs end); weekday chips sand `#f4ead0`, weekend chips mint `#d5efe3`; selected start/end navy with tiny Depart/Return or Check-in/Check-out; in-range `#d5daf4`; past/disabled muted; today navy outline. Sticky Reset + purple `--cta` Done (10px radius). Done emits then `SheetCloser.close` with no event; X uses the event path (no Edit-search click-through). Done stays disabled until a valid start (and end when range). Reset clears dates and leaves the sheet open. Sheet z-index 1600 so it sits above the no-flights modal (1500). No fare-price legend (we do not have fare colors). Trip type / Direct stay on the existing search form — not added inside the calendar. Desktop still uses ngx-bootstrap `bsDatepicker` / daterangepicker (unchanged two-month pickers). Parent `close-nav` orange/navy header overrides removed so the new white chrome wins.

**Book Affirm tab (desktop + mobile):** `#affirm-tab` header is plain “Pay monthly” only — removed nested `affirm-as-low-as` so tab click selects Affirm and no longer opens the qualify modal; promo widget stays in the Affirm panel body. Review has no pay-tab header.

**Book / review policy sheet — Booking terms:** shared `app-policy-sheet` tab **Information** is now **Booking terms**. Brief Destina Holidays T&C bullets (Texas travel services, e-ticket confirmation, fare can change, insurance, force majeure, non-refundable/non-transferable, no name changes, baggage fees extra, check-in, passport/visa, codeshare, support) plus **Read full terms and conditions** → `https://www.destinaholidays.com/static/terms-and-conditions` (`target=_blank` `rel=noopener`). `open(tab)` / `@Input() tab` accept `booking-terms` | `baggage` | `policies` (aliases `info` / `bags` / `rules` still map). Book price summary has a **Baggage & policies** control under **Review itinerary** (desktop sidebar + mobile summary). Mobile trip-strip Notices | Baggage & policies kept. Agree-checkbox “terms & conditions” `preventDefault` + `stopPropagation` and opens the sheet on Booking terms. Desktop standalone policy row and sheet X / Clear-filters / deviceReady / container-xl / mobile Continue unchanged.

**Results device-ready gate (no mobile flicker):** `isMobile` defaulted to `false`, so SSR / first paint rendered the desktop results tree on phones, then flipped. Flights, hotels, and activities search results now keep `deviceReady` false until a browser `window.innerWidth` check (`applyDeviceViewport` in `device-viewport.ts`). Until then only the shared navy band + 4px `--loader` shimmer + `.sk` cards show; after that the correct desktop or mobile view mounts. Not applied to homepage search forms or checkout.

**Mobile Filters Clear closes the sheet:** sheet footer Clear (`clearSheet`) resets then `closeFilters()` / `closeSheet()` with no event (instant hide, no 400ms catcher). Same for hotels/activities sheets. Desktop sidebars unchanged.

**Sheet X leftover tap:** iOS ghost click after Close was hitting Edit search (`.m-summary` in the sticky band, not `common-header`). SheetCloser now `pointer-events:none`s all page chrome while open/closing, swallows the capture-phase click, and leaves a full-viewport `.sheet-catcher` for 400ms after hide; search overlay moved out of sticky `.m-band` so the dimmer covers the viewport including the top safe area.

**Book itinerary — Baggage & policies in the same box:** mobile trip-strip has a tappable `Notices | Baggage & policies` line under the last flight row (inside the white card). Desktop book uses a standalone policy row (strip stays hidden). Shared `app-policy-sheet` extracted from review; both pages open the same Information / Baggage / Policies sheet.

**Mobile tooltips off** (≤991px and/or `pointer: coarse`): hide navy ngx-bootstrap tooltips and field `(i)` triggers; desktop hover stays. Results mobile template no longer binds tooltips (flight-info sheet is the replacement). CVV uses a static hint on mobile.

**Review baggage & policies sheet** (Trip.com-style entry, Destina tokens):

- Review row after itinerary cards: `Notices | Baggage & policies` (notice count when present, chevron, 10px radius, navy/purple). Review uses full flight cards, so the standalone row is the one entry (not duplicated in-card).
- Shared `app-policy-sheet` (extracted from review) opens a mobile bottom sheet (header drag-to-close + X; drag is **not** bound on the body so tabs stay tappable) or a desktop centered modal. Book and review both use this component and `checkout-format` helpers — do not add a second parser.
- Book mobile trip-strip has the same CTA as a **new line inside the white itinerary box** (below the last flight/airline row). Desktop book keeps the compact strip hidden and uses a standalone policy row.
- Tabs: Booking terms | Baggage | Policies. Default tab is Baggage.
- Baggage is per direction (Depart / Return / Flight N) with city pairs, through-check or airport-change notes, and personal / carry-on / checked from fare data.
- Compact “Bags per traveller” card stays as the summary; the row is the deep-dive.
- No COVID copy. Booking terms is a Destina summary + full-terms URL. Empty Policies tab uses a short fallback, not invented legal text.

**Desktop review layout** (previous):

- Review SCSS was missing `.hero-row` / `.timer-pill` (HTML already used them; book/shared already had the rules).
- Flight times were squeezed into fixed `110px` / `122px` columns while the track sprawled — times now `auto` min-width, track has both endpoint dots.
- Hero / time `letter-spacing` negatives removed (screenshot “run-together” was mostly OCR; tracking was the only real tightness).
- Trip pill is **per-leg** (`MIA → DXB · MIA → CDG`), not `MIA → DXB → CDG`.
- `tripTypeLabel` / `legLabel`: two segments are Round trip / Depart+Return only when the second leg returns to origin; otherwise Multi-city / Flight 1+2 (this itinerary is MIA→DXB and MIA→CDG).
- Desktop bag cards: Included status on the right. Contact stays hidden `>991px` — no second Continue under sidebar CTA.
- Same card/track/pill fixes in `checkout-shared.scss` so book / confirm / sold-out stay consistent. Mobile rules unchanged.

**Mobile review (previous):** extra **Continue** under name + email. Two Continues on mobile is **intentional**.

---

## Key files map

### ng-checkout-github (`/Applications/MAMP/htdocs/ng-checkout-github`)

| File | Purpose |
|---|---|
| `src/app/checkout-format.ts` | Shared formatters: `fmt`, `stopsCount`, `layovers`, `stopsLabel`, `layoverTip`, plus review policy helpers (`bagDirections`, `tripNotices`, `policyLines`) |
| `src/app/checkout-shared.scss` | Hotel-style fields, price bar, shimmer, orange pills, validation |
| `src/app/book-desktop/book-desktop.component.html` | Book step desktop/mobile markup (pax, extras, payment, Affirm, footer) |
| `src/app/book-desktop/book-desktop.component.scss` | Book step layout / mobile extras / pay tabs |
| `src/app/book-desktop/book-desktop.component.ts` | Book step controller |
| `src/app/FlightInitComponent/flight.book.component.ts` | Book flow logic, Affirm eligibility, hold itinerary, payment type |
| `src/app/FlightInitComponent/flight.init.component.ts` | Init / `validateRef` / currency wait / itinerary load |
| `src/app/policy-sheet/policy-sheet.component.{html,ts,scss}` | Shared Booking terms / Baggage / Policies sheet used by review and book |
| `src/app/review/review.component.html` | Review markup; baggage & policies row; mobile inline Continue under contact |
| `src/app/review/review.component.scss` | Review layout; policy row; mobile contact Continue; `.contact` hidden `>991px` |
| `src/app/review/review.component.ts` | Review controller; `gotoBookingPage()` |
| `src/app/review-desktop/review-desktop.component.ts` | Desktop review wrapper |
| `src/app/confirm/confirm.component.html` | Confirmation page |
| `src/app/confirm/confirm.component.scss` | Confirm inherits shared footer CSS |
| `src/app/confirm/confirm.component.ts` | Confirm controller (Affirm on confirm) |
| `src/app/soldout-desktop/soldout-desktop.component.html` | Sold-out desktop |
| `src/app/soldout-desktop/soldout-desktop.component.scss` | Sold-out inherits shared footer CSS |
| `src/app/soldout-desktop/soldout-desktop.component.ts` | Sold-out controller |
| `src/app/unavailable/unavailable.component.html` | Fare / itinerary unavailable |
| `src/app/unavailable/unavailable.component.scss` | Unavailable layout |
| `src/app/unavailable/unavailable.component.ts` | Unavailable controller |
| `src/app/data.service.ts` | Shared checkout data / fare / currency / hold flags |
| `src/app/app.component.ts` | Capture-phase scroll/wheel/touchmove tooltip hide |
| `src/styles.scss` | Global checkout styles |
| `src/index.html` | `viewport-fit=cover` for iPhone safe-area |
| `src/app/time.pipe.ts` | Time display (arrival `+1` etc.) |

### homepage-ssr-app (`/Applications/MAMP/htdocs/homepage-ssr-app`)

| File | Purpose |
|---|---|
| `src/app/flights/flight-results/flight-results.component.ts` | Results controller: Affirm, select bar, `displayFlights`, currency |
| `src/app/flights/flight-results-desktop/flight-results-desktop.component.html` | Desktop results cards / route / stops / Affirm |
| `src/app/flights/flight-results-desktop/flight-results-desktop.component.scss` | Desktop results layout |
| `src/app/flights/flight-results-mobile/flight-results-mobile.component.html` | Mobile results, flight-info sheet, single-result Select bar |
| `src/app/flights/flight-results-mobile/flight-results-mobile.component.scss` | Mobile results + bottom sheet (Filters-like) |
| `src/app/device-viewport.ts` | Shared `applyDeviceViewport`: sets `isMobile` + `deviceReady` from `window.innerWidth` in the browser; SSR stays not-ready |
| `src/app/mobile-calendar/mobile-calendar.component.{ts,html,scss}` | Shared mobile search calendar (flights / hotels / activities). CheapOair-style sheet: pills, weekday vs weekend chips, navy range, Reset + Done |
| `src/app/flights/results/results.component.{ts,html,scss}` | Flight results wrapper: device gate then desktop/mobile |
| `src/app/hotels/results/results.component.{ts,html,scss}` | Hotel results wrapper: device gate when `hasSearch`; home form unchanged |
| `src/app/search-activities/activity-results/activity-results.component.{ts,html,scss}` | Activity results: same gate around the page |
| `src/brand.scss` | Brand tokens; `brand-band-progress` / `band-indeterminate` 4px orange LTR shimmer; `brand-device-gate` |
| `src/app/app.component.ts` | App shell / tooltip / header / currency events |
| `src/app/hotels/hotel-checkout/hotel-checkout.component.{html,ts,scss}` | Hotels checkout — visual reference for flights |
| `src/app/search-activities/activity-checkout/activity-checkout.component.{html,ts,scss}` | Activities checkout — loader + field reference |
| `src/checkout-shared.scss` | Homepage hotel/activity shared checkout styles (imported by those checkouts) |

---

## Known issues / caveats

- **Uncommitted work in both repos.** Do not commit unless asked.
- Checkout **:4300** server may be down (user aborted `ng serve`).
- Header colour vs common-header may still be imperfect on some hosts.
- Live visual QA of iPhone footer / safe-area on production `destinaholidays.com` was **not** re-verified after CSS landing.
- Mobile review contact card still sits in the **aside** after itinerary / bags / price. Inline Continue skips the trust block, **not** the itinerary above.
- Two Continues on mobile review (inline + sticky) is **intentional**.

---

## Do not

- Do not commit unless asked
- No comments in application code
- Angular 11 only in checkout — do not upgrade or use later Angular APIs
- Do **not** revert the compact stop decision to include layover duration on the compact line
- Do **not** show Affirm on hold itineraries (`fareData.holdItinerary`)
