/** * Company Atlas mark — single source of geometry. * * The "atlas plate": a rounded square plate carrying a globe drawn as a sparse grid (ring, one meridian ellipse, two * parallels) crossed by a strong equator — the timeline — that ends on a bright "now" node sitting on the horizon, * with a short pulse trail behind it. One 64 × 64 geometry; only stroke weights change with the rendered size so the * mark stays crisp at 16 px (favicon) and rich at 1200 px (share image). * * Pure TypeScript, no JSX and no DOM: imported by the React components AND by `scripts/build-icons.mjs` (Node strips * the types), which writes the standalone SVGs, the favicon set and the PNGs from exactly this description. */ export const MARK_VIEWBOX = 64; export const MARK_PLATE_RADIUS = 16; const C = 32; // globe centre const R = 20; // globe radius const NODE_X = 52; // "now" node sits on the horizon, at the right end of the equator function circle(cx: number, cy: number, r: number): string { return `M${cx - r} ${cy}a${r} ${r} 0 1 0 ${2 * r} 0a${r} ${r} 0 1 0 ${-2 * r} 0Z`; } function ellipse(cx: number, cy: number, rx: number, ry: number): string { return `M${cx - rx} ${cy}a${rx} ${ry} 0 1 0 ${2 * rx} 0a${rx} ${ry} 0 1 0 ${-2 * rx} 0Z`; } const PAR_DY = 10; const PAR_HALF = Math.sqrt(R * R - PAR_DY * PAR_DY); // 17.32 /** Path data, in 64-unit coordinates. */ export const MARK_PATHS = { ring: circle(C, C, R), meridian: ellipse(C, C, 8.5, R), parallels: `M${(C - PAR_HALF).toFixed(2)} ${C - PAR_DY}H${(C + PAR_HALF).toFixed(2)}M${(C - PAR_HALF).toFixed(2)} ${C + PAR_DY}H${(C + PAR_HALF).toFixed(2)}`, equator: `M12 ${C}H${NODE_X}`, /** Tapered pulse trail running along the equator into the node. */ trail: `M35 30.9L48.6 28.3V35.7L35 33.1Z`, node: circle(NODE_X, C, 5), halo: circle(NODE_X, C, 8.6), } as const; export type MarkColors = { plate: string; ink: string; grid: string; accent: string }; /** Dark plate (for light surfaces, print, favicon). */ export const MARK_DARK: MarkColors = { plate: '#0f1419', ink: '#f7f7f4', grid: 'rgba(247,247,244,0.30)', accent: '#3fd07a' }; /** Light plate (for dark surfaces). */ export const MARK_LIGHT: MarkColors = { plate: '#e8ebf1', ink: '#0a0d12', grid: 'rgba(10,13,18,0.26)', accent: '#16a34a' }; /** Theme-following plate through the `--brand-*` tokens declared in globals.css. */ export const MARK_TOKENS: MarkColors = { plate: 'var(--brand-plate)', ink: 'var(--brand-ink)', grid: 'var(--brand-grid)', accent: 'var(--brand-accent)' }; /** Detail level: `tiny` ≤ 20 px (favicon 16), `small` ≤ 40 px (favicon 32, header), `full` above. */ export type MarkLevel = 'tiny' | 'small' | 'full'; export function markLevel(px: number): MarkLevel { return px <= 20 ? 'tiny' : px <= 40 ? 'small' : 'full'; } type Weights = { ring: number; grid: number; equator: number; halo: number; node: number; parallels: boolean; halo_on: boolean; bevel: boolean }; const WEIGHTS: Record = { tiny: { ring: 5.2, grid: 3.6, equator: 5.2, halo: 0, node: 6.6, parallels: false, halo_on: false, bevel: false }, small: { ring: 4, grid: 2.6, equator: 4, halo: 2, node: 5.6, parallels: true, halo_on: true, bevel: false }, full: { ring: 3, grid: 1.9, equator: 3, halo: 1.6, node: 5, parallels: true, halo_on: true, bevel: true }, }; export type Shape = { tag: 'rect' | 'path'; attrs: Record }; export type MarkOptions = { level?: MarkLevel; /** Draw the plate (false → globe only, e.g. mono variant in text). */ plate?: boolean; /** Plate corner radius in 64-unit coordinates (0 for full-bleed icons that get masked by the OS). */ radius?: number; }; /** The ordered list of shapes that make the mark — the one place its drawing is defined. */ export function markShapes(colors: MarkColors, { level = 'full', plate = true, radius = MARK_PLATE_RADIUS }: MarkOptions = {}): Shape[] { const w = WEIGHTS[level]; const out: Shape[] = []; if (plate) { out.push({ tag: 'rect', attrs: { x: 0, y: 0, width: MARK_VIEWBOX, height: MARK_VIEWBOX, rx: radius, fill: colors.plate } }); if (w.bevel && radius > 0) out.push({ tag: 'rect', attrs: { x: 0.75, y: 0.75, width: MARK_VIEWBOX - 1.5, height: MARK_VIEWBOX - 1.5, rx: Math.max(0, radius - 0.75), fill: 'none', stroke: colors.grid, 'stroke-width': 1, opacity: 0.55 } }); } out.push({ tag: 'path', attrs: { d: MARK_PATHS.meridian, fill: 'none', stroke: colors.grid, 'stroke-width': w.grid } }); if (w.parallels) out.push({ tag: 'path', attrs: { d: MARK_PATHS.parallels, fill: 'none', stroke: colors.grid, 'stroke-width': w.grid, 'stroke-linecap': 'round' } }); out.push({ tag: 'path', attrs: { d: MARK_PATHS.ring, fill: 'none', stroke: colors.ink, 'stroke-width': w.ring } }); out.push({ tag: 'path', attrs: { d: MARK_PATHS.equator, fill: 'none', stroke: colors.ink, 'stroke-width': w.equator, 'stroke-linecap': 'round' } }); out.push({ tag: 'path', attrs: { d: MARK_PATHS.trail, fill: colors.accent, opacity: 0.55 } }); if (w.halo_on) out.push({ tag: 'path', attrs: { d: MARK_PATHS.halo, fill: 'none', stroke: colors.accent, 'stroke-width': w.halo, opacity: 0.45 } }); out.push({ tag: 'path', attrs: { d: circle(NODE_X, C, w.node), fill: colors.accent } }); return out; } function attrString(attrs: Record): string { return Object.entries(attrs) .map(([k, v]) => `${k}="${v}"`) .join(' '); } /** Standalone SVG document of the mark (used for icon.svg, logo-mark.svg and Satori `` data URIs). */ export function markSvgString(colors: MarkColors, opts: MarkOptions & { size?: number } = {}): string { const shapes = markShapes(colors, opts); const dim = opts.size ? ` width="${opts.size}" height="${opts.size}"` : ''; return `${shapes.map((s) => `<${s.tag} ${attrString(s.attrs)}/>`).join('')}`; } export function markDataUri(colors: MarkColors, opts?: MarkOptions & { size?: number }): string { return `data:image/svg+xml;utf8,${encodeURIComponent(markSvgString(colors, opts))}`; }