# Blogs Page — Drupal 10 Mapping Sheet

This mapping shows how each component maps to a Drupal 10 implementation target (Block, Paragraph, View, Menu, Template). It follows the pattern the Drupal team established for prior pages (`docs/drupal-mapping.md`, `docs/cataract-drupal-mapping.md`, `docs/centre-cluster-drupal-mapping.md`, `docs/eye-specialists-drupal-mapping.md`, `docs/specialities-drupal-mapping.md`).

## Suggested content architecture

**Content type:** `node--landing_page` with a Paragraph field `field_sections` (bundle-restricted entity reference). The blogs page is one node of this type.

**Taxonomy vocabularies:**
- `speciality_extended` — term fields: `name`, `slug` (auto/pathauto), `description`. Extends the base `speciality` vocabulary with blog-specific terms (Myopia, "Lorem Ipsum" placeholder). Populates the category filter chips and the per-card `data-category` attribute.

**Content type: `node--blog`** — see full field spec below. Blogs are rendered on this page via a Views display.

---

## Blog content type — field spec

This is the most important mapping for this page. Each blog node feeds one `05-blog-card.html` row.

| Field name | Type | Required | Notes |
|-----------|------|----------|-------|
| `field_title` | Text (plain) | Yes | Card H3 text. Maps to `node--blog` title in most setups. |
| `field_slug` | Text (plain) | Yes | URL slug for the blog detail page; used in `href` on the card link. |
| `field_image` | Image (Media entity) | Yes | Full-bleed card image (`aspect-[5/3]`). `alt` = node title if empty. |
| `field_key_takeaways` | Text (plain) | No | Snippet shown in card body (`line-clamp-2`). Prefixed "Key Takeaways: " in template. |
| `field_published_date` | Datetime | No | Rendered as formatted date pill (e.g. "March 17, 2026") in card meta row. |
| `field_read_time_minutes` | Integer | No | Rendered as "{N} Minutes Read" pill with clock icon in card meta row. |
| `field_category` | Entity reference (term, vocabulary: `speciality_extended`) | No | Rendered as `data-category="<slug>"` on the card `<a>`; feeds the Views category filter. |
| `field_language` | List (text) | No | Values: `en` / `hi`. Drives Hindi Blogs sidebar widget filtering (`field_language=hi`). |
| `field_promoted` | Boolean | No | Drives the hero featured-post slider. Views slider display shows nodes where `field_promoted=true`. |

**View mode:** `card` on `node--blog` → `node--blog--card.html.twig`. This single Twig file renders every blog card consistently.

---

## Search filter as Views Exposed Form

The `03-search-filter.html` maps to a Views Exposed Form on the "blogs" View.

| Exposed filter | Views filter type | Field | Widget | Static HTML hook |
|---------------|------------------|-------|--------|-----------------|
| Blog search | Title, contains (or Search API) | `title` / `body` | Text input | `<input data-blog-search id="blog-search" name="q">` |
| Month | Date month | `field_published_date` | Select list (1–12) | `<select data-blog-month id="blog-month" name="month">` |
| Year | Date year | `field_published_date` | Select list (2021–2026+) | `<select data-blog-year id="blog-year" name="year">` |

Override `views-exposed-form.html.twig` to emit the exact HTML structure from `03-search-filter.html`, preserving all `id`, `name`, and `data-*` attributes. The "Need Help?" pill is decorative — do not add it to the form tag; render it as a sibling element positioned absolute in the section.

---

## Category filter as Views Exposed Form (chip variant)

The `04-category-filter.html` maps to a Views Exposed Filter on the "blogs" View.

| Exposed filter | Views filter type | Vocabulary / field | Widget | Static HTML hook |
|---------------|------------------|--------------------|--------|-----------------|
| Category | Taxonomy term | `field_category` → `speciality_extended` vocab | Link list (custom Twig) | `<a data-category-chip data-category="<slug>" href="?category=<slug>">` |
| All | No filter (default) | — | "All" chip, active when no `?category` param | `<a data-category-chip data-category="all" data-active="true">` |

Override `views-exposed-form.html.twig` to emit the chip `<ul>` structure from `04-category-filter.html`. Active chip detection: compare request `?category` param against each chip's slug; add `data-active="true"` and swap CSS classes accordingly in Twig.

---

## Pagination as Drupal Views pager

The `07-pagination.html` maps to the Drupal Views pager (mini or full pager).

Override `pager.html.twig` to emit the pill-style `<ul>` from `07-pagination.html`:
- Active page pill: `data-active="true"`, `bg-primary text-white`
- Inactive page pills: `border border-primary bg-white text-primary`
- Ellipsis: `aria-hidden="true"` decorative `…` element
- Drupal Views computes real page count at runtime; "12" in static markup is layout demo only.

---

## Sidebar widgets as Views block displays

Two separate Views block displays render inside `06-sidebar-list.html`:

**Widget 1: Reader's Favourite**
- Base: `node` (type = `blog`)
- Display: Views block (`blog_readers_favourite`)
- Sort: view count DESC (requires Statistics module or custom field) → fallback: `field_published_date` DESC
- Limit: 4–5 items
- Row template: inline Twig snippet matching `06-sidebar-list.html` item markup

**Widget 2: Hindi Blogs**
- Base: `node` (type = `blog`)
- Display: Views block (`blog_hindi`)
- Filter: `field_language = hi`
- Sort: `field_published_date` DESC
- Limit: 4–5 items
- Row template: inline Twig snippet matching `06-sidebar-list.html` item markup

Both widgets share the same `06-sidebar-list.html` template; only the widget heading (`{widget_heading}`) and the Views block source differ.

---

## Hero (featured-post slider) as Views slider display

The `02-hero-banner.html` maps to a Views page or block display with slider output:

- Base: `node` (type = `blog`)
- Display: Views slider (`blog_featured_hero`)
- Filter: `field_promoted = true` + published = Yes
- Sort: `field_published_date` DESC (most recent promoted post first)
- Row template: `node--blog--hero.html.twig` (view mode `hero`)
- Slider JS: existing `data-slider="featured-blog"` handler in `js/main.js`
- Fields rendered per post: `field_title`, `field_key_takeaways`, `field_image`, read-more URL, book-appointment URL

---

## Section-by-section mapping

| # | Component | Drupal implementation target | Notes |
|---|-----------|-----------------------------|-------|
| 1 | `01-utility-bar.html` | `region--secondary.html.twig` (utility region) + menu block | Same as homepage |
| 2 | `02-main-nav.html` | `region--header.html.twig` + `menu.html.twig` + CTA button block | Same as homepage |
| 3 | `01-breadcrumb.html` | Drupal breadcrumb block (`system_breadcrumb_block`) → `breadcrumb.html.twig` override | Trail: Home › Blogs |
| 4 | `02-hero-banner.html` | Views slider display (`blog_featured_hero`) — see Hero spec above | Slider JS via existing `data-slider` handler |
| 5 | `03-search-filter.html` | `views-exposed-form.html.twig` override on the "blogs" View | Expose `title`/`body` (contains), `field_published_date` month + year |
| 6 | `04-category-filter.html` | `views-exposed-form.html.twig` override (chip list variant) | Expose `field_category`; render as `<ul>` of chip links |
| 7 | `05-blog-card.html` (×10 in main grid) | `node--blog--card.html.twig` (view mode `card`) | Views row template; see Blog content type spec above |
| 8 | `06-sidebar-list.html` (×2 widgets) | Two Views block displays — `blog_readers_favourite` + `blog_hindi` | Same Twig shape; different block source and widget heading |
| 9 | `07-pagination.html` | Drupal Views pager → `pager.html.twig` override (pill style) | Drupal computes real page count; static "12" is layout demo |
| 10 | `08-our-story.html` | Paragraph bundle `text_callout` — fields: `field_heading`, `field_body` | Same bundle as specialities and eye-specialists `our-story`; fully reusable |
| 11 | `09-why-cfs.html` | Paragraph bundle `feature_card_grid` containing `field_cards` (entity ref → paragraph bundle `feature_card` with `field_heading`) | Same structure as specialities `09-why-cfs.html`; variable card count |
| 12 | `10-callback-form.html` | Paragraph bundle `callback_cta` — fields: `field_eyebrow`, `field_heading`, `field_background_image`. Webform reference `request_a_callback` | "Could not find" eyebrow variant; shared Webform; same paragraph bundle as specialities and eye-specialists |
| 13 | `15-footer.html` (shared) | `region--footer.html.twig` split into 3–4 blocks | Same as homepage |
| 14 | `16-sticky-bar.html` (shared) | Custom block inside `region--bottom.html.twig` | Same as homepage |

---

## Breadcrumb spec

Twig trail for this page:
```twig
{# breadcrumb.html.twig #}
<nav class="breadcrumb ..." aria-label="Breadcrumb">
  <ol ...>
    <li><a href="{{ path('<front>') }}">Home</a></li>
    <li aria-hidden="true" ...>›</li>
    <li aria-current="page" ...>Blogs</li>
  </ol>
</nav>
```

No dynamic ancestors required — this is a flat second-level page.

---

## Blog card field spec

| HTML element | Node field | Type | Notes |
|-------------|-----------|------|-------|
| `<a href>` | `field_slug` | Text (plain) | Card link URL |
| `<img src>` | `field_image` | Media (image) | `aspect-[5/3]` card image |
| `<h3>` text | `field_title` / `title` | Text (plain) | Card heading |
| Snippet `<p>` | `field_key_takeaways` | Text (plain) | "Key Takeaways: …" prefix added in Twig |
| Date pill | `field_published_date` | Datetime | Formatted e.g. "March 17, 2026" |
| Read-time pill | `field_read_time_minutes` | Integer | "{N} Minutes Read" |
| `data-category` | `field_category` → term slug | Entity reference | Used by category filter chip active state |

---

## Hero field spec (view mode: hero)

| HTML element | Node field | Type | Notes |
|-------------|-----------|------|-------|
| `<h1>` text | `field_title` / `title` | Text (plain) | Featured post title |
| Snippet `<p>` | `field_key_takeaways` | Text (plain) | "Key Takeaways: …" prefix added in Twig |
| Background `<img>` | `field_image` | Media (image) | Full-bleed hero image |
| "Read More" `<a>` | `field_slug` | Text (plain) | Link to blog detail page |
| "Book An Appointment" `<a>` | Site-level config / hardcoded URL | Link | Shared booking URL; optionally a paragraph field |

---

## Our Story paragraph fields

| HTML element | Paragraph field | Type | Notes |
|-------------|----------------|------|-------|
| `<h2>` text | `field_heading` | Text (plain) | Default: "Our Story and Vision" |
| Body `<p>` | `field_body` | Text (long, formatted) | Default: CFS founding story paragraph |

Paragraph bundle: `text_callout` (shared with specialities, eye-specialists, centre-cluster).

---

## Why CFS fields

**Paragraph bundle: `feature_card_grid`**

| Field | Type | Notes |
|-------|------|-------|
| `field_cards` | Entity reference (paragraph[], bundle: `feature_card`) | 4 cards for this page |

**Nested paragraph bundle: `feature_card`**

| Field | Type | Notes |
|-------|------|-------|
| `field_heading` | Text (plain) | Card title, e.g. "Eye Care Specialists" |

Card decorative line is rendered in the Twig template, not a field.

---

## Callback form fields

**Paragraph bundle: `callback_cta`**

| Field | Type | Notes |
|-------|------|-------|
| `field_eyebrow` | Text (plain) | Default: "Could not find what you are looking for?" |
| `field_heading` | Text (plain) | Default: "Request a Callback" |
| `field_background_image` | Media (image) | Default: `assets/images/callback-bg.png` |
| `field_webform` | Webform reference | Shared `request_a_callback` Webform |

Webform fields: `full_name` (text, required) + `phone` (telephone, required, pattern `[0-9]{10}`). Submit handlers: email notification + CRM hook.

---

## Layout risks / notes for Drupal team

1. **Category filter chip active state must be server-driven.** The static HTML uses `data-active="true"` and CSS class differences to distinguish the active chip. In Drupal, the Twig override must compare the request's `?category` query param against each chip's taxonomy term slug to set the active class. Do not attempt client-side JS toggling — the filter reloads the page.

2. **Blog card `data-category` must match the `?category` query param slug.** The Views Exposed Filter must populate `data-category` with the same slug value used in the chip href. Normalise in Twig using `term.path.alias` or a dedicated `field_slug` field if taxonomy machine names differ.

3. **"Lorem Ipsum" is the 12th category chip — explicit placeholder.** The taxonomy term slug `lorem-ipsum` is a design-time placeholder. The Drupal content team must rename it to a real speciality before launch. The chip's `data-category` and `href` must update to match the new slug.

4. **Pagination "12" is hardcoded for layout demo.** Do not hardcode page count in the `pager.html.twig` override — Drupal Views computes the real count. The static markup exists solely to show the pill layout with an ellipsis break.

5. **Hindi Blogs widget uses placeholder Devanagari content.** The sidebar widget heading "Hindi Blogs" and its items use `blog-cataracts.png` placeholder images. Drupal supplies real Hindi-language titles via the `blog_hindi` Views block filtered by `field_language=hi`.

6. **Hero slider arrows are decorative until Drupal expands the slider.** The static markup renders a single featured post. The `data-slider-prev` / `data-slider-next` buttons use the same handler as doctor-cards but have no real slides to navigate until multiple `field_promoted=true` blog nodes exist.

7. **`<main id="blogs-grid">` wraps only the grid+sidebar split.** Breadcrumb, hero, search filter, category filter, and pagination are direct `<body>` children in the static HTML — semantically valid HTML5. The Drupal team may expand `<main>` to encompass more sections in the page template without breaking any static preview behaviour.

8. **Search form `id` and `name` attributes must match Views exposed form output.** The `id="blog-search"` / `name="q"`, `id="blog-month"` / `name="month"`, `id="blog-year"` / `name="year"` attributes must be preserved in the Twig override for label associations to function and for URL query params to be consistent.

---

## Required Drupal modules (suggested)

- `paragraphs` (contrib)
- `webform` (for callback form)
- `media` + `media_library`
- `pathauto` (for taxonomy term URL aliases)
- `taxonomy` (core — for `speciality_extended` vocabulary)
- `views` (core — for blog grid, hero slider, and sidebar widgets)
- `statistics` (core — optional, for Reader's Favourite sort by view count)

## Testing checklist for Drupal theme integration

- [ ] Views "blogs" page display renders blog cards matching `05-blog-card.html` layout
- [ ] Search text input narrows the grid; empty result shows graceful message
- [ ] Month and year dropdowns filter by `field_published_date`
- [ ] Category filter "All" chip shows all blogs (no exposed filter applied)
- [ ] Clicking a category chip narrows the grid to matching blog nodes
- [ ] Active chip has accent bg; inactive chips have navy border + white bg
- [ ] Each blog card's `data-category` attribute reflects the node's `field_category` term slug
- [ ] Hero slider shows at least one `field_promoted=true` blog post
- [ ] Hero slider arrows navigate between multiple promoted posts (when > 1 post)
- [ ] Reader's Favourite sidebar shows 4–5 items sorted by view count
- [ ] Hindi Blogs sidebar shows 4–5 items filtered by `field_language=hi`
- [ ] Pagination pill style matches `07-pagination.html`; active page highlighted in navy
- [ ] Callback form submits correctly and sends email notification
- [ ] Mobile hamburger (`data-menu-toggle`) opens full-screen nav overlay
- [ ] Sticky bottom bar visible on mobile, chat bubble visible on desktop (not both)
- [ ] Breadcrumb shows "Home › Blogs"
