# DEI Toolkit Module - Technical Requirements & Progress Tracker

**Module:** `dei_toolkit`
**Path:** `public_html/sites/asht/modules/dei_toolkit/`
**Version:** 9.x-1.0

---

## Module Overview

The DEI Toolkit module provides a custom landing page at `/dei-toolkit` that aggregates content from multiple content types into a single-page experience. It uses a custom controller to assemble the page, Drupal Views for listing sections, and the Features module for self-contained configuration export.

---

## File Structure (Target)

```
dei_toolkit/
├── dei_toolkit.info.yml
├── dei_toolkit.module
├── dei_toolkit.routing.yml
├── dei_toolkit.features.yml
├── dei_toolkit.libraries.yml
├── dei_toolkit.permissions.yml
├── dei_toolkit_requirements.md
├── config/
│   └── install/
│       ├── block_content.type.dei_toolkit_header.yml
│       ├── field.storage.block_content.field_dei_header_*.yml
│       ├── field.field.block_content.dei_toolkit_header.field_dei_header_*.yml
│       ├── core.entity_form_display.block_content.dei_toolkit_header.default.yml
│       ├── core.entity_view_display.block_content.dei_toolkit_header.default.yml
│       ├── node.type.dei_resource.yml
│       ├── node.type.dei_spotlight.yml
│       ├── node.type.dei_term.yml
│       ├── node.type.dei_podcast.yml
│       ├── field.storage.node.field_dei_*.yml
│       ├── field.field.node.dei_resource.field_dei_*.yml
│       ├── field.field.node.dei_spotlight.field_dei_*.yml
│       ├── field.field.node.dei_term.field_dei_*.yml
│       ├── field.field.node.dei_podcast.field_dei_*.yml
│       ├── field.field.node.event.field_dei_event_image.yml
│       ├── core.entity_form_display.node.dei_resource.default.yml
│       ├── core.entity_form_display.node.dei_spotlight.default.yml
│       ├── core.entity_form_display.node.dei_term.default.yml
│       ├── core.entity_form_display.node.dei_podcast.default.yml
│       ├── core.entity_view_display.node.dei_resource.default.yml
│       ├── core.entity_view_display.node.dei_resource.teaser.yml
│       ├── core.entity_view_display.node.dei_spotlight.default.yml
│       ├── core.entity_view_display.node.dei_term.default.yml
│       ├── core.entity_view_display.node.dei_podcast.default.yml
│       ├── views.view.dei_resources.yml
│       ├── views.view.dei_events.yml
│       ├── views.view.dei_terms.yml
│       ├── views.view.dei_podcasts.yml
│       ├── image.style.dei_resource_thumbnail.yml
│       ├── image.style.dei_spotlight_image.yml
│       ├── image.style.dei_video_thumbnail.yml
│       ├── image.style.dei_event_image.yml
│       └── pathauto.pattern.dei_*.yml
├── src/
│   └── Controller/
│       └── DeiToolkitController.php
├── templates/
│   ├── dei-toolkit-page.html.twig
│   ├── dei-toolkit-hero.html.twig
│   └── dei-toolkit-spotlight.html.twig
├── js/
│   ├── dei-toolkit.js           (accordion, scroll-link, search filter)
│   └── dei-toolkit-video.js     (lazy-load video popup)
└── css/
    └── (optional - styles handled by theme SCSS)
```

---

## Content Types

### 1. DEI Resource (`dei_resource`)

**Machine name:** `dei_resource`
**Purpose:** Resources & Training Materials items

| Field | Machine Name | Type | Required | Notes |
|-------|-------------|------|----------|-------|
| Title | `title` | String (base) | Yes | Node title |
| Thumbnail Image | `field_dei_resource_image` | Image | Yes | 524x304 - thumbnail for /dei-toolkit listing |
| Short Description | `field_dei_resource_short_desc` | Text (plain, long) | No | Summary shown on /dei-toolkit listing only |
| Body | `body` | Text (formatted, long, summary) | No | Full content for detail page |

**Status:** Not started

- [ ] Create node type config YAML
- [ ] Create field storage configs
- [ ] Create field instance configs
- [ ] Create form display config
- [ ] Create view display config (default + teaser)
- [ ] Create image style `dei_resource_thumbnail` (524x304)
- [ ] Create pathauto pattern

---

### 2. DEI Member Spotlight (`dei_spotlight`)

**Machine name:** `dei_spotlight`
**Purpose:** Member Spotlight entries

| Field | Machine Name | Type | Required | Notes |
|-------|-------------|------|----------|-------|
| Title | `title` | String (base) | Yes | Internal admin title |
| Body | `field_dei_spotlight_body` | Text (formatted, long) | Yes | Testimonial/quote text |
| Name | `field_dei_spotlight_name` | Text (plain) | Yes | Member name |
| Job Title | `field_dei_spotlight_job_title` | Text (plain) | No | Member job title |
| Organization | `field_dei_spotlight_org` | Text (plain) | No | Member organization |
| Image | `field_dei_spotlight_image` | Image | Yes | 555x555 member photo |

> **NOTE - Future Display Flexibility:**
> Initial implementation shows the **most recent** published spotlight node.
> This may need to be changed later to one of:
> - **Random on page load** via AJAX callback to bypass page caching
> - **Rotating carousel** using tiny-slider (`/libraries/tiny-slider`) with fade/rotation between multiple spotlights
>
> The controller and template should be structured so the spotlight loading
> logic can be swapped without reworking the rest of the page. Consider
> isolating spotlight rendering into its own method/template so it can later
> be served via an AJAX endpoint or wrapped in a tiny-slider container.

**Status:** Not started

- [ ] Create node type config YAML
- [ ] Create field storage configs
- [ ] Create field instance configs
- [ ] Create form display config
- [ ] Create view display config
- [ ] Create image style `dei_spotlight_image` (555x555)
- [ ] Structure spotlight loading in controller as isolated/swappable method

---

### 3. DEI Term / "DEI"ctionary (`dei_term`)

**Machine name:** `dei_term`
**Purpose:** DEIctionary glossary terms displayed in accordion

| Field | Machine Name | Type | Required | Notes |
|-------|-------------|------|----------|-------|
| Title | `title` | String (base) | Yes | Term Name (shown in accordion header) |
| Description | `field_dei_term_description` | Text (formatted, long) | Yes | Term definition |
| Source | `field_dei_term_source` | Text (plain) | No | Citation source name |
| Citation | `field_dei_term_citation` | Text (plain, long) | No | Full citation text |
| URL | `field_dei_term_url` | Link | No | Source URL |

**Status:** Not started

- [ ] Create node type config YAML
- [ ] Create field storage configs
- [ ] Create field instance configs
- [ ] Create form display config
- [ ] Create view display config

---

### 4. DEI Podcast (`dei_podcast`)

**Machine name:** `dei_podcast`
**Purpose:** DEI-specific podcast entries (separate from existing podcast content type)

| Field | Machine Name | Type | Required | Notes |
|-------|-------------|------|----------|-------|
| Title | `title` | String (base) | Yes | Podcast title |
| Date | `field_dei_podcast_date` | Date | Yes | Podcast publish date |
| URL | `field_dei_podcast_url` | Link | Yes | Podcast episode URL |

**Status:** Not started

- [ ] Create node type config YAML
- [ ] Create field storage configs
- [ ] Create field instance configs
- [ ] Create form display config
- [ ] Create view display config

---

### 5. Existing Event Content Type Modifications

**Machine name:** `event` (existing)
**Purpose:** Add DEI event support

| Change | Details |
|--------|---------|
| Event Type Taxonomy | Add "DEI" term to `event_type` vocabulary |
| DEI Event Image | New field `field_dei_event_image` (Image) - required when Event Type = DEI |

**Status:** Not started

- [ ] Add `field_dei_event_image` field storage config
- [ ] Add `field_dei_event_image` field instance on event content type
- [ ] Update event form display config
- [ ] Update event view display config
- [ ] Create image style `dei_event_image`
- [ ] Implement conditional required logic in hook_form_alter (required when event_type = DEI)
- [ ] Note: "DEI" taxonomy term must be added manually or via install hook

---

## DEI Toolkit Header (Custom Block Type)

**Purpose:** Hero section at top of /dei-toolkit page
**Entity type:** `block_content`
**Bundle:** `dei_toolkit_header`
**Pattern reference:** `nadca/modules/block_settings/config/install/block_content.type.nadca_membership.yml`

Uses a custom block_content type with fields. Controller loads the block_content entity by type and renders it into the hero section. Editors manage it via Structure > Block layout > Custom block library.

| Field | Machine Name | Type | Required | Notes |
|-------|-------------|------|----------|-------|
| Block description | `info` | String (base) | Yes | Admin label (block_content base field) |
| Title | `field_dei_header_title` | Text (plain) | Yes | Main heading (e.g., "DEI Toolkit") |
| Subtitle | `field_dei_header_subtitle` | Text (plain) | No | e.g., "Mission & Vision" |
| Description | `field_dei_header_description` | Text (formatted, long) | No | Intro paragraph |
| Button 1 | `field_dei_header_button1` | Link | No | First CTA button |
| Button 2 | `field_dei_header_button2` | Link | No | Second CTA button |
| Video Thumbnail | `field_dei_header_video_thumb` | Image | No | 555x315 thumbnail image |
| Video URL | `field_dei_header_video_url` | Link | No | YouTube/video embed URL |

**Config files needed:**
```
block_content.type.dei_toolkit_header.yml
field.storage.block_content.field_dei_header_title.yml
field.storage.block_content.field_dei_header_subtitle.yml
field.storage.block_content.field_dei_header_description.yml
field.storage.block_content.field_dei_header_button1.yml
field.storage.block_content.field_dei_header_button2.yml
field.storage.block_content.field_dei_header_video_thumb.yml
field.storage.block_content.field_dei_header_video_url.yml
field.field.block_content.dei_toolkit_header.field_dei_header_*.yml
core.entity_form_display.block_content.dei_toolkit_header.default.yml
core.entity_view_display.block_content.dei_toolkit_header.default.yml
```

**Status:** Not started

- [ ] Create `block_content.type.dei_toolkit_header.yml`
- [ ] Create field storage configs (block_content entity type)
- [ ] Create field instance configs
- [ ] Create form display config
- [ ] Create view display config
- [ ] Create image style `dei_video_thumbnail` (555x315)

---

## Views

### 1. DEI Resources View (`dei_resources`)

| Display | Path | Description |
|---------|------|-------------|
| Block: `dei_resources_block` | (embedded) | 4 most recent, shows thumbnail, title, short desc, learn more link |
| Page: `dei_resources_page` | `/dei-toolkit/resources` | Full listing, all resources, paged, most recent first |

**Status:** Not started

- [ ] Create views config YAML with block and page displays
- [ ] Block: 4 items, fields: thumbnail (linked), title (linked), short_desc, learn more button
- [ ] Page: All items, paged, fields: thumbnail (linked), title (linked), short_desc, learn more button
- [ ] Sort: created DESC

---

### 2. DEI Events View (`dei_events`)

| Display | Path | Description |
|---------|------|-------------|
| Block: `dei_events_block` | (embedded) | 4 most recent DEI events (filtered by event_type = DEI) |
| Page: `dei_events_page` | `/dei-toolkit/events` | All DEI events, paged |

**Status:** Not started

- [ ] Create views config YAML with block and page displays
- [ ] Filter: event_type taxonomy = "DEI"
- [ ] Block: 4 items, fields: dei_event_image (linked), title (linked), date, location, learn more
- [ ] Page: All items, paged
- [ ] Sort: field_event_date DESC

---

### 3. DEI Terms View (`dei_terms`)

| Display | Path | Description |
|---------|------|-------------|
| Block: `dei_terms_block` | (embedded) | All terms, accordion via `ah_accordion` module, client-side search filter |

**Accordion:** Uses the shared `ah_accordion` module (`/modules/custom/ah_accordion`) and its
`applyAccordion()` JS function. The view outputs standard markup (`.view-content > .views-row`)
and JS calls `applyAccordion()` to transform it into an accordion. See usage reference:
`sites/asht/modules/asht_lms/js/accordion.js`

Expected `applyAccordion()` call:
```javascript
applyAccordion(
  '.view-dei-terms-search .view-content',  // parent_selector
  '.views-row',                             // row_selector
  '.accordion-header',                      // header_selector
  '.accordion-content',                     // body_selector
  '.accordion-content',                     // container_selector
  true,                                     // inject_controls
  'dei-terms',                              // unique_selector
  true,                                     // expanded (first item open)
  false,                                    // final (not last accordion on page)
  'plus_minus',                             // control_type
  false,                                    // auto_close
  500                                       // timing (ms)
);
```

**Search Filter:** Client-side jQuery filtering — no AJAX, no page reload. As the user types
in the search input, JS filters `.views-row` elements in real time by matching the input value
against the `.accordion-header h3` text. Filtering begins immediately (no minimum character
threshold needed since it's purely client-side DOM filtering).

**Status:** Not started

- [ ] Create views config YAML with block display
- [ ] Fields: title (accordion header h3), description, source, url (accordion content)
- [ ] No exposed filter in Views — search is handled entirely by client-side JS
- [ ] Sort: title ASC

---

### 4. DEI Podcasts View (`dei_podcasts`)

| Display | Path | Description |
|---------|------|-------------|
| Block: `dei_podcasts_block` | (embedded) | 4 most recent DEI podcasts |
| Page: `dei_podcasts_page` | `/dei-toolkit/podcasts` | All DEI podcasts, paged |

**Status:** Not started

- [ ] Create views config YAML with block and page displays
- [ ] Block: 4 items, fields: play button thumbnail, title (linked), date, play podcast button
- [ ] Page: All items, paged
- [ ] Sort: field_dei_podcast_date DESC

---

## Routing

| Route | Path | Controller Method | Access |
|-------|------|-------------------|--------|
| `dei_toolkit.page` | `/dei-toolkit` | `DeiToolkitController::page` | `access content` |

**Status:** Not started

- [ ] Create `dei_toolkit.routing.yml`

---

## Controller

**Class:** `Drupal\dei_toolkit\Controller\DeiToolkitController`
**Extends:** `Drupal\Core\Controller\ControllerBase`

### `page()` method responsibilities:
1. Load the DEI Toolkit Header block_content entity (query by bundle `dei_toolkit_header`, most recent)
2. Build the hero section render array from block_content fields
3. Embed the Resources view block (`dei_resources_block`)
4. Load the latest published `dei_spotlight` node and build the spotlight section (isolated method for future swap to AJAX/slider)
5. Embed the DEI Terms view block (`dei_terms_block`)
6. Embed the DEI Events view block (`dei_events_block`)
7. Embed the DEI Podcasts view block (`dei_podcasts_block`)
8. Build the static menu navigation
9. Attach the `dei_toolkit/dei_toolkit` library
10. Return render array using `#theme => 'dei_toolkit_page'`

**Status:** Not started

- [ ] Create `DeiToolkitController.php`
- [ ] Implement `page()` method
- [ ] Load and render header block_content entity
- [ ] Embed all view blocks
- [ ] Load and render spotlight node (isolated `buildSpotlight()` method)
- [ ] Attach JS library

---

## Templates

### `dei-toolkit-page.html.twig`
Master template wrapping all sections inside `<div class="dei-toolkit">`.
Must output HTML structure matching the example HTML to work with existing SCSS.

### `dei-toolkit-hero.html.twig`
Hero section with title, subtitle, description, 2 buttons, video thumbnail with play overlay.

### `dei-toolkit-spotlight.html.twig`
Member spotlight section with quote, member info, image, and action buttons.

**Status:** Not started

- [ ] Create `dei-toolkit-page.html.twig`
- [ ] Create `dei-toolkit-hero.html.twig`
- [ ] Create `dei-toolkit-spotlight.html.twig`
- [ ] Views templates may use views template overrides or custom row templates

---

## JavaScript

### `dei-toolkit.js`
- Smooth scroll for `.scroll-link` navigation
- DEIctionary accordion initialization via `applyAccordion()` from `ah_accordion` module
- DEIctionary client-side search filter (jQuery `.keyup()` on search input, show/hide `.views-row` based on text match against `.accordion-header h3`, no page reload, no AJAX)

### `dei-toolkit-video.js`
- Video popup functionality for hero section and spotlight "How It Works"
- Lazy load YouTube embed on play button click

### Dependencies
- `ah_accordion/accordion` — provides `applyAccordion()` global function
- `core/jquery` — for DOM manipulation and search filtering

**Status:** Not started

- [ ] Create `dei-toolkit.js` (scroll, accordion init, search filter)
- [ ] Create `dei-toolkit-video.js`
- [ ] Create `dei_toolkit.libraries.yml` (with `ah_accordion/accordion` dependency)

---

## Permissions

| Permission | Description |
|------------|-------------|
| `administer dei toolkit` | Administer DEI Toolkit settings |

Standard node permissions (`create dei_resource content`, etc.) are handled by Drupal core.

**Status:** Not started

- [ ] Create `dei_toolkit.permissions.yml`

---

## Features Integration

The module uses Features for self-contained config export. All configuration YAML files live in `config/install/` and are installed when the module is enabled.

**Status:** Not started

- [ ] Create `dei_toolkit.features.yml`
- [ ] Export all config to `config/install/`

---

## Static Menu

The in-page navigation menu is statically rendered in the page template (not a Drupal menu entity). Links use `#anchor` scroll targets:

| Label | Anchor |
|-------|--------|
| Resources & Training Materials | `#resources` |
| Member Spotlight | `#spotlight` |
| "DEI"ctionary | `#terms` |
| Events | `#events` |
| Podcasts | `#podcasts` |

---

## CSS/SCSS Notes

All styling is handled in the theme at:
`sites/asht/themes/asht/sass/pages/_dei-toolkit.scss`

The module's HTML output must match the example HTML class structure exactly to avoid any SCSS changes. Key CSS class requirements:

- Outer wrapper: `.dei-toolkit`
- Sections: `.hero`, `.menu`, `.resources`, `.spotlight`, `.terms`, `.events`, `.podcasts`
- Section IDs: `#resources`, `#spotlight`, `#terms`, `#events`, `#podcasts`
- View wrappers: `.view-resources`, `.view-events`, `.view-dei-terms-search`, `.view-dei-podcasts`
- Item structure: `.views-row.col-sm-3 > .view-item > .item-thumbnail + .item-detail`
- Button classes: `.btn-green-bg`, `.btn-green-bg.learn-more`, `.btn-green-bg.view-all`
- Accordion: `.accordion-header > h3` + `.accordion-content`
- Spotlight: `.member-quote`, `.member-name`, `.member-title`, `.member-org`

---

## Implementation Order

1. Module scaffolding (info, module, routing, permissions, features, libraries) - **Phase 1**
2. Content types + fields (config/install YAMLs) - **Phase 2**
3. Image styles - **Phase 2**
4. Views (config/install YAMLs) - **Phase 3**
5. Controller (DeiToolkitController.php) - **Phase 4**
6. Templates (twig files matching example HTML) - **Phase 4**
7. JavaScript (accordion, scroll, search filter, video popup) - **Phase 5**
8. Event content type modifications (DEI image field, form_alter) - **Phase 6**
9. Testing & refinement - **Phase 7**
10. Features export / config cleanup - **Phase 8**

---

## Progress Summary

| Component | Status |
|-----------|--------|
| Module scaffolding | **Done** (info.yml, .module, routing, permissions, features, libraries) |
| Block type: dei_toolkit_header | **Done** (block_content type + 7 field storages + 7 field instances + form/view display) |
| Content type: dei_resource | **Done** (node type + 2 field storages + 3 field instances + form display + default/teaser view displays) |
| Content type: dei_spotlight | **Done** (node type + 5 field storages + 5 field instances + form/view display) |
| Content type: dei_term | **Done** (node type + 4 field storages + 4 field instances + form/view display) |
| Content type: dei_podcast | **Done** (node type + 2 field storages + 2 field instances + form/view display) |
| Image styles | **Done** (dei_resource_thumbnail, dei_spotlight_image, dei_video_thumbnail, dei_event_image) |
| Views: dei_resources | **Done** (block: 4 items w/ footer + page: /dei-toolkit/resources paged) |
| Views: dei_events | **Done** (block: 4 DEI events w/ footer + page: /dei-toolkit/events paged) |
| Views: dei_terms | **Done** (block: all terms, accordion structure, sorted A-Z, w/ header recommend link) |
| Views: dei_podcasts | **Done** (block: 4 items w/ footer + page: /dei-toolkit/podcasts paged) |
| Controller | **Done** (DeiToolkitController::page assembles hero, views, spotlight) |
| Templates | **Done** (dei-toolkit-page, dei-toolkit-hero, dei-toolkit-spotlight) |
| JavaScript | **Done** (dei-toolkit.js: accordion/scroll/search, dei-toolkit-video.js: popup) |
| Event type modifications | **Done** (hook_install creates DEI term + updates view filter; hook_form_alter requires thumbnail for DEI events) |

### File Count: 79 files total (8 module root + 65 config/install + 1 controller + 3 templates + 2 JS)
