style: match status page to openshopgraph.org design language
Re-theme the UptimeFlare fork's chrome (surfaces, borders, type, header
wordmark, footer line) to the openshopgraph.org design tokens, sourced
from openshopgraph-frontend-v2 src/styles/site-tokens.css. Monitor set,
check logic and page title/links config are untouched.
- Copy the site's self-hosted Cabinet Grotesk / Switzer / Martian Mono
woff2 faces (~124 KB) into public/fonts and remap Mantine's CSS
variables (--mantine-font-family[-headings/-monospace]) to the same
stacks; drop the next/font/google Inter import (font-CDN-free, same
rule as the main site's CSP).
- Remap Mantine surface/text/border variables per color scheme
(--mantine-color-body/text/white/dark-6/dark-4/anchor/dimmed,
--mantine-primary-color-filled) to --paper/--ink/--card/--hair from
site-tokens.css; keeps following prefers-color-scheme via Mantine's
existing defaultColorScheme="auto" script, matching the main site's
light/dark behavior. Semantic monitor-state colors (green/red icons)
are left alone on purpose.
- Bump --mantine-radius-{sm,md,lg} to the main site's softer values.
- Replace the generic UptimeFlare <Image> logo in Header.tsx with the
OSG two-dot wordmark (same glyph as BaseLayout.astro's .top-brand
mark), linking to https://openshopgraph.org.
- Set pageConfig.customFooter to a one-line footer matching the main
site's "Verified data, not ads." tone instead of the stock UptimeFlare
attribution line.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/210/head
parent
3dca962be3
commit
b9f8546044
|
|
@ -1,9 +1,28 @@
|
||||||
import { Container, Group, Image } from '@mantine/core'
|
import { Container, Group } from '@mantine/core'
|
||||||
import classes from '@/styles/Header.module.css'
|
import classes from '@/styles/Header.module.css'
|
||||||
import { pageConfig } from '@/uptime.config'
|
import { pageConfig } from '@/uptime.config'
|
||||||
import { PageConfigLink } from '@/types/config'
|
import { PageConfigLink } from '@/types/config'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
// OSG brand mark — same two-dot "connect" glyph as the wordmark on
|
||||||
|
// openshopgraph.org (BaseLayout.astro .top-brand .mark svg).
|
||||||
|
function OsgMark() {
|
||||||
|
return (
|
||||||
|
<span className="osg-mark">
|
||||||
|
<svg width="10" height="10" viewBox="0 0 12 12" fill="none">
|
||||||
|
<circle cx="3" cy="9" r="1.7" fill="var(--mantine-color-body)" />
|
||||||
|
<circle cx="9" cy="3" r="1.7" fill="var(--mantine-color-body)" />
|
||||||
|
<path
|
||||||
|
d="M4.2 7.8L7.8 4.2"
|
||||||
|
stroke="var(--mantine-color-body)"
|
||||||
|
strokeWidth="1.4"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function Header({ style }: { style?: React.CSSProperties }) {
|
export default function Header({ style }: { style?: React.CSSProperties }) {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
const linkToElement = (link: PageConfigLink, i: number) => {
|
const linkToElement = (link: PageConfigLink, i: number) => {
|
||||||
|
|
@ -26,17 +45,10 @@ export default function Header({ style }: { style?: React.CSSProperties }) {
|
||||||
<header className={classes.header} style={style}>
|
<header className={classes.header} style={style}>
|
||||||
<Container size="md" className={classes.inner}>
|
<Container size="md" className={classes.inner}>
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a href="https://openshopgraph.org" className="osg-brand">
|
||||||
href={location.pathname == '/' ? 'https://github.com/lyc8503/UptimeFlare' : '/'}
|
<OsgMark />
|
||||||
target={location.pathname == '/' ? '_blank' : undefined}
|
OpenShopGraph
|
||||||
>
|
<span className="osg-status-label"> Status</span>
|
||||||
<Image
|
|
||||||
src={pageConfig.logo ?? '/logo.svg'}
|
|
||||||
h={56}
|
|
||||||
w={{ base: 140, sm: 190 }}
|
|
||||||
fit="contain"
|
|
||||||
alt="logo"
|
|
||||||
/>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import '@mantine/core/styles.css'
|
import '@mantine/core/styles.css'
|
||||||
|
import '@/styles/osg-fonts.css'
|
||||||
|
import '@/styles/osg-theme.css'
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
import { MantineProvider } from '@mantine/core'
|
import { MantineProvider } from '@mantine/core'
|
||||||
import NoSsr from '@/components/NoSsr'
|
import NoSsr from '@/components/NoSsr'
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
|
|
||||||
import { Inter } from 'next/font/google'
|
|
||||||
import { MaintenanceConfig, MonitorTarget } from '@/types/config'
|
import { MaintenanceConfig, MonitorTarget } from '@/types/config'
|
||||||
import { maintenances, pageConfig } from '@/uptime.config'
|
import { maintenances, pageConfig } from '@/uptime.config'
|
||||||
import Header from '@/components/Header'
|
import Header from '@/components/Header'
|
||||||
|
|
@ -12,7 +11,6 @@ import NoIncidentsAlert from '@/components/NoIncidents'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export const runtime = 'experimental-edge'
|
export const runtime = 'experimental-edge'
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
|
||||||
|
|
||||||
function getSelectedMonth() {
|
function getSelectedMonth() {
|
||||||
const hash = window.location.hash.replace('#', '')
|
const hash = window.location.hash.replace('#', '')
|
||||||
|
|
@ -87,7 +85,7 @@ export default function IncidentsPage({ monitors }: { monitors: MonitorTarget[]
|
||||||
<link rel="icon" href={pageConfig.favicon ?? '/favicon.png'} />
|
<link rel="icon" href={pageConfig.favicon ?? '/favicon.png'} />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<main className={inter.className}>
|
<main>
|
||||||
<Header
|
<Header
|
||||||
style={{
|
style={{
|
||||||
marginBottom: '40px',
|
marginBottom: '40px',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
|
|
||||||
import { Inter } from 'next/font/google'
|
|
||||||
import { MonitorTarget } from '@/types/config'
|
import { MonitorTarget } from '@/types/config'
|
||||||
import { maintenances, pageConfig } from '@/uptime.config'
|
import { maintenances, pageConfig } from '@/uptime.config'
|
||||||
import OverallStatus from '@/components/OverallStatus'
|
import OverallStatus from '@/components/OverallStatus'
|
||||||
|
|
@ -13,7 +12,6 @@ import { useTranslation } from 'react-i18next'
|
||||||
import { CompactedMonitorStateWrapper, getFromStore } from '@/worker/src/store'
|
import { CompactedMonitorStateWrapper, getFromStore } from '@/worker/src/store'
|
||||||
|
|
||||||
export const runtime = 'experimental-edge'
|
export const runtime = 'experimental-edge'
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
|
||||||
|
|
||||||
export default function Home({
|
export default function Home({
|
||||||
compactedStateStr,
|
compactedStateStr,
|
||||||
|
|
@ -48,7 +46,7 @@ export default function Home({
|
||||||
<link rel="icon" href={pageConfig.favicon ?? '/favicon.png'} />
|
<link rel="icon" href={pageConfig.favicon ?? '/favicon.png'} />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<main className={inter.className}>
|
<main>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
{state.lastUpdate === 0 ? (
|
{state.lastUpdate === 0 ? (
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -2,7 +2,7 @@
|
||||||
height: rem(56px);
|
height: rem(56px);
|
||||||
margin-bottom: rem(100px);
|
margin-bottom: rem(100px);
|
||||||
background-color: var(--mantine-color-body);
|
background-color: var(--mantine-color-body);
|
||||||
border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
border-bottom: rem(1px) solid var(--mantine-color-default-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-mantine-color-scheme] &[data-active] {
|
[data-mantine-color-scheme] &[data-active] {
|
||||||
background-color: var(--mantine-color-blue-filled);
|
background-color: var(--mantine-primary-color-filled);
|
||||||
color: var(--mantine-color-white);
|
color: var(--mantine-color-body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* osg-fonts.css — self-hosted brand faces, copied verbatim (same woff2
|
||||||
|
* files) from openshopgraph-frontend-v2 src/styles/site-fonts.css so the
|
||||||
|
* status page renders with the same type system as openshopgraph.org.
|
||||||
|
* No font CDNs at runtime — files live in /public/fonts/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Cabinet Grotesk';
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/cabinet-grotesk-700.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Cabinet Grotesk';
|
||||||
|
font-weight: 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/cabinet-grotesk-800.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Switzer';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/switzer-400.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Switzer';
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/switzer-500.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Switzer';
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/switzer-600.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Martian Mono';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/martian-mono-400.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Martian Mono';
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/martian-mono-600.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-display:
|
||||||
|
'Cabinet Grotesk', 'Switzer', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue',
|
||||||
|
Arial, sans-serif;
|
||||||
|
--font-body:
|
||||||
|
'Switzer', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--font-facts: 'Martian Mono', 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* osg-theme.css — remaps Mantine's CSS custom properties to the
|
||||||
|
* openshopgraph.org design tokens (see openshopgraph-frontend-v2
|
||||||
|
* src/styles/site-tokens.css, "source of truth"). Scoped to the
|
||||||
|
* [data-mantine-color-scheme] attribute Mantine already sets, so this
|
||||||
|
* keeps following prefers-color-scheme via ColorSchemeScript
|
||||||
|
* defaultColorScheme="auto" — same behaviour as the main site.
|
||||||
|
*
|
||||||
|
* Semantic monitor-state colors (operational green / down red used by
|
||||||
|
* Mantine's own IconCircleCheck/IconAlertCircle + accordion chevrons) are
|
||||||
|
* left untouched on purpose — only chrome (surfaces, borders, text,
|
||||||
|
* type, radius) is re-themed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-display:
|
||||||
|
'Cabinet Grotesk', 'Switzer', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue',
|
||||||
|
Arial, sans-serif;
|
||||||
|
--font-body:
|
||||||
|
'Switzer', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--font-facts: 'Martian Mono', 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, monospace;
|
||||||
|
|
||||||
|
--mantine-font-family: var(--font-body);
|
||||||
|
--mantine-font-family-headings: var(--font-display);
|
||||||
|
--mantine-font-family-monospace: var(--font-facts);
|
||||||
|
|
||||||
|
--mantine-radius-xs: 4px;
|
||||||
|
--mantine-radius-sm: 8px;
|
||||||
|
--mantine-radius-md: 12px;
|
||||||
|
--mantine-radius-lg: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-mantine-color-scheme='light'] {
|
||||||
|
--mantine-color-body: #f7f6f3; /* --paper */
|
||||||
|
--mantine-color-text: #141412; /* --ink */
|
||||||
|
--mantine-color-white: #ffffff; /* --card, Card/Paper surfaces */
|
||||||
|
--mantine-color-anchor: #141412; /* --ink; chrome links read as brand, not generic blue */
|
||||||
|
--mantine-color-dimmed: #6a6862; /* --muted */
|
||||||
|
--mantine-color-default-border: #e7e5df; /* --hair */
|
||||||
|
--mantine-color-gray-3: #e7e5df; /* --hair; Card.Section border */
|
||||||
|
--mantine-primary-color-filled: #141412; /* --ink; matches .btn-dark on the main site */
|
||||||
|
--mantine-primary-color-filled-hover: #2a2a26;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-mantine-color-scheme='dark'] {
|
||||||
|
--mantine-color-body: #161512; /* --paper (dark) */
|
||||||
|
--mantine-color-text: #edeae3; /* --ink (dark) */
|
||||||
|
--mantine-color-dark-6: #1e1d1a; /* --card (dark); Card surface */
|
||||||
|
--mantine-color-dark-4: #2e2c27; /* --hair (dark); Card.Section border */
|
||||||
|
--mantine-color-anchor: #edeae3;
|
||||||
|
--mantine-color-dimmed: #9a978e; /* --muted (dark) */
|
||||||
|
--mantine-color-default-border: #2e2c27;
|
||||||
|
--mantine-primary-color-filled: #edeae3;
|
||||||
|
--mantine-primary-color-filled-hover: #d9d5cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- OSG brand header (replaces the generic UptimeFlare logo) ---------- */
|
||||||
|
.osg-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--mantine-color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.osg-brand:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.osg-brand .osg-mark {
|
||||||
|
width: 19px;
|
||||||
|
height: 19px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--mantine-color-text);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.osg-brand .osg-mark svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.osg-status-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--mantine-color-dimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- OSG footer line (Footer.tsx customFooter) ---------- */
|
||||||
|
.osg-footer {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--mantine-color-dimmed);
|
||||||
|
margin-top: 10px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.osg-footer a {
|
||||||
|
color: var(--mantine-color-text);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,11 @@ const pageConfig: PageConfig = {
|
||||||
links: [
|
links: [
|
||||||
{ link: 'https://openshopgraph.org', label: 'openshopgraph.org', highlight: true },
|
{ link: 'https://openshopgraph.org', label: 'openshopgraph.org', highlight: true },
|
||||||
],
|
],
|
||||||
|
// Chrome/copy only — swaps the default "made with UptimeFlare" line for
|
||||||
|
// one matching openshopgraph.org's footer tone ("Verified data, not
|
||||||
|
// ads."). Monitor semantics below are untouched.
|
||||||
|
customFooter:
|
||||||
|
'<p class="osg-footer">Verified data, not ads. Independent status page for <a href="https://openshopgraph.org" target="_blank" rel="noopener">openshopgraph.org</a> — powered by <a href="https://github.com/lyc8503/UptimeFlare" target="_blank" rel="noopener">UptimeFlare</a>.</p>',
|
||||||
}
|
}
|
||||||
|
|
||||||
const workerConfig: WorkerConfig = {
|
const workerConfig: WorkerConfig = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue