Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ jobs:
cd site
fail=0
# Local hrefs only: strip anchors and query, map "/" to index.html.
# Routes are extensionless — Pages serves /docs from docs.html — so a
# link resolves if either the literal path or its .html twin exists.
for f in $(find . -name '*.html'); do
grep -oE 'href="/[^"#?]*"' "$f" | sed 's/href="//;s/"//' | sort -u | while read -r href; do
target=".${href}"
case "$href" in */) target=".${href}index.html" ;; esac
if [ ! -e "$target" ]; then
if [ ! -e "$target" ] && [ ! -e "${target}.html" ]; then
echo "::error file=site/${f#./}::broken internal link -> $href"
exit 1
fi
Expand Down
6 changes: 3 additions & 3 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ them carries meaning. `site.css` never names a ground colour again.
**Clay is the third meaning.** tofig's biggest use case is a design Claude
generated, and that journey has two halves. Violet is the tofig/Figma side,
clay is the Claude side, and the handoff is where they meet. It appears on the
Claude section of the landing page and on `/claude.html`, and nowhere else.
Claude section of the landing page and on `/claude`, and nowhere else.
`--c-500` on `--void` measures 5.2:1. White on `--c-600` measures only 4.17:1,
so the interactive fill is `--c-700`, the same correction violet needed.

**On `/claude.html` the handoff runs at page scale.** The atmospheric field
**On `/claude` the handoff runs at page scale.** The atmospheric field
is a background layer on the body rather than a fixed overlay, so it is
measured against the whole document: clay pools where the work starts,
drains through the middle, and violet gathers where the file lands. The
Expand Down Expand Up @@ -118,7 +118,7 @@ White on `--v-500` measures 4.49:1 — one step under AA. Interactive fills
therefore sit on `--v-600` and move *up* to `--v-500` on hover.

**Tint intensity is a share of the gamut, not a chroma number.**
`/claude.html` shifts its whole neutral ramp to clay's side of the wheel,
`/claude` shifts its whole neutral ramp to clay's side of the wheel,
and the first version did it by copying violet's chroma figures across to
hue 38. The page came out brown. The sRGB chroma ceiling is not the same
at every hue: at L 0.20 it is 0.118 at 288 and only 0.063 at 40, so the
Expand Down
2 changes: 1 addition & 1 deletion LISTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Still fully local: it drives your own Chrome, it just isn't sandbox-restricted.

Free and open source, MIT licensed. Built and maintained by acaso.
Site: tofig.aca.so
Claude → Figma walkthrough: tofig.aca.so/claude.html
Claude → Figma walkthrough: tofig.aca.so/claude
Code: github.com/aca-so/tofig
Built on @builder.io/html-to-figma (MIT); bundles React/ReactDOM (MIT).
```
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ No server, no browser extension, no account — just a Figma plugin.
there. Then paste your HTML and hit **Convert**. Reopen it later with right-click →
**Plugins** → **tofig**.

Guides for using it, including the [Claude walkthrough](https://tofig.aca.so/claude.html),
Guides for using it, including the [Claude walkthrough](https://tofig.aca.so/claude),
are at [tofig.aca.so](https://tofig.aca.so). The rest of this README is about building
and running it from source.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"build": "node esbuild.mjs",
"watch": "node esbuild.mjs --watch",
"render": "node bin/tofig-render.mjs",
"site": "python3 -m http.server 8777 --bind 127.0.0.1 --directory site",
"site": "node serve-site.mjs",
"typecheck": "tsc --noEmit -p tsconfig.json",
"lint": "eslint ."
},
Expand Down
63 changes: 63 additions & 0 deletions serve-site.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Local preview for site/, matching how GitHub Pages resolves a request.
//
// It exists because the routes are extensionless: Pages serves /docs from
// docs.html, and `python3 -m http.server` does not, so the nav links that work
// in production 404'd locally. A preview server that disagrees with the deploy
// about what a URL means is worse than no preview server.
//
// node serve-site.mjs [port]
import { createServer } from "node:http";
import { readFile, stat } from "node:fs/promises";
import { extname, join, normalize } from "node:path";

const ROOT = new URL("./site/", import.meta.url).pathname;
const PORT = Number(process.argv[2]) || 8777;

const TYPES = {
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".svg": "image/svg+xml",
".png": "image/png",
".jpg": "image/jpeg",
".webp": "image/webp",
".woff2": "font/woff2",
".xml": "application/xml; charset=utf-8",
".txt": "text/plain; charset=utf-8",
};

const isFile = async (p) => {
try {
return (await stat(p)).isFile();
} catch {
return false;
}
};

// Pages' order: the literal path, then its .html twin, then a directory index.
async function resolve(pathname) {
const rel = normalize(decodeURIComponent(pathname)).replace(/^(\.\.[/\\])+/, "");
const base = join(ROOT, rel);
for (const candidate of [base, `${base}.html`, join(base, "index.html")]) {
if (await isFile(candidate)) return candidate;
}
return null;
}

createServer(async (req, res) => {
const { pathname } = new URL(req.url, `http://localhost:${PORT}`);
const file = await resolve(pathname);
if (!file) {
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
res.end(`404 ${pathname}\n`);
return;
}
res.writeHead(200, {
"content-type": TYPES[extname(file)] ?? "application/octet-stream",
"cache-control": "no-store",
});
res.end(await readFile(file));
}).listen(PORT, "127.0.0.1", () => {
console.log(`tofig.aca.so preview → http://127.0.0.1:${PORT}`);
});
2 changes: 1 addition & 1 deletion site/assets/css/claude.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* ──────────────────────────────────────────────────────────────────
tofig.aca.so/claude.html — Claude to Figma
tofig.aca.so/claude — Claude to Figma

Page-scoped layer on top of tokens.css + site.css. Everything here
is prefixed `cl-` or scoped under `.p-claude`, so nothing leaks
Expand Down
2 changes: 1 addition & 1 deletion site/assets/css/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
--void rather than on it, and the nav wash sits level with it.

They are tokens rather than literals because a page may move its
whole neutral ramp to another hue — /claude.html rides clay's 44
whole neutral ramp to another hue — /claude rides clay's 44
instead of violet's 288 — and a component that hardcodes one hue
turns into a cold rectangle on a warm page. Every sunken surface
takes its ground from here; only the accent painted on top of it
Expand Down
16 changes: 8 additions & 8 deletions site/claude.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Claude to Figma | tofig</title>
<meta name="description" content="Take a design Claude generated and get it into Figma as editable layers. How to export the artifact's HTML, the prompt that makes it convert cleanly, and what to do when the artifact is a live app.">
<link rel="canonical" href="https://tofig.aca.so/claude.html">
<link rel="canonical" href="https://tofig.aca.so/claude">

<meta property="og:type" content="article">
<meta property="og:url" content="https://tofig.aca.so/claude.html">
<meta property="og:url" content="https://tofig.aca.so/claude">
<meta property="og:title" content="Claude to Figma | tofig">
<meta property="og:description" content="Get a Claude design into Figma as real frames, live text and editable vectors. The export, the prompt, and the escape hatch when the artifact is a live app.">
<meta property="og:image" content="https://tofig.aca.so/assets/og.png">
Expand All @@ -34,7 +34,7 @@
"@type": "TechArticle",
"headline": "Claude to Figma: turn a generated design into editable layers",
"description": "How to export a Claude artifact as self-contained HTML and rebuild it in Figma as native, editable layers with tofig.",
"url": "https://tofig.aca.so/claude.html",
"url": "https://tofig.aca.so/claude",
"inLanguage": "en",
"author": { "@type": "Organization", "name": "acaso", "url": "https://aca.so" },
"publisher": { "@type": "Organization", "name": "acaso", "url": "https://aca.so" },
Expand Down Expand Up @@ -78,11 +78,11 @@
<a class="byline" href="https://aca.so" rel="noopener">by <b>acaso</b></a>

<ul class="nav__links">
<li><a href="/claude.html" aria-current="page">Claude to Figma</a></li>
<li><a href="/claude" aria-current="page">Claude to Figma</a></li>
<li><a href="/#how">How it works</a></li>
<li><a href="/#survives">What survives</a></li>
<li><a href="/#renderer">Renderer</a></li>
<li><a href="/docs.html">Docs</a></li>
<li><a href="/docs">Docs</a></li>
</ul>

<div class="nav__tools">
Expand Down Expand Up @@ -532,7 +532,7 @@ <h2 class="display" data-rise>Ship the design,<br>not a <em>picture</em> of it.<
href="https://www.figma.com/community/plugin/1652349254564019815" rel="noopener">
<i class="ph-bold ph-figma-logo" aria-hidden="true"></i> Get it on Figma Community
</a>
<a class="btn btn--ghost btn--lg" href="/docs.html">Read the docs</a>
<a class="btn btn--ghost btn--lg" href="/docs">Read the docs</a>
</div>

<p class="caption install__meta" data-rise>
Expand Down Expand Up @@ -605,11 +605,11 @@ <h3>Renderer CLI</h3>
<div class="foot__col">
<p class="collabel">Product</p>
<a href="https://www.figma.com/community/plugin/1652349254564019815" rel="noopener">Get it on Figma</a>
<a href="/claude.html">Claude to Figma</a>
<a href="/claude">Claude to Figma</a>
<a href="/#how">How it works</a>
<a href="/#survives">What survives</a>
<a href="/#renderer">External renderer</a>
<a href="/docs.html">Documentation</a>
<a href="/docs">Documentation</a>
</div>
<div class="foot__col">
<p class="collabel">Source</p>
Expand Down
14 changes: 7 additions & 7 deletions site/docs.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Documentation | tofig</title>
<meta name="description" content="How to install and use tofig: converting HTML to Figma layers, font substitution, the external renderer, the .tofig.json format, and troubleshooting. An open-source tool by acaso.">
<link rel="canonical" href="https://tofig.aca.so/docs.html">
<link rel="canonical" href="https://tofig.aca.so/docs">
<meta property="og:type" content="article">
<meta property="og:url" content="https://tofig.aca.so/docs.html">
<meta property="og:url" content="https://tofig.aca.so/docs">
<meta property="og:title" content="Documentation | tofig">
<meta property="og:description" content="Everything tofig does, everything it doesn't, and what to do about the difference.">
<meta property="og:image" content="https://tofig.aca.so/assets/og.png">
Expand Down Expand Up @@ -50,11 +50,11 @@
<a class="byline" href="https://aca.so" rel="noopener">by <b>acaso</b></a>

<ul class="nav__links">
<li><a href="/claude.html">Claude to Figma</a></li>
<li><a href="/claude">Claude to Figma</a></li>
<li><a href="/#how">How it works</a></li>
<li><a href="/#survives">What survives</a></li>
<li><a href="/#renderer">Renderer</a></li>
<li><a href="/docs.html" aria-current="page">Docs</a></li>
<li><a href="/docs" aria-current="page">Docs</a></li>
</ul>

<div class="nav__tools">
Expand Down Expand Up @@ -155,7 +155,7 @@ <h2 id="quickstart" data-xn="Text · quickstart">Quick start</h2>
<h2 id="claude" data-xn="Text · claude">Coming from Claude</h2>
<p>The most common route in. Claude generates a design as an artifact, and
you want it in Figma as layers rather than a flat screenshot. The
<a href="/claude.html">full walkthrough is on its own page</a>, including
<a href="/claude">full walkthrough is on its own page</a>, including
the export prompt and what to do when the artifact turns out to be a live
app.</p>
<p>The short version: ask for the design as a single self-contained HTML
Expand Down Expand Up @@ -333,11 +333,11 @@ <h2 id="source" data-xn="Text · source">Build from source</h2>
<div class="foot__col">
<p class="collabel">Product</p>
<a href="https://www.figma.com/community/plugin/1652349254564019815" rel="noopener">Get it on Figma</a>
<a href="/claude.html">Claude to Figma</a>
<a href="/claude">Claude to Figma</a>
<a href="/#how">How it works</a>
<a href="/#survives">What survives</a>
<a href="/#renderer">External renderer</a>
<a href="/docs.html">Documentation</a>
<a href="/docs">Documentation</a>
</div>
<div class="foot__col">
<p class="collabel">Source</p>
Expand Down
12 changes: 6 additions & 6 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
<li><a href="#how">How it works</a></li>
<li><a href="#survives">What survives</a></li>
<li><a href="#renderer">Renderer</a></li>
<li><a href="/docs.html">Docs</a></li>
<li><a href="/docs">Docs</a></li>
</ul>

<div class="nav__tools">
Expand Down Expand Up @@ -238,7 +238,7 @@ <h2 data-xn="Text · h2">Claude designed it.<br><em>Now make it editable.</em></
deck and each slide lands as a <b>real Figma slide</b>.</p>

<div class="chand__actions">
<a class="btn btn--claude btn--lg" href="/claude.html">
<a class="btn btn--claude btn--lg" href="/claude">
Claude to Figma, step by step <i class="ph-bold ph-arrow-right" aria-hidden="true"></i>
</a>
</div>
Expand Down Expand Up @@ -329,7 +329,7 @@ <h3>Frames on the canvas</h3>
<h3>Real Figma slides</h3>
<p>tofig detects decks, including JavaScript-driven ones, and imports each
slide as its own Figma slide, sized and scaled to fit rather than clipped.
This is where a <a href="/claude.html">Claude-generated deck</a> pays off
This is where a <a href="/claude">Claude-generated deck</a> pays off
most: one HTML file in, a whole editable deck out.</p>
</div>
</article>
Expand Down Expand Up @@ -542,7 +542,7 @@ <h2 class="display" data-rise>Put it<br>on your <em>canvas</em>.</h2>
href="https://www.figma.com/community/plugin/1652349254564019815" rel="noopener">
<i class="ph-bold ph-figma-logo" aria-hidden="true"></i> Get it on Figma Community
</a>
<a class="btn btn--ghost btn--lg" href="/docs.html">Read the docs</a>
<a class="btn btn--ghost btn--lg" href="/docs">Read the docs</a>
</div>

<p class="caption install__meta" data-rise>
Expand Down Expand Up @@ -614,11 +614,11 @@ <h3>Renderer CLI</h3>
<div class="foot__col">
<p class="collabel">Product</p>
<a href="https://www.figma.com/community/plugin/1652349254564019815" rel="noopener">Get it on Figma</a>
<a href="/claude.html">Claude to Figma</a>
<a href="/claude">Claude to Figma</a>
<a href="#how">How it works</a>
<a href="#survives">What survives</a>
<a href="#renderer">External renderer</a>
<a href="/docs.html">Documentation</a>
<a href="/docs">Documentation</a>
</div>
<div class="foot__col">
<p class="collabel">Source</p>
Expand Down
4 changes: 2 additions & 2 deletions site/sitemap.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://tofig.aca.so/</loc><priority>1.0</priority></url>
<url><loc>https://tofig.aca.so/claude.html</loc><priority>0.9</priority></url>
<url><loc>https://tofig.aca.so/docs.html</loc><priority>0.8</priority></url>
<url><loc>https://tofig.aca.so/claude</loc><priority>0.9</priority></url>
<url><loc>https://tofig.aca.so/docs</loc><priority>0.8</priority></url>
</urlset>
Loading