feat(monitor/frontend): about modal (meme + project explainer, ethos-minimal)
Native <dialog> opened from the TopStrip ? icon via useUIStore.aboutOpen. Ref-driven showModal()/close() in useEffect, onClose synced to the store so Escape closes both the dialog and the store flag in one round trip (no listener fight). Three short paragraphs: the meme origin, the data sources, and the keyboard shortcuts. The kbd-styled spans (mono + 1px --fg-4 outline) call out F and Esc. No animation on open/close beyond the browser's default <dialog> showModal transition (which is OS UI, not decoration). No backdrop-click to close — Escape and the close button are enough; backdrop-click adds a fragile target===dialog comparison for marginal value. ::backdrop gets a 60% black tint so it reads as a modal, not a layer cake. prose line-height 1.4 (looser than the 1.25 table tightness because prose needs readability — operator-density still applies but Tufte data-ink isn't violated by paragraph leading). Plan 5 Task 23 Design QA: no illustration, no confetti, no tooltip arrows, monochrome border, native ::backdrop only.
This commit is contained in:
parent
21f54390df
commit
71e0529960
|
|
@ -0,0 +1,74 @@
|
|||
// ©AngelaMos | 2026
|
||||
// About.module.scss
|
||||
|
||||
.dialog {
|
||||
background: var(--bg-panel);
|
||||
color: var(--fg-1);
|
||||
border: 1px solid var(--fg-4);
|
||||
padding: 0;
|
||||
max-width: 480px;
|
||||
width: 90%;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--type-body);
|
||||
|
||||
&::backdrop {
|
||||
background: rgb(0, 0, 0, 60%);
|
||||
}
|
||||
}
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 12px;
|
||||
border-bottom: 1px solid var(--fg-4);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--type-label);
|
||||
letter-spacing: var(--letter-spacing-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--fg-2);
|
||||
}
|
||||
|
||||
.close {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
color: var(--fg-3);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--fg-1);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid var(--fg-2);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
color: var(--fg-2);
|
||||
line-height: 1.4;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.kbd {
|
||||
font-family: var(--font-mono);
|
||||
color: var(--fg-1);
|
||||
border: 1px solid var(--fg-4);
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// ©AngelaMos | 2026
|
||||
// About.tsx
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { FiX } from 'react-icons/fi'
|
||||
import { useUIStore } from '@/stores/ui'
|
||||
import styles from './About.module.scss'
|
||||
|
||||
export function About(): React.ReactElement {
|
||||
const isOpen = useUIStore((s) => s.aboutOpen)
|
||||
const close = useUIStore((s) => s.closeAbout)
|
||||
const dialogRef = useRef<HTMLDialogElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const dialog = dialogRef.current
|
||||
if (!dialog) return
|
||||
if (isOpen && !dialog.open) {
|
||||
dialog.showModal()
|
||||
} else if (!isOpen && dialog.open) {
|
||||
dialog.close()
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
return (
|
||||
<dialog ref={dialogRef} className={styles.dialog} onClose={close}>
|
||||
<header className={styles.head}>
|
||||
<span className={styles.title}>Monitoring the Situation</span>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.close}
|
||||
onClick={close}
|
||||
aria-label="Close"
|
||||
>
|
||||
<FiX aria-hidden />
|
||||
</button>
|
||||
</header>
|
||||
<div className={styles.body}>
|
||||
<p>
|
||||
"Monitoring the situation" is a Twitter/X meme from June 2025. This is
|
||||
the version that actually monitors the situation.
|
||||
</p>
|
||||
<p>
|
||||
Operator-grade real-time dashboard pulling live data from ten
|
||||
high-signal feeds: DShield mass-scan, Cloudflare Radar BGP/outage, NVD +
|
||||
EPSS CVE velocity, CISA KEV, ransomware.live, Coinbase BTC/ETH ticks,
|
||||
USGS earthquakes, NOAA SWPC space weather, Wikipedia ITN + GDELT theme
|
||||
spikes, and the ISS live position. Single Go binary backend, React 19
|
||||
frontend, Postgres + Redis, served behind a Cloudflare Tunnel.
|
||||
</p>
|
||||
<p>
|
||||
<span className={styles.kbd}>F</span> enters presentation mode (full
|
||||
viewport, chrome hidden). <span className={styles.kbd}>Esc</span> exits
|
||||
or closes this dialog.
|
||||
</p>
|
||||
</div>
|
||||
</dialog>
|
||||
)
|
||||
}
|
||||
|
||||
About.displayName = 'About'
|
||||
|
|
@ -11,6 +11,7 @@ import { KEVPanel } from '@/pages/panels/KEVPanel'
|
|||
import { RansomwarePanel } from '@/pages/panels/RansomwarePanel'
|
||||
import { SpaceWeatherPanel } from '@/pages/panels/SpaceWeatherPanel'
|
||||
import { useUIStore } from '@/stores/ui'
|
||||
import { About } from './About'
|
||||
import { AlertBanner } from './AlertBanner'
|
||||
import { BottomTicker } from './BottomTicker'
|
||||
import styles from './Dashboard.module.scss'
|
||||
|
|
@ -47,6 +48,7 @@ export function Dashboard(): React.ReactElement {
|
|||
</aside>
|
||||
</main>
|
||||
<BottomTicker />
|
||||
<About />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue