diff --git a/AGENTS.md b/AGENTS.md index 16fbaa5..fd29b0a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Project -This repository is `nodebb-theme-westgate`, a NodeBB theme for the Shadows Over Westgate forum. +This repository is `nodebb-theme-westgate`, a NodeBB theme for the Shadows Over Westgate forum and website. It is forked from `nodebb-theme-quickstart` and should remain a small, focused NodeBB child theme rather than a full replacement for NodeBB or Harmony. diff --git a/docs/superpowers/specs/2026-06-26-website-pages-design.md b/docs/superpowers/specs/2026-06-26-website-pages-design.md index de317fa..d7d9647 100644 --- a/docs/superpowers/specs/2026-06-26-website-pages-design.md +++ b/docs/superpowers/specs/2026-06-26-website-pages-design.md @@ -39,10 +39,9 @@ drafts, and authentication entry points. - `theme.json` declares `baseTheme: "nodebb-theme-harmony"`. - `theme.scss` imports Harmony first, then focused Westgate partials under `scss/westgate/`. -- `templates/header.tpl` currently matches the Harmony layout pattern: a - `layout-container`, `partials/sidebar-left.tpl`, `#panel`, and - `partials/header/brand.tpl`. -- Harmony's `footer.tpl` imports `partials/sidebar-right.tpl`. +- `templates/header.tpl` currently mounts the Westgate topbar inside the + `layout-container` before `#panel`. +- `templates/footer.tpl` no longer imports Harmony's right sidebar. - Harmony's sidebar partials under `/home/vicky/Repositories/nodebb-theme-harmony/templates/partials/sidebar/` own the live controls for navigation, user menu, search, notifications, chat, diff --git a/lib/theme.js b/lib/theme.js index ecac492..c7a2cd0 100644 --- a/lib/theme.js +++ b/lib/theme.js @@ -115,11 +115,6 @@ library.defineWidgetAreas = async function (areas) { template: 'global', location: 'sidebar-footer', }, - { - name: 'Brand Header', - template: 'global', - location: 'brand-header', - }, { name: 'About me (before)', template: 'account/profile.tpl', @@ -182,4 +177,3 @@ library.filterMiddlewareRenderHeader = async function (hookData) { hookData.templateData.bootswatchSkinOptions = await meta.css.getSkinSwitcherOptions(hookData.req.uid); return hookData; }; - diff --git a/public/client.js b/public/client.js index 4634899..22ba7f3 100644 --- a/public/client.js +++ b/public/client.js @@ -145,13 +145,126 @@ }); } + function getWestgateTopbar() { + if (!document || typeof document.querySelector !== 'function') { + return null; + } + + return document.querySelector('[data-wg-topbar]'); + } + + function closeWestgateTopbarPanels(topbar, exceptKey) { + if (!topbar || typeof topbar.querySelectorAll !== 'function') { + return; + } + + toArray(topbar.querySelectorAll('[data-wg-panel]')).forEach((panel) => { + if (panel.getAttribute('data-wg-panel') !== exceptKey) { + panel.classList.remove('is-open'); + } + }); + toArray(topbar.querySelectorAll('[data-wg-menu]')).forEach((trigger) => { + if (trigger.getAttribute('data-wg-menu') !== exceptKey) { + trigger.setAttribute('aria-expanded', 'false'); + } + }); + } + + function closeWestgateTopbarDrawer(topbar) { + if (!topbar) { + return; + } + + topbar.classList.remove('is-drawer-open'); + const burger = topbar.querySelector('[data-wg-burger]'); + if (burger) { + burger.setAttribute('aria-expanded', 'false'); + } + } + + function initWestgateTopbar() { + const topbar = getWestgateTopbar(); + if (!topbar) { + return; + } + + if (!window.westgateTheme.topbarEventsBound) { + document.addEventListener('click', (event) => { + const currentTopbar = getWestgateTopbar(); + if (!currentTopbar) { + return; + } + + const trigger = event.target.closest && event.target.closest('[data-wg-menu]'); + if (trigger && currentTopbar.contains(trigger)) { + event.preventDefault(); + event.stopPropagation(); + + const key = trigger.getAttribute('data-wg-menu'); + const panel = currentTopbar.querySelector(`[data-wg-panel="${key}"]`); + const shouldOpen = !!panel && !panel.classList.contains('is-open'); + closeWestgateTopbarPanels(currentTopbar, shouldOpen ? key : null); + if (panel) { + panel.classList.toggle('is-open', shouldOpen); + } + trigger.setAttribute('aria-expanded', String(shouldOpen)); + return; + } + + const burger = event.target.closest && event.target.closest('[data-wg-burger]'); + if (burger && currentTopbar.contains(burger)) { + event.preventDefault(); + event.stopPropagation(); + + const isOpen = !currentTopbar.classList.contains('is-drawer-open'); + currentTopbar.classList.toggle('is-drawer-open', isOpen); + burger.setAttribute('aria-expanded', String(isOpen)); + closeWestgateTopbarPanels(currentTopbar, null); + return; + } + + if (event.target.closest && event.target.closest('[data-wg-panel]')) { + return; + } + + closeWestgateTopbarPanels(currentTopbar, null); + if (!currentTopbar.contains(event.target)) { + closeWestgateTopbarDrawer(currentTopbar); + } + }); + + document.addEventListener('keydown', (event) => { + if (event.key !== 'Escape') { + return; + } + + const currentTopbar = getWestgateTopbar(); + closeWestgateTopbarPanels(currentTopbar, null); + closeWestgateTopbarDrawer(currentTopbar); + }); + + window.westgateTheme.topbarEventsBound = true; + } + + $(document) + .off('shown.bs.dropdown.westgateTopbar') + .on('shown.bs.dropdown.westgateTopbar', '.wg-topbar [component="sidebar/search"]', function () { + $(this).find('[component="search/fields"] input[name="query"]').trigger('focus'); + }); + } + window.westgateTheme = window.westgateTheme || {}; window.westgateTheme.wrapWikiTables = wrapWestgateWikiTables; + window.westgateTheme.initTopbar = initWestgateTopbar; $(document).ready(function () { wrapWestgateWikiTables(document); + initWestgateTopbar(); $(window).on('action:ajaxify.end', function () { wrapWestgateWikiTables(document); + initWestgateTopbar(); + closeWestgateTopbarPanels(getWestgateTopbar(), null); + closeWestgateTopbarDrawer(getWestgateTopbar()); }); require(['api'], function (api) { diff --git a/public/images/plum-header-bg.png b/public/images/plum-header-bg.png deleted file mode 100644 index 21d96d5..0000000 Binary files a/public/images/plum-header-bg.png and /dev/null differ diff --git a/scss/westgate/_header.scss b/scss/westgate/_header.scss deleted file mode 100644 index d148f5c..0000000 --- a/scss/westgate/_header.scss +++ /dev/null @@ -1,187 +0,0 @@ -.westgate-brand-hero { - position: relative; - overflow: hidden; - margin-top: 0.25rem; - border: 1px solid rgba(194, 163, 90, 0.22); - border-radius: 10px; - padding: 1.2rem 1.05rem; - filter: saturate(1) contrast(1) brightness(1); - background-color: #16121b; - background: - linear-gradient(90deg, rgba(5, 4, 7, 0.82) 0%, rgba(7, 6, 9, 0.72) 22%, rgba(9, 7, 11, 0.48) 34%, rgba(10, 8, 12, 0.18) 43%, rgba(10, 8, 12, 0) 52%), - url("./plugins/nodebb-theme-westgate/images/plum-header-bg.png") center 50% / cover no-repeat; - box-shadow: - inset 0 1px 0 rgba(255, 242, 204, 0.08), - inset 0 0 0 1px rgba(33, 21, 28, 0.5), - 0 18px 42px rgba(0, 0, 0, 0.3); - - &::before { - content: ""; - position: absolute; - inset: 0; - background: - radial-gradient(circle at 62% 100%, rgba(88, 38, 86, 0.26), rgba(88, 38, 86, 0.13) 20%, transparent 44%), - linear-gradient(155deg, transparent 58%, rgba(55, 24, 53, 0.08) 78%, rgba(78, 33, 75, 0.14) 100%); - pointer-events: none; - } -} - -.westgate-brand-hero__veil, -.westgate-brand-hero__inner { - position: relative; - z-index: 1; -} - -.westgate-brand-hero__veil { - display: none; -} - -.westgate-brand-hero__inner { - align-items: center; - justify-content: flex-start; - gap: 0; - min-height: 142px; - width: 100%; - z-index: 2; -} - -.westgate-brand-hero__brand { - width: 100%; - flex-wrap: wrap; - min-height: inherit; - padding: 0 0 0 clamp(1rem, 4vw, 3rem); - justify-content: flex-start; - text-align: left; - - &:hover, - &:focus-within { - background: transparent; - } -} - -.westgate-brand-hero__brand :where([component="brand/anchor"], [component="siteTitle"]) { - color: inherit; - text-decoration: none; - - &:hover, - &:focus { - color: inherit; - text-decoration: none; - background: transparent; - box-shadow: none; - outline: none; - } -} - -.westgate-brand-hero__title-shell { - width: 100%; - padding: 0; - min-height: inherit; - justify-content: flex-start; - text-align: left; - color: inherit; - position: relative; - z-index: 3; - text-decoration: none; - - &:hover, - &:focus { - color: inherit; - text-decoration: none; - background: transparent; - box-shadow: none; - outline: none; - } -} - -.westgate-brand-hero__title-block { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 0.35rem; - text-align: left; -} - -.westgate-brand-hero__brand [component="siteTitle"] :where(h1, .fs-6) { - font-family: var(--wg-font-display); - font-size: clamp(1.25rem, 2.1vw, 1.9rem) !important; - line-height: 0.92; - font-weight: 600; - letter-spacing: 0.1em; - text-transform: uppercase; - text-shadow: - 0 1px 0 rgba(255, 222, 142, 0.05), - 0 2px 0 rgba(52, 37, 15, 0.92), - 0 4px 0 rgba(16, 11, 6, 0.96), - 0 8px 14px rgba(0, 0, 0, 0.72), - 0 14px 26px rgba(0, 0, 0, 0.46), - 0 0 16px rgba(48, 10, 26, 0.24); - opacity: 0.82; - mix-blend-mode: screen; -} - -.westgate-brand-hero__subtitle { - color: #c2a35a; - font-family: var(--wg-font-display); - font-size: 0.64rem; - font-weight: 600; - letter-spacing: 0.22em; - line-height: 1; - text-transform: uppercase; - text-shadow: - 0 1px 0 rgba(0, 0, 0, 0.92), - 0 3px 8px rgba(0, 0, 0, 0.34); -} - -.westgate-brand-hero__widgets { - position: relative; - z-index: 4; - color: var(--wg-text); - text-shadow: 0 1px 0 rgba(0, 0, 0, 0.74); - width: 100%; - padding-left: 0; - border-left: 0; - text-align: center; - margin-top: 0.5rem; - - > :last-child { - margin-bottom: 0; - } -} - -@media (max-width: 991.98px) { - .westgate-brand-hero { - margin-top: 0; - padding: 1.05rem 0.9rem; - background-position: center 46%; - } - - .westgate-brand-hero__inner { - min-height: 118px; - flex-wrap: wrap; - } - - .westgate-brand-hero__brand { - min-height: 0; - padding: 0 0 0 0.65rem; - } - - .westgate-brand-hero__title-block { - gap: 0.28rem; - } - - .westgate-brand-hero__brand [component="siteTitle"] :where(h1, .fs-6) { - font-size: clamp(1rem, 3.9vw, 1.35rem) !important; - letter-spacing: 0.07em; - } - - .westgate-brand-hero__subtitle { - font-size: 0.56rem; - letter-spacing: 0.18em; - } - - .westgate-brand-hero__widgets { - border-top: 1px solid rgba(216, 194, 138, 0.12); - padding-top: 0.85rem; - } -} diff --git a/scss/westgate/_topbar.scss b/scss/westgate/_topbar.scss new file mode 100644 index 0000000..7ecec9e --- /dev/null +++ b/scss/westgate/_topbar.scss @@ -0,0 +1,418 @@ +.wg-layout-container { + min-height: 100vh; +} + +.wg-topbar { + top: 0; + z-index: 1020; + color: var(--wg-text-soft); + background: + linear-gradient(180deg, rgba(255, 241, 196, 0.035), transparent 42%), + linear-gradient(90deg, rgba(42, 18, 34, 0.96), rgba(15, 13, 18, 0.98) 38%, rgba(9, 8, 11, 0.99)); + border-bottom: 1px solid rgba(194, 163, 90, 0.2); + box-shadow: + inset 0 1px 0 rgba(255, 244, 221, 0.04), + 0 12px 32px rgba(0, 0, 0, 0.34); +} + +.wg-topbar__inner { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto auto; + align-items: center; + gap: 0.9rem; + width: min(100%, 1420px); + min-height: 64px; + margin: 0 auto; + padding: 0.55rem clamp(0.85rem, 2vw, 1.5rem); +} + +.wg-topbar__brand { + display: inline-flex; + align-items: center; + min-width: 0; + gap: 0.7rem; + color: var(--wg-text); + text-decoration: none; + + &:hover, + &:focus { + color: #f3ede4; + text-decoration: none; + } +} + +.wg-topbar__brand-mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.3rem; + height: 2.3rem; + border: 1px solid rgba(194, 163, 90, 0.36); + border-radius: 50%; + color: var(--wg-gold); + background: + radial-gradient(circle at 38% 24%, rgba(255, 238, 190, 0.2), transparent 30%), + linear-gradient(145deg, rgba(58, 24, 48, 0.7), rgba(10, 9, 13, 0.98)); + box-shadow: + inset 0 0 0 1px rgba(9, 8, 11, 0.66), + 0 8px 18px rgba(0, 0, 0, 0.35); + font-family: var(--wg-font-display); + font-weight: 700; +} + +.wg-topbar__brand-text { + display: flex; + flex-direction: column; + min-width: 0; + line-height: 1; +} + +.wg-topbar__brand-name { + max-width: 22ch; + overflow: hidden; + color: var(--wg-text); + font-family: var(--wg-font-display); + font-size: 0.96rem; + font-weight: 600; + letter-spacing: 0.08em; + text-overflow: ellipsis; + text-transform: uppercase; + white-space: nowrap; +} + +.wg-topbar__brand-subtitle { + margin-top: 0.28rem; + color: var(--wg-gold-soft); + font-family: var(--wg-font-code); + font-size: 0.62rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.wg-topbar__nav { + min-width: 0; +} + +.wg-topbar__nav-list, +.wg-topbar__utility-list { + gap: 0.28rem; +} + +.wg-topbar__nav-list { + min-width: 0; + overflow: hidden; +} + +.wg-topbar .nav-link, +.wg-topbar__nav-link, +.wg-topbar__forums-toggle { + min-height: 2.35rem; + border: 1px solid transparent; + border-radius: 6px; + padding: 0.42rem 0.62rem; + color: var(--wg-text-soft); + background: transparent; + font-family: var(--wg-font-ui); + font-size: 0.86rem; + line-height: 1.1; + text-decoration: none; +} + +.wg-topbar .nav-link:hover, +.wg-topbar .nav-link:focus, +.wg-topbar__nav-link:hover, +.wg-topbar__nav-link:focus, +.wg-topbar__forums-toggle:hover, +.wg-topbar__forums-toggle:focus, +.wg-topbar .nav-item.active > .nav-link, +.wg-topbar .nav-item.active > .navigation-link { + color: #f3ede4; + border-color: rgba(194, 163, 90, 0.2); + background: rgba(194, 163, 90, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 244, 221, 0.035); +} + +.wg-topbar__forums { + position: relative; +} + +.wg-topbar__forums-toggle { + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +.wg-topbar__utilities { + justify-self: end; +} + +.wg-topbar__utility-list { + align-items: center; +} + +.wg-topbar__utility-list > .nav-item { + position: relative; +} + +.wg-topbar__utility-list :where(.nav-text, .visible-open) { + display: none !important; +} + +.wg-topbar__utility-list .nav-link { + justify-content: center; + width: 2.35rem; + height: 2.35rem; + padding: 0; +} + +.wg-topbar__utility-list [component$="/count"], +.wg-topbar__drawer-actions [component$="/count"], +.wg-topbar [component="navigation/count"] { + background: linear-gradient(to bottom, rgba(142, 52, 56, 0.92), rgba(84, 28, 39, 0.96)) !important; + border: 1px solid rgba(216, 194, 138, 0.26); + color: #fff0dc; +} + +.wg-topbar__utility-list .visible-closed { + display: inline-block !important; +} + +.wg-topbar__skin .dropend { + margin: 0 !important; +} + +.wg-topbar__skin .dropdown-menu, +.wg-topbar__dropdown, +.wg-topbar .search-dropdown, +.wg-topbar .notifications-dropdown, +.wg-topbar .chats-dropdown, +.wg-topbar .drafts-dropdown, +.wg-topbar .user-dropdown, +.wg-topbar__panel { + min-width: min(22rem, calc(100vw - 1.5rem)); + max-width: calc(100vw - 1.5rem); + border: 1px solid rgba(194, 163, 90, 0.22) !important; + border-radius: 8px !important; + color: var(--wg-text-soft) !important; + background: + linear-gradient(180deg, rgba(255, 244, 221, 0.028), transparent 36%), + var(--wg-velvet-panel) !important; + box-shadow: + inset 0 1px 0 rgba(255, 244, 221, 0.035), + 0 18px 42px rgba(0, 0, 0, 0.42) !important; +} + +.wg-topbar .notifications-dropdown, +.wg-topbar .chats-dropdown, +.wg-topbar .drafts-dropdown { + width: min(24rem, calc(100vw - 1.5rem)); +} + +.wg-topbar .dropdown-item, +.wg-topbar .btn.btn-ghost, +.wg-topbar .btn.btn-light { + color: var(--wg-text-soft) !important; +} + +.wg-topbar .dropdown-item:hover, +.wg-topbar .dropdown-item:focus, +.wg-topbar .btn.btn-ghost:hover, +.wg-topbar .btn.btn-ghost:focus, +.wg-topbar .btn.btn-light:hover, +.wg-topbar .btn.btn-light:focus { + color: var(--wg-text) !important; + background: rgba(194, 163, 90, 0.08) !important; +} + +.wg-topbar .btn-primary, +.wg-topbar__register { + border-color: rgba(194, 163, 90, 0.34) !important; + color: #1b1208 !important; + background: linear-gradient(to bottom, #d8c28a, var(--wg-gold)) !important; +} + +.wg-topbar__login, +.wg-topbar__register { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 2.35rem; + border-radius: 6px; + padding: 0.35rem 0.78rem; + font-family: var(--wg-font-ui); + font-size: 0.86rem; + font-weight: 600; + text-decoration: none; +} + +.wg-topbar__login { + color: var(--wg-text-soft); + border: 1px solid rgba(194, 163, 90, 0.2); + background: rgba(194, 163, 90, 0.055); +} + +.wg-topbar__login:hover, +.wg-topbar__login:focus { + color: var(--wg-text); + border-color: rgba(194, 163, 90, 0.3); + background: rgba(194, 163, 90, 0.1); +} + +.wg-topbar__panel { + position: absolute; + top: calc(100% + 0.45rem); + right: 0; + display: none; + padding: 0.42rem; +} + +.wg-topbar__panel.is-open { + display: grid; + gap: 0.2rem; +} + +.wg-topbar__panel--forums a { + display: flex; + align-items: center; + min-height: 2.25rem; + border-radius: 6px; + padding: 0.35rem 0.55rem; + color: var(--wg-text-soft); + font-size: 0.9rem; + text-decoration: none; +} + +.wg-topbar__panel--forums a:hover, +.wg-topbar__panel--forums a:focus { + color: var(--wg-text); + background: rgba(194, 163, 90, 0.08); +} + +.wg-topbar__burger { + display: none; + align-items: center; + justify-content: center; + width: 2.4rem; + height: 2.4rem; + border: 1px solid rgba(194, 163, 90, 0.22); + border-radius: 6px; + color: var(--wg-text-soft); + background: rgba(194, 163, 90, 0.055); +} + +.wg-topbar__burger:hover, +.wg-topbar__burger:focus { + color: var(--wg-text); + background: rgba(194, 163, 90, 0.1); +} + +.wg-topbar__drawer { + display: none; + border-top: 1px solid rgba(194, 163, 90, 0.12); + background: + linear-gradient(180deg, rgba(42, 18, 34, 0.98), rgba(12, 10, 15, 0.99)); +} + +.wg-topbar.is-drawer-open .wg-topbar__drawer { + display: block; +} + +.wg-topbar__drawer-inner { + display: grid; + gap: 0.8rem; + width: min(100%, 42rem); + margin: 0 auto; + padding: 0.85rem; +} + +.wg-topbar__drawer-search { + display: flex; + align-items: center; + gap: 0.55rem; + border: 1px solid rgba(194, 163, 90, 0.18); + border-radius: 8px; + padding: 0.5rem 0.65rem; + background: rgba(9, 8, 11, 0.45); +} + +.wg-topbar__drawer-search input { + min-width: 0; + width: 100%; + border: 0; + color: var(--wg-text); + background: transparent; + outline: 0; +} + +.wg-topbar__drawer-nav, +.wg-topbar__drawer-actions, +.wg-topbar__drawer-auth { + display: grid; + gap: 0.35rem; +} + +.wg-topbar__drawer-link, +.wg-topbar__drawer-actions a { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.55rem; + border: 1px solid rgba(194, 163, 90, 0.12); + border-radius: 7px; + padding: 0.62rem 0.7rem; + color: var(--wg-text-soft); + background: rgba(255, 244, 221, 0.02); + text-decoration: none; +} + +.wg-topbar__drawer-link:hover, +.wg-topbar__drawer-link:focus, +.wg-topbar__drawer-actions a:hover, +.wg-topbar__drawer-actions a:focus { + color: var(--wg-text); + border-color: rgba(194, 163, 90, 0.22); + background: rgba(194, 163, 90, 0.08); +} + +@media (max-width: 1199.98px) { + .wg-topbar__inner { + grid-template-columns: auto minmax(0, 1fr) auto; + } + + .wg-topbar__nav { + display: none; + } +} + +@media (max-width: 991.98px) { + .wg-topbar__inner { + grid-template-columns: minmax(0, 1fr) auto; + } + + .wg-topbar__brand-name { + max-width: 16ch; + } + + .wg-topbar__brand-subtitle, + .wg-topbar__forums, + .wg-topbar__utilities { + display: none; + } + + .wg-topbar__burger { + display: inline-flex; + } +} + +@media (max-width: 480px) { + .wg-topbar__brand-mark { + width: 2rem; + height: 2rem; + } + + .wg-topbar__brand-name { + max-width: 13ch; + font-size: 0.82rem; + letter-spacing: 0.05em; + } +} diff --git a/templates/footer.tpl b/templates/footer.tpl new file mode 100644 index 0000000..60e0878 --- /dev/null +++ b/templates/footer.tpl @@ -0,0 +1,20 @@ + + + + + +
+ +
+ + {{{ if !isSpider }}} +
+
+ +
+
+ {{{ end }}} + + + + diff --git a/templates/header.tpl b/templates/header.tpl index 412826c..83fe707 100644 --- a/templates/header.tpl +++ b/templates/header.tpl @@ -25,15 +25,10 @@ [[global:skip-to-content]] - {{{ if config.theme.topMobilebar }}} - - {{{ end }}} - -
- +
+
-
diff --git a/templates/partials/header/brand.tpl b/templates/partials/header/brand.tpl deleted file mode 100644 index f7f0b69..0000000 --- a/templates/partials/header/brand.tpl +++ /dev/null @@ -1,26 +0,0 @@ -{{{ if (brand:logo || (config.showSiteTitle || widgets.brand-header.length)) }}} -
-
-
-
- {{{ if config.showSiteTitle }}} - - {{{ end }}} - {{{ if widgets.brand-header.length }}} -
- {{{each widgets.brand-header}}} - {{./html}} - {{{end}}} -
- {{{ end }}} -
-
-
-{{{ end }}} diff --git a/templates/partials/header/topbar.tpl b/templates/partials/header/topbar.tpl new file mode 100644 index 0000000..93f9623 --- /dev/null +++ b/templates/partials/header/topbar.tpl @@ -0,0 +1,150 @@ +
+
+ + + + {config.siteTitle} + Shadows Over Westgate + + + + + + + +
+ {{{ if config.loggedIn }}} +
    + {{{ if (config.searchEnabled && user.privileges.search:content) }}} + + {{{ end }}} + + + + {{{ if canChat }}} + + {{{ end }}} + + + + {{{ if !config.disableCustomUserSkins }}} + + {{{ end }}} + + +
+ {{{ else }}} +
    + {{{ if (config.searchEnabled && user.privileges.search:content) }}} + + {{{ end }}} + + + {{{ if allowRegistration }}} + + {{{ end }}} +
+ {{{ end }}} +
+ + +
+ +
+
+ {{{ if (config.searchEnabled && user.privileges.search:content) }}} + + {{{ end }}} + + + + {{{ if config.loggedIn }}} + + {{{ else }}} +
+ + {{{ if allowRegistration }}} + [[global:register]] + {{{ end }}} +
+ {{{ end }}} +
+
+
diff --git a/tests/global-topbar-contract.test.js b/tests/global-topbar-contract.test.js new file mode 100644 index 0000000..a6f2799 --- /dev/null +++ b/tests/global-topbar-contract.test.js @@ -0,0 +1,127 @@ +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +function read(relativePath) { + const filePath = path.join(__dirname, '..', relativePath); + assert(fs.existsSync(filePath), `${relativePath} should exist`); + return fs.readFileSync(filePath, 'utf8'); +} + +function assertMissing(relativePath, message) { + const filePath = path.join(__dirname, '..', relativePath); + assert(!fs.existsSync(filePath), message || `${relativePath} should not exist`); +} + +function assertIncludes(haystack, needle, message) { + assert( + haystack.includes(needle), + message || `Expected file to include ${needle}` + ); +} + +function assertExcludes(haystack, needle, message) { + assert( + !haystack.includes(needle), + message || `Expected file not to include ${needle}` + ); +} + +const header = read('templates/header.tpl'); +const footer = read('templates/footer.tpl'); +const topbar = read('templates/partials/header/topbar.tpl'); +const theme = read('theme.scss'); +const stylesheet = read('scss/westgate/_topbar.scss'); +const client = read('public/client.js'); +const themeHooks = read('lib/theme.js'); + +assertIncludes( + header, + '', + 'Header should mount the Westgate topbar partial' +); +assertExcludes( + header, + '', + "Header should no longer render Harmony's left global sidebar" +); +assertExcludes( + footer, + '', + "Footer should no longer render Harmony's right global sidebar" +); +assertIncludes( + theme, + '@import "./scss/westgate/topbar";', + 'theme.scss should import the focused topbar partial' +); +assertExcludes( + theme, + '@import "./scss/westgate/header";', + 'theme.scss should not import the removed brand banner stylesheet' +); +assertIncludes( + stylesheet, + '.wg-topbar', + 'Topbar styles should be scoped to .wg-topbar' +); +assertIncludes( + client, + 'initWestgateTopbar', + 'Client bundle should initialize Westgate topbar behavior' +); + +[ + 'component="sidebar/search"', + 'component="notifications"', + 'component="sidebar/drafts"', +].forEach((component) => { + assertIncludes(topbar, component, `Topbar should preserve live NodeBB hook ${component}`); +}); + +[ + '', + '', + '', + '', + '', +].forEach((templateImport) => { + assertIncludes(topbar, templateImport, `Topbar should reuse Harmony live partial ${templateImport}`); +}); + +[ + 'Velessa Thorne', + 'Guildmaster Orin', + 'A letter left at the Undergate', + 'wg-topbar-demo', + "localStorage.setItem('wg-topbar-presence'", +].forEach((fixture) => { + assertExcludes(topbar, fixture, `Topbar should not ship preview fixture ${fixture}`); +}); + +assertIncludes(topbar, '{{{ if config.loggedIn }}}', 'Topbar should branch on the real logged-in state'); +assertIncludes(topbar, '{{{ if canChat }}}', 'Chat controls should remain permission-gated'); +assertIncludes(topbar, '{{{ if allowRegistration }}}', 'Registration should remain server-gated'); +assertIncludes(topbar, '{{{ each navigation }}}', 'Topbar should render ACP Navigation items'); +assertIncludes(topbar, '{./textClass}', 'Topbar should honor ACP Navigation text visibility classes'); +assertExcludes(topbar, 'href="{relative_path}/admin"', 'Topbar should not hard-code the administrator link'); + +assertMissing( + 'templates/partials/header/brand.tpl', + 'Theme should inherit Harmony brand markup instead of shipping the removed Westgate brand banner override' +); +assertMissing( + 'scss/westgate/_header.scss', + 'Removed brand banner styles should not remain in the theme' +); +assertMissing( + 'public/images/plum-header-bg.png', + 'Removed brand banner background should not remain in static assets' +); +assertExcludes( + themeHooks, + 'brand-header', + 'Removed brand banner widget area should not remain registered' +); diff --git a/theme.scss b/theme.scss index ee8e390..a4a066c 100644 --- a/theme.scss +++ b/theme.scss @@ -1,7 +1,7 @@ @import "../nodebb-theme-harmony/theme"; @import "./scss/westgate/tokens"; @import "./scss/westgate/wiki-prose"; -@import "./scss/westgate/header"; +@import "./scss/westgate/topbar"; @import "./scss/westgate/surfaces"; @import "./scss/westgate/categories"; @import "./scss/westgate/topics";