Files
jarl/internal/web/static/assets/brand.js
T
abrown38andClaude Opus 5.5 1afed741bf Use the JARL wordmark in the header and as the README banner
The header now shows the login page's wordmark letters (without the rule)
instead of "Jarl" set in Cinzel, so the bundled Cinzel font, its preload
and its licence stanza in the .deb's copyright file are removed: the web
UI needs no web font.

The README opens with the login page's banner, the logo above the
wordmark (artwork/banner.svg). scripts/banner.py builds it from the web
UI's logo.js and brand.js, with light and dark colors, and logo.py runs
it after tracing the logo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:09:41 +00:00

61 lines
3.2 KiB
JavaScript

// The Jarl logo and the login page's wordmark, drawn as inline SVG so they
// appear together with the page instead of loading afterwards.
//
// The "JARL" wordmark: angular Norse-style letters drawn as paths (no font
// needed). The login page shows it over a rule with DNS nodes; the header
// shows the letters alone. The letters use the text color and the rule the
// accent color, so it follows the theme.
import * as logoData from "./logo.js";
const NS = "http://www.w3.org/2000/svg";
function el(tag, attrs, ...children) {
const e = document.createElementNS(NS, tag);
for (const [k, v] of Object.entries(attrs || {})) e.setAttribute(k, v);
for (const c of children) e.append(c instanceof Node ? c : document.createTextNode(c));
return e;
}
// Letters on a 100-unit cap height; counters are cut with evenodd.
const letters = [
// J: a heavy stem whose foot hooks back into a barb
{ x: 0, d: "M14 0 H48 V10 H42 V80 L31 100 H5 L0 93 L19 84 H24 V10 H14 Z" },
// A: peaked top, flaring legs, a diamond for a counter
{ x: 60, d: "M35 0 L46 10 L64 90 L70 100 H50 L44 76 L35 62 L26 76 L20 100 H0 L6 90 L24 10 Z M35 26 L41 40 L35 54 L29 40 Z" },
// R: squared bowl with cut corners, a long kicked leg
{ x: 142, d: "M0 0 H48 L60 12 V40 L50 50 L66 100 H46 L34 60 H18 V100 H0 Z M18 14 H41 L46 19 V35 L41 46 H18 Z" },
// L: the base ends in an upswept spike
{ x: 220, d: "M0 0 H18 V84 H40 L58 70 L54 100 H0 Z" },
];
// wordmark returns the login page's wordmark, letters over the rule.
// wordmark({ rule: false, height: 19, cls: "brand-name" }) returns just the
// letters, height pixels tall.
export function wordmark({ rule = true, height, cls = "wordmark" } = {}) {
const letterG = el("g", { fill: "currentColor", "fill-rule": "evenodd" },
...letters.map((l) => el("path", { d: l.d, transform: `translate(${l.x} 0)` })));
if (!rule) {
const attrs = { class: cls, viewBox: "-2 -2 282 104", role: "img", "aria-label": "Jarl" };
if (height) Object.assign(attrs, { height, width: Math.round(height * 282 / 104) });
return el("svg", attrs, el("title", null, "Jarl"), letterG);
}
// The rule: tapered lines, two nodes and a "DNS" node in the middle.
const ruleG = el("g", { class: "wordmark-rule", transform: "translate(0 118)" },
el("path", { d: "M2 18 L98 15.6 V20.4 Z M278 18 L182 15.6 V20.4 Z", class: "fill" }),
el("circle", { cx: 106, cy: 18, r: 4.6, class: "line" }),
el("path", { d: "M110.6 18 H122 M158 18 H169.4", class: "line" }),
el("circle", { cx: 140, cy: 18, r: 17, class: "line" }),
el("circle", { cx: 174, cy: 18, r: 4.6, class: "line" }),
el("text", { x: 140, y: 22.5, "text-anchor": "middle", class: "dns" }, "DNS"));
return el("svg", { class: cls, viewBox: "-4 -4 288 162", role: "img", "aria-label": "Jarl" },
el("title", null, "Jarl"), letterG, ruleG);
}
// logo returns the Jarl logo (traced from artwork/logo.png by
// scripts/logo.py) as an SVG of the given size in pixels.
export function logo(size, cls) {
return el("svg", { class: cls || "logo", width: size, height: size, viewBox: logoData.viewBox, "aria-hidden": "true", focusable: "false" },
el("g", { transform: logoData.transform },
...logoData.layers.map(([fill, d]) => el("path", { fill, d }))));
}