custom_pages reference
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
# Westgate live pages — theme integration spec
|
||||
|
||||
This covers the parts that live in the **theme repo** (`nodebb-theme-westgate`),
|
||||
not in ACP widgets:
|
||||
|
||||
- **News** (`/category/1/...`) and **Dev Blog** (`/category/84/...`) — rendered as
|
||||
banner-led article lists + article pages, pulled live from the forum.
|
||||
- **Gallery** — a player-upload screenshot category rendered as a masonry grid.
|
||||
- **Top bar** — global header partial that replaces the left sidebar.
|
||||
|
||||
The two static pages (Home, Join the Team) are NOT here — they ship as
|
||||
paste-ready HTML widgets in `westgate-pages/home.html` and
|
||||
`westgate-pages/join-the-team.html`.
|
||||
|
||||
---
|
||||
|
||||
## 0. One SCSS partial for all live pages
|
||||
|
||||
Create `scss/westgate/_pages.scss` and add it to `theme.scss`:
|
||||
|
||||
```scss
|
||||
// theme.scss (add after _widgets, before _responsive)
|
||||
@import "./scss/westgate/pages";
|
||||
```
|
||||
|
||||
All page styles use the existing `--wg-*` tokens — no new colours. Scope every
|
||||
rule under a body/template class so forum chrome is untouched. Suggested top-level
|
||||
hooks (added in the template overrides below):
|
||||
|
||||
- `body.wg-cat-news`, `body.wg-cat-blog` — the News / Dev Blog category index
|
||||
- `body.wg-article-news`, `body.wg-article-blog` — an individual post
|
||||
- `body.wg-gallery` — the Gallery category index
|
||||
|
||||
---
|
||||
|
||||
## 1. Category index re-skin (News / Dev Blog)
|
||||
|
||||
NodeBB renders a category at `categories/category.tpl` (we already override
|
||||
`category.tpl`). Rather than fork the whole template, branch on the category id
|
||||
and render a "cards" layout for cid 1 & 84, falling back to the normal list
|
||||
otherwise.
|
||||
|
||||
### 1a. Tag the body (lib/theme.js → filterMiddlewareRenderHeader or a new hook)
|
||||
|
||||
Add a body class so SCSS can target these categories. In `lib/theme.js`, extend
|
||||
the existing `filterMiddlewareRenderHeader` (or add `filter:category.build`):
|
||||
|
||||
```js
|
||||
// lib/theme.js
|
||||
const NEWS_CIDS = { 1: 'news', 84: 'blog' };
|
||||
|
||||
library.filterCategoryBuild = async function (hookData) {
|
||||
const cid = hookData.templateData && hookData.templateData.cid;
|
||||
if (NEWS_CIDS[cid]) {
|
||||
hookData.templateData.bodyClass =
|
||||
(hookData.templateData.bodyClass || '') + ' wg-cat-' + NEWS_CIDS[cid];
|
||||
hookData.templateData.wgArticleList = true; // template flag
|
||||
}
|
||||
return hookData;
|
||||
};
|
||||
```
|
||||
|
||||
Register it in `plugin.json` hooks:
|
||||
|
||||
```json
|
||||
{ "hook": "filter:category.build", "method": "filterCategoryBuild" }
|
||||
```
|
||||
|
||||
(If `filter:category.build` isn't available in your NodeBB version, set the flag
|
||||
in the existing `filter:middleware.renderHeader` by inspecting `req.url`.)
|
||||
|
||||
### 1b. Template branch (templates/category.tpl)
|
||||
|
||||
At the top of the topic loop, wrap the Westgate card markup behind the flag.
|
||||
Each topic already exposes `title`, `slug`, `timestamp`/`timestampISO`,
|
||||
`teaser`, `user`, and `thumb` (topic thumbnail) — that's everything the card needs.
|
||||
|
||||
```html
|
||||
{{{ if wgArticleList }}}
|
||||
<ul class="wg-article-list list-unstyled">
|
||||
{{{ each topics }}}
|
||||
<li class="wg-article-card">
|
||||
<a class="wg-article-card__media" href="{config.relative_path}/topic/{./slug}">
|
||||
{{{ if ./thumb }}}<img src="{./thumb}" alt="" loading="lazy">{{{ end }}}
|
||||
</a>
|
||||
<div class="wg-article-card__body">
|
||||
<div class="wg-article-card__meta">
|
||||
<span class="wg-tag">{./category.name}</span>
|
||||
<time datetime="{./timestampISO}">{function.humanReadableDate, ./timestamp}</time>
|
||||
</div>
|
||||
<h2 class="wg-article-card__title">
|
||||
<a href="{config.relative_path}/topic/{./slug}">{./title}</a>
|
||||
</h2>
|
||||
<p class="wg-article-card__teaser">{./teaser.content}</p>
|
||||
<div class="wg-article-card__byline">
|
||||
by {./user.username}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
</ul>
|
||||
{{{ else }}}
|
||||
<!-- existing default category list markup -->
|
||||
{{{ end }}}
|
||||
```
|
||||
|
||||
Mirror the visual target from the mockup (Westgate.dc.html → News view):
|
||||
featured first card larger, the rest in a grid. The SCSS below handles that with
|
||||
`:first-child`.
|
||||
|
||||
### 1c. SCSS (in _pages.scss)
|
||||
|
||||
```scss
|
||||
body.wg-cat-news,
|
||||
body.wg-cat-blog {
|
||||
.wg-article-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 22px;
|
||||
}
|
||||
.wg-article-card {
|
||||
background: var(--wg-velvet-panel);
|
||||
border: 1px solid var(--wg-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--wg-velvet-shadow);
|
||||
overflow: hidden;
|
||||
transition: border-color .25s;
|
||||
&:hover { border-color: rgba(194, 163, 90, .4); }
|
||||
// featured first post spans the row
|
||||
&:first-child {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 1fr;
|
||||
}
|
||||
}
|
||||
.wg-article-card__media img {
|
||||
width: 100%; height: 160px; object-fit: cover; display: block;
|
||||
border-bottom: 1px solid rgba(194, 163, 90, .12);
|
||||
}
|
||||
.wg-article-card:first-child .wg-article-card__media img {
|
||||
height: 100%; min-height: 300px; border-bottom: 0;
|
||||
}
|
||||
.wg-article-card__body { padding: 22px 24px; }
|
||||
.wg-article-card__meta {
|
||||
font-family: var(--wg-font-code);
|
||||
font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
|
||||
color: var(--wg-text-muted);
|
||||
display: flex; gap: 10px; align-items: center; margin-bottom: 10px;
|
||||
}
|
||||
.wg-tag {
|
||||
color: #150f08; background: var(--wg-gold);
|
||||
padding: 3px 9px; border-radius: 2px; font-weight: 600;
|
||||
}
|
||||
.wg-article-card__title {
|
||||
font-family: var(--wg-font-display); font-size: 19px; line-height: 1.35;
|
||||
color: var(--wg-text); margin: 0 0 10px;
|
||||
a { color: inherit; }
|
||||
}
|
||||
.wg-article-card:first-child .wg-article-card__title { font-size: 28px; }
|
||||
.wg-article-card__teaser {
|
||||
font-family: var(--wg-font-text); font-size: 14px; line-height: 1.7;
|
||||
color: var(--wg-text-muted); margin: 0;
|
||||
}
|
||||
.wg-article-card__byline {
|
||||
font-family: var(--wg-font-text); font-size: 12.5px;
|
||||
color: var(--wg-text-muted); margin-top: 14px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
body.wg-cat-news .wg-article-list,
|
||||
body.wg-cat-blog .wg-article-list { grid-template-columns: 1fr; }
|
||||
body.wg-cat-news .wg-article-card:first-child,
|
||||
body.wg-cat-blog .wg-article-card:first-child { grid-template-columns: 1fr; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Article page re-skin (an individual News / Dev Blog topic)
|
||||
|
||||
Override `topic.tpl` (or just its main-post partial) conditionally for cid 1 & 84.
|
||||
Tag the body the same way (`filter:topic.build` → `wg-article-news/blog`), then:
|
||||
|
||||
1. **Banner hero** — use `topic.thumb`, or the first image in the opening post,
|
||||
as a full-width bordered banner; overlay the category tag + date + Cinzel
|
||||
title + author byline. (Visual target: Westgate.dc.html → article view.)
|
||||
2. **Prose body** — add the wiki's prose class to the first post's content
|
||||
container so it inherits the magazine typography you already ship:
|
||||
`class="... wiki-article-prose"`. That single class gives gold H2 rules,
|
||||
blockquotes, drop-in image framing, etc. — no new CSS.
|
||||
3. **Replies** — leave the normal topic reply list below the article untouched.
|
||||
|
||||
```html
|
||||
{{{ if wgArticle }}}
|
||||
<header class="wg-article-hero" {{{ if topic.thumb }}}style="--wg-hero-img:url('{topic.thumb}')"{{{ end }}}>
|
||||
<div class="wg-article-hero__veil"></div>
|
||||
<div class="wg-article-hero__in">
|
||||
<div class="wg-article-hero__meta">
|
||||
<span class="wg-tag">{topic.category.name}</span>
|
||||
<time datetime="{topic.timestampISO}">{function.humanReadableDate, topic.timestamp}</time>
|
||||
</div>
|
||||
<h1 class="wg-article-hero__title">{topic.titleRaw}</h1>
|
||||
<div class="wg-article-hero__byline">by {topic.author.username}</div>
|
||||
</div>
|
||||
</header>
|
||||
{{{ end }}}
|
||||
```
|
||||
|
||||
```scss
|
||||
body.wg-article-news, body.wg-article-blog {
|
||||
.wg-article-hero {
|
||||
position: relative; min-height: 420px; border-radius: 10px;
|
||||
border: 1px solid rgba(194, 163, 90, .22);
|
||||
display: flex; align-items: flex-end; overflow: hidden;
|
||||
background: var(--wg-hero-img) center/cover no-repeat, var(--wg-panel-2);
|
||||
}
|
||||
.wg-article-hero__veil {
|
||||
position: absolute; inset: 0;
|
||||
background: linear-gradient(180deg, rgba(9,8,11,.2), rgba(9,8,11,.55) 55%, rgba(9,8,11,.94));
|
||||
}
|
||||
.wg-article-hero__in { position: relative; padding: 46px 50px; }
|
||||
.wg-article-hero__meta {
|
||||
font-family: var(--wg-font-code); font-size: 12px; color: var(--wg-text-soft);
|
||||
display: flex; gap: 12px; align-items: center; margin-bottom: 16px;
|
||||
}
|
||||
.wg-article-hero__title {
|
||||
font-family: var(--wg-font-display); font-weight: 700;
|
||||
font-size: clamp(34px, 4.4vw, 52px); line-height: 1.08;
|
||||
color: #f3ede4; margin: 0; max-width: 18em;
|
||||
text-shadow: 0 4px 30px rgba(0,0,0,.7);
|
||||
}
|
||||
// article body inherits .wiki-article-prose — no extra rules needed
|
||||
// constrain measure:
|
||||
[component="post/content"].wiki-article-prose { max-width: 760px; margin-inline: auto; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Gallery (player-upload category → masonry)
|
||||
|
||||
Make a new forum category "Gallery" (note its cid). Players post a topic with a
|
||||
screenshot; the topic thumbnail / first image is the tile. Re-skin its category
|
||||
view as masonry (CSS columns), same pattern as §1:
|
||||
|
||||
```scss
|
||||
body.wg-gallery {
|
||||
.wg-gallery-grid { column-count: 3; column-gap: 18px; }
|
||||
.wg-gallery-tile {
|
||||
break-inside: avoid; margin-bottom: 18px; position: relative;
|
||||
border: 1px solid var(--wg-border); border-radius: 5px; overflow: hidden;
|
||||
img { width: 100%; display: block; }
|
||||
}
|
||||
.wg-gallery-tile__cap {
|
||||
position: absolute; inset: auto 0 0 0; padding: 16px;
|
||||
background: linear-gradient(180deg, transparent, rgba(8,7,10,.9));
|
||||
.t { font-family: var(--wg-font-display); font-size: 15px; color: #ece2d2; }
|
||||
.u { font-family: var(--wg-font-text); font-size: 11px; color: var(--wg-text-muted); }
|
||||
}
|
||||
}
|
||||
@media (max-width: 900px) { body.wg-gallery .wg-gallery-grid { column-count: 2; } }
|
||||
@media (max-width: 560px) { body.wg-gallery .wg-gallery-grid { column-count: 1; } }
|
||||
```
|
||||
|
||||
Template: in `category.tpl`, add a `wgGallery` branch that loops topics into
|
||||
`.wg-gallery-tile` using each topic's image + title + poster.
|
||||
|
||||
---
|
||||
|
||||
## 4. Top bar (global header partial — replaces the sidebar)
|
||||
|
||||
Visual spec: `Westgate Top Bar.dc.html` (member / guest / Forums menu / user menu /
|
||||
mobile). Implementation outline:
|
||||
|
||||
1. **New partial** `templates/partials/header/topbar.tpl` containing brand +
|
||||
primary nav + the right cluster (search toggle, notifications, chat, user menu).
|
||||
Reuse Harmony's existing live components so they keep working — pull the
|
||||
notification/chat/user dropdown includes from `nodebb-theme-harmony`'s
|
||||
`partials/header/` rather than rebuilding them.
|
||||
2. **Mount it** in `templates/header.tpl`: replace the
|
||||
`<!-- IMPORT partials/sidebar-left.tpl -->` line with
|
||||
`<!-- IMPORT partials/header/topbar.tpl -->`, and drop the `layout-container`
|
||||
left-column so `#panel` is full-width.
|
||||
3. **Nav items** come from ACP → Navigation: News, Gallery, Wiki, Forums, Dev Blog.
|
||||
The "Forums" item is a dropdown whose panel lists categories + Recent/Unread/
|
||||
Tags/All-categories (sample markup in the mockup; wire to your real category
|
||||
list later).
|
||||
4. **Turn off sidebars**: set `openSidebars` off (already the default) and, since
|
||||
the left column is removed, the forum content goes full width. Keep the
|
||||
per-template `sidebar` widget areas empty.
|
||||
5. SCSS for the bar goes in `_pages.scss` (or a dedicated `_topbar.scss`), all
|
||||
`--wg-*` based — lift the literals from the mockup.
|
||||
|
||||
---
|
||||
|
||||
## Build / preview
|
||||
|
||||
After adding `_pages.scss` and the template branches:
|
||||
|
||||
```
|
||||
./nodebb build # or: ./nodebb dev for live rebuilds
|
||||
```
|
||||
|
||||
Visual targets to match pixel-for-pixel live in this project:
|
||||
- `Westgate.dc.html` — News index, article view, Gallery, Home, Join the Team
|
||||
- `Westgate Top Bar.dc.html` — the global bar and all its states
|
||||
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Westgate page preview (theme harness)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=Jost:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* --- Simulated theme globals (mirror of scss/westgate/_tokens.scss :root) --- */
|
||||
:root{
|
||||
--wg-font-display:"Cinzel",Georgia,serif;
|
||||
--wg-font-text:"Jost",system-ui,sans-serif;
|
||||
--wg-font-ui:"Jost",system-ui,sans-serif;
|
||||
--wg-font-code:"IBM Plex Mono",ui-monospace,monospace;
|
||||
--wg-bg:#0f0d12;--wg-bg-deep:#09080b;--wg-panel:#18141d;--wg-panel-2:#110f15;
|
||||
--wg-plum:#2a1222;--wg-plum-soft:#3a1830;--wg-gold:#c2a35a;--wg-gold-soft:#a8893f;
|
||||
--wg-red:#8e3438;--wg-status-online:#7fb86a;
|
||||
--wg-text:#e6e0d6;--wg-text-soft:#b9b2a6;--wg-text-muted:#9a9086;
|
||||
--wg-border:rgba(194,163,90,.14);--wg-border-soft:rgba(194,163,90,.08);
|
||||
--wg-velvet-panel:linear-gradient(100deg,rgba(42,18,34,.46) 0%,rgba(25,22,31,.95) 23%,rgba(18,16,23,.99) 62%,rgba(13,12,17,.98) 100%);
|
||||
--wg-velvet-panel-hover:linear-gradient(100deg,rgba(58,24,48,.56) 0%,rgba(31,25,39,.98) 23%,rgba(21,18,27,1) 62%,rgba(15,14,20,.99) 100%);
|
||||
--wg-velvet-shadow:inset 0 1px 0 rgba(255,255,255,.025),inset 0 0 34px rgba(96,32,68,.11),0 10px 26px rgba(0,0,0,.24);
|
||||
}
|
||||
html,body{margin:0;background:var(--wg-bg);color:var(--wg-text-soft);font-family:var(--wg-font-text);}
|
||||
/* simulate Harmony brand hero banner + content container */
|
||||
.harness-brand{max-width:1320px;margin:0 auto;padding:14px 20px 0;}
|
||||
.harness-brand .bar{border:1px solid rgba(194,163,90,.22);border-radius:10px;background:linear-gradient(90deg,rgba(5,4,7,.82),rgba(10,8,12,0) 52%),#16121b;padding:18px 26px;display:flex;align-items:center;gap:14px;box-shadow:0 18px 42px rgba(0,0,0,.3);}
|
||||
.harness-brand .logo{width:22px;height:22px;transform:rotate(45deg);border:1.5px solid var(--wg-gold);display:flex;align-items:center;justify-content:center;}
|
||||
.harness-brand .logo i{width:7px;height:7px;background:var(--wg-gold);}
|
||||
.harness-brand .ttl{font-family:var(--wg-font-display);font-weight:600;font-size:18px;letter-spacing:.12em;color:var(--wg-text);text-transform:uppercase;}
|
||||
.harness-brand .sub{font-family:var(--wg-font-display);font-size:11px;letter-spacing:.22em;color:var(--wg-gold);text-transform:uppercase;}
|
||||
.harness-nav{max-width:1320px;margin:10px auto 0;padding:0 20px;display:flex;gap:26px;}
|
||||
.harness-nav a{font-family:var(--wg-font-ui);font-size:12.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--wg-text-soft);text-decoration:none;padding:8px 0;border-bottom:2px solid transparent;}
|
||||
.harness-nav a.active{color:var(--wg-text);border-bottom-color:var(--wg-gold);}
|
||||
.harness-content{max-width:1320px;margin:18px auto 0;padding:0 20px 60px;}
|
||||
.harness-label{max-width:1320px;margin:0 auto;padding:10px 20px 0;font-family:var(--wg-font-code);font-size:11px;color:#6b6152;letter-spacing:.1em;text-transform:uppercase;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="harness-label">▾ Simulated theme chrome (brand hero + nav + #content) — the widget HTML is everything below the nav</div>
|
||||
<div class="harness-brand"><div class="bar"><div class="logo"><i></i></div><div><div class="ttl">Shadows Over Westgate</div><div class="sub">Persistent World</div></div></div></div>
|
||||
<nav class="harness-nav"><a class="active" href="#">Home</a><a href="#">News</a><a href="#">Gallery</a><a href="#">Dev Blog</a><a href="#">Forums</a><a href="#">Wiki</a></nav>
|
||||
<div class="harness-content" id="slot">Loading widget…</div>
|
||||
<script>
|
||||
fetch('home.html').then(r=>r.text()).then(html=>{
|
||||
// strip the leading HTML comment block, keep <style> + markup
|
||||
document.getElementById('slot').innerHTML = html.replace(/^<!--[\s\S]*?-->/,'');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Westgate page preview (theme harness)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=Jost:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* --- Simulated theme globals (mirror of scss/westgate/_tokens.scss :root) --- */
|
||||
:root{
|
||||
--wg-font-display:"Cinzel",Georgia,serif;
|
||||
--wg-font-text:"Jost",system-ui,sans-serif;
|
||||
--wg-font-ui:"Jost",system-ui,sans-serif;
|
||||
--wg-font-code:"IBM Plex Mono",ui-monospace,monospace;
|
||||
--wg-bg:#0f0d12;--wg-bg-deep:#09080b;--wg-panel:#18141d;--wg-panel-2:#110f15;
|
||||
--wg-plum:#2a1222;--wg-plum-soft:#3a1830;--wg-gold:#c2a35a;--wg-gold-soft:#a8893f;
|
||||
--wg-red:#8e3438;--wg-status-online:#7fb86a;
|
||||
--wg-text:#e6e0d6;--wg-text-soft:#b9b2a6;--wg-text-muted:#9a9086;
|
||||
--wg-border:rgba(194,163,90,.14);--wg-border-soft:rgba(194,163,90,.08);
|
||||
--wg-velvet-panel:linear-gradient(100deg,rgba(42,18,34,.46) 0%,rgba(25,22,31,.95) 23%,rgba(18,16,23,.99) 62%,rgba(13,12,17,.98) 100%);
|
||||
--wg-velvet-panel-hover:linear-gradient(100deg,rgba(58,24,48,.56) 0%,rgba(31,25,39,.98) 23%,rgba(21,18,27,1) 62%,rgba(15,14,20,.99) 100%);
|
||||
--wg-velvet-shadow:inset 0 1px 0 rgba(255,255,255,.025),inset 0 0 34px rgba(96,32,68,.11),0 10px 26px rgba(0,0,0,.24);
|
||||
}
|
||||
html,body{margin:0;background:var(--wg-bg);color:var(--wg-text-soft);font-family:var(--wg-font-text);}
|
||||
/* simulate Harmony brand hero banner + content container */
|
||||
.harness-brand{max-width:1320px;margin:0 auto;padding:14px 20px 0;}
|
||||
.harness-brand .bar{border:1px solid rgba(194,163,90,.22);border-radius:10px;background:linear-gradient(90deg,rgba(5,4,7,.82),rgba(10,8,12,0) 52%),#16121b;padding:18px 26px;display:flex;align-items:center;gap:14px;box-shadow:0 18px 42px rgba(0,0,0,.3);}
|
||||
.harness-brand .logo{width:22px;height:22px;transform:rotate(45deg);border:1.5px solid var(--wg-gold);display:flex;align-items:center;justify-content:center;}
|
||||
.harness-brand .logo i{width:7px;height:7px;background:var(--wg-gold);}
|
||||
.harness-brand .ttl{font-family:var(--wg-font-display);font-weight:600;font-size:18px;letter-spacing:.12em;color:var(--wg-text);text-transform:uppercase;}
|
||||
.harness-brand .sub{font-family:var(--wg-font-display);font-size:11px;letter-spacing:.22em;color:var(--wg-gold);text-transform:uppercase;}
|
||||
.harness-nav{max-width:1320px;margin:10px auto 0;padding:0 20px;display:flex;gap:26px;}
|
||||
.harness-nav a{font-family:var(--wg-font-ui);font-size:12.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--wg-text-soft);text-decoration:none;padding:8px 0;border-bottom:2px solid transparent;}
|
||||
.harness-nav a.active{color:var(--wg-text);border-bottom-color:var(--wg-gold);}
|
||||
.harness-content{max-width:1320px;margin:18px auto 0;padding:0 20px 60px;}
|
||||
.harness-label{max-width:1320px;margin:0 auto;padding:10px 20px 0;font-family:var(--wg-font-code);font-size:11px;color:#6b6152;letter-spacing:.1em;text-transform:uppercase;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="harness-label">▾ Simulated theme chrome (brand hero + nav + #content) — the widget HTML is everything below the nav</div>
|
||||
<div class="harness-brand"><div class="bar"><div class="logo"><i></i></div><div><div class="ttl">Shadows Over Westgate</div><div class="sub">Persistent World</div></div></div></div>
|
||||
<nav class="harness-nav"><a href="#">News</a><a href="#">Gallery</a><a href="#">Wiki</a><a href="#">Forums</a><a href="#">Dev Blog</a></nav>
|
||||
<div class="harness-content" id="slot">Loading widget…</div>
|
||||
<script>
|
||||
fetch('join-the-team.html').then(r=>r.text()).then(html=>{
|
||||
// strip the leading HTML comment block, keep <style> + markup
|
||||
document.getElementById('slot').innerHTML = html.replace(/^<!--[\s\S]*?-->/,'');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Westgate top bar preview (theme harness)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=Jost:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* --- Simulated theme globals (mirror of scss/westgate/_tokens.scss :root) --- */
|
||||
:root{
|
||||
--wg-font-display:"Cinzel",Georgia,serif;
|
||||
--wg-font-text:"Jost",system-ui,sans-serif;
|
||||
--wg-font-ui:"Jost",system-ui,sans-serif;
|
||||
--wg-font-code:"IBM Plex Mono",ui-monospace,monospace;
|
||||
--wg-bg:#0f0d12;--wg-bg-deep:#09080b;--wg-panel:#18141d;--wg-panel-2:#110f15;
|
||||
--wg-plum:#2a1222;--wg-plum-soft:#3a1830;--wg-gold:#c2a35a;--wg-gold-soft:#a8893f;
|
||||
--wg-red:#8e3438;--wg-status-online:#7fb86a;
|
||||
--wg-text:#e6e0d6;--wg-text-soft:#b9b2a6;--wg-text-muted:#9a9086;
|
||||
--wg-border:rgba(194,163,90,.14);--wg-border-soft:rgba(194,163,90,.08);
|
||||
--wg-velvet-panel:linear-gradient(100deg,rgba(42,18,34,.46) 0%,rgba(25,22,31,.95) 23%,rgba(18,16,23,.99) 62%,rgba(13,12,17,.98) 100%);
|
||||
--wg-velvet-shadow:inset 0 1px 0 rgba(255,255,255,.025),inset 0 0 34px rgba(96,32,68,.11),0 10px 26px rgba(0,0,0,.24);
|
||||
}
|
||||
html,body{margin:0;background:var(--wg-bg);color:var(--wg-text-soft);font-family:var(--wg-font-text);}
|
||||
/* dummy page body to demo sticky behaviour + full-width #panel */
|
||||
.harness-page{max-width:1180px;margin:34px auto;padding:0 24px;}
|
||||
.harness-page h1{font-family:var(--wg-font-display);font-weight:700;font-size:40px;color:#f3ede4;letter-spacing:.03em;margin:0 0 8px;}
|
||||
.harness-page .eb{font-family:var(--wg-font-display);font-size:12px;letter-spacing:.28em;text-transform:uppercase;color:var(--wg-gold);}
|
||||
.harness-page p{font-size:16px;line-height:1.9;color:var(--wg-text-muted);max-width:680px;}
|
||||
.harness-block{height:240px;margin:20px 0;border:1px dashed rgba(194,163,90,.2);border-radius:8px;
|
||||
display:flex;align-items:center;justify-content:center;font-family:var(--wg-font-code);font-size:12px;
|
||||
letter-spacing:.1em;text-transform:uppercase;color:#6b6152;
|
||||
background:repeating-linear-gradient(135deg,#1b1320,#1b1320 11px,#161019 11px,#161019 22px);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot">Loading top bar…</div>
|
||||
<main class="harness-page">
|
||||
<div class="eb">Simulated #panel content</div>
|
||||
<h1>The Gates Open</h1>
|
||||
<p>Scroll to confirm the bar stays pinned. This dummy body stands in for the full-width forum content that runs under the global top bar once the left sidebar is removed.</p>
|
||||
<div class="harness-block">Forum content / widget area</div>
|
||||
<div class="harness-block">Forum content / widget area</div>
|
||||
</main>
|
||||
<script>
|
||||
fetch('top-bar.html').then(r=>r.text()).then(html=>{
|
||||
var slot = document.getElementById('slot');
|
||||
// strip leading HTML comment block, keep <style> + markup + <script>
|
||||
slot.innerHTML = html.replace(/^[\s\S]*?-->/,'');
|
||||
// re-execute injected <script> tags (innerHTML doesn't run them)
|
||||
slot.querySelectorAll('script').forEach(function(old){
|
||||
var s=document.createElement('script');
|
||||
if(old.src) s.src=old.src; else s.textContent=old.textContent;
|
||||
old.parentNode.replaceChild(s,old);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,238 @@
|
||||
<!--
|
||||
============================================================================
|
||||
SHADOWS OVER WESTGATE — HOME (Custom Page content)
|
||||
----------------------------------------------------------------------------
|
||||
HOW TO USE (NodeBB Custom Pages + Widgets, theme: nodebb-theme-westgate)
|
||||
|
||||
1. ACP → Custom Pages: add a page with route "home" (or "/").
|
||||
2. ACP → Extend → Widgets: pick the "Home" template, choose the main
|
||||
content area, drag in an "HTML" widget, and paste THIS ENTIRE FILE in.
|
||||
3. To run without the left sidebar on this page, leave the sidebar widget
|
||||
area empty (Harmony hides an empty sidebar) and keep your nav links in
|
||||
ACP → Navigation (Home / News / Gallery / Dev Blog / Forums / Wiki).
|
||||
|
||||
NOTES
|
||||
- All colours/fonts use the theme's global --wg-* CSS variables, so this
|
||||
block matches the forum + wiki and tracks any future palette change.
|
||||
- Styles are scoped under .wg-page to avoid leaking into forum chrome.
|
||||
- Image placeholders show a striped fill. To go live, copy each
|
||||
data-src onto src (ACP → Uploads gives you the path). data-wg-art marks them.
|
||||
- Server-status numbers are static here; wire them to your status API/widget
|
||||
when ready (search "STATUS" below).
|
||||
============================================================================
|
||||
-->
|
||||
<style>
|
||||
.wg-page{
|
||||
--page-bright:#f3ede4;
|
||||
/* Full-width: the forum panel already provides left/right gutters, so content
|
||||
fills the panel (matching the footer rule). Override per-block where a
|
||||
narrower reading measure is wanted (e.g. .wg-pitch). */
|
||||
--page-maxw:none;
|
||||
font-family:var(--wg-font-text, "Jost", sans-serif);
|
||||
color:var(--wg-text-soft, #b9b2a6);
|
||||
}
|
||||
.wg-page *{box-sizing:border-box;}
|
||||
.wg-page .wg-wrap{max-width:var(--page-maxw);margin:0 auto;}
|
||||
.wg-page a{text-decoration:none;}
|
||||
.wg-page img[data-wg-art]{background:repeating-linear-gradient(135deg,#1b1320,#1b1320 11px,#161019 11px,#161019 22px);}
|
||||
|
||||
|
||||
/* ---- Buttons ---- */
|
||||
.wg-page .wg-btn{
|
||||
display:inline-block;font-family:var(--wg-font-ui,"Jost",sans-serif);
|
||||
font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-weight:600;
|
||||
color:#150f08;background:linear-gradient(180deg,#d8bd76,#a8893f);
|
||||
padding:14px 30px;border-radius:6px;border:1px solid rgba(216,194,138,.6);
|
||||
box-shadow:0 4px 22px rgba(168,133,74,.28);transition:filter .2s,transform .2s;
|
||||
}
|
||||
.wg-page .wg-btn:hover{filter:brightness(1.07);transform:translateY(-1px);}
|
||||
.wg-page .wg-btn-ghost{
|
||||
display:inline-block;font-family:var(--wg-font-ui,"Jost",sans-serif);
|
||||
font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-weight:600;
|
||||
color:var(--wg-text,#e6e0d6);background:rgba(194,163,90,.06);
|
||||
padding:14px 30px;border-radius:6px;border:1px solid rgba(194,163,90,.4);
|
||||
transition:background .2s,border-color .2s;
|
||||
}
|
||||
.wg-page .wg-btn-ghost:hover{background:rgba(194,163,90,.12);border-color:rgba(194,163,90,.6);}
|
||||
|
||||
/* ---- Shared bits ---- */
|
||||
.wg-page .wg-eyebrow{font-family:var(--wg-font-display,"Cinzel",serif);font-size:12px;
|
||||
letter-spacing:.28em;text-transform:uppercase;color:var(--wg-gold,#c2a35a);}
|
||||
.wg-page .wg-h2{font-family:var(--wg-font-display,"Cinzel",serif);font-weight:600;
|
||||
font-size:34px;color:var(--page-bright);margin:0;letter-spacing:.02em;
|
||||
text-shadow:0 1px 0 rgba(0,0,0,.9),0 0 10px rgba(104,32,76,.16);}
|
||||
.wg-page .wg-card{background:var(--wg-velvet-panel);border:1px solid var(--wg-border,rgba(194,163,90,.14));
|
||||
border-radius:8px;box-shadow:var(--wg-velvet-shadow);}
|
||||
.wg-page .wg-rule{display:flex;align-items:center;justify-content:center;gap:18px;}
|
||||
.wg-page .wg-rule i{height:1px;width:80px;display:block;}
|
||||
.wg-page .wg-rule span{width:7px;height:7px;background:var(--wg-gold,#c2a35a);transform:rotate(45deg);}
|
||||
|
||||
/* ---- Hero ---- */
|
||||
.wg-page .wg-hero{position:relative;overflow:hidden;border:1px solid rgba(194,163,90,.22);
|
||||
border-radius:10px;min-height:420px;display:flex;flex-direction:column;align-items:center;
|
||||
justify-content:center;text-align:center;padding:64px 24px;margin-top:.25rem;
|
||||
box-shadow:inset 0 1px 0 rgba(255,242,204,.06),inset 0 0 0 1px rgba(33,21,28,.5),0 18px 42px rgba(0,0,0,.3);}
|
||||
.wg-page .wg-hero__bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;}
|
||||
.wg-page .wg-hero__veil{position:absolute;inset:0;z-index:1;pointer-events:none;
|
||||
background:linear-gradient(180deg,rgba(9,8,11,.55) 0%,rgba(9,8,11,.25) 38%,rgba(9,8,11,.82) 86%,var(--wg-bg,#0f0d12) 100%);}
|
||||
.wg-page .wg-hero__in{position:relative;z-index:2;}
|
||||
.wg-page .wg-hero h1{font-family:var(--wg-font-display,"Cinzel",serif);font-weight:700;
|
||||
font-size:clamp(40px,6vw,68px);line-height:1.04;letter-spacing:.03em;color:var(--page-bright);
|
||||
margin:18px 0 0;text-shadow:0 4px 40px rgba(0,0,0,.7);}
|
||||
.wg-page .wg-hero p{font-family:var(--wg-font-text,"Jost",sans-serif);font-weight:300;
|
||||
font-size:19px;color:#cfc5b7;max-width:620px;margin:22px auto 0;line-height:1.6;
|
||||
text-shadow:0 2px 16px rgba(0,0,0,.8);}
|
||||
.wg-page .wg-hero__cta{display:flex;gap:16px;justify-content:center;flex-wrap:wrap;margin-top:34px;}
|
||||
|
||||
/* ---- Status bar ---- */
|
||||
.wg-page .wg-status{margin-top:-40px;position:relative;z-index:5;padding:24px 32px;
|
||||
display:flex;align-items:center;gap:36px;flex-wrap:wrap;}
|
||||
.wg-page .wg-status__dot{width:12px;height:12px;border-radius:50%;background:var(--wg-status-online,#7fb86a);
|
||||
box-shadow:0 0 0 4px rgba(127,184,106,.16);}
|
||||
.wg-page .wg-status__sep{width:1px;height:40px;background:rgba(194,163,90,.18);}
|
||||
.wg-page .wg-status .lbl{font-family:var(--wg-font-display,"Cinzel",serif);font-size:18px;
|
||||
letter-spacing:.05em;color:var(--wg-text,#e6e0d6);}
|
||||
.wg-page .wg-status .sub{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:11px;
|
||||
letter-spacing:.06em;color:var(--wg-text-muted,#9a9086);margin-top:3px;}
|
||||
.wg-page .wg-status .big{font-family:var(--wg-font-display,"Cinzel",serif);font-size:25px;color:var(--wg-text,#e6e0d6);}
|
||||
.wg-page .wg-status .big small{font-size:14px;color:var(--wg-text-muted,#9a9086);}
|
||||
.wg-page .wg-status .mono{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:14px;color:var(--wg-text-soft,#b9b2a6);margin-top:4px;}
|
||||
|
||||
/* ---- Pitch ---- */
|
||||
.wg-page .wg-pitch{max-width:760px;margin:0 auto;padding:92px 24px 24px;text-align:center;}
|
||||
.wg-page .wg-pitch .lead{font-family:var(--wg-font-text,"Jost",sans-serif);font-weight:300;
|
||||
font-size:23px;line-height:1.7;color:#cfc5b7;margin:0 0 32px;}
|
||||
.wg-page .wg-pitch p{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:17px;
|
||||
line-height:1.95;color:var(--wg-text-muted,#9a9086);margin:0 0 24px;}
|
||||
.wg-page .wg-pitch blockquote{font-family:var(--wg-font-display,"Cinzel",serif);font-style:italic;
|
||||
font-size:22px;line-height:1.5;color:#d8c28a;margin:44px auto;max-width:560px;}
|
||||
.wg-page .wg-pitch .sign{font-family:var(--wg-font-display,"Cinzel",serif);font-size:19px;
|
||||
letter-spacing:.04em;color:var(--wg-text,#e6e0d6);margin:32px 0 0;}
|
||||
|
||||
/* ---- Get started ---- */
|
||||
.wg-page .wg-steps{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;}
|
||||
.wg-page .wg-step{display:block;padding:28px 24px;transition:border-color .25s,transform .25s;}
|
||||
.wg-page .wg-step:hover{border-color:rgba(194,163,90,.4)!important;transform:translateY(-4px);}
|
||||
.wg-page .wg-step .ico{width:42px;height:42px;border:1px solid rgba(194,163,90,.4);border-radius:50%;
|
||||
display:flex;align-items:center;justify-content:center;margin-bottom:20px;}
|
||||
.wg-page .wg-step .ico i{width:12px;height:12px;border:1.5px solid var(--wg-gold,#c2a35a);transform:rotate(45deg);display:block;}
|
||||
.wg-page .wg-step h3{font-family:var(--wg-font-display,"Cinzel",serif);font-size:19px;color:var(--wg-text,#e6e0d6);margin:0 0 10px;}
|
||||
.wg-page .wg-step p{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:14.5px;line-height:1.7;color:var(--wg-text-muted,#9a9086);margin:0;}
|
||||
|
||||
/* ---- News strip ---- */
|
||||
.wg-page .wg-news{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;}
|
||||
.wg-page .wg-news article{overflow:hidden;transition:border-color .25s;}
|
||||
.wg-page .wg-news article:hover{border-color:rgba(194,163,90,.4)!important;}
|
||||
.wg-page .wg-news .thumb{height:160px;width:100%;object-fit:cover;display:block;border-bottom:1px solid rgba(194,163,90,.12);background:#161019;}
|
||||
.wg-page .wg-news .body{padding:22px 24px;}
|
||||
.wg-page .wg-news .meta{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:11px;
|
||||
letter-spacing:.08em;text-transform:uppercase;color:var(--wg-text-muted,#9a9086);margin-bottom:10px;}
|
||||
.wg-page .wg-news h3{font-family:var(--wg-font-display,"Cinzel",serif);font-size:19px;line-height:1.35;color:var(--wg-text,#e6e0d6);margin:0 0 10px;}
|
||||
.wg-page .wg-news p{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:14px;line-height:1.7;color:var(--wg-text-muted,#9a9086);margin:0;}
|
||||
|
||||
.wg-page .wg-section{padding:72px 24px 0;}
|
||||
.wg-page .wg-section.last{padding-bottom:96px;}
|
||||
.wg-page .wg-section__head{text-align:center;margin-bottom:44px;}
|
||||
.wg-page .wg-section__head .wg-eyebrow{display:block;margin-bottom:12px;}
|
||||
.wg-page .wg-flex-head{display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:32px;gap:16px;flex-wrap:wrap;}
|
||||
.wg-page .wg-more{font-family:var(--wg-font-ui,"Jost",sans-serif);font-size:12px;letter-spacing:.12em;
|
||||
text-transform:uppercase;color:var(--wg-gold,#c2a35a);border-bottom:1px solid rgba(194,163,90,.4);padding-bottom:3px;}
|
||||
|
||||
@media (max-width:900px){
|
||||
.wg-page .wg-steps{grid-template-columns:repeat(2,1fr);}
|
||||
.wg-page .wg-news{grid-template-columns:1fr;}
|
||||
.wg-page .wg-status{gap:22px;}
|
||||
.wg-page .wg-status__sep{display:none;}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="wg-page wg-home">
|
||||
|
||||
<!-- ============ HERO ============ -->
|
||||
<div class="wg-wrap">
|
||||
<section class="wg-hero">
|
||||
<!-- ART: replace src with your banner key art (wide, ~1600×640). -->
|
||||
<img class="wg-hero__bg" data-wg-art src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/uploads/westgate/home-hero.jpg" alt="" onerror="this.style.display='none'">
|
||||
<div class="wg-hero__veil"></div>
|
||||
<div class="wg-hero__in">
|
||||
<span class="wg-eyebrow">Neverwinter Nights · Persistent World</span>
|
||||
<h1>Welcome to Westgate</h1>
|
||||
<p>An ancient port city rises out of the mists, at the edge of the Sea of Fallen Stars. Mind the shadows.</p>
|
||||
<div class="wg-hero__cta">
|
||||
<a class="wg-btn" href="/wiki">How to Join</a>
|
||||
<a class="wg-btn-ghost" href="/news">Latest News</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ============ SERVER STATUS ============ -->
|
||||
<div class="wg-wrap">
|
||||
<div class="wg-status wg-card">
|
||||
<div style="display:flex;align-items:center;gap:14px;">
|
||||
<span class="wg-status__dot"></span>
|
||||
<div><div class="lbl">Server Online</div><div class="sub">ALPHA · MODULE 0.3</div></div>
|
||||
</div>
|
||||
<div class="wg-status__sep"></div>
|
||||
<!-- STATUS: wire these numbers to your status feed when ready -->
|
||||
<div><div class="big">14 <small>souls online</small></div><div class="sub">PEAKED 32 TODAY</div></div>
|
||||
<div class="wg-status__sep"></div>
|
||||
<div style="flex:1;min-width:160px;">
|
||||
<div style="font-family:var(--wg-font-code,monospace);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--wg-gold,#c2a35a);">Direct Connect</div>
|
||||
<div class="mono">play.westgate.pw</div>
|
||||
</div>
|
||||
<a class="wg-btn" href="/wiki">Connect Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ PITCH ============ -->
|
||||
<div class="wg-pitch">
|
||||
<div class="wg-rule" style="margin-bottom:36px;"><i style="background:linear-gradient(90deg,transparent,var(--wg-gold,#c2a35a));"></i><span></span><i style="background:linear-gradient(90deg,var(--wg-gold,#c2a35a),transparent);"></i></div>
|
||||
<p class="lead">Long ago, merchants from distant lands filled its harbors, and gold flowed as freely as wine — but time, as it does, has a way of curdling mortal splendor.</p>
|
||||
<p>Today its bones are old stone and salt-worn docks, and bargains are struck beneath every lantern's glow. Here, hoarded secrets, owed favour, and unspoken truths are the true measure of power — although coin certainly helps.</p>
|
||||
<p>Westgate is a city ever-balanced on a blade's edge, built upon decaying decadence. The masked and nameless guilds clot the veins of the city with their greed and cruelty. Noble houses cling to fading legacies as their bloodlines tangle with curses, ambition, and desperation. Old cults and buried powers hold the Undergate by its throat. Beneath it all, antediluvian things begin to stir — patient, watchful, and hungry.</p>
|
||||
<blockquote>“To walk Westgate's streets is to wager your soul in a game of chance.”</blockquote>
|
||||
<p>Every alley offers opportunity, yet each carries a price. While some come seeking fortune or power, others arrive hoping to disappear — and indeed they do, for in these boulevards you are lucky to merely survive. In Westgate, few leave unchanged… and fewer still leave at all.</p>
|
||||
<p class="sign">Welcome to Westgate. Mind the shadows.</p>
|
||||
</div>
|
||||
|
||||
<!-- ============ GET STARTED ============ -->
|
||||
<div class="wg-wrap wg-section">
|
||||
<div class="wg-section__head">
|
||||
<span class="wg-eyebrow">Your First Steps</span>
|
||||
<h2 class="wg-h2">Enter the City</h2>
|
||||
</div>
|
||||
<div class="wg-steps">
|
||||
<a class="wg-step wg-card" href="/wiki"><div class="ico"><i></i></div><h3>How to Join</h3><p>Install Neverwinter Nights: EE, point it at our server, and step through the gate.</p></a>
|
||||
<a class="wg-step wg-card" href="/wiki"><div class="ico"><i style="border-radius:0;"></i></div><h3>New Player Wiki</h3><p>Starter guides, character creation, and everything you need for your first night.</p></a>
|
||||
<a class="wg-step wg-card" href="/wiki"><div class="ico"><i style="border-radius:50%;transform:none;"></i></div><h3>The Setting Bible</h3><p>Lore of Westgate — its districts, guilds, noble houses, and buried histories.</p></a>
|
||||
<a class="wg-step wg-card" href="/categories"><div class="ico"><i style="transform:none;border-radius:2px;"></i></div><h3>Join the Community</h3><p>Introduce yourself in the forums and find your place among Westgate's denizens.</p></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ LATEST NEWS ============ -->
|
||||
<div class="wg-wrap wg-section last">
|
||||
<div class="wg-flex-head">
|
||||
<div>
|
||||
<span class="wg-eyebrow" style="display:block;margin-bottom:10px;">From the Watch</span>
|
||||
<h2 class="wg-h2">Latest News</h2>
|
||||
</div>
|
||||
<a class="wg-more" href="/news">All Dispatches →</a>
|
||||
</div>
|
||||
<div class="wg-news">
|
||||
<article class="wg-card">
|
||||
<a href="/category/1/news"><img class="thumb" data-wg-art src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/uploads/westgate/news-1.jpg" alt="" onerror="this.style.visibility='hidden'"></a>
|
||||
<div class="body"><div class="meta">24 Jun 2026 · Announcement</div><h3>The Gates Open — Alpha Has Begun</h3><p>Westgate opens its harbour to its first wave of adventurers. Everything you need to know.</p></div>
|
||||
</article>
|
||||
<article class="wg-card">
|
||||
<a href="/category/1/news"><img class="thumb" data-wg-art src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/uploads/westgate/news-2.jpg" alt="" onerror="this.style.visibility='hidden'"></a>
|
||||
<div class="body"><div class="meta">21 Jun 2026 · Patch Note</div><h3>Patch 0.3 — The Undergate</h3><p>The tunnels beneath the city are now open. New areas, factions, and dangers await below.</p></div>
|
||||
</article>
|
||||
<article class="wg-card">
|
||||
<a href="/category/1/news"><img class="thumb" data-wg-art src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/uploads/westgate/news-3.jpg" alt="" onerror="this.style.visibility='hidden'"></a>
|
||||
<div class="body"><div class="meta">18 Jun 2026 · Community</div><h3>The Community Charter</h3><p>Our revised rules and the principles that keep Westgate's streets worth walking.</p></div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,172 @@
|
||||
<!--
|
||||
============================================================================
|
||||
SHADOWS OVER WESTGATE — JOIN THE TEAM / Recruitment (Custom Page content)
|
||||
----------------------------------------------------------------------------
|
||||
HOW TO USE (NodeBB Custom Pages + Widgets, theme: nodebb-theme-westgate)
|
||||
|
||||
1. ACP -> Custom Pages: add a page with route "join" (or "recruitment").
|
||||
2. ACP -> Extend -> Widgets: pick that page's template, choose the main
|
||||
content area, drag in an "HTML" widget, and paste THIS ENTIRE FILE in.
|
||||
3. Sidebar-off + nav links: same as the Home page (see home.html notes).
|
||||
|
||||
NOTES
|
||||
- Shares the SAME scoped vocabulary as home.html (.wg-page, .wg-btn,
|
||||
.wg-card, .wg-eyebrow, .wg-rule, .wg-h2). If both pages ever live in one
|
||||
bundle the styles are identical and de-dupe cleanly.
|
||||
- All colours/fonts use the theme's global --wg-* variables.
|
||||
- DISCORD: replace every href="#discord" with your real invite link.
|
||||
- Image placeholders show a striped fill. To go live, copy each data-src
|
||||
onto src (data-wg-art marks them).
|
||||
============================================================================
|
||||
-->
|
||||
<style>
|
||||
.wg-page{
|
||||
--page-bright:#f3ede4;
|
||||
/* Full-width: the forum panel already provides left/right gutters, so content
|
||||
fills the panel (matching the footer rule). Override per-block where a
|
||||
narrower reading measure is wanted (e.g. .wg-cta). */
|
||||
--page-maxw:none;
|
||||
font-family:var(--wg-font-text,"Jost",sans-serif);
|
||||
color:var(--wg-text-soft,#b9b2a6);
|
||||
}
|
||||
.wg-page *{box-sizing:border-box;}
|
||||
.wg-page .wg-wrap{max-width:var(--page-maxw);margin:0 auto;}
|
||||
.wg-page a{text-decoration:none;}
|
||||
.wg-page img[data-wg-art]{background:repeating-linear-gradient(135deg,#1b1320,#1b1320 11px,#161019 11px,#161019 22px);}
|
||||
|
||||
|
||||
.wg-page .wg-btn{display:inline-block;font-family:var(--wg-font-ui,"Jost",sans-serif);
|
||||
font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-weight:600;color:#150f08;
|
||||
background:linear-gradient(180deg,#d8bd76,#a8893f);padding:14px 30px;border-radius:6px;
|
||||
border:1px solid rgba(216,194,138,.6);box-shadow:0 4px 22px rgba(168,133,74,.28);
|
||||
transition:filter .2s,transform .2s;}
|
||||
.wg-page .wg-btn:hover{filter:brightness(1.07);transform:translateY(-1px);}
|
||||
|
||||
.wg-page .wg-eyebrow{font-family:var(--wg-font-display,"Cinzel",serif);font-size:12px;
|
||||
letter-spacing:.28em;text-transform:uppercase;color:var(--wg-gold,#c2a35a);}
|
||||
.wg-page .wg-h2{font-family:var(--wg-font-display,"Cinzel",serif);font-weight:600;
|
||||
font-size:34px;color:var(--page-bright);margin:0;letter-spacing:.02em;
|
||||
text-shadow:0 1px 0 rgba(0,0,0,.9),0 0 10px rgba(104,32,76,.16);}
|
||||
.wg-page .wg-card{background:var(--wg-velvet-panel);border:1px solid var(--wg-border,rgba(194,163,90,.14));
|
||||
border-radius:8px;box-shadow:var(--wg-velvet-shadow);}
|
||||
.wg-page .wg-rule{display:flex;align-items:center;justify-content:center;gap:18px;}
|
||||
.wg-page .wg-rule i{height:1px;width:80px;display:block;}
|
||||
.wg-page .wg-rule span{width:7px;height:7px;background:var(--wg-gold,#c2a35a);transform:rotate(45deg);}
|
||||
|
||||
.wg-page .wg-section{padding:80px 24px 0;}
|
||||
.wg-page .wg-section.last{padding-bottom:100px;}
|
||||
.wg-page .wg-section__head{text-align:center;margin-bottom:44px;}
|
||||
.wg-page .wg-section__head .wg-eyebrow{display:block;margin-bottom:12px;}
|
||||
|
||||
/* hero */
|
||||
.wg-page .wg-rhero{display:grid;grid-template-columns:1.05fr 1fr;gap:56px;align-items:center;padding:82px 24px 20px;}
|
||||
.wg-page .wg-rhero h1{font-family:var(--wg-font-display,"Cinzel",serif);font-weight:700;
|
||||
font-size:clamp(36px,4.6vw,52px);line-height:1.08;letter-spacing:.02em;color:var(--page-bright);margin:0 0 22px;}
|
||||
.wg-page .wg-rhero .lead{font-family:var(--wg-font-text,"Jost",sans-serif);font-weight:300;
|
||||
font-size:18px;line-height:1.7;color:#cfc5b7;margin:0 0 34px;max-width:30em;}
|
||||
.wg-page .wg-shot{overflow:hidden;}
|
||||
.wg-page .wg-shot .bar{display:flex;align-items:center;gap:9px;padding:13px 16px;border-bottom:1px solid rgba(194,163,90,.16);}
|
||||
.wg-page .wg-shot .bar i{width:9px;height:9px;border-radius:50%;display:block;}
|
||||
.wg-page .wg-shot .bar span{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:12px;color:var(--wg-text-muted,#9a9086);margin-left:6px;}
|
||||
.wg-page .wg-shot img{width:100%;aspect-ratio:16/11;object-fit:cover;display:block;background:#161019;}
|
||||
|
||||
/* pillars */
|
||||
.wg-page .wg-pillars{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;}
|
||||
.wg-page .wg-pillar{overflow:hidden;display:flex;flex-direction:column;}
|
||||
.wg-page .wg-pillar img{width:100%;aspect-ratio:16/9;object-fit:cover;display:block;border-bottom:1px solid rgba(194,163,90,.12);background:#161019;}
|
||||
.wg-page .wg-pillar .body{padding:26px 26px 28px;}
|
||||
.wg-page .wg-pillar h3{font-family:var(--wg-font-display,"Cinzel",serif);font-size:20px;color:var(--wg-text,#e6e0d6);margin:0 0 10px;}
|
||||
.wg-page .wg-pillar p{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:14.5px;line-height:1.7;color:var(--wg-text-muted,#9a9086);margin:0;}
|
||||
|
||||
/* team panel */
|
||||
.wg-page .wg-team{padding:46px 50px;display:grid;grid-template-columns:1.1fr 1fr;gap:50px;align-items:center;}
|
||||
.wg-page .wg-team h2{font-family:var(--wg-font-display,"Cinzel",serif);font-weight:600;font-size:30px;line-height:1.2;color:var(--page-bright);margin:0 0 16px;}
|
||||
.wg-page .wg-team p{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:16px;line-height:1.85;color:var(--wg-text-muted,#9a9086);margin:0;}
|
||||
.wg-page .wg-team ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:16px;}
|
||||
.wg-page .wg-team li{display:flex;align-items:center;gap:14px;font-family:var(--wg-font-text,"Jost",sans-serif);font-size:15.5px;color:var(--wg-text-soft,#b9b2a6);}
|
||||
.wg-page .wg-team li b{width:7px;height:7px;background:var(--wg-gold,#c2a35a);transform:rotate(45deg);flex:none;}
|
||||
|
||||
/* cta */
|
||||
.wg-page .wg-cta{max-width:760px;margin:0 auto;padding:80px 24px 100px;text-align:center;}
|
||||
.wg-page .wg-cta h2{font-family:var(--wg-font-display,"Cinzel",serif);font-weight:700;font-size:40px;color:var(--page-bright);margin:0 0 16px;letter-spacing:.02em;}
|
||||
.wg-page .wg-cta .lead{font-family:var(--wg-font-text,"Jost",sans-serif);font-weight:300;font-size:18px;line-height:1.7;color:#cfc5b7;margin:0 0 30px;}
|
||||
.wg-page .wg-cta .foot{margin-top:46px;padding-top:22px;border-top:1px solid rgba(194,163,90,.12);font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:12px;letter-spacing:.06em;color:var(--wg-text-muted,#9a9086);}
|
||||
|
||||
@media (max-width:900px){
|
||||
.wg-page .wg-rhero{grid-template-columns:1fr;gap:32px;}
|
||||
.wg-page .wg-pillars{grid-template-columns:1fr;}
|
||||
.wg-page .wg-team{grid-template-columns:1fr;gap:28px;padding:32px;}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="wg-page wg-recruit">
|
||||
|
||||
<!-- ============ HERO ============ -->
|
||||
<div class="wg-wrap">
|
||||
<div class="wg-rhero">
|
||||
<div>
|
||||
<span class="wg-eyebrow">Persistent World · Made by Hand</span>
|
||||
<h1 style="margin-top:22px;">Come build a world that's a pleasure to work on.</h1>
|
||||
<p class="lead">Westgate is made by a handful of people who love this stuff — artists, writers, builders, scripters. We've kept the tools pleasant so your time goes where it matters: into the world itself. If you make things, we'd love to meet you.</p>
|
||||
<!-- DISCORD: replace href -->
|
||||
<a class="wg-btn" href="#discord">Say Hello on Discord</a>
|
||||
</div>
|
||||
<div class="wg-card wg-shot">
|
||||
<div class="bar"><i style="background:#7fb86a;"></i><i style="background:#d8bd76;"></i><i style="background:#bf8a86;"></i><span>westgate — the undergate</span></div>
|
||||
<!-- ART: a screenshot of a corner contributors helped build -->
|
||||
<img data-wg-art src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/uploads/westgate/recruit-shot.jpg" alt="" onerror="this.style.visibility='hidden'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ WHO WE NEED ============ -->
|
||||
<div class="wg-wrap wg-section">
|
||||
<div class="wg-section__head">
|
||||
<span class="wg-eyebrow">Who We Need</span>
|
||||
<h2 class="wg-h2">Every kind of maker has a place here</h2>
|
||||
<p style="font-family:var(--wg-font-text,'Jost',sans-serif);font-size:16px;line-height:1.7;color:var(--wg-text-muted,#9a9086);margin:14px auto 0;max-width:32em;">Code, art, words, world — whatever you make, there's a part of Westgate waiting for it.</p>
|
||||
</div>
|
||||
<div class="wg-pillars">
|
||||
<article class="wg-card wg-pillar">
|
||||
<img data-wg-art src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/uploads/westgate/recruit-scripts.jpg" alt="" onerror="this.style.visibility='hidden'">
|
||||
<div class="body"><h3>Tools that stay out of your way</h3><p>For builders and scripters: a clean toolset and modern scripting on a fast core — none of NWScript's old quirks to fight. Everything's saved and versioned, so you can experiment without fear of breaking things.</p></div>
|
||||
</article>
|
||||
<article class="wg-card wg-pillar">
|
||||
<img data-wg-art src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/uploads/westgate/recruit-art.jpg" alt="" onerror="this.style.visibility='hidden'">
|
||||
<div class="body"><h3>Your craft, your stamp</h3><p>For artists and asset-makers: models, textures, areas, sound. We're not after a house style to copy — we want the thing only you'd make, and we'll help get it into the game.</p></div>
|
||||
</article>
|
||||
<article class="wg-card wg-pillar">
|
||||
<img data-wg-art src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/uploads/westgate/recruit-lore.jpg" alt="" onerror="this.style.visibility='hidden'">
|
||||
<div class="body"><h3>Ideas that reach players</h3><p>For writers and designers: a questline, a faction, a forgotten corner of the city. What you dream up this month is something players are living in the next.</p></div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ WHAT IT'S LIKE ============ -->
|
||||
<div class="wg-wrap wg-section">
|
||||
<div class="wg-card wg-team">
|
||||
<div>
|
||||
<span class="wg-eyebrow">What It's Actually Like</span>
|
||||
<h2 style="margin-top:14px;">Low ego, high trust — and your work goes live.</h2>
|
||||
<p>No gatekeeping, no hazing, no "figure it out yourself." We give honest, kind feedback, lend a hand when it helps, and keep things moving. The thing you make this week is the thing players see this week.</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li><b></b>Honest, kind feedback on your work</li>
|
||||
<li><b></b>A hand whenever you want one</li>
|
||||
<li><b></b>What you make can be in-game within the week</li>
|
||||
<li><b></b>Work at whatever pace fits your life</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ CTA ============ -->
|
||||
<div class="wg-cta">
|
||||
<div class="wg-rule" style="margin-bottom:36px;"><i style="background:linear-gradient(90deg,transparent,var(--wg-gold,#c2a35a));"></i><span></span><i style="background:linear-gradient(90deg,var(--wg-gold,#c2a35a),transparent);"></i></div>
|
||||
<h2>Sound like your kind of place?</h2>
|
||||
<p class="lead">No test gauntlet, no résumé. Just come say hi and tell us what you'd love to build.</p>
|
||||
<!-- DISCORD: replace href -->
|
||||
<a class="wg-btn" href="#discord" style="padding:16px 36px;box-shadow:0 4px 26px rgba(168,133,74,.4);">Join the Team on Discord →</a>
|
||||
<div class="foot">Artists · writers · builders · scripters — all welcome</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,886 @@
|
||||
<!--
|
||||
============================================================================
|
||||
SHADOWS OVER WESTGATE — GLOBAL TOP BAR (theme header partial)
|
||||
----------------------------------------------------------------------------
|
||||
Visual target: "Westgate Top Bar.dc.html" (member / guest / Forums menu /
|
||||
user menu / mobile drawer). Theme: nodebb-theme-westgate.
|
||||
|
||||
WHAT THIS IS
|
||||
- A self-contained, modular header: markup + scoped CSS + a small vanilla-JS
|
||||
controller. Drop-in for previewing AND a faithful reference for the live
|
||||
NodeBB partial described in THEME-INTEGRATION.md §4.
|
||||
|
||||
HOW TO USE IT LIVE (templates/partials/header/topbar.tpl)
|
||||
1. Paste the <style> block once (or move it into scss/westgate/_topbar.scss).
|
||||
2. Paste the <header class="wg-topbar"> markup into the partial. Replace the
|
||||
two demo clusters with NodeBB conditionals:
|
||||
{{{ if loggedIn }}} ...member right-cluster...
|
||||
{{{ else }}} ...guest right-cluster... {{{ end }}}
|
||||
3. NAV ITEMS: loop ACP -> Navigation instead of the hard-coded <a>s. The
|
||||
"Forums" item keeps the data-wg-menu="forums" hook to open the mega-menu;
|
||||
populate its panel from your real category list.
|
||||
4. LIVE COMPONENTS: swap the static notification / chat / user dropdowns for
|
||||
Harmony's includes (partials/header/menu.tpl etc.) so counts stay live.
|
||||
The data-wg-menu / data-wg-panel hooks below show exactly where each goes.
|
||||
5. Mount in header.tpl (replace the IMPORT of partials/sidebar-left.tpl) and
|
||||
drop the left layout column so #panel runs full width.
|
||||
6. JS: the inline <script> at the bottom is for this self-contained preview.
|
||||
In production put the controller in ACP -> Custom JavaScript (see the
|
||||
paste-ready "topbar-custom.js" in this folder) — NodeBB is an SPA, so it
|
||||
uses document-level event delegation to survive page navigation, and the
|
||||
preview-only member/guest switch is dropped there.
|
||||
|
||||
NOTES
|
||||
- All colours/fonts use the theme's global --wg-* variables (with fallbacks),
|
||||
so the bar tracks the forum + wiki palette.
|
||||
- Everything is scoped under .wg-topbar so it can't leak into forum chrome.
|
||||
- The little floating switch (bottom-right) is PREVIEW-ONLY — it flips
|
||||
member/guest and is wrapped in .wg-topbar-demo. Delete that block + its
|
||||
markup for production; nothing else depends on it.
|
||||
- Avatar / badge counts are placeholders here.
|
||||
============================================================================
|
||||
-->
|
||||
|
||||
<!-- Preview-only: load brand fonts. The live theme already provides these, so
|
||||
this <link> is harmless (de-dupes by URL) but can be removed in the partial. -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=Jost:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
/* ===================== GLOBAL TOP BAR ===================== */
|
||||
.wg-topbar{
|
||||
--tb-h:66px;
|
||||
--tb-gold:var(--wg-gold,#c2a35a);
|
||||
--tb-text:var(--wg-text,#e6e0d6);
|
||||
--tb-soft:var(--wg-text-soft,#b9b2a6);
|
||||
--tb-muted:var(--wg-text-muted,#9a9086);
|
||||
--tb-bg:var(--wg-bg,#0f0d12);
|
||||
--tb-border:rgba(194,163,90,.16);
|
||||
--tb-presence:#7fb86a; /* status ring colour, switched by [data-presence] below */
|
||||
position:sticky;top:0;z-index:1000;
|
||||
font-family:var(--wg-font-text,"Jost",sans-serif);
|
||||
background:var(--tb-bg);
|
||||
border-bottom:1px solid var(--tb-border);
|
||||
}
|
||||
.wg-topbar[data-presence="online"] {--tb-presence:#7fb86a;}
|
||||
.wg-topbar[data-presence="away"] {--tb-presence:#d8bd76;}
|
||||
.wg-topbar[data-presence="dnd"] {--tb-presence:#bf6a66;}
|
||||
.wg-topbar[data-presence="offline"] {--tb-presence:#6b6152;}
|
||||
.wg-topbar *{box-sizing:border-box;}
|
||||
.wg-topbar a{text-decoration:none;color:inherit;}
|
||||
/* NB: use font-family (not the `font` shorthand) so this rule's specificity
|
||||
doesn't reset font-size on text buttons like the Forums nav trigger — the
|
||||
button's own class (e.g. .wg-topbar__link) sets the size. */
|
||||
.wg-topbar button{font-family:inherit;line-height:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:0;}
|
||||
|
||||
/* ---- bar shell ---- */
|
||||
.wg-topbar__bar{
|
||||
height:var(--tb-h);width:100%;margin:0 auto;
|
||||
display:flex;align-items:center;justify-content:space-between;
|
||||
padding:0 26px;gap:24px;
|
||||
background:linear-gradient(180deg,rgba(20,17,25,.9),rgba(15,13,18,.92));
|
||||
}
|
||||
.wg-topbar__left{display:flex;align-items:center;gap:36px;min-width:0;}
|
||||
|
||||
/* ---- brand ---- */
|
||||
.wg-topbar__brand{display:flex;align-items:center;gap:12px;flex:none;}
|
||||
.wg-topbar__glyph{
|
||||
width:24px;height:24px;transform:rotate(45deg);border:1.5px solid var(--tb-gold);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
box-shadow:0 0 12px rgba(194,163,90,.22);
|
||||
}
|
||||
.wg-topbar__glyph i{width:8px;height:8px;background:var(--tb-gold);display:block;}
|
||||
.wg-topbar__name{
|
||||
font-family:var(--wg-font-display,"Cinzel",serif);font-weight:600;font-size:15px;
|
||||
letter-spacing:.14em;color:var(--tb-text);white-space:nowrap;
|
||||
}
|
||||
.wg-topbar__name--short{display:none;}
|
||||
|
||||
/* ---- primary nav ---- */
|
||||
.wg-topbar__nav{display:flex;align-items:center;gap:26px;}
|
||||
.wg-topbar__link{
|
||||
position:relative;display:flex;align-items:center;gap:6px;
|
||||
font-size:12.5px;letter-spacing:.13em;text-transform:uppercase;
|
||||
color:var(--tb-soft);padding:4px 0;white-space:nowrap;
|
||||
transition:color .2s;
|
||||
}
|
||||
.wg-topbar__link:hover,.wg-topbar__link.is-active{color:var(--tb-text);}
|
||||
.wg-topbar__link .caret{transition:transform .25s;}
|
||||
.wg-topbar__link[aria-expanded="true"] .caret{transform:rotate(180deg);}
|
||||
.wg-topbar__link[aria-expanded="true"]{color:var(--tb-text);}
|
||||
.wg-topbar__link::after{
|
||||
content:"";position:absolute;left:-3px;right:-3px;bottom:-21px;height:2px;
|
||||
background:linear-gradient(90deg,transparent,var(--tb-gold),transparent);
|
||||
opacity:0;transform:scaleX(.4);transition:opacity .25s,transform .25s;
|
||||
}
|
||||
.wg-topbar__link.is-active::after,
|
||||
.wg-topbar__link[aria-expanded="true"]::after{opacity:1;transform:scaleX(1);}
|
||||
|
||||
/* ---- right cluster ---- */
|
||||
.wg-topbar__right{display:flex;align-items:center;gap:16px;flex:none;}
|
||||
.wg-topbar__icon{
|
||||
position:relative;width:34px;height:34px;border-radius:6px;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
color:var(--tb-soft);transition:background .2s,border-color .2s,color .2s;
|
||||
}
|
||||
.wg-topbar__icon:hover{color:var(--tb-text);background:rgba(194,163,90,.08);}
|
||||
.wg-topbar__icon--search{border:1px solid rgba(194,163,90,.18);background:rgba(194,163,90,.05);}
|
||||
.wg-topbar__badge{
|
||||
position:absolute;top:-2px;right:-2px;min-width:15px;height:15px;border-radius:8px;
|
||||
background:linear-gradient(180deg,#d8bd76,#a8893f);color:#150f08;
|
||||
font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:9px;font-weight:600;
|
||||
display:flex;align-items:center;justify-content:center;padding:0 3px;
|
||||
border:1px solid var(--tb-bg);
|
||||
}
|
||||
|
||||
/* ---- auth (guest) ---- */
|
||||
.wg-topbar__login{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--tb-soft);transition:color .2s;}
|
||||
.wg-topbar__login:hover{color:var(--tb-text);}
|
||||
.wg-topbar__register{
|
||||
font-size:12px;letter-spacing:.1em;text-transform:uppercase;font-weight:600;color:#150f08;
|
||||
background:linear-gradient(180deg,#d8bd76,#a8893f);padding:9px 20px;border-radius:6px;
|
||||
border:1px solid rgba(216,194,138,.6);box-shadow:0 2px 12px rgba(168,133,74,.3);
|
||||
transition:filter .2s,transform .2s;
|
||||
}
|
||||
.wg-topbar__register:hover{filter:brightness(1.07);transform:translateY(-1px);}
|
||||
|
||||
/* ---- avatar ---- */
|
||||
.wg-topbar__avatar{
|
||||
width:36px;height:36px;border-radius:50%;flex:none;overflow:hidden;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
background:radial-gradient(circle at 34% 24%,rgba(255,238,190,.22),transparent 30%),linear-gradient(145deg,#6f562d,#2c1122);
|
||||
border:1px solid rgba(216,194,138,.4);
|
||||
/* outer ring colour follows the chosen presence/status */
|
||||
box-shadow:0 0 0 2px var(--tb-bg),0 0 0 3px var(--tb-presence);
|
||||
background-size:cover;background-position:center;
|
||||
}
|
||||
.wg-topbar__avatar img{width:100%;height:100%;object-fit:cover;display:block;}
|
||||
.wg-topbar__avatar-letter{font-family:var(--wg-font-display,"Cinzel",serif);font-size:15px;font-weight:600;color:#ecdcb6;line-height:1;}
|
||||
/* the menu-header avatar shows the picture but no status ring */
|
||||
.wg-usermenu__head .av{box-shadow:none;}
|
||||
.wg-usermenu__head .av .wg-topbar__avatar-letter{font-size:17px;}
|
||||
|
||||
/* ---- pop-outs (drafts / notifications / chats) ---- */
|
||||
.wg-topbar__popout{position:relative;display:flex;align-items:center;}
|
||||
/* 360px + 400px-max scroll list = Harmony's .chats/.notifications/.drafts-dropdown */
|
||||
.wg-topbar__panel--list{top:calc(100% + 14px);right:0;width:360px;padding:8px;}
|
||||
.wg-pop__list{max-height:400px;overflow-y:auto;overscroll-behavior:contain;padding-right:2px;}
|
||||
.wg-pop__list::-webkit-scrollbar{width:8px;}
|
||||
.wg-pop__list::-webkit-scrollbar-thumb{background:rgba(194,163,90,.22);border-radius:4px;}
|
||||
.wg-pop__sep{border:0;border-top:1px solid rgba(194,163,90,.1);margin:4px 0;}
|
||||
.wg-ava{
|
||||
width:32px;height:32px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;
|
||||
font-family:var(--wg-font-display,"Cinzel",serif);font-size:13px;font-weight:600;color:#f1e6cf;
|
||||
background:var(--c,#4a3a2a);box-shadow:inset 0 0 0 1px rgba(255,255,255,.08);
|
||||
}
|
||||
.wg-timeago{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:11px;letter-spacing:.02em;color:var(--tb-muted);}
|
||||
|
||||
/* notification filter tabs */
|
||||
.wg-pop__tabs{display:flex;gap:6px;padding:4px 4px 2px;}
|
||||
.wg-pop__tab{
|
||||
font-size:12px;letter-spacing:.04em;color:var(--tb-soft);padding:6px 14px;border-radius:6px;
|
||||
display:flex;align-items:center;gap:7px;transition:background .18s,color .18s;
|
||||
}
|
||||
.wg-pop__tab:hover{background:rgba(194,163,90,.07);color:var(--tb-text);}
|
||||
.wg-pop__tab.is-active{background:rgba(194,163,90,.12);color:var(--tb-text);font-weight:500;}
|
||||
.wg-pop__tabcount{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:10px;color:var(--tb-gold);}
|
||||
|
||||
/* notification row */
|
||||
.wg-notif{display:flex;gap:11px;align-items:flex-start;padding:11px 10px;border-radius:6px;transition:background .18s;}
|
||||
.wg-notif:hover{background:rgba(194,163,90,.07);}
|
||||
.wg-notif.unread{background:rgba(194,163,90,.045);}
|
||||
.wg-notif__body{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1;}
|
||||
.wg-notif__text{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:13px;line-height:1.45;color:var(--tb-soft);
|
||||
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
||||
.wg-notif__text strong{color:var(--tb-text);font-weight:600;}
|
||||
.wg-notif__dot{width:8px;height:8px;border-radius:50%;background:var(--tb-gold);flex:none;margin-top:5px;}
|
||||
|
||||
/* chat row */
|
||||
.wg-chat{display:flex;gap:11px;align-items:flex-start;padding:11px 10px;border-radius:6px;transition:background .18s;}
|
||||
.wg-chat:hover{background:rgba(194,163,90,.07);}
|
||||
.wg-chat.unread{background:rgba(194,163,90,.045);}
|
||||
.wg-chat__body{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1;}
|
||||
.wg-chat__top{display:flex;align-items:baseline;justify-content:space-between;gap:8px;}
|
||||
.wg-chat__name{font-family:var(--wg-font-display,"Cinzel",serif);font-size:13.5px;color:var(--tb-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.wg-chat__last{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:12.5px;color:var(--tb-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.wg-chat__last strong{color:var(--tb-soft);font-weight:600;}
|
||||
|
||||
/* draft row */
|
||||
.wg-draft{display:flex;gap:6px;align-items:flex-start;padding:9px 8px;border-radius:6px;transition:background .18s;}
|
||||
.wg-draft:hover{background:rgba(194,163,90,.07);}
|
||||
.wg-draft__open{display:flex;flex-direction:column;gap:6px;flex:1;min-width:0;}
|
||||
.wg-draft__title{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:13px;font-weight:600;color:var(--tb-text);
|
||||
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
||||
.wg-draft__text{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:12.5px;line-height:1.5;color:var(--tb-muted);
|
||||
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
||||
.wg-draft__meta{display:flex;align-items:center;gap:9px;}
|
||||
.wg-cat-label{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:10px;letter-spacing:.06em;text-transform:uppercase;
|
||||
color:var(--tb-gold);border:1px solid rgba(194,163,90,.3);border-radius:4px;padding:2px 7px;}
|
||||
.wg-draft__del{width:26px;height:26px;border-radius:6px;flex:none;display:flex;align-items:center;justify-content:center;
|
||||
color:var(--tb-muted);transition:background .18s,color .18s;}
|
||||
.wg-draft__del:hover{background:rgba(191,138,134,.12);color:#d39c98;}
|
||||
|
||||
/* empty state */
|
||||
.wg-pop__empty{display:flex;flex-direction:column;align-items:center;gap:12px;padding:34px 12px;color:var(--tb-muted);
|
||||
font-family:var(--wg-font-text,"Jost",sans-serif);font-size:13px;}
|
||||
.wg-pop__empty[hidden]{display:none;}
|
||||
|
||||
/* quick-search results (Harmony search.tpl quick-search-container) */
|
||||
.wg-search__results{display:none;margin-top:8px;border-top:1px solid rgba(194,163,90,.12);padding-top:6px;}
|
||||
.wg-topbar.is-searching .wg-topbar__searchbox.has-results{height:auto;flex-direction:column;align-items:stretch;padding:10px 12px;}
|
||||
.wg-topbar__searchbox.has-results .wg-search__results{display:block;}
|
||||
.wg-search__row{display:flex;gap:9px;align-items:flex-start;padding:8px 6px;border-radius:6px;transition:background .18s;}
|
||||
.wg-search__row:hover{background:rgba(194,163,90,.08);}
|
||||
.wg-search__row .t{font-family:var(--wg-font-text,"Jost",sans-serif);font-size:13px;color:var(--tb-text);line-height:1.4;}
|
||||
.wg-search__row .m{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:10.5px;letter-spacing:.04em;color:var(--tb-muted);margin-top:3px;}
|
||||
.wg-search__all{display:block;text-align:center;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--tb-gold);padding:9px;border-radius:6px;}
|
||||
.wg-search__all:hover{background:rgba(194,163,90,.08);}
|
||||
|
||||
/* pop-out footer (mark-all-read / see-all) */
|
||||
.wg-pop__foot{display:flex;gap:7px;padding:4px 2px 2px;}
|
||||
.wg-pop__btn{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;
|
||||
font-size:12px;letter-spacing:.03em;color:var(--tb-soft);padding:8px 10px;border-radius:6px;
|
||||
background:rgba(194,163,90,.06);transition:background .18s,color .18s,filter .18s;}
|
||||
.wg-pop__btn:hover{background:rgba(194,163,90,.12);color:var(--tb-text);}
|
||||
.wg-pop__btn--primary{background:linear-gradient(180deg,#d8bd76,#a8893f);color:#150f08;font-weight:600;}
|
||||
.wg-pop__btn--primary:hover{filter:brightness(1.07);color:#150f08;}
|
||||
|
||||
.wg-topbar__searchbox{
|
||||
display:flex;align-items:center;gap:10px;overflow:hidden;
|
||||
width:0;opacity:0;padding:0;border:1px solid transparent;border-radius:6px;
|
||||
background:rgba(8,7,10,.55);
|
||||
transition:width .28s ease,opacity .2s,padding .28s,border-color .2s;
|
||||
}
|
||||
.wg-topbar.is-searching .wg-topbar__searchbox{width:240px;opacity:1;padding:0 12px;border-color:rgba(194,163,90,.18);height:34px;}
|
||||
.wg-topbar.is-searching .wg-topbar__icon--search{display:none;}
|
||||
.wg-topbar__searchbox input{
|
||||
flex:1;min-width:0;background:none;border:0;outline:none;color:var(--tb-text);
|
||||
font-family:inherit;font-size:13px;letter-spacing:.02em;
|
||||
}
|
||||
.wg-topbar__searchbox input::placeholder{color:var(--tb-muted);}
|
||||
|
||||
/* ===================== DROPDOWN PANELS ===================== */
|
||||
.wg-topbar__panel{
|
||||
position:absolute;z-index:1100;
|
||||
background:linear-gradient(100deg,rgba(42,18,34,.5),rgba(25,22,31,.97) 30%,rgba(18,16,23,.99));
|
||||
border:1px solid rgba(194,163,90,.2);border-radius:8px;
|
||||
box-shadow:inset 0 1px 0 rgba(255,255,255,.03),0 18px 40px rgba(0,0,0,.5);
|
||||
opacity:0;visibility:hidden;transform:translateY(-8px);
|
||||
transition:opacity .2s,transform .2s,visibility 0s linear .2s;
|
||||
}
|
||||
.wg-topbar__panel.is-open{opacity:1;visibility:visible;transform:translateY(0);transition:opacity .2s,transform .2s,visibility 0s;}
|
||||
|
||||
/* forums mega-menu */
|
||||
.wg-topbar__panel--forums{top:calc(var(--tb-h) - 4px);left:290px;width:560px;padding:10px;}
|
||||
.wg-mega__grid{display:grid;grid-template-columns:1fr 1fr;gap:4px;}
|
||||
.wg-mega__item{display:block;padding:13px 16px;border-radius:6px;transition:background .18s;}
|
||||
.wg-mega__item:hover{background:rgba(194,163,90,.08);}
|
||||
.wg-mega__item .t{font-family:var(--wg-font-display,"Cinzel",serif);font-size:15px;color:var(--tb-text);margin-bottom:3px;}
|
||||
.wg-mega__item .d{font-size:12.5px;color:var(--tb-muted);}
|
||||
.wg-mega__foot{
|
||||
display:flex;align-items:center;justify-content:space-between;
|
||||
margin-top:8px;padding:14px 16px 6px;border-top:1px solid rgba(194,163,90,.12);
|
||||
}
|
||||
.wg-mega__foot .links{display:flex;gap:20px;}
|
||||
.wg-mega__foot .links a{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--tb-soft);transition:color .2s;}
|
||||
.wg-mega__foot .links a:hover{color:var(--tb-text);}
|
||||
.wg-mega__foot .all{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--tb-gold);}
|
||||
|
||||
/* user menu */
|
||||
.wg-topbar__usermenu{position:relative;}
|
||||
.wg-topbar__panel--user{top:calc(var(--tb-h) - 2px);right:26px;width:248px;overflow:hidden;}
|
||||
.wg-usermenu__head{display:flex;align-items:center;gap:12px;padding:16px 18px;border-bottom:1px solid rgba(194,163,90,.12);}
|
||||
.wg-usermenu__head .av{width:40px;height:40px;flex:none;}
|
||||
.wg-usermenu__head .nm{font-family:var(--wg-font-display,"Cinzel",serif);font-size:15px;color:var(--tb-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.wg-usermenu__head .vp{font-size:12px;color:var(--tb-gold);}
|
||||
.wg-usermenu__list{padding:8px;}
|
||||
.wg-usermenu__row{
|
||||
display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:6px;
|
||||
font-size:14px;color:var(--tb-soft);transition:background .18s,color .18s;
|
||||
}
|
||||
.wg-usermenu__row:hover{background:rgba(194,163,90,.08);color:var(--tb-text);}
|
||||
.wg-usermenu__row.danger{color:#bf8a86;}
|
||||
.wg-usermenu__row.danger:hover{background:rgba(191,138,134,.1);color:#d39c98;}
|
||||
.wg-usermenu__sep{height:1px;background:rgba(194,163,90,.12);margin:7px 4px;}
|
||||
.wg-topbar__panel--forums .wg-mega__item.is-active,
|
||||
.wg-usermenu__row.is-active{background:rgba(194,163,90,.06);}
|
||||
.wg-usermenu__row{width:100%;text-align:left;}
|
||||
.wg-usermenu__label{font-family:var(--wg-font-code,"IBM Plex Mono",monospace);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--tb-muted);padding:11px 12px 5px;}
|
||||
.wg-usermenu__row .dot{width:11px;height:11px;border-radius:50%;flex:none;margin:0 2px;box-shadow:0 0 0 3px rgba(0,0,0,.25);}
|
||||
.wg-usermenu__row .chk{margin-left:auto;color:var(--tb-gold);flex:none;}
|
||||
.wg-usermenu__row svg{flex:none;}
|
||||
|
||||
/* ===================== MOBILE ===================== */
|
||||
.wg-topbar__burger{display:none;flex-direction:column;gap:4px;width:24px;flex:none;}
|
||||
.wg-topbar__burger span{width:20px;height:1.6px;background:var(--tb-gold);display:block;transition:transform .25s,opacity .25s;}
|
||||
.wg-topbar.is-drawer .wg-topbar__burger span:nth-child(1){transform:translateY(5.6px) rotate(45deg);}
|
||||
.wg-topbar.is-drawer .wg-topbar__burger span:nth-child(2){opacity:0;}
|
||||
.wg-topbar.is-drawer .wg-topbar__burger span:nth-child(3){transform:translateY(-5.6px) rotate(-45deg);}
|
||||
|
||||
.wg-topbar__drawer{
|
||||
display:none;overflow:hidden;max-height:0;
|
||||
border-top:1px solid var(--tb-border);
|
||||
transition:max-height .3s ease;
|
||||
}
|
||||
.wg-topbar.is-drawer .wg-topbar__drawer{max-height:560px;}
|
||||
.wg-topbar__drawer-in{padding:10px 16px 18px;}
|
||||
.wg-drawer__search{
|
||||
display:flex;align-items:center;gap:10px;background:rgba(8,7,10,.5);
|
||||
border:1px solid rgba(194,163,90,.18);border-radius:8px;padding:10px 12px;margin:8px 0 14px;
|
||||
}
|
||||
.wg-drawer__search input{flex:1;background:none;border:0;outline:none;color:var(--tb-text);font:inherit;font-size:13px;}
|
||||
.wg-drawer__search input::placeholder{color:var(--tb-muted);}
|
||||
.wg-drawer__link{
|
||||
display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:15px;color:var(--tb-soft);padding:12px 8px;border-bottom:1px solid rgba(194,163,90,.08);
|
||||
}
|
||||
.wg-drawer__link.is-active{color:var(--tb-text);}
|
||||
.wg-drawer__sub{display:none;padding:2px 8px 6px;}
|
||||
.wg-drawer__link[aria-expanded="true"] + .wg-drawer__sub{display:block;}
|
||||
.wg-drawer__sub a{display:block;font-size:13.5px;color:var(--tb-muted);padding:9px 14px;}
|
||||
.wg-drawer__sub a:hover{color:var(--tb-text);}
|
||||
.wg-drawer__util{display:flex;align-items:center;gap:18px;margin-top:18px;padding:0 8px;}
|
||||
.wg-drawer__util a{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--tb-soft);}
|
||||
.wg-drawer__util .dot{width:7px;height:7px;border-radius:50%;background:var(--tb-gold);}
|
||||
.wg-drawer__auth{display:flex;gap:12px;margin-top:18px;padding:0 8px;}
|
||||
.wg-drawer__auth .wg-topbar__register{flex:1;text-align:center;}
|
||||
.wg-drawer__auth .wg-topbar__login{flex:1;text-align:center;padding:9px 0;border:1px solid rgba(194,163,90,.4);border-radius:6px;}
|
||||
|
||||
@media (max-width:1024px){
|
||||
.wg-topbar__bar{padding:0 16px;height:58px;}
|
||||
.wg-topbar{--tb-h:58px;}
|
||||
.wg-topbar__nav,.wg-topbar__icon--search,.wg-topbar__searchbox{display:none;}
|
||||
.wg-topbar__left{gap:0;flex:1;justify-content:center;}
|
||||
.wg-topbar__burger{display:flex;}
|
||||
.wg-topbar__brand{position:absolute;left:50%;transform:translateX(-50%);}
|
||||
.wg-topbar__name{font-size:12px;letter-spacing:.1em;}
|
||||
.wg-topbar__name--full{display:none;}
|
||||
.wg-topbar__name--short{display:inline;}
|
||||
.wg-topbar__glyph{width:18px;height:18px;}
|
||||
.wg-topbar__glyph i{width:6px;height:6px;}
|
||||
.wg-topbar__drawer{display:block;}
|
||||
.wg-topbar__panel--forums,.wg-topbar__panel--user,.wg-topbar__panel--list{display:none;}
|
||||
.wg-topbar__right .wg-topbar__icon--chat,.wg-topbar__popout{display:none;}
|
||||
}
|
||||
|
||||
/* preview-only switch */
|
||||
.wg-topbar-demo{
|
||||
position:fixed;right:18px;bottom:18px;z-index:5000;
|
||||
display:flex;gap:6px;align-items:center;padding:6px;border-radius:999px;
|
||||
background:rgba(18,16,23,.95);border:1px solid rgba(194,163,90,.25);
|
||||
box-shadow:0 8px 26px rgba(0,0,0,.5);font-family:"Jost",sans-serif;
|
||||
}
|
||||
.wg-topbar-demo button{
|
||||
font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#b9b2a6;
|
||||
padding:7px 14px;border-radius:999px;cursor:pointer;transition:.2s;
|
||||
}
|
||||
.wg-topbar-demo button.on{background:linear-gradient(180deg,#d8bd76,#a8893f);color:#150f08;font-weight:600;}
|
||||
</style>
|
||||
|
||||
|
||||
<!-- ============================ TOP BAR ============================ -->
|
||||
<header class="wg-topbar" data-state="member" data-presence="online">
|
||||
<div class="wg-topbar__bar">
|
||||
|
||||
<!-- left: burger (mobile) + brand + nav -->
|
||||
<div class="wg-topbar__left">
|
||||
<button class="wg-topbar__burger" data-wg-burger aria-label="Menu" aria-expanded="false">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
|
||||
<a class="wg-topbar__brand" href="/">
|
||||
<span class="wg-topbar__glyph"><i></i></span>
|
||||
<span class="wg-topbar__name wg-topbar__name--full">SHADOWS OVER WESTGATE</span>
|
||||
<span class="wg-topbar__name wg-topbar__name--short">WESTGATE</span>
|
||||
</a>
|
||||
|
||||
<nav class="wg-topbar__nav">
|
||||
<a class="wg-topbar__link is-active" href="/news">News</a>
|
||||
<a class="wg-topbar__link" href="/gallery">Gallery</a>
|
||||
<a class="wg-topbar__link" href="/wiki">Wiki</a>
|
||||
<!-- Forums: dropdown trigger. data-wg-menu pairs with the panel's data-wg-panel -->
|
||||
<button class="wg-topbar__link" data-wg-menu="forums" aria-expanded="false" aria-haspopup="true">
|
||||
Forums
|
||||
<svg class="caret" width="9" height="6" viewBox="0 0 9 6" fill="none"><path d="M1 1l3.5 3.5L8 1" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<a class="wg-topbar__link" href="/category/84/dev-blog">Dev Blog</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- right: search + utilities + (member) avatar OR (guest) auth -->
|
||||
<div class="wg-topbar__right">
|
||||
|
||||
<!-- inline search (desktop) -->
|
||||
<button class="wg-topbar__icon wg-topbar__icon--search" data-wg-search aria-label="Search">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.4"/><path d="M11 11l3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<form class="wg-topbar__searchbox" action="/search" method="get" role="search">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="6.5" cy="6.5" r="4.5" stroke="#9a9086" stroke-width="1.3"/><path d="M10 10l3 3" stroke="#9a9086" stroke-width="1.3" stroke-linecap="round"/></svg>
|
||||
<input type="search" name="term" placeholder="Search Westgate…" data-wg-search-input>
|
||||
</form>
|
||||
|
||||
<!-- ===== MEMBER cluster ===== (live: {{{ if loggedIn }}}) -->
|
||||
<div class="wg-topbar__authed" data-wg-when="member" style="display:flex;align-items:center;gap:16px;">
|
||||
<!-- ============================================================
|
||||
POP-OUTS. Structure mirrors Harmony's sidebar partials:
|
||||
drafts -> partials/sidebar/drafts.tpl
|
||||
notifications -> partials/sidebar/notifications.tpl
|
||||
chats -> partials/sidebar/chats.tpl
|
||||
In the live theme these ARE those partials (Bootstrap
|
||||
data-bs-toggle="dropdown"); NodeBB's client JS fills each
|
||||
[component="…/list"] container at runtime. The rows below are
|
||||
styled stand-ins so the panels aren't empty in preview.
|
||||
============================================================ -->
|
||||
|
||||
<!-- Drafts (partials/sidebar/drafts.tpl) -->
|
||||
<div class="wg-topbar__popout">
|
||||
<button class="wg-topbar__icon" data-wg-menu="drafts" aria-label="Drafts" aria-expanded="false" aria-haspopup="true" title="Drafts">
|
||||
<svg width="17" height="17" viewBox="0 0 18 18" fill="none"><path d="M10 3H4a1 1 0 00-1 1v10a1 1 0 001 1h10a1 1 0 001-1V8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><path d="M12.5 2.5l3 3-6 6H6.5v-3l6-6z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>
|
||||
<span class="wg-topbar__badge" data-count="drafts">2</span>
|
||||
</button>
|
||||
<div class="wg-topbar__panel wg-topbar__panel--list" data-wg-panel="drafts" role="menu">
|
||||
<div class="wg-pop__list" data-component="drafts/list">
|
||||
<!-- each: drafts/open + drafts/delete -->
|
||||
<div class="wg-draft" data-save-id="1">
|
||||
<a class="wg-draft__open" href="/category/3/roleplay/compose">
|
||||
<div class="wg-draft__title">A letter left at the Undergate</div>
|
||||
<div class="wg-draft__text">My dear Caelum — the arrangement we spoke of cannot wait for the next moon. Meet me where the…</div>
|
||||
<div class="wg-draft__meta"><span class="wg-cat-label">Roleplay & Stories</span><span class="wg-timeago">2h ago</span></div>
|
||||
</a>
|
||||
<button class="wg-draft__del" aria-label="Delete draft" title="Delete draft">
|
||||
<svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M3 4h8M5.5 4V2.8h3V4M4.2 4l.5 7.2h4.6L9.8 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<hr class="wg-pop__sep">
|
||||
<div class="wg-draft" data-save-id="2">
|
||||
<a class="wg-draft__open" href="/category/2/general/compose">
|
||||
<div class="wg-draft__title">Replying to "Best smuggling routes?"</div>
|
||||
<div class="wg-draft__text">Avoid the Customs Quay after dusk — the new harbourmaster doubled the watch. The cistern tunnels…</div>
|
||||
<div class="wg-draft__meta"><span class="wg-cat-label">General Discussion</span><span class="wg-timeago">yesterday</span></div>
|
||||
</a>
|
||||
<button class="wg-draft__del" aria-label="Delete draft" title="Delete draft">
|
||||
<svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M3 4h8M5.5 4V2.8h3V4M4.2 4l.5 7.2h4.6L9.8 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- empty state (drafts.tpl .no-drafts) -->
|
||||
<div class="wg-pop__empty" hidden>
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none"><path d="M3 12h6l1.5-3 3 6L15 12h6" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<span>You have no saved drafts.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notifications (partials/sidebar/notifications.tpl) -->
|
||||
<div class="wg-topbar__popout">
|
||||
<button class="wg-topbar__icon" data-wg-menu="notifications" aria-label="Notifications" aria-expanded="false" aria-haspopup="true" title="Notifications">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M9 2a4 4 0 00-4 4c0 4-1.5 5-1.5 5h11S13 10 13 6a4 4 0 00-4-4z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M7.5 14a1.5 1.5 0 003 0" stroke="currentColor" stroke-width="1.3"/></svg>
|
||||
<span class="wg-topbar__badge" data-count="notifications">3</span>
|
||||
</button>
|
||||
<div class="wg-topbar__panel wg-topbar__panel--list" data-wg-panel="notifications" role="menu">
|
||||
<!-- filter tabs (notifications.tpl) -->
|
||||
<div class="wg-pop__tabs">
|
||||
<button class="wg-pop__tab is-active" data-filter="all">All</button>
|
||||
<button class="wg-pop__tab" data-filter="unread">Unread <span class="wg-pop__tabcount">3</span></button>
|
||||
</div>
|
||||
<hr class="wg-pop__sep">
|
||||
<div class="wg-pop__list" data-component="notifications/list">
|
||||
<a class="wg-notif unread" href="/post/2410">
|
||||
<span class="wg-ava" style="--c:#5a3d6e;">V</span>
|
||||
<span class="wg-notif__body">
|
||||
<span class="wg-notif__text"><strong>Velessa Thorne</strong> replied to your topic <strong>The Masquerade at Hollow House</strong>.</span>
|
||||
<span class="wg-timeago">12 minutes ago</span>
|
||||
</span>
|
||||
<span class="wg-notif__dot" aria-label="unread"></span>
|
||||
</a>
|
||||
<hr class="wg-pop__sep">
|
||||
<a class="wg-notif unread" href="/post/2402">
|
||||
<span class="wg-ava" style="--c:#6e4a2c;">G</span>
|
||||
<span class="wg-notif__body">
|
||||
<span class="wg-notif__text"><strong>Guildmaster Orin</strong> mentioned you in <strong>Contracts & Commissions</strong>.</span>
|
||||
<span class="wg-timeago">1 hour ago</span>
|
||||
</span>
|
||||
<span class="wg-notif__dot" aria-label="unread"></span>
|
||||
</a>
|
||||
<hr class="wg-pop__sep">
|
||||
<a class="wg-notif unread" href="/post/2388">
|
||||
<span class="wg-ava" style="--c:#3d5a4e;">M</span>
|
||||
<span class="wg-notif__body">
|
||||
<span class="wg-notif__text"><strong>Magpie</strong> and 3 others upvoted your post.</span>
|
||||
<span class="wg-timeago">3 hours ago</span>
|
||||
</span>
|
||||
<span class="wg-notif__dot" aria-label="unread"></span>
|
||||
</a>
|
||||
<hr class="wg-pop__sep">
|
||||
<a class="wg-notif" href="/post/2350">
|
||||
<span class="wg-ava" style="--c:#5a2c2c;">H</span>
|
||||
<span class="wg-notif__body">
|
||||
<span class="wg-notif__text"><strong>House Verrault</strong> accepted your application.</span>
|
||||
<span class="wg-timeago">yesterday</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<hr class="wg-pop__sep">
|
||||
<div class="wg-pop__foot">
|
||||
<button class="wg-pop__btn"><svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M1 7.5l3 3 5-7M7 10l1.5 1.5 5-7" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg> Mark all read</button>
|
||||
<a class="wg-pop__btn wg-pop__btn--primary" href="/notifications"><svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M4 3.5h8M4 7h8M4 10.5h8M1.4 3.5h.01M1.4 7h.01M1.4 10.5h.01" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg> See all</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat (partials/sidebar/chats.tpl) -->
|
||||
<div class="wg-topbar__popout wg-topbar__icon--chat">
|
||||
<button class="wg-topbar__icon" data-wg-menu="chats" aria-label="Messages" aria-expanded="false" aria-haspopup="true" title="Messages">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M3 4h12v8H7l-3 3v-3H3z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>
|
||||
<span class="wg-topbar__badge" data-count="chats">2</span>
|
||||
</button>
|
||||
<div class="wg-topbar__panel wg-topbar__panel--list" data-wg-panel="chats" role="menu">
|
||||
<div class="wg-pop__list" data-component="chat/list">
|
||||
<a class="wg-chat unread" href="/chats/12">
|
||||
<span class="wg-ava" style="--c:#5a3d6e;">V</span>
|
||||
<span class="wg-chat__body">
|
||||
<span class="wg-chat__top"><span class="wg-chat__name">Velessa Thorne</span><span class="wg-timeago">2m</span></span>
|
||||
<span class="wg-chat__last">Bring the ledger. We settle accounts tonight.</span>
|
||||
</span>
|
||||
<span class="wg-notif__dot" aria-label="unread"></span>
|
||||
</a>
|
||||
<hr class="wg-pop__sep">
|
||||
<a class="wg-chat unread" href="/chats/9">
|
||||
<span class="wg-ava" style="--c:#2c4a6e;">T</span>
|
||||
<span class="wg-chat__body">
|
||||
<span class="wg-chat__top"><span class="wg-chat__name">The Cinderfall Crew</span><span class="wg-timeago">40m</span></span>
|
||||
<span class="wg-chat__last"><strong>Rooke:</strong> meet at the third bell, usual place</span>
|
||||
</span>
|
||||
<span class="wg-notif__dot" aria-label="unread"></span>
|
||||
</a>
|
||||
<hr class="wg-pop__sep">
|
||||
<a class="wg-chat" href="/chats/4">
|
||||
<span class="wg-ava" style="--c:#6e4a2c;">O</span>
|
||||
<span class="wg-chat__body">
|
||||
<span class="wg-chat__top"><span class="wg-chat__name">Guildmaster Orin</span><span class="wg-timeago">3h</span></span>
|
||||
<span class="wg-chat__last">Good. The commission is yours.</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<hr class="wg-pop__sep">
|
||||
<div class="wg-pop__foot">
|
||||
<button class="wg-pop__btn"><svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M1 7.5l3 3 5-7M7 10l1.5 1.5 5-7" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg> Mark all read</button>
|
||||
<a class="wg-pop__btn wg-pop__btn--primary" href="/user/archvillainette/chats"><svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M4 3.5h8M4 7h8M4 10.5h8M1.4 3.5h.01M1.4 7h.01M1.4 10.5h.01" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg> See all</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- user menu -->
|
||||
<div class="wg-topbar__usermenu">
|
||||
<button class="wg-topbar__avatar" data-wg-menu="user" aria-label="Account" aria-expanded="false" aria-haspopup="true">
|
||||
<!-- LIVE: {{{ if user.picture }}}<img src="{user.picture}" alt="{user.username}">{{{ else }}}<span class="wg-topbar__avatar-letter" style="background-color:{user.icon:bgColor}">{user.icon:text}</span>{{{ end }}} -->
|
||||
<span class="wg-topbar__avatar-letter">A</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== GUEST cluster ===== (live: {{{ else }}}) -->
|
||||
<div class="wg-topbar__guest" data-wg-when="guest" style="display:none;align-items:center;gap:16px;">
|
||||
<a class="wg-topbar__login" href="/login">Log In</a>
|
||||
<a class="wg-topbar__register" href="/register">Register</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Forums mega-menu (sample categories — wire to real list) ===== -->
|
||||
<div class="wg-topbar__panel wg-topbar__panel--forums" data-wg-panel="forums" role="menu">
|
||||
<div class="wg-mega__grid">
|
||||
<a class="wg-mega__item" href="/category/1/news"><div class="t">News</div><div class="d">Official server announcements</div></a>
|
||||
<a class="wg-mega__item" href="/category/84/dev-blog"><div class="t">Developer Blog</div><div class="d">Behind-the-scenes updates</div></a>
|
||||
<a class="wg-mega__item is-active" href="/category/2/general"><div class="t">General Discussion</div><div class="d">Talk of the city and beyond</div></a>
|
||||
<a class="wg-mega__item" href="/category/3/roleplay"><div class="t">Roleplay & Stories</div><div class="d">In-character journals and tales</div></a>
|
||||
<a class="wg-mega__item" href="/category/4/guilds"><div class="t">Guilds & Factions</div><div class="d">Masked guilds and noble houses</div></a>
|
||||
<a class="wg-mega__item" href="/category/5/help"><div class="t">Help & Support</div><div class="d">New players start here</div></a>
|
||||
</div>
|
||||
<div class="wg-mega__foot">
|
||||
<div class="links">
|
||||
<a href="/recent">Recent</a>
|
||||
<a href="/unread">Unread</a>
|
||||
<a href="/tags">Tags</a>
|
||||
</div>
|
||||
<a class="all" href="/categories">All Categories →</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== User menu ===== (live: Harmony user dropdown include) ===== -->
|
||||
<div class="wg-topbar__panel wg-topbar__panel--user" data-wg-panel="user" role="menu">
|
||||
<a class="wg-usermenu__head" href="/user/archvillainette">
|
||||
<!-- LIVE: {{{ if user.picture }}}<img class="wg-topbar__avatar av" src="{user.picture}" alt="">{{{ else }}}<span class="wg-topbar__avatar av"><span class="wg-topbar__avatar-letter" style="background-color:{user.icon:bgColor}">{user.icon:text}</span></span>{{{ end }}} -->
|
||||
<span class="wg-topbar__avatar av"><span class="wg-topbar__avatar-letter">A</span></span>
|
||||
<span style="min-width:0;">
|
||||
<span class="nm" style="display:block;">archvillainette</span>
|
||||
<span class="vp" style="display:block;">View profile</span>
|
||||
</span>
|
||||
</a>
|
||||
<div class="wg-usermenu__list">
|
||||
<!-- Presence (LIVE: Harmony user-status include; sets your online state) -->
|
||||
<div class="wg-usermenu__label">Status</div>
|
||||
<button class="wg-usermenu__row presence is-active" data-presence="online">
|
||||
<span class="dot" style="background:#7fb86a;"></span>Online
|
||||
<svg class="chk" width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M2.5 7l3 3 5-6.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<button class="wg-usermenu__row presence" data-presence="away">
|
||||
<span class="dot" style="background:#d8bd76;"></span>Away
|
||||
</button>
|
||||
<button class="wg-usermenu__row presence" data-presence="dnd">
|
||||
<span class="dot" style="background:#bf6a66;"></span>Do not disturb
|
||||
</button>
|
||||
<button class="wg-usermenu__row presence" data-presence="offline">
|
||||
<span class="dot" style="background:#6b6152;"></span>Invisible
|
||||
</button>
|
||||
|
||||
<div class="wg-usermenu__sep"></div>
|
||||
<a class="wg-usermenu__row" href="/user/archvillainette/bookmarks">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><path d="M4 2h7v11l-3.5-2.2L4 13z" stroke="#a8893f" stroke-width="1.3" stroke-linejoin="round"/></svg>
|
||||
Bookmarks
|
||||
</a>
|
||||
<a class="wg-usermenu__row" href="/user/archvillainette/edit">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><path d="M9.4 2.8l2.8 2.8M9.8 2.4a1.3 1.3 0 011.8 0l.2.2a1.3 1.3 0 010 1.8L5.3 11.7l-3 .9.9-3 6.6-6.6z" stroke="#a8893f" stroke-width="1.3" stroke-linejoin="round"/></svg>
|
||||
Edit Profile
|
||||
</a>
|
||||
<a class="wg-usermenu__row" href="/user/archvillainette/settings">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="2.4" stroke="#a8893f" stroke-width="1.3"/><path d="M7.5 1.5v2M7.5 11.5v2M1.5 7.5h2M11.5 7.5h2" stroke="#a8893f" stroke-width="1.3" stroke-linecap="round"/></svg>
|
||||
Settings
|
||||
</a>
|
||||
|
||||
<!-- Moderator Tools — privilege-gated server-side ({{{ if privileges.isAdminOrMod }}}) -->
|
||||
<div class="wg-usermenu__label">Moderator Tools</div>
|
||||
<a class="wg-usermenu__row" href="/flags">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><path d="M3.5 1.8v11.4M3.5 2.4h7.5l-1.5 2.6 1.5 2.6h-7.5" stroke="#a8893f" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Flagged Content
|
||||
</a>
|
||||
<a class="wg-usermenu__row" href="/post-queue">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><rect x="2.6" y="2.6" width="9.8" height="9.8" rx="1.4" stroke="#a8893f" stroke-width="1.3"/><path d="M5 5.4h5M5 7.5h5M5 9.6h2.8" stroke="#a8893f" stroke-width="1.3" stroke-linecap="round"/></svg>
|
||||
Post Queue
|
||||
</a>
|
||||
<a class="wg-usermenu__row" href="/ip-blacklist">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="5" stroke="#a8893f" stroke-width="1.3"/><path d="M4 4l7 7" stroke="#a8893f" stroke-width="1.3" stroke-linecap="round"/></svg>
|
||||
IP Blacklist
|
||||
</a>
|
||||
|
||||
<div class="wg-usermenu__sep"></div>
|
||||
<a class="wg-usermenu__row danger" href="/logout">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><path d="M6 2H3v11h3M9.5 4.5L12.5 7.5 9.5 10.5M12 7.5H6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Mobile drawer ===== -->
|
||||
<div class="wg-topbar__drawer">
|
||||
<div class="wg-topbar__drawer-in">
|
||||
<form class="wg-drawer__search" action="/search" method="get" role="search">
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="6.5" cy="6.5" r="4.5" stroke="#9a9086" stroke-width="1.3"/><path d="M10 10l3 3" stroke="#9a9086" stroke-width="1.3" stroke-linecap="round"/></svg>
|
||||
<input type="search" name="term" placeholder="Search Westgate…">
|
||||
</form>
|
||||
|
||||
<a class="wg-drawer__link is-active" href="/news">News</a>
|
||||
<a class="wg-drawer__link" href="/gallery">Gallery</a>
|
||||
<a class="wg-drawer__link" href="/wiki">Wiki</a>
|
||||
<button class="wg-drawer__link" data-wg-drawer-sub aria-expanded="false">
|
||||
Forums
|
||||
<svg width="9" height="6" viewBox="0 0 9 6" fill="none"><path d="M1 1l3.5 3.5L8 1" stroke="#a8893f" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<div class="wg-drawer__sub">
|
||||
<a href="/category/2/general">General Discussion</a>
|
||||
<a href="/category/3/roleplay">Roleplay & Stories</a>
|
||||
<a href="/category/4/guilds">Guilds & Factions</a>
|
||||
<a href="/category/5/help">Help & Support</a>
|
||||
<a href="/categories">All Categories →</a>
|
||||
</div>
|
||||
<a class="wg-drawer__link" href="/category/84/dev-blog">Dev Blog</a>
|
||||
|
||||
<!-- member utilities -->
|
||||
<div class="wg-drawer__util" data-wg-when="member">
|
||||
<a href="/notifications"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M9 2a4 4 0 00-4 4c0 4-1.5 5-1.5 5h11S13 10 13 6a4 4 0 00-4-4z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M7.5 14a1.5 1.5 0 003 0" stroke="currentColor" stroke-width="1.3"/></svg>Notifications<span class="dot"></span></a>
|
||||
<a href="/chats"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M3 4h12v8H7l-3 3v-3H3z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>Messages</a>
|
||||
</div>
|
||||
|
||||
<!-- guest auth -->
|
||||
<div class="wg-drawer__auth" data-wg-when="guest" style="display:none;">
|
||||
<a class="wg-topbar__login" href="/login">Log In</a>
|
||||
<a class="wg-topbar__register" href="/register">Register</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- PREVIEW-ONLY: member/guest switch. Delete this block for production. -->
|
||||
<div class="wg-topbar-demo">
|
||||
<button data-wg-demo="member" class="on">Member</button>
|
||||
<button data-wg-demo="guest">Guest</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var bar = document.querySelector('.wg-topbar');
|
||||
if(!bar) return;
|
||||
|
||||
// ---- dropdown menus (Forums + user) ----
|
||||
var triggers = bar.querySelectorAll('[data-wg-menu]');
|
||||
function closeMenus(except){
|
||||
bar.querySelectorAll('[data-wg-panel]').forEach(function(p){
|
||||
if(p.getAttribute('data-wg-panel') !== except) p.classList.remove('is-open');
|
||||
});
|
||||
triggers.forEach(function(t){
|
||||
if(t.getAttribute('data-wg-menu') !== except) t.setAttribute('aria-expanded','false');
|
||||
});
|
||||
}
|
||||
triggers.forEach(function(t){
|
||||
var key = t.getAttribute('data-wg-menu');
|
||||
var panel = bar.querySelector('[data-wg-panel="'+key+'"]');
|
||||
t.addEventListener('click', function(e){
|
||||
e.stopPropagation();
|
||||
var open = panel.classList.contains('is-open');
|
||||
closeMenus(open ? null : key);
|
||||
panel.classList.toggle('is-open', !open);
|
||||
t.setAttribute('aria-expanded', String(!open));
|
||||
});
|
||||
});
|
||||
// keep panel open when interacting inside it
|
||||
bar.querySelectorAll('[data-wg-panel]').forEach(function(p){
|
||||
p.addEventListener('click', function(e){ e.stopPropagation(); });
|
||||
});
|
||||
|
||||
// ---- inline search ----
|
||||
var searchBtn = bar.querySelector('[data-wg-search]');
|
||||
var searchInput = bar.querySelector('[data-wg-search-input]');
|
||||
if(searchBtn){
|
||||
searchBtn.addEventListener('click', function(e){
|
||||
e.stopPropagation();
|
||||
bar.classList.add('is-searching');
|
||||
closeMenus(null);
|
||||
if(searchInput) setTimeout(function(){ searchInput.focus(); }, 60);
|
||||
});
|
||||
}
|
||||
function closeSearch(){
|
||||
if(searchInput && searchInput.value.trim() !== '') return; // keep if user typed
|
||||
bar.classList.remove('is-searching');
|
||||
}
|
||||
|
||||
// ---- mobile burger + drawer ----
|
||||
var burger = bar.querySelector('[data-wg-burger]');
|
||||
if(burger){
|
||||
burger.addEventListener('click', function(e){
|
||||
e.stopPropagation();
|
||||
var open = bar.classList.toggle('is-drawer');
|
||||
burger.setAttribute('aria-expanded', String(open));
|
||||
});
|
||||
}
|
||||
// drawer Forums accordion
|
||||
var drawerSub = bar.querySelector('[data-wg-drawer-sub]');
|
||||
if(drawerSub){
|
||||
drawerSub.addEventListener('click', function(){
|
||||
var open = drawerSub.getAttribute('aria-expanded') === 'true';
|
||||
drawerSub.setAttribute('aria-expanded', String(!open));
|
||||
});
|
||||
}
|
||||
|
||||
// ---- presence / status selector (drives the avatar ring; persists locally) ----
|
||||
var checkSvg = '<svg class="chk" width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M2.5 7l3 3 5-6.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||
function applyPresence(key){
|
||||
var row = bar.querySelector('[data-presence="' + key + '"]');
|
||||
if(!row) return;
|
||||
bar.setAttribute('data-presence', key); // switches --tb-presence ring colour
|
||||
bar.querySelectorAll('[data-presence]').forEach(function(x){
|
||||
x.classList.remove('is-active');
|
||||
var c = x.querySelector('.chk'); if(c) c.remove();
|
||||
});
|
||||
row.classList.add('is-active');
|
||||
if(!row.querySelector('.chk')) row.insertAdjacentHTML('beforeend', checkSvg);
|
||||
}
|
||||
bar.querySelectorAll('[data-presence]').forEach(function(p){
|
||||
p.addEventListener('click', function(e){
|
||||
e.stopPropagation();
|
||||
var key = p.getAttribute('data-presence');
|
||||
applyPresence(key);
|
||||
try { localStorage.setItem('wg-topbar-presence', key); } catch(_){}
|
||||
});
|
||||
});
|
||||
// restore last choice so it sticks across reloads (live: read NodeBB user status)
|
||||
try {
|
||||
var saved = localStorage.getItem('wg-topbar-presence');
|
||||
if(saved) applyPresence(saved);
|
||||
} catch(_){}
|
||||
|
||||
// ---- pop-out niceties (preview-only; live these are NodeBB component actions) ----
|
||||
// notification filter tabs (data-filter all/unread)
|
||||
bar.querySelectorAll('.wg-pop__tab').forEach(function(tab){
|
||||
tab.addEventListener('click', function(e){
|
||||
e.stopPropagation();
|
||||
var tabs = tab.parentElement.querySelectorAll('.wg-pop__tab');
|
||||
tabs.forEach(function(t){ t.classList.remove('is-active'); });
|
||||
tab.classList.add('is-active');
|
||||
var unreadOnly = tab.getAttribute('data-filter') === 'unread';
|
||||
var list = tab.closest('[data-wg-panel]').querySelector('[data-component="notifications/list"]');
|
||||
list.querySelectorAll('.wg-notif').forEach(function(row){
|
||||
var hide = unreadOnly && !row.classList.contains('unread');
|
||||
row.style.display = hide ? 'none' : '';
|
||||
var sep = row.nextElementSibling;
|
||||
if(sep && sep.classList.contains('wg-pop__sep')) sep.style.display = hide ? 'none' : '';
|
||||
});
|
||||
});
|
||||
});
|
||||
// mark-all-read: clear unread rows + dots + badge for that panel
|
||||
bar.querySelectorAll('.wg-pop__btn:not(.wg-pop__btn--primary)').forEach(function(btn){
|
||||
btn.addEventListener('click', function(e){
|
||||
e.stopPropagation();
|
||||
var panel = btn.closest('[data-wg-panel]');
|
||||
panel.querySelectorAll('.unread').forEach(function(r){ r.classList.remove('unread'); });
|
||||
panel.querySelectorAll('.wg-notif__dot').forEach(function(d){ d.remove(); });
|
||||
var key = panel.getAttribute('data-wg-panel');
|
||||
var badge = bar.querySelector('.wg-topbar__badge[data-count="'+key+'"]');
|
||||
if(badge) badge.style.display = 'none';
|
||||
var tabc = panel.querySelector('.wg-pop__tabcount'); if(tabc) tabc.textContent = '0';
|
||||
});
|
||||
});
|
||||
// draft delete: drop the row (+ its separator)
|
||||
bar.querySelectorAll('.wg-draft__del').forEach(function(del){
|
||||
del.addEventListener('click', function(e){
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
var row = del.closest('.wg-draft');
|
||||
var sep = row.nextElementSibling || row.previousElementSibling;
|
||||
if(sep && sep.classList && sep.classList.contains('wg-pop__sep')) sep.remove();
|
||||
row.remove();
|
||||
var list = bar.querySelector('[data-component="drafts/list"]');
|
||||
var badge = bar.querySelector('.wg-topbar__badge[data-count="drafts"]');
|
||||
var left = list.querySelectorAll('.wg-draft').length;
|
||||
if(badge) badge.textContent = left;
|
||||
if(left === 0){
|
||||
if(badge) badge.style.display = 'none';
|
||||
var empty = list.querySelector('.wg-pop__empty'); if(empty) empty.hidden = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---- outside click + Escape close everything ----
|
||||
document.addEventListener('click', function(){ closeMenus(null); closeSearch(); });
|
||||
document.addEventListener('keydown', function(e){
|
||||
if(e.key === 'Escape'){
|
||||
closeMenus(null);
|
||||
bar.classList.remove('is-searching');
|
||||
bar.classList.remove('is-drawer');
|
||||
if(burger) burger.setAttribute('aria-expanded','false');
|
||||
}
|
||||
});
|
||||
|
||||
// ---- PREVIEW-ONLY: member/guest switch ----
|
||||
var demo = document.querySelectorAll('[data-wg-demo]');
|
||||
function setState(state){
|
||||
bar.setAttribute('data-state', state);
|
||||
document.querySelectorAll('[data-wg-when]').forEach(function(el){
|
||||
var match = el.getAttribute('data-wg-when') === state;
|
||||
el.style.display = match ? 'flex' : 'none';
|
||||
});
|
||||
demo.forEach(function(b){ b.classList.toggle('on', b.getAttribute('data-wg-demo') === state); });
|
||||
closeMenus(null);
|
||||
}
|
||||
demo.forEach(function(b){
|
||||
b.addEventListener('click', function(){ setState(b.getAttribute('data-wg-demo')); });
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,223 @@
|
||||
/* ============================================================================
|
||||
SHADOWS OVER WESTGATE — top bar controller
|
||||
----------------------------------------------------------------------------
|
||||
Paste into ACP -> Settings -> Custom Content -> "Custom JavaScript".
|
||||
(In the live theme this can instead live in the theme's client-side bundle.)
|
||||
|
||||
WHY THIS FILE EXISTS
|
||||
- The two static pages (home.html / join-the-team.html) need NO JavaScript;
|
||||
they are pure HTML/CSS (the only inline bit is an <img onerror> fallback).
|
||||
- The TOP BAR needs this small controller for: Forums mega-menu, inline
|
||||
search, the mobile burger/drawer, and the drawer's Forums accordion.
|
||||
|
||||
NODEBB-SPECIFIC NOTES
|
||||
- NodeBB is a single-page app: it swaps #content on navigation instead of
|
||||
reloading. So we DON'T bind to specific nodes on DOMContentLoaded — we use
|
||||
event delegation on `document`, which keeps working across page changes and
|
||||
even if the header is re-rendered.
|
||||
- Inline <script> inside an HTML widget is sanitised/unreliable, which is the
|
||||
other reason the logic lives here rather than in the pasted markup.
|
||||
- The live notification / chat / user dropdowns come from Harmony and carry
|
||||
their OWN JavaScript. If you keep those, you do NOT need this script to open
|
||||
them — only the Forums menu, search, and mobile drawer below.
|
||||
- The preview-only member/guest switch from top-bar.html is intentionally
|
||||
omitted; in production the cluster is chosen server-side ({{{ if loggedIn }}}).
|
||||
========================================================================== */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function bar() { return document.querySelector('.wg-topbar'); }
|
||||
|
||||
function closeMenus(exceptKey) {
|
||||
var b = bar();
|
||||
if (!b) return;
|
||||
b.querySelectorAll('[data-wg-panel]').forEach(function (p) {
|
||||
if (p.getAttribute('data-wg-panel') !== exceptKey) p.classList.remove('is-open');
|
||||
});
|
||||
b.querySelectorAll('[data-wg-menu]').forEach(function (t) {
|
||||
if (t.getAttribute('data-wg-menu') !== exceptKey) t.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
var b = bar();
|
||||
if (!b) return;
|
||||
var input = b.querySelector('[data-wg-search-input]');
|
||||
if (input && input.value.trim() !== '') return; // keep open if the user typed
|
||||
b.classList.remove('is-searching');
|
||||
}
|
||||
|
||||
var CHECK_SVG = '<svg class="chk" width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M2.5 7l3 3 5-6.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||
function applyPresence(key) {
|
||||
var b = bar();
|
||||
if (!b) return;
|
||||
var row = b.querySelector('[data-presence="' + key + '"]');
|
||||
if (!row) return;
|
||||
b.setAttribute('data-presence', key); // switches the --tb-presence avatar ring
|
||||
b.querySelectorAll('[data-presence]').forEach(function (x) {
|
||||
x.classList.remove('is-active');
|
||||
var c = x.querySelector('.chk'); if (c) c.remove();
|
||||
});
|
||||
row.classList.add('is-active');
|
||||
if (!row.querySelector('.chk')) row.insertAdjacentHTML('beforeend', CHECK_SVG);
|
||||
}
|
||||
function restorePresence() {
|
||||
try {
|
||||
var saved = localStorage.getItem('wg-topbar-presence');
|
||||
if (saved) applyPresence(saved);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// ---- one delegated click handler for the whole bar ----
|
||||
document.addEventListener('click', function (e) {
|
||||
var b = bar();
|
||||
if (!b) return;
|
||||
|
||||
// dropdown trigger (Forums / and any future data-wg-menu)
|
||||
var trigger = e.target.closest && e.target.closest('[data-wg-menu]');
|
||||
if (trigger && b.contains(trigger)) {
|
||||
e.stopPropagation();
|
||||
var key = trigger.getAttribute('data-wg-menu');
|
||||
var panel = b.querySelector('[data-wg-panel="' + key + '"]');
|
||||
var open = panel && panel.classList.contains('is-open');
|
||||
closeMenus(open ? null : key);
|
||||
if (panel) panel.classList.toggle('is-open', !open);
|
||||
trigger.setAttribute('aria-expanded', String(!open));
|
||||
return;
|
||||
}
|
||||
|
||||
// clicks inside an open panel shouldn't close it
|
||||
if (e.target.closest && e.target.closest('[data-wg-panel]')) {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// search toggle
|
||||
var searchBtn = e.target.closest && e.target.closest('[data-wg-search]');
|
||||
if (searchBtn && b.contains(searchBtn)) {
|
||||
e.stopPropagation();
|
||||
b.classList.add('is-searching');
|
||||
closeMenus(null);
|
||||
var input = b.querySelector('[data-wg-search-input]');
|
||||
if (input) setTimeout(function () { input.focus(); }, 60);
|
||||
return;
|
||||
}
|
||||
|
||||
// mobile burger
|
||||
var burger = e.target.closest && e.target.closest('[data-wg-burger]');
|
||||
if (burger && b.contains(burger)) {
|
||||
e.stopPropagation();
|
||||
var drawerOpen = b.classList.toggle('is-drawer');
|
||||
burger.setAttribute('aria-expanded', String(drawerOpen));
|
||||
return;
|
||||
}
|
||||
|
||||
// drawer Forums accordion
|
||||
var sub = e.target.closest && e.target.closest('[data-wg-drawer-sub]');
|
||||
if (sub && b.contains(sub)) {
|
||||
var subOpen = sub.getAttribute('aria-expanded') === 'true';
|
||||
sub.setAttribute('aria-expanded', String(!subOpen));
|
||||
return;
|
||||
}
|
||||
|
||||
// presence / status selector (drives the avatar ring). LIVE: also POST the
|
||||
// new status to NodeBB (e.g. socket.emit('user.setStatus', ...)) so it
|
||||
// persists server-side; here it persists in localStorage so it sticks.
|
||||
var presence = e.target.closest && e.target.closest('[data-presence]');
|
||||
if (presence && b.contains(presence)) {
|
||||
e.stopPropagation();
|
||||
var key = presence.getAttribute('data-presence');
|
||||
applyPresence(key);
|
||||
try { localStorage.setItem('wg-topbar-presence', key); } catch (_) {}
|
||||
return;
|
||||
}
|
||||
|
||||
// ----- pop-out actions -----
|
||||
// NOTE: in the LIVE theme the pop-outs ARE Harmony's partials
|
||||
// (drafts.tpl / notifications.tpl / chats.tpl), and these actions are
|
||||
// handled by NodeBB core JS (component="notifications/list", drafts/delete,
|
||||
// chats/mark-all-read, etc). The handlers below only drive THIS preview's
|
||||
// static rows — drop them when wiring the real includes.
|
||||
|
||||
// notification filter tabs
|
||||
var tab = e.target.closest && e.target.closest('.wg-pop__tab');
|
||||
if (tab && b.contains(tab)) {
|
||||
e.stopPropagation();
|
||||
tab.parentElement.querySelectorAll('.wg-pop__tab').forEach(function (t) { t.classList.remove('is-active'); });
|
||||
tab.classList.add('is-active');
|
||||
var unreadOnly = tab.getAttribute('data-filter') === 'unread';
|
||||
var nlist = tab.closest('[data-wg-panel]').querySelector('[data-component="notifications/list"]');
|
||||
nlist.querySelectorAll('.wg-notif').forEach(function (row) {
|
||||
var hide = unreadOnly && !row.classList.contains('unread');
|
||||
row.style.display = hide ? 'none' : '';
|
||||
var sep = row.nextElementSibling;
|
||||
if (sep && sep.classList.contains('wg-pop__sep')) sep.style.display = hide ? 'none' : '';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// mark-all-read
|
||||
var markBtn = e.target.closest && e.target.closest('.wg-pop__btn:not(.wg-pop__btn--primary)');
|
||||
if (markBtn && b.contains(markBtn)) {
|
||||
e.stopPropagation();
|
||||
var mPanel = markBtn.closest('[data-wg-panel]');
|
||||
mPanel.querySelectorAll('.unread').forEach(function (r) { r.classList.remove('unread'); });
|
||||
mPanel.querySelectorAll('.wg-notif__dot').forEach(function (d) { d.remove(); });
|
||||
var mKey = mPanel.getAttribute('data-wg-panel');
|
||||
var mBadge = b.querySelector('.wg-topbar__badge[data-count="' + mKey + '"]');
|
||||
if (mBadge) mBadge.style.display = 'none';
|
||||
var tc = mPanel.querySelector('.wg-pop__tabcount'); if (tc) tc.textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
// draft delete
|
||||
var del = e.target.closest && e.target.closest('.wg-draft__del');
|
||||
if (del && b.contains(del)) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
var dRow = del.closest('.wg-draft');
|
||||
var dSep = dRow.nextElementSibling || dRow.previousElementSibling;
|
||||
if (dSep && dSep.classList && dSep.classList.contains('wg-pop__sep')) dSep.remove();
|
||||
dRow.remove();
|
||||
var dList = b.querySelector('[data-component="drafts/list"]');
|
||||
var dBadge = b.querySelector('.wg-topbar__badge[data-count="drafts"]');
|
||||
var left = dList.querySelectorAll('.wg-draft').length;
|
||||
if (dBadge) dBadge.textContent = left;
|
||||
if (left === 0) {
|
||||
if (dBadge) dBadge.style.display = 'none';
|
||||
var empty = dList.querySelector('.wg-pop__empty'); if (empty) empty.hidden = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// outside click: close everything that floats
|
||||
closeMenus(null);
|
||||
closeSearch();
|
||||
});
|
||||
|
||||
// ---- Escape closes everything ----
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape') return;
|
||||
var b = bar();
|
||||
if (!b) return;
|
||||
closeMenus(null);
|
||||
b.classList.remove('is-searching');
|
||||
b.classList.remove('is-drawer');
|
||||
var burger = b.querySelector('[data-wg-burger]');
|
||||
if (burger) burger.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
|
||||
// ---- close the mobile drawer after an in-app navigation ----
|
||||
if (window.$ && typeof $.fn === 'object') {
|
||||
$(window).on('action:ajaxify.end', function () {
|
||||
var b = bar();
|
||||
if (!b) return;
|
||||
b.classList.remove('is-drawer', 'is-searching');
|
||||
closeMenus(null);
|
||||
restorePresence();
|
||||
});
|
||||
}
|
||||
|
||||
// ---- restore the saved status on first load ----
|
||||
if (document.readyState !== 'loading') restorePresence();
|
||||
else document.addEventListener('DOMContentLoaded', restorePresence);
|
||||
})();
|
||||
Reference in New Issue
Block a user