Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@ All notable changes to the Nynaeve theme will be documented in this file.

For project-wide changes (infrastructure, tooling, cross-cutting concerns), see the [project root CHANGELOG.md](../../../../../CHANGELOG.md).

## [2.15.0] - 2026-06-19

### Added

**Services Mega Menu:**
- Full-width desktop mega menu panel for the Services navigation item, activated by the `mega-menu` CSS class on any top-level menu item
- Four-column grid layout: three service-category columns with icon-enhanced links plus a featured CTA card
- Column dividers and group headings with accent bars styled in the Imagewize primary blue
- Hover bridge element ensuring the menu stays open while the cursor moves from the nav bar to the panel
- Featured CTA card with gradient background promoting Roots Stack quotes, linking to Contact and Services pages
- Mobile fallback reusing the existing checkbox accordion, so mobile navigation is unchanged

**Blade Components:**
- New `<x-mega-menu>` component rendering the desktop grid panel and mobile accordion from WordPress menu data
- New `<x-mega-icon>` component mapping per-item `icon-*` CSS classes to inline SVGs (10 icons: code, blocks, stack, support, cart, check, seo, speed, shield, server)

### Changed

**Navigation Markup:**
- Added `lg:relative` to the `<nav>` element so the mega menu panel anchors to the full header width instead of the narrow menu item
- Menu item labels now render with unescaped Blade output (`{!! !!}`) to support HTML entities in WordPress menu labels
- Top-level items with the `mega-menu` class use the new `<x-mega-menu>` component instead of the standard dropdown

### Technical

**CSS Architecture:**
- ~200 lines of desktop-only mega menu styles scoped inside `@media (min-width: 1024px)`, placed in section 3.4 of app.css
- Reuses existing `.submenu-list` reveal mechanics (opacity + pointer-events) from the base navigation styles
- Button and link color rules scoped under `#menu` to override inherited anchor styles",

## [2.14.2] - 2026-06-15

### Changed - Build tooling major version upgrades
Expand Down
11 changes: 10 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Contributors: jasperfrumau
Requires at least: 6.6
Tested up to: 6.9
Requires PHP: 8.3
Stable tag: 2.14.2
Stable tag: 2.15.0
License: MIT License
License URI: https://opensource.org/licenses/MIT

Expand All @@ -13,6 +13,15 @@ Nynaeve is the Imagewize.com production theme built on Sage 11 (Roots.io stack)

== Changelog ==

= 2.15.0 - 06/19/26 =
* ADDED: Services mega menu — full-width desktop dropdown with icon-enhanced service links in a four-column grid layout and a featured CTA card.
* ADDED: Blade components `<x-mega-menu>` and `<x-mega-icon>` for mega menu rendering and SVG icon mapping (10 service icons).
* ADDED: Hover bridge on mega menu panel preventing accidental close when moving cursor from nav bar to dropdown.
* CHANGED: Nav element receives `lg:relative` positioning to anchor mega menu panels to full header width.
* CHANGED: Menu item labels use unescaped Blade output to support HTML entities in WordPress menu labels.
* CHANGED: Top-level items with `mega-menu` CSS class now route to the mega menu component instead of the standard dropdown."


= 2.14.2 - 06/15/26 =
* CHANGED: Vite 8 upgrade - Migrated theme build pipeline from Vite 7 to Vite 8, switching from Rollup to the Rust-based Rolldown bundler for faster builds.
* CHANGED: Upgraded @roots/vite-plugin (v1.3.1 to v2.2.0) and laravel-vite-plugin (v2 to v3) for Vite 8 compatibility.
Expand Down
199 changes: 199 additions & 0 deletions resources/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,205 @@ footer a {
}
}

/* -----------------------------------------------------------------------------
* 3.4. Mega Menu (Services)
* -----------------------------------------------------------------------------
* Desktop-only full-width panel for the top-level item flagged `mega-menu`.
* Mobile (<lg) reuses the standard checkbox accordion — see section 12 — so
* nothing here applies below 1024px. Colours mirror the prototype at
* designs/nynaeve/mega-menu/services-mega-menu.html and theme.json.
* -------------------------------------------------------------------------- */
@media (min-width: 1024px) {
/* Anchor the panel to the full-width <nav> (which is lg:relative) instead of
the narrow menu item, so left/right:0 spans the header. */
#menu .my-menu-item.mega-menu {
position: static;
}

/* Full-bleed panel. Reuses .submenu-list reveal (opacity + pointer-events)
from section 12; here we only re-style the surface and size. */
.submenu-list.mega-menu-panel {
left: 0;
right: 0;
width: 100%;
min-width: 0;
margin-top: 0;
border-radius: 0;
border-top: 1px solid rgba(255, 255, 255, 0.08);
background: linear-gradient(180deg, #1d2330 0%, #161b25 100%);
box-shadow: 0 24px 50px -12px rgba(0, 0, 0, 0.55);
}

/* Hover bridge: the mega-menu li is position:static so the generic
.group.relative::after bridge (section 3.3) doesn't cover it.
This ::before extends upward from the panel into the gap, keeping
.group:hover active while the pointer travels down. */
.submenu-list.mega-menu-panel::before {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 100%;
height: 2.5rem;
}

.mega-menu-panel .mega-inner {
max-width: 72rem; /* matches max-w-6xl container */
margin: 0 auto;
padding: 2.125rem 2rem 2.375rem;
}

/* 3 menu columns + featured card. Group order is positional (see component):
1 -> col1, 2 -> col2/row1, 3 (Growth) -> col2/row2, 4 -> col3, feature -> col4. */
.mega-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1.05fr;
gap: 0.875rem;
}
.mega-grid > .mega-group:nth-child(1) { grid-column: 1; grid-row: 1; }
.mega-grid > .mega-group:nth-child(2) { grid-column: 2; grid-row: 1; }
.mega-grid > .mega-group:nth-child(3) { grid-column: 2; grid-row: 2; margin-top: 1.625rem; }
.mega-grid > .mega-group:nth-child(4) { grid-column: 3; grid-row: 1; }
.mega-grid > .mega-feature { grid-column: 4; grid-row: 1 / span 2; }

.mega-group { padding: 0.25rem 0.625rem; }
/* Column dividers between the three menu columns. */
.mega-grid > .mega-group:nth-child(2),
.mega-grid > .mega-group:nth-child(4) {
border-left: 1px solid rgba(255, 255, 255, 0.08);
}

.mega-heading {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.875rem;
font-family: var(--font-montserrat, 'Montserrat', sans-serif);
font-size: 0.6875rem;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #017cb6; /* primary */
}
.mega-heading .bar {
width: 1.125rem;
height: 2px;
border-radius: 2px;
background: #017cb6;
}

.mega-list { list-style: none; margin: 0; padding: 0; }
.mega-list li + li { margin-top: 2px; }

.mega-link {
display: flex;
align-items: flex-start;
gap: 0.6875rem;
padding: 0.5625rem 0.625rem;
border-radius: 0.5rem;
text-decoration: none;
transition: background 0.15s ease;
}
.mega-link:hover { background: rgba(255, 255, 255, 0.05); }

.mega-link .ic {
flex: 0 0 auto;
display: grid;
place-items: center;
width: 2.125rem;
height: 2.125rem;
border-radius: 0.5rem;
background: rgba(1, 124, 182, 0.12);
color: #017cb6;
transition: background 0.15s ease, color 0.15s ease;
}
.mega-link:hover .ic { background: #017cb6; color: #ffffff; }
.mega-link .ic svg { width: 1.125rem; height: 1.125rem; }

.mega-link .txt { display: flex; flex-direction: column; }
#menu .mega-link .name {
color: #eef1f5;
font-size: 0.875rem;
font-weight: 700;
line-height: 1.3;
text-transform: none;
}
#menu .mega-link:hover .name { color: #ffffff; }
.mega-link .desc {
margin-top: 2px;
color: #8b93a3;
font-size: 0.75rem;
line-height: 1.45;
}

/* ---- Featured / CTA column ---- */
.mega-feature {
display: flex;
flex-direction: column;
height: 100%;
padding: 1.5rem 1.375rem;
border: 1px solid rgba(255, 255, 255, 0.10);
border-radius: 0.875rem;
background:
radial-gradient(ellipse at top right, rgba(1, 124, 182, 0.35) 0%, transparent 60%),
linear-gradient(160deg, #0d2438 0%, #102b44 50%, #026492 100%);
}
.mega-feature .eyebrow {
margin-bottom: 0.625rem;
font-family: var(--font-montserrat, 'Montserrat', sans-serif);
font-size: 0.6875rem;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #7fd0f3;
}
.mega-feature h3 {
margin-bottom: 0.625rem;
font-family: var(--font-montserrat, 'Montserrat', sans-serif);
color: #ffffff;
font-size: 1.25rem;
font-weight: 800;
line-height: 1.25;
}
.mega-feature p {
margin-bottom: 1.25rem;
color: #cfe1ef;
font-size: 0.8125rem;
line-height: 1.6;
}
.mega-cta-row {
margin-top: auto;
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.mega-feature .btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1.125rem;
border-radius: 0.5rem;
font-size: 0.8125rem;
font-weight: 700;
text-transform: none;
text-decoration: none;
transition: transform 0.12s ease, background 0.15s ease, box-shadow 0.15s ease;
}
#menu .mega-feature .btn-primary {
background: #017cb6;
color: #ffffff;
box-shadow: 0 8px 20px -6px rgba(1, 124, 182, 0.7);
}
#menu .mega-feature .btn-primary:hover { background: #028fd1; color: #ffffff; transform: translateY(-1px); }
#menu .mega-feature .btn-ghost {
background: rgba(255, 255, 255, 0.08);
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.18);
}
#menu .mega-feature .btn-ghost:hover { background: rgba(255, 255, 255, 0.16); color: #ffffff; }
}

/* -----------------------------------------------------------------------------
* 4. Code & Block Styling
* -------------------------------------------------------------------------- */
Expand Down
45 changes: 45 additions & 0 deletions resources/views/components/mega-icon.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@props(['classes' => ''])

{{--
Maps a per-item `icon-*` CSS class (set on the menu item in nav-menus.php)
to an inline SVG. Graceful no-match: no icon class -> nothing rendered.
Icon set mirrors designs/nynaeve/mega-menu/services-mega-menu.html.
--}}

@php
preg_match('/icon-[a-z]+/', $classes ?? '', $matches);
$icon = $matches[0] ?? null;
@endphp

@switch($icon)
@case('icon-code')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
@break
@case('icon-blocks')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
@break
@case('icon-stack')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2v20M2 7l10 5 10-5M2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
@break
@case('icon-support')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
@break
@case('icon-cart')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
@break
@case('icon-check')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
@break
@case('icon-seo')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M23 6l-9.5 9.5-5-5L1 18"/><path d="M17 6h6v6"/></svg>
@break
@case('icon-speed')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
@break
@case('icon-shield')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
@break
@case('icon-server')
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 10h.01M6 10h.01"/><rect x="2" y="4" width="20" height="8" rx="2"/><path d="M6 16h12a2 2 0 0 1 2 2v0a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v0a2 2 0 0 1 2-2z"/></svg>
@break
@endswitch
82 changes: 82 additions & 0 deletions resources/views/components/mega-menu.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@props(['item'])

{{--
Mega menu: an alternate renderer for ONE flagged top-level menu item
(CSS class `mega-menu`). All link data still lives in the WordPress menu and
is edited in nav-menus.php — this only changes how it is displayed.

Root reuses `.submenu-list` so the existing CSS does the work for free:
- mobile (<lg): the parent checkbox accordion expands this panel
- desktop (lg+): `.group:hover > .submenu-list` reveals it + the hover bridge

Two renderings of the same `$item->children`:
- Desktop grid (`.mega-grid`, hidden on mobile): labelled columns with icons,
descriptions and a hardcoded featured CTA card (column 4).
- Mobile accordion (`lg:hidden`): recurses <x-menu-item> so group headers
become the same collapsible rows used everywhere else — one behaviour,
no forked accordion logic.

Column layout (see app.css "3.4 Mega Menu") mirrors the prototype and assumes
the documented group order: Build & Develop · WooCommerce · Growth ·
Performance & Security (Growth stacks under WooCommerce in column 2).
--}}

@php($groups = is_array($item->children) ? $item->children : [])

<ul class="submenu-list mega-menu-panel
grid grid-rows-[0fr] lg:block!
transition-[grid-template-rows] lg:transition-none duration-500 ease-in-out
text-sm text-white"
role="menu"
aria-label="{{ $item->label }} submenu">
<div class="overflow-hidden lg:overflow-visible">

{{-- ===== Desktop grid panel ===== --}}
<div class="mega-inner hidden lg:block">
<div class="mega-grid">
@foreach ($groups as $group)
<div class="mega-group">
<p class="mega-heading"><span class="bar"></span>{!! $group->label !!}</p>
@if (is_array($group->children) && count($group->children))
<ul class="mega-list">
@foreach ($group->children as $link)
<li>
<a href="{{ $link->url }}" class="mega-link" role="menuitem"
@if ($link->target) target="{{ $link->target }}" @endif>
<span class="ic"><x-mega-icon :classes="$link->classes ?? ''" /></span>
<span class="txt">
<span class="name">{!! $link->label !!}</span>
@if (!empty($link->description))
<span class="desc">{!! $link->description !!}</span>
@endif
</span>
</a>
</li>
@endforeach
</ul>
@endif
</div>
@endforeach

{{-- Featured CTA card — hardcoded by design (a WP menu can't hold rich card copy). --}}
<div class="mega-feature">
<p class="eyebrow">Not sure where to start?</p>
<h3>Built on the Roots Stack</h3>
<p>Tell us about your project and get a tailored quote for a Trellis / Bedrock setup — no commitment required.</p>
<div class="mega-cta-row">
<a href="{{ home_url('/contact/') }}" class="btn btn-primary">Get a Roots Stack Quote</a>
<a href="{{ home_url('/services/roots-stack-development/') }}" class="btn btn-ghost">View Pricing</a>
</div>
</div>
</div>
</div>

{{-- ===== Mobile accordion (reuses the standard nested menu) ===== --}}
<div class="lg:hidden">
@foreach ($groups as $group)
<x-menu-item :item="$group" :level="1" />
@endforeach
</div>

</div>
</ul>
Loading
Loading