Merge pull request 'feat(brand): default emblem — favicon, hero medallion, footer credit' (#69) from feature/branding into main
Reviewed-on: #69 Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
This commit is contained in:
@@ -53,7 +53,7 @@ The design reference is [BACKEND_DESIGN.md](https://gitea.whitlocktech.com/Runic
|
|||||||
## Project structure
|
## Project structure
|
||||||
|
|
||||||
```
|
```
|
||||||
UOMSITE/
|
website/
|
||||||
├─ server/ Express API
|
├─ server/ Express API
|
||||||
│ ├─ src/
|
│ ├─ src/
|
||||||
│ │ ├─ server.js bootstrap: ensure schema → seed → listen (0.0.0.0)
|
│ │ ├─ server.js bootstrap: ensure schema → seed → listen (0.0.0.0)
|
||||||
|
|||||||
BIN
client/public/assets/img/favicon.ico
Normal file
BIN
client/public/assets/img/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
client/public/assets/img/runic-emblem.png
Normal file
BIN
client/public/assets/img/runic-emblem.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -5,31 +5,40 @@ export default function SiteFooter() {
|
|||||||
const { contactEmail, siteTitle } = useSite()
|
const { contactEmail, siteTitle } = useSite()
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
className="sans"
|
className="sans site-footer"
|
||||||
style={{
|
style={{
|
||||||
borderTop: '1px solid var(--line)',
|
borderTop: '1px solid var(--line)',
|
||||||
padding: '28px 16px',
|
padding: '28px 16px',
|
||||||
color: 'var(--muted)',
|
color: 'var(--muted)',
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
background: 'rgba(9,13,18,0.6)',
|
background: 'rgba(9,13,18,0.6)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
|
<div className="site-footer-inner">
|
||||||
<span>{siteTitle} is an independent private shard project.</span>
|
<div className="site-footer-badge">
|
||||||
<span style={{ color: 'var(--dim)', fontSize: '0.84rem' }}>
|
<img src="/assets/img/runic-emblem.png" alt="" aria-hidden="true" />
|
||||||
<a href={`mailto:${contactEmail}`} style={{ color: 'var(--accent)', textDecoration: 'none' }}>
|
<span>
|
||||||
{contactEmail}
|
Powered by
|
||||||
</a>
|
<br />
|
||||||
·
|
<strong>Runic Gateway</strong>
|
||||||
<Link to="/site/status" style={{ color: 'var(--accent)', textDecoration: 'none' }}>
|
</span>
|
||||||
Shard Status
|
</div>
|
||||||
</Link>
|
<div className="site-footer-info">
|
||||||
·
|
<span>{siteTitle} is an independent private shard project.</span>
|
||||||
<Link to="/admin/login" style={{ color: '#5d6b7d', textDecoration: 'none' }}>
|
<span style={{ color: 'var(--dim)', fontSize: '0.84rem' }}>
|
||||||
Admin
|
<a href={`mailto:${contactEmail}`} style={{ color: 'var(--accent)', textDecoration: 'none' }}>
|
||||||
</Link>
|
{contactEmail}
|
||||||
</span>
|
</a>
|
||||||
|
·
|
||||||
|
<Link to="/site/status" style={{ color: 'var(--accent)', textDecoration: 'none' }}>
|
||||||
|
Shard Status
|
||||||
|
</Link>
|
||||||
|
·
|
||||||
|
<Link to="/admin/login" style={{ color: '#5d6b7d', textDecoration: 'none' }}>
|
||||||
|
Admin
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function SiteProvider({ children }) {
|
|||||||
siteTitle: brand.name || settings.site_title || 'Runic Gateway',
|
siteTitle: brand.name || settings.site_title || 'Runic Gateway',
|
||||||
siteShortName: brand.shortName || brand.name || settings.site_title || 'Runic Gateway',
|
siteShortName: brand.shortName || brand.name || settings.site_title || 'Runic Gateway',
|
||||||
contactEmail: brand.contactEmail || settings.contact_email || '',
|
contactEmail: brand.contactEmail || settings.contact_email || '',
|
||||||
heroImage: brand.hero || '/assets/img/hero-moon.png',
|
heroImage: brand.hero || '/assets/img/runic-emblem.png',
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SiteContext.Provider value={value}>{children}</SiteContext.Provider>
|
return <SiteContext.Provider value={value}>{children}</SiteContext.Provider>
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
// Shared hero-layout helpers used by the public portal and the admin editor.
|
// 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
|
// Runic Gateway default hero emblem; the instance hero image (BRAND_HERO)
|
||||||
// runtime, threaded in as `defaultImage` by the portal.
|
// overrides it at runtime, threaded in as `defaultImage` by the portal.
|
||||||
export const DEFAULT_HERO_IMAGE = '/assets/img/hero-moon.png'
|
export const DEFAULT_HERO_IMAGE = '/assets/img/runic-emblem.png'
|
||||||
|
|
||||||
// The original hand-tuned multi-gradient hero background over a given image
|
// Default hero background: the emblem centered behind the text as a medallion,
|
||||||
// (used for the untouched default so the live page stays consistent until edited).
|
// 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) {
|
export function heroBgStack(image) {
|
||||||
return (
|
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) +
|
(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)
|
export const HERO_BG = heroBgStack(DEFAULT_HERO_IMAGE)
|
||||||
|
|
||||||
// Single-stop dark overlay driven by the editor's opacity slider.
|
// 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 } = {}) {
|
export function heroBackground(layout, { isDefault = false, defaultImage } = {}) {
|
||||||
const bg = layout.background || {}
|
const bg = layout.background || {}
|
||||||
const fallback = defaultImage || DEFAULT_HERO_IMAGE
|
const fallback = defaultImage || DEFAULT_HERO_IMAGE
|
||||||
const backgroundImage =
|
// Untouched default: emblem contained + centered behind the text (per-layer
|
||||||
isDefault && !bg.image_url
|
// size/position so the overlay stays full-bleed while the square emblem fits).
|
||||||
? heroBgStack(fallback)
|
if (isDefault && !bg.image_url) {
|
||||||
: `${buildOverlay(layout.overlay?.opacity ?? 0.72)}, url('${bg.image_url || fallback}')`
|
return {
|
||||||
|
backgroundColor: 'var(--bg-deep)',
|
||||||
|
backgroundImage: heroBgStack(fallback),
|
||||||
|
backgroundPosition: HERO_DEFAULT_POSITION,
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundSize: HERO_DEFAULT_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
backgroundColor: 'var(--bg-deep)',
|
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'}`,
|
backgroundPosition: `${bg.position_x || 'left'} ${bg.position_y || 'center'}`,
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundSize: bg.size || 'cover',
|
backgroundSize: bg.size || 'cover',
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ export default function AdminLogin() {
|
|||||||
backgroundColor: 'var(--bg-deep)',
|
backgroundColor: 'var(--bg-deep)',
|
||||||
backgroundImage: BG,
|
backgroundImage: BG,
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
backgroundSize: 'cover',
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundSize: 'min(60vh, 520px)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ width: '100%', maxWidth: 400 }}>
|
<div style={{ width: '100%', maxWidth: 400 }}>
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export default function PlayerShell({ subtitle, children, footer }) {
|
|||||||
backgroundColor: 'var(--bg-deep)',
|
backgroundColor: 'var(--bg-deep)',
|
||||||
backgroundImage: bg,
|
backgroundImage: bg,
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
backgroundSize: 'cover',
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundSize: 'min(60vh, 520px)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ width: '100%', maxWidth: 400 }}>
|
<div style={{ width: '100%', maxWidth: 400 }}>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function Maintenance() {
|
|||||||
backgroundImage: heroBg,
|
backgroundImage: heroBg,
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'min(60vh, 520px)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ maxWidth: 640, textShadow: '0 2px 22px rgba(0,0,0,0.85)' }}>
|
<div style={{ maxWidth: 640, textShadow: '0 2px 22px rgba(0,0,0,0.85)' }}>
|
||||||
|
|||||||
@@ -1009,6 +1009,67 @@ button[disabled] {
|
|||||||
border-color: #8a4b47;
|
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. */
|
/* Draft-preview banner on the public renderer. */
|
||||||
.page-preview-banner {
|
.page-preview-banner {
|
||||||
border: 1px solid var(--accent);
|
border: 1px solid var(--accent);
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const brand = {
|
|||||||
// Visual
|
// Visual
|
||||||
accent: process.env.BRAND_ACCENT_COLOR || '#7f99bd',
|
accent: process.env.BRAND_ACCENT_COLOR || '#7f99bd',
|
||||||
logo: process.env.BRAND_LOGO || '', // empty → no logo image rendered
|
logo: process.env.BRAND_LOGO || '', // empty → no logo image rendered
|
||||||
hero: process.env.BRAND_HERO || '/assets/img/hero-moon.png',
|
hero: process.env.BRAND_HERO || '/assets/img/runic-emblem.png',
|
||||||
favicon: process.env.BRAND_FAVICON || '', // empty → no favicon link injected
|
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
|
// Discord embeds want an int (0xRRGGBB). Parse the accent hex once; fall back to
|
||||||
|
|||||||
Reference in New Issue
Block a user