Add Westgate global footer (#8)
## Summary - add the global Westgate footer partial under `templates/partials/footer/` - import scoped footer styles from `theme.scss` - include the footer design spec and powered-by HTML/CSS references - add a contract test for the footer integration and required links ## Verification - `node tests/global-footer-contract.test.js` - `node tests/global-topbar-contract.test.js` - `for test in tests/*.test.js; do node "$test"; done` - `npm test --if-present` - `git diff --check` Reviewed-on: #8 Co-authored-by: vickydotbat <vickydotbat@tutamail.com> Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
This commit was merged in pull request #8.
This commit is contained in:
@@ -0,0 +1,238 @@
|
|||||||
|
# Global Footer Design
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Add a Westgate-owned global footer to the NodeBB theme. The footer is permanent
|
||||||
|
theme chrome, not widget or Custom Pages content. It renders on every normal
|
||||||
|
NodeBB page that uses the theme layout, sits below the main page content, and
|
||||||
|
includes both Westgate navigation and the platform credit pills for NodeBB and
|
||||||
|
Tiptap.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Use a hardcoded theme partial.
|
||||||
|
|
||||||
|
The footer links are intentionally different from the topbar. The topbar remains
|
||||||
|
broad, task-oriented navigation for moving through the site and forum. The
|
||||||
|
footer carries more specific, secondary, and contextual links. This matches the
|
||||||
|
usual website pattern and avoids forcing one navigation model to serve two
|
||||||
|
different jobs.
|
||||||
|
|
||||||
|
The "Powered by NodeBB" and "Powered by Tiptap" pills should be folded into the
|
||||||
|
same global footer as the final credit row. They should not remain as a separate
|
||||||
|
global footer fragment unless an upstream plugin forces them outside theme
|
||||||
|
control.
|
||||||
|
|
||||||
|
## Source References
|
||||||
|
|
||||||
|
- `custom_pages/Westgate.dc.html`: extracted Claude Design footer layout,
|
||||||
|
content, palette, and spacing direction.
|
||||||
|
- `docs/superpowers/specs/global-footer-powered-by.html`: supplied powered-by
|
||||||
|
markup reference.
|
||||||
|
- `docs/superpowers/specs/global-footer-powered-by.css`: supplied powered-by
|
||||||
|
style reference.
|
||||||
|
- `templates/footer.tpl`: current local footer override and integration point.
|
||||||
|
- `theme.scss`: imports-only SCSS entry point.
|
||||||
|
|
||||||
|
The deposited powered-by files are reference material. Production markup and
|
||||||
|
styles should live in the theme partial and focused SCSS file, not in
|
||||||
|
`docs/superpowers/specs/`.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
- `templates/partials/footer/westgate-global.tpl`
|
||||||
|
- `scss/westgate/_footer.scss`
|
||||||
|
|
||||||
|
Modify:
|
||||||
|
|
||||||
|
- `templates/footer.tpl`
|
||||||
|
- `theme.scss`
|
||||||
|
- a focused contract test, either a new `tests/global-footer-contract.test.js`
|
||||||
|
or a small extension of the existing global topbar contract test.
|
||||||
|
|
||||||
|
Do not add JavaScript, dependencies, widget areas, admin settings, or root-level
|
||||||
|
theme styling files.
|
||||||
|
|
||||||
|
## Template Integration
|
||||||
|
|
||||||
|
Mount the footer partial from `templates/footer.tpl` after `</main>` and before
|
||||||
|
the closing `.layout-container` wrapper:
|
||||||
|
|
||||||
|
```tpl
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- IMPORT partials/footer/westgate-global.tpl -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep the existing mobile topic navigator, toaster tray, reconnect alert, and
|
||||||
|
footer JS import in place. Do not reintroduce Harmony's right sidebar import.
|
||||||
|
|
||||||
|
The partial should use semantic footer markup:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<footer class="wg-footer" aria-label="Shadows Over Westgate footer">
|
||||||
|
...
|
||||||
|
</footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Content
|
||||||
|
|
||||||
|
The footer has three main content areas plus a final low-credit row.
|
||||||
|
|
||||||
|
### Brand Area
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
- Westgate diamond mark.
|
||||||
|
- Site name from `{config.siteTitle}` so the footer follows the configured
|
||||||
|
NodeBB site title.
|
||||||
|
- Short description:
|
||||||
|
"A Neverwinter Nights: Enhanced Edition persistent world of gothic intrigue,
|
||||||
|
set in the Forgotten Realms port city of Westgate."
|
||||||
|
- Flavor line:
|
||||||
|
"Mind the shadows."
|
||||||
|
|
||||||
|
### Explore Links
|
||||||
|
|
||||||
|
Hardcode these as footer-specific links:
|
||||||
|
|
||||||
|
- Home
|
||||||
|
- News
|
||||||
|
- Gallery
|
||||||
|
- Dev Blog
|
||||||
|
- Join the Team
|
||||||
|
|
||||||
|
Use real anchors. Prefer `{relative_path}` for internal routes. Do not use
|
||||||
|
Claude Design preview handlers such as `onClick="{{ goHome }}"`.
|
||||||
|
|
||||||
|
Hardcode the final `href` values in the footer partial after confirming the
|
||||||
|
existing production routes. Do not read these links dynamically from ACP
|
||||||
|
Navigation.
|
||||||
|
|
||||||
|
### Community Links
|
||||||
|
|
||||||
|
Hardcode these as footer-specific links:
|
||||||
|
|
||||||
|
- Forums
|
||||||
|
- Wiki
|
||||||
|
- Register
|
||||||
|
- How to Join
|
||||||
|
|
||||||
|
Use confirmed internal routes where they exist:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<a href="{relative_path}/categories">Forums</a>
|
||||||
|
<a href="{relative_path}/wiki">Wiki</a>
|
||||||
|
<a href="{relative_path}/register">Register</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
If a destination route is not confirmed at implementation time, use the closest
|
||||||
|
existing route rather than creating new routing behavior as part of this task.
|
||||||
|
|
||||||
|
### Legal And Setting Line
|
||||||
|
|
||||||
|
Include the existing Claude Design bottom text:
|
||||||
|
|
||||||
|
- `© 2026 Shadows Over Westgate · A NWN:EE Persistent World`
|
||||||
|
- `Forgotten Realms · Westgate · Sea of Fallen Stars`
|
||||||
|
|
||||||
|
This is display content, not a dynamic copyright system.
|
||||||
|
|
||||||
|
### Powered-By Row
|
||||||
|
|
||||||
|
Fold the NodeBB and Tiptap credit pills into the global footer as the final row.
|
||||||
|
Use the supplied reference files for structure and visual treatment.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Link NodeBB to `https://nodebb.org`.
|
||||||
|
- Link Tiptap to `https://tiptap.dev`.
|
||||||
|
- External links use `target="_blank"` and `rel="noopener noreferrer"`.
|
||||||
|
- Each pill has an accessible name such as `aria-label="Powered by NodeBB"`.
|
||||||
|
- The row is visually subordinate to the Westgate content.
|
||||||
|
- The row remains inside `.wg-footer` so the footer is one component.
|
||||||
|
|
||||||
|
If an upstream plugin also renders an unavoidable Tiptap credit elsewhere, do
|
||||||
|
not hide it blindly in this task. First confirm whether that credit is required
|
||||||
|
by the plugin license or only redundant UI.
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
Add footer styles to `scss/westgate/_footer.scss` and import that file from
|
||||||
|
`theme.scss`:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@import "./scss/westgate/footer";
|
||||||
|
```
|
||||||
|
|
||||||
|
Style scope must be `.wg-footer` and descendants.
|
||||||
|
|
||||||
|
Visual direction:
|
||||||
|
|
||||||
|
- near-black and plum base
|
||||||
|
- muted gold borders and focus accents
|
||||||
|
- restrained depth, not bright SaaS treatment
|
||||||
|
- max-width content container matching the extracted design, roughly `1240px`
|
||||||
|
- three-column desktop layout
|
||||||
|
- stacked mobile layout
|
||||||
|
- powered-by pills centered in the final footer row
|
||||||
|
|
||||||
|
Prefer existing theme tokens and SCSS variables where available. The deposited
|
||||||
|
powered-by CSS uses custom properties that may not exist in the theme; translate
|
||||||
|
those values into the theme's existing SCSS/token language rather than copying
|
||||||
|
non-theme variables verbatim.
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
The footer must:
|
||||||
|
|
||||||
|
- use semantic `<footer>` markup
|
||||||
|
- use real `<a>` links
|
||||||
|
- provide visible hover and focus states
|
||||||
|
- preserve readable contrast on the dark background
|
||||||
|
- avoid icon-only links without accessible names
|
||||||
|
- keep link text understandable out of context
|
||||||
|
- remain usable with the fixed mobile bottom navigator present
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Add a small contract test that verifies:
|
||||||
|
|
||||||
|
- `templates/footer.tpl` imports `partials/footer/westgate-global.tpl`.
|
||||||
|
- `theme.scss` imports `./scss/westgate/footer`.
|
||||||
|
- `templates/partials/footer/westgate-global.tpl` exists.
|
||||||
|
- the partial contains `.wg-footer`.
|
||||||
|
- the partial contains the NodeBB and Tiptap powered-by links.
|
||||||
|
- internal footer links use anchors rather than Claude Design preview handlers.
|
||||||
|
- internal links do not hardcode `https://westgate.pw`.
|
||||||
|
- Harmony's right sidebar import remains absent from `templates/footer.tpl`.
|
||||||
|
|
||||||
|
Run the relevant Node test script directly, matching the existing contract test
|
||||||
|
style.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
Do not:
|
||||||
|
|
||||||
|
- create a new widget area
|
||||||
|
- add admin configuration
|
||||||
|
- add JavaScript
|
||||||
|
- create or change routes
|
||||||
|
- copy the entire Claude Design export
|
||||||
|
- modify topbar behavior
|
||||||
|
- alter unrelated templates
|
||||||
|
- hide upstream powered-by content without confirming whether it is required
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Every normal theme page renders one Westgate global footer.
|
||||||
|
- Footer links are hardcoded and footer-specific.
|
||||||
|
- The powered-by pills are visually part of the footer, not a separate orphan
|
||||||
|
fragment.
|
||||||
|
- NodeBB mobile navigation, reconnect alerts, toaster tray, and footer JS still
|
||||||
|
work.
|
||||||
|
- The theme remains a small child theme over Harmony.
|
||||||
|
- Focused contract tests pass.
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// Hide sidebars
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
nav.sidebar.sidebar-left,
|
||||||
|
nav.sidebar.sidebar-right {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Westgate "Powered By..."
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
// -- Pill --
|
||||||
|
|
||||||
|
.westgate-powered-by {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 0 14px;
|
||||||
|
font-family: var(--wg-font-ui);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.westgate-powered-pill {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 7px;
|
||||||
|
height: 32px;
|
||||||
|
width: 174px;
|
||||||
|
padding: 0 12px;
|
||||||
|
background: var(--wg-velvet-panel) !important;
|
||||||
|
border: 1px solid var(--wg-border) !important;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: var(--wg-velvet-shadow) !important;
|
||||||
|
color: var(--wg-text-muted);
|
||||||
|
line-height: 1;
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
background 0.18s ease,
|
||||||
|
border-color 0.18s ease,
|
||||||
|
box-shadow 0.18s ease,
|
||||||
|
color 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.westgate-powered-by .powered-label {
|
||||||
|
color: var(--wg-ledger-ink);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0.95;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.westgate-powered-pill:hover,
|
||||||
|
.westgate-powered-pill:focus {
|
||||||
|
background: var(--wg-velvet-panel-hover) !important;
|
||||||
|
border-color: rgba(194, 163, 90, 0.22) !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.035),
|
||||||
|
inset 0 0 38px rgba(120, 36, 84, 0.14),
|
||||||
|
0 12px 30px rgba(0, 0, 0, 0.3) !important;
|
||||||
|
color: #efe7da;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.westgate-powered-pill:focus-visible {
|
||||||
|
border-color: rgba(216, 194, 138, 0.38) !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.035),
|
||||||
|
inset 0 0 38px rgba(120, 36, 84, 0.14),
|
||||||
|
0 0 0 2px rgba(194, 163, 90, 0.18),
|
||||||
|
0 12px 30px rgba(0, 0, 0, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.westgate-powered-by .powered-label {
|
||||||
|
color: var(--wg-ledger-ink);
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: 0.95;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.westgate-powered-pill:hover .powered-label,
|
||||||
|
.westgate-powered-pill:focus .powered-label {
|
||||||
|
color: #efe7da;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Logos --
|
||||||
|
|
||||||
|
.westgate-powered-by .nodebb-logo,
|
||||||
|
.westgate-powered-by .tiptap-logo {
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: auto;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Give both logos the same visual box */
|
||||||
|
.westgate-powered-by .nodebb-logo {
|
||||||
|
height: 14px;
|
||||||
|
min-width: 64px;
|
||||||
|
transform: translateY(-0.25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.westgate-powered-by .tiptap-logo {
|
||||||
|
height: 15px;
|
||||||
|
min-width: 64px;
|
||||||
|
color: #f2edf7;
|
||||||
|
transform: translateY(-0.25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.westgate-powered-pill:hover .nodebb-logo,
|
||||||
|
.westgate-powered-pill:focus .nodebb-logo,
|
||||||
|
.westgate-powered-pill:hover .tiptap-logo,
|
||||||
|
.westgate-powered-pill:focus .tiptap-logo {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<a
|
||||||
|
class="westgate-powered-pill"
|
||||||
|
href="https://nodebb.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Powered by NodeBB"
|
||||||
|
>
|
||||||
|
<span class="powered-label">Powered by</span>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="nodebb-logo"
|
||||||
|
viewBox="0 0 322 75"
|
||||||
|
role="img"
|
||||||
|
aria-label="NodeBB"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<radialGradient
|
||||||
|
id="westgate-nodebb-a"
|
||||||
|
cx="65.599%"
|
||||||
|
cy="0%"
|
||||||
|
r="169.036%"
|
||||||
|
fx="65.599%"
|
||||||
|
fy="0%"
|
||||||
|
gradientTransform="matrix(-.5183 .5916 -.38031 -.80624 .996 -.388)"
|
||||||
|
>
|
||||||
|
<stop offset="0%" stop-color="#2A6CBE" />
|
||||||
|
<stop offset="38.688%" stop-color="#2062BC" />
|
||||||
|
<stop offset="49.182%" stop-color="#1F5FBC" />
|
||||||
|
<stop offset="66.583%" stop-color="#1C5ABD" />
|
||||||
|
<stop offset="85.354%" stop-color="#1956BC" />
|
||||||
|
<stop offset="100%" stop-color="#1851BE" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" transform="translate(1 1)">
|
||||||
|
<path
|
||||||
|
fill="#FFF"
|
||||||
|
stroke="#FFF"
|
||||||
|
stroke-width=".275"
|
||||||
|
d="M195.810017,20 C210.691849,20 221.256813,28.7272272 221.484017,45.845679 C221.490864,46.3615394 221.461776,47.1194739 221.396754,48.1194827 C221.328352,49.1714504 220.455158,49.9897119 219.400969,49.9897119 L179.110557,49.9897119 C179.110557,56.6336943 182.723978,64.2757202 196.378026,64.2757202 C204.088827,64.2757202 211.625406,61.928339 218.987763,57.2335766 L219.553745,56.8677968 C220.246813,56.4139645 221.17656,56.6079034 221.630392,57.3009716 C221.757029,57.4943637 221.837254,57.7144077 221.864806,57.943925 C222.131013,60.1615294 222.004084,61.5832211 221.484017,62.2089999 C220.452051,63.4507301 210.901843,73 196.378026,73 C180.58738,73 169,61.8765432 169,46.3909465 C169,31.3415638 180.019372,20 195.810017,20 Z M162.92733,0 C164.031899,-4.2495073e-16 164.92733,0.8954305 164.92733,2 L164.928075,44.1266361 C164.975781,44.8118124 165,45.5058194 165,46.2079256 C165,61.5334332 153.460581,73 138,73 C122.427386,73 111,61.5334332 111,46.2079256 C111,30.882418 122.427386,19.4158511 138,19.4158511 C144.981278,19.4158511 151.163029,21.7538895 155.843826,25.7295173 L155.843557,4.54188628 C155.843557,2.03347175 157.877029,4.60788274e-16 160.385444,0 L162.92733,0 Z M81,20 C96.4605809,20 108,31.3415638 108,46.5 C108,61.6584362 96.4605809,73 81,73 C65.4273859,73 54,61.6584362 54,46.5 C54,31.3415638 65.4273859,20 81,20 Z M27.324263,20 C41.6099773,20 50,29.1649049 50,41.7801268 L50,67.5 C50,69.4329966 48.4329966,71 46.5,71 L42.2494331,71 C41.1448636,71 40.2494331,70.1045695 40.2494331,69 L40.2494331,42.5348837 C40.2494331,34.3403805 35.8276644,28.6257928 26.3038549,28.6257928 C10.0816327,28.6257928 9.75718821,35.7795705 9.75069932,44.6831843 L9.75056689,71 L2,71 C0.8954305,71 1.3527075e-16,70.1045695 0,69 L0,35.2932975 L0,35.2932975 C0,24.3935718 19.1609977,20 27.324263,20 Z M81,28.7242798 C70.8049793,28.7242798 63.6348548,36.4670782 63.6348548,46.5 C63.6348548,56.3148148 70.8049793,64.2757202 81,64.2757202 C91.1950207,64.2757202 98.3651452,56.3148148 98.3651452,46.5 C98.3651452,36.4670782 91.1950207,28.7242798 81,28.7242798 Z M138,28.2362872 C127.804979,28.2362872 120.634855,36.0644241 120.634855,46.2079256 C120.634855,56.1309161 127.804979,64.179564 138,64.179564 C148.195021,64.179564 155.365145,56.1309161 155.365145,46.2079256 C155.365145,36.0644241 148.195021,28.2362872 138,28.2362872 Z M195.810017,28.7242798 C186.721876,28.7242798 180.473779,33.9588477 179.224159,41.8106996 L211.259858,41.8106996 C210.010238,33.9588477 204.443752,28.7242798 195.810017,28.7242798 Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="url(#westgate-nodebb-a)"
|
||||||
|
d="M277,19 L276.999615,57.7303365 C276.99251,64.180962 276.88949,68.6242617 276.88949,73 L276.88949,73 L255.649055,73 C242.962262,73 236,67.06 236,57.7257143 C236,51.4771429 239.558488,46.9257143 245.1283,45.0742857 C240.564149,43.1457143 237.779243,38.8257143 237.779243,33.5028571 C237.779243,24.8628571 244.664149,19 256.732074,19 L256.732074,19 L277,19 Z M299.267926,19 C311.335851,19 318.220757,24.8628571 318.220757,33.5028571 C318.220757,38.8257143 315.435851,43.1457143 310.8717,45.0742857 C316.441512,46.9257143 320,51.4771429 320,57.7257143 C320,67.06 313.037738,73 300.350945,73 L279.11051,73 L279.11051,73 C279.11051,68.6242617 279.00749,64.180962 279.000385,57.7303365 L279,19 L299.267926,19 Z M266.092452,49.8571429 L256.499999,49.8571429 C250.156602,49.8571429 246.984904,52.2485714 246.984904,56.9542857 C246.984904,61.8914286 249.924527,64.36 255.726414,64.36 L255.726414,64.36 L260.754716,64.36 C264.777358,64.36 266.092452,62.4314286 266.092452,56.5685714 L266.092452,56.5685714 L266.092452,49.8571429 Z M299.500001,49.8571429 L289.907548,49.8571429 L289.907548,56.5685714 C289.907548,62.4314286 291.222642,64.36 295.245284,64.36 L300.273586,64.36 C306.075473,64.36 309.015096,61.8914286 309.015096,56.9542857 C309.015096,52.2485714 305.843398,49.8571429 299.500001,49.8571429 Z M266.169811,27.64 L257.350942,27.64 C251.703772,27.64 248.764149,30.1857143 248.764149,34.5057143 C248.764149,38.8257143 251.703772,41.3714286 257.350942,41.3714286 L257.350942,41.3714286 L266.169811,41.3714286 L266.169811,27.64 Z M298.649058,27.64 L289.830189,27.64 L289.830189,41.3714286 L298.649058,41.3714286 C304.296228,41.3714286 307.235851,38.8257143 307.235851,34.5057143 C307.235851,30.1857143 304.296228,27.64 298.649058,27.64 Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="westgate-powered-pill"
|
||||||
|
href="https://tiptap.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Powered by Tiptap"
|
||||||
|
>
|
||||||
|
<span class="powered-label">Powered by</span>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="tiptap-logo"
|
||||||
|
viewBox="0 0 176 44"
|
||||||
|
role="img"
|
||||||
|
aria-label="Tiptap"
|
||||||
|
>
|
||||||
|
<g fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M21.7 2.5c8.8 0 16.5 3.5 20.3 8.5H1.4c3.8-5 11.5-8.5 20.3-8.5Z"
|
||||||
|
/>
|
||||||
|
<rect x="0" y="15" width="43.5" height="6.6" rx="3.3" />
|
||||||
|
<rect x="0" y="25.4" width="43.5" height="6.6" rx="3.3" />
|
||||||
|
<path d="M1.4 36h40.6c-3.8 5-11.5 8.5-20.3 8.5S5.2 41 1.4 36Z" />
|
||||||
|
|
||||||
|
<text
|
||||||
|
x="54"
|
||||||
|
y="32"
|
||||||
|
font-family="Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
|
||||||
|
font-size="31"
|
||||||
|
font-weight="800"
|
||||||
|
letter-spacing="-1.7"
|
||||||
|
>
|
||||||
|
tiptap
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
.wg-footer {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
border-top: 1px solid rgba(194, 163, 90, 0.16);
|
||||||
|
color: var(--wg-text-muted);
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(13, 9, 18, 0), rgba(8, 5, 12, 0.76)),
|
||||||
|
var(--wg-bg-deep);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--wg-text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
border-color 0.18s ease,
|
||||||
|
box-shadow 0.18s ease,
|
||||||
|
color 0.18s ease,
|
||||||
|
background 0.18s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: var(--wg-text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: 2px solid var(--wg-focus);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__inner {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.6fr) minmax(10rem, 1fr) minmax(10rem, 1fr);
|
||||||
|
gap: clamp(2rem, 5vw, 3.125rem);
|
||||||
|
width: min(100%, 1240px);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: clamp(2.75rem, 6vw, 4rem) clamp(1rem, 4vw, 2rem) 1.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__brand-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__mark {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.375rem;
|
||||||
|
height: 1.375rem;
|
||||||
|
border: 1.5px solid var(--wg-gold);
|
||||||
|
color: var(--wg-gold);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
|
||||||
|
span {
|
||||||
|
width: 0.4375rem;
|
||||||
|
height: 0.4375rem;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__title,
|
||||||
|
.wg-footer .wg-footer__heading {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--wg-font-display);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__title {
|
||||||
|
color: var(--wg-text);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__description {
|
||||||
|
max-width: 24rem;
|
||||||
|
margin: 0 0 1.125rem;
|
||||||
|
color: var(--wg-text-muted);
|
||||||
|
font-family: var(--wg-font-text);
|
||||||
|
font-size: 0.94rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__motto {
|
||||||
|
margin: 0;
|
||||||
|
color: #9a8262;
|
||||||
|
font-family: var(--wg-font-display);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__heading {
|
||||||
|
margin-bottom: 1.125rem;
|
||||||
|
color: var(--wg-gold);
|
||||||
|
font-family: var(--wg-font-ui);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
font-family: var(--wg-font-ui);
|
||||||
|
font-size: 0.94rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
width: min(100%, 1240px);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1.375rem clamp(1rem, 4vw, 2rem);
|
||||||
|
border-top: 1px solid rgba(194, 163, 90, 0.1);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: #7f756f;
|
||||||
|
font-family: var(--wg-font-ui);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__powered-by {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.625rem;
|
||||||
|
width: min(100%, 1240px);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1rem 1.875rem;
|
||||||
|
font-family: var(--wg-font-ui);
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__powered-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
width: 10.875rem;
|
||||||
|
min-height: 2rem;
|
||||||
|
border: 1px solid var(--wg-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.42rem 0.75rem;
|
||||||
|
color: var(--wg-text-muted);
|
||||||
|
background: var(--wg-velvet-panel);
|
||||||
|
box-shadow: var(--wg-velvet-shadow);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
border-color: rgba(194, 163, 90, 0.22);
|
||||||
|
color: #efe7da;
|
||||||
|
background: var(--wg-velvet-panel-hover);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.035),
|
||||||
|
inset 0 0 38px rgba(120, 36, 84, 0.14),
|
||||||
|
0 12px 30px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.035),
|
||||||
|
inset 0 0 38px rgba(120, 36, 84, 0.14),
|
||||||
|
0 0 0 2px rgba(194, 163, 90, 0.18),
|
||||||
|
0 12px 30px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__powered-label {
|
||||||
|
color: var(--wg-ledger-ink);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__powered-name {
|
||||||
|
color: #f2edf7;
|
||||||
|
font-family: var(--wg-font-display);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.wg-footer {
|
||||||
|
margin-bottom: 3.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__inner {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-footer .wg-footer__meta {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
</div><!-- /.container#content -->
|
</div><!-- /.container#content -->
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- IMPORT partials/footer/westgate-global.tpl -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fixed-bottom d-lg-none">
|
<div class="fixed-bottom d-lg-none">
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<footer class="wg-footer" aria-label="Shadows Over Westgate footer">
|
||||||
|
<div class="wg-footer__inner">
|
||||||
|
<section class="wg-footer__brand" aria-labelledby="wg-footer-title">
|
||||||
|
<div class="wg-footer__brand-row">
|
||||||
|
<span class="wg-footer__mark" aria-hidden="true"><span></span></span>
|
||||||
|
<h2 class="wg-footer__title" id="wg-footer-title">{config.siteTitle}</h2>
|
||||||
|
</div>
|
||||||
|
<p class="wg-footer__description">
|
||||||
|
A Neverwinter Nights: Enhanced Edition persistent world of gothic intrigue, set in the Forgotten Realms port city of Westgate.
|
||||||
|
</p>
|
||||||
|
<p class="wg-footer__motto">Mind the shadows.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav class="wg-footer__nav" aria-label="Explore">
|
||||||
|
<h2 class="wg-footer__heading">Explore</h2>
|
||||||
|
<ul class="wg-footer__links">
|
||||||
|
<li><a href="{relative_path}/">Home</a></li>
|
||||||
|
<li><a href="{relative_path}/category/1/news">News</a></li>
|
||||||
|
<li><a href="{relative_path}/gallery">Gallery</a></li>
|
||||||
|
<li><a href="{relative_path}/category/84/developer-blog">Dev Blog</a></li>
|
||||||
|
<li><a href="{relative_path}/join-the-team">Join the Team</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<nav class="wg-footer__nav" aria-label="Community">
|
||||||
|
<h2 class="wg-footer__heading">Community</h2>
|
||||||
|
<ul class="wg-footer__links">
|
||||||
|
<li><a href="{relative_path}/categories">Forums</a></li>
|
||||||
|
<li><a href="{relative_path}/wiki">Wiki</a></li>
|
||||||
|
<li><a href="{relative_path}/register">Register</a></li>
|
||||||
|
<li><a href="{relative_path}/wiki">How to Join</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wg-footer__meta">
|
||||||
|
<p>© 2026 Shadows Over Westgate · A NWN:EE Persistent World</p>
|
||||||
|
<p>Forgotten Realms · Westgate · Sea of Fallen Stars</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wg-footer__powered-by" aria-label="Platform credits">
|
||||||
|
<a
|
||||||
|
class="wg-footer__powered-pill"
|
||||||
|
href="https://nodebb.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Powered by NodeBB"
|
||||||
|
>
|
||||||
|
<span class="wg-footer__powered-label">Powered by</span>
|
||||||
|
<span class="wg-footer__powered-name">NodeBB</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="wg-footer__powered-pill"
|
||||||
|
href="https://tiptap.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Powered by Tiptap"
|
||||||
|
>
|
||||||
|
<span class="wg-footer__powered-label">Powered by</span>
|
||||||
|
<span class="wg-footer__powered-name">Tiptap</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
'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 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 footer = read('templates/footer.tpl');
|
||||||
|
const theme = read('theme.scss');
|
||||||
|
const partial = read('templates/partials/footer/westgate-global.tpl');
|
||||||
|
|
||||||
|
assertIncludes(
|
||||||
|
footer,
|
||||||
|
'<!-- IMPORT partials/footer/westgate-global.tpl -->',
|
||||||
|
'Footer layout should mount the Westgate global footer partial'
|
||||||
|
);
|
||||||
|
assertExcludes(
|
||||||
|
footer,
|
||||||
|
'<!-- IMPORT partials/sidebar-right.tpl -->',
|
||||||
|
"Footer should not reintroduce Harmony's right global sidebar"
|
||||||
|
);
|
||||||
|
assertIncludes(
|
||||||
|
theme,
|
||||||
|
'@import "./scss/westgate/footer";',
|
||||||
|
'theme.scss should import the focused footer partial'
|
||||||
|
);
|
||||||
|
assertIncludes(partial, '<footer class="wg-footer"', 'Footer partial should use semantic footer markup');
|
||||||
|
assertIncludes(partial, 'href="https://nodebb.org"', 'Footer should credit NodeBB');
|
||||||
|
assertIncludes(partial, 'href="https://tiptap.dev"', 'Footer should credit Tiptap');
|
||||||
|
assertIncludes(partial, 'aria-label="Powered by NodeBB"', 'NodeBB credit should have an accessible name');
|
||||||
|
assertIncludes(partial, 'aria-label="Powered by Tiptap"', 'Tiptap credit should have an accessible name');
|
||||||
|
|
||||||
|
[
|
||||||
|
'{relative_path}/category/1/news',
|
||||||
|
'{relative_path}/gallery',
|
||||||
|
'{relative_path}/category/84/developer-blog',
|
||||||
|
'{relative_path}/join-the-team',
|
||||||
|
'{relative_path}/categories',
|
||||||
|
'{relative_path}/wiki',
|
||||||
|
'{relative_path}/register',
|
||||||
|
].forEach((href) => {
|
||||||
|
assertIncludes(partial, `href="${href}"`, `Footer should include internal link ${href}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertExcludes(partial, 'onClick="{{', 'Footer should use real anchors, not Claude Design preview handlers');
|
||||||
|
assertExcludes(partial, 'https://westgate.pw', 'Footer should use relative internal links');
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
@import "./scss/westgate/tokens";
|
@import "./scss/westgate/tokens";
|
||||||
@import "./scss/westgate/wiki-prose";
|
@import "./scss/westgate/wiki-prose";
|
||||||
@import "./scss/westgate/topbar";
|
@import "./scss/westgate/topbar";
|
||||||
|
@import "./scss/westgate/footer";
|
||||||
@import "./scss/westgate/surfaces";
|
@import "./scss/westgate/surfaces";
|
||||||
@import "./scss/westgate/categories";
|
@import "./scss/westgate/categories";
|
||||||
@import "./scss/westgate/topics";
|
@import "./scss/westgate/topics";
|
||||||
|
|||||||
Reference in New Issue
Block a user