Files
astrolabe/src/app/components/AboutModal.tsx
T

192 lines
7.5 KiB
TypeScript

/**
* About & Help modal body (spec §01B/§01C).
*
* Rendered inside ModalShell — no backdrop, close button, or focus trap here;
* the shell owns all of that (docs/architecture/03 → Layer 3). This component
* is pure content: app identity, keyboard shortcuts (§01D), privacy posture
* (SOUL.md — local-only, no accounts, no telemetry, no AI), and acknowledgements of
* the projects Astrolabe is built on and shaped by.
*/
import type { ReactNode } from 'react';
import { FEEDBACK_EMAIL, feedbackMailtoHref } from '../feedback';
import styles from './AboutModal.module.css';
/** True when the user agent is macOS / iOS — drives the Cmd vs. Ctrl label. */
const isMac = /Mac|iPhone|iPad|iPod/.test(navigator.platform);
const mod = isMac ? '⌘' : 'Ctrl';
/** Keyboard shortcut rows sourced from spec §01D. */
const SHORTCUTS: readonly { keys: string; action: string }[] = [
{ keys: `${mod}+Shift+N`, action: 'Create a new snippet' },
{ keys: `${mod}+K`, action: 'Toggle the Datasets manager' },
{ keys: `${mod}+S`, action: 'Publish the current snippet draft' },
{ keys: `${mod}+,`, action: 'Open editor settings' },
{ keys: 'Esc', action: 'Close the active modal' },
];
/**
* External acknowledgement link. Opens in a new tab so following a credit never
* unloads the workspace mid-edit; `rel` isolates the opened context.
*/
function Ack({ href, children }: { href: string; children: ReactNode }) {
return (
<a className={styles.link} href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
);
}
export function AboutModal() {
return (
<div className={styles.about}>
{/* Identity */}
<section className={styles.section}>
<h3 className={styles.heading}>Astrolabe</h3>
<p className={styles.body}>
v<span className={styles.version}>{__APP_VERSION__}</span>
</p>
<p className={styles.body}>
A local-first workspace for authoring and organizing Vega-Lite charts. Edit the JSON,
watch it render live, and keep a personal library of snippets.
</p>
</section>
{/* Keyboard shortcuts */}
<section className={styles.section}>
<h3 className={styles.heading}>Keyboard shortcuts</h3>
<table className={styles.shortcuts} aria-label="Keyboard shortcuts">
<tbody>
{SHORTCUTS.map(({ keys, action }) => (
<tr key={keys} className={styles.row}>
<td className={styles.keys}>
<kbd className={styles.kbd}>{keys}</kbd>
</td>
<td className={styles.action}>{action}</td>
</tr>
))}
</tbody>
</table>
</section>
{/* Privacy */}
<section className={styles.section}>
<h3 className={styles.heading}>Privacy</h3>
<p className={styles.body}>
Astrolabe runs in your browser. Your snippets, datasets, and settings are stored locally
on your device there&rsquo;s no server to send them to. Work that has to stay
confidential is safe here.
</p>
<ul className={styles.list}>
<li>No account or sign-in.</li>
<li>
No AI. No model authors, edits, or critiques your charts, and nothing is sent to one.
The chart builder&rsquo;s suggestions are computed locally from your data.
</li>
<li>
The app itself runs no analytics or tracking. Its host (Cloudflare) records standard,
aggregate traffic like any web server not your library or the charts you build, which
stay in your browser.
</li>
<li>The only outbound requests are ones you make: datasets you load from a URL.</li>
<li>After the first load, the app works offline.</li>
<li>
Use the header&rsquo;s import / export buttons to move your library between devices.
</li>
</ul>
</section>
{/* Feedback */}
<section className={styles.section}>
<h3 className={styles.heading}>Feedback</h3>
<p className={styles.body}>
Found a bug or have an idea? Email{' '}
<a className={styles.link} href={feedbackMailtoHref()}>
{FEEDBACK_EMAIL}
</a>
.
</p>
</section>
{/* Acknowledgements */}
<section className={styles.section}>
<h3 className={styles.heading}>Acknowledgements</h3>
<p className={styles.body}>Astrolabe stands on the work of many open projects.</p>
<div className={styles.ackGroup}>
<h4 className={styles.ackLabel}>Built on</h4>
<ul className={styles.list}>
<li>
<Ack href="https://vega.github.io/vega-lite/">Vega-Lite</Ack>,{' '}
<Ack href="https://vega.github.io/vega/">Vega</Ack>,{' '}
<Ack href="https://github.com/vega/vega-embed">vega-embed</Ack>
<span className={styles.ackNote}> render every chart</span>
</li>
<li>
<Ack href="https://microsoft.github.io/monaco-editor/">Monaco</Ack>
<span className={styles.ackNote}> the JSON editor</span>
</li>
<li>
<Ack href="https://react.dev/">React</Ack>,{' '}
<Ack href="https://github.com/pmndrs/zustand">Zustand</Ack>,{' '}
<Ack href="https://vite.dev/">Vite</Ack>
</li>
</ul>
</div>
<div className={styles.ackGroup}>
<h4 className={styles.ackLabel}>Chart guidance shaped by</h4>
<ul className={styles.list}>
<li>
<Ack href="https://github.com/uwdata/draco">Draco</Ack>,{' '}
<Ack href="https://github.com/vega/voyager">Voyager</Ack>
<span className={styles.ackNote}> encoding rules</span>
</li>
<li>
<Ack href="https://github.com/Financial-Times/chart-doctor">FT Visual Vocabulary</Ack>
, <Ack href="https://www.datawrapper.de/">Datawrapper</Ack>
</li>
<li>
<Ack href="https://github.com/vega/lyra">Lyra</Ack>
<span className={styles.ackNote}> authoring interactions</span>
</li>
</ul>
</div>
<div className={styles.ackGroup}>
<h4 className={styles.ackLabel}>Design informed by</h4>
<ul className={styles.list}>
<li>
<Ack href="https://carbondesignsystem.com/">IBM Carbon</Ack>,{' '}
<Ack href="https://design-system.service.gov.uk/">GOV.UK</Ack>
</li>
<li>
<Ack href="https://www.w3.org/WAI/ARIA/apg/">WAI-ARIA APG</Ack>,{' '}
<Ack href="https://www.nngroup.com/">Nielsen Norman</Ack>
</li>
</ul>
</div>
<div className={styles.ackGroup}>
<h4 className={styles.ackLabel}>Typography</h4>
<ul className={styles.list}>
<li>
<Ack href="https://github.com/IBM/plex">IBM Plex</Ack> and other open typefaces,
self-hosted via <Ack href="https://fontsource.org/">Fontsource</Ack>
</li>
</ul>
</div>
</section>
{/* Sibling project */}
<section className={styles.section}>
<h3 className={styles.heading}>Also from the maker</h3>
<p className={styles.body}>
Astrolabe has a sibling: <Ack href="https://syto.app">Syto</Ack>, a workspace for cleaning
and reshaping data before you chart it. Go take a look.
</p>
</section>
</div>
);
}