Hamburger menu

This commit is contained in:
Mork Swork 2026-05-14 21:23:54 -07:00
parent 4ab06a69b5
commit 4649c56a58
7 changed files with 297 additions and 99 deletions

View file

@ -10,7 +10,7 @@ services:
DB_NAME: drupal
DB_USER: drupal
DB_PASS: drupal
SITE_NAME: "Portfolio"
SITE_NAME: "Riverside Therapeutics"
ADMIN_PASS: "${ADMIN_PASS:-admin}"
HASH_SALT: "${HASH_SALT:-replace-this-in-production-with-a-long-random-string}"
POSTMARK_API_KEY: "${POSTMARK_API_KEY:-}"

View file

@ -63,5 +63,7 @@ else
echo "[entrypoint] No config to import, continuing."
fi
$DRUSH cache:rebuild >/dev/null 2>&1 && echo "[entrypoint] Cache rebuilt."
echo "[entrypoint] Starting services..."
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf

View file

@ -1,119 +1,194 @@
/* ── Hide Olivero's sticky-header toggle ─────────────────── */
/* ── Hide Olivero's built-in header entirely ─────────────── */
.site-header,
.sticky-header-toggle {
display: none;
display: none !important;
}
/* ── Header: let the nav take all remaining width ────────── */
.site-header__inner__container {
/* Push page content below the fixed header */
body {
padding-top: 80px;
}
/* ── Custom header shell ─────────────────────────────────── */
.rpt-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
height: 80px;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.rpt-header__inner {
display: flex;
align-items: center;
height: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
gap: 2rem;
}
.header-nav {
/* ── Brand / site name ───────────────────────────────────── */
.rpt-header__brand {
font-size: 1.125rem;
font-weight: 700;
color: #1e3a5f;
text-decoration: none;
white-space: nowrap;
flex-shrink: 0;
}
.rpt-header__brand:hover {
color: #1e3a8a;
}
/* ── Nav list ────────────────────────────────────────────── */
.rpt-header__nav {
flex: 1;
min-width: 0;
}
/* ── Primary nav: flat horizontal list ──────────────────── */
.primary-nav__menu--level-1 {
display: flex !important;
.rpt-header__list {
display: flex;
align-items: center;
list-style: none;
margin: 0;
padding: 0;
gap: 0.25rem;
}
.rpt-header__item--cta {
margin-left: auto;
}
/* ── Nav links ───────────────────────────────────────────── */
.rpt-header__link {
display: block;
font-size: 0.9375rem;
font-weight: 400;
color: #374151;
text-decoration: none;
padding: 0.25rem 0.875rem;
}
.rpt-header__link:hover,
.rpt-header__link:focus {
color: #1e3a8a;
}
.rpt-header__link.is-active {
color: #1e3a8a;
font-weight: 500;
}
/* ── CTA button ──────────────────────────────────────────── */
.rpt-header__cta {
display: inline-block;
padding: 0.5rem 1.25rem;
background: #1e3a5f;
color: #fff;
font-size: 0.9rem;
font-weight: 500;
text-decoration: none;
border-radius: 4px;
border: 1.5px solid #1e3a5f;
transition: background 0.15s, border-color 0.15s;
white-space: nowrap;
}
.rpt-header__cta:hover,
.rpt-header__cta:focus {
background: #152a45;
border-color: #152a45;
color: #fff;
}
/* ── Hamburger button (hidden on desktop) ────────────────── */
.rpt-header__hamburger {
display: none;
flex-direction: column;
justify-content: center;
gap: 6px;
width: 44px;
height: 44px;
padding: 8px;
background: none;
border: none;
cursor: pointer;
flex-shrink: 0;
margin-left: auto;
}
.rpt-header__hamburger span {
display: block;
height: 4px;
background: #1e3a5f;
border-radius: 3px;
transition: transform 0.25s ease, opacity 0.2s ease, width 0.25s ease;
}
.rpt-header__hamburger span:nth-child(1) { width: 45%; }
.rpt-header__hamburger span:nth-child(2) { width: 100%; }
.rpt-header__hamburger span:nth-child(3) { width: 45%; align-self: flex-end; }
/* Animate to X when open */
.rpt-header__hamburger[aria-expanded="true"] span:nth-child(1) {
width: 100%;
margin: 0 !important;
padding: 0 !important;
border: none !important;
background: none !important;
transform: translateY(10px) rotate(45deg);
}
.rpt-header__hamburger[aria-expanded="true"] span:nth-child(2) {
opacity: 0;
}
.rpt-header__hamburger[aria-expanded="true"] span:nth-child(3) {
width: 100%;
transform: translateY(-10px) rotate(-45deg);
}
.primary-nav__menu-item--level-1 {
border: none !important;
background: none !important;
padding: 0 !important;
margin: 0 !important;
/* ── Mobile layout ───────────────────────────────────────── */
@media (max-width: 768px) {
.rpt-header__hamburger {
display: flex !important;
}
/* Plain text nav links */
.primary-nav__menu-link--level-1 {
font-size: 0.9375rem !important;
font-weight: 400 !important;
color: #374151 !important;
padding: 0.25rem 0.875rem !important;
border: none !important;
background: none !important;
text-decoration: none !important;
border-radius: 0 !important;
.rpt-header__nav {
position: absolute;
top: 80px;
left: 0;
right: 0;
background: #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 0.3s ease, opacity 0.25s ease;
}
.primary-nav__menu-link--level-1:hover,
.primary-nav__menu-link--level-1:focus {
color: #1e3a8a !important;
background: none !important;
border: none !important;
text-decoration: none !important;
.rpt-header__nav.is-open {
max-height: 500px;
opacity: 1;
}
/* Active trail */
.primary-nav__menu-link--level-1.primary-nav__menu-link--active-trail {
color: #1e3a8a !important;
font-weight: 500 !important;
.rpt-header__list {
flex-direction: column;
align-items: stretch;
gap: 0;
}
/* ── Push CTA group to the far right ────────────────────── */
.primary-nav__menu-item--level-1:has(> .nav-cta) {
margin-left: auto !important;
.rpt-header__item--cta {
margin-left: 0;
padding: 0.5rem 1.5rem;
}
.primary-nav__menu-item--level-1:has(> .nav-cta)
~ .primary-nav__menu-item--level-1:has(> .nav-cta) {
margin-left: 0.5rem !important;
.rpt-header__link {
padding: 0.75rem 1.5rem;
font-size: 1rem;
}
/* ── CTA buttons — rectangular ──────────────────────────── */
.primary-nav__menu-link.nav-cta {
display: inline-block !important;
padding: 0.5rem 1.25rem !important;
border-radius: 4px !important;
font-size: 0.9rem !important;
font-weight: 500 !important;
text-decoration: none !important;
transition: background 0.15s, color 0.15s, border-color 0.15s;
.rpt-header__cta {
display: block;
text-align: center;
}
/* Contact: outlined */
.primary-nav__menu-link.nav-cta:not(.nav-cta--primary) {
background: transparent !important;
color: #1e3a5f !important;
border: 1.5px solid #1e3a5f !important;
}
.primary-nav__menu-link.nav-cta:not(.nav-cta--primary):hover,
.primary-nav__menu-link.nav-cta:not(.nav-cta--primary):focus {
background: #1e3a5f !important;
color: #fff !important;
}
/* Book An Appointment: filled */
.primary-nav__menu-link.nav-cta--primary {
background: #1e3a5f !important;
color: #fff !important;
border: 1.5px solid #1e3a5f !important;
}
.primary-nav__menu-link.nav-cta--primary:hover,
.primary-nav__menu-link.nav-cta--primary:focus {
background: #152a45 !important;
border-color: #152a45 !important;
color: #fff !important;
}
/* ── Inner span inside each link — remove Olivero padding ── */
.primary-nav__menu-link-inner--level-1 {
padding: 0 !important;
}
/* ── Hide search, login, and site name from header ──────── */
.block-search-narrow,
.secondary-nav,
.site-branding__name,
.site-branding__slogan {
display: none !important;
}

View file

@ -0,0 +1,28 @@
(function () {
'use strict';
document.addEventListener('DOMContentLoaded', function () {
var btn = document.querySelector('.rpt-header__hamburger');
var nav = document.getElementById('rpt-main-nav');
if (!btn || !nav) return;
btn.addEventListener('click', function () {
var open = nav.classList.toggle('is-open');
btn.setAttribute('aria-expanded', String(open));
});
nav.querySelectorAll('a').forEach(function (link) {
link.addEventListener('click', function () {
nav.classList.remove('is-open');
btn.setAttribute('aria-expanded', 'false');
});
});
document.addEventListener('click', function (e) {
if (!e.target.closest('.rpt-header')) {
nav.classList.remove('is-open');
btn.setAttribute('aria-expanded', 'false');
}
});
});
})();

View file

@ -7,6 +7,8 @@ navigation:
css:
theme:
css/nav.css: {}
js:
js/nav.js: {}
schedule:
css:

View file

@ -8,6 +8,61 @@ function riverside_pt_page_attachments(array &$attachments): void {
$attachments['#attached']['library'][] = 'riverside_pt/navigation';
}
function riverside_pt_theme(): array {
return [
'riverside_pt_header' => [
'variables' => [
'site_name' => NULL,
'home_url' => NULL,
'menu_items' => [],
'current_path' => NULL,
],
],
];
}
function riverside_pt_page_top(array &$page_top): void {
$page_top['rpt_header'] = [
'#theme' => 'riverside_pt_header',
'#cache' => ['contexts' => ['url.path']],
];
}
function riverside_pt_preprocess_riverside_pt_header(array &$variables): void {
$variables['site_name'] = \Drupal::config('system.site')->get('name');
$variables['home_url'] = \Drupal\Core\Url::fromRoute('<front>')->toString();
$tree_service = \Drupal::service('menu.link_tree');
$params = new \Drupal\Core\Menu\MenuTreeParameters();
$params->setMaxDepth(1);
$tree = $tree_service->load('main', $params);
$tree = $tree_service->transform($tree, [
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
]);
$seen = [];
$items = [];
foreach ($tree as $element) {
if (!$element->access->isAllowed()) {
continue;
}
$url = $element->link->getUrlObject()->toString();
if (in_array($url, $seen, TRUE)) {
continue;
}
$seen[] = $url;
$title = (string) $element->link->getTitle();
$items[] = [
'title' => $title,
'url' => $url,
'is_cta' => ($title === 'Book An Appointment'),
];
}
$variables['menu_items'] = $items;
$variables['current_path'] = \Drupal::request()->getPathInfo();
}
function riverside_pt_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context): void {
if ($route_match->getRouteName() === 'riverside_pt.booking') {
$breadcrumb = new Breadcrumb();

View file

@ -0,0 +1,36 @@
<header class="rpt-header" role="banner">
<div class="rpt-header__inner">
<a class="rpt-header__brand" href="{{ home_url }}">{{ site_name }}</a>
<nav class="rpt-header__nav" id="rpt-main-nav" aria-label="Main navigation">
<ul class="rpt-header__list">
{% for item in menu_items %}
{% if not item.is_cta %}
<li class="rpt-header__item">
<a class="rpt-header__link{% if current_path == item.url %} is-active{% endif %}"
href="{{ item.url }}">{{ item.title }}</a>
</li>
{% endif %}
{% endfor %}
{% for item in menu_items %}
{% if item.is_cta %}
<li class="rpt-header__item rpt-header__item--cta">
<a class="rpt-header__cta" href="{{ item.url }}">{{ item.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
<button class="rpt-header__hamburger"
aria-expanded="false"
aria-controls="rpt-main-nav"
aria-label="Toggle navigation">
<span></span>
<span></span>
<span></span>
</button>
</div>
</header>