diff --git a/README.md b/README.md index 89be6f0..1935027 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The design reference is [BACKEND_DESIGN.md](https://gitea.whitlocktech.com/Runic ## Project structure ``` -UOMSITE/ +website/ ├─ server/ Express API │ ├─ src/ │ │ ├─ server.js bootstrap: ensure schema → seed → listen (0.0.0.0) diff --git a/client/public/assets/img/favicon.ico b/client/public/assets/img/favicon.ico new file mode 100644 index 0000000..77b502d Binary files /dev/null and b/client/public/assets/img/favicon.ico differ diff --git a/client/public/assets/img/runic-emblem.png b/client/public/assets/img/runic-emblem.png new file mode 100644 index 0000000..eaeb042 Binary files /dev/null and b/client/public/assets/img/runic-emblem.png differ diff --git a/client/src/components/SiteFooter.jsx b/client/src/components/SiteFooter.jsx index 526fd8e..a3978de 100644 --- a/client/src/components/SiteFooter.jsx +++ b/client/src/components/SiteFooter.jsx @@ -5,31 +5,40 @@ export default function SiteFooter() { const { contactEmail, siteTitle } = useSite() return ( ) diff --git a/client/src/contexts/SiteContext.jsx b/client/src/contexts/SiteContext.jsx index af4435a..781d97f 100644 --- a/client/src/contexts/SiteContext.jsx +++ b/client/src/contexts/SiteContext.jsx @@ -40,7 +40,7 @@ export function SiteProvider({ children }) { siteTitle: brand.name || settings.site_title || 'Runic Gateway', siteShortName: brand.shortName || brand.name || settings.site_title || 'Runic Gateway', contactEmail: brand.contactEmail || settings.contact_email || '', - heroImage: brand.hero || '/assets/img/hero-moon.png', + heroImage: brand.hero || '/assets/img/runic-emblem.png', } return {children} diff --git a/client/src/lib/heroLayout.js b/client/src/lib/heroLayout.js index 368f7f8..b89f7cb 100644 --- a/client/src/lib/heroLayout.js +++ b/client/src/lib/heroLayout.js @@ -1,19 +1,27 @@ // Shared hero-layout helpers used by the public portal and the admin editor. -// Neutral built-in default; the instance hero image (BRAND_HERO) overrides it at -// runtime, threaded in as `defaultImage` by the portal. -export const DEFAULT_HERO_IMAGE = '/assets/img/hero-moon.png' +// Runic Gateway default hero emblem; the instance hero image (BRAND_HERO) +// overrides it at runtime, threaded in as `defaultImage` by the portal. +export const DEFAULT_HERO_IMAGE = '/assets/img/runic-emblem.png' -// The original hand-tuned multi-gradient hero background over a given image -// (used for the untouched default so the live page stays consistent until edited). +// Default hero background: the emblem centered behind the text as a medallion, +// under a symmetric dark overlay tuned to keep centered hero copy legible. +// Two layers (overlay gradient + image) so the per-layer background-size in +// `heroBackground` can contain the square emblem while the overlay stays full-bleed. export function heroBgStack(image) { return ( - "linear-gradient(90deg,rgba(11,15,20,0.34) 0%,rgba(11,15,20,0.5) 36%,rgba(11,15,20,0.78) 62%,rgba(11,15,20,0.66) 100%),linear-gradient(180deg,rgba(11,15,20,0.08) 0%,rgba(11,15,20,0.72) 100%),url('" + + 'linear-gradient(180deg,rgba(11,15,20,0.62) 0%,rgba(11,15,20,0.48) 38%,rgba(11,15,20,0.52) 58%,rgba(11,15,20,0.86) 100%),' + + "url('" + (image || DEFAULT_HERO_IMAGE) + "')" ) } +// Keep the emblem fully visible and centered, capped so it never overflows a +// narrow viewport; the overlay layer covers. +export const HERO_DEFAULT_SIZE = 'cover, min(74vh, 640px, 86vw)' +export const HERO_DEFAULT_POSITION = 'center, center' + export const HERO_BG = heroBgStack(DEFAULT_HERO_IMAGE) // Single-stop dark overlay driven by the editor's opacity slider. @@ -26,13 +34,20 @@ export function buildOverlay(opacity) { export function heroBackground(layout, { isDefault = false, defaultImage } = {}) { const bg = layout.background || {} const fallback = defaultImage || DEFAULT_HERO_IMAGE - const backgroundImage = - isDefault && !bg.image_url - ? heroBgStack(fallback) - : `${buildOverlay(layout.overlay?.opacity ?? 0.72)}, url('${bg.image_url || fallback}')` + // Untouched default: emblem contained + centered behind the text (per-layer + // size/position so the overlay stays full-bleed while the square emblem fits). + if (isDefault && !bg.image_url) { + return { + backgroundColor: 'var(--bg-deep)', + backgroundImage: heroBgStack(fallback), + backgroundPosition: HERO_DEFAULT_POSITION, + backgroundRepeat: 'no-repeat', + backgroundSize: HERO_DEFAULT_SIZE, + } + } return { backgroundColor: 'var(--bg-deep)', - backgroundImage, + backgroundImage: `${buildOverlay(layout.overlay?.opacity ?? 0.72)}, url('${bg.image_url || fallback}')`, backgroundPosition: `${bg.position_x || 'left'} ${bg.position_y || 'center'}`, backgroundRepeat: 'no-repeat', backgroundSize: bg.size || 'cover', diff --git a/client/src/routes/admin/AdminLogin.jsx b/client/src/routes/admin/AdminLogin.jsx index 758cfdf..5c4ba04 100644 --- a/client/src/routes/admin/AdminLogin.jsx +++ b/client/src/routes/admin/AdminLogin.jsx @@ -151,7 +151,8 @@ export default function AdminLogin() { backgroundColor: 'var(--bg-deep)', backgroundImage: BG, backgroundPosition: 'center', - backgroundSize: 'cover', + backgroundRepeat: 'no-repeat', + backgroundSize: 'min(60vh, 520px)', }} >
diff --git a/client/src/routes/player/PlayerShell.jsx b/client/src/routes/player/PlayerShell.jsx index b2d4a2e..b444054 100644 --- a/client/src/routes/player/PlayerShell.jsx +++ b/client/src/routes/player/PlayerShell.jsx @@ -18,7 +18,8 @@ export default function PlayerShell({ subtitle, children, footer }) { backgroundColor: 'var(--bg-deep)', backgroundImage: bg, backgroundPosition: 'center', - backgroundSize: 'cover', + backgroundRepeat: 'no-repeat', + backgroundSize: 'min(60vh, 520px)', }} >
diff --git a/client/src/routes/public/Maintenance.jsx b/client/src/routes/public/Maintenance.jsx index 3ca2742..b6b0b5a 100644 --- a/client/src/routes/public/Maintenance.jsx +++ b/client/src/routes/public/Maintenance.jsx @@ -23,7 +23,7 @@ export default function Maintenance() { backgroundImage: heroBg, backgroundPosition: 'center', backgroundRepeat: 'no-repeat', - backgroundSize: 'cover', + backgroundSize: 'min(60vh, 520px)', }} >
diff --git a/client/src/styles/theme.css b/client/src/styles/theme.css index eae23ab..bd51801 100644 --- a/client/src/styles/theme.css +++ b/client/src/styles/theme.css @@ -1009,6 +1009,67 @@ button[disabled] { border-color: #8a4b47; } +/* Site footer: powered-by badge left-justified, info centered. */ +.site-footer-inner { + position: relative; + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + max-width: 1100px; + margin: 0 auto; +} +.site-footer-badge { + display: flex; + align-items: center; + gap: 10px; + flex: 0 0 auto; +} +.site-footer-badge img { + height: 48px; + width: 48px; + object-fit: contain; + filter: drop-shadow(0 0 6px rgba(127, 153, 189, 0.35)); +} +.site-footer-badge span { + font-size: 0.72rem; + line-height: 1.25; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--dim); + text-align: left; +} +.site-footer-badge strong { + color: var(--muted); + font-weight: 700; + letter-spacing: 0.04em; +} +.site-footer-info { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + text-align: center; +} +/* Pin the badge to the left while the info column stays visually centered. */ +@media (min-width: 641px) { + .site-footer-badge { + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + } +} +@media (max-width: 640px) { + .site-footer-inner { + flex-direction: column; + } + .site-footer-badge img { + height: 40px; + width: 40px; + } +} + /* Draft-preview banner on the public renderer. */ .page-preview-banner { border: 1px solid var(--accent); diff --git a/server/src/config/brand.js b/server/src/config/brand.js index 9ee7a28..a5be124 100644 --- a/server/src/config/brand.js +++ b/server/src/config/brand.js @@ -31,8 +31,8 @@ const brand = { // Visual accent: process.env.BRAND_ACCENT_COLOR || '#7f99bd', logo: process.env.BRAND_LOGO || '', // empty → no logo image rendered - hero: process.env.BRAND_HERO || '/assets/img/hero-moon.png', - favicon: process.env.BRAND_FAVICON || '', // empty → no favicon link injected + hero: process.env.BRAND_HERO || '/assets/img/runic-emblem.png', + favicon: process.env.BRAND_FAVICON || '/assets/img/favicon.ico', // Runic Gateway default emblem } // Discord embeds want an int (0xRRGGBB). Parse the accent hex once; fall back to