diff --git a/src/css/header.css b/src/css/header.css index 596f561..8a8215b 100644 --- a/src/css/header.css +++ b/src/css/header.css @@ -8,7 +8,7 @@ html.is-clipped--navbar { } body { - padding-top: var(--navbar-height); + padding-top: var(--body-top); } .navbar { @@ -17,7 +17,7 @@ body { font-size: calc(16 / var(--rem-base) * 1rem); height: var(--navbar-height); position: fixed; - top: 0; + top: var(--universal-nav-height); width: 100%; z-index: var(--z-index-navbar); display: flex; @@ -233,7 +233,7 @@ body { position: fixed; display: none; z-index: 5; - top: var(--navbar-height); + top: var(--body-top); width: 100%; background: var(--navbar-menu-background); box-shadow: 0 8px 16px rgba(10, 10, 10, 0.1); diff --git a/src/css/nav.css b/src/css/nav.css index f5ae6e9..12e1f0b 100644 --- a/src/css/nav.css +++ b/src/css/nav.css @@ -10,7 +10,7 @@ .nav-container { position: fixed; - top: var(--navbar-height); + top: var(--body-top); left: 0; width: 100%; font-size: calc(17 / var(--rem-base) * 1rem); @@ -54,7 +54,7 @@ @media screen and (min-width: 1024px) { .nav { - top: calc(var(--navbar-height)); + top: var(--body-top); box-shadow: none; position: sticky; height: var(--nav-height--desktop); diff --git a/src/css/site.css b/src/css/site.css index 3caccc1..cab1180 100644 --- a/src/css/site.css +++ b/src/css/site.css @@ -10,6 +10,7 @@ @import "toc.css"; @import "doc.css"; @import "pagination.css"; +@import "universal-nav.css"; @import "header.css"; @import "footer.css"; @import "highlight.css"; diff --git a/src/css/toolbar.css b/src/css/toolbar.css index ae22f75..f545901 100644 --- a/src/css/toolbar.css +++ b/src/css/toolbar.css @@ -18,7 +18,7 @@ @media screen and (min-width: 1024px) { .toolbar { - top: calc(var(--navbar-height)); + top: var(--body-top); } } diff --git a/src/css/universal-nav.css b/src/css/universal-nav.css new file mode 100644 index 0000000..adf1463 --- /dev/null +++ b/src/css/universal-nav.css @@ -0,0 +1,302 @@ +/** + * The Stackable universal navigation strip — links the three Stackable + * properties (stackable.tech, docs, hub), mirroring the hub's meta strip. + */ + +.universal-nav { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: var(--universal-nav-height); + z-index: var(--z-index-universal-nav); + background: var(--universal-nav-background); + color: var(--universal-nav-font-color); + border-bottom: solid 1px transparent; +} + +.universal-nav__bar { + display: flex; + align-items: center; + gap: 18px; + height: 100%; + padding: 0 28px; +} + +.universal-nav a { + text-decoration: none; +} + +.universal-nav__brand { + display: flex; + align-items: center; + border-radius: 6px; + flex: none; +} + +.universal-nav__logo { + display: block; + height: 20px; + width: auto; +} + +.universal-nav__sep { + width: 1px; + height: 18px; + background: rgba(255, 255, 255, 0.16); + flex: none; +} + +.universal-nav__tabs { + display: flex; + align-items: center; + gap: 4px; + flex: 1; +} + +.universal-nav__tab { + display: inline-flex; + align-items: center; + gap: 7px; + height: 30px; + padding: 0 14px; + border-radius: 7px; + color: rgba(255, 255, 255, 0.62); + font-size: 13.5px; + font-weight: 500; + line-height: 1; + white-space: nowrap; + transition: + background var(--universal-nav-duration-fast) var(--universal-nav-ease), + color var(--universal-nav-duration-fast) var(--universal-nav-ease); +} + +.universal-nav__tab:hover { + background: var(--universal-nav-hover-background); + color: rgba(255, 255, 255, 0.85); +} + +.universal-nav__tab.is-current { + color: var(--universal-nav-font-color); + font-weight: 700; + background: rgba(255, 255, 255, 0.12); +} + +.universal-nav__dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex: none; + background: rgba(255, 255, 255, 0.3); +} + +.universal-nav__tab.is-current .universal-nav__dot { + background: var(--universal-nav-accent-current); +} + +.universal-nav__dot--site { + background: var(--universal-nav-accent-site); +} + +.universal-nav__dot--docs { + background: var(--universal-nav-accent-docs); +} + +.universal-nav__dot--hub { + background: var(--universal-nav-accent-hub); +} + +.universal-nav__icon { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 6px; + color: rgba(255, 255, 255, 0.7); + transition: color var(--universal-nav-duration-fast) var(--universal-nav-ease); +} + +.universal-nav__icon:hover { + color: var(--universal-nav-font-color); +} + +.universal-nav__cta { + display: inline-flex; + align-items: center; + justify-content: center; + height: 30px; + padding: 0 16px; + border-radius: 99px; + background: var(--universal-nav-cta-background); + color: var(--universal-nav-cta-font-color); + font-size: 13px; + font-weight: 500; + line-height: 1; + white-space: nowrap; + flex: none; + transition: background var(--universal-nav-duration-fast) var(--universal-nav-ease); +} + +.universal-nav__cta:hover { + background: var(--universal-nav-cta-hover-background); +} + +.universal-nav__disclosure { + display: none; + align-items: center; + gap: 7px; + margin-left: auto; + padding: 6px 8px; + border: 0; + background: none; + color: var(--universal-nav-font-color); + border-radius: 7px; + font-size: 13.5px; + font-weight: 600; + line-height: 1; + cursor: pointer; +} + +.universal-nav__disclosure:hover { + background: var(--universal-nav-hover-background); +} + +.universal-nav__caret { + display: inline-flex; + color: rgba(255, 255, 255, 0.7); + transition: transform var(--universal-nav-duration-med) var(--universal-nav-ease); +} + +.universal-nav__disclosure[aria-expanded="true"] .universal-nav__caret { + transform: rotate(180deg); +} + +.universal-nav__sr { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.universal-nav__sheet { + position: absolute; + top: var(--universal-nav-height); + left: 0; + width: 100%; + background: var(--universal-nav-background); + border-bottom: solid 1px rgba(255, 255, 255, 0.16); + padding: 8px 12px 12px; + line-height: 1.5; +} + +.universal-nav__sheet[hidden] { + display: none; +} + +.universal-nav__sheet-row { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + border-radius: 9px; + margin-bottom: 4px; + border: 1px solid transparent; +} + +.universal-nav__sheet-row:hover { + background: var(--universal-nav-hover-background); +} + +.universal-nav__sheet-row.is-current { + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.12); +} + +.universal-nav__sheet-dot { + width: 9px; + height: 9px; + border-radius: 50%; + flex: none; +} + +.universal-nav__sheet-text { + flex: 1; + min-width: 0; +} + +.universal-nav__sheet-name { + display: block; + font-size: 14.5px; + font-weight: 700; + color: var(--universal-nav-font-color); +} + +.universal-nav__sheet-host { + display: block; + color: rgba(255, 255, 255, 0.5); + font-family: var(--monospace-font-family); + font-size: 11px; + margin-top: 1px; +} + +.universal-nav__here { + color: var(--universal-nav-accent-current); + font-size: 11px; + font-weight: 600; + white-space: nowrap; +} + +.universal-nav__ext { + display: inline-flex; + color: rgba(255, 255, 255, 0.5); +} + +.universal-nav__sheet-cta { + width: 100%; + height: 40px; + margin-top: 6px; +} + +.universal-nav a:focus-visible, +.universal-nav button:focus-visible { + outline: 2px solid var(--universal-nav-font-color); + outline-offset: 2px; +} + +@media (prefers-reduced-motion: reduce) { + .universal-nav__tab, + .universal-nav__icon, + .universal-nav__caret, + .universal-nav__disclosure, + .universal-nav__cta { + transition: none; + } +} + +@media screen and (max-width: 720px) { + .universal-nav__bar { + padding: 0 14px; + gap: 10px; + } + + .universal-nav__tabs, + .universal-nav__icon, + .universal-nav__bar > .universal-nav__cta, + .universal-nav__sep { + display: none; + } + + .universal-nav__disclosure { + display: inline-flex; + } +} + +@media screen and (min-width: 721px) { + .universal-nav__sheet { + display: none; + } +} diff --git a/src/css/vars.css b/src/css/vars.css index 5a7c2fe..ad1b03e 100644 --- a/src/css/vars.css +++ b/src/css/vars.css @@ -41,6 +41,20 @@ --navbar-menu-font-color: var(--color-text); --navbar-menu-font-color-hover: var(--color-brand-primary); --navbar-menu_hover-background: var(--color-background2); + /* universal nav */ + --universal-nav-background: var(--color-text); + --universal-nav-font-color: var(--color-white); + --universal-nav-hover-background: rgba(255, 255, 255, 0.07); + --universal-nav-cta-background: var(--color-brand-secondary); + --universal-nav-cta-hover-background: #8a004e; + --universal-nav-cta-font-color: var(--color-white); + --universal-nav-ease: cubic-bezier(0.2, 0.7, 0.2, 1); + --universal-nav-duration-fast: 160ms; + --universal-nav-duration-med: 220ms; + --universal-nav-accent-current: #d4107d; + --universal-nav-accent-site: var(--color-brand-secondary); + --universal-nav-accent-docs: var(--color-brand-primary); + --universal-nav-accent-hub: #3d5266; /* nav */ --nav-background: var(--color-white); --nav-border-color: var(--panel-background); @@ -117,7 +131,8 @@ --navbar-height: calc(73 / var(--rem-base) * 1rem); --navbar-sub-height: calc(45 / var(--rem-base) * 1rem); --toolbar-height: calc(45 / var(--rem-base) * 1rem); - --body-top: calc(var(--navbar-height)); + --universal-nav-height: 44px; /* fixed px so the strip height doesn't shift with the theme's responsive root font-size */ + --body-top: calc(var(--universal-nav-height) + var(--navbar-height)); --body-min-height: calc(100vh - var(--body-top)); --nav-height: calc(var(--body-min-height) - var(--toolbar-height)); --nav-height--desktop: var(--body-min-height); @@ -134,4 +149,5 @@ --z-index-page-version-menu: 3; --z-index-page-version-menu-button: 4; --z-index-navbar: 5; + --z-index-universal-nav: 6; } diff --git a/src/img/stackable-mark.svg b/src/img/stackable-mark.svg new file mode 100644 index 0000000..919148b --- /dev/null +++ b/src/img/stackable-mark.svg @@ -0,0 +1,19 @@ + + diff --git a/src/js/04-universal-nav.js b/src/js/04-universal-nav.js new file mode 100644 index 0000000..8ecc5e7 --- /dev/null +++ b/src/js/04-universal-nav.js @@ -0,0 +1,27 @@ +;(function () { + 'use strict' + + var disclosure = document.querySelector('.universal-nav__disclosure') + var sheet = document.getElementById('universal-nav-sheet') + if (!disclosure || !sheet) return + + function setOpen (open) { + disclosure.setAttribute('aria-expanded', String(open)) + sheet.hidden = !open + var label = disclosure.querySelector('.universal-nav__sr') + if (label) label.textContent = (open ? 'Close' : 'Open') + ' Stackable property switcher' + } + + disclosure.addEventListener('click', function (e) { + e.stopPropagation() + setOpen(sheet.hidden) + }) + + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape' && !sheet.hidden) setOpen(false) + }) + + document.addEventListener('click', function (e) { + if (!sheet.hidden && !sheet.contains(e.target) && !disclosure.contains(e.target)) setOpen(false) + }) +})() diff --git a/src/layouts/404.hbs b/src/layouts/404.hbs index 8caab1d..d517401 100644 --- a/src/layouts/404.hbs +++ b/src/layouts/404.hbs @@ -4,6 +4,7 @@ {{> head defaultPageTitle='Page Not Found'}}
+{{> universal-nav}} {{> header}} {{> body}} {{> footer}} diff --git a/src/layouts/default.hbs b/src/layouts/default.hbs index c5282ec..e06fe16 100644 --- a/src/layouts/default.hbs +++ b/src/layouts/default.hbs @@ -4,6 +4,7 @@ {{> head defaultPageTitle='Untitled'}} +{{> universal-nav}} {{> header}} {{> body}} {{> footer}} diff --git a/src/layouts/landing.hbs b/src/layouts/landing.hbs index 2e3ddc0..b144bf5 100644 --- a/src/layouts/landing.hbs +++ b/src/layouts/landing.hbs @@ -4,6 +4,7 @@ {{> head defaultPageTitle='Untitled'}} +{{> universal-nav}} {{> header}}