Added references
This commit is contained in:
@@ -25,15 +25,11 @@ Use this palette direction:
|
|||||||
- Avoid bright, playful, clinical, or flat SaaS-style treatment.
|
- Avoid bright, playful, clinical, or flat SaaS-style treatment.
|
||||||
- Favor depth, restraint, texture, and rich contrast over loud ornament.
|
- Favor depth, restraint, texture, and rich contrast over loud ornament.
|
||||||
|
|
||||||
Reference prior theme work in this repo through files prefixed with `____`:
|
Reference prior theme work in this repo through files in the `_references` directory:
|
||||||
|
|
||||||
- `____live-copy.css`
|
`ref_palettes.css` is the clearest reference for the intended slick gradient treatment. Use that direction for foreground elements such as panels, topic rows, buttons, borders, accents, and active states. Keep gradients rich and polished, but avoid turning the entire page into a decorative gradient background.
|
||||||
- `____wikijs-live-copy.scss`
|
|
||||||
- `____bootstrap-overrides.scss`
|
|
||||||
|
|
||||||
`____live-copy.css` is the clearest reference for the intended slick gradient treatment. Use that direction for foreground elements such as panels, topic rows, buttons, borders, accents, and active states. Keep gradients rich and polished, but avoid turning the entire page into a decorative gradient background.
|
When adding new visuals, study the reference files first and match the existing direction before introducing new motifs.
|
||||||
|
|
||||||
When adding new visuals, study the `____` reference files first and match the existing direction before introducing new motifs.
|
|
||||||
|
|
||||||
## NodeBB Theme Rules
|
## NodeBB Theme Rules
|
||||||
|
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
// =========================================================
|
|
||||||
// WESTGATE BOOTSTRAP VARIABLE OVERRIDES FOR NODEBB
|
|
||||||
// Put this before Bootstrap/NodeBB imports that consume variables
|
|
||||||
// =========================================================
|
|
||||||
|
|
||||||
// --- Core palette
|
|
||||||
$white: #f5efe4;
|
|
||||||
$black: #08070a;
|
|
||||||
|
|
||||||
$gray-100: #f0ebe2;
|
|
||||||
$gray-200: #ddd5c8;
|
|
||||||
$gray-300: #c7bcad;
|
|
||||||
$gray-400: #a99d8e;
|
|
||||||
$gray-500: #8b8074;
|
|
||||||
$gray-600: #6f665d;
|
|
||||||
$gray-700: #4d4746;
|
|
||||||
$gray-800: #1a161d;
|
|
||||||
$gray-900: #0b0a0d;
|
|
||||||
|
|
||||||
// Accent / theme hues
|
|
||||||
$blue: #7cafc2; // muted lore blue
|
|
||||||
$indigo: #6c5a8e;
|
|
||||||
$purple: #7b4a6f;
|
|
||||||
$pink: #a06179;
|
|
||||||
$red: #9c4b4b;
|
|
||||||
$orange: #b57a45;
|
|
||||||
$yellow: #c2a35a; // signature Westgate gold
|
|
||||||
$green: #6f8c5b;
|
|
||||||
$teal: #5d8c83;
|
|
||||||
$cyan: #6f9eae;
|
|
||||||
|
|
||||||
// --- Semantic theme colors
|
|
||||||
$primary: #c2a35a;
|
|
||||||
$secondary: #1a161d;
|
|
||||||
$success: #6f8c5b;
|
|
||||||
$info: #6f9eae;
|
|
||||||
$warning: #c2a35a;
|
|
||||||
$danger: #9c4b4b;
|
|
||||||
$light: #ddd5c8;
|
|
||||||
$dark: #0b0a0d;
|
|
||||||
|
|
||||||
// --- Body / text / links
|
|
||||||
$body-bg: #0b0a0d;
|
|
||||||
$body-color: #e6e0d6;
|
|
||||||
|
|
||||||
$link-color: #d8c28a;
|
|
||||||
$link-hover-color: #e0c878;
|
|
||||||
|
|
||||||
$emphasis-color: #f0e8dc;
|
|
||||||
$secondary-color: #b9b2a6;
|
|
||||||
$tertiary-color: #9a9086;
|
|
||||||
|
|
||||||
$headings-color: #efe7da;
|
|
||||||
|
|
||||||
// --- Borders / surfaces
|
|
||||||
$border-color: #2a252d;
|
|
||||||
$border-color-translucent: rgba(194, 163, 90, 0.12);
|
|
||||||
|
|
||||||
$card-bg: #17151a;
|
|
||||||
$card-cap-bg: #141219;
|
|
||||||
$card-border-color: rgba(194, 163, 90, 0.12);
|
|
||||||
|
|
||||||
$dropdown-bg: #141219;
|
|
||||||
$dropdown-border-color: rgba(194, 163, 90, 0.14);
|
|
||||||
$dropdown-link-color: #b9b2a6;
|
|
||||||
$dropdown-link-hover-color: #efe7da;
|
|
||||||
$dropdown-link-hover-bg: #241e26;
|
|
||||||
$dropdown-link-active-color: #fff4dd;
|
|
||||||
$dropdown-link-active-bg: #2a2215;
|
|
||||||
$dropdown-header-color: #9a9086;
|
|
||||||
|
|
||||||
$modal-content-bg: #141219;
|
|
||||||
$modal-content-border-color: rgba(194, 163, 90, 0.14);
|
|
||||||
$popover-bg: #141219;
|
|
||||||
$popover-border-color: rgba(194, 163, 90, 0.14);
|
|
||||||
$tooltip-bg: #221c24;
|
|
||||||
$tooltip-color: #e6e0d6;
|
|
||||||
|
|
||||||
// --- Inputs / forms
|
|
||||||
$input-bg: #110f15;
|
|
||||||
$input-color: #e6e0d6;
|
|
||||||
$input-border-color: #2a252d;
|
|
||||||
$input-placeholder-color: #8f877d;
|
|
||||||
$input-focus-bg: #151219;
|
|
||||||
$input-focus-color: #f0e8dc;
|
|
||||||
$input-focus-border-color: rgba(194, 163, 90, 0.42);
|
|
||||||
$input-group-addon-bg: #1a161d;
|
|
||||||
$input-group-addon-border-color: #2a252d;
|
|
||||||
|
|
||||||
$form-check-input-bg: #151219;
|
|
||||||
$form-check-input-border: 1px solid rgba(194, 163, 90, 0.35);
|
|
||||||
$form-check-input-checked-bg-color: #a8893f;
|
|
||||||
$form-check-input-checked-border-color: #c2a35a;
|
|
||||||
|
|
||||||
// --- Navs / tabs / pills
|
|
||||||
$nav-link-color: #b9b2a6;
|
|
||||||
$nav-link-hover-color: #e6e0d6;
|
|
||||||
$nav-tabs-border-color: #2a252d;
|
|
||||||
$nav-tabs-link-hover-border-color: rgba(194, 163, 90, 0.18);
|
|
||||||
$nav-tabs-link-active-color: #e0c878;
|
|
||||||
$nav-tabs-link-active-bg: #141219;
|
|
||||||
$nav-tabs-link-active-border-color: rgba(194, 163, 90, 0.24);
|
|
||||||
|
|
||||||
$nav-pills-link-active-color: #fff4dd;
|
|
||||||
$nav-pills-link-active-bg: #473914;
|
|
||||||
|
|
||||||
// --- Buttons
|
|
||||||
$btn-color: #b9b2a6;
|
|
||||||
$btn-border-radius: 0.45rem;
|
|
||||||
$btn-focus-box-shadow: 0 0 0 0.2rem rgba(194, 163, 90, 0.16);
|
|
||||||
|
|
||||||
$btn-close-color: #e6e0d6;
|
|
||||||
$btn-close-opacity: 0.75;
|
|
||||||
$btn-close-hover-opacity: 1;
|
|
||||||
|
|
||||||
// --- List groups / tables
|
|
||||||
$list-group-bg: #141219;
|
|
||||||
$list-group-border-color: rgba(194, 163, 90, 0.1);
|
|
||||||
$list-group-color: #b9b2a6;
|
|
||||||
$list-group-hover-bg: #241e26;
|
|
||||||
$list-group-action-hover-color: #efe7da;
|
|
||||||
$list-group-action-active-bg: #2a2215;
|
|
||||||
$list-group-action-active-color: #e0c878;
|
|
||||||
|
|
||||||
$table-color: #b9b2a6;
|
|
||||||
$table-bg: transparent;
|
|
||||||
$table-border-color: rgba(194, 163, 90, 0.12);
|
|
||||||
$table-striped-bg: rgba(255, 255, 255, 0.012);
|
|
||||||
$table-hover-bg: rgba(194, 163, 90, 0.05);
|
|
||||||
$table-hover-color: #efe7da;
|
|
||||||
|
|
||||||
// --- Alerts using your lore palette instead of stock Bootstrap tones
|
|
||||||
$primary-text-emphasis: #f0e6d8;
|
|
||||||
$secondary-text-emphasis: #c6beaf;
|
|
||||||
$success-text-emphasis: #d6e2ce;
|
|
||||||
$info-text-emphasis: #d2e4ea;
|
|
||||||
$warning-text-emphasis: #f3e3b2;
|
|
||||||
$danger-text-emphasis: #e3c2c2;
|
|
||||||
$light-text-emphasis: #4d4746;
|
|
||||||
$dark-text-emphasis: #cfc7ba;
|
|
||||||
|
|
||||||
$primary-bg-subtle: rgba(194, 163, 90, 0.14);
|
|
||||||
$secondary-bg-subtle: rgba(26, 22, 29, 0.92);
|
|
||||||
$success-bg-subtle: rgba(111, 140, 91, 0.14);
|
|
||||||
$info-bg-subtle: rgba(111, 158, 174, 0.14);
|
|
||||||
$warning-bg-subtle: rgba(194, 163, 90, 0.14);
|
|
||||||
$danger-bg-subtle: rgba(156, 75, 75, 0.14);
|
|
||||||
$light-bg-subtle: #1a161d;
|
|
||||||
$dark-bg-subtle: #0b0a0d;
|
|
||||||
|
|
||||||
$primary-border-subtle: rgba(194, 163, 90, 0.28);
|
|
||||||
$secondary-border-subtle: rgba(194, 163, 90, 0.1);
|
|
||||||
$success-border-subtle: rgba(111, 140, 91, 0.28);
|
|
||||||
$info-border-subtle: rgba(111, 158, 174, 0.28);
|
|
||||||
$warning-border-subtle: rgba(194, 163, 90, 0.28);
|
|
||||||
$danger-border-subtle: rgba(156, 75, 75, 0.28);
|
|
||||||
$light-border-subtle: rgba(194, 163, 90, 0.12);
|
|
||||||
$dark-border-subtle: rgba(194, 163, 90, 0.12);
|
|
||||||
|
|
||||||
// --- Optional: make Bootstrap's theme map explicit for NodeBB components
|
|
||||||
$theme-colors: (
|
|
||||||
"primary": $primary,
|
|
||||||
"secondary": $secondary,
|
|
||||||
"success": $success,
|
|
||||||
"info": $info,
|
|
||||||
"warning": $warning,
|
|
||||||
"danger": $danger,
|
|
||||||
"light": $light,
|
|
||||||
"dark": $dark
|
|
||||||
);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
|||||||
|
.wg-latest-strip {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 1.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-card {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 1rem 1.1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
background:
|
||||||
|
linear-gradient(100deg, rgba(32, 19, 30, 0.96), rgba(13, 12, 17, 0.98)),
|
||||||
|
rgba(13, 12, 17, 0.98);
|
||||||
|
border: 1px solid rgba(194, 163, 90, 0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 244, 221, 0.04),
|
||||||
|
0 14px 30px rgba(0, 0, 0, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-card::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 auto 0 0;
|
||||||
|
width: 3px;
|
||||||
|
background: rgba(194, 163, 90, 0.58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-card--patch::before {
|
||||||
|
background: rgba(143, 178, 119, 0.58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-kicker {
|
||||||
|
margin-bottom: 0.45rem;
|
||||||
|
color: #c2a35a;
|
||||||
|
font-family: var(--wg-font-display, Georgia, serif);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-title {
|
||||||
|
display: block;
|
||||||
|
color: var(--wg-text, #e6e0d6);
|
||||||
|
font-family: var(--wg-font-display, Georgia, serif);
|
||||||
|
font-size: 1.08rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-title:hover,
|
||||||
|
.wg-latest-title:focus {
|
||||||
|
color: #fff4dd;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-meta {
|
||||||
|
min-height: 1.15rem;
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
color: rgba(185, 178, 166, 0.78);
|
||||||
|
font-family: var(--wg-font-ui, system-ui, sans-serif);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-excerpt {
|
||||||
|
display: -webkit-box;
|
||||||
|
margin: 0.55rem 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: rgba(216, 208, 196, 0.84);
|
||||||
|
font-family: var(--wg-font-text, Georgia, serif);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-card.is-loading .wg-latest-title,
|
||||||
|
.wg-latest-card.is-empty .wg-latest-title {
|
||||||
|
color: rgba(185, 178, 166, 0.74);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.wg-latest-strip {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-latest-card {
|
||||||
|
padding: 0.9rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
(() => {
|
||||||
|
const WESTGATE_LATEST_CONFIG = [
|
||||||
|
{
|
||||||
|
key: 'announcement',
|
||||||
|
cid: 1,
|
||||||
|
emptyTitle: 'No announcements yet',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'patch',
|
||||||
|
cid: 10,
|
||||||
|
emptyTitle: 'No patch notes yet',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getRelativePath = () => {
|
||||||
|
return window.config && window.config.relative_path ? window.config.relative_path : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const unwrap = (payload) => {
|
||||||
|
return payload && payload.response ? payload.response : payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
const textFromHtml = (html) => {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.innerHTML = html || '';
|
||||||
|
|
||||||
|
const firstParagraph = Array.from(el.querySelectorAll('p'))
|
||||||
|
.map((p) => (p.textContent || '').replace(/\s+/g, ' ').trim())
|
||||||
|
.find(Boolean);
|
||||||
|
|
||||||
|
return (firstParagraph || el.textContent || '').replace(/\s+/g, ' ').trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
const truncate = (text, limit = 240) => {
|
||||||
|
if (!text || text.length <= limit) {
|
||||||
|
return text || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${text.slice(0, limit).replace(/\s+\S*$/, '')}...`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (value) => {
|
||||||
|
const timestamp = Number(value);
|
||||||
|
if (!timestamp) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Intl.DateTimeFormat(undefined, {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
}).format(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getJSON = async (url) => {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
accept: 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Request failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return unwrap(await response.json());
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopicExcerpt = async (topic) => {
|
||||||
|
const relativePath = getRelativePath();
|
||||||
|
const topicPath = topic.slug || topic.tid;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const topicData = await getJSON(`${relativePath}/api/topic/${topicPath}?page=1&sort=oldest_to_newest`);
|
||||||
|
const firstPost = topicData.posts && topicData.posts[0];
|
||||||
|
const firstPostText = textFromHtml(firstPost && firstPost.content);
|
||||||
|
|
||||||
|
if (firstPostText) {
|
||||||
|
return truncate(firstPostText);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[Westgate latest widget original post excerpt]', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topic.mainPost && topic.mainPost.content) {
|
||||||
|
const mainPostText = textFromHtml(topic.mainPost.content);
|
||||||
|
if (mainPostText) {
|
||||||
|
return truncate(mainPostText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topic.teaser && topic.teaser.content) {
|
||||||
|
return truncate(textFromHtml(topic.teaser.content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLatestTopic = async (cid) => {
|
||||||
|
const relativePath = getRelativePath();
|
||||||
|
const category = await getJSON(`${relativePath}/api/v3/categories/${cid}/topics?sort=recently_created&after=0`);
|
||||||
|
const topic = category.topics && category.topics[0];
|
||||||
|
|
||||||
|
if (!topic) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let excerpt = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
excerpt = await getTopicExcerpt(topic);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[Westgate latest widget excerpt]', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: topic.titleRaw || topic.title || 'Untitled',
|
||||||
|
url: `${relativePath}/topic/${topic.slug || topic.tid}`,
|
||||||
|
author: topic.user && (topic.user.displayname || topic.user.username),
|
||||||
|
date: formatDate(topic.timestamp || topic.lastposttime),
|
||||||
|
excerpt,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCard = async (root, source) => {
|
||||||
|
const card = root.querySelector(`[data-card="${source.key}"]`);
|
||||||
|
if (!card) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = card.querySelector('.wg-latest-title');
|
||||||
|
const meta = card.querySelector('.wg-latest-meta');
|
||||||
|
const excerpt = card.querySelector('.wg-latest-excerpt');
|
||||||
|
|
||||||
|
card.classList.add('is-loading');
|
||||||
|
card.classList.remove('is-empty');
|
||||||
|
title.textContent = 'Loading...';
|
||||||
|
title.removeAttribute('href');
|
||||||
|
meta.textContent = '';
|
||||||
|
excerpt.textContent = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const topic = await getLatestTopic(source.cid);
|
||||||
|
|
||||||
|
if (!topic) {
|
||||||
|
card.classList.remove('is-loading');
|
||||||
|
card.classList.add('is-empty');
|
||||||
|
title.textContent = source.emptyTitle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
title.textContent = topic.title;
|
||||||
|
title.href = topic.url;
|
||||||
|
meta.textContent = [topic.author, topic.date].filter(Boolean).join(' · ');
|
||||||
|
excerpt.textContent = topic.excerpt || '';
|
||||||
|
card.classList.remove('is-loading', 'is-empty');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[Westgate latest widget]', error);
|
||||||
|
card.classList.remove('is-loading');
|
||||||
|
card.classList.add('is-empty');
|
||||||
|
title.textContent = source.emptyTitle;
|
||||||
|
meta.textContent = 'Unable to load right now';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderLatestStrip = () => {
|
||||||
|
const root = document.querySelector('[data-westgate-latest]');
|
||||||
|
|
||||||
|
if (!root || root.dataset.loaded === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.dataset.loaded = '1';
|
||||||
|
WESTGATE_LATEST_CONFIG.forEach((source) => renderCard(root, source));
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', renderLatestStrip);
|
||||||
|
window.addEventListener('load', renderLatestStrip);
|
||||||
|
|
||||||
|
if (window.jQuery) {
|
||||||
|
window.jQuery(window).on('action:ajaxify.end', renderLatestStrip);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLatestStrip();
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user