# LiveRoof Projects/Portfolio System - Complete Technical Documentation

**Last Updated:** January 18, 2026
**Plugin Version:** 2026.01.18
**Status:** Production Active
**Author:** Jon Eberly

## System Overview

The LiveRoof Projects/Portfolio system is a custom WordPress implementation built on top of the **Media Grid** plugin (v9.1.1) and its **Advanced Filters add-on** (v2.0.0). The system creates a filterable, searchable portfolio grid with detailed project modal lightbox functionality.

**Key Components:**
- Custom Plugin: `/wp-content/plugins/liveroof-custom/`
- Child Theme: `/wp-content/themes/childavada/`
- Parent Theme: Avada
- Dependencies: Media Grid, Vue.js 2.x, Vue Material

---

## 1. Architecture Overview

### Plugin Structure

```
liveroof-custom/
├── liveroof-custom.php          # Main plugin file
├── README.md                     # This file
└── nmg/                          # New Media Grid system
    ├── nmg.php                   # ✅ ACTIVE: Current implementation
    ├── nmg.js                    # ✅ ACTIVE: Legacy shim loader
    ├── nmg.718cf45476f683ce162ebbe52e92b42e.js    # ✅ ACTIVE: Main app
    ├── nmg.718cf45476f683ce162ebbe52e92b42e.js.bak # Backup
    ├── search.html               # ✅ ACTIVE: Vue template
    ├── style.css                 # ✅ ACTIVE: Grid styling
    └── vue/                      # Vue.js framework
        ├── js/
        │   ├── vue.js
        │   └── vue-material.min.js
        └── css/
            ├── default.css
            └── vue-material.min.css
```

### Child Theme Integration

```
childavada/
├── functions.php                # ✅ Enqueues CSS, removes old handlers
├── nmg-lightbox.css             # ✅ Lightbox styling (2026-01-13)
└── style.css                    # Child theme base styles
```

---

## 2. Data Flow Architecture

### Complete Pipeline

```
WordPress Database (mg_items posts)
          ↓
AJAX Handler: admin-ajax.php?action=liveroof_nmg_items
          ↓
PHP Processing (nmg.php): Query all mg_items + meta + taxonomies
          ↓
JSON Response: [{id, title, content, meta{}, terms{}}]
          ↓
Vue.js App (nmg.718*.js): Parse, filter, render
          ↓
User Interface: Grid + Filters + Lightbox
```

### WordPress Data Model

**Post Type:** `mg_items` (registered by Media Grid)

**Taxonomies:**
- `mgaf_country` - Country
- `mgaf_state` - State/Province
- `mgaf_public_access` - Public/Private
- `mgaf_active` - Active status
- `mgaf_existing` - Retrofit designation
- `mgaf_module_type` - Module type
- `mgaf_type` - Showcase type
- `mgaf_options` - Additional options
- `mgaf_leed` - LEED certification
- `mgaf_grower` - LiveRoof representative
- `mgaf_roofblue` - RoofBlue participation
- `mgaf_solagreen` - SolaGreen participation

**Meta Fields:**
- `mg_img_gallery_image-1,2,3` - Gallery images (URLs)
- `mg_img_gallery_city` - City
- `mg_img_gallery_dateinstalled` - Installation date
- `mgaf_size` - Project size (sq ft)

---

## 3. JavaScript Architecture

### File: nmg.js (Legacy Shim)

**Purpose:** Dynamically loads the main application JavaScript

**Key Features:**
- Checks for `wpAjax` global (AJAX URL)
- Prevents double-loading with `__NMG_LEGACY_LOADED__` flag
- Adds cache-busting timestamp: `?v=Date.now()`

**Dependencies:** jQuery

---

### File: nmg.718cf45476f683ce162ebbe52e92b42e.js (Main Application)

**Purpose:** Complete Vue.js grid system with filtering and modal display

**Execution:** `jQuery(document).ready()`

#### Core Data Structures

```javascript
items[]       // All portfolio items from AJAX
unfiltered[]  // Array of all item indices
filter[]      // Current filtered item indices
offset        // Pagination offset (24 items per page)
limit         // Items per page (24)
search_filters[] // Active search terms
size_min, size_max // Size range filters
vm            // Vue instance
```

#### Critical Functions

**1. handleItems(response, vm)**
- Processes AJAX response
- Builds taxonomy index for fast filtering
- Extracts size from meta
- Initializes state from URL parameters

**2. filter_items(vm)**
- Applies taxonomy filters (intersection logic)
- Applies search filters (title + content + city)
- Applies size range filters
- Updates `filter[]` array with matching indices

**3. display(vm, location_change, paramDisplay)**
- Renders grid items from `filter[]`
- Creates `.mgridnew_display_item` elements
- Attaches click handlers to open lightbox
- Handles pagination
- Supports deep linking via `?displayItem=ID`
- Updates URL with `history.pushState()`

**4. dialoagDisplayItem(d, item, current_dialog)** ⭐ **CRITICAL**
- Populates lightbox modal with project data
- Builds image carousel from `mg_img_gallery_image-*`
- Handles image navigation (prev/next)
- Sets up close handlers
- Updates URL with `?displayItem=ID`
- Manages browser history

#### Vue Component

```javascript
Vue.component('nmg-filter-select', {
  // Material Design dropdown filter
  // Emits update:value on selection change
})

new Vue({
  el: '#searchApp',
  data: { search, sizeMin, sizeMax, terms, filters{...} },
  methods: {
    onChange()      // Filter change handler
    searchChange()  // Search input handler
    minMaxChange()  // Size range handler
    p_prev()        // Previous page
    p_next()        // Next page
  }
})
```

#### Lightbox HTML Template

```javascript
var nmg_dialog = `
<div id="nmg_lb_wrap" class="nmg-lightbox">
    <div class="nmg-overlay"></div>
    <div class="nmg-modal">
        <button class="nmg-close">&times;</button>
        <div class="nmg-slider">
            <button class="nmg-prev">&#10094;</button>
            <img class="nmg_dialog_img" src="">
            <button class="nmg-next">&#10095;</button>
        </div>
        <div class="mg_item_content">
            <!-- Project details populated here -->
        </div>
    </div>
</div>
`;
```

---

## 4. CSS Architecture

### Layer 1: style.css (Grid Layout)

**Purpose:** Grid, item cards, pagination

**Key Classes:**
- `.mgridnew_search` - Filter container
- `.mgridnew_search_field` - Individual filter (237px)
- `.mgridnew_display` - Grid container (flexbox)
- `.mgridnew_display_item` - Card (250x250px)
- `.mgridnew_display_item_title` - Title overlay
- `.mgridnew_pagination` - Pagination controls

---

### Layer 2: nmg-lightbox.css (Lightbox Override) ⭐ **CRITICAL**

**Purpose:** Force lightbox visibility, override Media Grid/Avada hiding

**Created:** January 13, 2026

**Critical Overrides:**
```css
#nmg_lb_wrap.nmg-lightbox {
  display: flex !important;
  visibility: visible !important;
  opacity: 1 !important;
  position: fixed !important;
  z-index: 999999 !important;
  /* ... */
}
```

**Z-Index Stack:**
- 999999: Lightbox wrapper
- 10: Close button
- 5: Nav arrows
- 2: Modal content
- 1: Overlay backdrop

---

### Stylesheet Load Order

```
1. Google Fonts (Material Icons)
2. Vue Material core CSS
3. Vue Material theme CSS
4. NMG grid styles (style.css)
5. Lightbox override CSS (nmg-lightbox.css) [time() cache bust]
6. Avada parent theme
```

---

## 5. AJAX Architecture

### Endpoint: `liveroof_nmg_items`

**URL:** `/wp-admin/admin-ajax.php?action=liveroof_nmg_items`

**Method:** POST

**Authentication:** Both public and authenticated (wp_ajax + wp_ajax_nopriv)

**Handler:** `nmg.php:101-139`

**Response Format:**
```json
[
  {
    "id": 12345,
    "title": "Project Name",
    "content": "Description HTML",
    "meta": {
      "mg_img_gallery_city": ["New York"],
      "mg_img_gallery_dateinstalled": ["2023-06-15"],
      "mg_img_gallery_image-1": ["https://...jpg"],
      "mg_img_gallery_image-2": ["https://...jpg"],
      "mg_img_gallery_image-3": ["https://...jpg"],
      "mgaf_size": ["500"]
    },
    "terms": {
      "mgaf_country": ["USA"],
      "mgaf_state": ["New York"],
      "mgaf_grower": ["John Doe"],
      "mgaf_type": ["Residential"]
    }
  }
]
```

**Query Logic:**
```php
WP_Query([
    'post_type' => 'mg_items',
    'posts_per_page' => -1,  // ALL items
    'orderby' => 'date',
    'order' => 'DESC'
])

// Returns all posts with all meta + all taxonomies
```

⚠️ **Performance Note:** No caching - every request hits database

---

## 6. Filtering System

### Filter Types

1. **Taxonomy Filters** - Dropdown selects (intersection logic)
2. **Search Filter** - Text input (matches title/content/city)
3. **Size Range** - Min/max numerical inputs

### Filter Logic

```javascript
// 1. Start with all items or first selected filter
filter = terms[taxonomy][selected_value]

// 2. Intersect with additional filters
filter = filter.filter(idx => terms[tax2][val2].includes(idx))

// 3. Apply search (AND logic for multiple terms)
for (term of search_terms) {
    filter = filter.filter(idx =>
        (title + content + city).toLowerCase().includes(term)
    )
}

// 4. Apply size range
if (size_min) filter = filter.filter(idx => items[idx].size > size_min)
if (size_max) filter = filter.filter(idx => items[idx].size < size_max)
```

---

## 7. URL State Management

### URL Parameters

```
/projects/?mgaf_country=USA&mgaf_state=California&_search=solar&_min=500&_max=5000&_offset=24&displayItem=12345
```

**Parameters:**
- `mgaf_*` - Taxonomy filters
- `_search` - Search terms
- `_min`, `_max` - Size range
- `_offset` - Pagination
- `displayItem` - Open specific project modal

### Implementation

```javascript
// Save state
history.pushState(state, '', newUrl)

// Load state on page load
load_state(deparam(window.location.search), vm)

// Handle back/forward
window.addEventListener('popstate', (event) => {
    load_state(event.state, vm)
})

// Deep linking
if (params.displayItem) {
    // Open modal for item ID
}
```

---

## 8. Lightbox System (Recent Fix - Jan 13, 2026)

### Problem

Avada theme and Media Grid plugin were hiding the lightbox via:
- `display: none`
- `visibility: hidden`
- `top: -10000px`
- DOM mutations

### Solution

**1. Remove Old Handler** (childavada/functions.php)
```php
add_action('wp_loaded', function () {
    remove_action('wp_loaded', 'mg_ajax_lightbox', 999);
}, 1);
```

**2. Force Visibility** (nmg-lightbox.css)
```css
#nmg_lb_wrap.nmg-lightbox {
    display: flex !important;
    visibility: visible !important;
    position: fixed !important;
    z-index: 999999 !important;
}
```

**3. Client-Side Rendering** (nmg.718*.js)
```javascript
display_item.click(function(e) {
    jQuery("#nmg_lb_wrap").remove();  // Clear old
    d = jQuery(nmg_dialog);            // Create new
    dialoagDisplayItem(d, item, i);    // Populate
    jQuery("body").append(d);          // Display
});
```

### Modal Features

- Image carousel with prev/next navigation
- Project metadata (location, size, date, grower)
- Features display
- Copy URL to clipboard
- Close on overlay click or X button
- URL updates with `?displayItem=ID`
- Browser back/forward support

---

## 9. Performance Considerations

### Current Implementation

**Issues:**
- No server-side caching
- Fetches ALL items on every AJAX call
- All meta + all taxonomies for every post
- Client holds all data in memory

**Client-Side:**
- 24 items per page (pagination)
- Filtering happens in-browser (fast)
- No lazy loading

### Potential Improvements

1. **Server-Side Caching** (like nmg_p1.php had):
```php
$cache = wp_cache_get("nmg_items", "liveroof_custom");
if (!$cache) {
    // Build data
    wp_cache_set("nmg_items", $cache, "liveroof_custom", 86400);
}
```

2. **AJAX Pagination** - Return only 24 items at a time

3. **Lazy Image Loading** - Load grid images on scroll

4. **JSON Compression** - Reduce payload size

---

## 10. Script & Style Enqueuing

### Scripts (nmg.php)

```php
wp_enqueue_script('nmg-vue', 'vue/js/vue.js');
wp_enqueue_script('nmg-vue-material', 'vue/js/vue-material.min.js', ['nmg-vue']);
wp_enqueue_script('nmg', 'nmg.js', ['jquery', 'nmg-vue-material'], time());

wp_localize_script('nmg', 'wpAjax', [
    'ajaxurl' => admin_url('admin-ajax.php')
]);
```

### Styles (nmg.php)

```php
wp_enqueue_style('nmg-google-fonts', 'https://fonts.googleapis.com/...');
wp_enqueue_style('nmg-vue-material-css', 'vue/css/vue-material.min.css');
wp_enqueue_style('nmg-vue-material-theme', 'vue/css/default.css');
wp_enqueue_style('nmg-style', 'style.css');
```

### Child Theme (functions.php)

```php
wp_enqueue_style('nmg-lightbox',
    get_stylesheet_directory_uri() . '/nmg-lightbox.css',
    [],
    time()  // Cache bust
);
```

---

## 11. Debugging

### Console Logs

```javascript
console.log("✅ NMG.js loaded (legacy shim)")
console.log("✅ wpAjax present:", wpAjax.ajaxurl)
console.log("✅ Legacy NMG script executed")
console.log("🔥 EDITED VERSION RUNNING", item.id)
console.log("NMG images resolved:", images)
```

### Common Issues

| Issue | Cause | Solution |
|-------|-------|----------|
| Modal not showing | Theme hiding | Check nmg-lightbox.css loaded |
| Filters not working | AJAX failed | Verify wpAjax.ajaxurl in console |
| Images not loading | URL encoding | Check encodeURI() usage |
| Deep links broken | URL params | Verify deparam() function |
| Pagination broken | offset undefined | Check initialization |

### Browser Cache Issues

If changes don't appear:
1. Hard refresh (Ctrl+Shift+R / Cmd+Shift+R)
2. Clear browser cache
3. Check .htaccess JavaScript caching rules
4. Verify cache-busting parameters (?v=timestamp)

---

## 12. File Ownership Summary

| File | Status | Purpose |
|------|--------|---------|
| `nmg.php` | ✅ **ACTIVE** | Plugin configuration |
| `nmg.js` | ✅ **ACTIVE** | Legacy loader shim |
| `nmg.718*.js` | ✅ **ACTIVE** | Main application |
| `search.html` | ✅ **ACTIVE** | Vue template |
| `style.css` | ✅ **ACTIVE** | Grid styling |
| `nmg-lightbox.css` | ✅ **ACTIVE** | Lightbox override (new) |
| `functions.php` | ✅ **ACTIVE** | Theme integration |

---

## 13. Integration with Media Grid

### Dependencies

- **Post Type:** `mg_items` (Media Grid)
- **Taxonomies:** All `mgaf_*` (Advanced Filters add-on)
- **Meta Fields:** `mg_img_gallery_*` (Media Grid)

### Conflicts Resolved

- ✅ Removed Media Grid's default lightbox handler
- ✅ Custom lightbox replaces Media Grid modal
- ✅ Override CSS prevents theme interference

### No Custom Database Tables

All data stored in standard WordPress schema:
- `wp_posts`
- `wp_postmeta`
- `wp_terms`, `wp_term_taxonomy`, `wp_term_relationships`

---

## 13a. Project Submission System (USP Pro)

### Overview

Public users can submit new projects via a form at `/projects/share-project/`. This uses the **USP Pro** plugin to handle form submissions.

### Form Configuration

- **Form ID:** 4483
- **Post Type:** Submissions create `mg_items` posts
- **Post Status:** Draft (pending admin review)
- **Location:** `/projects/share-project/`

### Form Fields (Taxonomy Shortcodes)

```
[usp_taxonomy tax="mgaf_country" ...]      - Country
[usp_taxonomy tax="mgaf_state" ...]        - State
[usp_taxonomy tax="mgaf_public_access" ...] - Public Access
[usp_taxonomy tax="mgaf_existing" ...]     - Retrofit
[usp_taxonomy tax="mgaf_grower" ...]       - Grower
[usp_taxonomy tax="mgaf_type" ...]         - Showcase Type
[usp_taxonomy tax="mgaf_module_type" ...]  - Module Type
[usp_taxonomy tax="mgaf_options" ...]      - Options (checkboxes)
[usp_taxonomy tax="mgaf_leed" ...]         - LEED Certification
[usp_taxonomy tax="mgaf_roofblue" ...]     - RoofBlue (checkbox)
[usp_taxonomy tax="mgaf_solagreen" ...]    - SolaGreen (checkbox)
```

### Email Notifications

USP Pro has built-in email notification support:
- **Admin alerts** when new submissions arrive
- **User confirmation** emails on submission
- **Approval/denial** notifications when post status changes

**Configuration:** WP Admin → USP Pro → Email Alerts

**Available Placeholders:**
- `%%post_title%%` - Project name
- `%%post_author%%` - Submitter name
- `%%user_email%%` - Submitter email
- `%%post_custom%%` - All custom field data
- `%%edit_link%%` - Admin edit link
- `%%post_submitted_date%%` - Submission timestamp

### Admin Workflow

1. User submits project → Creates draft `mg_items` post
2. Admin receives email notification (if configured)
3. Admin reviews at **Media Grid → Items** (filter by Draft status)
4. Admin publishes approved submissions
5. Project appears on /projects/ page

### Important Notes

- **Use Media Grid → Items** for project management (NOT Portfolio menu)
- Portfolio (`avada_portfolio`) is a separate, legacy post type
- The standalone file `/custom/share-project.php` is NOT the live form

---

## 14. Recent Changes (January 2026)

### January 18, 2026 - SolaGreen Feature & Filter Improvements

**New Features:**

1. **SolaGreen Filter**
   - Added SolaGreen taxonomy filter to the /projects/ page
   - Works identically to existing RoofBlue filter
   - Users can filter projects that feature SolaGreen technology

2. **Project Size Filter Refinement**
   - Changed size filter step from 500 sq ft to **10 sq ft** increments
   - Provides more precise filtering for smaller projects

3. **Lightbox Feature Display Improvements**
   - RoofBlue and SolaGreen labels now only display when value is "Yes"
   - Previously showed labels even when feature was not applicable

4. **Project Submission Form Updates**
   - Added RoofBlue and SolaGreen checkboxes to USP Pro form (ID 4483)
   - Located at `/projects/share-project/`
   - Both options appear under the "Options" column

**Files Modified:**

1. **nmg.718cf45476f683ce162ebbe52e92b42e.js**
   - Added `mgaf_solagreen: ''` to Vue.js filters data model
   - Updated `terms_base` array to include `mgaf_solagreen`
   - Modified lightbox display logic for conditional RoofBlue/SolaGreen display
   - Changed size filter step from 500 to 10

2. **search.html**
   - SolaGreen filter dropdown already present (verified)

3. **USP Form ID 4483** (WordPress database)
   - Added shortcodes for RoofBlue and SolaGreen taxonomy fields

**Admin Clarification:**
- Project management should use **Media Grid → Items** (not Portfolio menu)
- Portfolio menu can be disabled via Avada → Theme Options → Advanced → Post Types

---

### January 13, 2026 - Lightbox Fix

**Problem:** Lightbox not displaying when clicking projects

**Root Cause:**
- Avada theme applying `visibility: hidden; top: -10000px`
- Media Grid AJAX handler conflict
- Browser caching preventing JavaScript updates

**Changes Made:**

1. **nmg.718cf45476f683ce162ebbe52e92b42e.js**
   - Restored click handler to directly create/display modal
   - Removed reliance on `window.NMG_OpenLightbox()` stub

2. **nmg-lightbox.css** (NEW)
   - Created comprehensive lightbox styling
   - Used `!important` to override theme hiding
   - Set z-index to 999999

3. **functions.php**
   - Removed Media Grid lightbox handler
   - Enqueued nmg-lightbox.css with time() cache-busting

4. **nmg.js**
   - Added `Date.now()` cache-busting to dynamic script loading

5. **nmg.php**
   - Added `time()` cache-busting to nmg.js enqueue

6. **.htaccess**
   - Temporarily disabled JavaScript caching for development

**Result:** ✅ Lightbox now displays correctly with all project data

---

## 15. System Architecture Diagram

```
┌─────────────────────────────────────────────────┐
│         LiveRoof Portfolio System                │
├─────────────────────────────────────────────────┤
│                                                   │
│  WordPress Database (mg_items)                   │
│           ↓                                       │
│  AJAX: liveroof_nmg_items                        │
│           ↓                                       │
│  JSON Response (all items + meta + terms)        │
│           ↓                                       │
│  Vue.js App (nmg.718*.js)                        │
│    ├─ Parse data                                 │
│    ├─ Build taxonomy index                       │
│    ├─ Filter pipeline                            │
│    ├─ Render grid                                │
│    └─ Handle modal                               │
│           ↓                                       │
│  User Interface                                  │
│    ├─ Filter dropdowns (Vue Material)            │
│    ├─ Grid (250px cards, 24/page)                │
│    └─ Lightbox (image carousel + details)        │
│                                                   │
└─────────────────────────────────────────────────┘
```

---

## 16. Maintenance Notes

### Before Making Changes

1. **Backup files** - especially nmg.718*.js
2. **Test in staging** if available
3. **Clear all caches** after deployment
4. **Check browser console** for errors

### Cache Management

**Development:**
```
- Disable .htaccess JavaScript caching
- Use time() for PHP enqueues
- Use Date.now() for JS dynamic loading
```

**Production:**
```
- Re-enable .htaccess caching
- Use version numbers instead of time()
- Implement server-side caching
```

### Future Enhancements

- [ ] Add server-side caching (WP Object Cache)
- [ ] Implement AJAX pagination
- [ ] Add lazy loading for images
- [ ] Optimize JSON payload size
- [ ] Add loading states/spinners
- [ ] Improve mobile responsiveness
- [ ] Add image alt text for accessibility
- [ ] Consider CDN for Vue.js dependencies

---

## 17. Support & Documentation

**Plugin Author:** LiveRoof (Custom Development)
**Last Updated:** January 18, 2026
**WordPress Version:** 6.x compatible
**PHP Version:** 7.4+ required

**Dependencies:**
- Media Grid Plugin (v9.1.1+)
- Media Grid - Advanced Filters (v2.0.0+)
- USP Pro (User Submitted Posts Pro) - Project submission form
- WP Mail SMTP - Email delivery
- Avada Theme (parent)
- jQuery (WordPress core)
- Vue.js 2.x (bundled)
- Vue Material (bundled)

**For questions or issues:**
- Check browser console for JavaScript errors
- Verify AJAX endpoint returns valid JSON
- Confirm all dependencies are active
- Clear all caches (browser, WordPress, server)

---

**End of Documentation**
