diff --git a/static/css/neuron-page.css b/static/css/neuron-page.css index c9cc547c..0d6bb61a 100644 --- a/static/css/neuron-page.css +++ b/static/css/neuron-page.css @@ -180,19 +180,91 @@ } } -/* Small screen styles - show hamburger button */ +/* Backdrop overlay for small-screen drawer. Hidden by default; shown only + when the drawer is open. Defined outside any media query so the JS class + toggle works the same way at all sizes — but on large screens it's never + visible since the drawer isn't a drawer. */ +.hamburger-backdrop { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + z-index: 1000; + opacity: 0; + transition: opacity 0.25s ease; +} +.hamburger-backdrop.open { + display: block; + opacity: 1; +} + +/* Section structure inside the drawer / dropdown */ +.hamburger-section + .hamburger-section { + border-top: 1px solid #e9ecef; + margin-top: 8px; + padding-top: 8px; +} +.hamburger-section-title { + font-weight: 600; + font-size: 0.75rem; + color: #888; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 20px 8px; +} +.hamburger-link-row { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 20px; + flex-wrap: wrap; +} +.hamburger-link-row .icon-link img { + display: block; +} +.hamburger-search { + padding: 4px 16px 8px; +} +.hamburger-search input { + width: 100%; + padding: 6px 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.85rem; +} + +/* Wide screens: site-nav section duplicates the top header-bar, so hide it. */ +@media (min-width: 1700px) { + .hamburger-section-site { + display: none; + } +} + +/* Small screen styles - hamburger button + slide-in drawer */ @media (max-width: 1699px) { /* Hide all other header elements */ .header-flex { display: none; } + /* On neuron-type pages collapse the top header-bar to brand-only: + its other columns (Home/Types/Help, search, social) are now inside + the drawer. Index/help pages still need their inline nav, so we + scope this to .is-neuron-page. */ + body.is-neuron-page .neuron-header-bar > .row > div:nth-child(1), + body.is-neuron-page .neuron-header-bar > .row > div:nth-child(3) { + display: none; + } + body.is-neuron-page .neuron-header-bar > .row { + justify-content: center; + } + .hamburger-menu { position: fixed; top: 10px; - left: 30px; + left: 10px; width: auto; - z-index: 1001; + z-index: 1002; background: transparent; border: none; box-shadow: none; @@ -211,69 +283,46 @@ backdrop-filter: blur(10px); } - .hamburger-menu:hover .hamburger-btn, - .hamburger-menu.menu-open .hamburger-btn { - display: block; - border-bottom: none; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - + /* Drawer: slides in from the left edge, full viewport height. */ .hamburger-dropdown { - display: none; - position: absolute; - top: 40px; - left: 50%; - transform: translateX(-50%); - background: rgba(255, 255, 255, 0.95); - border: 1px solid #ddd; - border-top: none; - border-radius: 0 0 8px 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - backdrop-filter: blur(10px); - padding: 8px 0; - min-width: 250px; - z-index: 1000; - opacity: 0; - visibility: hidden; - transition: - opacity 0.3s ease, - visibility 0.3s ease, - transform 0.3s ease; - transform: translateX(-50%) translateY(-10px); - } - - .hamburger-menu:hover .hamburger-dropdown, - .hamburger-dropdown.open { + position: fixed; + top: 0; + left: 0; + width: 280px; + max-width: 85vw; + height: 100vh; + background: #fff; + border: none; + border-right: 1px solid #ddd; + border-radius: 0; + box-shadow: 2px 0 12px rgba(0, 0, 0, 0.15); + padding: 56px 0 16px; /* leave room above for the hamburger button */ + overflow-y: auto; + z-index: 1001; + transform: translateX(-100%); + transition: transform 0.25s ease; display: block; opacity: 1; visibility: visible; - left: 0px; - transform: translateX(0) translateY(0); + } + .hamburger-dropdown.open { + transform: translateX(0); } - .hamburger-menu:hover, - .hamburger-menu.menu-open { - background: rgba(255, 255, 255, 0.95); - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - backdrop-filter: blur(10px); + /* When drawer is open, lift the hamburger button above it and shift it + so it appears inside the drawer area as a close affordance. */ + .hamburger-menu.menu-open .hamburger-btn { + background: #fff; } - /* Ensure dropdown is hidden by default on small screens */ + /* Override the static "display: none" rule from the base block so the + fixed positioning above takes effect. */ #hamburgerDropdown { - display: none; - opacity: 0; - visibility: hidden; + display: block; } - /* Show dropdown when open class is applied or on hover */ - .hamburger-menu:hover #hamburgerDropdown, - #hamburgerDropdown.open { - display: block; - opacity: 1; - visibility: visible; + .hamburger-section-title { + padding-left: 24px; } } @@ -550,6 +599,11 @@ span.truman-hl { margin-bottom: 1rem; text-align: center; border: 1px solid #e5e7eb; + /* Stretch to fill the row height so cards align with the tallest sibling. + Flexboxgrid's .row is display:flex, so cols already stretch; this just + lets the card inside the col take that full height. */ + height: calc(100% - 1rem); + box-sizing: border-box; } .total-column { @@ -1386,13 +1440,17 @@ abbr[data-original-title]:hover { max-width: 110px; } -/* Adjust specific columns for better fit */ +/* Below the md breakpoint each table is full-width and behaves fine with an + auto-sized first column. Above md, the tables sit side-by-side and the + first column was inflating because cols 2-6 only request 75% (5 * 15%) of + the table width, leaving the remaining 25% to col 1. Scoped fix at the + bottom of this file switches the connectivity tables to table-layout:fixed + with explicit column widths summing to 100% only on wide screens. */ #upstream-table th:first-child, #downstream-table th:first-child, #upstream-table td:first-child, #downstream-table td:first-child { - width: 40%; - min-width: 200px; + min-width: 5em; } #upstream-table th:nth-child(2), @@ -1455,6 +1513,30 @@ abbr[data-original-title]:hover { .row .col-xs-12.col-md-6:last-child { padding-left: 1rem; } + + /* Connectivity tables: use auto-layout so the partner-name column + sizes itself to the widest cell (across header *and* every body row), + which is what we need to "always show full names". With fixed-layout + the column only sees the first row (the th) and longer body names + overflow into the next column. */ + #upstream-table, + #downstream-table { + table-layout: auto !important; + } + #upstream-table th:first-child, + #downstream-table th:first-child, + #upstream-table td:first-child, + #downstream-table td:first-child { + /* Override the table-wide max-width:110px and overflow:hidden so the + partner-name column can grow to fit the longest entry without + ellipsis-truncating it. width:auto + white-space:nowrap lets the + browser take the column's max-content as the column width. */ + width: auto !important; + max-width: none !important; + overflow: visible !important; + text-overflow: clip !important; + white-space: nowrap !important; + } } /* Compact search field styling for all tables with sliders */ @@ -2795,7 +2877,8 @@ abbr[data-original-title]:hover { display: flex; align-items: center; gap: 8px; - width: 100%; + width: auto; + max-width: 18em; } .p-c-label { @@ -3071,6 +3154,15 @@ table.dataTable#roi-table tbody > tr > td:first-child { display: block; } +/* Floating variant used by the sidebar-drawer search instance: appended to +
and positioned with viewport coordinates so the drawer's + overflow-y:auto can't clip the suggestion list. JS sets top/left/width. */ +.neuron-search-dropdown.neuron-search-dropdown-floating { + position: fixed; + right: auto; + z-index: 1100; +} + .neuron-search-no-results { padding: 8px 12px; color: #666; diff --git a/static/js/neuron-page.js b/static/js/neuron-page.js index 57dbf87e..ce07e0b1 100644 --- a/static/js/neuron-page.js +++ b/static/js/neuron-page.js @@ -403,43 +403,75 @@ function initializeResponsiveNavigation() { var btn = document.getElementById("hamburgerBtn"); var dropdown = document.getElementById("hamburgerDropdown"); var menu = document.getElementById("hamburgerMenu"); + var backdrop = document.getElementById("hamburgerBackdrop"); if (!dropdown || !menu) return; - // Handle button click and hover (only relevant on small screens) + // Drawer mode is only active under this breakpoint (matches the CSS). + var DRAWER_BREAKPOINT = 1700; + function isDrawerMode() { + return window.innerWidth < DRAWER_BREAKPOINT; + } + + function openDrawer() { + dropdown.classList.add("open"); + menu.classList.add("menu-open"); + if (backdrop) backdrop.classList.add("open"); + if (btn) btn.setAttribute("aria-expanded", "true"); + if (isDrawerMode()) document.body.style.overflow = "hidden"; + } + function closeDrawer() { + dropdown.classList.remove("open"); + menu.classList.remove("menu-open"); + if (backdrop) backdrop.classList.remove("open"); + if (btn) btn.setAttribute("aria-expanded", "false"); + document.body.style.overflow = ""; + } + function toggleDrawer() { + if (dropdown.classList.contains("open")) closeDrawer(); + else openDrawer(); + } + + // Button click toggles the drawer. if (btn) { btn.addEventListener("click", function (e) { e.stopPropagation(); - dropdown.classList.toggle("open"); - menu.classList.toggle("menu-open"); + toggleDrawer(); }); - // Handle hover to open menu on small screens - btn.addEventListener("mouseenter", function (e) { - dropdown.classList.add("open"); - menu.classList.add("menu-open"); + // On large screens (no drawer), the menu sits open inline — hover-open + // is a nicety for the floating widget but irrelevant once the drawer + // owns the small-screen experience. Gate hover behaviors accordingly. + btn.addEventListener("mouseenter", function () { + if (!isDrawerMode()) openDrawer(); }); } - // Handle hover over entire menu area if (menu) { - menu.addEventListener("mouseenter", function (e) { - dropdown.classList.add("open"); - menu.classList.add("menu-open"); + menu.addEventListener("mouseenter", function () { + if (!isDrawerMode()) openDrawer(); }); - - // Close menu when mouse leaves the entire menu area - menu.addEventListener("mouseleave", function (e) { - dropdown.classList.remove("open"); - menu.classList.remove("menu-open"); + menu.addEventListener("mouseleave", function () { + if (!isDrawerMode()) closeDrawer(); }); } - // Close when clicking outside (only on small screens) + // Backdrop click closes the drawer (small screens only). + if (backdrop) { + backdrop.addEventListener("click", closeDrawer); + } + + // ESC closes the drawer. + document.addEventListener("keydown", function (e) { + if (e.key === "Escape" && dropdown.classList.contains("open")) { + closeDrawer(); + } + }); + + // Click outside the menu (large screens) — keep the legacy behavior. document.body.addEventListener("click", function (e) { - if (!menu.contains(e.target)) { - dropdown.classList.remove("open"); - menu.classList.remove("menu-open"); + if (!menu.contains(e.target) && !isDrawerMode()) { + closeDrawer(); } }); @@ -449,13 +481,17 @@ function initializeResponsiveNavigation() { menuLinks.forEach(function (link) { link.addEventListener("click", function (e) { - // Close menu on small screens - dropdown.classList.remove("open"); - menu.classList.remove("menu-open"); + // Always close menu on click — applies to both in-page anchors and + // site-nav links. + closeDrawer(); + + // Only intercept same-page anchor links for smooth scrolling; let + // cross-page links (Home/Types/Help) navigate normally. + var href = this.getAttribute("href") || ""; + if (!href.startsWith("#")) return; - // Smooth scroll to target e.preventDefault(); - var targetId = this.getAttribute("href").substring(1); // Remove # + var targetId = href.substring(1); var target = document.getElementById(targetId); if (target) { target.scrollIntoView({ diff --git a/templates/base.html.jinja b/templates/base.html.jinja index 8cd5126d..6aa406f3 100644 --- a/templates/base.html.jinja +++ b/templates/base.html.jinja @@ -25,7 +25,7 @@ {% endif %} - + {% block header %}{% endblock %}