# Drupal Handover Patch — Responsive Unification

**Date:** 2026-04-15
**Baseline commit:** `df20fa5`
**Final commit:** `d62adb4`
**Total commits in patch:** 14

## What triggered this patch

Post-handover, the client's Drupal developer flagged that several sections
render the same content twice — once inside `hidden lg:flex` (desktop) and
once inside `lg:hidden` (mobile). In Drupal, content sources (menus,
views, paragraph fields) render once, not twice, so double-rendering the
markup forces the Drupal theme to either duplicate data sources or add
brittle branching.

The client's flag was on `02-main-nav.html`. An audit surfaced 9 components
across the homepage and cataract page with the same pattern.

## What we did

- **Audited** each flagged component to separate true duplications
  (same content rendered twice) from responsive variants (UI chrome that
  only exists at one breakpoint — hamburger, floating ornaments, etc.)
  and view-mode patterns (same entity rendered in different display modes,
  which is Drupal-native and not a markup bug).
- **Structurally unified** the true duplications so each piece of content
  renders once in the DOM with CSS handling responsive presentation.
- **Annotated** the view-mode patterns with clear comments mapping each
  section to its expected Drupal primitive (menu block, view + view mode,
  paragraph type, entity reference) so the dev doesn't need to reinvent
  these calls.

## Components touched

| # | Component | Fix | Commit |
|---|-----------|-----|--------|
| 1 | `components/02-main-nav.html` | **Structural unification** — single `<ul>` for menu; nav-area morphs into overlay on mobile | `a157d73` |
| 2 | `components/04-centres-listing.html` | **Structural unification** — one title, `lg:contents` grid trick | `42956d5` |
| 3 | `cataract-components/05-patient-journey.html` | **Annotation** — mobile heading is JS-driven derivation, not duplicate | `ae6872e` |
| 4 | `components/06-doctor-cards.html` | **Structural unification** (component) + annotation (inline) | `d66a319` |
| 5 | `cataract-components/13-eye-care-experts.html` | **Annotation** — content divergence + view-mode pattern | `6dc0e7c` |
| 6 | `cataract-components/14-centres.html` | **Structural unification** — ported homepage pattern, fixed broken mobile UX | `9ca5017` |
| 7 | `components/09-testimonials.html` | **Annotation** — photo grid is derived decoration | `a933ab3` |
| 8 | `cataract-components/04-surgery-types.html` | **Annotation** — 3-render pattern is view-mode, not duplicate | `0b83370` |
| 9 | `cataract-components/06-iol-benefits.html` | **Annotation** — same view-mode pattern | `0d6d07b` |
| 10 | `cataract-components/11-testimonials.html` | **Structural unification** — 158 lines of duplication → 5 `[data-testimonial]` articles + new JS handler | `2b815ba`, `bfccda5` |
| 11 | `index.html` doctor cards inline | **Structural unification** — single H2 + arrow CTA, responsive classes; mobile + desktop subtitle copy kept as breakpoint-visible spans for Drupal dev to consolidate | bundled into `8c20d2d` |
| 12 | `index.html` testimonials inline | **Structural unification** — ported cataract pattern as `[data-slider="homepage-testimonials"]` with 5 `[data-testimonial]` articles + new handler in `main.js` (includes the desktop photo-collage rotation that cataract doesn't have) | `8c20d2d` |
| 13 | `cataract.html` eye-care-experts inline | **Structural unification** — single `<h2>` with both `Eye Care Experts` (desktop) and `Cataract Experts` (mobile) variants as `<span>` children; dev picks canonical | `d62adb4` |

## Drupal mapping cheat sheet

### 1. Main nav (`02-main-nav.html`)

- One `{{ menu('main-navigation') }}` block. Pass the same menu through once.
- Per-item breakpoint visibility goes on individual `<li>` classes:
  - `International Patients`: `lg:hidden` (mobile-only currently)
  - `Our Blogs`: `hidden lg:block` (desktop-only currently)
  - Confirm with the client whether these should become all-breakpoints.
- Secondary mobile menu (News & Media / Get In touch / Empanelments /
  Careers) mirrors the utility bar. In Drupal, render the utility-bar
  menu block a second time inside the mobile overlay region.
- Mobile overlay: hamburger toggles `hidden` class on `[data-menu-panel]`
  via the existing `js/main.js` handler. Mobile CSS turns the nav-area
  into `fixed inset-x-0 top-[85px] bottom-0 flex-col`; desktop (`lg:`)
  reverts to `static flex-row`.

### 2. Centres listing (`04-centres-listing.html`, `cataract/14-centres.html`)

- One "All Centres" title + arrow CTA. Responsive sizing via `lg:` utilities.
- One centres view iterating centre entities.
- CSS Grid pattern:
  - Outer wrapper: `lg:grid lg:grid-cols-3 lg:gap-x-8 lg:gap-y-[24px]`
  - Cards wrapper: `flex overflow-x-auto snap-x` on mobile,
    `lg:contents` on desktop → the cards become direct grid children.
  - Title block is the first grid cell (1,1) on desktop; flows above
    the carousel on mobile.

### 3. Doctor cards (`06-doctor-cards.html` + cataract `13-eye-care-experts.html`)

- **Header (title + arrow CTA + subtitle + tag badge):** unified flex
  block. Mobile = row with title + arrow; desktop = column with title +
  subtitle + arrow + badge. Desktop-only elements use `hidden lg:block`.
- **Featured doctor card** (desktop only): unique "flagship" entity.
  Map to a single `featured_doctor` entity reference with a desktop-
  featured view mode. NOT a duplication — the doctor shown here does
  not appear in the carousel below.
- **Scrollable doctor cards row:** one doctors view iterated with the
  `card` view mode. Appears at both breakpoints.
- **Mobile featured slider** (`[data-featured-slides]` in index.html):
  same 5 doctors as the scrollable row, rendered in a "mobile-featured"
  view mode (large, one-at-a-time). In Drupal this is the SAME view
  rendered twice with different view modes — not duplicate data.
- **Nav arrows:** unified into one set in the component file via
  `lg:contents` wrapper — flex-row below the track on mobile,
  absolute-positioned overlays on desktop.

### 4. Surgery types / IOL benefits tabs (`cataract/04-surgery-types.html`, `cataract/06-iol-benefits.html`)

- 4 entities per section (Femtosecond / MICS / Extracapsular / Phaco;
  Monofocal / Multifocal / Toric / EDOF).
- Each has fields: name, description, image.
- **One view** over the 4 entities.
- **Tab grouping:** desktop renders all 4 as a single tab strip;
  mobile splits into 2+2 tab groups. In Drupal this can be
  implemented with `:nth-child()` CSS or a split iteration (first 2,
  then last 2).
- **Mobile description truncation:** mobile text is a shorter version
  of desktop text + "Read More". Use CSS `line-clamp` with a JS-
  toggled "Read More" rather than separate content fields.
- **Tab JS** in `js/cataract.js` supports multiple `[data-tabs=*]`
  groups already; keeping the 2 mobile groups is fine.

### 5. Testimonials (cataract)

- **Cataract testimonials** (`cataract/11-testimonials.html`): now a
  single `[data-slider="cataract-testimonials"]` wrapper with 5
  `<article data-testimonial>` elements. Each article contains both
  the `lg:hidden` mobile presentation and the `hidden lg:block` desktop
  presentation, pulling the same name/quote/role/procedure fields.
- Drupal: one testimonials paragraph type with fields (name, role,
  quote, image, procedure). Iterate once per article. Inside each
  article, the mobile and desktop inner blocks reference the same
  fields — no duplication in Twig.
- **Slider JS** is in `js/cataract.js`. Toggles the `hidden` attribute
  on the `<article>`. CSS handles which inner block renders at each
  breakpoint.

### 6. Patient journey (`cataract/05-patient-journey.html`)

- Not a duplication — the 4 step labels live once on the
  `<button data-step>` elements. The mobile heading
  (`[data-journey-title]`) is an empty placeholder populated by
  `js/cataract.js` on step change.
- Drupal: one journey-steps paragraph field. Render each step's label
  on its button. Leave the mobile heading as an empty element; the
  existing JS will populate it.

### 7. Homepage testimonials (`components/09-testimonials.html`)

- Photo grid is desktop-only decoration derived from the same image
  assets as the 5 quote slides. Drupal: either a dedicated montage
  image field on the page/block, or a filtered view of the first 6
  testimonials rendered in image-only view mode.
- **Update:** the inline version in `html/index.html` was also
  unified in commit `8c20d2d` — same 5 `[data-testimonial]` article
  pattern, attribute `[data-slider="homepage-testimonials"]`. A new
  JS handler in `js/main.js` cycles articles AND rotates the desktop
  photo collage's 3 `[data-photo-slot]` imgs from neighbor
  testimonials (cataract doesn't have this rotation — its collage
  is fully static).

## Open items (need client input)

1. **Nav content parity.** Desktop currently has `Our Blogs` in the
   primary menu; mobile has `International Patients`. Confirm if these
   should stay breakpoint-specific or be unified.
2. **Doctor/experts header copy (3 places).** Currently preserved as
   breakpoint-visible `<span>` variants:
   - Homepage doctor cards subtitle: desktop = "Find top-rated eye care
     doctors for enhanced vision and wellness." / mobile = "Discover
     Better Health & Wellness By Using Our Doctor Ratings & Reviews
     To Make Your Choice."
   - Cataract experts H2: desktop = "Eye Care Experts" / mobile =
     "Cataract Experts".
   - Cataract experts subtitle: same mobile vs desktop split.
   Dev should pick one canonical string per field and delete the
   `<span>` variant that's not chosen.

## Breakpoint

Single breakpoint throughout: `lg:` = 1024px. Below 1024px = mobile
styles; 1024px+ = desktop styles. No intermediate `md:` variants used.

## How to verify

```bash
# Boot local preview
cd /home/mrwick/coding/fs/cfs
npx @tailwindcss/cli -i ./css/input.css -o ./css/output.css
python3 -m http.server 8765

# Open in browser
# http://localhost:8765/html/index.html
# http://localhost:8765/html/cataract.html

# Duplicate-ID sanity check
for f in html/index.html html/cataract.html html/components/*.html html/cataract-components/*.html; do
  dupes=$(grep -oE 'id="[^"]+"' "$f" | sort | uniq -d)
  [ -n "$dupes" ] && echo "$f:" && echo "$dupes"
done
# Expected: no output
```

## Rollback

Each component fix is a standalone commit. To roll back any one:

```bash
git revert <commit-sha>
```

Commits are ordered in the Components table above, from earliest
(`a157d73`) to latest (`bfccda5`).
