flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/services/og/src/brand.ts

132 lines7,959 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Social cards for every page: og.g1t.sh1/**
2 * The brand, as SVG for satori: the Fleet mark and the "g1t" wordmark, the
3 * same shapes as `.g1t/brand/svg/logo.svg`. The wordmark is outlined, so
4 * the card needs no ExtraBold font.
5 */
6
7export const COLOR = {
8 bg: "#0f0f11",
9 surface: "#161618",
10 raised: "#1e1e21",
11 line: "#28282c",
12 lineStrong: "#38383e",
13 fg: "#ededef",
14 fgSoft: "#dcdce0",
15 muted: "#a0a0a8",
16 faint: "#6a6a72",
17 /** Lavender: the accent. */
18 lavender: "#b6a8ff",
19 lavenderDeep: "#6b56e8",
20 /** Status colours, from the site's tokens. */
21 mint: "#86efc4",
22 danger: "#ff8394",
23} as const;
24
25/** The mark's shapes, in its 32-unit box. */
26function markShapes(front: string, behind: string): string {
27 return (
28 `<g transform="translate(0.7 0.5)">` +
29 `<rect x="6.2" y="9.5" width="4.4" height="17" rx="2.2" fill="${behind}" fill-opacity="0.35"/>` +
30 `<rect x="12.4" y="7" width="4.8" height="19.5" rx="2.4" fill="${behind}" fill-opacity="0.65"/>` +
31 `<rect x="19" y="4.5" width="5.4" height="22" rx="2.7" fill="${front}"/>` +
32 `<path d="M21.7 7.2 17.6 10.9" fill="none" stroke="${front}" stroke-width="4.6" stroke-linecap="round"/>` +
33 `</g>`
34 );
35}
36
37const WORDMARK =
38 "M30.42 21.29Q22.8 21.29 17.5 19.26Q12.21 17.24 9.16 13.67Q6.1 10.11 5.22 5.42L20.75 4.1Q21.19 5.66 22.46 6.86Q23.73 8.06 25.71 8.74Q27.69 9.42 30.37 9.42Q35.3 9.42 37.89 6.93Q40.48 4.44 40.48 -0.59V-9.28H39.89Q38.72 -6.54 36.65 -4.57Q34.57 -2.59 31.67 -1.56Q28.76 -0.54 25.1 -0.54Q18.7 -0.54 13.7 -3.64Q8.69 -6.74 5.83 -12.6Q2.98 -18.46 2.98 -26.76Q2.98 -35.3 5.91 -41.36Q8.84 -47.41 13.82 -50.63Q18.8 -53.86 25 -53.86Q28.81 -53.86 31.71 -52.66Q34.62 -51.46 36.77 -49.41Q38.92 -47.36 40.23 -44.73H40.48V-52.93H57.23V-2.1Q57.23 5.91 53.91 11.08Q50.59 16.26 44.56 18.77Q38.53 21.29 30.42 21.29ZM30.32 -13.38Q33.54 -13.38 35.89 -15.01Q38.23 -16.65 39.53 -19.7Q40.82 -22.75 40.82 -26.95Q40.82 -31.15 39.53 -34.23Q38.23 -37.3 35.89 -38.99Q33.54 -40.67 30.32 -40.67Q27.15 -40.67 24.88 -38.99Q22.61 -37.3 21.41 -34.23Q20.21 -31.15 20.21 -26.95Q20.21 -22.71 21.41 -19.65Q22.61 -16.6 24.88 -14.99Q27.15 -13.38 30.32 -13.38ZM94.12 -72.75V0H77.08V-57.37H76.84L60.33 -46.53V-61.57L77.47 -72.75ZM130.48 -52.93V-40.09H95.18V-52.93ZM103.28 -66.5H120.22V-17.04Q120.22 -14.75 121.23 -13.75Q122.23 -12.74 124.81 -12.74Q125.89 -12.74 127.62 -12.84Q129.36 -12.94 130.14 -13.04L131.11 -0.29Q129.11 0.1 126.43 0.22Q123.74 0.34 121.1 0.34Q112.07 0.34 107.68 -3.42Q103.28 -7.18 103.28 -14.79Z";
39
40/**
41 * The lockup: the mark as tall as the wordmark's capitals, standing on its
42 * baseline. Its box is 233 by 95; the baseline is at 72.75.
43 */
44export const LOCKUP_BOX = { width: 233, height: 95, baseline: 72.75 } as const;
45
46export function lockupSvg(front: string = COLOR.fg, behind: string = COLOR.lavender): string {
47 return (
48 `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${LOCKUP_BOX.width} ${LOCKUP_BOX.height}">` +
49 `<g transform="translate(-22.77 -16.35) scale(3.3)">${markShapes(front, behind)}</g>` +
50 `<path transform="translate(101.08 72.75)" fill="${front}" d="${WORDMARK}"/>` +
51 `</svg>`
52 );
53}
54
55/** The mark alone, cropped to the figure: 18.2 by 22. */
56export function markSvg(front: string = COLOR.fg, behind: string = COLOR.lavender): string {
57 return (
58 `<svg xmlns="http://www.w3.org/2000/svg" viewBox="6.9 5 18.2 22">${markShapes(front, behind)}</svg>`
59 );
60}
61
62/** As an image source; base64, which both satori and resvg read. The SVG is ASCII. */
63/**
64 * The card's backdrop: the brand card's dot grid, lavender light from the
65 * top right, and the fleet standing large off the right edge, its 1s
66 * fading downwards into the dark. Darker towards the bottom, where the
67 * facts sit.
68 */
69export function backdropSvg(width: number, height: number, withFleet: boolean): string {
70 // The fleet at 24 times its 32-unit size, its front 1 cut by the edge.
71 const scale = 24;
72 // Drawn solid and faded as a whole, so the flag and the bar it joins
73 // read as one shape.
74 const fleet = withFleet
75 ? `<g mask="url(#fleetFade)" opacity="0.34">` +
76 `<g transform="translate(${width - 22.4 * scale} ${height - 27.5 * scale + 40}) scale(${scale})">` +
77 markShapes(COLOR.lavender, COLOR.lavender) +
78 `</g></g>`
79 : "";
80 return (
81 `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">` +
82 `<defs>` +
83 `<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">` +
84 `<circle cx="20" cy="15" r="1.4" fill="#2a2a30"/></pattern>` +
85 `<radialGradient id="light" cx="${width - 160}" cy="-40" r="760" gradientUnits="userSpaceOnUse">` +
86 `<stop offset="0" stop-color="${COLOR.lavenderDeep}" stop-opacity="0.30"/>` +
87 `<stop offset="0.45" stop-color="${COLOR.lavenderDeep}" stop-opacity="0.09"/>` +
88 `<stop offset="1" stop-color="${COLOR.lavenderDeep}" stop-opacity="0"/></radialGradient>` +
89 `<linearGradient id="dusk" x1="0" y1="0" x2="0" y2="1">` +
90 `<stop offset="0.5" stop-color="${COLOR.bg}" stop-opacity="0"/>` +
91 `<stop offset="1" stop-color="${COLOR.bg}" stop-opacity="0.9"/></linearGradient>` +
92 `<linearGradient id="fadeMask" x1="0" y1="0" x2="0" y2="1">` +
93 `<stop offset="0.1" stop-color="#fff"/>` +
94 `<stop offset="0.95" stop-color="#000"/></linearGradient>` +
95 `<mask id="fleetFade" maskUnits="userSpaceOnUse" x="0" y="0" width="${width}" height="${height}">` +
96 `<rect width="${width}" height="${height}" fill="url(#fadeMask)"/></mask>` +
97 `</defs>` +
98 `<rect width="${width}" height="${height}" fill="${COLOR.bg}"/>` +
99 `<rect width="${width}" height="${height}" fill="url(#dots)"/>` +
100 `<rect width="${width}" height="${height}" fill="url(#light)"/>` +
101 fleet +
102 `<rect width="${width}" height="${height}" fill="url(#dusk)"/>` +
103 `</svg>`
104 );
105}
106
107export function dataUri(svg: string): string {
108 return `data:image/svg+xml;base64,${btoa(svg)}`;
109}
110
111/** Lucide's icons, as the site uses them, at 24 units. */
112export const ICON = {
113 issue: `<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="1"/>`,
114 issueDone: `<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>`,
115 issueNotPlanned: `<circle cx="12" cy="12" r="10"/><line x1="9" x2="15" y1="15" y2="9"/>`,
116 pull: `<circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><line x1="6" x2="6" y1="9" y2="21"/>`,
117 pullDraft: `<circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M18 6V5"/><path d="M18 11v-1"/><line x1="6" x2="6" y1="9" y2="21"/>`,
118 merged: `<circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 0 0 9 9"/>`,
119 pullClosed: `<circle cx="6" cy="6" r="3"/><path d="M6 9v12"/><path d="m21 3-6 6"/><path d="m21 9-6-6"/><path d="M18 11.5V15"/><circle cx="18" cy="18" r="3"/>`,
120 box: `<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>`,
121 sparkles: `<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/>`,
122 book: `<path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>`,
123 user: `<circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 0 0-16 0"/>`,
124 globe: `<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>`,
125} as const;
126
127export function iconSvg(shapes: string, color: string): string {
128 return (
129 `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${color}" ` +
130 `stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${shapes}</svg>`
131 );
132}