OpenIcon 0.1: an icon set as a folder, with terminal glyphs (#198)

One openicon.json naming every icon (kebab-case keys, unique aliases,
shared keywords), 24x24 currentColor SVGs with a stated grid, and three
terminal glyphs per icon: a Nerd Font character with its codepoint and
glyph name, a Unicode symbol and a 1-4 character ASCII spelling, picked
nerd > unicode > ascii. Brand logos carry brand: true, a trademark note,
source and licence. made_by + W3C AI disclosure per set and per icon.
Mapping from Lucide, Tabler, Font Awesome, Nerd Fonts and Simple Icons.
Landing page shows 47 icons from the reference set (profullstack/openicon).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-09-23 22:27:15 -07:00 • committed by GitHub
parent f80488ce92
commit 3883ee7a2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 423 additions and 0 deletions

View file

@ -0,0 +1,248 @@
import Link from "next/link";
import type { ReactNode } from "react";
import type { Metadata } from "next";
import { SiteShell } from "@/components/site-shell";
import { mono, pre, table, td, th } from "../openontology/ui";
export const metadata: Metadata = {
title: "OpenIcon · LogicSRC",
description:
"OpenIcon is an icon set as a folder: one openicon.json naming every icon with aliases and keywords, 24x24 currentColor SVGs, and three terminal glyphs per icon (Nerd Font, Unicode, ASCII) so a TUI draws the best one it can. Brand logos say they are brands.",
alternates: { canonical: "/openicon" }
};
/** From the reference set, profullstack/openicon; copied into public/openicon. */
const SAMPLE = [
"mail", "phone", "chat", "at", "link", "search", "settings", "filter", "menu", "add", "edit", "delete",
"copy", "share", "download", "upload", "refresh", "bell", "calendar", "user", "users", "lock", "key", "eye",
"home", "map-pin", "globe", "cloud", "database", "package", "terminal", "code", "git-branch", "git-pull-request",
"rocket", "sparkles", "rss", "github", "x", "bluesky", "mastodon", "discord", "slack", "signal", "whatsapp",
"youtube", "npm"
];
/** key, Nerd Font codepoint, Unicode, ASCII: straight from openicon.json. */
const GLYPHS: Array<[string, string, string, string]> = [
["mail", "U+F01F0", "✉", "@"],
["phone", "U+F0DF0", "☎", "tel"],
["link", "U+F0339", "🔗", "~"],
["settings", "U+F0493", "⚙", "*"],
["terminal", "U+F018D", "⌨", ">_"],
["git-branch", "U+F418", "⎇", "Y"],
["warning", "U+F002A", "⚠", "!"],
["github", "U+F02A4", "🐙", "gh"]
];
const EXAMPLE = `{
"openicon": "0.1",
"name": "OpenIcon",
"license": "MIT",
"made_by": "ai",
"grid": { "size": 24, "stroke": 2, "padding": 2 },
"sizes": [16, 24, 32, 64, 128],
"icons": [
{ "key": "mail", "category": "communication",
"aliases": ["email", "envelope"], "keywords": ["message", "inbox"],
"svg": "svg/mail.svg", "png": "png/{size}/mail.png",
"tui": { "nerd": "\\u{f01f0}", "nerd_code": "f01f0",
"nerd_name": "md-email_outline", "unicode": "✉", "ascii": "@" } },
{ "key": "github", "category": "brand", "brand": true,
"trademark": "GitHub and its logo are trademarks of their owner. …",
"source": "simple-icons:github", "license": "CC0-1.0", "made_by": "human",
"svg": "svg/github.svg",
"tui": { "nerd_code": "f02a4", "unicode": "🐙", "ascii": "gh" } }
]
}`;
const RULES: Array<[string, string]> = [
["A set is a folder", "openicon.json at the root, every path relative to it; unknown keys are kept."],
["The key is the name", "Kebab case: mail, git-pull-request. The key is the file name; a brand's key is its own name."],
["Aliases find one icon, keywords find many", "email is mail everywhere, so every key and alias is unique; keywords like money are shared search terms."],
["Files take the text colour", "24x24 view box, currentColor, and a grid block stating size, stroke and padding so sets can be matched."],
["Every icon has terminal glyphs", "tui.nerd (with its codepoint and Nerd Fonts name), tui.unicode (one character), tui.ascii (1 to 4 printable characters)."],
["A terminal picks the best it can draw", "Nerd, then Unicode, then ASCII, from OPENICON_GLYPHS, NERD_FONT=1 or the locale. Pad to the cell width: an emoji can be two columns."],
["A brand says it is one", "brand: true, a trademark note, its source and its own licence. Logos are taken from their owners or a set that publishes them, never redrawn."],
["The set says what made it", "made_by and the W3C AI disclosure vocabulary, per set and per icon: the reference set's drawn icons are ai, its logos human."]
];
const ABSENT: Array<[string, string]> = [
["No colour", "Icons take the text colour. A brand's own colour is optional and ignorable."],
["No style variants", "Outline and filled are two sets, or two keys. One key, one drawing."],
["No webfont format", "A font is a build output a set may ship; SVG and the tui glyphs are the interchange."],
["No name registry", "Keys are the set's own; aliases are how it answers to another set's names."],
["No animation", "A spinner is a component, not an icon."]
];
export default function OpenIconPage(): ReactNode {
return (
<SiteShell active="OpenIcon">
<div className="band">
<div className="section-head">
<p className="eyebrow">LogicSRC standards surface</p>
<h2>OpenIcon</h2>
<p>
An icon set as a folder, and the first one that knows what to draw in a terminal: SVG in a browser, a
Nerd Font glyph in a patched terminal, a Unicode symbol in a plain one, ASCII over a serial line.
</p>
</div>
<p style={{ color: "#41505d" }}>
Every interface needs the same few hundred icons, and every set names them differently: Lucide&apos;s mail
is Font Awesome&apos;s envelope is Material&apos;s email. Brand logos sit beside drawn icons as if a
trademark were just another shape. And no set says what an icon should be when there are no pixels, so
TUIs hard-code a Nerd Font codepoint and show a box everywhere else.
</p>
<p style={{ color: "#5b6b7a" }}>
Status: 0.1. A sibling of <Link href="/openemoji">OpenEmoji</Link>.
</p>
</div>
<div className="band">
<div className="section-head">
<h2>The reference set</h2>
<p>
370 icons: 259 drawn on a 24x24 grid with 2px strokes, 111 brand logos from Simple Icons and Font Awesome
Free. <a href="https://github.com/profullstack/openicon">profullstack/openicon</a>.
</p>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(76px, 1fr))", gap: "0.45rem" }}>
{SAMPLE.map((key) => (
<figure
key={key}
style={{
margin: 0,
padding: "0.75rem 0.25rem 0.5rem",
border: "1px solid #e3e6e0",
borderRadius: "0.6rem",
background: "#fff",
textAlign: "center"
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={`/openicon/${key}.svg`} alt="" width={28} height={28} />
<figcaption style={{ fontSize: "0.7rem", color: "#5b6b7a", marginTop: "0.35rem" }}>{key}</figcaption>
</figure>
))}
</div>
</div>
<div className="band">
<div className="section-head">
<h2>Three glyphs per icon</h2>
<p>What a TUI draws, best first. Your browser probably has no Nerd Font, so that column shows the codepoint.</p>
</div>
<table style={table}>
<thead>
<tr>
<th style={th}>Icon</th>
<th style={th}>Nerd Font</th>
<th style={th}>Unicode</th>
<th style={th}>ASCII</th>
</tr>
</thead>
<tbody>
{GLYPHS.map(([key, nerd, unicode, ascii]) => (
<tr key={key}>
<td style={td}>
<code style={mono}>{key}</code>
</td>
<td style={td}>
<code style={mono}>{nerd}</code>
</td>
<td style={{ ...td, fontSize: "1.2rem" }}>{unicode}</td>
<td style={td}>
<code style={mono}>{ascii}</code>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="band">
<div className="section-head">
<h2>The shape</h2>
<p>openicon.json, trimmed to a drawn icon and a brand logo.</p>
</div>
<pre style={pre}>{EXAMPLE}</pre>
</div>
<div className="band">
<div className="section-head">
<h2>The eight rules</h2>
<p>Every one of them degrades rather than fails.</p>
</div>
<table style={table}>
<thead>
<tr>
<th style={th}>Rule</th>
<th style={th}>What it means</th>
</tr>
</thead>
<tbody>
{RULES.map(([rule, meaning], index) => (
<tr key={rule}>
<td style={td}>
<strong>
{index + 1}. {rule}
</strong>
</td>
<td style={td}>{meaning}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="band">
<div className="section-head">
<h2>What is deliberately absent</h2>
</div>
<table style={table}>
<tbody>
{ABSENT.map(([what, why]) => (
<tr key={what}>
<td style={td}>
<strong>{what}</strong>
</td>
<td style={td}>{why}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="band">
<div className="section-head">
<h2>Use it</h2>
<p>
<code style={mono}>icon</code> in <a href="https://github.com/profullstack/cli-tools">profullstack/cli-tools</a>{" "}
builds the set; <a href="https://hqtui.com">hqtui</a> ships it as its default icon pack.
</p>
</div>
<pre style={pre}>{`icon build --out ./openicon # openicon.json, svg/, png/, sprite.svg
icon glyph mail # the best glyph this terminal can draw
// hqtui
import { icon } from "@profullstack/hqtui";
icon("mail"); // "\\u{f01f0}", "✉" or "@"`}</pre>
</div>
<div className="band">
<div className="section-head">
<h2>Where everything lives</h2>
</div>
<ul style={{ color: "#41505d", lineHeight: 1.9, paddingLeft: "1.1rem" }}>
<li>
<Link href="/docs/openicon">Specification</Link>: the shape, eight rules, discovery, the mapping from Lucide,
Tabler, Font Awesome, Nerd Fonts and Simple Icons
</li>
<li>
<a href="https://github.com/profullstack/openicon">profullstack/openicon</a>: the reference set
</li>
<li>
<Link href="/openemoji">OpenEmoji</Link>, the sibling format for emoji
</li>
</ul>
</div>
</SiteShell>
);
}

View file

@ -97,6 +97,7 @@ export const FAMILIES: Family[] = [
s("openrecipe", "OpenRecipe.md", "One Markdown file that is a recipe, with schema.org derived from it and never the reverse"),
s("opensong", "OpenSong", "One plain-text file that is a song: title, style, exclusions and lyrics as the blocks a generator takes, kept beside the audio"),
s("openemoji", "OpenEmoji", "An emoji set as a folder: one file that states coverage, licence and whether a person or a model drew it, and glyphs named by the codepoints they draw"),
s("openicon", "OpenIcon", "An icon set as a folder: every icon named, with aliases, 24x24 currentColor SVGs and a Nerd Font, Unicode and ASCII glyph each, so a terminal draws the best one it can"),
s("openthreat", "OpenThreat", "One file a security tool serves about what it found in the open: public subjects only, secrets never located"),
s("openrental", "OpenRental", "One file an operator serves about the agents and file swarms it rents out: members, metadata and rates through CoinPay", { landing: undefined, status: "draft" }),
s("opensite", "OpenSite", "One record about a page or a site: the card a reader would draw, declared by the site or read from it, kept by an index"),