Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-gatekeeper/test/rail-icon-fixture.ts

Snapshot at 34e3175

Containing directory

3,986 bytes · typescriptView raw
1// Offline visual fixture of pinned OS rail/tile styling, not a connected OS session.
2// Run with Node and redirect stdout to an HTML file; ?mode=dark switches theme.
3import brand from "../../../brand.json" with { type: "json" };
4import tokens from "../../forkhorse-dashboard/src/host-tokens.json" with { type: "json" };
5import { markIconUrl } from "../../forkhorse-dashboard/src/mark.ts";
6import { railIconUrl } from "../src/rail-icon.ts";
7
8// Phosphor House regular 2.1.10, for comparison with the OS built-in Home row.
9const housePath = "M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z";
10const escape = (value: string) => value.replace(/[&<>"']/g, c => ({
11  "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
12})[c]!);
13const vars = (values: Record<string, string>) => Object.entries(values).map(([key, value]) => `${key}:${value}`).join(";");
14const railUrl = escape(railIconUrl(brand.railIcon));
15const markUrl = escape(markIconUrl(brand.mark));
16console.log(`<!doctype html><html lang="en"><meta charset="utf-8"><title>Offline rail icon fixture</title>
17<style>
18:root{${vars(tokens.light)}}:root[data-mode=dark]{${vars(tokens.dark)}}
19*{box-sizing:border-box}body{margin:0;padding:24px;font-family:system-ui,sans-serif;background:var(--color-kumo-base);color:var(--text-color-kumo-default)}
20h1{font-size:14px;margin:0 0 8px}p{font-size:12px;color:var(--text-color-kumo-subtle);margin:0 0 20px}
21section{display:flex;gap:32px}.rail{width:260px;padding:12px 8px;background:var(--color-kumo-elevated);border:1px solid var(--color-kumo-line);border-radius:8px}
22.row{display:flex;height:32px;align-items:center;gap:10px;padding:0 10px;font-size:13px;line-height:18px;border-radius:8px;margin-bottom:2px}
23.slot{display:flex;width:20px;height:20px;align-items:center;justify-content:center;color:var(--text-color-kumo-subtle);flex-shrink:0}
24.slot svg,.glyph{width:14px;height:14px}.glyph{background:currentColor;mask-image:url("${railUrl}");-webkit-mask-image:url("${railUrl}");mask-repeat:no-repeat;-webkit-mask-repeat:no-repeat;mask-position:center;-webkit-mask-position:center;mask-size:contain;-webkit-mask-size:contain}
25.active{background:var(--color-kumo-fill);font-weight:500;color:var(--text-color-kumo-strong)}.active .slot{color:var(--color-kumo-brand)}
26.tile{width:280px}.tile h2{font-size:18px;margin:8px 0 12px}.vendor{display:flex;gap:12px;align-items:center;padding:12px;background:var(--color-kumo-tint);border-radius:8px;font-size:14px;font-weight:600}.vendor img{width:20px;height:20px;object-fit:contain}.badge{margin-left:auto;font-size:10px;color:var(--text-color-kumo-subtle);font-weight:500}.note{margin-top:12px}
27</style><script>document.documentElement.dataset.mode=new URL(location.href).searchParams.get("mode")==="dark"?"dark":"light";</script>
28<h1>OFFLINE FIXTURE — FH-047 · <span id="mode"></span></h1><p>14px glyphs; pinned OS mask and row dimensions. No live connection.</p>
29<section><nav class="rail" aria-label="Offline rail comparison">
30<div class="row"><span class="slot"><svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="${housePath}"/></svg></span>Home</div>
31<div class="row"><span class="slot"><span class="glyph" aria-hidden="true"></span></span>${escape(brand.name)}</div>
32<div class="row active"><span class="slot"><span class="glyph" aria-hidden="true"></span></span>${escape(brand.name)} <span class="badge">active</span></div>
33</nav><div class="tile"><h2>Gatekeepers</h2><div class="vendor"><img src="${markUrl}" alt="">${escape(brand.name)}<span class="badge">auto-provisioned</span></div><p class="note">Vendor logo still uses brand.mark (${escape(brand.mark)}).</p></div></section>
34<script>document.getElementById("mode").textContent=document.documentElement.dataset.mode;</script></html>`);
35