mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-02 12:54:03 +00:00
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:
parent
f80488ce92
commit
3883ee7a2c
50 changed files with 423 additions and 0 deletions
248
apps/logicsrc-web/src/app/openicon/page.tsx
Normal file
248
apps/logicsrc-web/src/app/openicon/page.tsx
Normal 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's mail
|
||||
is Font Awesome's envelope is Material'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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue